[pkg-horde] [SCM] Debian Horde Packages repository: chora2 package branch, debian-sid, updated. 6087b59a8a7d68af8bd357d44788b534d60bc52c

Gregory Colpart reg at foulademer.gcolpart.com
Sun May 31 15:31:18 UTC 2009


The following commit has been merged in the debian-sid branch:
commit 9a15604018cfd2b31dde30de7e62a43826baaf1f
Author: Gregory Colpart <reg at foulademer.gcolpart.com>
Date:   Sun May 31 16:06:10 2009 +0200

    merge from upstream

diff --git a/.htaccess b/.htaccess
index 9111f1a..2ace2c4 100644
--- a/.htaccess
+++ b/.htaccess
@@ -1,6 +1,7 @@
 <IfModule mod_rewrite.c>
 	RewriteEngine On
+	RewriteRule   ^([a-z]+).php/(.*)$ $1.php?f=$2 [QSA,L]
 	RewriteCond   %{REQUEST_FILENAME}  !-d
 	RewriteCond   %{REQUEST_FILENAME}  !-f
-	RewriteRule   ^(.*\/.*)$ browse.php\/$1 [QSA]
+	RewriteRule   ^(.*)$ browse.php?f=$1 [QSA]
 </IfModule>
diff --git a/COPYING b/COPYING
index 5a965fb..a6b6756 100644
--- a/COPYING
+++ b/COPYING
@@ -1,7 +1,7 @@
 		    GNU GENERAL PUBLIC LICENSE
 		       Version 2, June 1991
 
- Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ Copyright 1989, 1991 Free Software Foundation, Inc.
      59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  Everyone is permitted to copy and distribute verbatim copies
  of this license document, but changing it is not allowed.
diff --git a/README b/README
index c11d5ee..721b28b 100644
--- a/README
+++ b/README
@@ -1,8 +1,8 @@
 What is Chora?
 ==============
 
-:Last update:   $Date: 2006/07/27 12:23:50 $
-:Revision:      $Revision: 1.11.10.1 $
+:Last update:   $Date: 2008/10/09 17:40:02 $
+:Revision:      $Revision: 1.11.10.2 $
 :Contact:       chora at lists.horde.org
 
 .. contents:: Contents
diff --git a/annotate.php b/annotate.php
index cd1c65b..b1abaed 100644
--- a/annotate.php
+++ b/annotate.php
@@ -1,15 +1,18 @@
 <?php
 /**
- * $Horde: chora/annotate.php,v 1.48.8.5 2007/02/23 03:58:53 selsky Exp $
+ * $Horde: chora/annotate.php,v 1.48.8.7 2009/01/06 15:22:34 jan Exp $
  *
- * Copyright 2000-2007 Anil Madhavapeddy <anil at recoil.org>
+ * Copyright 2000-2009 The Horde Project (http://www.horde.org/)
  *
- * See the enclosed file COPYING for license information (GPL).  If you
+ * See the enclosed file COPYING for license information (GPL). If you
  * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author  Anil Madhavapeddy <avsm at horde.org>
  */
 
 @define('CHORA_BASE', dirname(__FILE__));
 require_once CHORA_BASE . '/lib/base.php';
+require_once 'Horde/Text/Filter.php';
 
 /* Spawn the file object. */
 $fl = &$VC->getFileObject($where, $cache);
@@ -18,14 +21,14 @@ Chora::checkError($fl);
 /* Retrieve the desired revision from the GET variable. */
 $rev = Util::getFormData('rev', '1.1');
 if (!VC_Revision::valid($rev)) {
-    Chora::fatal("Revision $rev not found");
+    Chora::fatal('404 Not Found', "Revision $rev not found");
 }
 
 $ann = &$VC->getAnnotateObject($fl);
 Chora::checkError($lines = $ann->doAnnotate($rev));
 
-$title = sprintf(_("Source Annotation of %s for version %s"), Text::htmlAllSpaces($where), $rev);
-$extraLink = sprintf('<a href="%s">%s</a> <b>|</b> <a href="%s">%s</a>',
+$title = sprintf(_("Source Annotation of %s (revision %s)"), Text::htmlAllSpaces($where), $rev);
+$extraLink = sprintf('<a href="%s">%s</a> | <a href="%s">%s</a>',
                      Chora::url('co', $where, array('r' => $rev)), _("View"),
                      Chora::url('co', $where, array('r' => $rev, 'p' => 1)), _("Download"));
 require CHORA_TEMPLATES . '/common-header.inc';
@@ -36,6 +39,17 @@ require CHORA_TEMPLATES . '/annotate/header.inc';
 $author = '';
 $style = 0;
 
+/* Map of revisions for finding the previous revision to a change. */
+$revMap = $fl->revs;
+sort($revMap);
+$rrevMap = array_flip($revMap);
+
+/* Keep track of any revision we encounter in the following loop. */
+$revList = array();
+
+/* Use this counter so that we can give each tooltip object a unique
+ * id attribute (which we use to set the tooltip text later). */
+$i = 0;
 foreach ($lines as $line) {
     $lineno = $line['lineno'];
     $prevAuthor = $author;
@@ -44,8 +58,16 @@ foreach ($lines as $line) {
         $style = (++$style % 2);
     }
     $rev = $line['rev'];
+    $prev = isset($revMap[$rrevMap[$rev] - 1]) ? $revMap[$rrevMap[$rev] - 1] : null;
+    if (!isset($revList[$rev])) {
+        $revList[$rev] = $fl->logs[$rev];
+    }
+    if (!is_null($prev) && !isset($revList[$prev])) {
+        $revList[$prev] = $fl->logs[$prev];
+    }
     $line = Text::htmlAllSpaces($line['line']);
     include CHORA_TEMPLATES . '/annotate/line.inc';
+    ++$i;
 }
 
 require CHORA_TEMPLATES . '/annotate/footer.inc';
diff --git a/browse.php b/browse.php
index 7a678ae..9293b1c 100644
--- a/browse.php
+++ b/browse.php
@@ -1,25 +1,28 @@
 <?php
 /**
- * $Horde: chora/browse.php,v 1.5.4.6 2007/02/23 03:58:53 selsky Exp $
+ * $Horde: chora/browse.php,v 1.5.4.8 2009/01/06 15:22:34 jan Exp $
  *
- * Copyright 1999-2007 Anil Madhavapeddy <anil at recoil.org>
- * Copyright 1999-2007 Charles Hagenbuch <chuck at horde.org>
+ * Copyright 1999-2009 The Horde Project (http://www.horde.org/)
  *
- * See the enclosed file COPYING for license information (GPL).  If you
+ * See the enclosed file COPYING for license information (GPL). If you
  * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author  Anil Madhavapeddy <anil at recoil.org>
+ * @author  Chuck Hagenbuch <chuck at horde.org>
+ * @package Chora
  */
 
 @define('CHORA_BASE', dirname(__FILE__));
 require_once CHORA_BASE . '/lib/base.php';
-require_once 'Horde/MIME/Magic.php';
-require_once 'Horde/MIME/Viewer.php';
-require_once HORDE_BASE . '/config/mime_drivers.php';
-require_once CHORA_BASE . '/config/mime_drivers.php';
+
+if (!$atdir && !$VC->isFile($fullname)) {
+    Chora::fatal('404 Not Found', "$where: no such file or directory");
+}
 
 if ($atdir) {
     Chora::checkError($dir = $VC->queryDir($where));
 
-    $atticFlags = (boolean)$acts['sa'];
+    $atticFlags = (bool)$acts['sa'];
     Chora::checkError($dir->browseDir($cache, true, $atticFlags));
     $dir->applySort($acts['sbt'], $acts['ord']);
     Chora::checkError($dirList = &$dir->queryDirList());
@@ -29,27 +32,27 @@ if ($atdir) {
     if ($where == '') {
         $title = $conf['options']['introTitle'];
     } else {
-        $title = sprintf(_("Source Directory of /%s"), Text::htmlallSpaces($where));
+        $title = sprintf(_("Source Directory of /%s"), $where);
     }
 
-    if (is_a($VC, 'VC_svn')) {
-        // Showing deleted files is not supported in svn.
-        $extraLink = '';
-    } else {
-        if ($acts['sa']) {
-            $extraLink = Horde::widget(Chora::url('', $where . '/', array('sa' => 0)), _("Hide Deleted Files"), 'widget', '', '', _("Hide _Deleted Files"));
-        } else {
-            $extraLink = Horde::widget(Chora::url('', $where . '/', array('sa' => 1)), _("Show Deleted Files"), 'widget', '', '', _("Show _Deleted Files"));
-        }
+    $extraLink = '';
+    if (is_a($VC, 'VC_cvs')) {
+        $extraLink = Horde::widget(Chora::url(
+            '', $where . '/', array('sa' => ($acts['sa'] ? 0 : 1))),
+            $acts['sa'] ? _("Hide Deleted Files") : _("Show Deleted Files"),
+            'widget', '', '',
+            $acts['sa'] ? _("Hide _Deleted Files") : _("Show _Deleted Files")
+        );
     }
 
-    require CHORA_TEMPLATES . '/common-header.inc';
-    require CHORA_TEMPLATES . '/menu.inc';
-    require CHORA_TEMPLATES . '/headerbar.inc';
+    $umap = array(
+        'age' => VC_SORT_AGE,
+        'rev' => VC_SORT_REV,
+        'name' => VC_SORT_NAME,
+        'author' => VC_SORT_AUTHOR
+    );
 
     foreach (array('age', 'rev', 'name', 'author') as $u) {
-        $umap = array('age' => VC_SORT_AGE, 'rev' => VC_SORT_REV,
-                      'name' => VC_SORT_NAME, 'author' => VC_SORT_AUTHOR);
         $arg = array('sbt' => $umap[$u]);
         if ($acts['sbt'] == $umap[$u]) {
             $arg['ord'] = !$acts['ord'];
@@ -59,186 +62,174 @@ if ($atdir) {
 
     /* Print out the directory header. */
     $printAllCols = count($fileList);
+
+    Horde::addScriptFile('prototype.js', 'chora', true);
+    Horde::addScriptFile('tables.js', 'chora', true);
+    require CHORA_TEMPLATES . '/common-header.inc';
+    require CHORA_TEMPLATES . '/menu.inc';
+    require CHORA_TEMPLATES . '/headerbar.inc';
     require CHORA_TEMPLATES . '/directory/header.inc';
 
     /* Unless we're at the top, display the 'back' bar. */
-    $dirrow = 1;
     if ($where != '') {
-        $dirrow = ++$dirrow % 2;
         $url = Chora::url('', preg_replace('|[^/]+$|', '', $where));
         require CHORA_TEMPLATES . '/directory/back.inc';
     }
 
     /* Display all the directories first. */
-    foreach ($dirList as $currentDir) {
-        if ($conf['hide_restricted'] && Chora::isRestricted($currentDir)) {
-            continue;
+    if ($dirList) {
+        echo '<tbody>';
+        foreach ($dirList as $currentDir) {
+            if ($conf['hide_restricted'] && Chora::isRestricted($currentDir)) {
+                continue;
+            }
+            $url = Chora::url('', "$where/$currentDir/");
+            $currDir = Text::htmlAllSpaces($currentDir);
+            require CHORA_TEMPLATES . '/directory/dir.inc';
         }
-        $dirrow = ++$dirrow % 2;
-        $url = Chora::url('', "$where/$currentDir/");
-        $currDir = Text::htmlAllSpaces($currentDir);
-        require CHORA_TEMPLATES . '/directory/dir.inc';
+        echo '</tbody>';
     }
 
     /* Display all of the files in this directory */
-    foreach ($fileList as $currFile) {
-        if ($conf['hide_restricted'] && Chora::isRestricted($currFile->queryName())) {
-            continue;
-        }
-        $dirrow = ++$dirrow % 2;
-        $lg = $currFile->queryLastLog();
-        if (is_a($lg, 'PEAR_Error')) {
-            continue;
-        }
-        $realname = $currFile->queryName();
-        $mimeType = MIME_Magic::filenameToMIME($realname);
-
-        $icon = MIME_Viewer::getIcon($mimeType);
-
-        $aid = $lg->queryAuthor();
-        $author = Chora::showAuthorName($aid);
-        $head = $currFile->queryHead();
-        $date = $lg->queryDate();
-        $log  = $lg->queryLog();
-        $attic = $currFile->isAtticFile();
-        $fileName = $where . ($attic ? '/' . 'Attic' : '') . '/' . $realname;
-        $name = Text::htmlAllSpaces($realname);
-        $url = Chora::url('', $fileName);
-        $readableDate = Chora::readableTime($date);
-        if ($log) {
-            $shortLog = str_replace("\n", ' ',
-                trim(substr($log, 0, $conf['options']['shortLogLength'] - 1)));
-            if (strlen($log) > 80) {
-                $shortLog .= "...";
+    if ($fileList) {
+        require_once 'Horde/MIME/Magic.php';
+        require_once 'Horde/MIME/Viewer.php';
+        if (is_callable(array('Horde', 'loadConfiguration'))) {
+            $result = Horde::loadConfiguration('mime_drivers.php', array('mime_drivers', 'mime_drivers_map'), 'horde');
+            extract($result);
+            $result = Horde::loadConfiguration('mime_drivers.php', array('mime_drivers', 'mime_drivers_map'), 'chora');
+            require_once 'Horde/Array.php';
+            if (isset($result['mime_drivers'])) {
+                $mime_drivers = Horde_Array::array_merge_recursive_overwrite($mime_drivers, $result['mime_drivers']);
+            }
+            if (isset($result['mime_drivers_map'])) {
+                $mime_drivers_map = Horde_Array::array_merge_recursive_overwrite($mime_drivers_map, $result['mime_drivers_map']);
             }
+        } else {
+            require_once HORDE_BASE . '/config/mime_drivers.php';
+            require_once CHORA_BASE . '/config/mime_drivers.php';
         }
-        require CHORA_TEMPLATES.'/directory/file.inc';
-    }
-    /* Display the options control panel at the bottom */
-    $formwhere = $scriptName . '/' . $where;
-
-    require CHORA_TEMPLATES . '/directory/footer.inc';
-    require $registry->get('templates', 'horde') . '/common-footer.inc';
-
-} elseif ($VC->isFile($fullname)) {
-    $fl = &$VC->getFileObject($where, $cache);
-    Chora::checkError($fl);
-    $title = sprintf(_("Source Log for %s"), Text::htmlAllSpaces($where));
-    $upwhere = preg_replace('|[^/]+$|', '', $where);
-    $onb = Util::getFormData('onb', 0);
-    $r1 = Util::getFormData('r1', 0);
-
-    $isBranch = isset($onb) && isset($fl->branches[$onb]) ? $fl->branches[$onb] : '';
-    $extraLink = Chora::getFileViews();
-
-    require CHORA_TEMPLATES . '/common-header.inc';
-    require CHORA_TEMPLATES . '/menu.inc';
-    require CHORA_TEMPLATES . '/headerbar.inc';
 
-    $mimeType = MIME_Magic::filenameToMIME($fullname);
-    $defaultTextPlain = ($mimeType == 'text/plain');
-
-    foreach ($fl->logs as $lg) {
-        $rev = $lg->rev;
-
-        /* Are we sticking only to one branch ? */
-        if ($onb && VC_Revision::valid($onb)) {
-
-            /* If so, if we are on the branch itself, let it through */
-            if (substr($rev, 0, strlen($onb)) != $onb) {
-
-                /* We are not on the branch, see if we are on a trunk
-                 * branch below the branch */
-                $baseRev = VC_Revision::strip($onb, 1);
-
-                /* Check we are at the same level of branching or less */
-                if (substr_count($rev, '.') <= substr_count($baseRev, '.')) {
-                    /* If we are at the same level, and the revision is
-                     * less, then let the revision through, since it was
-                     * committed before the branch actually took place
-                     */
-                    if (VC_Revision::cmp($rev, $baseRev) > 0) {
-                        continue;
-                    }
-                } else {
-                    continue;
+        echo '<tbody>';
+        foreach ($fileList as $currFile) {
+            if ($conf['hide_restricted'] &&
+                Chora::isRestricted($currFile->queryName())) {
+                continue;
+            }
+            $lg = $currFile->queryLastLog();
+            if (is_a($lg, 'PEAR_Error')) {
+                continue;
+            }
+            $realname = $currFile->queryName();
+            $mimeType = MIME_Magic::filenameToMIME($realname);
+
+            $icon = MIME_Viewer::getIcon($mimeType);
+
+            $author = Chora::showAuthorName($lg->queryAuthor());
+            $head = $currFile->queryHead();
+            $date = $lg->queryDate();
+            $log = $lg->queryLog();
+            $attic = $currFile->isAtticFile();
+            $fileName = $where . ($attic ? '/' . 'Attic' : '') . '/' . $realname;
+            $name = Text::htmlAllSpaces($realname);
+            $url = Chora::url('', $fileName);
+            $readableDate = Chora::readableTime($date);
+            if ($log) {
+                $shortLog = str_replace("\n", ' ',
+                    trim(substr($log, 0, $conf['options']['shortLogLength'] - 1)));
+                if (strlen($log) > 80) {
+                    $shortLog .= '...';
                 }
             }
+            require CHORA_TEMPLATES . '/directory/file.inc';
         }
+        echo '</tbody>';
+    }
 
-        $textURL = Chora::url('co', $where, array('r' => $rev));
-        $commitDate = strftime('%c', $lg->date);
-        $readableDate = Chora::readableTime($lg->date, true);
-
-        $aid = $lg->queryAuthor();
-        $author = Chora::showAuthorName($aid, true);
+    echo '</table>';
+    require $registry->get('templates', 'horde') . '/common-footer.inc';
+    exit;
+}
 
-        if (!empty($lg->tags)) {
-            $commitTags = implode(', ', $lg->tags);
-        } else {
-            $commitTags = '';
-        }
+/* Showing a file. */
+$fl = &$VC->getFileObject($where, $cache);
+Chora::checkError($fl);
+$title = sprintf(_("Revisions for %s"), $where);
+$onb = Util::getFormData('onb', 0);
+if (VC_Revision::valid($onb)) {
+    $onb_len = strlen($onb);
+    $onb_base = VC_Revision::strip($onb, 1);
+    $onb_parents = array();
+    while (substr_count($onb_base, '.')) {
+        $onb_parents[$onb_base] = true;
+        $onb_base = VC_Revision::strip($onb_base, 1);
+    }
+} else {
+    $onb = null;
+}
 
-        $branchPointsArr = array();
-        foreach ($lg->querySymbolicBranches() as $symb => $bra) {
-            $branchPointsArr[] = '<a href="' . Chora::url('', $where, array('onb' => $bra)) . '">'. $symb . '</a>';
-        }
+$extraLink = Chora::getFileViews();
+$first = end($fl->logs);
+$diffValueLeft = $first->queryRevision();
+$diffValueRight = $fl->queryRevision();
 
-        /* Calculate the current branch name and revision. */
-        $branchPoints = implode(' , ', $branchPointsArr);
-        $branchRev = VC_Revision::strip($rev, 1);
-        if (@isset($fl->branches[$branchRev])) {
-            $branchName = $fl->branches[$branchRev];
-        } else {
-            $branchName = '';
-        }
+$sel = '';
+foreach ($fl->symrev as $sm => $rv) {
+    $sel .= '<option value="' . $rv . '">' . $sm . '</option>';
+}
 
-        if ($prevRevision = $fl->queryPreviousRevision($lg->queryRevision())) {
-            $changedLines = $lg->queryChangedLines();
-            $coloredDiffURL = Chora::url('diff', $where, array('r1' => $prevRevision, 'r2' => $rev, 'ty' => 'h'));
-            $longDiffURL = Chora::url('diff', $where, array('r1' => $prevRevision, 'r2' => $rev, 'ty' => 'h', 'num' => 10));
-            $uniDiffURL = Chora::url('diff', $where, array('r1' => $prevRevision, 'r2' => $rev, 'ty' => 'u'));
-            $nowsDiffURL = Chora::url('diff', $where, array('ws' => 0, 'r1' => $prevRevision, 'r2' => $rev, 'ty' => 'u'));
-        }
+$selAllBranches = '';
+foreach ($fl->branches as $num => $sym) {
+    $selAllBranches .= '<option value="' . $num . '"' . ($num == $onb ? ' selected="selected"' : '') . '>' . $sym . '</option>';
+}
 
-        $manyRevisions = !($fl->queryRevision() === '1.1');
-        if ($manyRevisions) {
-            $selURL = Chora::url('', $where, array('r1' => $rev, 'onb' => $onb));
-            if (!empty($r1)) {
-                $selColoredDiffURL = Chora::url('diff', $where, array('r1' => $r1, 'r2' => $rev, 'ty' => 'h'));
-                $selLongDiffURL = Chora::url('diff', $where, array('r1' => $r1, 'r2' => $rev, 'ty' => 'h', 'num' => 10));
-                $selUniDiffURL = Chora::url('diff', $where, array('r1' => $r1, 'r2' => $rev, 'ty' => 'u'));
-                $selNowsDiffURL = Chora::url('diff', $where, array('ws' => 0, 'r1' => $r1, 'r2' => $rev, 'ty' => 'u'));
+Horde::addScriptFile('prototype.js', 'chora', true);
+Horde::addScriptFile('tables.js', 'chora', true);
+Horde::addScriptFile('QuickFinder.js', 'chora', true);
+Horde::addScriptFile('revlog.js', 'chora', true);
+require CHORA_TEMPLATES . '/common-header.inc';
+require CHORA_TEMPLATES . '/menu.inc';
+require CHORA_TEMPLATES . '/headerbar.inc';
+require CHORA_TEMPLATES . '/log/header.inc';
+
+$i = 0;
+foreach ($fl->logs as $lg) {
+    $rev = $lg->queryRevision();
+    list($branchName, $branchRev) = Chora::getBranch($fl, $rev);
+
+    /* Are we tracking a branch? */
+    if ($onb) {
+        /* If we are on the branch itself, let it through */
+        if (substr($rev, 0, $onb_len) != $onb) {
+            /* If the revision is on one of the parent branches, and
+             * is before the branch was made, let it through. */
+            if ((!isset($onb_parents[$branchRev]) && substr_count($rev, '.') > 1) ||
+                VC_Revision::cmp($rev, $onb) > 0) {
+                continue;
             }
         }
+    }
 
-        $logMessage = Chora::formatLogMessage($lg->log);
+    $textUrl = Chora::url('co', $where, array('r' => $rev));
+    $commitDate = Chora::formatDate($lg->queryDate());
+    $readableDate = Chora::readableTime($lg->queryDate(), true);
 
-        if ($r1 === $rev) {
-            $bgclass = 'diff-selected';
-        } else {
-            $bgclass = 'control';
-        }
+    $author = Chora::showAuthorName($lg->queryAuthor(), true);
+    $tags = Chora::getTags($lg, $where);
 
-        require CHORA_TEMPLATES . '/log/rev.inc';
+    if ($prevRevision = $fl->queryPreviousRevision($lg->queryRevision())) {
+        $diffUrl = Chora::url('diff', $where, array('r1' => $prevRevision, 'r2' => $rev));
+    } else {
+        $diffUrl = '';
     }
 
-    $first = end($fl->logs);
-    $diffValueLeft  = $first->rev;
-    $diffValueRight = $fl->queryRevision();
+    $logMessage = Chora::formatLogMessage($lg->queryLog());
 
-    $sel = '';
-    foreach ($fl->symrev as $sm => $rv) {
-        $sel .= '<option value="' . $rv . '">' . $sm . '</option>';
-    }
+    require CHORA_TEMPLATES . '/log/rev.inc';
 
-    $selAllBranches = '';
-    foreach ($fl->branches as $num => $sym) {
-        $selAllBranches .= '<option value="' . $num . '">' . $sym . '</option>';
+    if ($i++ > 100 && !Util::getFormData('all')) {
+        break;
     }
-
-    require CHORA_TEMPLATES . '/log/request.inc';
-    require $registry->get('templates', 'horde') . '/common-footer.inc';
-} else {
-    Chora::fatal("$where: no such file or directory");
 }
+require CHORA_TEMPLATES . '/log/footer.inc';
+require $registry->get('templates', 'horde') . '/common-footer.inc';
diff --git a/co.php b/co.php
index 4c4b699..63d3cc3 100644
--- a/co.php
+++ b/co.php
@@ -1,11 +1,13 @@
 <?php
 /**
- * $Horde: chora/co.php,v 1.59.2.5 2007/02/23 03:58:53 selsky Exp $
+ * $Horde: chora/co.php,v 1.59.2.7 2009/01/06 15:22:34 jan Exp $
  *
- * Copyright 2000-2007 Anil Madhavapeddy <anil at recoil.org>
+ * Copyright 2000-2009 The Horde Project (http://www.horde.org/)
  *
  * See the enclosed file COPYING for license information (GPL). If you
  * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author  Anil Madhavapeddy <avsm at horde.org>
  */
 
 @define('CHORA_BASE', dirname(__FILE__));
@@ -13,8 +15,21 @@ require_once CHORA_BASE . '/lib/base.php';
 require_once 'Horde/MIME/Part.php';
 require_once 'Horde/MIME/Magic.php';
 require_once 'Horde/MIME/Viewer.php';
-require_once HORDE_BASE . '/config/mime_drivers.php';
-require_once CHORA_BASE . '/config/mime_drivers.php';
+if (is_callable(array('Horde', 'loadConfiguration'))) {
+    $result = Horde::loadConfiguration('mime_drivers.php', array('mime_drivers', 'mime_drivers_map'), 'horde');
+    extract($result);
+    $result = Horde::loadConfiguration('mime_drivers.php', array('mime_drivers', 'mime_drivers_map'), 'chora');
+    require_once 'Horde/Array.php';
+    if (isset($result['mime_drivers'])) {
+        $mime_drivers = Horde_Array::array_merge_recursive_overwrite($mime_drivers, $result['mime_drivers']);
+    }
+    if (isset($result['mime_drivers_map'])) {
+        $mime_drivers_map = Horde_Array::array_merge_recursive_overwrite($mime_drivers_map, $result['mime_drivers_map']);
+    }
+} else {
+    require_once HORDE_BASE . '/config/mime_drivers.php';
+    require_once CHORA_BASE . '/config/mime_drivers.php';
+}
 
 /* If we know we're at a directory, just go to browse.php. */
 if ($atdir) {
@@ -43,7 +58,7 @@ if ($r == 0) {
 
 /* Is this a valid revision being requested? */
 if (!VC_Revision::valid($r)) {
-    Chora::fatal("Revision Not Found: $r is not a valid RCS revision number");
+    Chora::fatal('404 Not Found', "Revision Not Found: $r is not a valid revision number");
 }
 
 /* Retrieve the actual checkout. */
@@ -61,87 +76,52 @@ Chora::checkError($checkOut);
 
 if (!$plain) {
     /* Pretty-print the checked out copy */
-    $pretty = &Chora::pretty($mime_type, $checkOut);
-
-    $type = $pretty->getType();
-    if ((strpos($type, 'text/html') !== false || strpos($type, 'text/plain') !== false)
-        && $pretty->canDisplayInline()) {
-
-        $title = sprintf(_("Checkout of %s (revision %s)"), basename($fullname), $r);
-        $extraLink = sprintf('<a href="%s">%s</a> <b>|</b> <a href="%s">%s</a>',
-                             Chora::url('annotate', $where, array('rev' => $r)), _("Annotate"),
-                             Chora::url('co', $where, array('r' => $r, 'p' => 1)), _("Download"));
-
-        /* Make sure this revision exists. */
-        if (empty($file->logs[$r])) {
-            Chora::fatal(sprintf(_("Revision %s for file %s not found."), $r, $file));
-        }
-
-        /* Get this revision's attributes in printable form. */
-        $log = $file->logs[$r];
-        $commitDate = strftime('%c', $log->date);
-        $readableDate = Chora::readableTime($log->date, true);
-
-        $aid = $log->queryAuthor();
-        $author = Chora::showAuthorName($aid, true);
-
-        if (!empty($log->tags)) {
-            $commitTags = implode(', ', $log->tags);
-        } else {
-            $commitTags = '';
-        }
-
-        $branchPointsArr = array();
-        foreach ($log->querySymbolicBranches() as $symb => $bra) {
-            $branchPointsArr[] = '<a href="' . Chora::url('browse', $where, array('onb' => $bra)) . '">'. $symb . '</a>';
-        }
-
-        /* Calculate the current branch name and revision. */
-        $branchPoints = implode(' , ', $branchPointsArr);
-        $branchRev = VC_Revision::strip($r, 1);
-        if (@isset($fl->branches[$branchRev])) {
-            $branchName = $fl->branches[$branchRev];
-        } else {
-            $branchName = '';
-        }
-
-        if ($prevRevision = VC_Revision::prev($log->queryRevision())) {
-            $changedLines = $log->queryChangedLines();
-        }
-
-        $log_print = Chora::formatLogMessage($log->queryLog());
-        $i = 0;
-
-        require CHORA_TEMPLATES . '/common-header.inc';
-        require CHORA_TEMPLATES . '/menu.inc';
-        require CHORA_TEMPLATES . '/headerbar.inc';
-        require CHORA_TEMPLATES . '/checkout/checkout.inc';
-        require $registry->get('templates', 'horde') . '/common-footer.inc';
-    } else {
-        header('Content-Type: ' . $pretty->getType());
-        echo $pretty->render();
-    }
+    $pretty = Chora::pretty($mime_type, $checkOut);
+
+    /* Get this revision's attributes in printable form. */
+    $log = $file->logs[$r];
+
+    $title = sprintf(_("%s Revision %s (%s ago)"),
+                     basename($fullname),
+                     $r,
+                     Chora::readableTime($log->date, true));
+    $extraLink = sprintf('<a href="%s">%s</a> | <a href="%s">%s</a>',
+                         Chora::url('annotate', $where, array('rev' => $r)), _("Annotate"),
+                         Chora::url('co', $where, array('r' => $r, 'p' => 1)), _("Download"));
+
+    $tags = Chora::getTags($log, $where);
+    list($branchName,) = Chora::getBranch($file, $r);
+
+    $log_print = Chora::formatLogMessage($log->queryLog());
+    $author = Chora::showAuthorName($log->queryAuthor(), true);
+
+    Horde::addScriptFile('prototype.js', 'chora', true);
+    Horde::addScriptFile('stripe.js', 'chora', true);
+    require CHORA_TEMPLATES . '/common-header.inc';
+    require CHORA_TEMPLATES . '/menu.inc';
+    require CHORA_TEMPLATES . '/headerbar.inc';
+    require CHORA_TEMPLATES . '/checkout/checkout.inc';
+    require $registry->get('templates', 'horde') . '/common-footer.inc';
     exit;
-} else {
-    /* Download the file. */
-
-    // Get data.
-    $content = '';
-    while ($line = fgets($checkOut)) {
-        $content .= $line;
-    }
-    @fclose($checkOut);
+}
 
-    // Get name.
-    $filename = $file->queryName();
-    if ($browser->getBrowser() == 'opera') {
-        $filename = strtr($filename, ' ', '_');
-    }
+/* Download the file. */
 
-    // Send headers.
-    $browser->downloadHeaders($filename, $mime_type, false, strlen($content));
+// Get data.
+$content = '';
+while ($line = fgets($checkOut)) {
+    $content .= $line;
+}
+ at fclose($checkOut);
 
-    // Send data.
-    echo $content;
-    exit;
+// Get name.
+$filename = $file->queryName();
+if ($browser->getBrowser() == 'opera') {
+    $filename = strtr($filename, ' ', '_');
 }
+
+// Send headers.
+$browser->downloadHeaders($filename, $mime_type, false, strlen($content));
+
+// Send data.
+echo $content;
diff --git a/config/conf.xml b/config/conf.xml
index 0e097f5..493ba40 100644
--- a/config/conf.xml
+++ b/config/conf.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0"?>
-<!-- $Horde: chora/config/conf.xml,v 1.8.10.2 2005/09/22 12:56:37 jan Exp $ -->
+<!-- $Horde: chora/config/conf.xml,v 1.8.10.3 2008/10/09 17:40:03 jan Exp $ -->
 <configuration>
 
  <configtab name="paths" desc="Paths and Locations">
@@ -17,6 +17,12 @@
    <configstring name="diff" desc="diff">/usr/bin/diff</configstring>
    <configstring name="svn" desc="svn">/usr/bin/svn</configstring>
 
+   <configstring name="svn_home" required="false" desc="Home directory for
+   execution of svn binary (specified via --config-dir to svn). This directory
+   must at least be readable to the webserver, otherwise svn will not work.
+   If you leave this empty, the system's temporary directory will be used.">
+   </configstring>
+
    <configstring name="cvsps" required="false" desc="If you have cvsps
    installed, we can generate patchset information. You need at least version
    2.0b6 of cvsps. Path to the cvsps executable, e.g. /usr/local/bin/cvsps"/>
@@ -39,11 +45,11 @@
    expression, or an array of regexps, which if matched, will link a string to
    a ticket-tracking/bug-tracking system. The replacement is the second
    argument to preg_replace(), so you can backreference anything you match in
-   the matching regexp. Example: &lt;code&gt;&lt;nobr&gt;'|bug:?
-   #?(\d+)|i'&lt;/nobr&gt;&lt;/code&gt;"/>
+   the matching regexp. Example: &lt;code class=&quot;nowrap&quot;&gt;'|bug:?
+   #?(\d+)|i'&lt;/code&gt;"/>
    <configstring name="replacement" required="false" desc="Replacement
-   string. Example: &lt;code&gt;&lt;nobr&gt;'&amp;lt;a
-   href=&quot;http://bugs.example.com/show_bug.cgi?id=\1&quot;&amp;gt;\0&amp;lt;/a&amp;gt;'&lt;/nobr&gt;&lt;/code&gt;"/>
+   string. Example: &lt;code class=&quot;nowrap&quot;&gt;'&amp;lt;a
+   href=&quot;http://bugs.example.com/show_bug.cgi?id=\1&quot;&amp;gt;\0&amp;lt;/a&amp;gt;'&lt;/code&gt;"/>
   </configsection>
  </configtab>
 
@@ -69,26 +75,32 @@
      <value desc="sort by author name">VC_SORT_AUTHOR</value>
     </values>
    </configenum>
-   <configenum name="urls" desc="Does your web server support the PATH_INFO
-   method of passing URL data (some Windows web servers do not)?  If this
-   option is unchecked, pathnames will be propagated using a GET variable
-   instead.">get
+   <configenum name="urls" desc="Does your web server support
+   mod_rewrite?  If so, we can generate &quot;pretty&quot; URLs. If
+   not, pathnames will be propagated using a GET variable instead.">get
     <values>
      <value desc="GET (will always work)">get</value>
-     <value desc="PATH_INFO (somewhat prettier URLs)">path_info</value>
-     <value desc="mod_rewrite (prettiest, shortest URLs)">rewrite</value>
+     <value desc="mod_rewrite (pretty, shorter URLs)">rewrite</value>
     </values>
    </configenum>
   </configsection>
 
   <configlist name="restrictions" required="false" desc="If you wish to protect
-   a file pattern on a global basis (i.e. across all sourceroots defined in
-   sourceroots.php) list the perl-style regex file patterns in this array. For
-   example: &lt;code&gt;&lt;nobr&gt;'^/?CVSROOT'&lt;/nobr&gt;&lt;/code&gt;"/>
+  a file pattern on a global basis (i.e. across all sourceroots defined in
+  sourceroots.php) list the perl-style regex file patterns in this array. For
+  example: &lt;code class=&quot;nowrap&quot;&gt;'^/?CVSROOT'&lt;/code&gt;"/>
 
   <configboolean name="hide_restricted" desc="If you wish to hide restricted
-  files, check this option, and restricted files will not be
-  displayed.">true</configboolean>
+  files from listings as well as protect their contents, check this option, and
+  restricted files will not be displayed in directory
+  lists.">true</configboolean>
+
+  <configenum name="filename_linkto" desc="When clicking on a file name in the browse view, should we show the revision log, or display the most recent revision? The opposite action will be available when clicking on the revision number.">revlog
+   <values>
+    <value desc="Revision Log">revlog</value>
+    <value desc="Most recent version">checkout</value>
+   </values>
+  </configenum>
  </configtab>
 
  <configtab name="menu" desc="Menu Settings">
diff --git a/config/cvsgraph.conf.dist b/config/cvsgraph.conf.dist
index c324b7d..318f872 100644
--- a/config/cvsgraph.conf.dist
+++ b/config/cvsgraph.conf.dist
@@ -1,6 +1,6 @@
 # CvsGraph configuration
 #
-# $Horde: chora/config/cvsgraph.conf.dist,v 1.1 2003/06/10 01:29:50 mikec Exp $
+# $Horde: chora/config/cvsgraph.conf.dist,v 1.1.12.1 2008/10/09 17:40:03 jan Exp $
 #
 # - Empty lines and whitespace are ignored.
 #
@@ -87,11 +87,11 @@ date_format     = "%d-%b-%Y %H:%M:%S";
 box_shadow      = true;
 
 tag_font        = medium;
-tag_color       = "#007000";
+tag_color       = "#333399";
 
 rev_font        = giant;
 rev_color       = "#000000";
-rev_bgcolor     = "#f0f0f0";
+rev_bgcolor     = "#e9e9e9";
 rev_separator   = 1;
 rev_minline     = 15;
 rev_maxline     = 30;
@@ -101,7 +101,7 @@ rev_tspace      = 3;
 rev_bspace      = 3;
 rev_text        = "%d\n%a, %s"; # or "%d" for just the date
 rev_text_font   = tiny;
-rev_text_color  = "#500020";
+rev_text_color  = "#000000";
 
 # branch_font <number>
 #   The font of the number and tags
@@ -113,9 +113,9 @@ rev_text_color  = "#500020";
 #   Exterior spacing
 # branch_connect <number>
 #   Length of the vertical connector
-branch_font     = medium;
-branch_color    = "#0000c0";
-branch_bgcolor  = "#ffffc0";
+branch_font     = giant;
+branch_color    = "#333399";
+branch_bgcolor  = "#ffffff";
 branch_lspace   = 5;
 branch_rspace   = 5;
 branch_tspace   = 3;
@@ -134,12 +134,12 @@ branch_connect  = 8;
 #   1 = center
 #   2 = right
 # title_color <color>
-title           = "%C: %p%F\nRevisions: %r, Branches: %b";
+title           = "%p%F\nRevisions: %r, Branches: %b";
 title_x         = 10;
 title_y         = 5;
 title_font      = small;
 title_align     = left;
-title_color     = "#800000";
+title_color     = "#000000";
 
 # Margins of the image
 # Note: the title is outside the margin
diff --git a/config/longIntro.txt.dist b/config/longIntro.txt.dist
index d3d6203..ab3a003 100644
--- a/config/longIntro.txt.dist
+++ b/config/longIntro.txt.dist
@@ -9,5 +9,5 @@ display diffs between arbitrary revisions.
 </p>
 <p>
 More information on accessing the Horde CVS repository is available here:
-<a href="http://horde.org/source/">http://horde.org/source/</a>
+<a href="http://www.horde.org/source/">http://www.horde.org/source/</a>
 </p>
diff --git a/config/mime_drivers.php.dist b/config/mime_drivers.php.dist
index 01aa690..a1aa6ed 100644
--- a/config/mime_drivers.php.dist
+++ b/config/mime_drivers.php.dist
@@ -1,7 +1,7 @@
 <?php
 
 /**
- * $Horde: chora/config/mime_drivers.php.dist,v 1.5 2004/10/20 05:35:03 slusarz Exp $
+ * $Horde: chora/config/mime_drivers.php.dist,v 1.5.10.1 2008/10/09 17:40:03 jan Exp $
  *
  * Decide which output drivers you want to activate for the Chora application.
  *
diff --git a/config/prefs.php.dist b/config/prefs.php.dist
index 010e519..3a84b35 100644
--- a/config/prefs.php.dist
+++ b/config/prefs.php.dist
@@ -1,6 +1,6 @@
 <?php
 /**
- * $Horde: chora/config/prefs.php.dist,v 1.3 2003/11/18 20:28:11 slusarz Exp $
+ * $Horde: chora/config/prefs.php.dist,v 1.3.12.1 2008/10/09 17:40:03 jan Exp $
  *
  * See horde/config/prefs.php for documentation on the structure of this file.
  */
diff --git a/config/sourceroots.php.dist b/config/sourceroots.php.dist
index 9cf3bf3..febe066 100644
--- a/config/sourceroots.php.dist
+++ b/config/sourceroots.php.dist
@@ -1,6 +1,6 @@
 <?php
 /*
- * $Horde: chora/config/sourceroots.php.dist,v 1.2 2003/08/27 14:08:21 chuck Exp $
+ * $Horde: chora/config/sourceroots.php.dist,v 1.2.12.2 2008/10/09 17:40:03 jan Exp $
  *
  * This file contains all the configuration information for the
  * various CVS repositories that you wish to display. You should have
@@ -11,7 +11,7 @@
  * 'name'     [M] : Short name for the repository.
  *
  * 'location' [M] : Location on the filesystem of the repository.
- * 
+ *
  * 'title'    [M] : Long title for the repository.
  *
  * 'type'     [M] : Indicates software used, either 'cvs' or 'svn'.
@@ -29,6 +29,12 @@
  *
  * 'restrictions' : Array of perl-style regular expressions for those files
  *                  whose contents should be protected and not displayed.
+ *
+ * 'username'     : Username to use for the repository (only for Subversion
+ *                  repositories that require authentication).
+ *
+ * 'password'     : Password to use for the repository (only for Subversion
+ *                  repositories that require authentication).
  */
 
 $sourceroots['php4'] = array(
diff --git a/cvs.php b/cvs.php
index 58e3be0..8e6ca5b 100644
--- a/cvs.php
+++ b/cvs.php
@@ -1,12 +1,13 @@
 <?php
 /**
- * $Horde: chora/cvs.php,v 1.180.10.4 2007/01/02 13:54:04 jan Exp $
+ * $Horde: chora/cvs.php,v 1.180.10.6 2009/01/06 15:22:34 jan Exp $
  *
- * Copyright 1999-2007 Anil Madhavapeddy <anil at recoil.org>
- * Copyright 1999-2007 Charles Hagenbuch <chuck at horde.org>
+ * Copyright 1999-2009 The Horde Project (http://www.horde.org/)
  *
- * See the enclosed file COPYING for license information (GPL).  If you
+ * See the enclosed file COPYING for license information (GPL). If you
  * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @package Chora
  */
 
 // The functionality in this script has moved to browse.php. This file
diff --git a/cvsgraph.php b/cvsgraph.php
index 3723089..bfdc6e6 100644
--- a/cvsgraph.php
+++ b/cvsgraph.php
@@ -1,14 +1,17 @@
 <?php
 /**
- * $Horde: chora/cvsgraph.php,v 1.15.8.4 2007/02/23 03:58:53 selsky Exp $
- *
  * Wrapper for CVSGraph.
  *
- * Copyright 1999-2007 Anil Madhavapeddy <anil at recoil.org>
- * Copyright 1999-2007 Charles Hagenbuch <chuck at horde.org>
+ * $Horde: chora/cvsgraph.php,v 1.15.8.6 2009/01/06 15:22:34 jan Exp $
+ *
+ * Copyright 1999-2009 The Horde Project (http://www.horde.org/)
  *
- * See the enclosed file COPYING for license information (GPL).  If you
+ * See the enclosed file COPYING for license information (GPL). If you
  * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author  Anil Madhavapeddy <anil at recoil.org>
+ * @author  Chuck Hagenbuch <chuck at horde.org>
+ * @package Chora
  */
 
 @define('CHORA_BASE', dirname(__FILE__));
@@ -31,7 +34,7 @@ if (@is_file($fullname . ',v')) {
 
         // Build up the argument string.
         $argstr = '';
-        if (substr(PHP_OS, 0, 3) == 'WIN') {
+        if (!strncasecmp(PHP_OS, 'WIN', 3)) {
             foreach ($args as $key => $val) {
                 $argstr .= "-$key \"$val\" ";
             }
@@ -46,7 +49,6 @@ if (@is_file($fullname . ',v')) {
     } else {
         // Display the wrapper page for the image.
         $title = sprintf(_("Graph for %s"), Text::htmlAllSpaces($where));
-        $upwhere = preg_replace('|[^/]+$|', '', $where);
         $extraLink = Chora::getFileViews();
 
         require CHORA_TEMPLATES . '/common-header.inc';
@@ -66,7 +68,7 @@ if (@is_file($fullname . ',v')) {
 
         // Build up the argument string.
         $argstr = '';
-        if (substr(PHP_OS, 0, 3) == 'WIN') {
+        if (!strncasecmp(PHP_OS, 'WIN', 3)) {
             foreach ($args as $key => $val) {
                 $argstr .= "-$key \"$val\" ";
             }
@@ -83,5 +85,5 @@ if (@is_file($fullname . ',v')) {
         require $registry->get('templates', 'horde') . '/common-footer.inc';
     }
 } else {
-    Chora::fatal("$where: no such file or directory");
+    Chora::fatal('404 Not Found', "$where: no such file or directory");
 }
diff --git a/diff.php b/diff.php
index d871d40..e4fa2e0 100644
--- a/diff.php
+++ b/diff.php
@@ -1,197 +1,180 @@
 <?php
 /**
- * $Horde: chora/diff.php,v 1.87.8.3 2007/01/02 13:54:05 jan Exp $
+ * $Horde: chora/diff.php,v 1.87.8.5 2009/01/06 15:22:34 jan Exp $
  *
- * Copyright 2000-2007 Anil Madhavapeddy <anil at recoil.org>
+ * Copyright 2000-2009 The Horde Project (http://www.horde.org/)
  *
- * See the enclosed file COPYING for license information (GPL).  If you
+ * See the enclosed file COPYING for license information (GPL). If you
  * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author  Anil Madhavapeddy <avsm at horde.org>
  */
 
 @define('CHORA_BASE', dirname(__FILE__));
 require_once CHORA_BASE . '/lib/base.php';
+require_once 'Horde/MIME/Magic.php';
 
 /* Spawn the repository and file objects */
 $fl = &$VC->getFileObject($where, $cache);
 Chora::checkError($fl);
 
-/* Initialise the form variables correctly.
- * If r1/r2 are empty, then use the corresponding text field instead */
-$r1 = Util::getFormData('r1', 0);
-$r2 = Util::getFormData('r2', 0);
-
-if (!$r1) {
-    $r1 = Util::getFormData('tr1');
-}
-if (!$r2) {
-    $r2 = Util::getFormData('tr2');
+/* Initialise the form variables correctly. */
+$r1 = Util::getFormData('r1');
+$r2 = Util::getFormData('r2');
+
+/* If we are in a SVN repository (or another VC system that doesn't
+ * always use consecutive revision numbers from change-to-change
+ * per-file) and we compare against an older version that does not
+ * exist, then we search for the newest version older than the given
+ * old version. */
+if (!isset($fl->logs[$r1]) && isset($fl->logs[$r2]) && $r1 < $r2) {
+    $rn = 0;
+    foreach (array_keys($fl->logs) as $r) {
+        if ($r > $rn && $r < $r1) {
+            $rn = $r;
+        }
+    }
+    if ($rn) {
+        $r1 = $rn;
+    }
 }
 
-/* If no context-size has been specified, default to 3. */
-$num = (int)Util::getFormData('num', 3);
+/* Ensure that we have valid revision numbers. */
+if (!VC_Revision::valid($r1) || !VC_Revision::valid($r2)) {
+    Chora::fatal('403 Forbidden', _("Malformed Query"));
+}
 
 /* If no type has been specified, then default to human readable. */
-$ty = Util::getFormData('ty', 'h');
-
-/* Unless otherwise specified, show whitespace differences. */
-$ws = Util::getFormData('ws', 1);
-
-/* Figure out what type of diff has been requested. */
-switch ($ty) {
-case 's':
-    $type = 'column';
-    break;
-
-case 'c':
-    $type = 'context';
-    break;
-
-case 'e':
-    $type = 'ed';
-    break;
-
-case 'u':
-case 'h':
-default:
+$type = Util::getFormData('t', 'colored');
+if (Util::getFormData('ty') == 'u') {
     $type = 'unified';
 }
 
-/* Ensure that we have valid revision numbers. */
-if (!VC_Revision::valid($r1) || !VC_Revision::valid($r2)) {
-    Chora::fatal(_("Malformed Query"));
-}
+/* Unless otherwise specified, show whitespace differences and 3 lines
+ * of context. */
+$ws = Util::getFormData('ws', 1);
+$num = (int)Util::getFormData('num', 3);
 
 /* Cache the output of the diff for a week - it can be longer, since
- * it should never change */
+ * it should never change. */
 header('Cache-Control: max-age=604800');
 
-/* Title to use for html output pages */
+/* All is ok, proceed with the diff. Always make sure there is a newline at
+ * the end of the file - patch requires it. */
+if ($type != 'colored') {
+    header('Content-Type: text/plain');
+    echo implode("\n", $VC->getDiff($fl, $r1, $r2, $type, $num, $ws)) . "\n";
+    exit;
+}
+
+/* Human-Readable diff. */
 $title = sprintf(_("Diff for %s between version %s and %s"),
                  Text::htmlallspaces($where), $r1, $r2);
 
-/* All is ok, proceed with the diff */
-switch ($type) {
-case 'column':
-    /* We'll need to know the mime type to modify diffs based on the mime
-       type. */
-    require_once 'Horde/MIME/Magic.php';
-    $mime_type = MIME_Magic::filenameToMIME($fullname);
-
-    if ($browser->isViewable($mime_type)) {
-        // The above are images that most web browsers can inline
-        // We borrow a *large* part of this from the Human-Readable case
-        $url1 = Chora::url('co', $where, array('r' => $r1));
-        $url2 = Chora::url('co', $where, array('r' => $r2));
-        $path = $fl->queryModulePath();
-
-        // Get the log entry so we can display it
-        $log = $fl->logs[$r2];
-        $log_print = Chora::formatLogMessage($log->queryLog());
-
-        // Start the html output, include menu bar and headers.
-        require CHORA_TEMPLATES . '/common-header.inc';
-        require CHORA_TEMPLATES . '/menu.inc';
-        require CHORA_TEMPLATES . '/headerbar.inc';
-
-        // Create a table for the two revisions, display log, and
-        // print a labeled bar for the revisions.
-        require CHORA_TEMPLATES . '/diff/hr/header.inc';
-        echo "<td><img src=\"$url1\"></td>";
-        echo "<td><img src=\"$url2\"></td>";
-        echo '</tr>';
-        require $registry->get('templates', 'horde') . '/common-footer.inc';
-    } else {
-        header('Content-Type: text/plain');
-        echo implode("\n", $VC->getDiff($fl, $r1, $r2, $type, $num, $ws));
+/* Format log entries. */
+$log = &$fl->logs;
+$log_messages = array();
+$range = Chora::getRevisionRange($r1, $r2);
+foreach ($range as $val) {
+    list($branchname, $branchrev) = Chora::getBranch($fl, $val);
+    if (isset($log[$val])) {
+        $clog = &$log[$val];
+        $log_messages[] = array(
+            'rev' => $val,
+            'msg' => Chora::formatLogMessage($clog->queryLog()),
+            'author' => Chora::showAuthorName($clog->queryAuthor(), true),
+            'branchRev' => $branchrev,
+            'branchName' => $branchname,
+            'date' => Chora::formatDate($clog->queryDate()),
+            'tags' => Chora::getTags($clog, $where),
+        );
     }
-    break;
-
-case 'context':
-    header('Content-Type: text/plain');
-    echo implode("\n", $VC->getDiff($fl, $r1, $r2, $type, $num, $ws));
-    break;
+}
 
-case 'ed':
-    header('Content-Type: text/plain');
-    echo implode("\n", $VC->getDiff($fl, $r1, $r2, $type, $num, $ws));
-    break;
-
-case 'unified':
-default:
-    if ($ty != 'h') {
-        /* Not Human-Readable format. */
-        header('Content-Type: text/plain');
-        echo implode("\n", $VC->getDiff($fl, $r1, $r2, $type, $num, $ws));
+Horde::addScriptFile('prototype.js', 'chora', true);
+Horde::addScriptFile('stripe.js', 'chora', true);
+require CHORA_TEMPLATES . '/common-header.inc';
+require CHORA_TEMPLATES . '/menu.inc';
+require CHORA_TEMPLATES . '/headerbar.inc';
+require CHORA_TEMPLATES . '/diff/hr/header.inc';
+
+$mime_type = MIME_Magic::filenameToMIME($fullname);
+if (substr($mime_type, 0, 6) == 'image/') {
+    /* Check for images. */
+    $url1 = Chora::url('co', $where, array('r' => $r1, 'p' => 1));
+    $url2 = Chora::url('co', $where, array('r' => $r2, 'p' => 1));
+
+    echo "<tr><td><img src=\"$url1\" alt=\"" . htmlspecialchars($r1) . '" /></td>' .
+        "<td><img src=\"$url2\" alt=\"" . htmlspecialchars($r2) . '" /></td></tr>';
+} else {
+    /* Retrieve the tree of changes. */
+    $lns = VC_Diff::humanReadable($VC->getDiff($fl, $r1, $r2, 'unified', $num, $ws));
+    if (!$lns) {
+        /* Is the diff empty? */
+        require CHORA_TEMPLATES . '/diff/hr/nochange.inc';
     } else {
-        /* Human-Readable diff. */
-
-        /* Output standard header information for the page. */
-        $filename = preg_replace('/^.*\//', '', $where);
-        $pathname = preg_replace('/[^\/]*$/', '', $where);
-
-        $log = $fl->logs[$r2];
-        $log_print = Chora::formatLogMessage($log->queryLog());
+        /* Iterate through every header block of changes. */
+        foreach ($lns as $header) {
+            $lefthead = $header['oldline'];
+            $righthead = $header['newline'];
+            require CHORA_TEMPLATES . '/diff/hr/row.inc';
+
+            /* Each header block consists of a number of changes
+             * (add, remove, change). */
+            $curContext = '';
+            foreach ($header['contents'] as $change) {
+                if (!empty($curContext) && $change['type'] != 'empty') {
+                    $line = $curContext;
+                    $curContext = '';
+                    require CHORA_TEMPLATES . '/diff/hr/empty.inc';
+                }
 
-        require CHORA_TEMPLATES . '/common-header.inc';
-        require CHORA_TEMPLATES . '/menu.inc';
-        require CHORA_TEMPLATES . '/headerbar.inc';
-        require CHORA_TEMPLATES . '/diff/hr/header.inc';
+                switch ($change['type']) {
+                case 'add':
+                    $line = '';
+                    foreach ($change['lines'] as $l) {
+                        $line .= htmlspecialchars($l) . '<br />';
+                    }
+                    require CHORA_TEMPLATES . '/diff/hr/add.inc';
+                    break;
 
-        /* Retrieve the tree of changes. */
-        $lns = VC_Diff::humanReadable($VC->getDiff($fl, $r1, $r2, 'unified', $num, $ws));
-        /* TODO: check for errors here (PEAR_Error returned) - avsm */
-        /* Is the diff empty? */
-        if (!sizeof($lns)) {
-            require CHORA_TEMPLATES . '/diff/hr/nochange.inc';
-        } else {
-            /* Iterate through every header block of changes */
-            foreach ($lns as $header) {
-                $lefthead = Text::htmlspaces(@$header['oldline']);
-                $righthead = Text::htmlspaces(@$header['newline']);
-                $headfunc = Text::htmlspaces(@$header['function']);
-                require CHORA_TEMPLATES . '/diff/hr/row.inc';
-
-                /* Each header block consists of a number of changes
-                 * (add, remove, change) */
-                foreach ($header['contents'] as $change) {
-                    switch ($change['type']) {
-                    case 'add':
-                        foreach ($change['lines'] as $line) {
-                            $line = Text::htmlAllSpaces($line);
-                            require CHORA_TEMPLATES . '/diff/hr/add.inc';
-                        }
-                        break;
-
-                    case 'remove':
-                        foreach ($change['lines'] as $line) {
-                            $line = Text::htmlAllSpaces($line);
-                            require CHORA_TEMPLATES . '/diff/hr/remove.inc';
-                        }
-                        break;
-
-                    case 'empty':
-                        $line = Text::htmlAllSpaces($change['line']);
-                        require CHORA_TEMPLATES . '/diff/hr/empty.inc';
-                        break;
-
-                    case 'change':
-                        /* Pop the old/new stacks one by one, until
-                         * both are empty. */
-                        $oldsize = count($change['old']);
-                        $newsize = count($change['new']);
-                        for ($row = 0; $row < max($oldsize, $newsize); $row++) {
-                            $left = isset($change['old'][$row]) ? Text::htmlAllSpaces($change['old'][$row]) : '';
-                            $right = isset($change['new'][$row]) ? Text::htmlAllSpaces($change['new'][$row]) : '';
-                            require CHORA_TEMPLATES . '/diff/hr/change.inc';
-                        }
-                        break;
+                case 'remove':
+                    $line = '';
+                    foreach ($change['lines'] as $l) {
+                        $line .= htmlspecialchars($l) . '<br />';
                     }
+                    require CHORA_TEMPLATES . '/diff/hr/remove.inc';
+                    break;
+
+                case 'empty':
+                    $curContext .= htmlspecialchars($change['line']) . '<br />';
+                    break;
+
+                case 'change':
+                    /* Pop the old/new stacks one by one, until both are
+                     * empty. */
+                    $oldsize = count($change['old']);
+                    $newsize = count($change['new']);
+                    $left = $right = '';
+                    for ($row = 0, $rowMax = max($oldsize, $newsize); $row < $rowMax; ++$row) {
+                        $left .= isset($change['old'][$row]) ? htmlspecialchars($change['old'][$row]) : '';
+                        $left .= '<br />';
+                        $right .= isset($change['new'][$row]) ? htmlspecialchars($change['new'][$row]) : '';
+                        $right .= '<br />';
+                    }
+                    require CHORA_TEMPLATES . '/diff/hr/change.inc';
+                    break;
                 }
             }
-        }
 
-        /* Print legend. */
-        require CHORA_TEMPLATES . '/diff/hr/footer.inc';
-        require $registry->get('templates', 'horde') . '/common-footer.inc';
+            if (!empty($curContext)) {
+                $line = $curContext;
+                $curContext = '';
+                require CHORA_TEMPLATES . '/diff/hr/empty.inc';
+            }
+        }
     }
 }
+
+require CHORA_TEMPLATES . '/diff/hr/footer.inc';
+require $registry->get('templates', 'horde') . '/common-footer.inc';
diff --git a/docs/CHANGES b/docs/CHANGES
index 234a040..40ac258 100644
--- a/docs/CHANGES
+++ b/docs/CHANGES
@@ -1,3 +1,36 @@
+----
+v2.1
+----
+
+[jan] Add Italian translation (Fabio Pedretti <fabio.pedretti at ing.unibs.it>).
+[cjh] Allow configuring which views the file name and latest revision number
+      link to (jon at spriggs.org.uk, Request #6690).
+[mms] Add log entries to diff screen (Request #6193).
+[cjh] Apply fix for http://dev.rubyonrails.org/ticket/11473 to prototype.js
+      (Request #6590).
+[cjh] With Horde 3.2+, a username and password can be supplied for
+      authenticating to Subversion repositories (duck at obala.net,
+      Request #5958).
+[cjh] Use a dropdown list to show alternate code repositories
+      (greg_swift at aotx.uscourts.gov, Request #5600).
+[cjh] Add the ability to walk backwards to the next oldest revision when
+      diffing on a VC system where the revision numbers of a single file
+      are not always consecutive (helly at php.net, Request #5350).
+[jan] Add configuration to specify temporary directory for SVN (requires Horde
+      3.2, meyer at mesw.de, Request #5051).
+[cjh] Include a link to the previous revision in annotation views
+      (Request #197).
+[cjh] Make the revision log searchable and sortable.
+[cjh] Only show the 100 latest revisions by default (Bug #2584).
+[cjh] Show commit logs when hovering on the revision number in the
+      annotate view (robzilla at siklos.ca, Bug #3076).
+[cjh] Simplify codepaths by supporting only GET or mod_rewrite URLs.
+[jan] Add permissions per repository (ben at alkaloid.net, Request #3186).
+[cjh] Add support for dynamic re-sorting of file lists.
+[mas] Change any output of <b> and <i> tags to <strong> and <em> for better
+      accessibility support.
+
+
 ------
 v2.0.2
 ------
@@ -172,7 +205,7 @@ v0.6.5-dev
 ----------
 
 [avsm] Diff selection from the log view.
-       (Mathieu Arnold <arn_mat at club-internet.fr>)
+       (Mathieu Arnold <mat at mat.cc>)
 [avsm] Improve cvsusers parsing for varied email addresses.
 [avsm] Use the Horde Cache in the file log view to speed
        things up.
@@ -226,11 +259,11 @@ v0.4-dev
 [cjh]  Start using the Horde framework and complying more with
        CODING_STANDARDS.
 [cjh]  A number of fixes for files and directories with spaces in their
-       names (Mathieu Arnold <arn_mat at club-internet.fr>).
+       names (Mathieu Arnold <mat at mat.cc>).
 [cjh]  Add long unified diff option (Mathieu Arnold
-       <arn_mat at club-internet.fr>).
+       <mat at mat.cc>).
 [cjh]  Add default sort direction configuration option (Mathieu Arnold
-       <arn_mat at club-internet.fr>).
+       <mat at mat.cc>).
 [cjh]  Fix bug with Chora in the webroot (http://cvs.example.com/).
 [avsm] Overhaul CVSLib_Checkout interface to be in line with
        the rest of the CVSLib_* API (and much simpler).
diff --git a/docs/CREDITS b/docs/CREDITS
index ec7f6bd..5094838 100644
--- a/docs/CREDITS
+++ b/docs/CREDITS
@@ -34,6 +34,7 @@ Finnish                 Tero Matinlassi <tero.matinlassi at edu.vantaa.fi>
                         Leena Heino <leena.heino at uta.fi>
 French                  Mathieu Arnold <mat at absolight.net>
 German                  Jan Schneider <jan at horde.org>
+Italian                 Fabio Pedretti <fabio.pedretti at ing.unibs.it>
 Norwegian Bokmaal       Oystein Steimler <oystein at rexta.net>
 Polish                  Piotr Roszatycki <Piotr_Roszatycki at netia.net.pl>
 Romanian                Eugen Hoanca <eugenh at urban-grafx.ro>
@@ -42,5 +43,5 @@ Russian                 Illya Belov <belov at iop.irkps.ru>
 Slovak                  Ivan Noris <vix at vazka.sk>
 Spanish                 Julian Jares <jjares at techie.com>
                         Manuel Perez Ayala <mperaya at alcazaba.unex.es>
-Swedish                 Andreas Dahlén <andreas at dahlen.ws>
+Swedish                 Andreas Dahlén <andreas at dahlen.ws>
 =====================   ======================================================
diff --git a/docs/INSTALL b/docs/INSTALL
index 72dff73..f610143 100644
--- a/docs/INSTALL
+++ b/docs/INSTALL
@@ -2,8 +2,8 @@
  Installing Chora 2.0
 ======================
 
-:Last update:   $Date: 2005/02/03 15:04:39 $
-:Revision:      $Revision: 1.7.4.1 $
+:Last update:   $Date: 2008/10/09 17:40:04 $
+:Revision:      $Revision: 1.7.4.2 $
 :Contact:       chora at lists.horde.org
 
 .. contents:: Contents
@@ -168,9 +168,8 @@ subscription information can be found at
 
   http://www.horde.org/mail/
 
-Lastly, Horde developers, contributors and users also make occasional
-appearances on IRC, on the channel #horde on the freenode Network
-(irc.freenode.net).
+Lastly, Horde developers, contributors and users may also be found on IRC,
+on the channel #horde on the Freenode Network (irc.freenode.net).
 
 Please keep in mind that Chora is free software written by volunteers.  For
 information on reasonable support expectations, please read
diff --git a/docs/RELEASE_NOTES b/docs/RELEASE_NOTES
index eef7767..4381956 100644
--- a/docs/RELEASE_NOTES
+++ b/docs/RELEASE_NOTES
@@ -12,29 +12,36 @@
  * 8 - Minor security fixes
  * 9 - Major security fixes
  */
-$this->notes['fm']['focus'] = 4;
+$this->notes['fm']['focus'] = 5;
 
 /* Mailing list release notes. */
 $this->notes['ml']['changes'] = <<<ML
 The Horde Team is pleased to announce the final release of the Chora
-Repository Viewer version H3 (2.0.2).
+Repository Viewer version H3 (2.1).
 
 Chora is built upon the Horde Application Framework and provides a read-only
 browser interface to any number of version control repositories.  Advanced
 features include a visual branch view of the repository's history,
 pretty-printed output, annotation, patchsets and basic statistics.
 
-Major changes compared to the Chora version H3 (2.0.1) are:
-    * Changed sort order of patchsets to match commit logs.
-    * Added Danish translation.
-    * Updated almost all translations.
+The major changes compared to the Chora H3 (2.0) versions are:
+    * Log entries are shown for each revision in a diff
+    * Links to the previous revision in the annotate view
+    * Multiple repositories are shown in a dropdown menu.
+    * Dynamic sorting in browse views, and dynamic sorting and
+      searching in the history view.
+    * Control access to each repository with Horde Permissions
+    * Authentication for subversion repositories
+    * Performance improvements
+    * Better accessibility
 ML;
 
 /* Freshmeat release notes. */
 $this->notes['fm']['changes'] = <<<FM
-The sort order of patchsets has been changed to match commit log ordering.
-A Danish translation has been added and almost all other translations have
-been updated.
+This release includes more information in annotate and diff views, adds dynamic
+sorting and searching of information in the browse and history views, allows
+controlling permissions on individual repositories, and adds better performance
+and accessibility than the previous version.
 FM;
 
 $this->notes['name'] = 'Chora';
diff --git a/docs/TODO b/docs/TODO
index 76d1cbc..8c4a309 100644
--- a/docs/TODO
+++ b/docs/TODO
@@ -2,8 +2,8 @@
  Chora Development TODO List
 =============================
 
-:Last update:   $Date: 2004/09/28 15:18:26 $
-:Revision:      $Revision: 1.5 $
+:Last update:   $Date: 2008/10/09 17:40:04 $
+:Revision:      $Revision: 1.5.10.1 $
 :Contact:       chora at lists.horde.org
 
 - Other utility support (lint support for C files, pod2html for perl, PHPDoc
diff --git a/history.php b/history.php
index e47f6b0..1aab152 100644
--- a/history.php
+++ b/history.php
@@ -1,44 +1,43 @@
 <?php
 /**
- * $Horde: chora/history.php,v 1.53.8.4 2007/01/02 13:54:05 jan Exp $
+ * $Horde: chora/history.php,v 1.53.8.6 2009/01/06 15:22:34 jan Exp $
  *
- * Copyright 2000-2007 Anil Madhavapeddy <anil at recoil.org>
+ * Copyright 2000-2009 The Horde Project (http://www.horde.org/)
  *
- * See the enclosed file COPYING for license information (GPL).  If you
+ * See the enclosed file COPYING for license information (GPL). If you
  * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author  Anil Madhavapeddy <avsm at horde.org>
  */
 
 @define('CHORA_BASE', dirname(__FILE__));
 require_once CHORA_BASE . '/lib/base.php';
 
-// Exit if it's not supported.
+/* Exit if it's not supported. */
 if (is_a($VC, 'VC_svn')) {
     header('Location: ' . Chora::url('browse', $where));
     exit;
 }
 
-/* Spawn the file object */
+/* Spawn the file object. */
 $fl = &$VC->getFileObject($where, $cache);
 Chora::checkError($fl);
 
-/* $trunk contains an array of trunk revisions */
+/* $trunk contains an array of trunk revisions. */
 $trunk = array();
 
-/* $branches is a hash with the branch revision as the
- * key, and value being an array of revs on that branch */
+/* $branches is a hash with the branch revision as the key, and value
+ * being an array of revs on that branch. */
 $branches = array();
 
-/* Populate $col with a list of all the branch points */
+/* Populate $col with a list of all the branch points. */
 foreach ($fl->branches as $rev => $sym) {
     $branches[$rev] = array();
 }
 
-/* For every revision, figure out if it is a trunk
- * revision, or instead associated with a branch.
- * If trunk, add it to the $trunk array.
- * Otherwise, add it to an array in $branches[$branch]
- */
-
+/* For every revision, figure out if it is a trunk revision, or
+ * instead associated with a branch.  If trunk, add it to the $trunk
+ * array.  Otherwise, add it to an array in $branches[$branch]. */
 foreach ($fl->logs as $log) {
     $rev = $log->queryRevision();
     $baseRev = VC_Revision::strip($rev, 1);
@@ -49,106 +48,110 @@ foreach ($fl->logs as $log) {
             $branchFound = true;
         }
     }
-    /* If its not a branch, then add it to the trunk */
-    /* TODO: this silently drops vendor branches atm! - avsm */
+    /* If its not a branch, then add it to the trunk. */
+    /* TODO: this silently drops vendor branches atm! - avsm. */
     if (!$branchFound && VC_Revision::sizeof($rev) == 2) {
         array_unshift($trunk, $rev);
     }
 }
 
 foreach ($branches as $col => $rows) {
-    /* If this branch has no actual commits on it, then it's a
-     * stub branch, and we can remove it for this view */
+    /* If this branch has no actual commits on it, then it's a stub
+     * branch, and we can remove it for this view. */
     if (!sizeof($rows)) {
         unset($branches[$col]);
     }
 }
 
-$colset = array('#ccdeff','#eeccff','#ffeecc','#eeffcc','#ccffdd','#dcdba0');
+$colset = array('#ccdeff', '#ecf', '#fec', '#efc', '#cfd', '#dcdba0');
 $colStack = array();
 $branchColours = array();
 foreach ($branches as $brrev => $brcont) {
-   if (!sizeof($colStack)) $colStack = $colset;
-   $branchColours[$brrev] = array_shift($colset);
+    if (!count($colStack)) {
+        $colStack = $colset;
+    }
+    $branchColours[$brrev] = array_shift($colset);
 }
 
-/* This takes a row and a column, and recursively iterates through
- * any sub-revisions or branches from the value that was already in
- * the grid at the co-ordinates that it was called with.
+/**
+ * This takes a row and a column, and recursively iterates through any
+ * sub-revisions or branches from the value that was already in the
+ * grid at the co-ordinates that it was called with.
  *
- * Calling this function on every revision of the trunk is enough
- * to render out the whole tree. */
-
-function populateGrid($row, $col) {
+ * Calling this function on every revision of the trunk is enough to
+ * render out the whole tree.
+ */
+function populateGrid($row, $col)
+{
     global $grid, $branches;
 
-    /* Figure out the starting revision this function uses */
+    /* Figure out the starting revision this function uses. */
     $rev = $grid[$row][$col];
 
-    /* For every branch that is known, try to see if it forks here */
+    /* For every branch that is known, try to see if it forks here. */
     $brkeys = array_keys($branches);
 
     /* NOTE: do not optimise to use foreach () or each() here, as that
      * really screws up the $branches pointer array due to the
-     * recursion, and parallel branches fail - avsm */
-    for ($a = 0; $a < sizeof($brkeys); $a++) {
+     * recursion, and parallel branches fail - avsm. */
+    for ($a = 0, $aMax = count($brkeys); $a < $aMax; ++$a) {
         $brrev = $brkeys[$a];
         $brcont = $branches[$brrev];
-        /* Check to see if current point matches a branch point */
+        /* Check to see if current point matches a branch point. */
         if (!strcmp($rev, VC_Revision::strip($brrev, 1))) {
-            /* If it does, figure out how many rows we have to add */
+            /* If it does, figure out how many rows we have to add. */
             $numRows = sizeof($brcont);
-            /* Check rows in columns to the right, until one is free */
+            /* Check rows in columns to the right, until one is
+             * free. */
             $insCol = $col + 1;
             while (true) {
-                /* Look in the current column for a set value */
+                /* Look in the current column for a set value. */
                 $inc = false;
-                for ($i = $row; $i <= ($row + $numRows); $i++) {
+                for ($i = $row; $i <= ($row + $numRows); ++$i) {
                     if (isset($grid[$i][$insCol])) {
                         $inc = true;
                     }
                 }
                 /* If a set value was found, shift to the right and
-                 * try again.  Otherwise, break out of the loop */
+                 * try again.  Otherwise, break out of the loop. */
                 if ($inc) {
                     if (!isset($grid[$row][$insCol])) {
                         $grid[$row][$insCol] = ':' . $brcont[0];
                     }
-                    $insCol++;
+                    ++$insCol;
                 } else {
                     break;
                 }
             }
 
-            /* Put a fork marker in the top of the branch */
+            /* Put a fork marker in the top of the branch. */
             $grid[$row][$insCol] = $brrev;
 
-            /* Populate the grid with the branch values at this point */
-            for ($i = 0; $i < $numRows; $i++) {
+            /* Populate the grid with the branch values at this
+             * point. */
+            for ($i = 0; $i < $numRows; ++$i) {
                 $grid[1 + $i + $row][$insCol] = $brcont[$i];
             }
-            /* For each value just set, check for sub-branches,
-             * - but in reverse (VERY IMPORTANT!) */
-            for ($i = $numRows - 1; $i >= 0 ; $i--) {
+            /* For each value just set, check for sub-branches, - but
+             * in reverse (VERY IMPORTANT!). */
+            for ($i = $numRows - 1; $i >= 0 ; --$i) {
                 populateGrid(1+$i+$row, $insCol);
             }
         }
     }
 }
 
-/* Start row at the bottom trunk revision.  Since branches always
- * go down, there can never be one above 1.1, and so this is a
- * safe location to start.  We will then work our way up,
- * recursively populating the grid with branch revisions */
-
+/* Start row at the bottom trunk revision.  Since branches always go
+ * down, there can never be one above 1.1, and so this is a safe
+ * location to start.  We will then work our way up, recursively
+ * populating the grid with branch revisions. */
 for ($row = sizeof($trunk) - 1; $row >= 0; $row--) {
     $grid[$row][0] = $trunk[$row];
     populateGrid($row, 0);
 }
 
-/* Sort the grid array into row order, and determine the maximum column
- * size that we need to render out in HTML */
-
+/* Sort the grid array into row order, and determine the maximum
+ * column size that we need to render out in HTML. */
 ksort($grid);
 $maxCol = 0;
 foreach ($grid as $cols) {
@@ -166,37 +169,38 @@ require CHORA_TEMPLATES . '/headerbar.inc';
 require CHORA_TEMPLATES . '/history/header.inc';
 
 foreach ($grid as $row) {
-    require CHORA_TEMPLATES . '/history/row_start.inc';
+    echo '<tr>';
 
-    /* Start traversing the grid of rows and columns */
-    for ($i = 0; $i <= $maxCol; $i++) {
+    /* Start traversing the grid of rows and columns. */
+    for ($i = 0; $i <= $maxCol; ++$i) {
 
-        /* If this column has nothing in it, require a blank cell */
+        /* If this column has nothing in it, require a blank cell. */
         if (!isset($row[$i])) {
              $bg = '';
-             require CHORA_TEMPLATES.'/history/blank.inc';
+             require CHORA_TEMPLATES . '/history/blank.inc';
              continue;
         }
 
-        /* Otherwise, this cell has content; determine what it is */
+        /* Otherwise, this cell has content; determine what it is. */
         $rev = $row[$i];
 
         if (VC_Revision::valid($rev) && (VC_Revision::sizeof($rev) % 2)) {
-            /* This is a branch point, so put the info out */
-            $bg = isset($branchColours[$rev]) ? $branchColours[$rev] : 'white';
+            /* This is a branch point, so put the info out. */
+            $bg = isset($branchColours[$rev]) ? $branchColours[$rev] : '#e9e9e9';
             $symname = $fl->branches[$rev];
             require CHORA_TEMPLATES . '/history/branch_cell.inc';
 
         } elseif (preg_match('|^:|', $rev)) {
-            /* This is a continuation cell, so render it with the branch colour */
+            /* This is a continuation cell, so render it with the
+             * branch colour. */
             $bgbr = VC_Revision::strip(preg_replace('|^\:|', '', $rev), 1);
-            $bg = isset($branchColours[$bgbr]) ? $branchColours[$bgbr] : 'white';
+            $bg = isset($branchColours[$bgbr]) ? $branchColours[$bgbr] : '#e9e9e9';
             require CHORA_TEMPLATES . '/history/blank.inc';
 
         } elseif (VC_Revision::valid($rev)) {
-            /* This cell contains a revision, so render it */
+            /* This cell contains a revision, so render it. */
             $bgbr = VC_Revision::strip($rev, 1);
-            $bg = isset($branchColours[$bgbr]) ? $branchColours[$bgbr] : 'white';
+            $bg = isset($branchColours[$bgbr]) ? $branchColours[$bgbr] : '#e9e9e9';
             $log = $fl->logs[$rev];
             $author = Chora::showAuthorName($log->queryAuthor());
             $date = strftime('%e %b %Y', $log->queryDate());
@@ -204,11 +208,12 @@ foreach ($grid as $row) {
             require CHORA_TEMPLATES . '/history/rev.inc';
 
         } else {
-            /* Exhausted other possibilities, just show a blank cell */
+            /* Exhausted other possibilities, just show a blank cell. */
             require CHORA_TEMPLATES . '/history/blank.inc';
         }
     }
-    require CHORA_TEMPLATES . '/history/row_end.inc';
+
+    echo '</tr>';
 }
 
 require CHORA_TEMPLATES . '/history/footer.inc';
diff --git a/index.php b/index.php
index dfd82f4..9649bba 100644
--- a/index.php
+++ b/index.php
@@ -1,18 +1,20 @@
 <?php
 /**
- * $Horde: chora/index.php,v 1.25.10.3 2007/01/02 13:54:05 jan Exp $
+ * $Horde: chora/index.php,v 1.25.10.5 2009/01/06 15:22:34 jan Exp $
  *
- * Copyright 1999-2007 Anil Madhavapeddy <anil at recoil.org>
+ * Copyright 1999-2009 The Horde Project (http://www.horde.org/)
  *
- * See the enclosed file COPYING for license information (GPL).  If you
+ * See the enclosed file COPYING for license information (GPL). If you
  * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author  Anil Madhavapeddy <avsm at horde.org>
  */
 
 define('CHORA_BASE', dirname(__FILE__));
-$chora_configured = (@is_readable(CHORA_BASE . '/config/conf.php') &&
-                     @is_readable(CHORA_BASE . '/config/sourceroots.php') &&
-                     @is_readable(CHORA_BASE . '/config/mime_drivers.php') &&
-                     @is_readable(CHORA_BASE . '/config/prefs.php'));
+$chora_configured = (is_readable(CHORA_BASE . '/config/conf.php') &&
+                     is_readable(CHORA_BASE . '/config/sourceroots.php') &&
+                     is_readable(CHORA_BASE . '/config/mime_drivers.php') &&
+                     is_readable(CHORA_BASE . '/config/prefs.php'));
 
 if (!$chora_configured) {
     /* Chora isn't configured. */
diff --git a/js/QuickFinder.js b/js/QuickFinder.js
new file mode 100644
index 0000000..2d23ade
--- /dev/null
+++ b/js/QuickFinder.js
@@ -0,0 +1 @@
+var QuickFinder={attachBehavior:function(){$$("input").each(function(C){var B=C.readAttribute("for");if(!B){return }if(B.indexOf(",")!=-1){C.filterTargets=[];var A=B.split(",");for(var F=0;F<A.length;++F){var E=$(A[F]);if(E){C.filterTargets.push(E)}}if(!C.filterTargets.length){return }}else{C.filterTargets=[$(B)];if(!C.filterTargets[0]){return }}filterEmpty=C.readAttribute("empty");if(filterEmpty){C.filterEmpty=$(filterEmpty)}C.observe("keyup",QuickFinder.onKeyUp);for(var F=0,D=C.filterTargets.length;F<D;F++){C.filterTargets[F].immediateDescendants().each(function(G){var H=G.filterText||G.readAttribute("filterText");if(!H){G.filterText=G.innerHTML.stripTags()}G.filterText=G.filterText.toLowerCase()})}QuickFinder.filter(C)})},onKeyUp:function(A){input=A.element();if(input.filterTargets){QuickFinder.filter(input)}},filter:function(B){var F=B.value.toLowerCase();var A=0;for(var D=0,C=B.filterTargets.length;D<C;D++){B.filterTargets[D].immediateDescendants().each(function(G){var H=G.filterText;if(H.indexOf(F)==-1){G.addClassName("QuickFinderNoMatch")}else{++A;G.removeClassName("QuickFinderNoMatch")}})}try{if(B.filterEmpty){(A==0)?B.filterEmpty.show():B.filterEmpty.hide()}}catch(E){}}};document.observe("dom:loaded",QuickFinder.attachBehavior)
\ No newline at end of file
diff --git a/js/prototype.js b/js/prototype.js
new file mode 100644
index 0000000..3c2493f
--- /dev/null
+++ b/js/prototype.js
@@ -0,0 +1 @@
+var Prototype={Version:"1.6.0.3",Browser:{IE:!!(window.attachEvent&&navigator.userAgent.indexOf("Opera")===-1),Opera:navigator.userAgent.indexOf("Opera")>-1,WebKit:navigator.userAgent.indexOf("AppleWebKit/")>-1,Gecko:navigator.userAgent.indexOf("Gecko")>-1&&navigator.userAgent.indexOf("KHTML")===-1,MobileSafari:!!navigator.userAgent.match(/Apple.*Mobile.*Safari/)},BrowserFeatures:{XPath:!!document.evaluate,SelectorsAPI:!!document.querySelector,ElementExtensions:!!window.HTMLElement,SpecificElementExtensions:document.createElement("div")["__proto__"]&&document.createElement("div")["__proto__"]!==document.createElement("form")["__proto__"]},ScriptFragment:"<script[^>]*>([\\S\\s]*?)<\/script>",JSONFilter:/^\/\*-secure-([\s\S]*)\*\/\s*$/,emptyFunction:function(){},K:function(a){return a}};if(Prototype.Browser.MobileSafari){Prototype.BrowserFeatures.SpecificElementExtensions=false}var Class={create:function(){var e=null,d=$A(arguments);if(Object.isFunction(d[0])){e=d.shift()}function a(){this.initialize.apply(this,arguments)}Object.extend(a,Class.Methods);a.superclass=e;a.subclasses=[];if(e){var b=function(){};b.prototype=e.prototype;a.prototype=new b;e.subclasses.push(a)}for(var c=0;c<d.length;c++){a.addMethods(d[c])}if(!a.prototype.initialize){a.prototype.initialize=Prototype.emptyFunction}a.prototype.constructor=a;return a}};Class.Methods={addMethods:function(g){var c=this.superclass&&this.superclass.prototype;var b=Object.keys(g);if(!Object.keys({toString:true}).length){b.push("toString","valueOf")}for(var a=0,d=b.length;a<d;a++){var f=b[a],e=g[f];if(c&&Object.isFunction(e)&&e.argumentNames().first()=="$super"){var h=e;e=(function(i){return function(){return c[i].apply(this,arguments)}})(f).wrap(h);e.valueOf=h.valueOf.bind(h);e.toString=h.toString.bind(h)}this.prototype[f]=e}return this}};var Abstract={};Object.extend=function(a,c){for(var b in c){a[b]=c[b]}return a};Object.extend(Object,{inspect:function(a){try{if(Object.isUndefined(a)){return"undefined"}if(a===null){return"null"}return a.inspect?a.inspect():String(a)}catch(b){if(b instanceof RangeError){return"..."}throw b}},toJSON:function(a){var c=typeof a;switch(c){case"undefined":case"function":case"unknown":return;case"boolean":return a.toString()}if(a===null){return"null"}if(a.toJSON){return a.toJSON()}if(Object.isElement(a)){return}var b=[];for(var e in a){var d=Object.toJSON(a[e]);if(!Object.isUndefined(d)){b.push(e.toJSON()+": "+d)}}return"{"+b.join(", ")+"}"},toQueryString:function(a){return $H(a).toQueryString()},toHTML:function(a){return a&&a.toHTML?a.toHTML():String.interpret(a)},keys:function(a){var b=[];for(var c in a){b.push(c)}return b},values:function(b){var a=[];for(var c in b){a.push(b[c])}return a},clone:function(a){return Object.extend({},a)},isElement:function(a){return!!(a&&a.nodeType==1)},isArray:function(a){return a!=null&&typeof a=="object"&&"splice"in a&&"join"in a},isHash:function(a){return a instanceof Hash},isFunction:function(a){return typeof a=="function"},isString:function(a){return typeof a=="string"},isNumber:function(a){return typeof a=="number"},isUndefined:function(a){return typeof a=="undefined"}});Object.extend(Function.prototype,{argumentNames:function(){var a=this.toString().match(/^[\s\(]*function[^(]*\(([^\)]*)\)/)[1].replace(/\s+/g,"").split(",");return a.length==1&&!a[0]?[]:a},bind:function(){if(arguments.length<2&&Object.isUndefined(arguments[0])){return this}var a=this,c=$A(arguments),b=c.shift();return function(){return a.apply(b,c.concat($A(arguments)))}},bindAsEventListener:function(){var a=this,c=$A(arguments),b=c.shift();return function(d){return a.apply(b,[d||window.event].concat(c))}},curry:function(){if(!arguments.length){return this}var a=this,b=$A(arguments);return function(){return a.apply(this,b.concat($A(arguments)))}},delay:function(){var a=this,b=$A(arguments),c=b.shift()*1000;return window.setTimeout(function(){return a.apply(a,b)},c)},defer:function(){var a=[0.01].concat($A(arguments));return this.delay.apply(this,a)},wrap:function(b){var a=this;return function(){return b.apply(this,[a.bind(this)].concat($A(arguments)))}},methodize:function(){if(this._methodized){return this._methodized}var a=this;return this._methodized=function(){return a.apply(null,[this].concat($A(arguments)))}}});Date.prototype.toJSON=function(){return'"'+this.getUTCFullYear()+"-"+(this.getUTCMonth()+1).toPaddedString(2)+"-"+this.getUTCDate().toPaddedString(2)+"T"+this.getUTCHours().toPaddedString(2)+":"+this.getUTCMinutes().toPaddedString(2)+":"+this.getUTCSeconds().toPaddedString(2)+'Z"'};var Try={these:function(){var c;for(var b=0,d=arguments.length;b<d;b++){var a=arguments[b];try{c=a();break}catch(f){}}return c}};RegExp.prototype.match=RegExp.prototype.test;RegExp.escape=function(a){return String(a).replace(/([.*+?^=!:${}()|[\]\/\\])/g,"\\$1")};var PeriodicalExecuter=Class.create({initialize:function(b,a){this.callback=b;this.frequency=a;this.currentlyExecuting=false;this.registerCallback()},registerCallback:function(){this.timer=setInterval(this.onTimerEvent.bind(this),this.frequency*1000)},execute:function(){this.callback(this)},stop:function(){if(!this.timer){return}clearInterval(this.timer);this.timer=null},onTimerEvent:function(){if(!this.currentlyExecuting){try{this.currentlyExecuting=true;this.execute()}finally{this.currentlyExecuting=false}}}});Object.extend(String,{interpret:function(a){return a==null?"":String(a)},specialChar:{"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r","\\":"\\\\"}});Object.extend(String.prototype,{gsub:function(e,c){var a="",d=this,b;c=arguments.callee.prepareReplacement(c);while(d.length>0){if(b=d.match(e)){a+=d.slice(0,b.index);a+=String.interpret(c(b));d=d.slice(b.index+b[0].length)}else{a+=d,d=""}}return a},sub:function(c,a,b){a=this.gsub.prepareReplacement(a);b=Object.isUndefined(b)?1:b;return this.gsub(c,function(d){if(--b<0){return d[0]}return a(d)})},scan:function(b,a){this.gsub(b,a);return String(this)},truncate:function(b,a){b=b||30;a=Object.isUndefined(a)?"...":a;return this.length>b?this.slice(0,b-a.length)+a:String(this)},strip:function(){return this.replace(/^\s+/,"").replace(/\s+$/,"")},stripTags:function(){return this.replace(/<\/?[^>]+>/gi,"")},stripScripts:function(){return this.replace(new RegExp(Prototype.ScriptFragment,"img"),"")},extractScripts:function(){var b=new RegExp(Prototype.ScriptFragment,"img");var a=new RegExp(Prototype.ScriptFragment,"im");return(this.match(b)||[]).map(function(c){return(c.match(a)||["",""])[1]})},evalScripts:function(){return this.extractScripts().map(function(script){return eval(script)})},escapeHTML:function(){var a=arguments.callee;a.text.data=this;return a.div.innerHTML},unescapeHTML:function(){var a=new Element("div");a.innerHTML=this.stripTags();return a.childNodes[0]?(a.childNodes.length>1?$A(a.childNodes).inject("",function(b,c){return b+c.nodeValue}):a.childNodes[0].nodeValue):""},toQueryParams:function(b){var a=this.strip().match(/([^?#]*)(#.*)?$/);if(!a){return{}}return a[1].split(b||"&").inject({},function(e,f){if((f=f.split("="))[0]){var c=decodeURIComponent(f.shift());var d=f.length>1?f.join("="):f[0];if(d!=undefined){d=decodeURIComponent(d)}if(c in e){if(!Object.isArray(e[c])){e[c]=[e[c]]}e[c].push(d)}else{e[c]=d}}return e})},toArray:function(){return this.split("")},succ:function(){return this.slice(0,this.length-1)+String.fromCharCode(this.charCodeAt(this.length-1)+1)},times:function(a){return a<1?"":new Array(a+1).join(this)},camelize:function(){var d=this.split("-"),a=d.length;if(a==1){return d[0]}var c=this.charAt(0)=="-"?d[0].charAt(0).toUpperCase()+d[0].substring(1):d[0];for(var b=1;b<a;b++){c+=d[b].charAt(0).toUpperCase()+d[b].substring(1)}return c},capitalize:function(){return this.charAt(0).toUpperCase()+this.substring(1).toLowerCase()},underscore:function(){return this.gsub(/::/,"/").gsub(/([A-Z]+)([A-Z][a-z])/,"#{1}_#{2}").gsub(/([a-z\d])([A-Z])/,"#{1}_#{2}").gsub(/-/,"_").toLowerCase()},dasherize:function(){return this.gsub(/_/,"-")},inspect:function(b){var a=this.gsub(/[\x00-\x1f\\]/,function(c){var d=String.specialChar[c[0]];return d?d:"\\u00"+c[0].charCodeAt().toPaddedString(2,16)});if(b){return'"'+a.replace(/"/g,'\\"')+'"'}return"'"+a.replace(/'/g,"\\'")+"'"},toJSON:function(){return this.inspect(true)},unfilterJSON:function(a){return this.sub(a||Prototype.JSONFilter,"#{1}")},isJSON:function(){var a=this;if(a.blank()){return false}a=this.replace(/\\./g,"@").replace(/"[^"\\\n\r]*"/g,"");return(/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(a)},evalJSON:function(sanitize){var json=this.unfilterJSON();try{if(!sanitize||json.isJSON()){return eval("("+json+")")}}catch(e){}throw new SyntaxError("Badly formed JSON string: "+this.inspect())},include:function(a){return this.indexOf(a)>-1},startsWith:function(a){return this.indexOf(a)===0},endsWith:function(a){var b=this.length-a.length;return b>=0&&this.lastIndexOf(a)===b},empty:function(){return this==""},blank:function(){return/^\s*$/.test(this)},interpolate:function(a,b){return new Template(this,b).evaluate(a)}});if(Prototype.Browser.WebKit||Prototype.Browser.IE){Object.extend(String.prototype,{escapeHTML:function(){return this.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")},unescapeHTML:function(){return this.stripTags().replace(/&amp;/g,"&").replace(/&lt;/g,"<").replace(/&gt;/g,">")}})}String.prototype.gsub.prepareReplacement=function(b){if(Object.isFunction(b)){return b}var a=new Template(b);return function(c){return a.evaluate(c)}};String.prototype.parseQuery=String.prototype.toQueryParams;Object.extend(String.prototype.escapeHTML,{div:document.createElement("div"),text:document.createTextNode("")});String.prototype.escapeHTML.div.appendChild(String.prototype.escapeHTML.text);var Template=Class.create({initialize:function(a,b){this.template=a.toString();this.pattern=b||Template.Pattern},evaluate:function(a){if(Object.isFunction(a.toTemplateReplacements)){a=a.toTemplateReplacements()}return this.template.gsub(this.pattern,function(d){if(a==null){return""}var f=d[1]||"";if(f=="\\"){return d[2]}var b=a,g=d[3];var e=/^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;d=e.exec(g);if(d==null){return f}while(d!=null){var c=d[1].startsWith("[")?d[2].gsub("\\\\]","]"):d[1];b=b[c];if(null==b||""==d[3]){break}g=g.substring("["==d[3]?d[1].length:d[0].length);d=e.exec(g)}return f+String.interpret(b)})}});Template.Pattern=/(^|.|\r|\n)(#\{(.*?)\})/;var $break={};var Enumerable={each:function(c,b){var a=0;try{this._each(function(e){c.call(b,e,a++)})}catch(d){if(d!=$break){throw d}}return this},eachSlice:function(d,c,b){var a=-d,e=[],f=this.toArray();if(d<1){return f}while((a+=d)<f.length){e.push(f.slice(a,a+d))}return e.collect(c,b)},all:function(c,b){c=c||Prototype.K;var a=true;this.each(function(e,d){a=a&&!!c.call(b,e,d);if(!a){throw $break}});return a},any:function(c,b){c=c||Prototype.K;var a=false;this.each(function(e,d){if(a=!!c.call(b,e,d)){throw $break}});return a},collect:function(c,b){c=c||Prototype.K;var a=[];this.each(function(e,d){a.push(c.call(b,e,d))});return a},detect:function(c,b){var a;this.each(function(e,d){if(c.call(b,e,d)){a=e;throw $break}});return a},findAll:function(c,b){var a=[];this.each(function(e,d){if(c.call(b,e,d)){a.push(e)}});return a},grep:function(d,c,b){c=c||Prototype.K;var a=[];if(Object.isString(d)){d=new RegExp(d)}this.each(function(f,e){if(d.match(f)){a.push(c.call(b,f,e))}});return a},include:function(a){if(Object.isFunction(this.indexOf)){if(this.indexOf(a)!=-1){return true}}var b=false;this.each(function(c){if(c==a){b=true;throw $break}});return b},inGroupsOf:function(b,a){a=Object.isUndefined(a)?null:a;return this.eachSlice(b,function(c){while(c.length<b){c.push(a)}return c})},inject:function(a,c,b){this.each(function(e,d){a=c.call(b,a,e,d)});return a},invoke:function(b){var a=$A(arguments).slice(1);return this.map(function(c){return c[b].apply(c,a)})},max:function(c,b){c=c||Prototype.K;var a;this.each(function(e,d){e=c.call(b,e,d);if(a==null||e>=a){a=e}});return a},min:function(c,b){c=c||Prototype.K;var a;this.each(function(e,d){e=c.call(b,e,d);if(a==null||e<a){a=e}});return a},partition:function(d,b){d=d||Prototype.K;var c=[],a=[];this.each(function(f,e){(d.call(b,f,e)?c:a).push(f)});return[c,a]},pluck:function(b){var a=[];this.each(function(c){a.push(c[b])});return a},reject:function(c,b){var a=[];this.each(function(e,d){if(!c.call(b,e,d)){a.push(e)}});return a},sortBy:function(b,a){return this.map(function(d,c){return{value:d,criteria:b.call(a,d,c)}}).sort(function(f,e){var d=f.criteria,c=e.criteria;return d<c?-1:d>c?1:0}).pluck("value")},toArray:function(){return this.map()},zip:function(){var b=Prototype.K,a=$A(arguments);if(Object.isFunction(a.last())){b=a.pop()}var c=[this].concat(a).map($A);return this.map(function(e,d){return b(c.pluck(d))})},size:function(){return this.toArray().length},inspect:function(){return"#<Enumerable:"+this.toArray().inspect()+">"}};Object.extend(Enumerable,{map:Enumerable.collect,find:Enumerable.detect,select:Enumerable.findAll,filter:Enumerable.findAll,member:Enumerable.include,entries:Enumerable.toArray,every:Enumerable.all,some:Enumerable.any});function $A(c){if(!c){return[]}if(c.toArray){return c.toArray()}var b=c.length||0,a=new Array(b);while(b--){a[b]=c[b]}return a}if(Prototype.Browser.WebKit){$A=function(c){if(!c){return[]}if(!(typeof c==="function"&&typeof c.length==="number"&&typeof c.item==="function")&&c.toArray){return c.toArray()}var b=c.length||0,a=new Array(b);while(b--){a[b]=c[b]}return a}}Array.from=$A;Object.extend(Array.prototype,Enumerable);if(!Array.prototype._reverse){Array.prototype._reverse=Array.prototype.reverse}Object.extend(Array.prototype,{_each:function(b){for(var a=0,c=this.length;a<c;a++){b(this[a])}},clear:function(){this.length=0;return this},first:function(){return this[0]},last:function(){return this[this.length-1]},compact:function(){return this.select(function(a){return a!=null})},flatten:function(){return this.inject([],function(b,a){return b.concat(Object.isArray(a)?a.flatten():[a])})},without:function(){var a=$A(arguments);return this.select(function(b){return!a.include(b)})},reverse:function(a){return(a!==false?this:this.toArray())._reverse()},reduce:function(){return this.length>1?this:this[0]},uniq:function(a){return this.inject([],function(d,c,b){if(0==b||(a?d.last()!=c:!d.include(c))){d.push(c)}return d})},intersect:function(a){return this.uniq().findAll(function(b){return a.detect(function(c){return b===c})})},clone:function(){return[].concat(this)},size:function(){return this.length},inspect:function(){return"["+this.map(Object.inspect).join(", ")+"]"},toJSON:function(){var a=[];this.each(function(b){var c=Object.toJSON(b);if(!Object.isUndefined(c)){a.push(c)}});return"["+a.join(", ")+"]"}});if(Object.isFunction(Array.prototype.forEach)){Array.prototype._each=Array.prototype.forEach}if(!Array.prototype.indexOf){Array.prototype.indexOf=function(c,a){a||(a=0);var b=this.length;if(a<0){a=b+a}for(;a<b;a++){if(this[a]===c){return a}}return-1}}if(!Array.prototype.lastIndexOf){Array.prototype.lastIndexOf=function(b,a){a=isNaN(a)?this.length:(a<0?this.length+a:a)+1;var c=this.slice(0,a).reverse().indexOf(b);return(c<0)?c:a-c-1}}Array.prototype.toArray=Array.prototype.clone;function $w(a){if(!Object.isString(a)){return[]}a=a.strip();return a?a.split(/\s+/):[]}if(Prototype.Browser.Opera){Array.prototype.concat=function(){var e=[];for(var b=0,c=this.length;b<c;b++){e.push(this[b])}for(var b=0,c=arguments.length;b<c;b++){if(Object.isArray(arguments[b])){for(var a=0,d=arguments[b].length;a<d;a++){e.push(arguments[b][a])}}else{e.push(arguments[b])}}return e}}Object.extend(Number.prototype,{toColorPart:function(){return this.toPaddedString(2,16)},succ:function(){return this+1},times:function(b,a){$R(0,this,true).each(b,a);return this},toPaddedString:function(c,b){var a=this.toString(b||10);return"0".times(c-a.length)+a},toJSON:function(){return isFinite(this)?this.toString():"null"}});$w("abs round ceil floor").each(function(a){Number.prototype[a]=Math[a].methodize()});function $H(a){return new Hash(a)}var Hash=Class.create(Enumerable,(function(){function a(b,c){if(Object.isUndefined(c)){return b}return b+"="+encodeURIComponent(String.interpret(c))}return{initialize:function(b){this._object=Object.isHash(b)?b.toObject():Object.clone(b)},_each:function(c){for(var b in this._object){var d=this._object[b],e=[b,d];e.key=b;e.value=d;c(e)}},set:function(b,c){return this._object[b]=c},get:function(b){if(this._object[b]!==Object.prototype[b]){return this._object[b]}},unset:function(b){var c=this._object[b];delete this._object[b];return c},toObject:function(){return Object.clone(this._object)},keys:function(){return this.pluck("key")},values:function(){return this.pluck("value")},index:function(c){var b=this.detect(function(d){return d.value===c});return b&&b.key},merge:function(b){return this.clone().update(b)},update:function(b){return new Hash(b).inject(this,function(c,d){c.set(d.key,d.value);return c})},toQueryString:function(){return this.inject([],function(d,e){var c=encodeURIComponent(e.key),b=e.value;if(b&&typeof b=="object"){if(Object.isArray(b)){return d.concat(b.map(a.curry(c)))}}else{d.push(a(c,b))}return d}).join("&")},inspect:function(){return"#<Hash:{"+this.map(function(b){return b.map(Object.inspect).join(": ")}).join(", ")+"}>"},toJSON:function(){return Object.toJSON(this.toObject())},clone:function(){return new Hash(this)}}})());Hash.prototype.toTemplateReplacements=Hash.prototype.toObject;Hash.from=$H;var ObjectRange=Class.create(Enumerable,{initialize:function(c,a,b){this.start=c;this.end=a;this.exclusive=b},_each:function(a){var b=this.start;while(this.include(b)){a(b);b=b.succ()}},include:function(a){if(a<this.start){return false}if(this.exclusive){return a<this.end}return a<=this.end}});var $R=function(c,a,b){return new ObjectRange(c,a,b)};var Ajax={getTransport:function(){return Try.these(function(){return new XMLHttpRequest()},function(){return new ActiveXObject("Msxml2.XMLHTTP")},function(){return new ActiveXObject("Microsoft.XMLHTTP")})||false},activeRequestCount:0};Ajax.Responders={responders:[],_each:function(a){this.responders._each(a)},register:function(a){if(!this.include(a)){this.responders.push(a)}},unregister:function(a){this.responders=this.responders.without(a)},dispatch:function(d,b,c,a){this.each(function(f){if(Object.isFunction(f[d])){try{f[d].apply(f,[b,c,a])}catch(g){}}})}};Object.extend(Ajax.Responders,Enumerable);Ajax.Responders.register({onCreate:function(){Ajax.activeRequestCount++},onComplete:function(){Ajax.activeRequestCount--}});Ajax.Base=Class.create({initialize:function(a){this.options={method:"post",asynchronous:true,contentType:"application/x-www-form-urlencoded",encoding:"UTF-8",parameters:"",evalJSON:true,evalJS:true};Object.extend(this.options,a||{});this.options.method=this.options.method.toLowerCase();if(Object.isString(this.options.parameters)){this.options.parameters=this.options.parameters.toQueryParams()}else{if(Object.isHash(this.options.parameters)){this.options.parameters=this.options.parameters.toObject()}}}});Ajax.Request=Class.create(Ajax.Base,{_complete:false,initialize:function($super,b,a){$super(a);this.transport=Ajax.getTransport();this.request(b)},request:function(b){this.url=b;this.method=this.options.method;var d=Object.clone(this.options.parameters);if(!["get","post"].include(this.method)){d._method=this.method;this.method="post"}this.parameters=d;if(d=Object.toQueryString(d)){if(this.method=="get"){this.url+=(this.url.include("?")?"&":"?")+d}else{if(/Konqueror|Safari|KHTML/.test(navigator.userAgent)){d+="&_="}}}try{var a=new Ajax.Response(this);if(this.options.onCreate){this.options.onCreate(a)}Ajax.Responders.dispatch("onCreate",this,a);this.transport.open(this.method.toUpperCase(),this.url,this.options.asynchronous);if(this.options.asynchronous){this.respondToReadyState.bind(this).defer(1)}this.transport.onreadystatechange=this.onStateChange.bind(this);this.setRequestHeaders();this.body=this.method=="post"?(this.options.postBody||d):null;this.transport.send(this.body);if(!this.options.asynchronous&&this.transport.overrideMimeType){this.onStateChange()}}catch(c){this.dispatchException(c)}},onStateChange:function(){var a=this.transport.readyState;if(a>1&&!((a==4)&&this._complete)){this.respondToReadyState(this.transport.readyState)}},setRequestHeaders:function(){var e={"X-Requested-With":"XMLHttpRequest","X-Prototype-Version":Prototype.Version,Accept:"text/javascript, text/html, application/xml, text/xml, */*"};if(this.method=="post"){e["Content-type"]=this.options.contentType+(this.options.encoding?"; charset="+this.options.encoding:"");if(this.transport.overrideMimeType&&(navigator.userAgent.match(/Gecko\/(\d{4})/)||[0,2005])[1]<2005){e.Connection="close"}}if(typeof this.options.requestHeaders=="object"){var c=this.options.requestHeaders;if(Object.isFunction(c.push)){for(var b=0,d=c.length;b<d;b+=2){e[c[b]]=c[b+1]}}else{$H(c).each(function(f){e[f.key]=f.value})}}for(var a in e){this.transport.setRequestHeader(a,e[a])}},success:function(){var a=this.getStatus();return!a||(a>=200&&a<300)},getStatus:function(){try{return this.transport.status||0}catch(a){return 0}},respondToReadyState:function(a){var c=Ajax.Request.Events[a],b=new Ajax.Response(this);if(c=="Complete"){try{this._complete=true;(this.options["on"+b.status]||this.options["on"+(this.success()?"Success":"Failure")]||Prototype.emptyFunction)(b,b.headerJSON)}catch(d){this.dispatchException(d)}var f=b.getHeader("Content-type");if(this.options.evalJS=="force"||(this.options.evalJS&&this.isSameOrigin()&&f&&f.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i))){this.evalResponse()}}try{(this.options["on"+c]||Prototype.emptyFunction)(b,b.headerJSON);Ajax.Responders.dispatch("on"+c,this,b,b.headerJSON)}catch(d){this.dispatchException(d)}if(c=="Complete"){this.transport.onreadystatechange=Prototype.emptyFunction}},isSameOrigin:function(){var a=this.url.match(/^\s*https?:\/\/[^\/]*/);return!a||(a[0]=="#{protocol}//#{domain}#{port}".interpolate({protocol:location.protocol,domain:document.domain,port:location.port?":"+location.port:""}))},getHeader:function(a){try{return this.transport.getResponseHeader(a)||null}catch(b){return null}},evalResponse:function(){try{return eval((this.transport.responseText||"").unfilterJSON())}catch(e){this.dispatchException(e)}},dispatchException:function(a){(this.options.onException||Prototype.emptyFunction)(this,a);Ajax.Responders.dispatch("onException",this,a)}});Ajax.Request.Events=["Uninitialized","Loading","Loaded","Interactive","Complete"];Ajax.Response=Class.create({initialize:function(c){this.request=c;var d=this.transport=c.transport,a=this.readyState=d.readyState;if((a>2&&!Prototype.Browser.IE)||a==4){this.status=this.getStatus();this.statusText=this.getStatusText();this.responseText=String.interpret(d.responseText);this.headerJSON=this._getHeaderJSON()}if(a==4){var b=d.responseXML;this.responseXML=Object.isUndefined(b)?null:b;this.responseJSON=this._getResponseJSON()}},status:0,statusText:"",getStatus:Ajax.Request.prototype.getStatus,getStatusText:function(){try{return this.transport.statusText||""}catch(a){return""}},getHeader:Ajax.Request.prototype.getHeader,getAllHeaders:function(){try{return this.getAllResponseHeaders()}catch(a){return null}},getResponseHeader:function(a){return this.transport.getResponseHeader(a)},getAllResponseHeaders:function(){return this.transport.getAllResponseHeaders()},_getHeaderJSON:function(){var a=this.getHeader("X-JSON");if(!a){return null}a=decodeURIComponent(escape(a));try{return a.evalJSON(this.request.options.sanitizeJSON||!this.request.isSameOrigin())}catch(b){this.request.dispatchException(b)}},_getResponseJSON:function(){var a=this.request.options;if(!a.evalJSON||(a.evalJSON!="force"&&!(this.getHeader("Content-type")||"").include("application/json"))||this.responseText.blank()){return null}try{return this.responseText.evalJSON(a.sanitizeJSON||!this.request.isSameOrigin())}catch(b){this.request.dispatchException(b)}}});Ajax.Updater=Class.create(Ajax.Request,{initialize:function($super,a,c,b){this.container={success:(a.success||a),failure:(a.failure||(a.success?null:a))};b=Object.clone(b);var d=b.onComplete;b.onComplete=(function(e,f){this.updateContent(e.responseText);if(Object.isFunction(d)){d(e,f)}}).bind(this);$super(c,b)},updateContent:function(d){var c=this.container[this.success()?"success":"failure"],a=this.options;if(!a.evalScripts){d=d.stripScripts()}if(c=$(c)){if(a.insertion){if(Object.isString(a.insertion)){var b={};b[a.insertion]=d;c.insert(b)}else{a.insertion(c,d)}}else{c.update(d)}}}});Ajax.PeriodicalUpdater=Class.create(Ajax.Base,{initialize:function($super,a,c,b){$super(b);this.onComplete=this.options.onComplete;this.frequency=(this.options.frequency||2);this.decay=(this.options.decay||1);this.updater={};this.container=a;this.url=c;this.start()},start:function(){this.options.onComplete=this.updateComplete.bind(this);this.onTimerEvent()},stop:function(){this.updater.options.onComplete=undefined;clearTimeout(this.timer);(this.onComplete||Prototype.emptyFunction).apply(this,arguments)},updateComplete:function(a){if(this.options.decay){this.decay=(a.responseText==this.lastText?this.decay*this.options.decay:1);this.lastText=a.responseText}this.timer=this.onTimerEvent.bind(this).delay(this.decay*this.frequency)},onTimerEvent:function(){this.updater=new Ajax.Updater(this.container,this.url,this.options)}});function $(b){if(arguments.length>1){for(var a=0,d=[],c=arguments.length;a<c;a++){d.push($(arguments[a]))}return d}if(Object.isString(b)){b=document.getElementById(b)}return Element.extend(b)}if(Prototype.BrowserFeatures.XPath){document._getElementsByXPath=function(f,a){var c=[];var e=document.evaluate(f,$(a)||document,null,XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,null);for(var b=0,d=e.snapshotLength;b<d;b++){c.push(Element.extend(e.snapshotItem(b)))}return c}}if(!window.Node){var Node={}}if(!Node.ELEMENT_NODE){Object.extend(Node,{ELEMENT_NODE:1,ATTRIBUTE_NODE:2,TEXT_NODE:3,CDATA_SECTION_NODE:4,ENTITY_REFERENCE_NODE:5,ENTITY_NODE:6,PROCESSING_INSTRUCTION_NODE:7,COMMENT_NODE:8,DOCUMENT_NODE:9,DOCUMENT_TYPE_NODE:10,DOCUMENT_FRAGMENT_NODE:11,NOTATION_NODE:12})}(function(){var a=this.Element;this.Element=function(d,c){c=c||{};d=d.toLowerCase();var b=Element.cache;if(Prototype.Browser.IE&&c.name){d="<"+d+' name="'+c.name+'">';delete c.name;return Element.writeAttribute(document.createElement(d),c)}if(!b[d]){b[d]=Element.extend(document.createElement(d))}return Element.writeAttribute(b[d].cloneNode(false),c)};Object.extend(this.Element,a||{});if(a){this.Element.prototype=a.prototype}}).call(window);Element.cache={};Element.Methods={visible:function(a){return $(a).style.display!="none"},toggle:function(a){a=$(a);Element[Element.visible(a)?"hide":"show"](a);return a},hide:function(a){a=$(a);a.style.display="none";return a},show:function(a){a=$(a);a.style.display="";return a},remove:function(a){a=$(a);a.parentNode.removeChild(a);return a},update:function(a,b){a=$(a);if(b&&b.toElement){b=b.toElement()}if(Object.isElement(b)){return a.update().insert(b)}b=Object.toHTML(b);a.innerHTML=b.stripScripts();b.evalScripts.bind(b).defer();return a},replace:function(b,c){b=$(b);if(c&&c.toElement){c=c.toElement()}else{if(!Object.isElement(c)){c=Object.toHTML(c);var a=b.ownerDocument.createRange();a.selectNode(b);c.evalScripts.bind(c).defer();c=a.createContextualFragment(c.stripScripts())}}b.parentNode.replaceChild(c,b);return b},insert:function(c,e){c=$(c);if(Object.isString(e)||Object.isNumber(e)||Object.isElement(e)||(e&&(e.toElement||e.toHTML))){e={bottom:e}}var d,f,b,g;for(var a in e){d=e[a];a=a.toLowerCase();f=Element._insertionTranslations[a];if(d&&d.toElement){d=d.toElement()}if(Object.isElement(d)){f(c,d);continue}d=Object.toHTML(d);b=((a=="before"||a=="after")?c.parentNode:c).tagName.toUpperCase();g=Element._getContentFromAnonymousElement(b,d.stripScripts());if(a=="top"||a=="after"){g.reverse()}g.each(f.curry(c));d.evalScripts.bind(d).defer()}return c},wrap:function(b,c,a){b=$(b);if(Object.isElement(c)){$(c).writeAttribute(a||{})}else{if(Object.isString(c)){c=new Element(c,a)}else{c=new Element("div",c)}}if(b.parentNode){b.parentNode.replaceChild(c,b)}c.appendChild(b);return c},inspect:function(b){b=$(b);var a="<"+b.tagName.toLowerCase();$H({id:"id",className:"class"}).each(function(f){var e=f.first(),c=f.last();var d=(b[e]||"").toString();if(d){a+=" "+c+"="+d.inspect(true)}});return a+">"},recursivelyCollect:function(a,c){a=$(a);var b=[];while(a=a[c]){if(a.nodeType==1){b.push(Element.extend(a))}}return b},ancestors:function(a){return $(a).recursivelyCollect("parentNode")},descendants:function(a){return $(a).select("*")},firstDescendant:function(a){a=$(a).firstChild;while(a&&a.nodeType!=1){a=a.nextSibling}return $(a)},immediateDescendants:function(a){if(!(a=$(a).firstChild)){return[]}while(a&&a.nodeType!=1){a=a.nextSibling}if(a){return[a].concat($(a).nextSiblings())}return[]},previousSiblings:function(a){return $(a).recursivelyCollect("previousSibling")},nextSiblings:function(a){return $(a).recursivelyCollect("nextSibling")},siblings:function(a){a=$(a);return a.previousSiblings().reverse().concat(a.nextSiblings())},match:function(b,a){if(Object.isString(a)){a=new Selector(a)}return a.match($(b))},up:function(b,d,a){b=$(b);if(arguments.length==1){return $(b.parentNode)}var c=b.ancestors();return Object.isNumber(d)?c[d]:Selector.findElement(c,d,a)},down:function(b,c,a){b=$(b);if(arguments.length==1){return b.firstDescendant()}return Object.isNumber(c)?b.descendants()[c]:Element.select(b,c)[a||0]},previous:function(b,d,a){b=$(b);if(arguments.length==1){return $(Selector.handlers.previousElementSibling(b))}var c=b.previousSiblings();return Object.isNumber(d)?c[d]:Selector.findElement(c,d,a)},next:function(c,d,b){c=$(c);if(arguments.length==1){return $(Selector.handlers.nextElementSibling(c))}var a=c.nextSiblings();return Object.isNumber(d)?a[d]:Selector.findElement(a,d,b)},select:function(){var a=$A(arguments),b=$(a.shift());return Selector.findChildElements(b,a)},adjacent:function(){var a=$A(arguments),b=$(a.shift());return Selector.findChildElements(b.parentNode,a).without(b)},identify:function(b){b=$(b);var c=b.readAttribute("id"),a=arguments.callee;if(c){return c}do{c="anonymous_element_"+a.counter++}while($(c));b.writeAttribute("id",c);return c},readAttribute:function(c,a){c=$(c);if(Prototype.Browser.IE){var b=Element._attributeTranslations.read;if(b.values[a]){return b.values[a](c,a)}if(b.names[a]){a=b.names[a]}if(a.include(":")){return(!c.attributes||!c.attributes[a])?null:c.attributes[a].value}}return c.getAttribute(a)},writeAttribute:function(e,c,f){e=$(e);var b={},d=Element._attributeTranslations.write;if(typeof c=="object"){b=c}else{b[c]=Object.isUndefined(f)?true:f}for(var a in b){c=d.names[a]||a;f=b[a];if(d.values[a]){c=d.values[a](e,f)}if(f===false||f===null){e.removeAttribute(c)}else{if(f===true){e.setAttribute(c,c)}else{e.setAttribute(c,f)}}}return e},getHeight:function(a){return $(a).getDimensions().height},getWidth:function(a){return $(a).getDimensions().width},classNames:function(a){return new Element.ClassNames(a)},hasClassName:function(a,b){if(!(a=$(a))){return}var c=a.className;return(c.length>0&&(c==b||new RegExp("(^|\\s)"+b+"(\\s|$)").test(c)))},addClassName:function(a,b){if(!(a=$(a))){return}if(!a.hasClassName(b)){a.className+=(a.className?" ":"")+b}return a},removeClassName:function(a,b){if(!(a=$(a))){return}a.className=a.className.replace(new RegExp("(^|\\s+)"+b+"(\\s+|$)")," ").strip();return a},toggleClassName:function(a,b){if(!(a=$(a))){return}return a[a.hasClassName(b)?"removeClassName":"addClassName"](b)},cleanWhitespace:function(b){b=$(b);var c=b.firstChild;while(c){var a=c.nextSibling;if(c.nodeType==3&&!/\S/.test(c.nodeValue)){b.removeChild(c)}c=a}return b},empty:function(a){return $(a).innerHTML.blank()},descendantOf:function(b,a){b=$(b),a=$(a);if(b.compareDocumentPosition){return(b.compareDocumentPosition(a)&8)===8}if(a.contains){return a.contains(b)&&a!==b}while(b=b.parentNode){if(b==a){return true}}return false},scrollTo:function(a){a=$(a);var b=a.cumulativeOffset();window.scrollTo(b[0],b[1]);return a},getStyle:function(b,c){b=$(b);c=c=="float"?"cssFloat":c.camelize();var d=b.style[c];if(!d||d=="auto"){var a=document.defaultView.getComputedStyle(b,null);d=a?a[c]:null}if(c=="opacity"){return d?parseFloat(d):1}return d=="auto"?null:d},getOpacity:function(a){return $(a).getStyle("opacity")},setStyle:function(b,c){b=$(b);var e=b.style,a;if(Object.isString(c)){b.style.cssText+=";"+c;return c.include("opacity")?b.setOpacity(c.match(/opacity:\s*(\d?\.?\d*)/)[1]):b}for(var d in c){if(d=="opacity"){b.setOpacity(c[d])}else{e[(d=="float"||d=="cssFloat")?(Object.isUndefined(e.styleFloat)?"cssFloat":"styleFloat"):d]=c[d]}}return b},setOpacity:function(a,b){a=$(a);a.style.opacity=(b==1||b==="")?"":(b<0.00001)?0:b;return a},getDimensions:function(c){c=$(c);var g=c.getStyle("display");if(g!="none"&&g!=null){return{width:c.offsetWidth,height:c.offsetHeight}}var b=c.style;var f=b.visibility;var d=b.position;var a=b.display;b.visibility="hidden";b.position="absolute";b.display="block";var h=c.clientWidth;var e=c.clientHeight;b.display=a;b.position=d;b.visibility=f;return{width:h,height:e}},makePositioned:function(a){a=$(a);var b=Element.getStyle(a,"position");if(b=="static"||!b){a._madePositioned=true;a.style.position="relative";if(Prototype.Browser.Opera){a.style.top=0;a.style.left=0}}return a},undoPositioned:function(a){a=$(a);if(a._madePositioned){a._madePositioned=undefined;a.style.position=a.style.top=a.style.left=a.style.bottom=a.style.right=""}return a},makeClipping:function(a){a=$(a);if(a._overflow){return a}a._overflow=Element.getStyle(a,"overflow")||"auto";if(a._overflow!=="hidden"){a.style.overflow="hidden"}return a},undoClipping:function(a){a=$(a);if(!a._overflow){return a}a.style.overflow=a._overflow=="auto"?"":a._overflow;a._overflow=null;return a},cumulativeOffset:function(b){var a=0,c=0;do{a+=b.offsetTop||0;c+=b.offsetLeft||0;b=b.offsetParent}while(b);return Element._returnOffset(c,a)},positionedOffset:function(b){var a=0,d=0;do{a+=b.offsetTop||0;d+=b.offsetLeft||0;b=b.offsetParent;if(b){if(b.tagName.toUpperCase()=="BODY"){break}var c=Element.getStyle(b,"position");if(c!=="static"){break}}}while(b);return Element._returnOffset(d,a)},absolutize:function(b){b=$(b);if(b.getStyle("position")=="absolute"){return b}var d=b.positionedOffset();var f=d[1];var e=d[0];var c=b.clientWidth;var a=b.clientHeight;b._originalLeft=e-parseFloat(b.style.left||0);b._originalTop=f-parseFloat(b.style.top||0);b._originalWidth=b.style.width;b._originalHeight=b.style.height;b.style.position="absolute";b.style.top=f+"px";b.style.left=e+"px";b.style.width=c+"px";b.style.height=a+"px";return b},relativize:function(a){a=$(a);if(a.getStyle("position")=="relative"){return a}a.style.position="relative";var c=parseFloat(a.style.top||0)-(a._originalTop||0);var b=parseFloat(a.style.left||0)-(a._originalLeft||0);a.style.top=c+"px";a.style.left=b+"px";a.style.height=a._originalHeight;a.style.width=a._originalWidth;return a},cumulativeScrollOffset:function(b){var a=0,c=0;do{a+=b.scrollTop||0;c+=b.scrollLeft||0;b=b.parentNode}while(b);return Element._returnOffset(c,a)},getOffsetParent:function(b){b=$(b);var d=b.offsetParent,a=document.body,c=document.documentElement;if(d&&d!==c){return $(d)}if(d===c||b===c||b===a){return $(a)}while((b=b.parentNode)&&b!==a){if(Element.getStyle(b,"position")!="static"){return $(b)}}return $(a)},viewportOffset:function(d){d=$(d);var b=d,a=0,c=0;do{a+=b.offsetTop||0;c+=b.offsetLeft||0}while((b=b.getOffsetParent())!=document.body);b=d;do{if(!Prototype.Browser.Opera||(b.tagName&&(b.tagName.toUpperCase()=="BODY"))){a-=b.scrollTop||0;c-=b.scrollLeft||0}}while(b=b.parentNode);return Element._returnOffset(c,a)},clonePosition:function(b,d){var a=Object.extend({setLeft:true,setTop:true,setWidth:true,setHeight:true,offsetTop:0,offsetLeft:0},arguments[2]||{});d=$(d);var e=d.viewportOffset();b=$(b);var f=[0,0];var c=null;if(Element.getStyle(b,"position")=="absolute"){c=b.getOffsetParent();f=c.viewportOffset()}if(c==document.body){f[0]-=document.body.offsetLeft;f[1]-=document.body.offsetTop}if(a.setLeft){b.style.left=(e[0]-f[0]+a.offsetLeft)+"px"}if(a.setTop){b.style.top=(e[1]-f[1]+a.offsetTop)+"px"}if(a.setWidth){b.style.width=d.offsetWidth+"px"}if(a.setHeight){b.style.height=d.offsetHeight+"px"}return b}};Element.Methods.identify.counter=1;Object.extend(Element.Methods,{getElementsBySelector:Element.Methods.select,childElements:Element.Methods.immediateDescendants});Element._attributeTranslations={write:{names:{className:"class",htmlFor:"for"},values:{}}};if(Prototype.Browser.Opera){Element.Methods.getStyle=Element.Methods.getStyle.wrap(function(d,b,c){switch(c){case"left":case"top":case"right":case"bottom":if(d(b,"position")==="static"){return null}case"height":case"width":if(!Element.visible(b)){return null}var e=parseInt(d(b,c),10);if(e!==b["offset"+c.capitalize()]){return e+"px"}var a;if(c==="height"){a=["border-top-width","padding-top","padding-bottom","border-bottom-width"]}else{a=["border-left-width","padding-left","padding-right","border-right-width"]}return a.inject(e,function(f,g){var h=d(b,g);return h===null?f:f-parseInt(h,10)})+"px";default:return d(b,c)}});Element.Methods.readAttribute=Element.Methods.readAttribute.wrap(function(c,a,b){if(b==="title"){return a.title}return c(a,b)})}else{if(Prototype.Browser.IE){Element.Methods.getOffsetParent=Element.Methods.getOffsetParent.wrap(function(c,b){b=$(b);try{b.offsetParent}catch(f){return $(document.body)}var a=b.getStyle("position");if(a!=="static"){return c(b)}b.setStyle({position:"relative"});var d=c(b);b.setStyle({position:a});return d});$w("positionedOffset viewportOffset").each(function(a){Element.Methods[a]=Element.Methods[a].wrap(function(f,c){c=$(c);try{c.offsetParent}catch(h){return Element._returnOffset(0,0)}var b=c.getStyle("position");if(b!=="static"){return f(c)}var d=c.getOffsetParent();if(d&&d.getStyle("position")==="fixed"){d.setStyle({zoom:1})}c.setStyle({position:"relative"});var g=f(c);c.setStyle({position:b});return g})});Element.Methods.cumulativeOffset=Element.Methods.cumulativeOffset.wrap(function(b,a){try{a.offsetParent}catch(c){return Element._returnOffset(0,0)}return b(a)});Element.Methods.getStyle=function(a,b){a=$(a);b=(b=="float"||b=="cssFloat")?"styleFloat":b.camelize();var c=a.style[b];if(!c&&a.currentStyle){c=a.currentStyle[b]}if(b=="opacity"){if(c=(a.getStyle("filter")||"").match(/alpha\(opacity=(.*)\)/)){if(c[1]){return parseFloat(c[1])/100}}return 1}if(c=="auto"){if((b=="width"||b=="height")&&(a.getStyle("display")!="none")){return a["offset"+b.capitalize()]+"px"}return null}return c};Element.Methods.setOpacity=function(b,e){function f(g){return g.replace(/alpha\([^\)]*\)/gi,"")}b=$(b);var a=b.currentStyle;if((a&&!a.hasLayout)||(!a&&b.style.zoom=="normal")){b.style.zoom=1}var d=b.getStyle("filter"),c=b.style;if(e==1||e===""){(d=f(d))?c.filter=d:c.removeAttribute("filter");return b}else{if(e<0.00001){e=0}}c.filter=f(d)+"alpha(opacity="+(e*100)+")";return b};Element._attributeTranslations={read:{names:{"class":"className","for":"htmlFor"},values:{_getAttr:function(a,b){return a.getAttribute(b,2)},_getAttrNode:function(a,c){var b=a.getAttributeNode(c);return b?b.value:""},_getEv:function(a,b){b=a.getAttribute(b);return b?b.toString().slice(23,-2):null},_flag:function(a,b){return $(a).hasAttribute(b)?b:null},style:function(a){return a.style.cssText.toLowerCase()},title:function(a){return a.title}}}};Element._attributeTranslations.write={names:Object.extend({cellpadding:"cellPadding",cellspacing:"cellSpacing"},Element._attributeTranslations.read.names),values:{checked:function(a,b){a.checked=!!b},style:function(a,b){a.style.cssText=b?b:""}}};Element._attributeTranslations.has={};$w("colSpan rowSpan vAlign dateTime accessKey tabIndex encType maxLength readOnly longDesc frameBorder").each(function(a){Element._attributeTranslations.write.names[a.toLowerCase()]=a;Element._attributeTranslations.has[a.toLowerCase()]=a});(function(a){Object.extend(a,{href:a._getAttr,src:a._getAttr,type:a._getAttr,action:a._getAttrNode,disabled:a._flag,checked:a._flag,readonly:a._flag,multiple:a._flag,onload:a._getEv,onunload:a._getEv,onclick:a._getEv,ondblclick:a._getEv,onmousedown:a._getEv,onmouseup:a._getEv,onmouseover:a._getEv,onmousemove:a._getEv,onmouseout:a._getEv,onfocus:a._getEv,onblur:a._getEv,onkeypress:a._getEv,onkeydown:a._getEv,onkeyup:a._getEv,onsubmit:a._getEv,onreset:a._getEv,onselect:a._getEv,onchange:a._getEv})})(Element._attributeTranslations.read.values)}else{if(Prototype.Browser.Gecko&&/rv:1\.8\.0/.test(navigator.userAgent)){Element.Methods.setOpacity=function(a,b){a=$(a);a.style.opacity=(b==1)?0.999999:(b==="")?"":(b<0.00001)?0:b;return a}}else{if(Prototype.Browser.WebKit){Element.Methods.setOpacity=function(a,b){a=$(a);a.style.opacity=(b==1||b==="")?"":(b<0.00001)?0:b;if(b==1){if(a.tagName.toUpperCase()=="IMG"&&a.width){a.width++;a.width--}else{try{var d=document.createTextNode(" ");a.appendChild(d);a.removeChild(d)}catch(c){}}}return a};Element.Methods.cumulativeOffset=function(b){var a=0,c=0;do{a+=b.offsetTop||0;c+=b.offsetLeft||0;if(b.offsetParent==document.body){if(Element.getStyle(b,"position")=="absolute"){break}}b=b.offsetParent}while(b);return Element._returnOffset(c,a)}}}}}if(Prototype.Browser.IE||Prototype.Browser.Opera){Element.Methods.update=function(b,c){b=$(b);if(c&&c.toElement){c=c.toElement()}if(Object.isElement(c)){return b.update().insert(c)}c=Object.toHTML(c);var a=b.tagName.toUpperCase();if(a in Element._insertionTranslations.tags){$A(b.childNodes).each(function(d){b.removeChild(d)});Element._getContentFromAnonymousElement(a,c.stripScripts()).each(function(d){b.appendChild(d)})}else{b.innerHTML=c.stripScripts()}c.evalScripts.bind(c).defer();return b}}if("outerHTML"in document.createElement("div")){Element.Methods.replace=function(c,e){c=$(c);if(e&&e.toElement){e=e.toElement()}if(Object.isElement(e)){c.parentNode.replaceChild(e,c);return c}e=Object.toHTML(e);var d=c.parentNode,b=d.tagName.toUpperCase();if(Element._insertionTranslations.tags[b]){var f=c.next();var a=Element._getContentFromAnonymousElement(b,e.stripScripts());d.removeChild(c);if(f){a.each(function(g){d.insertBefore(g,f)})}else{a.each(function(g){d.appendChild(g)})}}else{c.outerHTML=e.stripScripts()}e.evalScripts.bind(e).defer();return c}}Element._returnOffset=function(b,c){var a=[b,c];a.left=b;a.top=c;return a};Element._getContentFromAnonymousElement=function(c,b){var d=new Element("div"),a=Element._insertionTranslations.tags[c];if(a){d.innerHTML=a[0]+b+a[1];a[2].times(function(){d=d.firstChild})}else{d.innerHTML=b}return $A(d.childNodes)};Element._insertionTranslations={before:function(a,b){a.parentNode.insertBefore(b,a)},top:function(a,b){a.insertBefore(b,a.firstChild)},bottom:function(a,b){a.appendChild(b)},after:function(a,b){a.parentNode.insertBefore(b,a.nextSibling)},tags:{TABLE:["<table>","</table>",1],TBODY:["<table><tbody>","</tbody></table>",2],TR:["<table><tbody><tr>","</tr></tbody></table>",3],TD:["<table><tbody><tr><td>","</td></tr></tbody></table>",4],SELECT:["<select>","</select>",1]}};(function(){Object.extend(this.tags,{THEAD:this.tags.TBODY,TFOOT:this.tags.TBODY,TH:this.tags.TD})}).call(Element._insertionTranslations);Element.Methods.Simulated={hasAttribute:function(a,c){c=Element._attributeTranslations.has[c]||c;var b=$(a).getAttributeNode(c);return!!(b&&b.specified)}};Element.Methods.ByTag={};Object.extend(Element,Element.Methods);if(!Prototype.BrowserFeatures.ElementExtensions&&document.createElement("div")["__proto__"]){window.HTMLElement={};window.HTMLElement.prototype=document.createElement("div")["__proto__"];Prototype.BrowserFeatures.ElementExtensions=true}Element.extend=(function(){if(Prototype.BrowserFeatures.SpecificElementExtensions){return Prototype.K}var a={},b=Element.Methods.ByTag;var c=Object.extend(function(f){if(!f||f._extendedByPrototype||f.nodeType!=1||f==window){return f}var d=Object.clone(a),e=f.tagName.toUpperCase(),h,g;if(b[e]){Object.extend(d,b[e])}for(h in d){g=d[h];if(Object.isFunction(g)&&!(h in f)){f[h]=g.methodize()}}f._extendedByPrototype=Prototype.emptyFunction;return f},{refresh:function(){if(!Prototype.BrowserFeatures.ElementExtensions){Object.extend(a,Element.Methods);Object.extend(a,Element.Methods.Simulated)}}});c.refresh();return c})();Element.hasAttribute=function(a,b){if(a.hasAttribute){return a.hasAttribute(b)}return Element.Methods.Simulated.hasAttribute(a,b)};Element.addMethods=function(c){var h=Prototype.BrowserFeatures,d=Element.Methods.ByTag;if(!c){Object.extend(Form,Form.Methods);Object.extend(Form.Element,Form.Element.Methods);Object.extend(Element.Methods.ByTag,{FORM:Object.clone(Form.Methods),INPUT:Object.clone(Form.Element.Methods),SELECT:Object.clone(Form.Element.Methods),TEXTAREA:Object.clone(Form.Element.Methods)})}if(arguments.length==2){var b=c;c=arguments[1]}if(!b){Object.extend(Element.Methods,c||{})}else{if(Object.isArray(b)){b.each(g)}else{g(b)}}function g(j){j=j.toUpperCase();if(!Element.Methods.ByTag[j]){Element.Methods.ByTag[j]={}}Object.extend(Element.Methods.ByTag[j],c)}function a(l,k,j){j=j||false;for(var n in l){var m=l[n];if(!Object.isFunction(m)){continue}if(!j||!(n in k)){k[n]=m.methodize()}}}function e(l){var j;var k={OPTGROUP:"OptGroup",TEXTAREA:"TextArea",P:"Paragraph",FIELDSET:"FieldSet",UL:"UList",OL:"OList",DL:"DList",DIR:"Directory",H1:"Heading",H2:"Heading",H3:"Heading",H4:"Heading",H5:"Heading",H6:"Heading",Q:"Quote",INS:"Mod",DEL:"Mod",A:"Anchor",IMG:"Image",CAPTION:"TableCaption",COL:"TableCol",COLGROUP:"TableCol",THEAD:"TableSection",TFOOT:"TableSection",TBODY:"TableSection",TR:"TableRow",TH:"TableCell",TD:"TableCell",FRAMESET:"FrameSet",IFRAME:"IFrame"};if(k[l]){j="HTML"+k[l]+"Element"}if(window[j]){return window[j]}j="HTML"+l+"Element";if(window[j]){return window[j]}j="HTML"+l.capitalize()+"Element";if(window[j]){return window[j]}window[j]={};window[j].prototype=document.createElement(l)["__proto__"];return window[j]}if(h.ElementExtensions){a(Element.Methods,HTMLElement.prototype);a(Element.Methods.Simulated,HTMLElement.prototype,true)}if(h.SpecificElementExtensions){for(var i in Element.Methods.ByTag){var f=e(i);if(Object.isUndefined(f)){continue}a(d[i],f.prototype)}}Object.extend(Element,Element.Methods);delete Element.ByTag;if(Element.extend.refresh){Element.extend.refresh()}Element.cache={}};document.viewport={getDimensions:function(){var a={},b=Prototype.Browser;$w("width height").each(function(e){var c=e.capitalize();if(b.WebKit&&!document.evaluate){a[e]=self["inner"+c]}else{if(b.Opera&&parseFloat(window.opera.version())<9.5){a[e]=document.body["client"+c]}else{a[e]=document.documentElement["client"+c]}}});return a},getWidth:function(){return this.getDimensions().width},getHeight:function(){return this.getDimensions().height},getScrollOffsets:function(){return Element._returnOffset(window.pageXOffset||document.documentElement.scrollLeft||document.body.scrollLeft,window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop)}};var Selector=Class.create({initialize:function(a){this.expression=a.strip();if(this.shouldUseSelectorsAPI()){this.mode="selectorsAPI"}else{if(this.shouldUseXPath()){this.mode="xpath";this.compileXPathMatcher()}else{this.mode="normal";this.compileMatcher()}}},shouldUseXPath:function(){if(!Prototype.BrowserFeatures.XPath){return false}var a=this.expression;if(Prototype.Browser.WebKit&&(a.include("-of-type")||a.include(":empty"))){return false}if((/(\[[\w-]*?:|:checked)/).test(a)){return false}return true},shouldUseSelectorsAPI:function(){if(!Prototype.BrowserFeatures.SelectorsAPI){return false}if(!Selector._div){Selector._div=new Element("div")}try{Selector._div.querySelector(this.expression)}catch(a){return false}return true},compileMatcher:function(){var e=this.expression,ps=Selector.patterns,h=Selector.handlers,c=Selector.criteria,le,p,m;if(Selector._cache[e]){this.matcher=Selector._cache[e];return}this.matcher=["this.matcher = function(root) {","var r = root, h = Selector.handlers, c = false, n;"];while(e&&le!=e&&(/\S/).test(e)){le=e;for(var i in ps){p=ps[i];if(m=e.match(p)){this.matcher.push(Object.isFunction(c[i])?c[i](m):new Template(c[i]).evaluate(m));e=e.replace(m[0],"");break}}}this.matcher.push("return h.unique(n);\n}");eval(this.matcher.join("\n"));Selector._cache[this.expression]=this.matcher},compileXPathMatcher:function(){var f=this.expression,g=Selector.patterns,b=Selector.xpath,d,a;if(Selector._cache[f]){this.xpath=Selector._cache[f];return}this.matcher=[".//*"];while(f&&d!=f&&(/\S/).test(f)){d=f;for(var c in g){if(a=f.match(g[c])){this.matcher.push(Object.isFunction(b[c])?b[c](a):new Template(b[c]).evaluate(a));f=f.replace(a[0],"");break}}}this.xpath=this.matcher.join("");Selector._cache[this.expression]=this.xpath},findElements:function(a){a=a||document;var c=this.expression,b;switch(this.mode){case"selectorsAPI":if(a!==document){var d=a.id,f=$(a).identify();c="#"+f+" "+c}b=$A(a.querySelectorAll(c)).map(Element.extend);a.id=d;return b;case"xpath":return document._getElementsByXPath(this.xpath,a);default:return this.matcher(a)}},match:function(j){this.tokens=[];var o=this.expression,a=Selector.patterns,f=Selector.assertions;var b,d,g;while(o&&b!==o&&(/\S/).test(o)){b=o;for(var k in a){d=a[k];if(g=o.match(d)){if(f[k]){this.tokens.push([k,Object.clone(g)]);o=o.replace(g[0],"")}else{return this.findElements(document).include(j)}}}}var n=true,c,l;for(var k=0,h;h=this.tokens[k];k++){c=h[0],l=h[1];if(!Selector.assertions[c](j,l)){n=false;break}}return n},toString:function(){return this.expression},inspect:function(){return"#<Selector:"+this.expression.inspect()+">"}});Object.extend(Selector,{_cache:{},xpath:{descendant:"//*",child:"/*",adjacent:"/following-sibling::*[1]",laterSibling:"/following-sibling::*",tagName:function(a){if(a[1]=="*"){return""}return"[local-name()='"+a[1].toLowerCase()+"' or local-name()='"+a[1].toUpperCase()+"']"},className:"[contains(concat(' ', @class, ' '), ' #{1} ')]",id:"[@id='#{1}']",attrPresence:function(a){a[1]=a[1].toLowerCase();return new Template("[@#{1}]").evaluate(a)},attr:function(a){a[1]=a[1].toLowerCase();a[3]=a[5]||a[6];return new Template(Selector.xpath.operators[a[2]]).evaluate(a)},pseudo:function(a){var b=Selector.xpath.pseudos[a[1]];if(!b){return""}if(Object.isFunction(b)){return b(a)}return new Template(Selector.xpath.pseudos[a[1]]).evaluate(a)},operators:{"=":"[@#{1}='#{3}']","!=":"[@#{1}!='#{3}']","^=":"[starts-with(@#{1}, '#{3}')]","$=":"[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']","*=":"[contains(@#{1}, '#{3}')]","~=":"[contains(concat(' ', @#{1}, ' '), ' #{3} ')]","|=":"[contains(concat('-', @#{1}, '-'), '-#{3}-')]"},pseudos:{"first-child":"[not(preceding-sibling::*)]","last-child":"[not(following-sibling::*)]","only-child":"[not(preceding-sibling::* or following-sibling::*)]",empty:"[count(*) = 0 and (count(text()) = 0)]",checked:"[@checked]",disabled:"[(@disabled) and (@type!='hidden')]",enabled:"[not(@disabled) and (@type!='hidden')]",not:function(b){var j=b[6],h=Selector.patterns,a=Selector.xpath,f,c;var g=[];while(j&&f!=j&&(/\S/).test(j)){f=j;for(var d in h){if(b=j.match(h[d])){c=Object.isFunction(a[d])?a[d](b):new Template(a[d]).evaluate(b);g.push("("+c.substring(1,c.length-1)+")");j=j.replace(b[0],"");break}}}return"[not("+g.join(" and ")+")]"},"nth-child":function(a){return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ",a)},"nth-last-child":function(a){return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ",a)},"nth-of-type":function(a){return Selector.xpath.pseudos.nth("position() ",a)},"nth-last-of-type":function(a){return Selector.xpath.pseudos.nth("(last() + 1 - position()) ",a)},"first-of-type":function(a){a[6]="1";return Selector.xpath.pseudos["nth-of-type"](a)},"last-of-type":function(a){a[6]="1";return Selector.xpath.pseudos["nth-last-of-type"](a)},"only-of-type":function(a){var b=Selector.xpath.pseudos;return b["first-of-type"](a)+b["last-of-type"](a)},nth:function(g,e){var h,i=e[6],d;if(i=="even"){i="2n+0"}if(i=="odd"){i="2n+1"}if(h=i.match(/^(\d+)$/)){return"["+g+"= "+h[1]+"]"}if(h=i.match(/^(-?\d*)?n(([+-])(\d+))?/)){if(h[1]=="-"){h[1]=-1}var f=h[1]?Number(h[1]):1;var c=h[2]?Number(h[2]):0;d="[((#{fragment} - #{b}) mod #{a} = 0) and ((#{fragment} - #{b}) div #{a} >= 0)]";return new Template(d).evaluate({fragment:g,a:f,b:c})}}}},criteria:{tagName:'n = h.tagName(n, r, "#{1}", c);      c = false;',className:'n = h.className(n, r, "#{1}", c);    c = false;',id:'n = h.id(n, r, "#{1}", c);           c = false;',attrPresence:'n = h.attrPresence(n, r, "#{1}", c); c = false;',attr:function(a){a[3]=(a[5]||a[6]);return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(a)},pseudo:function(a){if(a[6]){a[6]=a[6].replace(/"/g,'\\"')}return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(a)},descendant:'c = "descendant";',child:'c = "child";',adjacent:'c = "adjacent";',laterSibling:'c = "laterSibling";'},patterns:{laterSibling:/^\s*~\s*/,child:/^\s*>\s*/,adjacent:/^\s*\+\s*/,descendant:/^\s/,tagName:/^\s*(\*|[\w\-]+)(\b|$)?/,id:/^#([\w\-\*]+)(\b|$)/,className:/^\.([\w\-\*]+)(\b|$)/,pseudo:/^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/,attrPresence:/^\[((?:[\w]+:)?[\w]+)\]/,attr:/\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/},assertions:{tagName:function(a,b){return b[1].toUpperCase()==a.tagName.toUpperCase()},className:function(a,b){return Element.hasClassName(a,b[1])},id:function(a,b){return a.id===b[1]},attrPresence:function(a,b){return Element.hasAttribute(a,b[1])},attr:function(b,c){var a=Element.readAttribute(b,c[1]);return a&&Selector.operators[c[2]](a,c[5]||c[6])}},handlers:{concat:function(d,c){for(var e=0,f;f=c[e];e++){d.push(f)}return d},mark:function(a){var d=Prototype.emptyFunction;for(var b=0,c;c=a[b];b++){c._countedByPrototype=d}return a},unmark:function(a){for(var b=0,c;c=a[b];b++){c._countedByPrototype=undefined}return a},index:function(a,d,g){a._countedByPrototype=Prototype.emptyFunction;if(d){for(var b=a.childNodes,e=b.length-1,c=1;e>=0;e--){var f=b[e];if(f.nodeType==1&&(!g||f._countedByPrototype)){f.nodeIndex=c++}}}else{for(var e=0,c=1,b=a.childNodes;f=b[e];e++){if(f.nodeType==1&&(!g||f._countedByPrototype)){f.nodeIndex=c++}}}},unique:function(b){if(b.length==0){return b}var d=[],e;for(var c=0,a=b.length;c<a;c++){if(!(e=b[c])._countedByPrototype){e._countedByPrototype=Prototype.emptyFunction;d.push(Element.extend(e))}}return Selector.handlers.unmark(d)},descendant:function(a){var d=Selector.handlers;for(var c=0,b=[],e;e=a[c];c++){d.concat(b,e.getElementsByTagName("*"))}return b},child:function(a){var e=Selector.handlers;for(var d=0,c=[],f;f=a[d];d++){for(var b=0,g;g=f.childNodes[b];b++){if(g.nodeType==1&&g.tagName!="!"){c.push(g)}}}return c},adjacent:function(a){for(var c=0,b=[],e;e=a[c];c++){var d=this.nextElementSibling(e);if(d){b.push(d)}}return b},laterSibling:function(a){var d=Selector.handlers;for(var c=0,b=[],e;e=a[c];c++){d.concat(b,Element.nextSiblings(e))}return b},nextElementSibling:function(a){while(a=a.nextSibling){if(a.nodeType==1){return a}}return null},previousElementSibling:function(a){while(a=a.previousSibling){if(a.nodeType==1){return a}}return null},tagName:function(a,j,c,b){var k=c.toUpperCase();var e=[],g=Selector.handlers;if(a){if(b){if(b=="descendant"){for(var f=0,d;d=a[f];f++){g.concat(e,d.getElementsByTagName(c))}return e}else{a=this[b](a)}if(c=="*"){return a}}for(var f=0,d;d=a[f];f++){if(d.tagName.toUpperCase()===k){e.push(d)}}return e}else{return j.getElementsByTagName(c)}},id:function(b,a,j,f){var g=$(j),d=Selector.handlers;if(!g){return[]}if(!b&&a==document){return[g]}if(b){if(f){if(f=="child"){for(var c=0,e;e=b[c];c++){if(g.parentNode==e){return[g]}}}else{if(f=="descendant"){for(var c=0,e;e=b[c];c++){if(Element.descendantOf(g,e)){return[g]}}}else{if(f=="adjacent"){for(var c=0,e;e=b[c];c++){if(Selector.handlers.previousElementSibling(g)==e){return[g]}}}else{b=d[f](b)}}}}for(var c=0,e;e=b[c];c++){if(e==g){return[g]}}return[]}return(g&&Element.descendantOf(g,a))?[g]:[]},className:function(b,a,c,d){if(b&&d){b=this[d](b)}return Selector.handlers.byClassName(b,a,c)},byClassName:function(c,b,f){if(!c){c=Selector.handlers.descendant([b])}var h=" "+f+" ";for(var e=0,d=[],g,a;g=c[e];e++){a=g.className;if(a.length==0){continue}if(a==f||(" "+a+" ").include(h)){d.push(g)}}return d},attrPresence:function(c,b,a,g){if(!c){c=b.getElementsByTagName("*")}if(c&&g){c=this[g](c)}var e=[];for(var d=0,f;f=c[d];d++){if(Element.hasAttribute(f,a)){e.push(f)}}return e},attr:function(a,j,h,k,c,b){if(!a){a=j.getElementsByTagName("*")}if(a&&b){a=this[b](a)}var l=Selector.operators[c],f=[];for(var e=0,d;d=a[e];e++){var g=Element.readAttribute(d,h);if(g===null){continue}if(l(g,k)){f.push(d)}}return f},pseudo:function(b,c,e,a,d){if(b&&d){b=this[d](b)}if(!b){b=a.getElementsByTagName("*")}return Selector.pseudos[c](b,e,a)}},pseudos:{"first-child":function(b,f,a){for(var d=0,c=[],e;e=b[d];d++){if(Selector.handlers.previousElementSibling(e)){continue}c.push(e)}return c},"last-child":function(b,f,a){for(var d=0,c=[],e;e=b[d];d++){if(Selector.handlers.nextElementSibling(e)){continue}c.push(e)}return c},"only-child":function(b,g,a){var e=Selector.handlers;for(var d=0,c=[],f;f=b[d];d++){if(!e.previousElementSibling(f)&&!e.nextElementSibling(f)){c.push(f)}}return c},"nth-child":function(b,c,a){return Selector.pseudos.nth(b,c,a)},"nth-last-child":function(b,c,a){return Selector.pseudos.nth(b,c,a,true)},"nth-of-type":function(b,c,a){return Selector.pseudos.nth(b,c,a,false,true)},"nth-last-of-type":function(b,c,a){return Selector.pseudos.nth(b,c,a,true,true)},"first-of-type":function(b,c,a){return Selector.pseudos.nth(b,"1",a,false,true)},"last-of-type":function(b,c,a){return Selector.pseudos.nth(b,"1",a,true,true)},"only-of-type":function(b,d,a){var c=Selector.pseudos;return c["last-of-type"](c["first-of-type"](b,d,a),d,a)},getIndices:function(d,c,e){if(d==0){return c>0?[c]:[]}return $R(1,e).inject([],function(a,b){if(0==(b-c)%d&&(b-c)/d>=0){a.push(b)}return a})},nth:function(c,s,u,r,e){if(c.length==0){return[]}if(s=="even"){s="2n+0"}if(s=="odd"){s="2n+1"}var q=Selector.handlers,p=[],d=[],g;q.mark(c);for(var o=0,f;f=c[o];o++){if(!f.parentNode._countedByPrototype){q.index(f.parentNode,r,e);d.push(f.parentNode)}}if(s.match(/^\d+$/)){s=Number(s);for(var o=0,f;f=c[o];o++){if(f.nodeIndex==s){p.push(f)}}}else{if(g=s.match(/^(-?\d*)?n(([+-])(\d+))?/)){if(g[1]=="-"){g[1]=-1}var v=g[1]?Number(g[1]):1;var t=g[2]?Number(g[2]):0;var w=Selector.pseudos.getIndices(v,t,c.length);for(var o=0,f,k=w.length;f=c[o];o++){for(var n=0;n<k;n++){if(f.nodeIndex==w[n]){p.push(f)}}}}}q.unmark(c);q.unmark(d);return p},empty:function(b,f,a){for(var d=0,c=[],e;e=b[d];d++){if(e.tagName=="!"||e.firstChild){continue}c.push(e)}return c},not:function(a,d,k){var g=Selector.handlers,l,c;var j=new Selector(d).findElements(k);g.mark(j);for(var f=0,e=[],b;b=a[f];f++){if(!b._countedByPrototype){e.push(b)}}g.unmark(j);return e},enabled:function(b,f,a){for(var d=0,c=[],e;e=b[d];d++){if(!e.disabled&&(!e.type||e.type!=="hidden")){c.push(e)}}return c},disabled:function(b,f,a){for(var d=0,c=[],e;e=b[d];d++){if(e.disabled){c.push(e)}}return c},checked:function(b,f,a){for(var d=0,c=[],e;e=b[d];d++){if(e.checked){c.push(e)}}return c}},operators:{"=":function(b,a){return b==a},"!=":function(b,a){return b!=a},"^=":function(b,a){return b==a||b&&b.startsWith(a)},"$=":function(b,a){return b==a||b&&b.endsWith(a)},"*=":function(b,a){return b==a||b&&b.include(a)},"$=":function(b,a){return b.endsWith(a)},"*=":function(b,a){return b.include(a)},"~=":function(b,a){return(" "+b+" ").include(" "+a+" ")},"|=":function(b,a){return("-"+(b||"").toUpperCase()+"-").include("-"+(a||"").toUpperCase()+"-")}},split:function(b){var a=[];b.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/,function(c){a.push(c[1].strip())});return a},matchElements:function(f,g){var e=$$(g),d=Selector.handlers;d.mark(e);for(var c=0,b=[],a;a=f[c];c++){if(a._countedByPrototype){b.push(a)}}d.unmark(e);return b},findElement:function(b,c,a){if(Object.isNumber(c)){a=c;c=false}return Selector.matchElements(b,c||"*")[a||0]},findChildElements:function(e,g){g=Selector.split(g.join(","));var d=[],f=Selector.handlers;for(var c=0,b=g.length,a;c<b;c++){a=new Selector(g[c].strip());f.concat(d,a.findElements(e))}return(b>1)?f.unique(d):d}});if(Prototype.Browser.IE){Object.extend(Selector.handlers,{concat:function(d,c){for(var e=0,f;f=c[e];e++){if(f.tagName!=="!"){d.push(f)}}return d},unmark:function(a){for(var b=0,c;c=a[b];b++){c.removeAttribute("_countedByPrototype")}return a}})}function $$(){return Selector.findChildElements(document,$A(arguments))}var Form={reset:function(a){$(a).reset();return a},serializeElements:function(g,b){if(typeof b!="object"){b={hash:!!b}}else{if(Object.isUndefined(b.hash)){b.hash=true}}var c,f,a=false,e=b.submit;var d=g.inject({},function(h,i){if(!i.disabled&&i.name){c=i.name;f=$(i).getValue();if(f!=null&&i.type!="file"&&(i.type!="submit"||(!a&&e!==false&&(!e||c==e)&&(a=true)))){if(c in h){if(!Object.isArray(h[c])){h[c]=[h[c]]}h[c].push(f)}else{h[c]=f}}}return h});return b.hash?d:Object.toQueryString(d)}};Form.Methods={serialize:function(b,a){return Form.serializeElements(Form.getElements(b),a)},getElements:function(a){return $A($(a).getElementsByTagName("*")).inject([],function(b,c){if(Form.Element.Serializers[c.tagName.toLowerCase()]){b.push(Element.extend(c))}return b})},getInputs:function(g,c,d){g=$(g);var a=g.getElementsByTagName("input");if(!c&&!d){return $A(a).map(Element.extend)}for(var e=0,h=[],f=a.length;e<f;e++){var b=a[e];if((c&&b.type!=c)||(d&&b.name!=d)){continue}h.push(Element.extend(b))}return h},disable:function(a){a=$(a);Form.getElements(a).invoke("disable");return a},enable:function(a){a=$(a);Form.getElements(a).invoke("enable");return a},findFirstElement:function(b){var c=$(b).getElements().findAll(function(d){return"hidden"!=d.type&&!d.disabled});var a=c.findAll(function(d){return d.hasAttribute("tabIndex")&&d.tabIndex>=0}).sortBy(function(d){return d.tabIndex}).first();return a?a:c.find(function(d){return["input","select","textarea"].include(d.tagName.toLowerCase())})},focusFirstElement:function(a){a=$(a);a.findFirstElement().activate();return a},request:function(b,a){b=$(b),a=Object.clone(a||{});var d=a.parameters,c=b.readAttribute("action")||"";if(c.blank()){c=window.location.href}a.parameters=b.serialize(true);if(d){if(Object.isString(d)){d=d.toQueryParams()}Object.extend(a.parameters,d)}if(b.hasAttribute("method")&&!a.method){a.method=b.method}return new Ajax.Request(c,a)}};Form.Element={focus:function(a){$(a).focus();return a},select:function(a){$(a).select();return a}};Form.Element.Methods={serialize:function(a){a=$(a);if(!a.disabled&&a.name){var b=a.getValue();if(b!=undefined){var c={};c[a.name]=b;return Object.toQueryString(c)}}return""},getValue:function(a){a=$(a);var b=a.tagName.toLowerCase();return Form.Element.Serializers[b](a)},setValue:function(a,b){a=$(a);var c=a.tagName.toLowerCase();Form.Element.Serializers[c](a,b);return a},clear:function(a){$(a).value="";return a},present:function(a){return $(a).value!=""},activate:function(a){a=$(a);try{a.focus();if(a.select&&(a.tagName.toLowerCase()!="input"||!["button","reset","submit"].include(a.type))){a.select()}}catch(b){}return a},disable:function(a){a=$(a);a.disabled=true;return a},enable:function(a){a=$(a);a.disabled=false;return a}};var Field=Form.Element;var $F=Form.Element.Methods.getValue;Form.Element.Serializers={input:function(a,b){switch(a.type.toLowerCase()){case"checkbox":case"radio":return Form.Element.Serializers.inputSelector(a,b);default:return Form.Element.Serializers.textarea(a,b)}},inputSelector:function(a,b){if(Object.isUndefined(b)){return a.checked?a.value:null}else{a.checked=!!b}},textarea:function(a,b){if(Object.isUndefined(b)){return a.value}else{a.value=b}},select:function(c,f){if(Object.isUndefined(f)){return this[c.type=="select-one"?"selectOne":"selectMany"](c)}else{var b,d,g=!Object.isArray(f);for(var a=0,e=c.length;a<e;a++){b=c.options[a];d=this.optionValue(b);if(g){if(d==f){b.selected=true;return}}else{b.selected=f.include(d)}}}},selectOne:function(b){var a=b.selectedIndex;return a>=0?this.optionValue(b.options[a]):null},selectMany:function(d){var a,e=d.length;if(!e){return null}for(var c=0,a=[];c<e;c++){var b=d.options[c];if(b.selected){a.push(this.optionValue(b))}}return a},optionValue:function(a){return Element.extend(a).hasAttribute("value")?a.value:a.text}};Abstract.TimedObserver=Class.create(PeriodicalExecuter,{initialize:function($super,a,b,c){$super(c,b);this.element=$(a);this.lastValue=this.getValue()},execute:function(){var a=this.getValue();if(Object.isString(this.lastValue)&&Object.isString(a)?this.lastValue!=a:String(this.lastValue)!=String(a)){this.callback(this.element,a);this.lastValue=a}}});Form.Element.Observer=Class.create(Abstract.TimedObserver,{getValue:function(){return Form.Element.getValue(this.element)}});Form.Observer=Class.create(Abstract.TimedObserver,{getValue:function(){return Form.serialize(this.element)}});Abstract.EventObserver=Class.create({initialize:function(a,b){this.element=$(a);this.callback=b;this.lastValue=this.getValue();if(this.element.tagName.toLowerCase()=="form"){this.registerFormCallbacks()}else{this.registerCallback(this.element)}},onElementEvent:function(){var a=this.getValue();if(this.lastValue!=a){this.callback(this.element,a);this.lastValue=a}},registerFormCallbacks:function(){Form.getElements(this.element).each(this.registerCallback,this)},registerCallback:function(a){if(a.type){switch(a.type.toLowerCase()){case"checkbox":case"radio":Event.observe(a,"click",this.onElementEvent.bind(this));break;default:Event.observe(a,"change",this.onElementEvent.bind(this));break}}}});Form.Element.EventObserver=Class.create(Abstract.EventObserver,{getValue:function(){return Form.Element.getValue(this.element)}});Form.EventObserver=Class.create(Abstract.EventObserver,{getValue:function(){return Form.serialize(this.element)}});if(!window.Event){var Event={}}Object.extend(Event,{KEY_BACKSPACE:8,KEY_TAB:9,KEY_RETURN:13,KEY_ESC:27,KEY_LEFT:37,KEY_UP:38,KEY_RIGHT:39,KEY_DOWN:40,KEY_DELETE:46,KEY_HOME:36,KEY_END:35,KEY_PAGEUP:33,KEY_PAGEDOWN:34,KEY_INSERT:45,cache:{},relatedTarget:function(b){var a;switch(b.type){case"mouseover":a=b.fromElement;break;case"mouseout":a=b.toElement;break;default:return null}return Element.extend(a)}});Event.Methods=(function(){var a;if(Prototype.Browser.IE){var b={0:1,1:4,2:2};a=function(d,c){return d.button==b[c]}}else{if(Prototype.Browser.WebKit){a=function(d,c){switch(c){case 0:return d.which==1&&!d.metaKey;case 1:return d.which==1&&d.metaKey;default:return false}}}else{a=function(d,c){return d.which?(d.which===c+1):(d.button===c)}}}return{isLeftClick:function(c){return a(c,0)},isMiddleClick:function(c){return a(c,1)},isRightClick:function(c){return a(c,2)},element:function(e){e=Event.extend(e);var d=e.target,c=e.type,f=e.currentTarget;if(f&&f.tagName){if(c==="load"||c==="error"||(c==="click"&&f.tagName.toLowerCase()==="input"&&f.type==="radio")){d=f}}if(d.nodeType==Node.TEXT_NODE){d=d.parentNode}return Element.extend(d)},findElement:function(d,f){var c=Event.element(d);if(!f){return c}var e=[c].concat(c.ancestors());return Selector.findElement(e,f,0)},pointer:function(e){var d=document.documentElement,c=document.body||{scrollLeft:0,scrollTop:0};return{x:e.pageX||(e.clientX+(d.scrollLeft||c.scrollLeft)-(d.clientLeft||0)),y:e.pageY||(e.clientY+(d.scrollTop||c.scrollTop)-(d.clientTop||0))}},pointerX:function(c){return Event.pointer(c).x},pointerY:function(c){return Event.pointer(c).y},stop:function(c){Event.extend(c);c.preventDefault();c.stopPropagation();c.stopped=true}}})();Event.extend=(function(){var a=Object.keys(Event.Methods).inject({},function(b,c){b[c]=Event.Methods[c].methodize();return b});if(Prototype.Browser.IE){Object.extend(a,{stopPropagation:function(){this.cancelBubble=true},preventDefault:function(){this.returnValue=false},inspect:function(){return"[object Event]"}});return function(b){if(!b){return false}if(b._extendedByPrototype){return b}b._extendedByPrototype=Prototype.emptyFunction;var c=Event.pointer(b);Object.extend(b,{target:b.srcElement,relatedTarget:Event.relatedTarget(b),pageX:c.x,pageY:c.y});return Object.extend(b,a)}}else{Event.prototype=Event.prototype||document.createEvent("HTMLEvents")["__proto__"];Object.extend(Event.prototype,a);return Prototype.K}})();Object.extend(Event,(function(){var b=Event.cache;function c(j){if(j._prototypeEventID){return j._prototypeEventID[0]}arguments.callee.id=arguments.callee.id||1;return j._prototypeEventID=[++arguments.callee.id]}function g(j){if(j&&j.include(":")){return"dataavailable"}return j}function a(j){return b[j]=b[j]||{}}function f(l,j){var k=a(l);return k[j]=k[j]||[]}function h(k,j,l){var o=c(k);var n=f(o,j);if(n.pluck("handler").include(l)){return false}var m=function(p){if(!Event||!Event.extend||(p.eventName&&p.eventName!=j)){return false}Event.extend(p);l.call(k,p)};m.handler=l;n.push(m);return m}function i(m,j,k){var l=f(m,j);return l.find(function(n){return n.handler==k})}function d(m,j,k){var l=a(m);if(!l[j]){return false}l[j]=l[j].without(i(m,j,k))}function e(){for(var k in b){for(var j in b[k]){b[k][j]=null}}}if(window.attachEvent){window.attachEvent("onunload",e)}if(Prototype.Browser.WebKit){window.addEventListener("unload",Prototype.emptyFunction,false)}return{observe:function(l,j,m){l=$(l);var k=g(j);var n=h(l,j,m);if(!n){return l}if(l.addEventListener){l.addEventListener(k,n,false)}else{l.attachEvent("on"+k,n)}return l},stopObserving:function(l,j,m){l=$(l);var o=c(l),k=g(j);if(!m&&j){f(o,j).each(function(p){l.stopObserving(j,p.handler)});return l}else{if(!j){Object.keys(a(o)).each(function(p){l.stopObserving(p)});return l}}var n=i(o,j,m);if(!n){return l}if(l.removeEventListener){l.removeEventListener(k,n,false)}else{l.detachEvent("on"+k,n)}d(o,j,m);return l},fire:function(l,k,j){l=$(l);if(l==document&&document.createEvent&&!l.dispatchEvent){l=document.documentElement}var m;if(document.createEvent){m=document.createEvent("HTMLEvents");m.initEvent("dataavailable",true,true)}else{m=document.createEventObject();m.eventType="ondataavailable"}m.eventName=k;m.memo=j||{};if(document.createEvent){l.dispatchEvent(m)}else{l.fireEvent(m.eventType,m)}return Event.extend(m)}}})());Object.extend(Event,Event.Methods);Element.addMethods({fire:Event.fire,observe:Event.observe,stopObserving:Event.stopObserving});Object.extend(document,{fire:Element.Methods.fire.methodize(),observe:Element.Methods.observe.methodize(),stopObserving:Element.Methods.stopObserving.methodize(),loaded:false});(function(){var b;function a(){if(document.loaded){return}if(b){window.clearInterval(b)}document.fire("dom:loaded");document.loaded=true}if(document.addEventListener){if(Prototype.Browser.WebKit){b=window.setInterval(function(){if(/loaded|complete/.test(document.readyState)){a()}},0);Event.observe(window,"load",a)}else{document.addEventListener("DOMContentLoaded",a,false)}}else{document.write("<script id=__onDOMContentLoaded defer src=//:><\/script>");$("__onDOMContentLoaded").onreadystatechange=function(){if(this.readyState=="complete"){this.onreadystatechange=null;a()}}}})();Hash.toQueryString=Object.toQueryString;var Toggle={display:Element.toggle};Element.Methods.childOf=Element.Methods.descendantOf;var Insertion={Before:function(a,b){return Element.insert(a,{before:b})},Top:function(a,b){return Element.insert(a,{top:b})},Bottom:function(a,b){return Element.insert(a,{bottom:b})},After:function(a,b){return Element.insert(a,{after:b})}};var $continue=new Error('"throw $continue" is deprecated, use "return" instead');var Position={includeScrollOffsets:false,prepare:function(){this.deltaX=window.pageXOffset||document.documentElement.scrollLeft||document.body.scrollLeft||0;this.deltaY=window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop||0},within:function(b,a,c){if(this.includeScrollOffsets){return this.withinIncludingScrolloffsets(b,a,c)}this.xcomp=a;this.ycomp=c;this.offset=Element.cumulativeOffset(b);return(c>=this.offset[1]&&c<this.offset[1]+b.offsetHeight&&a>=this.offset[0]&&a<this.offset[0]+b.offsetWidth)},withinIncludingScrolloffsets:function(b,a,d){var c=Element.cumulativeScrollOffset(b);this.xcomp=a+c[0]-this.deltaX;this.ycomp=d+c[1]-this.deltaY;this.offset=Element.cumulativeOffset(b);return(this.ycomp>=this.offset[1]&&this.ycomp<this.offset[1]+b.offsetHeight&&this.xcomp>=this.offset[0]&&this.xcomp<this.offset[0]+b.offsetWidth)},overlap:function(b,a){if(!b){return 0}if(b=="vertical"){return((this.offset[1]+a.offsetHeight)-this.ycomp)/a.offsetHeight}if(b=="horizontal"){return((this.offset[0]+a.offsetWidth)-this.xcomp)/a.offsetWidth}},cumulativeOffset:Element.Methods.cumulativeOffset,positionedOffset:Element.Methods.positionedOffset,absolutize:function(a){Position.prepare();return Element.absolutize(a)},relativize:function(a){Position.prepare();return Element.relativize(a)},realOffset:Element.Methods.cumulativeScrollOffset,offsetParent:Element.Methods.getOffsetParent,page:Element.Methods.viewportOffset,clone:function(b,c,a){a=a||{};return Element.clonePosition(c,b,a)}};if(!document.getElementsByClassName){document.getElementsByClassName=function(b){function a(c){return c.blank()?null:"[contains(concat(' ', @class, ' '), ' "+c+" ')]"}b.getElementsByClassName=Prototype.BrowserFeatures.XPath?function(c,e){e=e.toString().strip();var d=/\s/.test(e)?$w(e).map(a).join(""):a(e);return d?document._getElementsByXPath(".//*"+d,c):[]}:function(e,f){f=f.toString().strip();var g=[],h=(/\s/.test(f)?$w(f):null);if(!h&&!f){return g}var c=$(e).getElementsByTagName("*");f=" "+f+" ";for(var d=0,k,j;k=c[d];d++){if(k.className&&(j=" "+k.className+" ")&&(j.include(f)||(h&&h.all(function(i){return!i.toString().blank()&&j.include(" "+i+" ")})))){g.push(Element.extend(k))}}return g};return function(d,c){return $(c||document.body).getElementsByClassName(d)}}(Element.Methods)}Element.ClassNames=Class.create();Element.ClassNames.prototype={initialize:function(a){this.element=$(a)},_each:function(a){this.element.className.split(/\s+/).select(function(b){return b.length>0})._each(a)},set:function(a){this.element.className=a},add:function(a){if(this.include(a)){return}this.set($A(this).concat(a).join(" "))},remove:function(a){if(!this.include(a)){return}this.set($A(this).without(a).join(" "))},toString:function(){return $A(this).join(" ")}};Object.extend(Element.ClassNames.prototype,Enumerable);Element.addMethods();
\ No newline at end of file
diff --git a/js/revlog.js b/js/revlog.js
new file mode 100644
index 0000000..ec85793
--- /dev/null
+++ b/js/revlog.js
@@ -0,0 +1 @@
+var revlog_selected=null;var isMSIE=/*@cc_on!@*/false;function revlog_highlight(){var A=$("revlog_body");$A(A.getElementsByTagName("TR")).each(function(B){if(isMSIE){Event.observe(B,"mouseover",(function(){Element.addClassName(this,"hover")}).bind(B));Event.observe(B,"mouseout",(function(){Element.removeClassName(this,"hover")}).bind(B))}Event.observe(B,"click",revlog_toggle.bindAsEventListener(B))})}function revlog_toggle(B){var A=Event.element(B);while(A!=this){if(A.tagName.toUpperCase()=="A"&&A.getAttribute("href")){return }A=A.parentNode}if(revlog_selected!=null){Element.removeClassName(revlog_selected,"selected");if(revlog_selected==this){revlog_selected=null;Element.removeClassName("revlog_body","selection");return }}revlog_selected=this;Element.addClassName(this,"selected");Element.addClassName("revlog_body","selection")}function revlog_sdiff(A){A.href=A.href.replace(/r1=([\d\.]+)/,"r1="+revlog_selected.id.substring(3))}Event.observe(window,"load",revlog_highlight)
\ No newline at end of file
diff --git a/js/src/QuickFinder.js b/js/src/QuickFinder.js
new file mode 100644
index 0000000..660f841
--- /dev/null
+++ b/js/src/QuickFinder.js
@@ -0,0 +1,101 @@
+/**
+ * Component for filtering a table or any list of children based on
+ * the dynamic value of a text input. It requires the prototype.js
+ * library.
+ *
+ * You should define the CSS class .QuickFinderNoMatch to say what
+ * happens to items that don't match the criteria. A reasonable
+ * default would be display:none.
+ *
+ * This code is heavily inspired by Filterlicious by Gavin
+ * Kistner. The filterlicious JavaScript file did not have a license;
+ * however, most of Gavin's code is under the license defined by
+ * http://phrogz.net/JS/_ReuseLicense.txt, so I'm including that URL
+ * and Gavin's name as acknowledgements.
+ *
+ * @author Chuck Hagenbuch <chuck at horde.org>
+ *
+ * $Horde: chora/js/src/QuickFinder.js,v 1.4.2.1 2008/10/09 17:40:05 jan Exp $
+ */
+
+var QuickFinder = {
+
+    attachBehavior: function() {
+        $$('input').each(function(input) {
+            var filterTarget = input.readAttribute('for');
+            if (!filterTarget) {
+                return;
+            }
+
+            if (filterTarget.indexOf(',') != -1) {
+                input.filterTargets = [];
+                var targets = filterTarget.split(',');
+                for (var i = 0; i < targets.length; ++i) {
+                    var t = $(targets[i]);
+                    if (t) {
+                        input.filterTargets.push(t);
+                    }
+                }
+                if (!input.filterTargets.length) {
+                    return;
+                }
+            } else {
+                input.filterTargets = [ $(filterTarget) ];
+                if (!input.filterTargets[0]) {
+                    return;
+                }
+            }
+
+            filterEmpty = input.readAttribute('empty');
+            if (filterEmpty) {
+                input.filterEmpty = $(filterEmpty);
+            }
+
+            input.observe('keyup', QuickFinder.onKeyUp);
+
+            for (var i = 0, i_max = input.filterTargets.length; i < i_max; i++) {
+                input.filterTargets[i].immediateDescendants().each(function(line) {
+                    var filterText = line.filterText || line.readAttribute('filterText');
+                    if (!filterText) {
+                        line.filterText = line.innerHTML.stripTags();
+                    }
+                    line.filterText = line.filterText.toLowerCase();
+                });
+            }
+
+            QuickFinder.filter(input);
+        });
+    },
+
+    onKeyUp: function(e) {
+        input = e.element();
+        if (input.filterTargets) {
+            QuickFinder.filter(input);
+        }
+    },
+
+    filter: function(input) {
+        var val = input.value.toLowerCase();
+        var matched = 0;
+        for (var i = 0, i_max = input.filterTargets.length; i < i_max; i++) {
+            input.filterTargets[i].immediateDescendants().each(function(line) {
+                var filterText = line.filterText;
+                if (filterText.indexOf(val) == -1) {
+                    line.addClassName('QuickFinderNoMatch');
+                } else {
+                    ++matched;
+                    line.removeClassName('QuickFinderNoMatch');
+                }
+            });
+        }
+
+        try {
+            if (input.filterEmpty) {
+                (matched == 0) ? input.filterEmpty.show() : input.filterEmpty.hide();
+            }
+        } catch (e) {}
+    }
+
+}
+
+document.observe('dom:loaded', QuickFinder.attachBehavior);
diff --git a/js/src/prototype.js b/js/src/prototype.js
new file mode 100644
index 0000000..a7fd383
--- /dev/null
+++ b/js/src/prototype.js
@@ -0,0 +1,4320 @@
+/*  Prototype JavaScript framework, version 1.6.0.3
+ *  (c) 2005-2008 Sam Stephenson
+ *
+ *  Prototype is freely distributable under the terms of an MIT-style license.
+ *  For details, see the Prototype web site: http://www.prototypejs.org/
+ *
+ *--------------------------------------------------------------------------*/
+
+var Prototype = {
+  Version: '1.6.0.3',
+
+  Browser: {
+    IE:     !!(window.attachEvent &&
+      navigator.userAgent.indexOf('Opera') === -1),
+    Opera:  navigator.userAgent.indexOf('Opera') > -1,
+    WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1,
+    Gecko:  navigator.userAgent.indexOf('Gecko') > -1 &&
+      navigator.userAgent.indexOf('KHTML') === -1,
+    MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/)
+  },
+
+  BrowserFeatures: {
+    XPath: !!document.evaluate,
+    SelectorsAPI: !!document.querySelector,
+    ElementExtensions: !!window.HTMLElement,
+    SpecificElementExtensions:
+      document.createElement('div')['__proto__'] &&
+      document.createElement('div')['__proto__'] !==
+        document.createElement('form')['__proto__']
+  },
+
+  ScriptFragment: '<script[^>]*>([\\S\\s]*?)<\/script>',
+  JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/,
+
+  emptyFunction: function() { },
+  K: function(x) { return x }
+};
+
+if (Prototype.Browser.MobileSafari)
+  Prototype.BrowserFeatures.SpecificElementExtensions = false;
+
+
+/* Based on Alex Arnell's inheritance implementation. */
+var Class = {
+  create: function() {
+    var parent = null, properties = $A(arguments);
+    if (Object.isFunction(properties[0]))
+      parent = properties.shift();
+
+    function klass() {
+      this.initialize.apply(this, arguments);
+    }
+
+    Object.extend(klass, Class.Methods);
+    klass.superclass = parent;
+    klass.subclasses = [];
+
+    if (parent) {
+      var subclass = function() { };
+      subclass.prototype = parent.prototype;
+      klass.prototype = new subclass;
+      parent.subclasses.push(klass);
+    }
+
+    for (var i = 0; i < properties.length; i++)
+      klass.addMethods(properties[i]);
+
+    if (!klass.prototype.initialize)
+      klass.prototype.initialize = Prototype.emptyFunction;
+
+    klass.prototype.constructor = klass;
+
+    return klass;
+  }
+};
+
+Class.Methods = {
+  addMethods: function(source) {
+    var ancestor   = this.superclass && this.superclass.prototype;
+    var properties = Object.keys(source);
+
+    if (!Object.keys({ toString: true }).length)
+      properties.push("toString", "valueOf");
+
+    for (var i = 0, length = properties.length; i < length; i++) {
+      var property = properties[i], value = source[property];
+      if (ancestor && Object.isFunction(value) &&
+          value.argumentNames().first() == "$super") {
+        var method = value;
+        value = (function(m) {
+          return function() { return ancestor[m].apply(this, arguments) };
+        })(property).wrap(method);
+
+        value.valueOf = method.valueOf.bind(method);
+        value.toString = method.toString.bind(method);
+      }
+      this.prototype[property] = value;
+    }
+
+    return this;
+  }
+};
+
+var Abstract = { };
+
+Object.extend = function(destination, source) {
+  for (var property in source)
+    destination[property] = source[property];
+  return destination;
+};
+
+Object.extend(Object, {
+  inspect: function(object) {
+    try {
+      if (Object.isUndefined(object)) return 'undefined';
+      if (object === null) return 'null';
+      return object.inspect ? object.inspect() : String(object);
+    } catch (e) {
+      if (e instanceof RangeError) return '...';
+      throw e;
+    }
+  },
+
+  toJSON: function(object) {
+    var type = typeof object;
+    switch (type) {
+      case 'undefined':
+      case 'function':
+      case 'unknown': return;
+      case 'boolean': return object.toString();
+    }
+
+    if (object === null) return 'null';
+    if (object.toJSON) return object.toJSON();
+    if (Object.isElement(object)) return;
+
+    var results = [];
+    for (var property in object) {
+      var value = Object.toJSON(object[property]);
+      if (!Object.isUndefined(value))
+        results.push(property.toJSON() + ': ' + value);
+    }
+
+    return '{' + results.join(', ') + '}';
+  },
+
+  toQueryString: function(object) {
+    return $H(object).toQueryString();
+  },
+
+  toHTML: function(object) {
+    return object && object.toHTML ? object.toHTML() : String.interpret(object);
+  },
+
+  keys: function(object) {
+    var keys = [];
+    for (var property in object)
+      keys.push(property);
+    return keys;
+  },
+
+  values: function(object) {
+    var values = [];
+    for (var property in object)
+      values.push(object[property]);
+    return values;
+  },
+
+  clone: function(object) {
+    return Object.extend({ }, object);
+  },
+
+  isElement: function(object) {
+    return !!(object && object.nodeType == 1);
+  },
+
+  isArray: function(object) {
+    return object != null && typeof object == "object" &&
+      'splice' in object && 'join' in object;
+  },
+
+  isHash: function(object) {
+    return object instanceof Hash;
+  },
+
+  isFunction: function(object) {
+    return typeof object == "function";
+  },
+
+  isString: function(object) {
+    return typeof object == "string";
+  },
+
+  isNumber: function(object) {
+    return typeof object == "number";
+  },
+
+  isUndefined: function(object) {
+    return typeof object == "undefined";
+  }
+});
+
+Object.extend(Function.prototype, {
+  argumentNames: function() {
+    var names = this.toString().match(/^[\s\(]*function[^(]*\(([^\)]*)\)/)[1]
+      .replace(/\s+/g, '').split(',');
+    return names.length == 1 && !names[0] ? [] : names;
+  },
+
+  bind: function() {
+    if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this;
+    var __method = this, args = $A(arguments), object = args.shift();
+    return function() {
+      return __method.apply(object, args.concat($A(arguments)));
+    }
+  },
+
+  bindAsEventListener: function() {
+    var __method = this, args = $A(arguments), object = args.shift();
+    return function(event) {
+      return __method.apply(object, [event || window.event].concat(args));
+    }
+  },
+
+  curry: function() {
+    if (!arguments.length) return this;
+    var __method = this, args = $A(arguments);
+    return function() {
+      return __method.apply(this, args.concat($A(arguments)));
+    }
+  },
+
+  delay: function() {
+    var __method = this, args = $A(arguments), timeout = args.shift() * 1000;
+    return window.setTimeout(function() {
+      return __method.apply(__method, args);
+    }, timeout);
+  },
+
+  defer: function() {
+    var args = [0.01].concat($A(arguments));
+    return this.delay.apply(this, args);
+  },
+
+  wrap: function(wrapper) {
+    var __method = this;
+    return function() {
+      return wrapper.apply(this, [__method.bind(this)].concat($A(arguments)));
+    }
+  },
+
+  methodize: function() {
+    if (this._methodized) return this._methodized;
+    var __method = this;
+    return this._methodized = function() {
+      return __method.apply(null, [this].concat($A(arguments)));
+    };
+  }
+});
+
+Date.prototype.toJSON = function() {
+  return '"' + this.getUTCFullYear() + '-' +
+    (this.getUTCMonth() + 1).toPaddedString(2) + '-' +
+    this.getUTCDate().toPaddedString(2) + 'T' +
+    this.getUTCHours().toPaddedString(2) + ':' +
+    this.getUTCMinutes().toPaddedString(2) + ':' +
+    this.getUTCSeconds().toPaddedString(2) + 'Z"';
+};
+
+var Try = {
+  these: function() {
+    var returnValue;
+
+    for (var i = 0, length = arguments.length; i < length; i++) {
+      var lambda = arguments[i];
+      try {
+        returnValue = lambda();
+        break;
+      } catch (e) { }
+    }
+
+    return returnValue;
+  }
+};
+
+RegExp.prototype.match = RegExp.prototype.test;
+
+RegExp.escape = function(str) {
+  return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
+};
+
+/*--------------------------------------------------------------------------*/
+
+var PeriodicalExecuter = Class.create({
+  initialize: function(callback, frequency) {
+    this.callback = callback;
+    this.frequency = frequency;
+    this.currentlyExecuting = false;
+
+    this.registerCallback();
+  },
+
+  registerCallback: function() {
+    this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
+  },
+
+  execute: function() {
+    this.callback(this);
+  },
+
+  stop: function() {
+    if (!this.timer) return;
+    clearInterval(this.timer);
+    this.timer = null;
+  },
+
+  onTimerEvent: function() {
+    if (!this.currentlyExecuting) {
+      try {
+        this.currentlyExecuting = true;
+        this.execute();
+      } finally {
+        this.currentlyExecuting = false;
+      }
+    }
+  }
+});
+Object.extend(String, {
+  interpret: function(value) {
+    return value == null ? '' : String(value);
+  },
+  specialChar: {
+    '\b': '\\b',
+    '\t': '\\t',
+    '\n': '\\n',
+    '\f': '\\f',
+    '\r': '\\r',
+    '\\': '\\\\'
+  }
+});
+
+Object.extend(String.prototype, {
+  gsub: function(pattern, replacement) {
+    var result = '', source = this, match;
+    replacement = arguments.callee.prepareReplacement(replacement);
+
+    while (source.length > 0) {
+      if (match = source.match(pattern)) {
+        result += source.slice(0, match.index);
+        result += String.interpret(replacement(match));
+        source  = source.slice(match.index + match[0].length);
+      } else {
+        result += source, source = '';
+      }
+    }
+    return result;
+  },
+
+  sub: function(pattern, replacement, count) {
+    replacement = this.gsub.prepareReplacement(replacement);
+    count = Object.isUndefined(count) ? 1 : count;
+
+    return this.gsub(pattern, function(match) {
+      if (--count < 0) return match[0];
+      return replacement(match);
+    });
+  },
+
+  scan: function(pattern, iterator) {
+    this.gsub(pattern, iterator);
+    return String(this);
+  },
+
+  truncate: function(length, truncation) {
+    length = length || 30;
+    truncation = Object.isUndefined(truncation) ? '...' : truncation;
+    return this.length > length ?
+      this.slice(0, length - truncation.length) + truncation : String(this);
+  },
+
+  strip: function() {
+    return this.replace(/^\s+/, '').replace(/\s+$/, '');
+  },
+
+  stripTags: function() {
+    return this.replace(/<\/?[^>]+>/gi, '');
+  },
+
+  stripScripts: function() {
+    return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
+  },
+
+  extractScripts: function() {
+    var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
+    var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
+    return (this.match(matchAll) || []).map(function(scriptTag) {
+      return (scriptTag.match(matchOne) || ['', ''])[1];
+    });
+  },
+
+  evalScripts: function() {
+    return this.extractScripts().map(function(script) { return eval(script) });
+  },
+
+  escapeHTML: function() {
+    var self = arguments.callee;
+    self.text.data = this;
+    return self.div.innerHTML;
+  },
+
+  unescapeHTML: function() {
+    var div = new Element('div');
+    div.innerHTML = this.stripTags();
+    return div.childNodes[0] ? (div.childNodes.length > 1 ?
+      $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) :
+      div.childNodes[0].nodeValue) : '';
+  },
+
+  toQueryParams: function(separator) {
+    var match = this.strip().match(/([^?#]*)(#.*)?$/);
+    if (!match) return { };
+
+    return match[1].split(separator || '&').inject({ }, function(hash, pair) {
+      if ((pair = pair.split('='))[0]) {
+        var key = decodeURIComponent(pair.shift());
+        var value = pair.length > 1 ? pair.join('=') : pair[0];
+        if (value != undefined) value = decodeURIComponent(value);
+
+        if (key in hash) {
+          if (!Object.isArray(hash[key])) hash[key] = [hash[key]];
+          hash[key].push(value);
+        }
+        else hash[key] = value;
+      }
+      return hash;
+    });
+  },
+
+  toArray: function() {
+    return this.split('');
+  },
+
+  succ: function() {
+    return this.slice(0, this.length - 1) +
+      String.fromCharCode(this.charCodeAt(this.length - 1) + 1);
+  },
+
+  times: function(count) {
+    return count < 1 ? '' : new Array(count + 1).join(this);
+  },
+
+  camelize: function() {
+    var parts = this.split('-'), len = parts.length;
+    if (len == 1) return parts[0];
+
+    var camelized = this.charAt(0) == '-'
+      ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)
+      : parts[0];
+
+    for (var i = 1; i < len; i++)
+      camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);
+
+    return camelized;
+  },
+
+  capitalize: function() {
+    return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
+  },
+
+  underscore: function() {
+    return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase();
+  },
+
+  dasherize: function() {
+    return this.gsub(/_/,'-');
+  },
+
+  inspect: function(useDoubleQuotes) {
+    var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) {
+      var character = String.specialChar[match[0]];
+      return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16);
+    });
+    if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"';
+    return "'" + escapedString.replace(/'/g, '\\\'') + "'";
+  },
+
+  toJSON: function() {
+    return this.inspect(true);
+  },
+
+  unfilterJSON: function(filter) {
+    return this.sub(filter || Prototype.JSONFilter, '#{1}');
+  },
+
+  isJSON: function() {
+    var str = this;
+    if (str.blank()) return false;
+    str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, '');
+    return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str);
+  },
+
+  evalJSON: function(sanitize) {
+    var json = this.unfilterJSON();
+    try {
+      if (!sanitize || json.isJSON()) return eval('(' + json + ')');
+    } catch (e) { }
+    throw new SyntaxError('Badly formed JSON string: ' + this.inspect());
+  },
+
+  include: function(pattern) {
+    return this.indexOf(pattern) > -1;
+  },
+
+  startsWith: function(pattern) {
+    return this.indexOf(pattern) === 0;
+  },
+
+  endsWith: function(pattern) {
+    var d = this.length - pattern.length;
+    return d >= 0 && this.lastIndexOf(pattern) === d;
+  },
+
+  empty: function() {
+    return this == '';
+  },
+
+  blank: function() {
+    return /^\s*$/.test(this);
+  },
+
+  interpolate: function(object, pattern) {
+    return new Template(this, pattern).evaluate(object);
+  }
+});
+
+if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, {
+  escapeHTML: function() {
+    return this.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
+  },
+  unescapeHTML: function() {
+    return this.stripTags().replace(/&amp;/g,'&').replace(/&lt;/g,'<').replace(/&gt;/g,'>');
+  }
+});
+
+String.prototype.gsub.prepareReplacement = function(replacement) {
+  if (Object.isFunction(replacement)) return replacement;
+  var template = new Template(replacement);
+  return function(match) { return template.evaluate(match) };
+};
+
+String.prototype.parseQuery = String.prototype.toQueryParams;
+
+Object.extend(String.prototype.escapeHTML, {
+  div:  document.createElement('div'),
+  text: document.createTextNode('')
+});
+
+String.prototype.escapeHTML.div.appendChild(String.prototype.escapeHTML.text);
+
+var Template = Class.create({
+  initialize: function(template, pattern) {
+    this.template = template.toString();
+    this.pattern = pattern || Template.Pattern;
+  },
+
+  evaluate: function(object) {
+    if (Object.isFunction(object.toTemplateReplacements))
+      object = object.toTemplateReplacements();
+
+    return this.template.gsub(this.pattern, function(match) {
+      if (object == null) return '';
+
+      var before = match[1] || '';
+      if (before == '\\') return match[2];
+
+      var ctx = object, expr = match[3];
+      var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;
+      match = pattern.exec(expr);
+      if (match == null) return before;
+
+      while (match != null) {
+        var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1];
+        ctx = ctx[comp];
+        if (null == ctx || '' == match[3]) break;
+        expr = expr.substring('[' == match[3] ? match[1].length : match[0].length);
+        match = pattern.exec(expr);
+      }
+
+      return before + String.interpret(ctx);
+    });
+  }
+});
+Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
+
+var $break = { };
+
+var Enumerable = {
+  each: function(iterator, context) {
+    var index = 0;
+    try {
+      this._each(function(value) {
+        iterator.call(context, value, index++);
+      });
+    } catch (e) {
+      if (e != $break) throw e;
+    }
+    return this;
+  },
+
+  eachSlice: function(number, iterator, context) {
+    var index = -number, slices = [], array = this.toArray();
+    if (number < 1) return array;
+    while ((index += number) < array.length)
+      slices.push(array.slice(index, index+number));
+    return slices.collect(iterator, context);
+  },
+
+  all: function(iterator, context) {
+    iterator = iterator || Prototype.K;
+    var result = true;
+    this.each(function(value, index) {
+      result = result && !!iterator.call(context, value, index);
+      if (!result) throw $break;
+    });
+    return result;
+  },
+
+  any: function(iterator, context) {
+    iterator = iterator || Prototype.K;
+    var result = false;
+    this.each(function(value, index) {
+      if (result = !!iterator.call(context, value, index))
+        throw $break;
+    });
+    return result;
+  },
+
+  collect: function(iterator, context) {
+    iterator = iterator || Prototype.K;
+    var results = [];
+    this.each(function(value, index) {
+      results.push(iterator.call(context, value, index));
+    });
+    return results;
+  },
+
+  detect: function(iterator, context) {
+    var result;
+    this.each(function(value, index) {
+      if (iterator.call(context, value, index)) {
+        result = value;
+        throw $break;
+      }
+    });
+    return result;
+  },
+
+  findAll: function(iterator, context) {
+    var results = [];
+    this.each(function(value, index) {
+      if (iterator.call(context, value, index))
+        results.push(value);
+    });
+    return results;
+  },
+
+  grep: function(filter, iterator, context) {
+    iterator = iterator || Prototype.K;
+    var results = [];
+
+    if (Object.isString(filter))
+      filter = new RegExp(filter);
+
+    this.each(function(value, index) {
+      if (filter.match(value))
+        results.push(iterator.call(context, value, index));
+    });
+    return results;
+  },
+
+  include: function(object) {
+    if (Object.isFunction(this.indexOf))
+      if (this.indexOf(object) != -1) return true;
+
+    var found = false;
+    this.each(function(value) {
+      if (value == object) {
+        found = true;
+        throw $break;
+      }
+    });
+    return found;
+  },
+
+  inGroupsOf: function(number, fillWith) {
+    fillWith = Object.isUndefined(fillWith) ? null : fillWith;
+    return this.eachSlice(number, function(slice) {
+      while(slice.length < number) slice.push(fillWith);
+      return slice;
+    });
+  },
+
+  inject: function(memo, iterator, context) {
+    this.each(function(value, index) {
+      memo = iterator.call(context, memo, value, index);
+    });
+    return memo;
+  },
+
+  invoke: function(method) {
+    var args = $A(arguments).slice(1);
+    return this.map(function(value) {
+      return value[method].apply(value, args);
+    });
+  },
+
+  max: function(iterator, context) {
+    iterator = iterator || Prototype.K;
+    var result;
+    this.each(function(value, index) {
+      value = iterator.call(context, value, index);
+      if (result == null || value >= result)
+        result = value;
+    });
+    return result;
+  },
+
+  min: function(iterator, context) {
+    iterator = iterator || Prototype.K;
+    var result;
+    this.each(function(value, index) {
+      value = iterator.call(context, value, index);
+      if (result == null || value < result)
+        result = value;
+    });
+    return result;
+  },
+
+  partition: function(iterator, context) {
+    iterator = iterator || Prototype.K;
+    var trues = [], falses = [];
+    this.each(function(value, index) {
+      (iterator.call(context, value, index) ?
+        trues : falses).push(value);
+    });
+    return [trues, falses];
+  },
+
+  pluck: function(property) {
+    var results = [];
+    this.each(function(value) {
+      results.push(value[property]);
+    });
+    return results;
+  },
+
+  reject: function(iterator, context) {
+    var results = [];
+    this.each(function(value, index) {
+      if (!iterator.call(context, value, index))
+        results.push(value);
+    });
+    return results;
+  },
+
+  sortBy: function(iterator, context) {
+    return this.map(function(value, index) {
+      return {
+        value: value,
+        criteria: iterator.call(context, value, index)
+      };
+    }).sort(function(left, right) {
+      var a = left.criteria, b = right.criteria;
+      return a < b ? -1 : a > b ? 1 : 0;
+    }).pluck('value');
+  },
+
+  toArray: function() {
+    return this.map();
+  },
+
+  zip: function() {
+    var iterator = Prototype.K, args = $A(arguments);
+    if (Object.isFunction(args.last()))
+      iterator = args.pop();
+
+    var collections = [this].concat(args).map($A);
+    return this.map(function(value, index) {
+      return iterator(collections.pluck(index));
+    });
+  },
+
+  size: function() {
+    return this.toArray().length;
+  },
+
+  inspect: function() {
+    return '#<Enumerable:' + this.toArray().inspect() + '>';
+  }
+};
+
+Object.extend(Enumerable, {
+  map:     Enumerable.collect,
+  find:    Enumerable.detect,
+  select:  Enumerable.findAll,
+  filter:  Enumerable.findAll,
+  member:  Enumerable.include,
+  entries: Enumerable.toArray,
+  every:   Enumerable.all,
+  some:    Enumerable.any
+});
+function $A(iterable) {
+  if (!iterable) return [];
+  if (iterable.toArray) return iterable.toArray();
+  var length = iterable.length || 0, results = new Array(length);
+  while (length--) results[length] = iterable[length];
+  return results;
+}
+
+if (Prototype.Browser.WebKit) {
+  $A = function(iterable) {
+    if (!iterable) return [];
+    // In Safari, only use the `toArray` method if it's not a NodeList.
+    // A NodeList is a function, has an function `item` property, and a numeric
+    // `length` property. Adapted from Google Doctype.
+    if (!(typeof iterable === 'function' && typeof iterable.length ===
+        'number' && typeof iterable.item === 'function') && iterable.toArray)
+      return iterable.toArray();
+    var length = iterable.length || 0, results = new Array(length);
+    while (length--) results[length] = iterable[length];
+    return results;
+  };
+}
+
+Array.from = $A;
+
+Object.extend(Array.prototype, Enumerable);
+
+if (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse;
+
+Object.extend(Array.prototype, {
+  _each: function(iterator) {
+    for (var i = 0, length = this.length; i < length; i++)
+      iterator(this[i]);
+  },
+
+  clear: function() {
+    this.length = 0;
+    return this;
+  },
+
+  first: function() {
+    return this[0];
+  },
+
+  last: function() {
+    return this[this.length - 1];
+  },
+
+  compact: function() {
+    return this.select(function(value) {
+      return value != null;
+    });
+  },
+
+  flatten: function() {
+    return this.inject([], function(array, value) {
+      return array.concat(Object.isArray(value) ?
+        value.flatten() : [value]);
+    });
+  },
+
+  without: function() {
+    var values = $A(arguments);
+    return this.select(function(value) {
+      return !values.include(value);
+    });
+  },
+
+  reverse: function(inline) {
+    return (inline !== false ? this : this.toArray())._reverse();
+  },
+
+  reduce: function() {
+    return this.length > 1 ? this : this[0];
+  },
+
+  uniq: function(sorted) {
+    return this.inject([], function(array, value, index) {
+      if (0 == index || (sorted ? array.last() != value : !array.include(value)))
+        array.push(value);
+      return array;
+    });
+  },
+
+  intersect: function(array) {
+    return this.uniq().findAll(function(item) {
+      return array.detect(function(value) { return item === value });
+    });
+  },
+
+  clone: function() {
+    return [].concat(this);
+  },
+
+  size: function() {
+    return this.length;
+  },
+
+  inspect: function() {
+    return '[' + this.map(Object.inspect).join(', ') + ']';
+  },
+
+  toJSON: function() {
+    var results = [];
+    this.each(function(object) {
+      var value = Object.toJSON(object);
+      if (!Object.isUndefined(value)) results.push(value);
+    });
+    return '[' + results.join(', ') + ']';
+  }
+});
+
+// use native browser JS 1.6 implementation if available
+if (Object.isFunction(Array.prototype.forEach))
+  Array.prototype._each = Array.prototype.forEach;
+
+if (!Array.prototype.indexOf) Array.prototype.indexOf = function(item, i) {
+  i || (i = 0);
+  var length = this.length;
+  if (i < 0) i = length + i;
+  for (; i < length; i++)
+    if (this[i] === item) return i;
+  return -1;
+};
+
+if (!Array.prototype.lastIndexOf) Array.prototype.lastIndexOf = function(item, i) {
+  i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1;
+  var n = this.slice(0, i).reverse().indexOf(item);
+  return (n < 0) ? n : i - n - 1;
+};
+
+Array.prototype.toArray = Array.prototype.clone;
+
+function $w(string) {
+  if (!Object.isString(string)) return [];
+  string = string.strip();
+  return string ? string.split(/\s+/) : [];
+}
+
+if (Prototype.Browser.Opera){
+  Array.prototype.concat = function() {
+    var array = [];
+    for (var i = 0, length = this.length; i < length; i++) array.push(this[i]);
+    for (var i = 0, length = arguments.length; i < length; i++) {
+      if (Object.isArray(arguments[i])) {
+        for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++)
+          array.push(arguments[i][j]);
+      } else {
+        array.push(arguments[i]);
+      }
+    }
+    return array;
+  };
+}
+Object.extend(Number.prototype, {
+  toColorPart: function() {
+    return this.toPaddedString(2, 16);
+  },
+
+  succ: function() {
+    return this + 1;
+  },
+
+  times: function(iterator, context) {
+    $R(0, this, true).each(iterator, context);
+    return this;
+  },
+
+  toPaddedString: function(length, radix) {
+    var string = this.toString(radix || 10);
+    return '0'.times(length - string.length) + string;
+  },
+
+  toJSON: function() {
+    return isFinite(this) ? this.toString() : 'null';
+  }
+});
+
+$w('abs round ceil floor').each(function(method){
+  Number.prototype[method] = Math[method].methodize();
+});
+function $H(object) {
+  return new Hash(object);
+};
+
+var Hash = Class.create(Enumerable, (function() {
+
+  function toQueryPair(key, value) {
+    if (Object.isUndefined(value)) return key;
+    return key + '=' + encodeURIComponent(String.interpret(value));
+  }
+
+  return {
+    initialize: function(object) {
+      this._object = Object.isHash(object) ? object.toObject() : Object.clone(object);
+    },
+
+    _each: function(iterator) {
+      for (var key in this._object) {
+        var value = this._object[key], pair = [key, value];
+        pair.key = key;
+        pair.value = value;
+        iterator(pair);
+      }
+    },
+
+    set: function(key, value) {
+      return this._object[key] = value;
+    },
+
+    get: function(key) {
+      // simulating poorly supported hasOwnProperty
+      if (this._object[key] !== Object.prototype[key])
+        return this._object[key];
+    },
+
+    unset: function(key) {
+      var value = this._object[key];
+      delete this._object[key];
+      return value;
+    },
+
+    toObject: function() {
+      return Object.clone(this._object);
+    },
+
+    keys: function() {
+      return this.pluck('key');
+    },
+
+    values: function() {
+      return this.pluck('value');
+    },
+
+    index: function(value) {
+      var match = this.detect(function(pair) {
+        return pair.value === value;
+      });
+      return match && match.key;
+    },
+
+    merge: function(object) {
+      return this.clone().update(object);
+    },
+
+    update: function(object) {
+      return new Hash(object).inject(this, function(result, pair) {
+        result.set(pair.key, pair.value);
+        return result;
+      });
+    },
+
+    toQueryString: function() {
+      return this.inject([], function(results, pair) {
+        var key = encodeURIComponent(pair.key), values = pair.value;
+
+        if (values && typeof values == 'object') {
+          if (Object.isArray(values))
+            return results.concat(values.map(toQueryPair.curry(key)));
+        } else results.push(toQueryPair(key, values));
+        return results;
+      }).join('&');
+    },
+
+    inspect: function() {
+      return '#<Hash:{' + this.map(function(pair) {
+        return pair.map(Object.inspect).join(': ');
+      }).join(', ') + '}>';
+    },
+
+    toJSON: function() {
+      return Object.toJSON(this.toObject());
+    },
+
+    clone: function() {
+      return new Hash(this);
+    }
+  }
+})());
+
+Hash.prototype.toTemplateReplacements = Hash.prototype.toObject;
+Hash.from = $H;
+var ObjectRange = Class.create(Enumerable, {
+  initialize: function(start, end, exclusive) {
+    this.start = start;
+    this.end = end;
+    this.exclusive = exclusive;
+  },
+
+  _each: function(iterator) {
+    var value = this.start;
+    while (this.include(value)) {
+      iterator(value);
+      value = value.succ();
+    }
+  },
+
+  include: function(value) {
+    if (value < this.start)
+      return false;
+    if (this.exclusive)
+      return value < this.end;
+    return value <= this.end;
+  }
+});
+
+var $R = function(start, end, exclusive) {
+  return new ObjectRange(start, end, exclusive);
+};
+
+var Ajax = {
+  getTransport: function() {
+    return Try.these(
+      function() {return new XMLHttpRequest()},
+      function() {return new ActiveXObject('Msxml2.XMLHTTP')},
+      function() {return new ActiveXObject('Microsoft.XMLHTTP')}
+    ) || false;
+  },
+
+  activeRequestCount: 0
+};
+
+Ajax.Responders = {
+  responders: [],
+
+  _each: function(iterator) {
+    this.responders._each(iterator);
+  },
+
+  register: function(responder) {
+    if (!this.include(responder))
+      this.responders.push(responder);
+  },
+
+  unregister: function(responder) {
+    this.responders = this.responders.without(responder);
+  },
+
+  dispatch: function(callback, request, transport, json) {
+    this.each(function(responder) {
+      if (Object.isFunction(responder[callback])) {
+        try {
+          responder[callback].apply(responder, [request, transport, json]);
+        } catch (e) { }
+      }
+    });
+  }
+};
+
+Object.extend(Ajax.Responders, Enumerable);
+
+Ajax.Responders.register({
+  onCreate:   function() { Ajax.activeRequestCount++ },
+  onComplete: function() { Ajax.activeRequestCount-- }
+});
+
+Ajax.Base = Class.create({
+  initialize: function(options) {
+    this.options = {
+      method:       'post',
+      asynchronous: true,
+      contentType:  'application/x-www-form-urlencoded',
+      encoding:     'UTF-8',
+      parameters:   '',
+      evalJSON:     true,
+      evalJS:       true
+    };
+    Object.extend(this.options, options || { });
+
+    this.options.method = this.options.method.toLowerCase();
+
+    if (Object.isString(this.options.parameters))
+      this.options.parameters = this.options.parameters.toQueryParams();
+    else if (Object.isHash(this.options.parameters))
+      this.options.parameters = this.options.parameters.toObject();
+  }
+});
+
+Ajax.Request = Class.create(Ajax.Base, {
+  _complete: false,
+
+  initialize: function($super, url, options) {
+    $super(options);
+    this.transport = Ajax.getTransport();
+    this.request(url);
+  },
+
+  request: function(url) {
+    this.url = url;
+    this.method = this.options.method;
+    var params = Object.clone(this.options.parameters);
+
+    if (!['get', 'post'].include(this.method)) {
+      // simulate other verbs over post
+      params['_method'] = this.method;
+      this.method = 'post';
+    }
+
+    this.parameters = params;
+
+    if (params = Object.toQueryString(params)) {
+      // when GET, append parameters to URL
+      if (this.method == 'get')
+        this.url += (this.url.include('?') ? '&' : '?') + params;
+      else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent))
+        params += '&_=';
+    }
+
+    try {
+      var response = new Ajax.Response(this);
+      if (this.options.onCreate) this.options.onCreate(response);
+      Ajax.Responders.dispatch('onCreate', this, response);
+
+      this.transport.open(this.method.toUpperCase(), this.url,
+        this.options.asynchronous);
+
+      if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1);
+
+      this.transport.onreadystatechange = this.onStateChange.bind(this);
+      this.setRequestHeaders();
+
+      this.body = this.method == 'post' ? (this.options.postBody || params) : null;
+      this.transport.send(this.body);
+
+      /* Force Firefox to handle ready state 4 for synchronous requests */
+      if (!this.options.asynchronous && this.transport.overrideMimeType)
+        this.onStateChange();
+
+    }
+    catch (e) {
+      this.dispatchException(e);
+    }
+  },
+
+  onStateChange: function() {
+    var readyState = this.transport.readyState;
+    if (readyState > 1 && !((readyState == 4) && this._complete))
+      this.respondToReadyState(this.transport.readyState);
+  },
+
+  setRequestHeaders: function() {
+    var headers = {
+      'X-Requested-With': 'XMLHttpRequest',
+      'X-Prototype-Version': Prototype.Version,
+      'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
+    };
+
+    if (this.method == 'post') {
+      headers['Content-type'] = this.options.contentType +
+        (this.options.encoding ? '; charset=' + this.options.encoding : '');
+
+      /* Force "Connection: close" for older Mozilla browsers to work
+       * around a bug where XMLHttpRequest sends an incorrect
+       * Content-length header. See Mozilla Bugzilla #246651.
+       */
+      if (this.transport.overrideMimeType &&
+          (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
+            headers['Connection'] = 'close';
+    }
+
+    // user-defined headers
+    if (typeof this.options.requestHeaders == 'object') {
+      var extras = this.options.requestHeaders;
+
+      if (Object.isFunction(extras.push))
+        for (var i = 0, length = extras.length; i < length; i += 2)
+          headers[extras[i]] = extras[i+1];
+      else
+        $H(extras).each(function(pair) { headers[pair.key] = pair.value });
+    }
+
+    for (var name in headers)
+      this.transport.setRequestHeader(name, headers[name]);
+  },
+
+  success: function() {
+    var status = this.getStatus();
+    return !status || (status >= 200 && status < 300);
+  },
+
+  getStatus: function() {
+    try {
+      return this.transport.status || 0;
+    } catch (e) { return 0 }
+  },
+
+  respondToReadyState: function(readyState) {
+    var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this);
+
+    if (state == 'Complete') {
+      try {
+        this._complete = true;
+        (this.options['on' + response.status]
+         || this.options['on' + (this.success() ? 'Success' : 'Failure')]
+         || Prototype.emptyFunction)(response, response.headerJSON);
+      } catch (e) {
+        this.dispatchException(e);
+      }
+
+      var contentType = response.getHeader('Content-type');
+      if (this.options.evalJS == 'force'
+          || (this.options.evalJS && this.isSameOrigin() && contentType
+          && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i)))
+        this.evalResponse();
+    }
+
+    try {
+      (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON);
+      Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON);
+    } catch (e) {
+      this.dispatchException(e);
+    }
+
+    if (state == 'Complete') {
+      // avoid memory leak in MSIE: clean up
+      this.transport.onreadystatechange = Prototype.emptyFunction;
+    }
+  },
+
+  isSameOrigin: function() {
+    var m = this.url.match(/^\s*https?:\/\/[^\/]*/);
+    return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({
+      protocol: location.protocol,
+      domain: document.domain,
+      port: location.port ? ':' + location.port : ''
+    }));
+  },
+
+  getHeader: function(name) {
+    try {
+      return this.transport.getResponseHeader(name) || null;
+    } catch (e) { return null }
+  },
+
+  evalResponse: function() {
+    try {
+      return eval((this.transport.responseText || '').unfilterJSON());
+    } catch (e) {
+      this.dispatchException(e);
+    }
+  },
+
+  dispatchException: function(exception) {
+    (this.options.onException || Prototype.emptyFunction)(this, exception);
+    Ajax.Responders.dispatch('onException', this, exception);
+  }
+});
+
+Ajax.Request.Events =
+  ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
+
+Ajax.Response = Class.create({
+  initialize: function(request){
+    this.request = request;
+    var transport  = this.transport  = request.transport,
+        readyState = this.readyState = transport.readyState;
+
+    if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) {
+      this.status       = this.getStatus();
+      this.statusText   = this.getStatusText();
+      this.responseText = String.interpret(transport.responseText);
+      this.headerJSON   = this._getHeaderJSON();
+    }
+
+    if(readyState == 4) {
+      var xml = transport.responseXML;
+      this.responseXML  = Object.isUndefined(xml) ? null : xml;
+      this.responseJSON = this._getResponseJSON();
+    }
+  },
+
+  status:      0,
+  statusText: '',
+
+  getStatus: Ajax.Request.prototype.getStatus,
+
+  getStatusText: function() {
+    try {
+      return this.transport.statusText || '';
+    } catch (e) { return '' }
+  },
+
+  getHeader: Ajax.Request.prototype.getHeader,
+
+  getAllHeaders: function() {
+    try {
+      return this.getAllResponseHeaders();
+    } catch (e) { return null }
+  },
+
+  getResponseHeader: function(name) {
+    return this.transport.getResponseHeader(name);
+  },
+
+  getAllResponseHeaders: function() {
+    return this.transport.getAllResponseHeaders();
+  },
+
+  _getHeaderJSON: function() {
+    var json = this.getHeader('X-JSON');
+    if (!json) return null;
+    json = decodeURIComponent(escape(json));
+    try {
+      return json.evalJSON(this.request.options.sanitizeJSON ||
+        !this.request.isSameOrigin());
+    } catch (e) {
+      this.request.dispatchException(e);
+    }
+  },
+
+  _getResponseJSON: function() {
+    var options = this.request.options;
+    if (!options.evalJSON || (options.evalJSON != 'force' &&
+      !(this.getHeader('Content-type') || '').include('application/json')) ||
+        this.responseText.blank())
+          return null;
+    try {
+      return this.responseText.evalJSON(options.sanitizeJSON ||
+        !this.request.isSameOrigin());
+    } catch (e) {
+      this.request.dispatchException(e);
+    }
+  }
+});
+
+Ajax.Updater = Class.create(Ajax.Request, {
+  initialize: function($super, container, url, options) {
+    this.container = {
+      success: (container.success || container),
+      failure: (container.failure || (container.success ? null : container))
+    };
+
+    options = Object.clone(options);
+    var onComplete = options.onComplete;
+    options.onComplete = (function(response, json) {
+      this.updateContent(response.responseText);
+      if (Object.isFunction(onComplete)) onComplete(response, json);
+    }).bind(this);
+
+    $super(url, options);
+  },
+
+  updateContent: function(responseText) {
+    var receiver = this.container[this.success() ? 'success' : 'failure'],
+        options = this.options;
+
+    if (!options.evalScripts) responseText = responseText.stripScripts();
+
+    if (receiver = $(receiver)) {
+      if (options.insertion) {
+        if (Object.isString(options.insertion)) {
+          var insertion = { }; insertion[options.insertion] = responseText;
+          receiver.insert(insertion);
+        }
+        else options.insertion(receiver, responseText);
+      }
+      else receiver.update(responseText);
+    }
+  }
+});
+
+Ajax.PeriodicalUpdater = Class.create(Ajax.Base, {
+  initialize: function($super, container, url, options) {
+    $super(options);
+    this.onComplete = this.options.onComplete;
+
+    this.frequency = (this.options.frequency || 2);
+    this.decay = (this.options.decay || 1);
+
+    this.updater = { };
+    this.container = container;
+    this.url = url;
+
+    this.start();
+  },
+
+  start: function() {
+    this.options.onComplete = this.updateComplete.bind(this);
+    this.onTimerEvent();
+  },
+
+  stop: function() {
+    this.updater.options.onComplete = undefined;
+    clearTimeout(this.timer);
+    (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
+  },
+
+  updateComplete: function(response) {
+    if (this.options.decay) {
+      this.decay = (response.responseText == this.lastText ?
+        this.decay * this.options.decay : 1);
+
+      this.lastText = response.responseText;
+    }
+    this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency);
+  },
+
+  onTimerEvent: function() {
+    this.updater = new Ajax.Updater(this.container, this.url, this.options);
+  }
+});
+function $(element) {
+  if (arguments.length > 1) {
+    for (var i = 0, elements = [], length = arguments.length; i < length; i++)
+      elements.push($(arguments[i]));
+    return elements;
+  }
+  if (Object.isString(element))
+    element = document.getElementById(element);
+  return Element.extend(element);
+}
+
+if (Prototype.BrowserFeatures.XPath) {
+  document._getElementsByXPath = function(expression, parentElement) {
+    var results = [];
+    var query = document.evaluate(expression, $(parentElement) || document,
+      null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+    for (var i = 0, length = query.snapshotLength; i < length; i++)
+      results.push(Element.extend(query.snapshotItem(i)));
+    return results;
+  };
+}
+
+/*--------------------------------------------------------------------------*/
+
+if (!window.Node) var Node = { };
+
+if (!Node.ELEMENT_NODE) {
+  // DOM level 2 ECMAScript Language Binding
+  Object.extend(Node, {
+    ELEMENT_NODE: 1,
+    ATTRIBUTE_NODE: 2,
+    TEXT_NODE: 3,
+    CDATA_SECTION_NODE: 4,
+    ENTITY_REFERENCE_NODE: 5,
+    ENTITY_NODE: 6,
+    PROCESSING_INSTRUCTION_NODE: 7,
+    COMMENT_NODE: 8,
+    DOCUMENT_NODE: 9,
+    DOCUMENT_TYPE_NODE: 10,
+    DOCUMENT_FRAGMENT_NODE: 11,
+    NOTATION_NODE: 12
+  });
+}
+
+(function() {
+  var element = this.Element;
+  this.Element = function(tagName, attributes) {
+    attributes = attributes || { };
+    tagName = tagName.toLowerCase();
+    var cache = Element.cache;
+    if (Prototype.Browser.IE && attributes.name) {
+      tagName = '<' + tagName + ' name="' + attributes.name + '">';
+      delete attributes.name;
+      return Element.writeAttribute(document.createElement(tagName), attributes);
+    }
+    if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName));
+    return Element.writeAttribute(cache[tagName].cloneNode(false), attributes);
+  };
+  Object.extend(this.Element, element || { });
+  if (element) this.Element.prototype = element.prototype;
+}).call(window);
+
+Element.cache = { };
+
+Element.Methods = {
+  visible: function(element) {
+    return $(element).style.display != 'none';
+  },
+
+  toggle: function(element) {
+    element = $(element);
+    Element[Element.visible(element) ? 'hide' : 'show'](element);
+    return element;
+  },
+
+  hide: function(element) {
+    element = $(element);
+    element.style.display = 'none';
+    return element;
+  },
+
+  show: function(element) {
+    element = $(element);
+    element.style.display = '';
+    return element;
+  },
+
+  remove: function(element) {
+    element = $(element);
+    element.parentNode.removeChild(element);
+    return element;
+  },
+
+  update: function(element, content) {
+    element = $(element);
+    if (content && content.toElement) content = content.toElement();
+    if (Object.isElement(content)) return element.update().insert(content);
+    content = Object.toHTML(content);
+    element.innerHTML = content.stripScripts();
+    content.evalScripts.bind(content).defer();
+    return element;
+  },
+
+  replace: function(element, content) {
+    element = $(element);
+    if (content && content.toElement) content = content.toElement();
+    else if (!Object.isElement(content)) {
+      content = Object.toHTML(content);
+      var range = element.ownerDocument.createRange();
+      range.selectNode(element);
+      content.evalScripts.bind(content).defer();
+      content = range.createContextualFragment(content.stripScripts());
+    }
+    element.parentNode.replaceChild(content, element);
+    return element;
+  },
+
+  insert: function(element, insertions) {
+    element = $(element);
+
+    if (Object.isString(insertions) || Object.isNumber(insertions) ||
+        Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))
+          insertions = {bottom:insertions};
+
+    var content, insert, tagName, childNodes;
+
+    for (var position in insertions) {
+      content  = insertions[position];
+      position = position.toLowerCase();
+      insert = Element._insertionTranslations[position];
+
+      if (content && content.toElement) content = content.toElement();
+      if (Object.isElement(content)) {
+        insert(element, content);
+        continue;
+      }
+
+      content = Object.toHTML(content);
+
+      tagName = ((position == 'before' || position == 'after')
+        ? element.parentNode : element).tagName.toUpperCase();
+
+      childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
+
+      if (position == 'top' || position == 'after') childNodes.reverse();
+      childNodes.each(insert.curry(element));
+
+      content.evalScripts.bind(content).defer();
+    }
+
+    return element;
+  },
+
+  wrap: function(element, wrapper, attributes) {
+    element = $(element);
+    if (Object.isElement(wrapper))
+      $(wrapper).writeAttribute(attributes || { });
+    else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes);
+    else wrapper = new Element('div', wrapper);
+    if (element.parentNode)
+      element.parentNode.replaceChild(wrapper, element);
+    wrapper.appendChild(element);
+    return wrapper;
+  },
+
+  inspect: function(element) {
+    element = $(element);
+    var result = '<' + element.tagName.toLowerCase();
+    $H({'id': 'id', 'className': 'class'}).each(function(pair) {
+      var property = pair.first(), attribute = pair.last();
+      var value = (element[property] || '').toString();
+      if (value) result += ' ' + attribute + '=' + value.inspect(true);
+    });
+    return result + '>';
+  },
+
+  recursivelyCollect: function(element, property) {
+    element = $(element);
+    var elements = [];
+    while (element = element[property])
+      if (element.nodeType == 1)
+        elements.push(Element.extend(element));
+    return elements;
+  },
+
+  ancestors: function(element) {
+    return $(element).recursivelyCollect('parentNode');
+  },
+
+  descendants: function(element) {
+    return $(element).select("*");
+  },
+
+  firstDescendant: function(element) {
+    element = $(element).firstChild;
+    while (element && element.nodeType != 1) element = element.nextSibling;
+    return $(element);
+  },
+
+  immediateDescendants: function(element) {
+    if (!(element = $(element).firstChild)) return [];
+    while (element && element.nodeType != 1) element = element.nextSibling;
+    if (element) return [element].concat($(element).nextSiblings());
+    return [];
+  },
+
+  previousSiblings: function(element) {
+    return $(element).recursivelyCollect('previousSibling');
+  },
+
+  nextSiblings: function(element) {
+    return $(element).recursivelyCollect('nextSibling');
+  },
+
+  siblings: function(element) {
+    element = $(element);
+    return element.previousSiblings().reverse().concat(element.nextSiblings());
+  },
+
+  match: function(element, selector) {
+    if (Object.isString(selector))
+      selector = new Selector(selector);
+    return selector.match($(element));
+  },
+
+  up: function(element, expression, index) {
+    element = $(element);
+    if (arguments.length == 1) return $(element.parentNode);
+    var ancestors = element.ancestors();
+    return Object.isNumber(expression) ? ancestors[expression] :
+      Selector.findElement(ancestors, expression, index);
+  },
+
+  down: function(element, expression, index) {
+    element = $(element);
+    if (arguments.length == 1) return element.firstDescendant();
+    return Object.isNumber(expression) ? element.descendants()[expression] :
+      Element.select(element, expression)[index || 0];
+  },
+
+  previous: function(element, expression, index) {
+    element = $(element);
+    if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element));
+    var previousSiblings = element.previousSiblings();
+    return Object.isNumber(expression) ? previousSiblings[expression] :
+      Selector.findElement(previousSiblings, expression, index);
+  },
+
+  next: function(element, expression, index) {
+    element = $(element);
+    if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element));
+    var nextSiblings = element.nextSiblings();
+    return Object.isNumber(expression) ? nextSiblings[expression] :
+      Selector.findElement(nextSiblings, expression, index);
+  },
+
+  select: function() {
+    var args = $A(arguments), element = $(args.shift());
+    return Selector.findChildElements(element, args);
+  },
+
+  adjacent: function() {
+    var args = $A(arguments), element = $(args.shift());
+    return Selector.findChildElements(element.parentNode, args).without(element);
+  },
+
+  identify: function(element) {
+    element = $(element);
+    var id = element.readAttribute('id'), self = arguments.callee;
+    if (id) return id;
+    do { id = 'anonymous_element_' + self.counter++ } while ($(id));
+    element.writeAttribute('id', id);
+    return id;
+  },
+
+  readAttribute: function(element, name) {
+    element = $(element);
+    if (Prototype.Browser.IE) {
+      var t = Element._attributeTranslations.read;
+      if (t.values[name]) return t.values[name](element, name);
+      if (t.names[name]) name = t.names[name];
+      if (name.include(':')) {
+        return (!element.attributes || !element.attributes[name]) ? null :
+         element.attributes[name].value;
+      }
+    }
+    return element.getAttribute(name);
+  },
+
+  writeAttribute: function(element, name, value) {
+    element = $(element);
+    var attributes = { }, t = Element._attributeTranslations.write;
+
+    if (typeof name == 'object') attributes = name;
+    else attributes[name] = Object.isUndefined(value) ? true : value;
+
+    for (var attr in attributes) {
+      name = t.names[attr] || attr;
+      value = attributes[attr];
+      if (t.values[attr]) name = t.values[attr](element, value);
+      if (value === false || value === null)
+        element.removeAttribute(name);
+      else if (value === true)
+        element.setAttribute(name, name);
+      else element.setAttribute(name, value);
+    }
+    return element;
+  },
+
+  getHeight: function(element) {
+    return $(element).getDimensions().height;
+  },
+
+  getWidth: function(element) {
+    return $(element).getDimensions().width;
+  },
+
+  classNames: function(element) {
+    return new Element.ClassNames(element);
+  },
+
+  hasClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    var elementClassName = element.className;
+    return (elementClassName.length > 0 && (elementClassName == className ||
+      new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName)));
+  },
+
+  addClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    if (!element.hasClassName(className))
+      element.className += (element.className ? ' ' : '') + className;
+    return element;
+  },
+
+  removeClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    element.className = element.className.replace(
+      new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip();
+    return element;
+  },
+
+  toggleClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    return element[element.hasClassName(className) ?
+      'removeClassName' : 'addClassName'](className);
+  },
+
+  // removes whitespace-only text node children
+  cleanWhitespace: function(element) {
+    element = $(element);
+    var node = element.firstChild;
+    while (node) {
+      var nextNode = node.nextSibling;
+      if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
+        element.removeChild(node);
+      node = nextNode;
+    }
+    return element;
+  },
+
+  empty: function(element) {
+    return $(element).innerHTML.blank();
+  },
+
+  descendantOf: function(element, ancestor) {
+    element = $(element), ancestor = $(ancestor);
+
+    if (element.compareDocumentPosition)
+      return (element.compareDocumentPosition(ancestor) & 8) === 8;
+
+    if (ancestor.contains)
+      return ancestor.contains(element) && ancestor !== element;
+
+    while (element = element.parentNode)
+      if (element == ancestor) return true;
+
+    return false;
+  },
+
+  scrollTo: function(element) {
+    element = $(element);
+    var pos = element.cumulativeOffset();
+    window.scrollTo(pos[0], pos[1]);
+    return element;
+  },
+
+  getStyle: function(element, style) {
+    element = $(element);
+    style = style == 'float' ? 'cssFloat' : style.camelize();
+    var value = element.style[style];
+    if (!value || value == 'auto') {
+      var css = document.defaultView.getComputedStyle(element, null);
+      value = css ? css[style] : null;
+    }
+    if (style == 'opacity') return value ? parseFloat(value) : 1.0;
+    return value == 'auto' ? null : value;
+  },
+
+  getOpacity: function(element) {
+    return $(element).getStyle('opacity');
+  },
+
+  setStyle: function(element, styles) {
+    element = $(element);
+    var elementStyle = element.style, match;
+    if (Object.isString(styles)) {
+      element.style.cssText += ';' + styles;
+      return styles.include('opacity') ?
+        element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element;
+    }
+    for (var property in styles)
+      if (property == 'opacity') element.setOpacity(styles[property]);
+      else
+        elementStyle[(property == 'float' || property == 'cssFloat') ?
+          (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') :
+            property] = styles[property];
+
+    return element;
+  },
+
+  setOpacity: function(element, value) {
+    element = $(element);
+    element.style.opacity = (value == 1 || value === '') ? '' :
+      (value < 0.00001) ? 0 : value;
+    return element;
+  },
+
+  getDimensions: function(element) {
+    element = $(element);
+    var display = element.getStyle('display');
+    if (display != 'none' && display != null) // Safari bug
+      return {width: element.offsetWidth, height: element.offsetHeight};
+
+    // All *Width and *Height properties give 0 on elements with display none,
+    // so enable the element temporarily
+    var els = element.style;
+    var originalVisibility = els.visibility;
+    var originalPosition = els.position;
+    var originalDisplay = els.display;
+    els.visibility = 'hidden';
+    els.position = 'absolute';
+    els.display = 'block';
+    var originalWidth = element.clientWidth;
+    var originalHeight = element.clientHeight;
+    els.display = originalDisplay;
+    els.position = originalPosition;
+    els.visibility = originalVisibility;
+    return {width: originalWidth, height: originalHeight};
+  },
+
+  makePositioned: function(element) {
+    element = $(element);
+    var pos = Element.getStyle(element, 'position');
+    if (pos == 'static' || !pos) {
+      element._madePositioned = true;
+      element.style.position = 'relative';
+      // Opera returns the offset relative to the positioning context, when an
+      // element is position relative but top and left have not been defined
+      if (Prototype.Browser.Opera) {
+        element.style.top = 0;
+        element.style.left = 0;
+      }
+    }
+    return element;
+  },
+
+  undoPositioned: function(element) {
+    element = $(element);
+    if (element._madePositioned) {
+      element._madePositioned = undefined;
+      element.style.position =
+        element.style.top =
+        element.style.left =
+        element.style.bottom =
+        element.style.right = '';
+    }
+    return element;
+  },
+
+  makeClipping: function(element) {
+    element = $(element);
+    if (element._overflow) return element;
+    element._overflow = Element.getStyle(element, 'overflow') || 'auto';
+    if (element._overflow !== 'hidden')
+      element.style.overflow = 'hidden';
+    return element;
+  },
+
+  undoClipping: function(element) {
+    element = $(element);
+    if (!element._overflow) return element;
+    element.style.overflow = element._overflow == 'auto' ? '' : element._overflow;
+    element._overflow = null;
+    return element;
+  },
+
+  cumulativeOffset: function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+      element = element.offsetParent;
+    } while (element);
+    return Element._returnOffset(valueL, valueT);
+  },
+
+  positionedOffset: function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+      element = element.offsetParent;
+      if (element) {
+        if (element.tagName.toUpperCase() == 'BODY') break;
+        var p = Element.getStyle(element, 'position');
+        if (p !== 'static') break;
+      }
+    } while (element);
+    return Element._returnOffset(valueL, valueT);
+  },
+
+  absolutize: function(element) {
+    element = $(element);
+    if (element.getStyle('position') == 'absolute') return element;
+    // Position.prepare(); // To be done manually by Scripty when it needs it.
+
+    var offsets = element.positionedOffset();
+    var top     = offsets[1];
+    var left    = offsets[0];
+    var width   = element.clientWidth;
+    var height  = element.clientHeight;
+
+    element._originalLeft   = left - parseFloat(element.style.left  || 0);
+    element._originalTop    = top  - parseFloat(element.style.top || 0);
+    element._originalWidth  = element.style.width;
+    element._originalHeight = element.style.height;
+
+    element.style.position = 'absolute';
+    element.style.top    = top + 'px';
+    element.style.left   = left + 'px';
+    element.style.width  = width + 'px';
+    element.style.height = height + 'px';
+    return element;
+  },
+
+  relativize: function(element) {
+    element = $(element);
+    if (element.getStyle('position') == 'relative') return element;
+    // Position.prepare(); // To be done manually by Scripty when it needs it.
+
+    element.style.position = 'relative';
+    var top  = parseFloat(element.style.top  || 0) - (element._originalTop || 0);
+    var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);
+
+    element.style.top    = top + 'px';
+    element.style.left   = left + 'px';
+    element.style.height = element._originalHeight;
+    element.style.width  = element._originalWidth;
+    return element;
+  },
+
+  cumulativeScrollOffset: function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.scrollTop  || 0;
+      valueL += element.scrollLeft || 0;
+      element = element.parentNode;
+    } while (element);
+    return Element._returnOffset(valueL, valueT);
+  },
+
+  getOffsetParent: function(element) {
+    element = $(element);
+    var op = element.offsetParent, body = document.body, docEl = document.documentElement;
+
+    /* IE with strict doctype may try to return documentElement as offsetParent
+       on relatively positioned elements, we will return body instead */
+    if (op && op !== docEl) return $(op);
+    if (op === docEl || element === docEl || element === body) return $(body);
+
+    while ((element = element.parentNode) && element !== body)
+      if (Element.getStyle(element, 'position') != 'static')
+        return $(element);
+
+    return $(body);
+  },
+
+  viewportOffset: function(forElement) {
+    forElement = $(forElement);
+
+    var element = forElement, valueT = 0, valueL = 0;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+    } while ((element = element.getOffsetParent()) != document.body);
+
+    element = forElement;
+    do {
+      if (!Prototype.Browser.Opera || (element.tagName && (element.tagName.toUpperCase() == 'BODY'))) {
+        valueT -= element.scrollTop  || 0;
+        valueL -= element.scrollLeft || 0;
+      }
+    } while (element = element.parentNode);
+
+    return Element._returnOffset(valueL, valueT);
+  },
+
+  clonePosition: function(element, source) {
+    var options = Object.extend({
+      setLeft:    true,
+      setTop:     true,
+      setWidth:   true,
+      setHeight:  true,
+      offsetTop:  0,
+      offsetLeft: 0
+    }, arguments[2] || { });
+
+    // find page position of source
+    source = $(source);
+    var p = source.viewportOffset();
+
+    // find coordinate system to use
+    element = $(element);
+    var delta = [0, 0];
+    var parent = null;
+    // delta [0,0] will do fine with position: fixed elements,
+    // position:absolute needs offsetParent deltas
+    if (Element.getStyle(element, 'position') == 'absolute') {
+      parent = element.getOffsetParent();
+      delta = parent.viewportOffset();
+    }
+
+    // correct by body offsets (fixes Safari)
+    if (parent == document.body) {
+      delta[0] -= document.body.offsetLeft;
+      delta[1] -= document.body.offsetTop;
+    }
+
+    // set position
+    if (options.setLeft)   element.style.left  = (p[0] - delta[0] + options.offsetLeft) + 'px';
+    if (options.setTop)    element.style.top   = (p[1] - delta[1] + options.offsetTop) + 'px';
+    if (options.setWidth)  element.style.width = source.offsetWidth + 'px';
+    if (options.setHeight) element.style.height = source.offsetHeight + 'px';
+    return element;
+  }
+};
+
+Element.Methods.identify.counter = 1;
+
+Object.extend(Element.Methods, {
+  getElementsBySelector: Element.Methods.select,
+  childElements: Element.Methods.immediateDescendants
+});
+
+Element._attributeTranslations = {
+  write: {
+    names: {
+      className: 'class',
+      htmlFor:   'for'
+    },
+    values: { }
+  }
+};
+
+if (Prototype.Browser.Opera) {
+  Element.Methods.getStyle = Element.Methods.getStyle.wrap(
+    function(proceed, element, style) {
+      switch (style) {
+        case 'left': case 'top': case 'right': case 'bottom':
+          if (proceed(element, 'position') === 'static') return null;
+        case 'height': case 'width':
+          // returns '0px' for hidden elements; we want it to return null
+          if (!Element.visible(element)) return null;
+
+          // returns the border-box dimensions rather than the content-box
+          // dimensions, so we subtract padding and borders from the value
+          var dim = parseInt(proceed(element, style), 10);
+
+          if (dim !== element['offset' + style.capitalize()])
+            return dim + 'px';
+
+          var properties;
+          if (style === 'height') {
+            properties = ['border-top-width', 'padding-top',
+             'padding-bottom', 'border-bottom-width'];
+          }
+          else {
+            properties = ['border-left-width', 'padding-left',
+             'padding-right', 'border-right-width'];
+          }
+          return properties.inject(dim, function(memo, property) {
+            var val = proceed(element, property);
+            return val === null ? memo : memo - parseInt(val, 10);
+          }) + 'px';
+        default: return proceed(element, style);
+      }
+    }
+  );
+
+  Element.Methods.readAttribute = Element.Methods.readAttribute.wrap(
+    function(proceed, element, attribute) {
+      if (attribute === 'title') return element.title;
+      return proceed(element, attribute);
+    }
+  );
+}
+
+else if (Prototype.Browser.IE) {
+  // IE doesn't report offsets correctly for static elements, so we change them
+  // to "relative" to get the values, then change them back.
+  Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap(
+    function(proceed, element) {
+      element = $(element);
+      // IE throws an error if element is not in document
+      try { element.offsetParent }
+      catch(e) { return $(document.body) }
+      var position = element.getStyle('position');
+      if (position !== 'static') return proceed(element);
+      element.setStyle({ position: 'relative' });
+      var value = proceed(element);
+      element.setStyle({ position: position });
+      return value;
+    }
+  );
+
+  $w('positionedOffset viewportOffset').each(function(method) {
+    Element.Methods[method] = Element.Methods[method].wrap(
+      function(proceed, element) {
+        element = $(element);
+        try { element.offsetParent }
+        catch(e) { return Element._returnOffset(0,0) }
+        var position = element.getStyle('position');
+        if (position !== 'static') return proceed(element);
+        // Trigger hasLayout on the offset parent so that IE6 reports
+        // accurate offsetTop and offsetLeft values for position: fixed.
+        var offsetParent = element.getOffsetParent();
+        if (offsetParent && offsetParent.getStyle('position') === 'fixed')
+          offsetParent.setStyle({ zoom: 1 });
+        element.setStyle({ position: 'relative' });
+        var value = proceed(element);
+        element.setStyle({ position: position });
+        return value;
+      }
+    );
+  });
+
+  Element.Methods.cumulativeOffset = Element.Methods.cumulativeOffset.wrap(
+    function(proceed, element) {
+      try { element.offsetParent }
+      catch(e) { return Element._returnOffset(0,0) }
+      return proceed(element);
+    }
+  );
+
+  Element.Methods.getStyle = function(element, style) {
+    element = $(element);
+    style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize();
+    var value = element.style[style];
+    if (!value && element.currentStyle) value = element.currentStyle[style];
+
+    if (style == 'opacity') {
+      if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
+        if (value[1]) return parseFloat(value[1]) / 100;
+      return 1.0;
+    }
+
+    if (value == 'auto') {
+      if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none'))
+        return element['offset' + style.capitalize()] + 'px';
+      return null;
+    }
+    return value;
+  };
+
+  Element.Methods.setOpacity = function(element, value) {
+    function stripAlpha(filter){
+      return filter.replace(/alpha\([^\)]*\)/gi,'');
+    }
+    element = $(element);
+    var currentStyle = element.currentStyle;
+    if ((currentStyle && !currentStyle.hasLayout) ||
+      (!currentStyle && element.style.zoom == 'normal'))
+        element.style.zoom = 1;
+
+    var filter = element.getStyle('filter'), style = element.style;
+    if (value == 1 || value === '') {
+      (filter = stripAlpha(filter)) ?
+        style.filter = filter : style.removeAttribute('filter');
+      return element;
+    } else if (value < 0.00001) value = 0;
+    style.filter = stripAlpha(filter) +
+      'alpha(opacity=' + (value * 100) + ')';
+    return element;
+  };
+
+  Element._attributeTranslations = {
+    read: {
+      names: {
+        'class': 'className',
+        'for':   'htmlFor'
+      },
+      values: {
+        _getAttr: function(element, attribute) {
+          return element.getAttribute(attribute, 2);
+        },
+        _getAttrNode: function(element, attribute) {
+          var node = element.getAttributeNode(attribute);
+          return node ? node.value : "";
+        },
+        _getEv: function(element, attribute) {
+          attribute = element.getAttribute(attribute);
+          return attribute ? attribute.toString().slice(23, -2) : null;
+        },
+        _flag: function(element, attribute) {
+          return $(element).hasAttribute(attribute) ? attribute : null;
+        },
+        style: function(element) {
+          return element.style.cssText.toLowerCase();
+        },
+        title: function(element) {
+          return element.title;
+        }
+      }
+    }
+  };
+
+  Element._attributeTranslations.write = {
+    names: Object.extend({
+      cellpadding: 'cellPadding',
+      cellspacing: 'cellSpacing'
+    }, Element._attributeTranslations.read.names),
+    values: {
+      checked: function(element, value) {
+        element.checked = !!value;
+      },
+
+      style: function(element, value) {
+        element.style.cssText = value ? value : '';
+      }
+    }
+  };
+
+  Element._attributeTranslations.has = {};
+
+  $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' +
+      'encType maxLength readOnly longDesc frameBorder').each(function(attr) {
+    Element._attributeTranslations.write.names[attr.toLowerCase()] = attr;
+    Element._attributeTranslations.has[attr.toLowerCase()] = attr;
+  });
+
+  (function(v) {
+    Object.extend(v, {
+      href:        v._getAttr,
+      src:         v._getAttr,
+      type:        v._getAttr,
+      action:      v._getAttrNode,
+      disabled:    v._flag,
+      checked:     v._flag,
+      readonly:    v._flag,
+      multiple:    v._flag,
+      onload:      v._getEv,
+      onunload:    v._getEv,
+      onclick:     v._getEv,
+      ondblclick:  v._getEv,
+      onmousedown: v._getEv,
+      onmouseup:   v._getEv,
+      onmouseover: v._getEv,
+      onmousemove: v._getEv,
+      onmouseout:  v._getEv,
+      onfocus:     v._getEv,
+      onblur:      v._getEv,
+      onkeypress:  v._getEv,
+      onkeydown:   v._getEv,
+      onkeyup:     v._getEv,
+      onsubmit:    v._getEv,
+      onreset:     v._getEv,
+      onselect:    v._getEv,
+      onchange:    v._getEv
+    });
+  })(Element._attributeTranslations.read.values);
+}
+
+else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) {
+  Element.Methods.setOpacity = function(element, value) {
+    element = $(element);
+    element.style.opacity = (value == 1) ? 0.999999 :
+      (value === '') ? '' : (value < 0.00001) ? 0 : value;
+    return element;
+  };
+}
+
+else if (Prototype.Browser.WebKit) {
+  Element.Methods.setOpacity = function(element, value) {
+    element = $(element);
+    element.style.opacity = (value == 1 || value === '') ? '' :
+      (value < 0.00001) ? 0 : value;
+
+    if (value == 1)
+      if(element.tagName.toUpperCase() == 'IMG' && element.width) {
+        element.width++; element.width--;
+      } else try {
+        var n = document.createTextNode(' ');
+        element.appendChild(n);
+        element.removeChild(n);
+      } catch (e) { }
+
+    return element;
+  };
+
+  // Safari returns margins on body which is incorrect if the child is absolutely
+  // positioned.  For performance reasons, redefine Element#cumulativeOffset for
+  // KHTML/WebKit only.
+  Element.Methods.cumulativeOffset = function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+      if (element.offsetParent == document.body)
+        if (Element.getStyle(element, 'position') == 'absolute') break;
+
+      element = element.offsetParent;
+    } while (element);
+
+    return Element._returnOffset(valueL, valueT);
+  };
+}
+
+if (Prototype.Browser.IE || Prototype.Browser.Opera) {
+  // IE and Opera are missing .innerHTML support for TABLE-related and SELECT elements
+  Element.Methods.update = function(element, content) {
+    element = $(element);
+
+    if (content && content.toElement) content = content.toElement();
+    if (Object.isElement(content)) return element.update().insert(content);
+
+    content = Object.toHTML(content);
+    var tagName = element.tagName.toUpperCase();
+
+    if (tagName in Element._insertionTranslations.tags) {
+      $A(element.childNodes).each(function(node) { element.removeChild(node) });
+      Element._getContentFromAnonymousElement(tagName, content.stripScripts())
+        .each(function(node) { element.appendChild(node) });
+    }
+    else element.innerHTML = content.stripScripts();
+
+    content.evalScripts.bind(content).defer();
+    return element;
+  };
+}
+
+if ('outerHTML' in document.createElement('div')) {
+  Element.Methods.replace = function(element, content) {
+    element = $(element);
+
+    if (content && content.toElement) content = content.toElement();
+    if (Object.isElement(content)) {
+      element.parentNode.replaceChild(content, element);
+      return element;
+    }
+
+    content = Object.toHTML(content);
+    var parent = element.parentNode, tagName = parent.tagName.toUpperCase();
+
+    if (Element._insertionTranslations.tags[tagName]) {
+      var nextSibling = element.next();
+      var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
+      parent.removeChild(element);
+      if (nextSibling)
+        fragments.each(function(node) { parent.insertBefore(node, nextSibling) });
+      else
+        fragments.each(function(node) { parent.appendChild(node) });
+    }
+    else element.outerHTML = content.stripScripts();
+
+    content.evalScripts.bind(content).defer();
+    return element;
+  };
+}
+
+Element._returnOffset = function(l, t) {
+  var result = [l, t];
+  result.left = l;
+  result.top = t;
+  return result;
+};
+
+Element._getContentFromAnonymousElement = function(tagName, html) {
+  var div = new Element('div'), t = Element._insertionTranslations.tags[tagName];
+  if (t) {
+    div.innerHTML = t[0] + html + t[1];
+    t[2].times(function() { div = div.firstChild });
+  } else div.innerHTML = html;
+  return $A(div.childNodes);
+};
+
+Element._insertionTranslations = {
+  before: function(element, node) {
+    element.parentNode.insertBefore(node, element);
+  },
+  top: function(element, node) {
+    element.insertBefore(node, element.firstChild);
+  },
+  bottom: function(element, node) {
+    element.appendChild(node);
+  },
+  after: function(element, node) {
+    element.parentNode.insertBefore(node, element.nextSibling);
+  },
+  tags: {
+    TABLE:  ['<table>',                '</table>',                   1],
+    TBODY:  ['<table><tbody>',         '</tbody></table>',           2],
+    TR:     ['<table><tbody><tr>',     '</tr></tbody></table>',      3],
+    TD:     ['<table><tbody><tr><td>', '</td></tr></tbody></table>', 4],
+    SELECT: ['<select>',               '</select>',                  1]
+  }
+};
+
+(function() {
+  Object.extend(this.tags, {
+    THEAD: this.tags.TBODY,
+    TFOOT: this.tags.TBODY,
+    TH:    this.tags.TD
+  });
+}).call(Element._insertionTranslations);
+
+Element.Methods.Simulated = {
+  hasAttribute: function(element, attribute) {
+    attribute = Element._attributeTranslations.has[attribute] || attribute;
+    var node = $(element).getAttributeNode(attribute);
+    return !!(node && node.specified);
+  }
+};
+
+Element.Methods.ByTag = { };
+
+Object.extend(Element, Element.Methods);
+
+if (!Prototype.BrowserFeatures.ElementExtensions &&
+    document.createElement('div')['__proto__']) {
+  window.HTMLElement = { };
+  window.HTMLElement.prototype = document.createElement('div')['__proto__'];
+  Prototype.BrowserFeatures.ElementExtensions = true;
+}
+
+Element.extend = (function() {
+  if (Prototype.BrowserFeatures.SpecificElementExtensions)
+    return Prototype.K;
+
+  var Methods = { }, ByTag = Element.Methods.ByTag;
+
+  var extend = Object.extend(function(element) {
+    if (!element || element._extendedByPrototype ||
+        element.nodeType != 1 || element == window) return element;
+
+    var methods = Object.clone(Methods),
+      tagName = element.tagName.toUpperCase(), property, value;
+
+    // extend methods for specific tags
+    if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]);
+
+    for (property in methods) {
+      value = methods[property];
+      if (Object.isFunction(value) && !(property in element))
+        element[property] = value.methodize();
+    }
+
+    element._extendedByPrototype = Prototype.emptyFunction;
+    return element;
+
+  }, {
+    refresh: function() {
+      // extend methods for all tags (Safari doesn't need this)
+      if (!Prototype.BrowserFeatures.ElementExtensions) {
+        Object.extend(Methods, Element.Methods);
+        Object.extend(Methods, Element.Methods.Simulated);
+      }
+    }
+  });
+
+  extend.refresh();
+  return extend;
+})();
+
+Element.hasAttribute = function(element, attribute) {
+  if (element.hasAttribute) return element.hasAttribute(attribute);
+  return Element.Methods.Simulated.hasAttribute(element, attribute);
+};
+
+Element.addMethods = function(methods) {
+  var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag;
+
+  if (!methods) {
+    Object.extend(Form, Form.Methods);
+    Object.extend(Form.Element, Form.Element.Methods);
+    Object.extend(Element.Methods.ByTag, {
+      "FORM":     Object.clone(Form.Methods),
+      "INPUT":    Object.clone(Form.Element.Methods),
+      "SELECT":   Object.clone(Form.Element.Methods),
+      "TEXTAREA": Object.clone(Form.Element.Methods)
+    });
+  }
+
+  if (arguments.length == 2) {
+    var tagName = methods;
+    methods = arguments[1];
+  }
+
+  if (!tagName) Object.extend(Element.Methods, methods || { });
+  else {
+    if (Object.isArray(tagName)) tagName.each(extend);
+    else extend(tagName);
+  }
+
+  function extend(tagName) {
+    tagName = tagName.toUpperCase();
+    if (!Element.Methods.ByTag[tagName])
+      Element.Methods.ByTag[tagName] = { };
+    Object.extend(Element.Methods.ByTag[tagName], methods);
+  }
+
+  function copy(methods, destination, onlyIfAbsent) {
+    onlyIfAbsent = onlyIfAbsent || false;
+    for (var property in methods) {
+      var value = methods[property];
+      if (!Object.isFunction(value)) continue;
+      if (!onlyIfAbsent || !(property in destination))
+        destination[property] = value.methodize();
+    }
+  }
+
+  function findDOMClass(tagName) {
+    var klass;
+    var trans = {
+      "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph",
+      "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList",
+      "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading",
+      "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote",
+      "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION":
+      "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD":
+      "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR":
+      "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET":
+      "FrameSet", "IFRAME": "IFrame"
+    };
+    if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element';
+    if (window[klass]) return window[klass];
+    klass = 'HTML' + tagName + 'Element';
+    if (window[klass]) return window[klass];
+    klass = 'HTML' + tagName.capitalize() + 'Element';
+    if (window[klass]) return window[klass];
+
+    window[klass] = { };
+    window[klass].prototype = document.createElement(tagName)['__proto__'];
+    return window[klass];
+  }
+
+  if (F.ElementExtensions) {
+    copy(Element.Methods, HTMLElement.prototype);
+    copy(Element.Methods.Simulated, HTMLElement.prototype, true);
+  }
+
+  if (F.SpecificElementExtensions) {
+    for (var tag in Element.Methods.ByTag) {
+      var klass = findDOMClass(tag);
+      if (Object.isUndefined(klass)) continue;
+      copy(T[tag], klass.prototype);
+    }
+  }
+
+  Object.extend(Element, Element.Methods);
+  delete Element.ByTag;
+
+  if (Element.extend.refresh) Element.extend.refresh();
+  Element.cache = { };
+};
+
+document.viewport = {
+  getDimensions: function() {
+    var dimensions = { }, B = Prototype.Browser;
+    $w('width height').each(function(d) {
+      var D = d.capitalize();
+      if (B.WebKit && !document.evaluate) {
+        // Safari <3.0 needs self.innerWidth/Height
+        dimensions[d] = self['inner' + D];
+      } else if (B.Opera && parseFloat(window.opera.version()) < 9.5) {
+        // Opera <9.5 needs document.body.clientWidth/Height
+        dimensions[d] = document.body['client' + D]
+      } else {
+        dimensions[d] = document.documentElement['client' + D];
+      }
+    });
+    return dimensions;
+  },
+
+  getWidth: function() {
+    return this.getDimensions().width;
+  },
+
+  getHeight: function() {
+    return this.getDimensions().height;
+  },
+
+  getScrollOffsets: function() {
+    return Element._returnOffset(
+      window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft,
+      window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop);
+  }
+};
+/* Portions of the Selector class are derived from Jack Slocum's DomQuery,
+ * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style
+ * license.  Please see http://www.yui-ext.com/ for more information. */
+
+var Selector = Class.create({
+  initialize: function(expression) {
+    this.expression = expression.strip();
+
+    if (this.shouldUseSelectorsAPI()) {
+      this.mode = 'selectorsAPI';
+    } else if (this.shouldUseXPath()) {
+      this.mode = 'xpath';
+      this.compileXPathMatcher();
+    } else {
+      this.mode = "normal";
+      this.compileMatcher();
+    }
+
+  },
+
+  shouldUseXPath: function() {
+    if (!Prototype.BrowserFeatures.XPath) return false;
+
+    var e = this.expression;
+
+    // Safari 3 chokes on :*-of-type and :empty
+    if (Prototype.Browser.WebKit &&
+     (e.include("-of-type") || e.include(":empty")))
+      return false;
+
+    // XPath can't do namespaced attributes, nor can it read
+    // the "checked" property from DOM nodes
+    if ((/(\[[\w-]*?:|:checked)/).test(e))
+      return false;
+
+    return true;
+  },
+
+  shouldUseSelectorsAPI: function() {
+    if (!Prototype.BrowserFeatures.SelectorsAPI) return false;
+
+    if (!Selector._div) Selector._div = new Element('div');
+
+    // Make sure the browser treats the selector as valid. Test on an
+    // isolated element to minimize cost of this check.
+    try {
+      Selector._div.querySelector(this.expression);
+    } catch(e) {
+      return false;
+    }
+
+    return true;
+  },
+
+  compileMatcher: function() {
+    var e = this.expression, ps = Selector.patterns, h = Selector.handlers,
+        c = Selector.criteria, le, p, m;
+
+    if (Selector._cache[e]) {
+      this.matcher = Selector._cache[e];
+      return;
+    }
+
+    this.matcher = ["this.matcher = function(root) {",
+                    "var r = root, h = Selector.handlers, c = false, n;"];
+
+    while (e && le != e && (/\S/).test(e)) {
+      le = e;
+      for (var i in ps) {
+        p = ps[i];
+        if (m = e.match(p)) {
+          this.matcher.push(Object.isFunction(c[i]) ? c[i](m) :
+            new Template(c[i]).evaluate(m));
+          e = e.replace(m[0], '');
+          break;
+        }
+      }
+    }
+
+    this.matcher.push("return h.unique(n);\n}");
+    eval(this.matcher.join('\n'));
+    Selector._cache[this.expression] = this.matcher;
+  },
+
+  compileXPathMatcher: function() {
+    var e = this.expression, ps = Selector.patterns,
+        x = Selector.xpath, le, m;
+
+    if (Selector._cache[e]) {
+      this.xpath = Selector._cache[e]; return;
+    }
+
+    this.matcher = ['.//*'];
+    while (e && le != e && (/\S/).test(e)) {
+      le = e;
+      for (var i in ps) {
+        if (m = e.match(ps[i])) {
+          this.matcher.push(Object.isFunction(x[i]) ? x[i](m) :
+            new Template(x[i]).evaluate(m));
+          e = e.replace(m[0], '');
+          break;
+        }
+      }
+    }
+
+    this.xpath = this.matcher.join('');
+    Selector._cache[this.expression] = this.xpath;
+  },
+
+  findElements: function(root) {
+    root = root || document;
+    var e = this.expression, results;
+
+    switch (this.mode) {
+      case 'selectorsAPI':
+        // querySelectorAll queries document-wide, then filters to descendants
+        // of the context element. That's not what we want.
+        // Add an explicit context to the selector if necessary.
+        if (root !== document) {
+          var oldId = root.id, id = $(root).identify();
+          e = "#" + id + " " + e;
+        }
+
+        results = $A(root.querySelectorAll(e)).map(Element.extend);
+        root.id = oldId;
+
+        return results;
+      case 'xpath':
+        return document._getElementsByXPath(this.xpath, root);
+      default:
+       return this.matcher(root);
+    }
+  },
+
+  match: function(element) {
+    this.tokens = [];
+
+    var e = this.expression, ps = Selector.patterns, as = Selector.assertions;
+    var le, p, m;
+
+    while (e && le !== e && (/\S/).test(e)) {
+      le = e;
+      for (var i in ps) {
+        p = ps[i];
+        if (m = e.match(p)) {
+          // use the Selector.assertions methods unless the selector
+          // is too complex.
+          if (as[i]) {
+            this.tokens.push([i, Object.clone(m)]);
+            e = e.replace(m[0], '');
+          } else {
+            // reluctantly do a document-wide search
+            // and look for a match in the array
+            return this.findElements(document).include(element);
+          }
+        }
+      }
+    }
+
+    var match = true, name, matches;
+    for (var i = 0, token; token = this.tokens[i]; i++) {
+      name = token[0], matches = token[1];
+      if (!Selector.assertions[name](element, matches)) {
+        match = false; break;
+      }
+    }
+
+    return match;
+  },
+
+  toString: function() {
+    return this.expression;
+  },
+
+  inspect: function() {
+    return "#<Selector:" + this.expression.inspect() + ">";
+  }
+});
+
+Object.extend(Selector, {
+  _cache: { },
+
+  xpath: {
+    descendant:   "//*",
+    child:        "/*",
+    adjacent:     "/following-sibling::*[1]",
+    laterSibling: '/following-sibling::*',
+    tagName:      function(m) {
+      if (m[1] == '*') return '';
+      return "[local-name()='" + m[1].toLowerCase() +
+             "' or local-name()='" + m[1].toUpperCase() + "']";
+    },
+    className:    "[contains(concat(' ', @class, ' '), ' #{1} ')]",
+    id:           "[@id='#{1}']",
+    attrPresence: function(m) {
+      m[1] = m[1].toLowerCase();
+      return new Template("[@#{1}]").evaluate(m);
+    },
+    attr: function(m) {
+      m[1] = m[1].toLowerCase();
+      m[3] = m[5] || m[6];
+      return new Template(Selector.xpath.operators[m[2]]).evaluate(m);
+    },
+    pseudo: function(m) {
+      var h = Selector.xpath.pseudos[m[1]];
+      if (!h) return '';
+      if (Object.isFunction(h)) return h(m);
+      return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m);
+    },
+    operators: {
+      '=':  "[@#{1}='#{3}']",
+      '!=': "[@#{1}!='#{3}']",
+      '^=': "[starts-with(@#{1}, '#{3}')]",
+      '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']",
+      '*=': "[contains(@#{1}, '#{3}')]",
+      '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]",
+      '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]"
+    },
+    pseudos: {
+      'first-child': '[not(preceding-sibling::*)]',
+      'last-child':  '[not(following-sibling::*)]',
+      'only-child':  '[not(preceding-sibling::* or following-sibling::*)]',
+      'empty':       "[count(*) = 0 and (count(text()) = 0)]",
+      'checked':     "[@checked]",
+      'disabled':    "[(@disabled) and (@type!='hidden')]",
+      'enabled':     "[not(@disabled) and (@type!='hidden')]",
+      'not': function(m) {
+        var e = m[6], p = Selector.patterns,
+            x = Selector.xpath, le, v;
+
+        var exclusion = [];
+        while (e && le != e && (/\S/).test(e)) {
+          le = e;
+          for (var i in p) {
+            if (m = e.match(p[i])) {
+              v = Object.isFunction(x[i]) ? x[i](m) : new Template(x[i]).evaluate(m);
+              exclusion.push("(" + v.substring(1, v.length - 1) + ")");
+              e = e.replace(m[0], '');
+              break;
+            }
+          }
+        }
+        return "[not(" + exclusion.join(" and ") + ")]";
+      },
+      'nth-child':      function(m) {
+        return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m);
+      },
+      'nth-last-child': function(m) {
+        return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m);
+      },
+      'nth-of-type':    function(m) {
+        return Selector.xpath.pseudos.nth("position() ", m);
+      },
+      'nth-last-of-type': function(m) {
+        return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m);
+      },
+      'first-of-type':  function(m) {
+        m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m);
+      },
+      'last-of-type':   function(m) {
+        m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m);
+      },
+      'only-of-type':   function(m) {
+        var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m);
+      },
+      nth: function(fragment, m) {
+        var mm, formula = m[6], predicate;
+        if (formula == 'even') formula = '2n+0';
+        if (formula == 'odd')  formula = '2n+1';
+        if (mm = formula.match(/^(\d+)$/)) // digit only
+          return '[' + fragment + "= " + mm[1] + ']';
+        if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
+          if (mm[1] == "-") mm[1] = -1;
+          var a = mm[1] ? Number(mm[1]) : 1;
+          var b = mm[2] ? Number(mm[2]) : 0;
+          predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " +
+          "((#{fragment} - #{b}) div #{a} >= 0)]";
+          return new Template(predicate).evaluate({
+            fragment: fragment, a: a, b: b });
+        }
+      }
+    }
+  },
+
+  criteria: {
+    tagName:      'n = h.tagName(n, r, "#{1}", c);      c = false;',
+    className:    'n = h.className(n, r, "#{1}", c);    c = false;',
+    id:           'n = h.id(n, r, "#{1}", c);           c = false;',
+    attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;',
+    attr: function(m) {
+      m[3] = (m[5] || m[6]);
+      return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m);
+    },
+    pseudo: function(m) {
+      if (m[6]) m[6] = m[6].replace(/"/g, '\\"');
+      return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m);
+    },
+    descendant:   'c = "descendant";',
+    child:        'c = "child";',
+    adjacent:     'c = "adjacent";',
+    laterSibling: 'c = "laterSibling";'
+  },
+
+  patterns: {
+    // combinators must be listed first
+    // (and descendant needs to be last combinator)
+    laterSibling: /^\s*~\s*/,
+    child:        /^\s*>\s*/,
+    adjacent:     /^\s*\+\s*/,
+    descendant:   /^\s/,
+
+    // selectors follow
+    tagName:      /^\s*(\*|[\w\-]+)(\b|$)?/,
+    id:           /^#([\w\-\*]+)(\b|$)/,
+    className:    /^\.([\w\-\*]+)(\b|$)/,
+    pseudo:
+/^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/,
+    attrPresence: /^\[((?:[\w]+:)?[\w]+)\]/,
+    attr:         /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/
+  },
+
+  // for Selector.match and Element#match
+  assertions: {
+    tagName: function(element, matches) {
+      return matches[1].toUpperCase() == element.tagName.toUpperCase();
+    },
+
+    className: function(element, matches) {
+      return Element.hasClassName(element, matches[1]);
+    },
+
+    id: function(element, matches) {
+      return element.id === matches[1];
+    },
+
+    attrPresence: function(element, matches) {
+      return Element.hasAttribute(element, matches[1]);
+    },
+
+    attr: function(element, matches) {
+      var nodeValue = Element.readAttribute(element, matches[1]);
+      return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]);
+    }
+  },
+
+  handlers: {
+    // UTILITY FUNCTIONS
+    // joins two collections
+    concat: function(a, b) {
+      for (var i = 0, node; node = b[i]; i++)
+        a.push(node);
+      return a;
+    },
+
+    // marks an array of nodes for counting
+    mark: function(nodes) {
+      var _true = Prototype.emptyFunction;
+      for (var i = 0, node; node = nodes[i]; i++)
+        node._countedByPrototype = _true;
+      return nodes;
+    },
+
+    unmark: function(nodes) {
+      for (var i = 0, node; node = nodes[i]; i++)
+        node._countedByPrototype = undefined;
+      return nodes;
+    },
+
+    // mark each child node with its position (for nth calls)
+    // "ofType" flag indicates whether we're indexing for nth-of-type
+    // rather than nth-child
+    index: function(parentNode, reverse, ofType) {
+      parentNode._countedByPrototype = Prototype.emptyFunction;
+      if (reverse) {
+        for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {
+          var node = nodes[i];
+          if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
+        }
+      } else {
+        for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)
+          if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
+      }
+    },
+
+    // filters out duplicates and extends all nodes
+    unique: function(nodes) {
+      if (nodes.length == 0) return nodes;
+      var results = [], n;
+      for (var i = 0, l = nodes.length; i < l; i++)
+        if (!(n = nodes[i])._countedByPrototype) {
+          n._countedByPrototype = Prototype.emptyFunction;
+          results.push(Element.extend(n));
+        }
+      return Selector.handlers.unmark(results);
+    },
+
+    // COMBINATOR FUNCTIONS
+    descendant: function(nodes) {
+      var h = Selector.handlers;
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        h.concat(results, node.getElementsByTagName('*'));
+      return results;
+    },
+
+    child: function(nodes) {
+      var h = Selector.handlers;
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        for (var j = 0, child; child = node.childNodes[j]; j++)
+          if (child.nodeType == 1 && child.tagName != '!') results.push(child);
+      }
+      return results;
+    },
+
+    adjacent: function(nodes) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        var next = this.nextElementSibling(node);
+        if (next) results.push(next);
+      }
+      return results;
+    },
+
+    laterSibling: function(nodes) {
+      var h = Selector.handlers;
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        h.concat(results, Element.nextSiblings(node));
+      return results;
+    },
+
+    nextElementSibling: function(node) {
+      while (node = node.nextSibling)
+        if (node.nodeType == 1) return node;
+      return null;
+    },
+
+    previousElementSibling: function(node) {
+      while (node = node.previousSibling)
+        if (node.nodeType == 1) return node;
+      return null;
+    },
+
+    // TOKEN FUNCTIONS
+    tagName: function(nodes, root, tagName, combinator) {
+      var uTagName = tagName.toUpperCase();
+      var results = [], h = Selector.handlers;
+      if (nodes) {
+        if (combinator) {
+          // fastlane for ordinary descendant combinators
+          if (combinator == "descendant") {
+            for (var i = 0, node; node = nodes[i]; i++)
+              h.concat(results, node.getElementsByTagName(tagName));
+            return results;
+          } else nodes = this[combinator](nodes);
+          if (tagName == "*") return nodes;
+        }
+        for (var i = 0, node; node = nodes[i]; i++)
+          if (node.tagName.toUpperCase() === uTagName) results.push(node);
+        return results;
+      } else return root.getElementsByTagName(tagName);
+    },
+
+    id: function(nodes, root, id, combinator) {
+      var targetNode = $(id), h = Selector.handlers;
+      if (!targetNode) return [];
+      if (!nodes && root == document) return [targetNode];
+      if (nodes) {
+        if (combinator) {
+          if (combinator == 'child') {
+            for (var i = 0, node; node = nodes[i]; i++)
+              if (targetNode.parentNode == node) return [targetNode];
+          } else if (combinator == 'descendant') {
+            for (var i = 0, node; node = nodes[i]; i++)
+              if (Element.descendantOf(targetNode, node)) return [targetNode];
+          } else if (combinator == 'adjacent') {
+            for (var i = 0, node; node = nodes[i]; i++)
+              if (Selector.handlers.previousElementSibling(targetNode) == node)
+                return [targetNode];
+          } else nodes = h[combinator](nodes);
+        }
+        for (var i = 0, node; node = nodes[i]; i++)
+          if (node == targetNode) return [targetNode];
+        return [];
+      }
+      return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : [];
+    },
+
+    className: function(nodes, root, className, combinator) {
+      if (nodes && combinator) nodes = this[combinator](nodes);
+      return Selector.handlers.byClassName(nodes, root, className);
+    },
+
+    byClassName: function(nodes, root, className) {
+      if (!nodes) nodes = Selector.handlers.descendant([root]);
+      var needle = ' ' + className + ' ';
+      for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) {
+        nodeClassName = node.className;
+        if (nodeClassName.length == 0) continue;
+        if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle))
+          results.push(node);
+      }
+      return results;
+    },
+
+    attrPresence: function(nodes, root, attr, combinator) {
+      if (!nodes) nodes = root.getElementsByTagName("*");
+      if (nodes && combinator) nodes = this[combinator](nodes);
+      var results = [];
+      for (var i = 0, node; node = nodes[i]; i++)
+        if (Element.hasAttribute(node, attr)) results.push(node);
+      return results;
+    },
+
+    attr: function(nodes, root, attr, value, operator, combinator) {
+      if (!nodes) nodes = root.getElementsByTagName("*");
+      if (nodes && combinator) nodes = this[combinator](nodes);
+      var handler = Selector.operators[operator], results = [];
+      for (var i = 0, node; node = nodes[i]; i++) {
+        var nodeValue = Element.readAttribute(node, attr);
+        if (nodeValue === null) continue;
+        if (handler(nodeValue, value)) results.push(node);
+      }
+      return results;
+    },
+
+    pseudo: function(nodes, name, value, root, combinator) {
+      if (nodes && combinator) nodes = this[combinator](nodes);
+      if (!nodes) nodes = root.getElementsByTagName("*");
+      return Selector.pseudos[name](nodes, value, root);
+    }
+  },
+
+  pseudos: {
+    'first-child': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        if (Selector.handlers.previousElementSibling(node)) continue;
+          results.push(node);
+      }
+      return results;
+    },
+    'last-child': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        if (Selector.handlers.nextElementSibling(node)) continue;
+          results.push(node);
+      }
+      return results;
+    },
+    'only-child': function(nodes, value, root) {
+      var h = Selector.handlers;
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (!h.previousElementSibling(node) && !h.nextElementSibling(node))
+          results.push(node);
+      return results;
+    },
+    'nth-child':        function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root);
+    },
+    'nth-last-child':   function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root, true);
+    },
+    'nth-of-type':      function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root, false, true);
+    },
+    'nth-last-of-type': function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root, true, true);
+    },
+    'first-of-type':    function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, "1", root, false, true);
+    },
+    'last-of-type':     function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, "1", root, true, true);
+    },
+    'only-of-type':     function(nodes, formula, root) {
+      var p = Selector.pseudos;
+      return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root);
+    },
+
+    // handles the an+b logic
+    getIndices: function(a, b, total) {
+      if (a == 0) return b > 0 ? [b] : [];
+      return $R(1, total).inject([], function(memo, i) {
+        if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i);
+        return memo;
+      });
+    },
+
+    // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type
+    nth: function(nodes, formula, root, reverse, ofType) {
+      if (nodes.length == 0) return [];
+      if (formula == 'even') formula = '2n+0';
+      if (formula == 'odd')  formula = '2n+1';
+      var h = Selector.handlers, results = [], indexed = [], m;
+      h.mark(nodes);
+      for (var i = 0, node; node = nodes[i]; i++) {
+        if (!node.parentNode._countedByPrototype) {
+          h.index(node.parentNode, reverse, ofType);
+          indexed.push(node.parentNode);
+        }
+      }
+      if (formula.match(/^\d+$/)) { // just a number
+        formula = Number(formula);
+        for (var i = 0, node; node = nodes[i]; i++)
+          if (node.nodeIndex == formula) results.push(node);
+      } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
+        if (m[1] == "-") m[1] = -1;
+        var a = m[1] ? Number(m[1]) : 1;
+        var b = m[2] ? Number(m[2]) : 0;
+        var indices = Selector.pseudos.getIndices(a, b, nodes.length);
+        for (var i = 0, node, l = indices.length; node = nodes[i]; i++) {
+          for (var j = 0; j < l; j++)
+            if (node.nodeIndex == indices[j]) results.push(node);
+        }
+      }
+      h.unmark(nodes);
+      h.unmark(indexed);
+      return results;
+    },
+
+    'empty': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        // IE treats comments as element nodes
+        if (node.tagName == '!' || node.firstChild) continue;
+        results.push(node);
+      }
+      return results;
+    },
+
+    'not': function(nodes, selector, root) {
+      var h = Selector.handlers, selectorType, m;
+      var exclusions = new Selector(selector).findElements(root);
+      h.mark(exclusions);
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (!node._countedByPrototype) results.push(node);
+      h.unmark(exclusions);
+      return results;
+    },
+
+    'enabled': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (!node.disabled && (!node.type || node.type !== 'hidden'))
+          results.push(node);
+      return results;
+    },
+
+    'disabled': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (node.disabled) results.push(node);
+      return results;
+    },
+
+    'checked': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (node.checked) results.push(node);
+      return results;
+    }
+  },
+
+  operators: {
+    '=':  function(nv, v) { return nv == v; },
+    '!=': function(nv, v) { return nv != v; },
+    '^=': function(nv, v) { return nv == v || nv && nv.startsWith(v); },
+    '$=': function(nv, v) { return nv == v || nv && nv.endsWith(v); },
+    '*=': function(nv, v) { return nv == v || nv && nv.include(v); },
+    '$=': function(nv, v) { return nv.endsWith(v); },
+    '*=': function(nv, v) { return nv.include(v); },
+    '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); },
+    '|=': function(nv, v) { return ('-' + (nv || "").toUpperCase() +
+     '-').include('-' + (v || "").toUpperCase() + '-'); }
+  },
+
+  split: function(expression) {
+    var expressions = [];
+    expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
+      expressions.push(m[1].strip());
+    });
+    return expressions;
+  },
+
+  matchElements: function(elements, expression) {
+    var matches = $$(expression), h = Selector.handlers;
+    h.mark(matches);
+    for (var i = 0, results = [], element; element = elements[i]; i++)
+      if (element._countedByPrototype) results.push(element);
+    h.unmark(matches);
+    return results;
+  },
+
+  findElement: function(elements, expression, index) {
+    if (Object.isNumber(expression)) {
+      index = expression; expression = false;
+    }
+    return Selector.matchElements(elements, expression || '*')[index || 0];
+  },
+
+  findChildElements: function(element, expressions) {
+    expressions = Selector.split(expressions.join(','));
+    var results = [], h = Selector.handlers;
+    for (var i = 0, l = expressions.length, selector; i < l; i++) {
+      selector = new Selector(expressions[i].strip());
+      h.concat(results, selector.findElements(element));
+    }
+    return (l > 1) ? h.unique(results) : results;
+  }
+});
+
+if (Prototype.Browser.IE) {
+  Object.extend(Selector.handlers, {
+    // IE returns comment nodes on getElementsByTagName("*").
+    // Filter them out.
+    concat: function(a, b) {
+      for (var i = 0, node; node = b[i]; i++)
+        if (node.tagName !== "!") a.push(node);
+      return a;
+    },
+
+    // IE improperly serializes _countedByPrototype in (inner|outer)HTML.
+    unmark: function(nodes) {
+      for (var i = 0, node; node = nodes[i]; i++)
+        node.removeAttribute('_countedByPrototype');
+      return nodes;
+    }
+  });
+}
+
+function $$() {
+  return Selector.findChildElements(document, $A(arguments));
+}
+var Form = {
+  reset: function(form) {
+    $(form).reset();
+    return form;
+  },
+
+  serializeElements: function(elements, options) {
+    if (typeof options != 'object') options = { hash: !!options };
+    else if (Object.isUndefined(options.hash)) options.hash = true;
+    var key, value, submitted = false, submit = options.submit;
+
+    var data = elements.inject({ }, function(result, element) {
+      if (!element.disabled && element.name) {
+        key = element.name; value = $(element).getValue();
+        if (value != null && element.type != 'file' && (element.type != 'submit' || (!submitted &&
+            submit !== false && (!submit || key == submit) && (submitted = true)))) {
+          if (key in result) {
+            // a key is already present; construct an array of values
+            if (!Object.isArray(result[key])) result[key] = [result[key]];
+            result[key].push(value);
+          }
+          else result[key] = value;
+        }
+      }
+      return result;
+    });
+
+    return options.hash ? data : Object.toQueryString(data);
+  }
+};
+
+Form.Methods = {
+  serialize: function(form, options) {
+    return Form.serializeElements(Form.getElements(form), options);
+  },
+
+  getElements: function(form) {
+    return $A($(form).getElementsByTagName('*')).inject([],
+      function(elements, child) {
+        if (Form.Element.Serializers[child.tagName.toLowerCase()])
+          elements.push(Element.extend(child));
+        return elements;
+      }
+    );
+  },
+
+  getInputs: function(form, typeName, name) {
+    form = $(form);
+    var inputs = form.getElementsByTagName('input');
+
+    if (!typeName && !name) return $A(inputs).map(Element.extend);
+
+    for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) {
+      var input = inputs[i];
+      if ((typeName && input.type != typeName) || (name && input.name != name))
+        continue;
+      matchingInputs.push(Element.extend(input));
+    }
+
+    return matchingInputs;
+  },
+
+  disable: function(form) {
+    form = $(form);
+    Form.getElements(form).invoke('disable');
+    return form;
+  },
+
+  enable: function(form) {
+    form = $(form);
+    Form.getElements(form).invoke('enable');
+    return form;
+  },
+
+  findFirstElement: function(form) {
+    var elements = $(form).getElements().findAll(function(element) {
+      return 'hidden' != element.type && !element.disabled;
+    });
+    var firstByIndex = elements.findAll(function(element) {
+      return element.hasAttribute('tabIndex') && element.tabIndex >= 0;
+    }).sortBy(function(element) { return element.tabIndex }).first();
+
+    return firstByIndex ? firstByIndex : elements.find(function(element) {
+      return ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
+    });
+  },
+
+  focusFirstElement: function(form) {
+    form = $(form);
+    form.findFirstElement().activate();
+    return form;
+  },
+
+  request: function(form, options) {
+    form = $(form), options = Object.clone(options || { });
+
+    var params = options.parameters, action = form.readAttribute('action') || '';
+    if (action.blank()) action = window.location.href;
+    options.parameters = form.serialize(true);
+
+    if (params) {
+      if (Object.isString(params)) params = params.toQueryParams();
+      Object.extend(options.parameters, params);
+    }
+
+    if (form.hasAttribute('method') && !options.method)
+      options.method = form.method;
+
+    return new Ajax.Request(action, options);
+  }
+};
+
+/*--------------------------------------------------------------------------*/
+
+Form.Element = {
+  focus: function(element) {
+    $(element).focus();
+    return element;
+  },
+
+  select: function(element) {
+    $(element).select();
+    return element;
+  }
+};
+
+Form.Element.Methods = {
+  serialize: function(element) {
+    element = $(element);
+    if (!element.disabled && element.name) {
+      var value = element.getValue();
+      if (value != undefined) {
+        var pair = { };
+        pair[element.name] = value;
+        return Object.toQueryString(pair);
+      }
+    }
+    return '';
+  },
+
+  getValue: function(element) {
+    element = $(element);
+    var method = element.tagName.toLowerCase();
+    return Form.Element.Serializers[method](element);
+  },
+
+  setValue: function(element, value) {
+    element = $(element);
+    var method = element.tagName.toLowerCase();
+    Form.Element.Serializers[method](element, value);
+    return element;
+  },
+
+  clear: function(element) {
+    $(element).value = '';
+    return element;
+  },
+
+  present: function(element) {
+    return $(element).value != '';
+  },
+
+  activate: function(element) {
+    element = $(element);
+    try {
+      element.focus();
+      if (element.select && (element.tagName.toLowerCase() != 'input' ||
+          !['button', 'reset', 'submit'].include(element.type)))
+        element.select();
+    } catch (e) { }
+    return element;
+  },
+
+  disable: function(element) {
+    element = $(element);
+    element.disabled = true;
+    return element;
+  },
+
+  enable: function(element) {
+    element = $(element);
+    element.disabled = false;
+    return element;
+  }
+};
+
+/*--------------------------------------------------------------------------*/
+
+var Field = Form.Element;
+var $F = Form.Element.Methods.getValue;
+
+/*--------------------------------------------------------------------------*/
+
+Form.Element.Serializers = {
+  input: function(element, value) {
+    switch (element.type.toLowerCase()) {
+      case 'checkbox':
+      case 'radio':
+        return Form.Element.Serializers.inputSelector(element, value);
+      default:
+        return Form.Element.Serializers.textarea(element, value);
+    }
+  },
+
+  inputSelector: function(element, value) {
+    if (Object.isUndefined(value)) return element.checked ? element.value : null;
+    else element.checked = !!value;
+  },
+
+  textarea: function(element, value) {
+    if (Object.isUndefined(value)) return element.value;
+    else element.value = value;
+  },
+
+  select: function(element, value) {
+    if (Object.isUndefined(value))
+      return this[element.type == 'select-one' ?
+        'selectOne' : 'selectMany'](element);
+    else {
+      var opt, currentValue, single = !Object.isArray(value);
+      for (var i = 0, length = element.length; i < length; i++) {
+        opt = element.options[i];
+        currentValue = this.optionValue(opt);
+        if (single) {
+          if (currentValue == value) {
+            opt.selected = true;
+            return;
+          }
+        }
+        else opt.selected = value.include(currentValue);
+      }
+    }
+  },
+
+  selectOne: function(element) {
+    var index = element.selectedIndex;
+    return index >= 0 ? this.optionValue(element.options[index]) : null;
+  },
+
+  selectMany: function(element) {
+    var values, length = element.length;
+    if (!length) return null;
+
+    for (var i = 0, values = []; i < length; i++) {
+      var opt = element.options[i];
+      if (opt.selected) values.push(this.optionValue(opt));
+    }
+    return values;
+  },
+
+  optionValue: function(opt) {
+    // extend element because hasAttribute may not be native
+    return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text;
+  }
+};
+
+/*--------------------------------------------------------------------------*/
+
+Abstract.TimedObserver = Class.create(PeriodicalExecuter, {
+  initialize: function($super, element, frequency, callback) {
+    $super(callback, frequency);
+    this.element   = $(element);
+    this.lastValue = this.getValue();
+  },
+
+  execute: function() {
+    var value = this.getValue();
+    if (Object.isString(this.lastValue) && Object.isString(value) ?
+        this.lastValue != value : String(this.lastValue) != String(value)) {
+      this.callback(this.element, value);
+      this.lastValue = value;
+    }
+  }
+});
+
+Form.Element.Observer = Class.create(Abstract.TimedObserver, {
+  getValue: function() {
+    return Form.Element.getValue(this.element);
+  }
+});
+
+Form.Observer = Class.create(Abstract.TimedObserver, {
+  getValue: function() {
+    return Form.serialize(this.element);
+  }
+});
+
+/*--------------------------------------------------------------------------*/
+
+Abstract.EventObserver = Class.create({
+  initialize: function(element, callback) {
+    this.element  = $(element);
+    this.callback = callback;
+
+    this.lastValue = this.getValue();
+    if (this.element.tagName.toLowerCase() == 'form')
+      this.registerFormCallbacks();
+    else
+      this.registerCallback(this.element);
+  },
+
+  onElementEvent: function() {
+    var value = this.getValue();
+    if (this.lastValue != value) {
+      this.callback(this.element, value);
+      this.lastValue = value;
+    }
+  },
+
+  registerFormCallbacks: function() {
+    Form.getElements(this.element).each(this.registerCallback, this);
+  },
+
+  registerCallback: function(element) {
+    if (element.type) {
+      switch (element.type.toLowerCase()) {
+        case 'checkbox':
+        case 'radio':
+          Event.observe(element, 'click', this.onElementEvent.bind(this));
+          break;
+        default:
+          Event.observe(element, 'change', this.onElementEvent.bind(this));
+          break;
+      }
+    }
+  }
+});
+
+Form.Element.EventObserver = Class.create(Abstract.EventObserver, {
+  getValue: function() {
+    return Form.Element.getValue(this.element);
+  }
+});
+
+Form.EventObserver = Class.create(Abstract.EventObserver, {
+  getValue: function() {
+    return Form.serialize(this.element);
+  }
+});
+if (!window.Event) var Event = { };
+
+Object.extend(Event, {
+  KEY_BACKSPACE: 8,
+  KEY_TAB:       9,
+  KEY_RETURN:   13,
+  KEY_ESC:      27,
+  KEY_LEFT:     37,
+  KEY_UP:       38,
+  KEY_RIGHT:    39,
+  KEY_DOWN:     40,
+  KEY_DELETE:   46,
+  KEY_HOME:     36,
+  KEY_END:      35,
+  KEY_PAGEUP:   33,
+  KEY_PAGEDOWN: 34,
+  KEY_INSERT:   45,
+
+  cache: { },
+
+  relatedTarget: function(event) {
+    var element;
+    switch(event.type) {
+      case 'mouseover': element = event.fromElement; break;
+      case 'mouseout':  element = event.toElement;   break;
+      default: return null;
+    }
+    return Element.extend(element);
+  }
+});
+
+Event.Methods = (function() {
+  var isButton;
+
+  if (Prototype.Browser.IE) {
+    var buttonMap = { 0: 1, 1: 4, 2: 2 };
+    isButton = function(event, code) {
+      return event.button == buttonMap[code];
+    };
+
+  } else if (Prototype.Browser.WebKit) {
+    isButton = function(event, code) {
+      switch (code) {
+        case 0: return event.which == 1 && !event.metaKey;
+        case 1: return event.which == 1 && event.metaKey;
+        default: return false;
+      }
+    };
+
+  } else {
+    isButton = function(event, code) {
+      return event.which ? (event.which === code + 1) : (event.button === code);
+    };
+  }
+
+  return {
+    isLeftClick:   function(event) { return isButton(event, 0) },
+    isMiddleClick: function(event) { return isButton(event, 1) },
+    isRightClick:  function(event) { return isButton(event, 2) },
+
+    element: function(event) {
+      event = Event.extend(event);
+
+      var node          = event.target,
+          type          = event.type,
+          currentTarget = event.currentTarget;
+
+      if (currentTarget && currentTarget.tagName) {
+        // Firefox screws up the "click" event when moving between radio buttons
+        // via arrow keys. It also screws up the "load" and "error" events on images,
+        // reporting the document as the target instead of the original image.
+        if (type === 'load' || type === 'error' ||
+          (type === 'click' && currentTarget.tagName.toLowerCase() === 'input'
+            && currentTarget.type === 'radio'))
+              node = currentTarget;
+      }
+      if (node.nodeType == Node.TEXT_NODE) node = node.parentNode;
+      return Element.extend(node);
+    },
+
+    findElement: function(event, expression) {
+      var element = Event.element(event);
+      if (!expression) return element;
+      var elements = [element].concat(element.ancestors());
+      return Selector.findElement(elements, expression, 0);
+    },
+
+    pointer: function(event) {
+      var docElement = document.documentElement,
+      body = document.body || { scrollLeft: 0, scrollTop: 0 };
+      return {
+        x: event.pageX || (event.clientX +
+          (docElement.scrollLeft || body.scrollLeft) -
+          (docElement.clientLeft || 0)),
+        y: event.pageY || (event.clientY +
+          (docElement.scrollTop || body.scrollTop) -
+          (docElement.clientTop || 0))
+      };
+    },
+
+    pointerX: function(event) { return Event.pointer(event).x },
+    pointerY: function(event) { return Event.pointer(event).y },
+
+    stop: function(event) {
+      Event.extend(event);
+      event.preventDefault();
+      event.stopPropagation();
+      event.stopped = true;
+    }
+  };
+})();
+
+Event.extend = (function() {
+  var methods = Object.keys(Event.Methods).inject({ }, function(m, name) {
+    m[name] = Event.Methods[name].methodize();
+    return m;
+  });
+
+  if (Prototype.Browser.IE) {
+    Object.extend(methods, {
+      stopPropagation: function() { this.cancelBubble = true },
+      preventDefault:  function() { this.returnValue = false },
+      inspect: function() { return "[object Event]" }
+    });
+
+    return function(event) {
+      if (!event) return false;
+      if (event._extendedByPrototype) return event;
+
+      event._extendedByPrototype = Prototype.emptyFunction;
+      var pointer = Event.pointer(event);
+      Object.extend(event, {
+        target: event.srcElement,
+        relatedTarget: Event.relatedTarget(event),
+        pageX:  pointer.x,
+        pageY:  pointer.y
+      });
+      return Object.extend(event, methods);
+    };
+
+  } else {
+    Event.prototype = Event.prototype || document.createEvent("HTMLEvents")['__proto__'];
+    Object.extend(Event.prototype, methods);
+    return Prototype.K;
+  }
+})();
+
+Object.extend(Event, (function() {
+  var cache = Event.cache;
+
+  function getEventID(element) {
+    if (element._prototypeEventID) return element._prototypeEventID[0];
+    arguments.callee.id = arguments.callee.id || 1;
+    return element._prototypeEventID = [++arguments.callee.id];
+  }
+
+  function getDOMEventName(eventName) {
+    if (eventName && eventName.include(':')) return "dataavailable";
+    return eventName;
+  }
+
+  function getCacheForID(id) {
+    return cache[id] = cache[id] || { };
+  }
+
+  function getWrappersForEventName(id, eventName) {
+    var c = getCacheForID(id);
+    return c[eventName] = c[eventName] || [];
+  }
+
+  function createWrapper(element, eventName, handler) {
+    var id = getEventID(element);
+    var c = getWrappersForEventName(id, eventName);
+    if (c.pluck("handler").include(handler)) return false;
+
+    var wrapper = function(event) {
+      if (!Event || !Event.extend ||
+        (event.eventName && event.eventName != eventName))
+          return false;
+
+      Event.extend(event);
+      handler.call(element, event);
+    };
+
+    wrapper.handler = handler;
+    c.push(wrapper);
+    return wrapper;
+  }
+
+  function findWrapper(id, eventName, handler) {
+    var c = getWrappersForEventName(id, eventName);
+    return c.find(function(wrapper) { return wrapper.handler == handler });
+  }
+
+  function destroyWrapper(id, eventName, handler) {
+    var c = getCacheForID(id);
+    if (!c[eventName]) return false;
+    c[eventName] = c[eventName].without(findWrapper(id, eventName, handler));
+  }
+
+  function destroyCache() {
+    for (var id in cache)
+      for (var eventName in cache[id])
+        cache[id][eventName] = null;
+  }
+
+
+  // Internet Explorer needs to remove event handlers on page unload
+  // in order to avoid memory leaks.
+  if (window.attachEvent) {
+    window.attachEvent("onunload", destroyCache);
+  }
+
+  // Safari has a dummy event handler on page unload so that it won't
+  // use its bfcache. Safari <= 3.1 has an issue with restoring the "document"
+  // object when page is returned to via the back button using its bfcache.
+  if (Prototype.Browser.WebKit) {
+    window.addEventListener('unload', Prototype.emptyFunction, false);
+  }
+
+  return {
+    observe: function(element, eventName, handler) {
+      element = $(element);
+      var name = getDOMEventName(eventName);
+
+      var wrapper = createWrapper(element, eventName, handler);
+      if (!wrapper) return element;
+
+      if (element.addEventListener) {
+        element.addEventListener(name, wrapper, false);
+      } else {
+        element.attachEvent("on" + name, wrapper);
+      }
+
+      return element;
+    },
+
+    stopObserving: function(element, eventName, handler) {
+      element = $(element);
+      var id = getEventID(element), name = getDOMEventName(eventName);
+
+      if (!handler && eventName) {
+        getWrappersForEventName(id, eventName).each(function(wrapper) {
+          element.stopObserving(eventName, wrapper.handler);
+        });
+        return element;
+
+      } else if (!eventName) {
+        Object.keys(getCacheForID(id)).each(function(eventName) {
+          element.stopObserving(eventName);
+        });
+        return element;
+      }
+
+      var wrapper = findWrapper(id, eventName, handler);
+      if (!wrapper) return element;
+
+      if (element.removeEventListener) {
+        element.removeEventListener(name, wrapper, false);
+      } else {
+        element.detachEvent("on" + name, wrapper);
+      }
+
+      destroyWrapper(id, eventName, handler);
+
+      return element;
+    },
+
+    fire: function(element, eventName, memo) {
+      element = $(element);
+      if (element == document && document.createEvent && !element.dispatchEvent)
+        element = document.documentElement;
+
+      var event;
+      if (document.createEvent) {
+        event = document.createEvent("HTMLEvents");
+        event.initEvent("dataavailable", true, true);
+      } else {
+        event = document.createEventObject();
+        event.eventType = "ondataavailable";
+      }
+
+      event.eventName = eventName;
+      event.memo = memo || { };
+
+      if (document.createEvent) {
+        element.dispatchEvent(event);
+      } else {
+        element.fireEvent(event.eventType, event);
+      }
+
+      return Event.extend(event);
+    }
+  };
+})());
+
+Object.extend(Event, Event.Methods);
+
+Element.addMethods({
+  fire:          Event.fire,
+  observe:       Event.observe,
+  stopObserving: Event.stopObserving
+});
+
+Object.extend(document, {
+  fire:          Element.Methods.fire.methodize(),
+  observe:       Element.Methods.observe.methodize(),
+  stopObserving: Element.Methods.stopObserving.methodize(),
+  loaded:        false
+});
+
+(function() {
+  /* Support for the DOMContentLoaded event is based on work by Dan Webb,
+     Matthias Miller, Dean Edwards and John Resig. */
+
+  var timer;
+
+  function fireContentLoadedEvent() {
+    if (document.loaded) return;
+    if (timer) window.clearInterval(timer);
+    document.fire("dom:loaded");
+    document.loaded = true;
+  }
+
+  if (document.addEventListener) {
+    if (Prototype.Browser.WebKit) {
+      timer = window.setInterval(function() {
+        if (/loaded|complete/.test(document.readyState))
+          fireContentLoadedEvent();
+      }, 0);
+
+      Event.observe(window, "load", fireContentLoadedEvent);
+
+    } else {
+      document.addEventListener("DOMContentLoaded",
+        fireContentLoadedEvent, false);
+    }
+
+  } else {
+    document.write("<script id=__onDOMContentLoaded defer src=//:><\/script>");
+    $("__onDOMContentLoaded").onreadystatechange = function() {
+      if (this.readyState == "complete") {
+        this.onreadystatechange = null;
+        fireContentLoadedEvent();
+      }
+    };
+  }
+})();
+/*------------------------------- DEPRECATED -------------------------------*/
+
+Hash.toQueryString = Object.toQueryString;
+
+var Toggle = { display: Element.toggle };
+
+Element.Methods.childOf = Element.Methods.descendantOf;
+
+var Insertion = {
+  Before: function(element, content) {
+    return Element.insert(element, {before:content});
+  },
+
+  Top: function(element, content) {
+    return Element.insert(element, {top:content});
+  },
+
+  Bottom: function(element, content) {
+    return Element.insert(element, {bottom:content});
+  },
+
+  After: function(element, content) {
+    return Element.insert(element, {after:content});
+  }
+};
+
+var $continue = new Error('"throw $continue" is deprecated, use "return" instead');
+
+// This should be moved to script.aculo.us; notice the deprecated methods
+// further below, that map to the newer Element methods.
+var Position = {
+  // set to true if needed, warning: firefox performance problems
+  // NOT neeeded for page scrolling, only if draggable contained in
+  // scrollable elements
+  includeScrollOffsets: false,
+
+  // must be called before calling withinIncludingScrolloffset, every time the
+  // page is scrolled
+  prepare: function() {
+    this.deltaX =  window.pageXOffset
+                || document.documentElement.scrollLeft
+                || document.body.scrollLeft
+                || 0;
+    this.deltaY =  window.pageYOffset
+                || document.documentElement.scrollTop
+                || document.body.scrollTop
+                || 0;
+  },
+
+  // caches x/y coordinate pair to use with overlap
+  within: function(element, x, y) {
+    if (this.includeScrollOffsets)
+      return this.withinIncludingScrolloffsets(element, x, y);
+    this.xcomp = x;
+    this.ycomp = y;
+    this.offset = Element.cumulativeOffset(element);
+
+    return (y >= this.offset[1] &&
+            y <  this.offset[1] + element.offsetHeight &&
+            x >= this.offset[0] &&
+            x <  this.offset[0] + element.offsetWidth);
+  },
+
+  withinIncludingScrolloffsets: function(element, x, y) {
+    var offsetcache = Element.cumulativeScrollOffset(element);
+
+    this.xcomp = x + offsetcache[0] - this.deltaX;
+    this.ycomp = y + offsetcache[1] - this.deltaY;
+    this.offset = Element.cumulativeOffset(element);
+
+    return (this.ycomp >= this.offset[1] &&
+            this.ycomp <  this.offset[1] + element.offsetHeight &&
+            this.xcomp >= this.offset[0] &&
+            this.xcomp <  this.offset[0] + element.offsetWidth);
+  },
+
+  // within must be called directly before
+  overlap: function(mode, element) {
+    if (!mode) return 0;
+    if (mode == 'vertical')
+      return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
+        element.offsetHeight;
+    if (mode == 'horizontal')
+      return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
+        element.offsetWidth;
+  },
+
+  // Deprecation layer -- use newer Element methods now (1.5.2).
+
+  cumulativeOffset: Element.Methods.cumulativeOffset,
+
+  positionedOffset: Element.Methods.positionedOffset,
+
+  absolutize: function(element) {
+    Position.prepare();
+    return Element.absolutize(element);
+  },
+
+  relativize: function(element) {
+    Position.prepare();
+    return Element.relativize(element);
+  },
+
+  realOffset: Element.Methods.cumulativeScrollOffset,
+
+  offsetParent: Element.Methods.getOffsetParent,
+
+  page: Element.Methods.viewportOffset,
+
+  clone: function(source, target, options) {
+    options = options || { };
+    return Element.clonePosition(target, source, options);
+  }
+};
+
+/*--------------------------------------------------------------------------*/
+
+if (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){
+  function iter(name) {
+    return name.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + name + " ')]";
+  }
+
+  instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ?
+  function(element, className) {
+    className = className.toString().strip();
+    var cond = /\s/.test(className) ? $w(className).map(iter).join('') : iter(className);
+    return cond ? document._getElementsByXPath('.//*' + cond, element) : [];
+  } : function(element, className) {
+    className = className.toString().strip();
+    var elements = [], classNames = (/\s/.test(className) ? $w(className) : null);
+    if (!classNames && !className) return elements;
+
+    var nodes = $(element).getElementsByTagName('*');
+    className = ' ' + className + ' ';
+
+    for (var i = 0, child, cn; child = nodes[i]; i++) {
+      if (child.className && (cn = ' ' + child.className + ' ') && (cn.include(className) ||
+          (classNames && classNames.all(function(name) {
+            return !name.toString().blank() && cn.include(' ' + name + ' ');
+          }))))
+        elements.push(Element.extend(child));
+    }
+    return elements;
+  };
+
+  return function(className, parentElement) {
+    return $(parentElement || document.body).getElementsByClassName(className);
+  };
+}(Element.Methods);
+
+/*--------------------------------------------------------------------------*/
+
+Element.ClassNames = Class.create();
+Element.ClassNames.prototype = {
+  initialize: function(element) {
+    this.element = $(element);
+  },
+
+  _each: function(iterator) {
+    this.element.className.split(/\s+/).select(function(name) {
+      return name.length > 0;
+    })._each(iterator);
+  },
+
+  set: function(className) {
+    this.element.className = className;
+  },
+
+  add: function(classNameToAdd) {
+    if (this.include(classNameToAdd)) return;
+    this.set($A(this).concat(classNameToAdd).join(' '));
+  },
+
+  remove: function(classNameToRemove) {
+    if (!this.include(classNameToRemove)) return;
+    this.set($A(this).without(classNameToRemove).join(' '));
+  },
+
+  toString: function() {
+    return $A(this).join(' ');
+  }
+};
+
+Object.extend(Element.ClassNames.prototype, Enumerable);
+
+/*--------------------------------------------------------------------------*/
+
+Element.addMethods();
\ No newline at end of file
diff --git a/js/src/revlog.js b/js/src/revlog.js
new file mode 100644
index 0000000..a912b0e
--- /dev/null
+++ b/js/src/revlog.js
@@ -0,0 +1,53 @@
+/**
+ * $Horde: chora/js/src/revlog.js,v 1.4.2.1 2008/10/09 17:40:05 jan Exp $
+ *
+ * Revision log javascript.
+ */
+
+var revlog_selected = null;
+var isMSIE = /*@cc_on!@*/false;
+
+function revlog_highlight()
+{
+    var revlog_body = $('revlog_body');
+
+    $A(revlog_body.getElementsByTagName('TR')).each(function(tr) {
+        if (isMSIE) {
+            Event.observe(tr, 'mouseover', (function() { Element.addClassName(this, 'hover'); }).bind(tr));
+            Event.observe(tr, 'mouseout', (function() { Element.removeClassName(this, 'hover'); }).bind(tr));
+        }
+        Event.observe(tr, 'click', revlog_toggle.bindAsEventListener(tr));
+    });
+}
+
+function revlog_toggle(e)
+{
+    // Ignore clicks on links.
+    var elt = Event.element(e);
+    while (elt != this) {
+        if (elt.tagName.toUpperCase() == 'A' && elt.getAttribute('href')) {
+            return;
+        }
+        elt = elt.parentNode;
+    }
+
+    if (revlog_selected != null) {
+        Element.removeClassName(revlog_selected, 'selected');
+        if (revlog_selected == this) {
+            revlog_selected = null;
+            Element.removeClassName('revlog_body', 'selection');
+            return;
+        }
+    }
+
+    revlog_selected = this;
+    Element.addClassName(this, 'selected');
+    Element.addClassName('revlog_body', 'selection');
+}
+
+function revlog_sdiff(link)
+{
+    link.href = link.href.replace(/r1=([\d\.]+)/, 'r1=' + revlog_selected.id.substring(3));
+}
+
+Event.observe(window, 'load', revlog_highlight);
diff --git a/js/src/stripe.js b/js/src/stripe.js
new file mode 100644
index 0000000..feaa123
--- /dev/null
+++ b/js/src/stripe.js
@@ -0,0 +1,43 @@
+/**
+ * Javascript code for finding all tables with classname "striped" and
+ * dynamically striping their row colors.
+ *
+ * $Horde: chora/js/src/stripe.js,v 1.4.2.1 2008/10/09 17:40:05 jan Exp $
+ *
+ * @author Chuck Hagenbuch <chuck at horde.org>
+ * @author Matt Warden <mwarden at gmail.com>
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ */
+
+function stripeAllElements()
+{
+    $$('.striped').each(stripeElement);
+}
+
+function stripeElement(elt)
+{
+    var classes = [ 'rowEven', 'rowOdd' ],
+        e = [];
+
+    if (elt.tagName == 'TABLE') {
+        // Tables can have more than one tbody element; get all child
+        // tbody tags and interate through them.
+        elt.immediateDescendants().each(stripeElement);
+        return;
+    } else {
+        // Toggle the classname of any child node that is an element.
+        e = elt.immediateDescendants();
+    }
+
+    e.each(function(c) {
+        c.removeClassName(classes[1]);
+        c.addClassName(classes[0]);
+        classes.reverse(true);
+    });
+}
+
+/* We have to wait for the full DOM to be loaded to ensure we don't
+ * miss anything. */
+document.observe('dom:loaded', stripeAllElements);
diff --git a/js/src/tables.js b/js/src/tables.js
new file mode 100644
index 0000000..f4e840b
--- /dev/null
+++ b/js/src/tables.js
@@ -0,0 +1,264 @@
+/**
+ * Javascript code for finding all tables with classname "striped" and
+ * dynamically striping their row colors, and finding all tables with
+ * classname "sortable" and making them dynamically sortable.
+ *
+ * TODO: incorporate missing features (if wanted) and improvements from:
+ * http://tetlaw.id.au/view/blog/table-sorting-with-prototype/
+ * http://www.millstream.com.au/view/code/tablekit/
+ * http://tablesorter.com/docs/
+ *
+ * $Horde: chora/js/src/tables.js,v 1.6.2.1 2008/10/09 17:40:05 jan Exp $
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ */
+
+var SORT_COLUMN_INDEX;
+
+function table_stripe(table)
+{
+    var classes = [ 'rowEven', 'rowOdd' ];
+
+    // Tables can have more than one tbody element; get all child
+    // tbody tags and interate through them.
+    table.select('tbody tr').each(function(c) {
+        c.removeClassName(classes[1]);
+        c.addClassName(classes[0]);
+        classes.reverse(true);
+    });
+}
+
+function table_makeSortable(table)
+{
+    var i = 0;
+
+    // We have a first row: assume it's the header, and make its
+    // contents clickable links.
+    table.down('tr').immediateDescendants().each(function(cell) {
+        if (cell.hasClassName('nosort')) {
+            ++i;
+            return;
+        }
+
+        cell.setAttribute('columnIndex', i++);
+        cell.setStyle({ cursor: 'pointer' });
+        cell.observe('click', function(e) {
+            var c = e.findElement('th');
+            e.stop();
+            var a = c.down('a');
+            if (a && !a.hasClassName('sortlink')) {
+                return true;
+            }
+            table_resortTable(c);
+            return false;
+        });
+    });
+}
+
+function table_getSortValue(el)
+{
+    var str = '';
+
+    if (Object.isString(el) || Object.isUndefined(el)) {
+        return el;
+    }
+
+    // Use "sortval" if defined.
+    el = $(el);
+    if (el.readAttribute('sortval')) {
+        return el.readAttribute('sortval');
+    }
+
+    if (el.innerText) {
+        // Not needed but it is faster.
+        return el.innerText;
+    }
+
+    $A(el.childNodes).each(function(e) {
+        switch (e.nodeType) {
+        case 1:
+            // ELEMENT_NODE
+            str += table_getSortValue(e);
+            break;
+
+        case 3:
+            // TEXT_NODE
+            str += e.nodeValue;
+            break;
+        }
+    });
+
+    return str;
+}
+
+function table_resortTable(th)
+{
+    var table = th.up('table'),
+        th_siblings = th.up().immediateDescendants(),
+        sortfn,
+        sortDown = 0;
+
+    th_siblings.each(function(e) {
+        if (th == e) {
+            if (e.hasClassName('sortup')) {
+                e.removeClassName('sortup');
+                e.addClassName('sortdown');
+            } else if (e.hasClassName('sortdown')) {
+                e.removeClassName('sortdown');
+                e.addClassName('sortup');
+                sortDown = 1;
+            } else {
+                e.addClassName('sortdown');
+            }
+        } else {
+            e.removeClassName('sortup');
+            e.removeClassName('sortdown');
+        }
+    });
+
+    // Work out a type for the column
+    if (th_siblings.size() <= 1) {
+        return;
+    }
+
+    SORT_COLUMN_INDEX = th.readAttribute('columnIndex');
+    var itm = table_getSortValue(table.down('tbody > tr').cells[SORT_COLUMN_INDEX]);
+
+    if (itm.match(/^\d\d[\/-]\d\d[\/-]\d\d\d\d$/) ||
+        itm.match(/^\d\d[\/-]\d\d[\/-]\d\d$/)) {
+        sortfn = table_sort_date;
+    } else if (itm.match(/^[£$]/)) {
+        sortfn = table_sort_currency;
+    } else if (itm.match(/^[\d\.]+$/)) {
+        sortfn = table_sort_numeric;
+    } else {
+        sortfn = table_sort_caseinsensitive;
+    }
+
+    // Don't mix up seperate tbodies; sort each in turn.
+    $A(table.getElementsByTagName('tbody')).each(function(tbody) {
+        var bottomRows = [ ],
+            newRows = $A(tbody.getElementsByTagName('tr'));
+
+        newRows.sort(sortfn);
+        if (sortDown) {
+            newRows.reverse();
+        }
+
+        // We appendChild rows that already exist to the tbody, so it
+        // moves them rather than creating new ones. Don't do
+        // sortbottom rows.
+        newRows.each(function(r) {
+            if (r.hasClassName('sortbottom')) {
+                bottomRows.push(r);
+            } else {
+                tbody.appendChild(r);
+            }
+        });
+
+        // Do sortbottom rows only.
+        bottomRows.each(function(r) {
+            tbody.appendChild(r);
+        });
+    });
+
+    // If we just resorted a striped table, re-stripe it.
+    if (table.hasClassName('striped')) {
+        table_stripe(table);
+    }
+
+    // Finally, see if we have a callback function to trigger.
+    if (typeof table_sortCallback != 'undefined' && Object.isFunction(table_sortCallback)) {
+        table_sortCallback(table.id, th.id, sortDown);
+    }
+}
+
+function table_sort_date(a, b)
+{
+    // Two digit years less than 50 are treated as 20XX, greater than
+    // 50 are treated as 19XX.
+    var aa = table_getSortValue(a.cells[SORT_COLUMN_INDEX]),
+        bb = table_getSortValue(b.cells[SORT_COLUMN_INDEX]),
+        dt1, dt2, yr;
+
+    if (aa.length == 10) {
+        dt1 = aa.substr(6, 4) + aa.substr(3, 2) + aa.substr(0, 2);
+    } else {
+        yr = aa.substr(6, 2);
+        if (parseInt(yr) < 50) {
+            yr = '20' + yr;
+        } else {
+            yr = '19' + yr;
+        }
+        dt1 = yr + aa.substr(3, 2) + aa.substr(0, 2);
+    }
+    if (bb.length == 10) {
+        dt2 = bb.substr(6, 4) + bb.substr(3, 2) + bb.substr(0, 2);
+    } else {
+        yr = bb.substr(6, 2);
+        if (parseInt(yr) < 50) {
+            yr = '20' + yr;
+        } else {
+            yr = '19' + yr;
+        }
+        dt2 = yr + bb.substr(3, 2) + bb.substr(0, 2);
+    }
+    if (dt1 == dt2) {
+        return 0;
+    } else if (dt1 < dt2) {
+        return -1;
+    }
+    return 1;
+}
+
+function table_sort_currency(a, b)
+{
+    var aa = table_getSortValue(a.cells[SORT_COLUMN_INDEX]).replace(/[^0-9.]/g, ''),
+        bb = table_getSortValue(b.cells[SORT_COLUMN_INDEX]).replace(/[^0-9.]/g, '');
+    return parseFloat(aa) - parseFloat(bb);
+}
+
+function table_sort_numeric(a, b)
+{
+    var aa = parseFloat(table_getSortValue(a.cells[SORT_COLUMN_INDEX]));
+    if (isNaN(aa)) {
+        aa = 0;
+    }
+    var bb = parseFloat(table_getSortValue(b.cells[SORT_COLUMN_INDEX]));
+    if (isNaN(bb)) {
+        bb = 0;
+    }
+    return aa - bb;
+}
+
+function table_sort_caseinsensitive(a, b)
+{
+    var aa = table_getSortValue(a.cells[SORT_COLUMN_INDEX]).toLowerCase(),
+        bb = table_getSortValue(b.cells[SORT_COLUMN_INDEX]).toLowerCase();
+    if (aa == bb) {
+        return 0;
+    } else if (aa < bb) {
+        return -1;
+    }
+    return 1;
+}
+
+function table_sort_default(a, b)
+{
+    var aa = table_getSortValue(a.cells[SORT_COLUMN_INDEX]),
+        bb = table_getSortValue(b.cells[SORT_COLUMN_INDEX]);
+    if (aa == bb) {
+        return 0;
+    } else if (aa < bb) {
+        return -1;
+    }
+    return 1;
+}
+
+/* We do everything onload so that the entire document is present
+ * before we start searching it for tables. */
+document.observe('dom:loaded', function() {
+    $$('table.striped').each(table_stripe);
+    $$('table.sortable').each(table_makeSortable);
+});
diff --git a/js/stripe.js b/js/stripe.js
new file mode 100644
index 0000000..f4c09bc
--- /dev/null
+++ b/js/stripe.js
@@ -0,0 +1 @@
+function stripeAllElements(){$$(".striped").each(stripeElement)}function stripeElement(A){var B=["rowEven","rowOdd"],C=[];if(A.tagName=="TABLE"){A.immediateDescendants().each(stripeElement);return }else{C=A.immediateDescendants()}C.each(function(D){D.removeClassName(B[1]);D.addClassName(B[0]);B.reverse(true)})}document.observe("dom:loaded",stripeAllElements)
\ No newline at end of file
diff --git a/js/tables.js b/js/tables.js
new file mode 100644
index 0000000..5dc3bfa
--- /dev/null
+++ b/js/tables.js
@@ -0,0 +1 @@
+var SORT_COLUMN_INDEX;function table_stripe(B){var A=["rowEven","rowOdd"];B.select("tbody tr").each(function(C){C.removeClassName(A[1]);C.addClassName(A[0]);A.reverse(true)})}function table_makeSortable(B){var A=0;B.down("tr").immediateDescendants().each(function(C){if(C.hasClassName("nosort")){++A;return }C.setAttribute("columnIndex",A++);C.setStyle({cursor:"pointer"});C.observe("click",function(E){var F=E.findElement("th");E.stop();var D=F.down("a");if(D&&!D.hasClassName("sortlink")){return true}table_resortTable(F);return false})})}function table_getSortValue(A){var B="";if(Object.isString(A)||Object.isUndefined(A)){return A}A=$(A);if(A.readAttribute("sortval")){return A.readAttribute("sortval")}if(A.innerText){return A.innerText}$A(A.childNodes).each(function(C){switch(C.nodeType){case 1:B+=table_getSortValue(C);break;case 3:B+=C.nodeValue;break}});return B}function table_resortTable(D){var C=D.up("table"),E=D.up().immediateDescendants(),B,A=0;E.each(function(G){if(D==G){if(G.hasClassName("sortup")){G.removeClassName("sortup");G.addClassName("sortdown")}else{if(G.hasClassName("sortdown")){G.removeClassName("sortdown");G.addClassName("sortup");A=1}else{G.addClassName("sortdown")}}}else{G.removeClassName("sortup");G.removeClassName("sortdown")}});if(E.size()<=1){return }SORT_COLUMN_INDEX=D.readAttribute("columnIndex");var F=table_getSortValue(C.down("tbody > tr").cells[SORT_COLUMN_INDEX]);if(F.match(/^\d\d[\/-]\d\d[\/-]\d\d\d\d$/)||F.match(/^\d\d[\/-]\d\d[\/-]\d\d$/)){B=table_sort_date}else{if(F.match(/^[?$]/)){B=table_sort_currency}else{if(F.match(/^[\d\.]+$/)){B=table_sort_numeric}else{B=table_sort_caseinsensitive}}}$A(C.getElementsByTagName("tbody")).each(function(H){var G=[],I=$A(H.getElementsByTagName("tr"));I.sort(B);if(A){I.reverse()}I.each(function(J){if(J.hasClassName("sortbottom")){G.push(J)}else{H.appendChild(J)}});G.each(function(J){H.appendChild(J)})});if(C.hasClassName("striped")){table_stripe(C)}if(typeof table_sortCallback!="undefined"&&Object.isFunction(table_sortCallback)){table_sortCallback(C.id,D.id,A)}}function table_sort_date(C,A){var F=table_getSortValue(C.cells[SORT_COLUMN_INDEX]),G=table_getSortValue(A.cells[SORT_COLUMN_INDEX]),D,B,E;if(F.length==10){D=F.substr(6,4)+F.substr(3,2)+F.substr(0,2)}else{E=F.substr(6,2);if(parseInt(E)<50){E="20"+E}else{E="19"+E}D=E+F.substr(3,2)+F.substr(0,2)}if(G.length==10){B=G.substr(6,4)+G.substr(3,2)+G.substr(0,2)}else{E=G.substr(6,2);if(parseInt(E)<50){E="20"+E}else{E="19"+E}B=E+G.substr(3,2)+G.substr(0,2)}if(D==B){return 0}else{if(D<B){return -1}}return 1}function table_sort_currency(B,A){var C=table_getSortValue(B.cells[SORT_COLUMN_INDEX]).replace(/[^0-9.]/g,""),D=table_getSortValue(A.cells[SORT_COLUMN_INDEX]).replace(/[^0-9.]/g,"");return parseFloat(C)-parseFloat(D)}function table_sort_numeric(B,A){var C=parseFloat(table_getSortValue(B.cells[SORT_COLUMN_INDEX]));if(isNaN(C)){C=0}var D=parseFloat(table_getSortValue(A.cells[SORT_COLUMN_INDEX]));if(isNaN(D)){D=0}return C-D}function table_sort_caseinsensitive(B,A){var C=table_getSortValue(B.cells[SORT_COLUMN_INDEX]).toLowerCase(),D=table_getSortValue(A.cells[SORT_COLUMN_INDEX]).toLowerCase();if(C==D){return 0}else{if(C<D){return -1}}return 1}function table_sort_default(B,A){var C=table_getSortValue(B.cells[SORT_COLUMN_INDEX]),D=table_getSortValue(A.cells[SORT_COLUMN_INDEX]);if(C==D){return 0}else{if(C<D){return -1}}return 1}document.observe("dom:loaded",function(){$$("table.striped").each(table_stripe);$$("table.sortable").each(table_makeSortable)})
\ No newline at end of file
diff --git a/lib/Block/tree_menu.php b/lib/Block/tree_menu.php
new file mode 100644
index 0000000..a221961
--- /dev/null
+++ b/lib/Block/tree_menu.php
@@ -0,0 +1,40 @@
+<?php
+
+$block_name = _("Menu List");
+$block_type = 'tree';
+
+/**
+ * $Horde: chora/lib/Block/tree_menu.php,v 1.5.2.1 2008/10/09 17:40:05 jan Exp $
+ */
+class Horde_Block_chora_tree_menu extends Horde_Block {
+
+    var $_app = 'chora';
+
+    function _buildTree(&$tree, $indent = 0, $parent = null)
+    {
+        global $perms, $sourceroots;
+
+        define('CHORA_ERROR_HANDLER', true);
+        require_once dirname(__FILE__) . '/../base.php';
+
+        $arr = array();
+        asort($sourceroots);
+        foreach ($sourceroots as $key => $val) {
+            if ((!$perms->exists('chora:sourceroots:' . $key) ||
+                 $perms->hasPermission('chora:sourceroots:' . $key,
+                                       Auth::getAuth(),
+                                       PERMS_READ | PERMS_SHOW))) {
+                $tree->addNode($parent . $key,
+                               $parent,
+                               $val['name'],
+                               $indent + 1,
+                               false,
+                               array('icon' => 'folder.png',
+                                     'icondir' => $registry->getImageDir('horde') . '/tree',
+                                     'url' => Chora::url('', '', array('rt' => $key))));
+            }
+        }
+
+    }
+
+}
diff --git a/lib/Chora.php b/lib/Chora.php
index bfc70f2..e34bc44 100644
--- a/lib/Chora.php
+++ b/lib/Chora.php
@@ -2,7 +2,7 @@
 /**
  * Chora Base Class.
  *
- * $Horde: chora/lib/Chora.php,v 1.72.6.7 2007/02/23 03:58:53 selsky Exp $
+ * $Horde: chora/lib/Chora.php,v 1.72.6.8 2008/10/09 17:40:05 jan Exp $
  *
  * @author  Anil Madhavapeddy <avsm at horde.org>
  * @package Chora
@@ -64,11 +64,12 @@ class Chora {
     /**
      * Initialize global variables and objects.
      */
-    function init()
+    function initialize()
     {
         global $acts, $defaultActs, $conf, $where, $atdir,
-            $fullname, $prefs, $sourceroots, $sourceroot, $scriptName;
+            $fullname, $prefs, $sourceroot, $scriptName;
 
+        $sourceroots = Chora::sourceroots();
         /**
          * Variables we wish to propagate across web pages
          *  sbt = Sort By Type (name, age, author, etc)
@@ -111,7 +112,7 @@ class Chora {
         }
 
         if (!isset($sourceroots[$acts['rt']])) {
-            Chora::fatal('Malformed URL');
+            Chora::fatal(404, 'Malformed URL');
         }
 
         $sourcerootopts = $sourceroots[$acts['rt']];
@@ -120,22 +121,17 @@ class Chora {
         $conf['paths']['temp'] = Horde::getTempDir();
         $GLOBALS['VC'] = &VC::factory($sourcerootopts['type'],
                                       array('sourceroot' => $sourcerootopts['location'],
-                                            'paths' => $conf['paths']));
+                                            'paths' => $conf['paths'],
+                                            'username' => isset($sourcerootopts['username']) ? $sourcerootopts['username'] : '',
+                                            'password' => isset($sourcerootopts['password']) ? $sourcerootopts['password'] : ''));
 
         $conf['paths']['sourceroot'] = $sourcerootopts['location'];
-        $conf['paths']['cvsusers'] = $sourcerootopts['location'] . '/' . @$sourcerootopts['cvsusers'];
-        $conf['paths']['introText'] = CHORA_BASE . '/config/' . @$sourcerootopts['intro'];
-        $conf['options']['introTitle'] = @$sourcerootopts['title'];
+        $conf['paths']['cvsusers'] = $sourcerootopts['location'] . '/' . (isset($sourcerootopts['cvsusers']) ? $sourcerootopts['cvsusers'] : '');
+        $conf['paths']['introText'] = CHORA_BASE . '/config/' . (isset($sourcerootopts['intro']) ? $sourcerootopts['intro'] : '');
+        $conf['options']['introTitle'] = isset($sourcerootopts['title']) ? $sourcerootopts['title'] : '';
         $conf['options']['sourceRootName'] = $sourcerootopts['name'];
 
         $where = Util::getFormData('f', '');
-
-        /* Override $where with PATH_INFO if appropriate. */
-        if (($conf['options']['urls'] == 'path_info' || $conf['options']['urls'] == 'rewrite') &&
-            isset($_SERVER['PATH_INFO'])) {
-            $where = $_SERVER['PATH_INFO'];
-        }
-
         if ($where == '') {
             $where = '/';
         }
@@ -170,6 +166,7 @@ class Chora {
         }
 
         $fullname = $sourcerootopts['location'] . (substr($sourcerootopts['location'], -1) == '/' ? '' : '/') . $where;
+
         if ($sourcerootopts['type'] == 'cvs') {
             $fullname = preg_replace('|/$|', '', $fullname);
             $atdir = @is_dir($fullname);
@@ -179,7 +176,11 @@ class Chora {
         $where = preg_replace('|/$|', '', $where);
 
         if ($sourcerootopts['type'] == 'cvs' && !@is_dir($sourcerootopts['location'])) {
-            Chora::fatal(_("SourceRoot not found! This could be a misconfiguration by the server administrator, or the server could be having temporary problems. Please try again later."));
+            Chora::fatal('500 Internal Server Error', _("SourceRoot not found! This could be a misconfiguration by the server administrator, or the server could be having temporary problems. Please try again later."));
+        }
+
+        if (Chora::isRestricted($where)) {
+            Chora::fatal('403 Forbidden', "$where: Forbidden by server configuration");
         }
     }
 
@@ -210,16 +211,21 @@ class Chora {
     /**
      * Output an error page.
      *
-     * @param string $msg  The verbose error message to be displayed.
+     * @param string $errcode  The HTTP error number and text.
+     * @param string $errmsg   The verbose error message to be displayed.
      */
-    function fatal($msg)
+    function fatal($errcode, $errmsg)
     {
+        if (defined('CHORA_ERROR_HANDLER') && constant('CHORA_ERROR_HANDLER')) {
+            return;
+        }
+
         global $registry, $conf, $notification, $browser, $prefs;
 
         /* Don't store the bad file in the user's preferences. */
         $prefs->setValue('last_file', '');
 
-        $notification->push($msg, 'horde.error');
+        $notification->push($errcode . ': ' . $errmsg, 'horde.error');
         require CHORA_TEMPLATES . '/common-header.inc';
         require CHORA_TEMPLATES . '/menu.inc';
         require $registry->get('templates', 'horde') . '/common-footer.inc';
@@ -235,51 +241,11 @@ class Chora {
     function checkError($e)
     {
         if (is_a($e, 'PEAR_Error')) {
-            Chora::fatal($e->getMessage());
+            Chora::fatal($e->getCode(), $e->getMessage());
         }
     }
 
     /**
-     * Return an array with the names of any of the variables we need
-     * to keep, that are different from the defaults.
-     *
-     * @return array  Names/vals of differing variables.
-     */
-    function differingVars()
-    {
-        global $acts, $defaultActs;
-        reset($acts);
-        $ret = array();
-        foreach ($acts as $key => $val) {
-            if ($val != $defaultActs[$key]) {
-                $ret[$key] = $val;
-            }
-        }
-        return $ret;
-    }
-
-    /**
-     * Generate a series of hidden input variables based on the GET
-     * parameters which are different from the defaults.
-     *
-     * @param array $except  Array of exceptions to never output.
-     *
-     * @return string  A set of input tags with the different variables.
-     */
-    function generateHiddens($except = array())
-    {
-        global $acts;
-        $toOut = Chora::differingVars();
-        $ret = Util::formInput() . "\n";
-        while (list($key, $val) = each($toOut)) {
-            if (is_array($except) && !in_array($key, $except)) {
-                $ret .= "<input type=\"hidden\" name=\"$key\" value=\"$val\" />\n";
-            }
-        }
-        return $ret;
-    }
-
-    /**
      * Convert a commit-name into whatever the user wants.
      *
      * @param string $name  Account name.
@@ -288,23 +254,24 @@ class Chora {
      */
     function showAuthorName($name, $fullname = false)
     {
-        global $VC;
+        static $users = null;
+        if ($users === null) {
+            $users = $GLOBALS['VC']->getUsers($GLOBALS['conf']['paths']['cvsusers']);
+        }
 
-        $users = $VC->getUsers($GLOBALS['conf']['paths']['cvsusers']);
         if (is_array($users) && isset($users[$name])) {
-            return '<a href="mailto:' . $users[$name]['mail'] . '">' .
-                ($fullname ? $users[$name]['name'] : $name) .
-                '</a>' . ($fullname ? " <i>($name)</i>" : '');
-        } else {
-            return $name;
+            return '<a href="mailto:' . htmlspecialchars($users[$name]['mail']) . '">' .
+                htmlspecialchars($fullname ? $users[$name]['name'] : $name) .
+                '</a>' . ($fullname ? ' <em>' . htmlspecialchars($name) . '</em>' : '');
         }
+        return htmlspecialchars($name);
     }
 
     /**
      * Generate a URL that links into Chora.
      *
      * @param string $script  Name of the Chora script to link into
-     * @param string $uri     Any PATH_INFO portion that should be included
+     * @param string $uri     The path being browsed.
      * @param array  $args    Key/value pair of any GET parameters to append
      * @param string $anchor  Anchor entity name
      *
@@ -312,33 +279,32 @@ class Chora {
      */
     function url($script = '', $uri = '', $args = array(), $anchor = '')
     {
-        global $conf;
-
-        $arglist = array_merge(Chora::differingVars(), $args);
-        $script = $script ? $script . '.php' : '';
+        global $conf, $acts, $defaultActs;
 
-        if ($conf['options']['urls'] != 'get') {
-            if ($conf['options']['urls'] == 'path_info' && empty($script)) {
-                $script = 'browse.php';
+        $differing = array();
+        foreach ($acts as $key => $val) {
+            if ($val != $defaultActs[$key]) {
+                $differing[$key] = $val;
             }
+        }
 
-            if (substr($uri, 0, 1) != '/' && $script) {
-                $script .= '/';
-            }
-            $script .= $uri;
-            if (substr($script, 0, 1) == '/') {
-                $script = substr($script, 1);
+        $arglist = array_merge($differing, $args);
+        $script = $script ? $script . '.php' : 'browse.php';
+
+        if ($conf['options']['urls'] == 'rewrite') {
+            if ($script == 'browse.php') {
+                $script = $uri;
+                if (substr($script, 0, 1) == '/') {
+                    $script = substr($script, 1);
+                }
+            } else {
+                $script .= '/' . $uri;
             }
         } else {
             $arglist['f'] = $uri;
-            if (empty($script)) {
-                $script = 'browse.php';
-            }
         }
 
-        $url = Horde::applicationUrl($script);
-        $url = Util::addParameter($url, $arglist);
-
+        $url = Util::addParameter(Horde::applicationUrl($script), $arglist);
         if (!empty($anchor)) {
             $url .= "#$anchor";
         }
@@ -347,6 +313,57 @@ class Chora {
     }
 
     /**
+     * Generates hidden form fields with all required parameters.
+     *
+     * @param array  $args    Key/value pair of any POST parameters to append
+     *
+     * @return string  The form fields, with session information if necessary.
+     */
+    function formInputs($args = array())
+    {
+        global $conf, $acts, $defaultActs;
+
+        $differing = array();
+        foreach ($acts as $key => $val) {
+            if ($val != $defaultActs[$key]) {
+                $differing[$key] = $val;
+            }
+        }
+
+        $arglist = array_merge($differing, $args);
+
+        $fields = Util::formInput();
+        foreach ($arglist as $key => $val) {
+            $fields .= '<input type="hidden" name="' . htmlspecialchars($key)
+                . '" value="' . htmlspecialchars($val) . '" />';
+        }
+
+        return $fields;
+    }
+
+    /**
+     * Returns the entries of $sourceroots that the current user has access to.
+     *
+     * @return array  The sourceroots that the current user has access to.
+     */
+    function sourceroots()
+    {
+        global $perms, $sourceroot, $sourceroots;
+
+        $arr = array();
+        foreach ($sourceroots as $key => $val) {
+            if (!$perms->exists('chora:sourceroots:' . $key) ||
+                 $perms->hasPermission('chora:sourceroots:' . $key,
+                                       Auth::getAuth(),
+                                       PERMS_READ | PERMS_SHOW)) {
+                $arr[$key] = $val;
+            }
+        }
+
+        return $arr;
+    }
+
+    /**
      * Generate a list of repositories available from this
      * installation of Chora.
      *
@@ -354,21 +371,26 @@ class Chora {
      */
     function repositories()
     {
-        global $sourceroot, $sourceroots, $defaultActs;
+        $sourceroots = Chora::sourceroots();
+        $num_repositories = count($sourceroots);
+        if ($num_repositories == 1) {
+            return '';
+        }
 
         $arr = array();
         foreach ($sourceroots as $key => $val) {
-            if ($sourceroot != $key) {
-                $arr[] = '<b><a href="' . Chora::url('', '', array('rt' => $key)) . '">' .
-                    $val['name'] . '</a></b>';
+            if ($GLOBALS['sourceroot'] != $key) {
+                $arr[] = '<option value="' .
+                    Chora::url('', '', array('rt' => $key)) .
+                    '">' . $val['name'] . '</option>';
             }
         }
 
-        if (count($arr)) {
-            return _("Other Repositories") . ': ' . implode(' , ', $arr);
-        } else {
-            return '';
-        }
+        return
+            '<form action="#" id="repository-picker">'
+            . '<select onchange="location.href=this[this.selectedIndex].value">'
+            . '<option value="">' . _("Change repositories:") . '</option>'
+            . implode(' , ', $arr) . '</select></form>';
     }
 
     /**
@@ -398,9 +420,17 @@ class Chora {
      **/
     function isRestricted($item)
     {
-        global $conf, $sourceroots, $sourceroot;
+        global $conf, $perms, $sourceroots, $sourceroot;
         static $restricted;
 
+        // First check if the current user has access to this repository.
+        if ($perms->exists('chora:sourceroots:' . $sourceroot) &&
+            !$perms->hasPermission('chora:sourceroots:' . $sourceroot,
+                                   Auth::getAuth(),
+                                   PERMS_READ | PERMS_SHOW)) {
+            return true;
+        }
+
         if (!isset($restricted)) {
             $restricted = array();
             if (isset($conf['restrictions']) && is_array($conf['restrictions'])) {
@@ -445,33 +475,50 @@ class Chora {
         }
     }
 
+    /**
+     */
     function getFileViews()
     {
         global $where;
 
         $views = array();
-        $current = $_SERVER['PHP_SELF'];
-        if (!empty($_SERVER['PATH_INFO'])) {
-            $current = str_replace($_SERVER['PATH_INFO'], '', $current);
-        }
-        $current = str_replace('.php', '', basename($current));
+        $current = str_replace('.php', '', basename($_SERVER['PHP_SELF']));
 
-        $views[] = $current == 'browse' ? '<i class="widget">' . _("Logs") . '</i>' : Horde::widget(Chora::url('', $where), _("Logs"), 'widget', '', '', _("_Logs"));
+        $views[] = $current == 'browse'
+            ? '<em class="widget">' . _("Logs") . '</em>'
+            : Horde::widget(Chora::url('', $where), _("Logs"), 'widget', '',
+                            '', _("_Logs"));
         // Subversion supports patchsets natively.
-        if (!empty($GLOBALS['conf']['paths']['cvsps']) || is_a($GLOBALS['VC'], 'VC_svn')) {
-            $views[] = $current == 'patchsets' ? '<i class="widget">' . _("Patchsets") . '</i>' : Horde::widget(Chora::url('patchsets', $where), _("Patchsets"), 'widget', '', '', _("_Patchsets"));
+        if (!empty($GLOBALS['conf']['paths']['cvsps']) ||
+            is_a($GLOBALS['VC'], 'VC_svn')) {
+            $views[] = $current == 'patchsets'
+                ? '<em class="widget">' . _("Patchsets") . '</em>'
+                : Horde::widget(Chora::url('patchsets', $where), _("Patchsets"),
+                                'widget', '', '', _("_Patchsets"));
         }
         if (!is_a($GLOBALS['VC'], 'VC_svn')) {
-            $views[] = $current == 'history' ? '<i class="widget">' . _("Branches") . '</i>' : Horde::widget(Chora::url('history', $where), _("Branches"), 'widget', '', '', _("_Branches"));
-        }
-        if (!empty($GLOBALS['conf']['paths']['cvsgraph']) && !is_a($GLOBALS['VC'], 'VC_svn')) {
-            $views[] = $current == 'cvsgraph' ? '<i class="widget">' . _("Graph") . '</i>' : Horde::widget(Chora::url('cvsgraph', $where), _("Graph"), 'widget', '', '', _("_Graph"));
+            if (empty($GLOBALS['conf']['paths']['cvsgraph'])) {
+                $views[] = $current == 'history'
+                    ? '<em class="widget">' . _("Branches") . '</em>'
+                    : Horde::widget(Chora::url('history', $where), _("Branches"),
+                                    'widget', '', '', _("_Branches"));
+            } else {
+                $views[] = $current == 'cvsgraph'
+                    ? '<em class="widget">' . _("Branches") . '</em>'
+                    : Horde::widget(Chora::url('cvsgraph', $where), _("Branches"),
+                                    'widget', '', '', _("_Branches"));
+            }
         }
-        $views[] = $current == 'stats' ? '<i class="widget">' . _("Statistics") . '</i>' : Horde::widget(Chora::url('stats', $where), _("Statistics"), 'widget', '', '', _("_Statistics"));
+        $views[] = $current == 'stats'
+            ? '<em class="widget">' . _("Statistics") . '</em>'
+            : Horde::widget(Chora::url('stats', $where), _("Statistics"),
+                            'widget', '', '', _("_Statistics"));
 
         return _("View:") . ' ' . implode(' | ', $views);
     }
 
+    /**
+     */
     function formatLogMessage($log)
     {
         global $conf;
@@ -487,4 +534,109 @@ class Chora {
         return $log;
     }
 
+    /**
+     * Return a list of tags for a given log entry.
+     *
+     * @since Chora 2.1
+     *
+     * @param VC_Log_* $lg   The VC_Log object.
+     * @param string $where  The filename.
+     *
+     * @return array  An array of linked tags.
+     */
+    function getTags($lg, $where)
+    {
+        $tags = array();
+        foreach ($lg->querySymbolicBranches() as $symb => $bra) {
+            $tags[] = '<a href="' . Chora::url('', $where, array('onb' => $bra)) . '">'. htmlspecialchars($symb) . '</a>';
+        }
+        if ($lg->tags) {
+            foreach ($lg->tags as $tag) {
+            $tags[] = htmlspecialchars($tag);
+            }
+        }
+        return $tags;
+    }
+
+    /**
+     * Return branch information for a given revision.
+     *
+     * @since Chora 2.1
+     *
+     * @param VC_File $fl  The VC_File object.
+     * @param string $rev  The filename.
+     *
+     * @return array  An 2-member array - branch name and branch revision.
+     */
+    function getBranch($fl, $rev)
+    {
+        $branchName = '';
+        $branchRev = VC_Revision::strip($rev, 1);
+        if (isset($fl->branches[$rev])) {
+            $branchName = $fl->branches[$rev];
+        } elseif (isset($fl->branches[$branchRev])) {
+            $branchName = $fl->branches[$branchRev];
+        }
+        return array($branchName, $branchRev);
+    }
+
+    /**
+     * Create a range of revisions between two revision numbers.
+     *
+     * @since Chora 2.1
+     *
+     * @param string $r1  The initial revision.
+     * @param string $r2  The ending revision.
+     *
+     * @return array  The revision range, or empty if there is no straight
+     *                line path between the revisions.
+     */
+    function getRevisionRange($r1, $r2)
+    {
+        if (VC_Revision::cmp($r1, $r2) == 1) {
+            $curr = VC_Revision::prev($r1);
+            $stop = VC_Revision::prev($r2);
+            $flip = true;
+        } else {
+            $curr = $r2;
+            $stop = $r1;
+            $flip = false;
+        }
+
+        $ret_array = array();
+
+        do {
+            $ret_array[] = $curr;
+            $curr = VC_Revision::prev($curr);
+            if ($curr == $stop) {
+                return ($flip) ? array_reverse($ret_array) : $ret_array;
+            }
+        } while (VC_Revision::cmp($curr, $stop) != -1);
+
+        return array();
+    }
+
+    /**
+     * Return formatted date information.
+     *
+     * @since Chora 2.1
+     *
+     * @param integer $date  Number of seconds since epoch we wish to display.
+     *
+     * @return string  The date formatted pursuant to Horde prefs.
+     */
+    function formatDate($date)
+    {
+        static $format;
+
+        if (!isset($format)) {
+            $format = $GLOBALS['prefs']->getValue('date_format') .
+                ($GLOBALS['prefs']->getValue('twenty_four')
+                 ? ' %H:%M'
+                 : ' %I:%M %p');
+        }
+
+        return strftime($format, $date);
+    }
+
 }
diff --git a/lib/api.php b/lib/api.php
new file mode 100644
index 0000000..42cc2a2
--- /dev/null
+++ b/lib/api.php
@@ -0,0 +1,40 @@
+<?php
+/**
+ * Chora external API interface.
+ *
+ * $Horde: chora/lib/api.php,v 1.1.2.1 2008/10/09 17:40:05 jan Exp $
+ *
+ * This file defines Chora's external API interface. Other applications can
+ * interact with Chora through this API.
+ *
+ * @package Chora
+ */
+ at define('CHORA_BASE', dirname(__FILE__) . "/..");
+
+$_services['perms'] = array(
+    'args' => array(),
+    'type' => '{urn:horde}stringArray'
+);
+
+function _chora_perms()
+{
+    static $perms = array();
+    if (!empty($perms)) {
+        return $perms;
+    }
+
+    @define('CHORA_BASE', dirname(__FILE__) . '/..');
+    require_once CHORA_BASE . '/config/sourceroots.php';
+
+    $perms['tree']['chora']['sourceroots'] = false;
+    $perms['title']['chora:sourceroots'] = _("Repositories");
+
+    // Run through every source repository
+    foreach ($sourceroots as $sourceroot => $srconfig) {
+        $perms['tree']['chora']['sourceroots'][$sourceroot] = false;
+        $perms['title']['chora:sourceroots:' . $sourceroot] = $srconfig['name'];
+    }
+
+    return $perms;
+}
+
diff --git a/lib/base.php b/lib/base.php
index d59a3cb..7e3e840 100644
--- a/lib/base.php
+++ b/lib/base.php
@@ -2,7 +2,7 @@
 /**
  * Chora base inclusion file.
  *
- * $Horde: chora/lib/base.php,v 1.101.10.1 2007/02/23 03:58:53 selsky Exp $
+ * $Horde: chora/lib/base.php,v 1.101.10.2 2008/10/09 17:40:05 jan Exp $
  *
  * This file brings in all of the dependencies that every Chora script
  * will need, and sets up objects that all scripts use.
@@ -11,7 +11,7 @@
 // Check for a prior definition of HORDE_BASE (perhaps by an
 // auto_prepend_file definition for site customization).
 if (!defined('HORDE_BASE')) {
-    @define('HORDE_BASE', dirname(__FILE__) . '/../..');
+    define('HORDE_BASE', dirname(__FILE__) . '/../..');
 }
 
 // Load the Horde Framework core, and set up inclusion paths.
@@ -21,39 +21,41 @@ require_once HORDE_BASE . '/lib/core.php';
 $registry = &Registry::singleton();
 if (is_a(($pushed = $registry->pushApp('chora', !defined('AUTH_HANDLER'))), 'PEAR_Error')) {
     if ($pushed->getCode() == 'permission_denied') {
-        Horde::authenticationFailureRedirect(); 
+        Horde::authenticationFailureRedirect();
     }
     Horde::fatal($pushed, __FILE__, __LINE__, false);
 }
 $conf = &$GLOBALS['conf'];
- at define('CHORA_TEMPLATES', $registry->get('templates'));
+define('CHORA_TEMPLATES', $registry->get('templates'));
 
 // Notification system.
 $notification = &Notification::singleton();
 $notification->attach('status');
 
-// Cache
+// Cache.
 require_once 'Horde/Cache.php';
 $cache = &Horde_Cache::singleton($conf['cache']['driver'], Horde::getDriverConfig('cache', $conf['cache']['driver']));
 
 // Find the base file path of Chora.
- at define('CHORA_BASE', dirname(__FILE__) . '/..');
+if (!defined('CHORA_BASE')) {
+    define('CHORA_BASE', dirname(__FILE__) . '/..');
+}
 
 // Horde base libraries.
 require_once 'Horde/Text.php';
 require_once 'Horde/Help.php';
 
 // Chora libraries and config.
-require_once CHORA_BASE . '/config/sourceroots.php';
+if (is_callable(array('Horde', 'loadConfiguration'))) {
+    $sourceroots = Horde::loadConfiguration('sourceroots.php', 'sourceroots');
+} else {
+    require_once CHORA_BASE . '/config/sourceroots.php';
+}
 require_once CHORA_BASE . '/lib/Chora.php';
 require_once 'Horde/VC.php';
 
 // Initialize objects, path, etc.
-Chora::init();
-
-if (Chora::isRestricted($where)) {
-    Chora::fatal("$where: Forbidden by server configuration");
-}
+Chora::initialize();
 
-/* Start compression, if requested. */
+// Start compression, if requested.
 Horde::compressOutput();
diff --git a/lib/version.php b/lib/version.php
index 8bf5459..9077d70 100644
--- a/lib/version.php
+++ b/lib/version.php
@@ -1 +1 @@
-<?php define('CHORA_VERSION', 'H3 (2.0.2)') ?>
+<?php define('CHORA_VERSION', 'H3 (2.1)') ?>
diff --git a/locale/cs_CZ/LC_MESSAGES/chora.mo b/locale/cs_CZ/LC_MESSAGES/chora.mo
index 22794c1..2bebf51 100644
Binary files a/locale/cs_CZ/LC_MESSAGES/chora.mo and b/locale/cs_CZ/LC_MESSAGES/chora.mo differ
diff --git a/locale/da_DK/LC_MESSAGES/chora.mo b/locale/da_DK/LC_MESSAGES/chora.mo
index 61d71b0..fd499fd 100644
Binary files a/locale/da_DK/LC_MESSAGES/chora.mo and b/locale/da_DK/LC_MESSAGES/chora.mo differ
diff --git a/locale/de_DE/LC_MESSAGES/chora.mo b/locale/de_DE/LC_MESSAGES/chora.mo
index 22656fe..c5d2ff1 100644
Binary files a/locale/de_DE/LC_MESSAGES/chora.mo and b/locale/de_DE/LC_MESSAGES/chora.mo differ
diff --git a/locale/de_DE/help.xml b/locale/de_DE/help.xml
index fd4fec8..bdd2e04 100644
--- a/locale/de_DE/help.xml
+++ b/locale/de_DE/help.xml
@@ -2,36 +2,36 @@
 <help>
 
   <entry id="Overview" md5="6c904e7e17dea0fc75802e2c1a534b03" state="uptodate">
-    <title>Chora Überblick</title>
+    <title>Chora Überblick</title>
     <para>
       Bei Chora handelt es sich um ein Werkzeug zum Betrachten von
       Repositories, die durch Systeme zur Versionskontrolle verwaltet
-      werden. Es stellt verschiedene Ansichten für Verzeichnisübersichten, die
+      werden. Es stellt verschiedene Ansichten für Verzeichnisübersichten, die
       Log-Geschichte, Unterschiede zwischen Versionen, grafische
-      Branchdarstellungen und kommentierte Dateien zur Verfügung.
+      Branchdarstellungen und kommentierte Dateien zur Verfügung.
     </para>
   </entry>
 
   <entry id="chora-button" md5="23ab7e475dc04b66c91b98f6b0f1d743" state="uptodate">
-    <title>Menü: Liste</title>
+    <title>Menü: Liste</title>
     <para>
       Mit dem Liste-Symbol gelangen Sie zur Hauptansicht
-      (Verzeichnisübersicht) des Standard-Repositories zurück.
+      (Verzeichnisübersicht) des Standard-Repositories zurück.
     </para>
   </entry>
 
   <entry id="options-button" md5="51f095602bb3e62703e72e7fe7fef996" state="uptodate">
-    <title>Menü: Einstellungen</title>
+    <title>Menü: Einstellungen</title>
     <para>
-      Über das Einstellungen-Symbol gelangt man zu den persönlichen
-      Einstellungen, die festlegen, wie sich Chora verhält.
+      Über das Einstellungen-Symbol gelangt man zu den persönlichen
+      Einstellungen, die festlegen, wie sich Chora verhält.
     </para>
   </entry>
 
   <entry id="help-button" md5="11b68a5e1f6cb8f5c2a885c2d65db322" state="uptodate">
-    <title>Menü: Hilfe</title>
+    <title>Menü: Hilfe</title>
     <para>
-      Das Hilfe-Symbol öffnet diese Hilfe.
+      Das Hilfe-Symbol öffnet diese Hilfe.
     </para>
   </entry>
 
@@ -47,7 +47,7 @@
     <title>Kommentierte Ansicht</title>
     <para>
       Die Kommentierte Ansicht zeigt den Inhalt einer Datei zusammen mit den
-      Namen der Autoren, die für die einzelnen Abschnitte verantwortlich sind.
+      Namen der Autoren, die für die einzelnen Abschnitte verantwortlich sind.
     </para>
   </entry>
 
@@ -64,7 +64,7 @@
     <title>Log-Ansicht</title>
     <para>
       Die Log-Ansicht zeigt die Geschichte einer Datei, indem alle
-      Log-Einträge aufgelistet werden, wie sie beim Einchecken der Versionen
+      Log-Einträge aufgelistet werden, wie sie beim Einchecken der Versionen
       angegeben wurden.
     </para>
   </entry>
@@ -73,22 +73,22 @@
     <title>Ansicht der Unterschiede</title>
     <para>
       Die Ansicht der Unterschiede zeigt die Unterschiede zwischen zwei
-      Versionen einer Datei an. So lässt sich leicht erkennen, wie eine Datei
-      verändert wurde. Es stehen verschiedene Anzeigeformate zur Verfügung.
+      Versionen einer Datei an. So lässt sich leicht erkennen, wie eine Datei
+      verändert wurde. Es stehen verschiedene Anzeigeformate zur Verfügung.
     </para>
   </entry>
 
   <entry id="show-deleted-files" md5="96c17a1efe41cd0e1de5c4f37d1e68f2" state="uptodate">
-    <title>Gelöschte Dateien anzeigen/verstecken</title>
+    <title>Gelöschte Dateien anzeigen/verstecken</title>
     <para>
-      Sie können auswählen, ob gelöschte Dateien (aus dem Attic) in der
+      Sie können auswählen, ob gelöschte Dateien (aus dem Attic) in der
       Verzeichnisansicht angezeigt oder versteckt werden sollen. Wenn Sie sich
-      gelöschte Dateien anzeigen lassen, werden die entsprechenden Zeilen in
+      gelöschte Dateien anzeigen lassen, werden die entsprechenden Zeilen in
       einer anderen Darstellung und mit einem <i>Papierkorb</i>-Symbol
       angezeigt.
     </para>
     <para>
-      Standardmäßig werden gelöschte Dateien nicht angezeigt.
+      Standardmäßig werden gelöschte Dateien nicht angezeigt.
     </para>
   </entry>
 
diff --git a/locale/es_ES/LC_MESSAGES/chora.mo b/locale/es_ES/LC_MESSAGES/chora.mo
index 38f66d5..736761e 100644
Binary files a/locale/es_ES/LC_MESSAGES/chora.mo and b/locale/es_ES/LC_MESSAGES/chora.mo differ
diff --git a/locale/es_ES/help.xml b/locale/es_ES/help.xml
index 8427eca..504b1b4 100644
--- a/locale/es_ES/help.xml
+++ b/locale/es_ES/help.xml
@@ -1,241 +1,54 @@
 <?xml version="1.0"?>
-<!-- $Horde: chora/locale/es_ES/help.xml,v 1.3 2004/12/18 16:01:17 jan Exp $ -->
+<!-- $Horde: chora/locale/es_ES/help.xml,v 1.3.2.2 2008/10/09 17:40:07 jan Exp $ -->
 <help>
-  <entry id="Overview" md5="45e4ecb52bfdcdda3d89ae65e16a99a8" state="changed">
-    <title>Introducci&#xF3;n a Chora</title>
-    <heading>Introducci&#xF3;n</heading>
-    <para>
-    Chora es una herramienta para examinar depositos de c&#xF3;digo gestionados mediante el sistema de control de c&#xF3;digo CVS. Pretende ofrecer un alto grado de integraci&#xF3;n con CVS y el resto de las herramientas basadas en web de Horde. Ofrece vista de directorio (archivos), historial del registro, diferencias entre versiones, vista ramificada y anotaciones.
-    </para>
-<!-- English entry:
-<entry id="Overview">
-    <title>Chora Overview</title>
-    <para>
-      Chora is a tool for viewing code repositories that are managed using
-      source control systems. It provides a directory (file) view, a log
-      history view, difference views between versions, a visual branch view,
-      and an annotated view.
-    </para>
-  </entry>--><!-- English entry:
-<entry id="Overview">
-    <title>Chora Overview</title>
-    <para>
-      Chora is a tool for viewing code repositories that are managed using
-      source control systems. It provides a directory (file) view, a log
-      history view, difference views between versions, a visual branch view,
-      and an annotated view.
-    </para>
-  </entry>--></entry>
-  <entry id="chora-button" md5="e52195deacd47a4a6172b8150ea30dc4" state="changed">
-    <title>Botones del men&#xFA;: Examinar</title>
-    <heading>Examinar</heading>
-    <para>
-      El bot&#xF3;n Examinar le devuelve a la visualizaci&#xF3;n principal de archivos de Chora del dep&#xF3;sito por omisi&#xF3;n.
-    </para>
-<!-- English entry:
-<entry id="chora-button">
-    <title>Menu Buttons: Browse</title>
-    <para>
-      The Browse button takes you back to the main Chora files view for the
-      default repository.
-    </para>
-  </entry>--><!-- English entry:
-<entry id="chora-button">
-    <title>Menu Buttons: Browse</title>
-    <para>
-      The Browse button takes you back to the main Chora files view for the
-      default repository.
-    </para>
-  </entry>--></entry>
-  <entry id="options-button" md5="66b35eace503e7591c0c548830f9bc4d" state="changed">
-    <title>Botones del men&#xFA;: Opciones</title>
-    <heading>Opciones</heading>
-    <para>
-    El bot&#xF3;n Opciones le permite configurar varias preferencias personales para controlar el comportamiento de Chora.
-    </para>
-<!-- English entry:
-<entry id="options-button">
-    <title>Menu Buttons: Options</title>
-    <para>
-      The Options button lets you set some personal preferences to control the
-      way Chora behaves.
-    </para>
-  </entry>--><!-- English entry:
-<entry id="options-button">
-    <title>Menu Buttons: Options</title>
-    <para>
-      The Options button lets you set some personal preferences to control the
-      way Chora behaves.
-    </para>
-  </entry>--></entry>
-  <entry id="help-button" md5="407323d4c4a4a20eda2df95607087e2c" state="changed">
-    <title>Botones del men&#xFA;: Ayuda</title>
-    <heading>Ayuda</heading>
-    <para>
-      El bot&#xF3;n Ayuda le muestra esta pantalla de ayuda.
-    </para>
-<!-- English entry:
-<entry id="help-button">
-    <title>Menu Buttons: Help</title>
-    <para>
-      The Help button brings up this help display.
-    </para>
-  </entry>--><!-- English entry:
-<entry id="help-button">
-    <title>Menu Buttons: Help</title>
-    <para>
-      The Help button brings up this help display.
-    </para>
-  </entry>--></entry>
-  <entry id="view-checkout" md5="c15ba2379a7ceba3e61fb0733c97b788" state="changed">
-    <title>Vista de inspecci&#xF3;n CVS</title>
-    <heading>Vista de inspecci&#xF3;n CVS</heading>
-    <para>
-      La vista de inspecci&#xF3;n CVS muestra el contenido de un archivo en un punto de revisi&#xF3;n, junto con las entradas de registro de dicha revisi&#xF3;n.
-    </para>
-<!-- English entry:
-<entry id="view-checkout">
-    <title>Checkout View</title>
-    <para>
-      The Checkout View shows a file contents at a revision point, along with
-      the log entry for that revision.
-    </para>
-  </entry>--><!-- English entry:
-<entry id="view-checkout">
-    <title>Checkout View</title>
-    <para>
-      The Checkout View shows a file contents at a revision point, along with
-      the log entry for that revision.
-    </para>
-  </entry>--></entry>
-  <entry id="view-annotate" md5="b13f3d6bac6e4dddeec18ce03dd099cf" state="changed">
-    <title>Vista anotada</title>
-    <heading>Vista anotada</heading>
-    <para>
-    La vista anotada muestra un archivo con las anotaciones de qu&#xE9; autores son responsables de qu&#xE9; partes del contenido del archivo (y por lo tanto se le conoce a menudo como la vista "culpable")
-    </para>
-<!-- English entry:
-<entry id="view-annotate">
-    <title>Annotate View</title>
-    <para>
-      The Annotated View shows a file annotated with which authors are
-      responsible for which portions of a file's contents (and is hence often
-      called the "blame" view).
-    </para>
-  </entry>--><!-- English entry:
-<entry id="view-annotate">
-    <title>Annotate View</title>
-    <para>
-      The Annotated View shows a file annotated with which authors are
-      responsible for which portions of a file's contents (and is hence often
-      called the "blame" view).
-    </para>
-  </entry>--></entry>
-  <entry id="view-branch" md5="3a5e4bdacbac2ebf33ed29a101accac4" state="changed">
-    <title>Vista ramificada</title>
-    <heading>Vista ramificada (Hist&#xF3;rica)</heading>
-    <para>
-    La vista ramificada muestra la historia de versiones de un archivo, distribuida en ramas. &#xC9;sto permite comprobar, de forma visual, d&#xF3;nde se crearon los puntos de ramificaci&#xF3;n del &#xE1;rbol.
-    </para>
-<!-- English entry:
-<entry id="view-branch">
-    <title>Branch View</title>
-    <para>
-      The Branch View shows a file's version history, layed out by
-      branches. This allows you to visually see where branch points were
-      created in the tree.
-    </para>
-  </entry>--><!-- English entry:
-<entry id="view-branch">
-    <title>Branch View</title>
-    <para>
-      The Branch View shows a file's version history, layed out by
-      branches. This allows you to visually see where branch points were
-      created in the tree.
-    </para>
-  </entry>--></entry>
-  <entry id="view-log" md5="39c6e0be53135cb52e60b8694ba16428" state="changed">
-    <title>Vista de registro</title>
-    <heading>Vista de registro</heading>
-    <para>
-    La vista de registro muestra la historia de un archivo mediante los mensajes de registro de comprobaci&#xF3;n. &#xC9;sto permite ver una historia de los cambios desde el punto de vista de los mensajes de registro que se introdujeron en el momento de la revisi&#xF3;n.
-    </para>
-<!-- English entry:
-<entry id="view-log">
-    <title>Log View</title>
-    <para>
-      The Log View shows a file's history via the checkin log messages. This
-      allows you to see a history of changes from the point of view log
-      messages entered at checkin time.
-    </para>
-  </entry>--><!-- English entry:
-<entry id="view-log">
-    <title>Log View</title>
-    <para>
-      The Log View shows a file's history via the checkin log messages. This
-      allows you to see a history of changes from the point of view log
-      messages entered at checkin time.
-    </para>
-  </entry>--></entry>
-  <entry id="view-diff" md5="662b414167a7427ca87b6063ac9194be" state="changed">
-    <title>Vista de diferencias</title>
-    <heading>Vista de diferencias</heading>
-    <para>
-    La vista de diferencias muestra las diferencias entre dos revisiones distintas de un archivo. &#xC9;sto le permite ver f&#xE1;cilmente los cambios de un archivo. Puede ver las diferencias en varios formatos, incluyendo entre otros un listado coloreado conjunto de las dos revisiones, o un listado de diferencias mediante comandos del editor ed.
-    </para>
-<!-- English entry:
-<entry id="view-diff">
-    <title>Differences View</title>
-    <para>
-      The Differnences View shows the differences between two different
-      revisions of a file. This allows you to easily see how a file has
-      changed.  You can view the differences in various formats, including
-      among others a color coded side by side listing of the two revisions, or
-      in terms of ed editor commands.
-    </para>
-  </entry>--><!-- English entry:
-<entry id="view-diff">
-    <title>Differences View</title>
-    <para>
-      The Differnences View shows the differences between two different
-      revisions of a file. This allows you to easily see how a file has
-      changed.  You can view the differences in various formats, including
-      among others a color coded side by side listing of the two revisions, or
-      in terms of ed editor commands.
-    </para>
-  </entry>--></entry>
-  <entry id="show-deleted-files" md5="6b4878d4170900daebb8ccb308a34327" state="changed">
-    <title>Mostrar/Ocultar archivos eliminados</title>
-    <heading>Mostrar/Ocultar archivos eliminados</heading>
-    <para>
-    Puede optar por ver los archivos eliminados (de Attic) o por no mostrarlos (ocultarlos) en la vista de archivos cvs. Si prefiere mostrarlos, se ver&#xE1;n atenuados y con un icono de <i>papelera</i>.
-    </para>
-    <para>
-    La opci&#xF3;n por omisi&#xF3;n es no mostrarlos.
-    </para>
-<!-- English entry:
-<entry id="show-deleted-files">
-    <title>Show/Hide Deleted Files</title>
-    <para>
-      You can choose to either view deleted files (from the Attic) or to not
-      show (hide) deleted files in the file view.  If you choose to show
-      deleted files, they will show up dimmed and with a <i>trash can</i>
-      icon.
-    </para>
-    <para>
-      The default is not to show deleted files.
-    </para>
-  </entry>--><!-- English entry:
-<entry id="show-deleted-files">
-    <title>Show/Hide Deleted Files</title>
-    <para>
-      You can choose to either view deleted files (from the Attic) or to not
-      show (hide) deleted files in the file view.  If you choose to show
-      deleted files, they will show up dimmed and with a <i>trash can</i>
-      icon.
-    </para>
-    <para>
-      The default is not to show deleted files.
-    </para>
-  </entry>--></entry>
+	<entry id="Overview">
+		<title>Introducci&#xF3;n a Chora</title>
+		<para>Chora es una herramienta para examinar dep&#xF3;sitos de c&#xF3;digo gestionados mediante sistemas de control de c&#xF3;digo. Ofrece vista de directorio (archivos), historial del registro, diferencias entre versiones, vista ramificada gr&#xE1;fica y vista de anotaciones.</para>
+	</entry>
+  
+	<entry id="chora-button">
+		<title>Botones del men&#xFA;: Examinar</title>
+		<para>El bot&#xF3;n Examinar le devuelve a la visualizaci&#xF3;n principal de archivos de Chora del dep&#xF3;sito por omisi&#xF3;n.</para>
+	</entry>
+
+	<entry id="options-button">
+		<title>Botones del men&#xFA;: Opciones</title>
+		<para>El bot&#xF3;n Opciones le permite configurar varias preferencias personales para controlar el comportamiento de Chora.</para>
+	</entry>
+
+	<entry id="help-button">
+		<title>Botones del men&#xFA;: Ayuda</title>
+		<para>El bot&#xF3;n Ayuda le muestra esta pantalla de ayuda.</para>
+	</entry>
+	
+	<entry id="view-checkout">
+		<title>Vista de inspecci&#xF3;n</title>
+		<para>La vista de inspecci&#xF3;n muestra el contenido de un archivo en un punto de revisi&#xF3;n, junto con las entradas de registro de dicha revisi&#xF3;n.</para>
+	</entry>
+
+	<entry id="view-annotate">
+		<title>Vista anotada</title>
+		<para>La vista anotada muestra un archivo con las anotaciones de qu&#xE9; autores son responsables de qu&#xE9; partes del contenido del archivo (y por lo tanto se le conoce a menudo como la vista de la "culpa")</para>
+	</entry>
+
+	<entry id="view-branch">
+		<title>Vista ramificada</title>
+		<para>La vista ramificada muestra la historia de versiones de un archivo, distribuida en ramas. &#xC9;sto permite comprobar, de forma visual, d&#xF3;nde se crearon los puntos de ramificaci&#xF3;n del &#xE1;rbol.</para>
+	</entry>
+
+	<entry id="view-log">
+		<title>Vista de registro</title>
+		<para>La vista de registro muestra la historia de un archivo mediante los mensajes de registro de comprobaci&#xF3;n. &#xC9;sto permite ver una historia de los cambios desde el punto de vista de los mensajes de registro que se introdujeron en el momento de la revisi&#xF3;n.</para>
+	</entry>
+
+	<entry id="view-diff">
+		<title>Vista de diferencias</title>
+		<para>La vista de diferencias muestra las diferencias entre dos revisiones distintas de un archivo. &#xC9;sto le permite ver f&#xE1;cilmente los cambios de un archivo. Puede ver las diferencias en varios formatos, incluyendo entre otros un listado coloreado conjunto de las dos revisiones, o un listado de diferencias mediante comandos del editor ed.</para>
+	</entry>
+
+	<entry id="show-deleted-files">
+		<title>Mostrar/Ocultar archivos eliminados</title>
+		<para>Puede optar por ver los archivos eliminados (desde Attic) o por no mostrarlos (ocultarlos) en la vista de archivos. Si prefiere mostrarlos, se ver&#xE1;n atenuados y con un icono de <i>papelera</i>.</para>
+		<para>La opci&#xF3;n por omisi&#xF3;n es no mostrarlos.</para>
+	</entry>
 </help>
diff --git a/locale/fi_FI/LC_MESSAGES/chora.mo b/locale/fi_FI/LC_MESSAGES/chora.mo
index 52280f0..15f3890 100644
Binary files a/locale/fi_FI/LC_MESSAGES/chora.mo and b/locale/fi_FI/LC_MESSAGES/chora.mo differ
diff --git a/locale/fi_FI/help.xml b/locale/fi_FI/help.xml
index 37fc42e..5033d2b 100644
--- a/locale/fi_FI/help.xml
+++ b/locale/fi_FI/help.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0"?>
-<!-- $Horde: chora/locale/fi_FI/help.xml,v 1.1.2.2 2006/02/20 11:20:35 jan Exp $ -->
+<!-- $Horde: chora/locale/fi_FI/help.xml,v 1.1.2.4 2008/10/09 17:40:14 jan Exp $ -->
 <help>
   <entry id="Overview" md5="6c904e7e17dea0fc75802e2c1a534b03" state="uptodate">
     <title>Chora yleiskuva</title>
diff --git a/locale/fr_FR/LC_MESSAGES/chora.mo b/locale/fr_FR/LC_MESSAGES/chora.mo
index 54ed683..b068c45 100644
Binary files a/locale/fr_FR/LC_MESSAGES/chora.mo and b/locale/fr_FR/LC_MESSAGES/chora.mo differ
diff --git a/locale/it_IT/LC_MESSAGES/chora.mo b/locale/it_IT/LC_MESSAGES/chora.mo
new file mode 100644
index 0000000..5337cc8
Binary files /dev/null and b/locale/it_IT/LC_MESSAGES/chora.mo differ
diff --git a/locale/nb_NO/LC_MESSAGES/chora.mo b/locale/nb_NO/LC_MESSAGES/chora.mo
index 7ebb8ef..7445a4d 100644
Binary files a/locale/nb_NO/LC_MESSAGES/chora.mo and b/locale/nb_NO/LC_MESSAGES/chora.mo differ
diff --git a/locale/nl_NL/LC_MESSAGES/chora.mo b/locale/nl_NL/LC_MESSAGES/chora.mo
index 7a003b1..d7d41bf 100644
Binary files a/locale/nl_NL/LC_MESSAGES/chora.mo and b/locale/nl_NL/LC_MESSAGES/chora.mo differ
diff --git a/locale/pl_PL/LC_MESSAGES/chora.mo b/locale/pl_PL/LC_MESSAGES/chora.mo
index af3e92f..f72cde8 100644
Binary files a/locale/pl_PL/LC_MESSAGES/chora.mo and b/locale/pl_PL/LC_MESSAGES/chora.mo differ
diff --git a/locale/pt_BR/LC_MESSAGES/chora.mo b/locale/pt_BR/LC_MESSAGES/chora.mo
index eb8154c..c722fe7 100644
Binary files a/locale/pt_BR/LC_MESSAGES/chora.mo and b/locale/pt_BR/LC_MESSAGES/chora.mo differ
diff --git a/locale/ro_RO/LC_MESSAGES/chora.mo b/locale/ro_RO/LC_MESSAGES/chora.mo
index aa115ec..7fa7476 100644
Binary files a/locale/ro_RO/LC_MESSAGES/chora.mo and b/locale/ro_RO/LC_MESSAGES/chora.mo differ
diff --git a/locale/ru_RU/LC_MESSAGES/chora.mo b/locale/ru_RU/LC_MESSAGES/chora.mo
index be04998..b18da60 100644
Binary files a/locale/ru_RU/LC_MESSAGES/chora.mo and b/locale/ru_RU/LC_MESSAGES/chora.mo differ
diff --git a/locale/sk_SK/LC_MESSAGES/chora.mo b/locale/sk_SK/LC_MESSAGES/chora.mo
index 77f8798..9815d9a 100644
Binary files a/locale/sk_SK/LC_MESSAGES/chora.mo and b/locale/sk_SK/LC_MESSAGES/chora.mo differ
diff --git a/locale/sv_SE/LC_MESSAGES/chora.mo b/locale/sv_SE/LC_MESSAGES/chora.mo
index de1fba8..9b8cad4 100644
Binary files a/locale/sv_SE/LC_MESSAGES/chora.mo and b/locale/sv_SE/LC_MESSAGES/chora.mo differ
diff --git a/locale/zh_CN/LC_MESSAGES/chora.mo b/locale/zh_CN/LC_MESSAGES/chora.mo
index 421b52d..05c6960 100644
Binary files a/locale/zh_CN/LC_MESSAGES/chora.mo and b/locale/zh_CN/LC_MESSAGES/chora.mo differ
diff --git a/locale/zh_TW/LC_MESSAGES/chora.mo b/locale/zh_TW/LC_MESSAGES/chora.mo
index 564acfb..20189d4 100644
Binary files a/locale/zh_TW/LC_MESSAGES/chora.mo and b/locale/zh_TW/LC_MESSAGES/chora.mo differ
diff --git a/patchsets.php b/patchsets.php
index c44d388..bbb7a8f 100644
--- a/patchsets.php
+++ b/patchsets.php
@@ -1,12 +1,15 @@
 <?php
 /**
- * $Horde: chora/patchsets.php,v 1.18.8.6 2007/02/23 03:58:53 selsky Exp $
+ * $Horde: chora/patchsets.php,v 1.18.8.8 2009/01/06 15:22:34 jan Exp $
  *
- * Copyright 1999-2007 Anil Madhavapeddy <anil at recoil.org>
- * Copyright 1999-2007 Charles Hagenbuch <chuck at horde.org>
+ * Copyright 1999-2009 The Horde Project (http://www.horde.org/)
  *
- * See the enclosed file COPYING for license information (GPL).  If you
+ * See the enclosed file COPYING for license information (GPL). If you
  * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author  Anil Madhavapeddy <anil at recoil.org>
+ * @author  Chuck Hagenbuch <chuck at horde.org>
+ * @package Chora
  */
 
 @define('CHORA_BASE', dirname(__FILE__));
@@ -19,28 +22,31 @@ if (empty($conf['paths']['cvsps']) && !is_a($VC, 'VC_svn')) {
 }
 
 if (@is_dir($fullname)) {
-    Chora::fatal('No patchsets for directories yet.');
+    Chora::fatal('403 Forbidden', 'No patchsets for directories yet.');
 }
 
 if (!$VC->isFile($fullname)) {
-    Chora::fatal("$where: no such file or directory");
+    Chora::fatal('404 Not Found', "$where: no such file or directory");
 }
 
 $ps = $VC->getPatchsetObject($where, $cache);
 Chora::checkError($ps);
 
-$title = sprintf(_("Patchsets for %s"), Text::htmlallspaces($where));
-$upwhere = preg_replace('|[^/]+$|', '', $where);
+$title = sprintf(_("Patchsets for %s"), $where);
 $extraLink = Chora::getFileViews();
 
+Horde::addScriptFile('prototype.js', 'chora', true);
+Horde::addScriptFile('tables.js', 'chora', true);
+Horde::addScriptFile('QuickFinder.js', 'chora', true);
 require CHORA_TEMPLATES . '/common-header.inc';
 require CHORA_TEMPLATES . '/menu.inc';
 require CHORA_TEMPLATES . '/headerbar.inc';
+require CHORA_TEMPLATES . '/patchsets/header.inc';
 
 $patchsets = $ps->_patchsets;
 krsort($patchsets);
 foreach ($patchsets as $id => $patchset) {
-    $commitDate = @strftime('%c', $patchset['date']);
+    $commitDate = Chora::formatTime($patchset['date']);
     $readableDate = Chora::readableTime($patchset['date'], true);
     $author = Chora::showAuthorName($patchset['author'], true);
     if (is_a($VC, 'VC_svn')) {
@@ -49,11 +55,11 @@ foreach ($patchsets as $id => $patchset) {
         $topDir = substr($where, 0, strpos($where, '/', 1));
 
         // Subversion supports patchset diffs natively.
-        $allDiffsLink = Horde::link(Chora::url('diff', $topDir, array('r1' => $id - 1, 'r2' => $id, 'ty' => 'u')),
-                                    _("Diff All Files")) . _("Diff All Files") . '</a>';
+        $patchset_link = Horde::link(Chora::url('diff', $topDir, array('r1' => $id - 1, 'r2' => $id, 't' => 'unified'))) .
+            $id . '</a>';
     } else {
         // Not supported in any other VC systems yet.
-        $allDiffsLink = '';
+        $patchset_link = $id;
     }
 
     $files = array();
@@ -61,19 +67,19 @@ foreach ($patchsets as $id => $patchset) {
     foreach ($patchset['members'] as $member) {
         $file = array();
         $mywhere = is_a($VC, 'VC_svn') ? $member['file'] : $dir . '/' . $member['file'];
-        $file['file'] = Horde::link(Chora::url('patchsets', $mywhere), $member['file']) . $member['file'] . '</a>';
+        $file['file'] = Horde::link(Chora::url('patchsets', $mywhere)) . htmlspecialchars($member['file']) . '</a>';
         if ($member['from'] == 'INITIAL') {
-            $file['from'] = '<i>' . _("New File") . '</i>';
+            $file['from'] = '<ins>' . _("New File") . '</ins>';
             $file['diff'] = '';
         } else {
-            $file['from'] = Horde::link(Chora::url('co', $mywhere, array('r' => $member['from'])), $member['from']) . $member['from'] . '</a>';
-            $file['diff'] = Horde::link(Chora::url('diff', $mywhere, array('r1' => $member['from'], 'r2' => $member['to'], 'ty' => 'u')), _("Diff")) . '(' . _("Diff") . ')';
+            $file['from'] = Horde::link(Chora::url('co', $mywhere, array('r' => $member['from']))) . htmlspecialchars($member['from']) . '</a>';
+            $file['diff'] = Horde::link(Chora::url('diff', $mywhere, array('r1' => $member['from'], 'r2' => $member['to'], 't' => 'unified'))) . ' ' . Horde::img('diff.png', _("Diff")) . '</a>';
         }
         if (substr($member['to'], -6) == '(DEAD)') {
-            $file['to'] = '<i>' . _("Deleted") . '</i>';
+            $file['to'] = '<del>' . _("Deleted") . '</del>';
             $file['diff'] = '';
         } else {
-            $file['to'] = Horde::link(Chora::url('co', $mywhere, array('r' => $member['to'])), $member['to']) . $member['to'] . '</a>';
+            $file['to'] = Horde::link(Chora::url('co', $mywhere, array('r' => $member['to']))) . htmlspecialchars($member['to']) . '</a>';
         }
 
         $files[] = $file;
@@ -83,4 +89,5 @@ foreach ($patchsets as $id => $patchset) {
     require CHORA_TEMPLATES . '/patchsets/ps.inc';
 }
 
+require CHORA_TEMPLATES . '/patchsets/footer.inc';
 require $registry->get('templates', 'horde') . '/common-footer.inc';
diff --git a/po/chora.pot b/po/chora.pot
index 804e54f..801b882 100644
--- a/po/chora.pot
+++ b/po/chora.pot
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: dev at lists.horde.org\n"
-"POT-Creation-Date: 2006-03-06 12:47+0100\n"
+"POT-Creation-Date: 2009-03-19 10:37+0100\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
 "Language-Team: LANGUAGE <LL at li.org>\n"
@@ -20,43 +20,54 @@ msgstr ""
 msgid "#"
 msgstr ""
 
-#: templates/patchsets/ps.inc:12 templates/log/rev.inc:26
+#: templates/log/header.inc:17
 #, php-format
-msgid "%s ago"
+msgid ""
+"%1$s shows diffs to the previous revision. If you select a revision by "
+"clicking its row, %1$s will show the differences to the selected row."
+msgstr ""
+
+#: co.php:84
+#, php-format
+msgid "%s Revision %s (%s ago)"
 msgstr ""
 
-#: templates/diff/hr/footer.inc:19
+#: templates/annotate/footer.inc:22
 #, php-format
-msgid "Added in v.%s"
+msgid "%s ago"
 msgstr ""
 
-#: templates/log/request.inc:57
+#: templates/diff/hr/header.inc:25
+msgid "Added"
+msgstr ""
+
+#: templates/log/header.inc:31
 msgid "All Branches"
 msgstr ""
 
-#: co.php:72 templates/log/rev.inc:8
+#: co.php:89
 msgid "Annotate"
 msgstr ""
 
-#: templates/directory/header.inc:18 templates/stats/stats.inc:3
-#: templates/annotate/header.inc:5
+#: templates/directory/header.inc:12 templates/patchsets/header.inc:15
+#: templates/stats/stats.inc:4 templates/annotate/header.inc:5
+#: templates/log/header.inc:49
 msgid "Author"
 msgstr ""
 
-#: templates/directory/back.inc:3
-msgid "Back"
+#: templates/checkout/checkout.inc:4 templates/diff/hr/header.inc:35
+msgid "Author:"
 msgstr ""
 
-#: templates/checkout/checkout.inc:27 templates/log/rev.inc:31
-msgid "Branch Point for:"
+#: templates/directory/back.inc:4
+msgid "Back"
 msgstr ""
 
-#: templates/checkout/checkout.inc:11 templates/patchsets/ps.inc:14
-#: templates/log/rev.inc:28
+#: templates/checkout/checkout.inc:5 templates/diff/hr/header.inc:37
 msgid "Branch:"
 msgstr ""
 
-#: lib/Chora.php:465
+#: lib/Chora.php:502 lib/Chora.php:503 lib/Chora.php:507 lib/Chora.php:508
 msgid "Branches"
 msgstr ""
 
@@ -64,49 +75,38 @@ msgstr ""
 msgid "Branching to"
 msgstr ""
 
-#: templates/checkout/checkout.inc:35
-#, php-format
-msgid "Changed since <b>%s</b>"
-msgstr ""
-
-#: templates/log/rev.inc:37
-#, php-format
-msgid "Changed since <strong>%s</strong>"
+#: lib/Chora.php:392
+msgid "Change repositories:"
 msgstr ""
 
-#: templates/history/rev.inc:8
+#: templates/history/rev.inc:7
 #, php-format
 msgid "Changed: %s"
 msgstr ""
 
-#: co.php:70
-#, php-format
-msgid "Checkout of %s (revision %s)"
+#: templates/checkout/checkout.inc:12
+msgid "Checkout"
 msgstr ""
 
-#: templates/cvsgraph/cvsgraph.inc:11
+#: templates/cvsgraph/cvsgraph.inc:5
 msgid ""
 "Click on the links in between revisions to get a diff between those "
 "revisions."
 msgstr ""
 
-#: templates/cvsgraph/cvsgraph.inc:10
+#: templates/cvsgraph/cvsgraph.inc:4
 msgid "Click on the revisions and branches to display the file."
 msgstr ""
 
-#: templates/log/rev.inc:42 templates/log/rev.inc:50
-msgid "Colored"
-msgstr ""
-
-#: templates/checkout/checkout.inc:19 templates/patchsets/ps.inc:17
-msgid "Commit Tags:"
+#: templates/diff/hr/header.inc:13
+msgid "Column"
 msgstr ""
 
-#: templates/stats/stats.inc:4
+#: templates/stats/stats.inc:5
 msgid "Commits"
 msgstr ""
 
-#: templates/log/request.inc:28
+#: templates/diff/hr/header.inc:12
 msgid "Context"
 msgstr ""
 
@@ -114,58 +114,54 @@ msgstr ""
 msgid "Customize tasks to run upon logging in to Chora."
 msgstr ""
 
-#: templates/directory/header.inc:23
+#: templates/directory/header.inc:15
 msgid "Da_te"
 msgstr ""
 
-#: templates/directory/header.inc:23
+#: templates/directory/header.inc:15 templates/patchsets/header.inc:14
+#: templates/log/header.inc:48
 msgid "Date"
 msgstr ""
 
-#: patchsets.php:73
-msgid "Deleted"
+#: templates/diff/hr/header.inc:36
+msgid "Date:"
 msgstr ""
 
-#: templates/directory/file.inc:5 templates/directory/file.inc:17
-msgid "Deleted File"
+#: patchsets.php:79
+msgid "Deleted"
 msgstr ""
 
-#: templates/log/rev.inc:13
-msgid "Deselect"
+#: templates/directory/file.inc:5 templates/directory/file.inc:28
+msgid "Deleted File"
 msgstr ""
 
-#: patchsets.php:70
+#: patchsets.php:76 templates/log/header.inc:16
 msgid "Diff"
 msgstr ""
 
-#: patchsets.php:53
-msgid "Diff All Files"
-msgstr ""
-
-#: diff.php:69
+#: diff.php:71
 #, php-format
 msgid "Diff for %s between version %s and %s"
 msgstr ""
 
-#: templates/log/rev.inc:49
-#, php-format
-msgid "Diffs to <strong>%s</strong>"
+#: templates/directory/dir.inc:4 templates/directory/dir.inc:6
+msgid "Directory"
 msgstr ""
 
-#: templates/log/rev.inc:41
-#, php-format
-msgid "Diffs to version %s"
+#: annotate.php:33 co.php:90
+msgid "Download"
 msgstr ""
 
-#: templates/directory/dir.inc:4 templates/directory/dir.inc:6
-msgid "Directory"
+#: templates/diff/hr/header.inc:9
+msgid "Download diff as: "
 msgstr ""
 
-#: annotate.php:30 co.php:73 templates/log/rev.inc:10
-msgid "Download"
+#: templates/checkout/checkout.inc:23
+#, php-format
+msgid "Download revision %s"
 msgstr ""
 
-#: templates/log/request.inc:30
+#: templates/diff/hr/header.inc:14
 msgid "Ed Script"
 msgstr ""
 
@@ -177,120 +173,106 @@ msgstr ""
 msgid "Error Encountered"
 msgstr ""
 
-#: templates/directory/file.inc:7 templates/directory/file.inc:19
-#: templates/directory/header.inc:8
+#: templates/directory/file.inc:7 templates/directory/file.inc:30
+#: templates/directory/header.inc:6
 msgid "File"
 msgstr ""
 
-#: templates/patchsets/ps.inc:21
-msgid "Files Changed:"
-msgstr ""
-
-#: templates/log/request.inc:44
-msgid "Get Diffs"
+#: templates/patchsets/header.inc:16
+msgid "Files"
 msgstr ""
 
-#: lib/Chora.php:468
-msgid "Graph"
+#: templates/diff/hr/header.inc:16
+msgid "Get Diff"
 msgstr ""
 
-#: cvsgraph.php:48
+#: cvsgraph.php:51
 #, php-format
 msgid "Graph for %s"
 msgstr ""
 
-#: browse.php:40
+#: browse.php:42
 msgid "Hide Deleted Files"
 msgstr ""
 
-#: browse.php:40
+#: browse.php:44
 msgid "Hide _Deleted Files"
 msgstr ""
 
-#: templates/log/request.inc:26
-msgid "Human Readable"
-msgstr ""
-
-#: templates/directory/header.inc:25
+#: templates/directory/header.inc:17
 msgid "Last Log"
 msgstr ""
 
-#: templates/diff/hr/header.inc:5
-#, php-format
-msgid "Last Log Message for rev %s:"
-msgstr ""
-
-#: templates/diff/hr/footer.inc:5
-msgid "Legend:"
-msgstr ""
-
-#: templates/annotate/header.inc:7
+#: templates/annotate/header.inc:8
 msgid "Line"
 msgstr ""
 
-#: templates/diff/hr/row.inc:3 templates/diff/hr/row.inc:7
+#: templates/diff/hr/row.inc:3 templates/diff/hr/row.inc:6
 #, php-format
 msgid "Line %s"
 msgstr ""
 
-#: templates/headerbar.inc:11
+#: templates/headerbar.inc:5
 msgid "Location:"
 msgstr ""
 
-#: templates/checkout/checkout.inc:42
-msgid "Log:"
+#: templates/checkout/checkout.inc:1 templates/patchsets/header.inc:17
+#: templates/diff/hr/header.inc:30 templates/log/header.inc:50
+msgid "Log Message"
 msgstr ""
 
 #: config/prefs.php.dist:10
 msgid "Login Tasks"
 msgstr ""
 
-#: lib/Chora.php:459
+#: lib/Chora.php:488 lib/Chora.php:489
 msgid "Logs"
 msgstr ""
 
-#: templates/log/rev.inc:43 templates/log/rev.inc:51
-msgid "Long"
+#: diff.php:44
+msgid "Malformed Query"
 msgstr ""
 
-#: diff.php:61
-msgid "Malformed Query"
+#: lib/Block/tree_menu.php:3
+msgid "Menu List"
+msgstr ""
+
+#: templates/diff/hr/header.inc:26
+msgid "Modified"
 msgstr ""
 
-#: patchsets.php:66
+#: patchsets.php:72
 msgid "New File"
 msgstr ""
 
-#: templates/diff/hr/nochange.inc:6
-msgid "No Viewable Change"
+#: templates/diff/hr/nochange.inc:3
+msgid "No Visible Changes"
 msgstr ""
 
-#: templates/log/rev.inc:44 templates/log/rev.inc:52
-msgid "NoWhitespaceChanges"
+#: templates/log/footer.inc:8
+#, php-format
+msgid ""
+"Only showing the 100 latest revisions. %sShow all revisions?</a> (may take a "
+"while)."
 msgstr ""
 
 #: config/prefs.php.dist:9
 msgid "Other Options"
 msgstr ""
 
-#: lib/Chora.php:368
-msgid "Other Repositories"
-msgstr ""
-
-#: templates/directory/back.inc:3
+#: templates/directory/back.inc:4
 msgid "Parent Directory"
 msgstr ""
 
-#: templates/patchsets/ps.inc:6
-#, php-format
-msgid "PatchSet %s</span> by %s"
+#: templates/patchsets/header.inc:13
+msgid "Patchset"
 msgstr ""
 
-#: lib/Chora.php:462
+#: lib/Chora.php:495 lib/Chora.php:496
 msgid "Patchsets"
 msgstr ""
 
-#: patchsets.php:32
+#: patchsets.php:35
 #, php-format
 msgid "Patchsets for %s"
 msgstr ""
@@ -299,78 +281,91 @@ msgstr ""
 msgid "Please contact"
 msgstr ""
 
-#: templates/diff/hr/footer.inc:11
-#, php-format
-msgid "Removed in v.%s"
+#: templates/annotate/header.inc:7
+msgid "Prev"
+msgstr ""
+
+#: templates/diff/hr/header.inc:27
+msgid "Removed"
 msgstr ""
 
-#: templates/log/request.inc:14
-msgid "Retrieve diffs between:"
+#: lib/api.php:30
+msgid "Repositories"
 msgstr ""
 
-#: templates/directory/header.inc:13 templates/annotate/header.inc:6
+#: templates/directory/header.inc:9 templates/annotate/header.inc:6
 msgid "Rev"
 msgstr ""
 
-#: co.php:77
-#, php-format
-msgid "Revision %s for file %s not found."
+#: templates/log/header.inc:47
+msgid "Revision"
 msgstr ""
 
-#: templates/checkout/checkout.inc:4
+#: browse.php:157
 #, php-format
-msgid "Revision <b>%s</b>, <i>%s</i> (%s ago) by %s"
+msgid "Revisions for %s"
+msgstr ""
+
+#: templates/patchsets/header.inc:4
+msgid "Search Patchsets:"
+msgstr ""
+
+#: templates/log/header.inc:4
+msgid "Search Revisions:"
 msgstr ""
 
-#: templates/log/rev.inc:15
-msgid "Select for Diff"
+#: templates/log/header.inc:29
+msgid "Show Branch:"
 msgstr ""
 
 #: browse.php:42
 msgid "Show Deleted Files"
 msgstr ""
 
-#: browse.php:42
+#: templates/log/header.inc:18
+msgid "Show Differences"
+msgstr ""
+
+#: browse.php:44
 msgid "Show _Deleted Files"
 msgstr ""
 
-#: templates/log/request.inc:29
-msgid "Side-by-Side"
+#: templates/log/rev.inc:4
+msgid "Show changes to the previous revision"
 msgstr ""
 
-#: templates/directory/header.inc:7 templates/directory/header.inc:12
-#: templates/directory/header.inc:17 templates/directory/header.inc:22
-msgid "Sort Order"
+#: templates/log/rev.inc:6
+msgid "Show changes to the selected revision"
 msgstr ""
 
-#: annotate.php:27
+#: templates/log/header.inc:13
 #, php-format
-msgid "Source Annotation of %s for version %s"
+msgid "Show diffs between %s and %s"
 msgstr ""
 
-#: history.php:160
+#: annotate.php:30
 #, php-format
-msgid "Source Branching View for %s"
+msgid "Source Annotation of %s (revision %s)"
 msgstr ""
 
-#: browse.php:32
+#: history.php:163
 #, php-format
-msgid "Source Directory of /%s"
+msgid "Source Branching View for %s"
 msgstr ""
 
-#: browse.php:126
+#: browse.php:35
 #, php-format
-msgid "Source Log for %s"
+msgid "Source Directory of /%s"
 msgstr ""
 
-#: lib/Chora.php:182
+#: lib/Chora.php:179
 msgid ""
 "SourceRoot not found! This could be a misconfiguration by the server "
 "administrator, or the server could be having temporary problems. Please try "
 "again later."
 msgstr ""
 
-#: lib/Chora.php:470
+#: lib/Chora.php:513 lib/Chora.php:514
 msgid "Statistics"
 msgstr ""
 
@@ -379,102 +374,80 @@ msgstr ""
 msgid "Statistics for %s"
 msgstr ""
 
-#: templates/log/rev.inc:34
-msgid "Tags:"
+#: templates/patchsets/ps.inc:22 templates/log/rev.inc:14
+msgid "Tags"
 msgstr ""
 
-#: templates/log/request.inc:5
-msgid ""
-"This form allows you to request diffs between any two revisions of a file.  "
-"You may select a symbolic revision name using the selection box or you may "
-"type in a numeric name using the type-in text box."
+#: templates/checkout/checkout.inc:6 templates/diff/hr/header.inc:38
+msgid "Tags:"
 msgstr ""
 
-#: templates/headerbar.inc:16
+#: templates/headerbar.inc:9
 msgid "Tracking Branch"
 msgstr ""
 
-#: templates/log/request.inc:23
-msgid "Type:"
-msgstr ""
-
-#: templates/log/request.inc:27
+#: templates/diff/hr/header.inc:11
 msgid "Unified"
 msgstr ""
 
-#: templates/log/request.inc:18 templates/log/request.inc:38
-msgid "Use Text Field"
+#: templates/diff/hr/header.inc:24
+msgid "Unmodified"
 msgstr ""
 
 #: config/prefs.php.dist:37
 msgid "Use last viewed file or directory at login time"
 msgstr ""
 
-#: annotate.php:29 templates/log/rev.inc:9
-msgid "View"
-msgstr ""
-
-#: templates/log/request.inc:60
-msgid "View Branch"
+#: templates/diff/hr/header.inc:48 templates/diff/hr/header.inc:49
+#, php-format
+msgid "Version %s"
 msgstr ""
 
-#: templates/log/request.inc:54
-msgid "View revisions on:"
+#: annotate.php:32 templates/log/header.inc:34
+msgid "View"
 msgstr ""
 
-#: lib/Chora.php:472
+#: lib/Chora.php:517
 msgid "View:"
 msgstr ""
 
-#: templates/directory/header.inc:18
+#: templates/directory/header.inc:12
 msgid "_Author"
 msgstr ""
 
-#: lib/Chora.php:465
+#: lib/Chora.php:504 lib/Chora.php:509
 msgid "_Branches"
 msgstr ""
 
-#: lib/Chora.php:439
+#: lib/Chora.php:469
 msgid "_Browse"
 msgstr ""
 
-#: templates/directory/header.inc:8
+#: templates/directory/header.inc:6
 msgid "_File"
 msgstr ""
 
-#: lib/Chora.php:468
-msgid "_Graph"
-msgstr ""
-
-#: lib/Chora.php:459
+#: lib/Chora.php:490
 msgid "_Logs"
 msgstr ""
 
-#: lib/Chora.php:462
+#: lib/Chora.php:497
 msgid "_Patchsets"
 msgstr ""
 
-#: templates/directory/header.inc:13
+#: templates/directory/header.inc:9
 msgid "_Rev"
 msgstr ""
 
-#: lib/Chora.php:470
+#: lib/Chora.php:515
 msgid "_Statistics"
 msgstr ""
 
-#: templates/log/request.inc:35
-msgid "and:"
-msgstr ""
-
-#: templates/history/rev.inc:4 templates/log/rev.inc:5
+#: templates/history/rev.inc:3
 #, php-format
 msgid "by %s"
 msgstr ""
 
-#: templates/diff/hr/footer.inc:15
-msgid "changed lines"
-msgstr ""
-
 #: lib/Chora.php:31
 msgid "day"
 msgstr ""
@@ -524,11 +497,6 @@ msgstr ""
 msgid "seconds"
 msgstr ""
 
-#: templates/diff/hr/header.inc:13 templates/diff/hr/header.inc:14
-#, php-format
-msgid "version %s"
-msgstr ""
-
 #: lib/Chora.php:42
 msgid "very little time"
 msgstr ""
diff --git a/po/cs_CZ.po b/po/cs_CZ.po
index da3538a..8f8142e 100644
--- a/po/cs_CZ.po
+++ b/po/cs_CZ.po
@@ -1,5 +1,5 @@
 # Chora Czech Translation.
-# Copyright (C) 2004 Horde Project
+# Copyright 2004-2009 The Horde Project
 # This file is distributed under the same license as the Horde package.
 # Pavel Chytil <pavel at chytil.tk>, 2004.
 #
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: Chora 2.0-cvs\n"
 "Report-Msgid-Bugs-To: dev at lists.horde.org\n"
-"POT-Creation-Date: 2005-10-13 17:16+0200\n"
+"POT-Creation-Date: 2004-11-25 09:19-0800\n"
 "PO-Revision-Date:  2004-11-21 14:56-0800\n"
 "Last-Translator: Pavel Chytil <pavel at chytil.tk>\n"
 "Language-Team: Czech <dev at lists.horde.org>\n"
@@ -21,7 +21,7 @@ msgstr ""
 msgid "#"
 msgstr "èíslo"
 
-#: templates/patchsets/ps.inc:12 templates/log/rev.inc:26
+#: templates/patchsets/ps.inc:12 templates/log/rev.inc:24
 #, php-format
 msgid "%s ago"
 msgstr "pøed %s"
@@ -35,11 +35,11 @@ msgstr "P
 msgid "All Branches"
 msgstr "V¹echny vìtve"
 
-#: co.php:72 templates/log/rev.inc:8
+#: co.php:68 templates/log/rev.inc:8
 msgid "Annotate"
 msgstr "Komentovat"
 
-#: templates/directory/header.inc:18 templates/stats/stats.inc:3
+#: templates/stats/stats.inc:3 templates/directory/header.inc:18
 #: templates/annotate/header.inc:5
 msgid "Author"
 msgstr "Autor"
@@ -48,16 +48,16 @@ msgstr "Autor"
 msgid "Back"
 msgstr "Zpìt"
 
-#: templates/checkout/checkout.inc:27 templates/log/rev.inc:31
+#: templates/log/rev.inc:29 templates/checkout/checkout.inc:27
 msgid "Branch Point for:"
 msgstr "Vìtvící bod pro:"
 
-#: templates/checkout/checkout.inc:11 templates/patchsets/ps.inc:14
-#: templates/log/rev.inc:28
+#: templates/patchsets/ps.inc:14 templates/log/rev.inc:26
+#: templates/checkout/checkout.inc:11
 msgid "Branch:"
 msgstr "Vìtev"
 
-#: lib/Chora.php:465
+#: lib/Chora.php:467
 msgid "Branches"
 msgstr "Vìtve"
 
@@ -65,22 +65,17 @@ msgstr "V
 msgid "Branching to"
 msgstr "Vìtvící se do"
 
-#: templates/checkout/checkout.inc:35
+#: templates/log/rev.inc:35 templates/checkout/checkout.inc:35
 #, php-format
 msgid "Changed since <b>%s</b>"
 msgstr "Zmìny od <b>%s</b>"
 
-#: templates/log/rev.inc:37
-#, fuzzy, php-format
-msgid "Changed since <strong>%s</strong>"
-msgstr "Zmìny od <b>%s</b>"
-
 #: templates/history/rev.inc:8
 #, php-format
 msgid "Changed: %s"
 msgstr "Zmìnìn: %s"
 
-#: co.php:70
+#: co.php:66
 #, php-format
 msgid "Checkout of %s (revision %s)"
 msgstr "Obdr¾ení %s (revize %s)"
@@ -95,11 +90,11 @@ msgstr "Pro obdr
 msgid "Click on the revisions and branches to display the file."
 msgstr "Pro zobrazení souboru, kliknìte na revize a vìtve."
 
-#: templates/log/rev.inc:42 templates/log/rev.inc:50
+#: templates/log/rev.inc:40 templates/log/rev.inc:49
 msgid "Colored"
 msgstr "Zabarvení"
 
-#: templates/checkout/checkout.inc:19 templates/patchsets/ps.inc:17
+#: templates/patchsets/ps.inc:17 templates/checkout/checkout.inc:19
 msgid "Commit Tags:"
 msgstr "Ukládající Znaèky:"
 
@@ -127,7 +122,7 @@ msgstr "Datum"
 msgid "Deleted"
 msgstr "Smazáno"
 
-#: templates/directory/file.inc:5 templates/directory/file.inc:17
+#: templates/directory/file.inc:6 templates/directory/file.inc:18
 msgid "Deleted File"
 msgstr "Smazané Soubory"
 
@@ -148,12 +143,12 @@ msgstr "Diff V
 msgid "Diff for %s between version %s and %s"
 msgstr "Diff pro %s mezi verzemi %s a %s"
 
-#: templates/log/rev.inc:49
-#, fuzzy, php-format
-msgid "Diffs to <strong>%s</strong>"
+#: templates/log/rev.inc:48
+#, php-format
+msgid "Diffs to <b>%s</b>"
 msgstr "Diff k <b>%s</b>"
 
-#: templates/log/rev.inc:41
+#: templates/log/rev.inc:39
 #, php-format
 msgid "Diffs to version %s"
 msgstr "Diffs do verze %s"
@@ -162,7 +157,7 @@ msgstr "Diffs do verze %s"
 msgid "Directory"
 msgstr "Adresáø"
 
-#: annotate.php:30 co.php:73 templates/log/rev.inc:10
+#: annotate.php:30 co.php:69 templates/log/rev.inc:10
 msgid "Download"
 msgstr "Download"
 
@@ -178,7 +173,7 @@ msgstr "Chyba"
 msgid "Error Encountered"
 msgstr "Vyskytla se Chyba"
 
-#: templates/directory/file.inc:7 templates/directory/file.inc:19
+#: templates/directory/file.inc:8 templates/directory/file.inc:20
 #: templates/directory/header.inc:8
 msgid "File"
 msgstr "Soubor"
@@ -191,7 +186,7 @@ msgstr "Zm
 msgid "Get Diffs"
 msgstr "Dostat Diffs"
 
-#: lib/Chora.php:468
+#: lib/Chora.php:470
 msgid "Graph"
 msgstr "Graf"
 
@@ -246,11 +241,11 @@ msgstr "Z
 msgid "Login Tasks"
 msgstr "Úkony Provádìné pøi Pøihlá¹ení"
 
-#: lib/Chora.php:459
+#: lib/Chora.php:461
 msgid "Logs"
 msgstr "Záznamy"
 
-#: templates/log/rev.inc:43 templates/log/rev.inc:51
+#: templates/log/rev.inc:41 templates/log/rev.inc:50
 msgid "Long"
 msgstr "Dlouhý"
 
@@ -266,7 +261,7 @@ msgstr "Nov
 msgid "No Viewable Change"
 msgstr "®ádné Viditelné Zmìny"
 
-#: templates/log/rev.inc:44 templates/log/rev.inc:52
+#: templates/log/rev.inc:42 templates/log/rev.inc:51
 msgid "NoWhitespaceChanges"
 msgstr "®ádné Zmìny Mezer èi Prázdných Øádkù"
 
@@ -274,7 +269,7 @@ msgstr "
 msgid "Other Options"
 msgstr "Dal¹í Mo¾nosti"
 
-#: lib/Chora.php:368
+#: lib/Chora.php:369
 msgid "Other Repositories"
 msgstr "Dal¹í zdroje CVS"
 
@@ -287,7 +282,7 @@ msgstr "Adres
 msgid "PatchSet %s</span> by %s"
 msgstr "PatchSet %s</span> provedl(a) %s"
 
-#: lib/Chora.php:462
+#: lib/Chora.php:464
 msgid "Patchsets"
 msgstr "Patchsets"
 
@@ -313,7 +308,7 @@ msgstr "Obdr
 msgid "Rev"
 msgstr "Rev"
 
-#: co.php:77
+#: co.php:73
 #, php-format
 msgid "Revision %s for file %s not found."
 msgstr "Nebyla nalezena revize %s pro soubor %s."
@@ -364,7 +359,7 @@ msgstr "Zdrojov
 msgid "Source Log for %s"
 msgstr "Zdrojový Záznam pro %s"
 
-#: lib/Chora.php:182
+#: lib/Chora.php:183
 msgid ""
 "SourceRoot not found! This could be a misconfiguration by the server "
 "administrator, or the server could be having temporary problems. Please try "
@@ -373,7 +368,7 @@ msgstr ""
 "Koøenový adresáø zdroje nebyl nalezen! Toto mù¾e být zpùsobeno ¹patnou "
 "konfigurací nebo server není dostupný. Prosím zkuste to pozdìji."
 
-#: lib/Chora.php:470
+#: lib/Chora.php:472
 msgid "Statistics"
 msgstr "Statistiky"
 
@@ -382,7 +377,7 @@ msgstr "Statistiky"
 msgid "Statistics for %s"
 msgstr "Statistiky pro %s"
 
-#: templates/log/rev.inc:34
+#: templates/log/rev.inc:32
 msgid "Tags:"
 msgstr "Znaèky:"
 
@@ -428,7 +423,7 @@ msgstr "Zobrazit V
 msgid "View revisions on:"
 msgstr "Zobrazit revize z:"
 
-#: lib/Chora.php:472
+#: lib/Chora.php:474
 msgid "View:"
 msgstr "Zobrazit:"
 
@@ -436,11 +431,11 @@ msgstr "Zobrazit:"
 msgid "_Author"
 msgstr "_Autor"
 
-#: lib/Chora.php:465
+#: lib/Chora.php:467
 msgid "_Branches"
 msgstr "_Vìtve"
 
-#: lib/Chora.php:439
+#: lib/Chora.php:441
 msgid "_Browse"
 msgstr "_Prohlí¾et"
 
@@ -448,15 +443,15 @@ msgstr "_Prohl
 msgid "_File"
 msgstr "_Soubor"
 
-#: lib/Chora.php:468
+#: lib/Chora.php:470
 msgid "_Graph"
 msgstr "_Graf"
 
-#: lib/Chora.php:459
+#: lib/Chora.php:461
 msgid "_Logs"
 msgstr "_Záznamy"
 
-#: lib/Chora.php:462
+#: lib/Chora.php:464
 msgid "_Patchsets"
 msgstr "_Patchsets"
 
@@ -464,7 +459,7 @@ msgstr "_Patchsets"
 msgid "_Rev"
 msgstr "_Rev"
 
-#: lib/Chora.php:470
+#: lib/Chora.php:472
 msgid "_Statistics"
 msgstr "_Statistiky"
 
@@ -472,7 +467,7 @@ msgstr "_Statistiky"
 msgid "and:"
 msgstr "a:"
 
-#: templates/history/rev.inc:4 templates/log/rev.inc:5
+#: templates/log/rev.inc:5 templates/history/rev.inc:4
 #, php-format
 msgid "by %s"
 msgstr "podle %s"
@@ -481,11 +476,11 @@ msgstr "podle %s"
 msgid "changed lines"
 msgstr "zmìnìné øádky"
 
-#: lib/Chora.php:31
+#: lib/Chora.php:32
 msgid "day"
 msgstr "den"
 
-#: lib/Chora.php:31
+#: lib/Chora.php:32
 msgid "days"
 msgstr "dnù"
 
@@ -493,27 +488,27 @@ msgstr "dn
 msgid "for further information."
 msgstr "pro dal¹í informace."
 
-#: lib/Chora.php:30
+#: lib/Chora.php:31
 msgid "hour"
 msgstr "hodina"
 
-#: lib/Chora.php:30
+#: lib/Chora.php:31
 msgid "hours"
 msgstr "hodin"
 
-#: lib/Chora.php:29
+#: lib/Chora.php:30
 msgid "minute"
 msgstr "minuta"
 
-#: lib/Chora.php:29
+#: lib/Chora.php:30
 msgid "minutes"
 msgstr "minuty"
 
-#: lib/Chora.php:33
+#: lib/Chora.php:34
 msgid "month"
 msgstr "mìsíc"
 
-#: lib/Chora.php:33
+#: lib/Chora.php:34
 msgid "months"
 msgstr "mìsícù"
 
@@ -522,11 +517,11 @@ msgstr "m
 msgid "revision %s"
 msgstr "revize %s"
 
-#: lib/Chora.php:28
+#: lib/Chora.php:29
 msgid "second"
 msgstr "sekunda"
 
-#: lib/Chora.php:28
+#: lib/Chora.php:29
 msgid "seconds"
 msgstr "sekundy"
 
@@ -535,22 +530,22 @@ msgstr "sekundy"
 msgid "version %s"
 msgstr "verze %s"
 
-#: lib/Chora.php:42
+#: lib/Chora.php:43
 msgid "very little time"
 msgstr "pøed chvílí"
 
-#: lib/Chora.php:32
+#: lib/Chora.php:33
 msgid "week"
 msgstr "týden"
 
-#: lib/Chora.php:32
+#: lib/Chora.php:33
 msgid "weeks"
 msgstr "týdny"
 
-#: lib/Chora.php:34
+#: lib/Chora.php:35
 msgid "year"
 msgstr "rok"
 
-#: lib/Chora.php:34
+#: lib/Chora.php:35
 msgid "years"
 msgstr "rokù"
diff --git a/po/da_DK.po b/po/da_DK.po
index 3bcbb13..12a1155 100644
--- a/po/da_DK.po
+++ b/po/da_DK.po
@@ -1,15 +1,15 @@
 # Danish translations for Chora package
 # Danske oversættelser for pakke Chora.
-# Copyright (C) 2006 Horde Project
-# This file is distributed under the same license as the FRAMEWORK package.
+# Copyright 2006-2009 The Horde Project
+# This file is distributed under the same license as the Chora package.
 # Brian Truelsen <horde+i18n at briantruelsen.dk>, 2006.
 #
 msgid ""
 msgstr ""
-"Project-Id-Version: Chora H3 (2.0.2-cvs)\n"
+"Project-Id-Version: Chora H3 (2.1-cvs)\n"
 "Report-Msgid-Bugs-To: dev at lists.horde.org\n"
-"POT-Creation-Date: 2006-03-06 12:47+0100\n"
-"PO-Revision-Date: 2006-03-11 17:23+0100\n"
+"POT-Creation-Date: 2006-03-05 18:16+0100\n"
+"PO-Revision-Date: 2006-03-11 15:39+0100\n"
 "Last-Translator: Brian Truelsen <horde+i18n at briantruelsen.dk>\n"
 "Language-Team: i18n at lists.horde.org\n"
 "MIME-Version: 1.0\n"
@@ -17,11 +17,12 @@ msgstr ""
 "Content-Transfer-Encoding: 8bit\n"
 "Plural-Forms: nplurals=2; plural=(n != 1);\n"
 
-#: templates/annotate/header.inc:4
+#: templates/annotate/header.inc:3
 msgid "#"
 msgstr "#"
 
 #: templates/patchsets/ps.inc:12 templates/log/rev.inc:26
+#: templates/annotate/footer.inc:22
 #, php-format
 msgid "%s ago"
 msgstr "%s siden"
@@ -39,8 +40,8 @@ msgstr "Alle forgreninger"
 msgid "Annotate"
 msgstr "Annotér"
 
-#: templates/stats/stats.inc:3 templates/directory/header.inc:18
-#: templates/annotate/header.inc:5
+#: templates/stats/stats.inc:3 templates/directory/header.inc:12
+#: templates/annotate/header.inc:4
 msgid "Author"
 msgstr "Forfatter"
 
@@ -57,7 +58,7 @@ msgstr "Forgreningspunkt for:"
 msgid "Branch:"
 msgstr "Forgrening:"
 
-#: lib/Chora.php:465
+#: lib/Chora.php:455
 msgid "Branches"
 msgstr "Forgreninger"
 
@@ -65,12 +66,7 @@ msgstr "Forgreninger"
 msgid "Branching to"
 msgstr "Forgrén til"
 
-#: templates/checkout/checkout.inc:35
-#, php-format
-msgid "Changed since <b>%s</b>"
-msgstr "Ændret siden <b>%s</b>"
-
-#: templates/log/rev.inc:37
+#: templates/log/rev.inc:37 templates/checkout/checkout.inc:35
 #, php-format
 msgid "Changed since <strong>%s</strong>"
 msgstr "Ændret siden <strong>%s</strong>"
@@ -90,7 +86,7 @@ msgid ""
 "Click on the links in between revisions to get a diff between those "
 "revisions."
 msgstr ""
-"Klik på henvisningerne mellem revisionerne for at få en diff mellem disse "
+"Klik på henvisningen mellem revisioner for at få en diff mellem disse "
 "revisioner."
 
 #: templates/cvsgraph/cvsgraph.inc:10
@@ -117,11 +113,11 @@ msgstr "Sammenh
 msgid "Customize tasks to run upon logging in to Chora."
 msgstr "Tilpas opgaver, der bliver udført, når du logger ind i Chora."
 
-#: templates/directory/header.inc:23
+#: templates/directory/header.inc:15
 msgid "Da_te"
 msgstr "Dato"
 
-#: templates/directory/header.inc:23
+#: templates/directory/header.inc:15
 msgid "Date"
 msgstr "Dato"
 
@@ -164,7 +160,7 @@ msgstr "Diff til version %s"
 msgid "Directory"
 msgstr "Katalog"
 
-#: annotate.php:30 co.php:73 templates/log/rev.inc:10
+#: annotate.php:31 co.php:73 templates/log/rev.inc:10
 msgid "Download"
 msgstr "Hent"
 
@@ -178,10 +174,10 @@ msgstr "Fejl"
 
 #: templates/error_page.inc:7
 msgid "Error Encountered"
-msgstr "Fejl påtruffet"
+msgstr "Fejl optrådt"
 
 #: templates/directory/file.inc:7 templates/directory/file.inc:19
-#: templates/directory/header.inc:8
+#: templates/directory/header.inc:6
 msgid "File"
 msgstr "Fil"
 
@@ -193,7 +189,7 @@ msgstr "Filer 
 msgid "Get Diffs"
 msgstr "Hent diff'er"
 
-#: lib/Chora.php:468
+#: lib/Chora.php:458
 msgid "Graph"
 msgstr "Graf"
 
@@ -214,7 +210,7 @@ msgstr "Skjul slettede filer"
 msgid "Human Readable"
 msgstr "Læsbart"
 
-#: templates/directory/header.inc:25
+#: templates/directory/header.inc:17
 msgid "Last Log"
 msgstr "Sidste logfil"
 
@@ -227,7 +223,7 @@ msgstr "Sidste log-besked for rev %s:"
 msgid "Legend:"
 msgstr "Forklaring:"
 
-#: templates/annotate/header.inc:7
+#: templates/annotate/header.inc:6
 msgid "Line"
 msgstr "Linie"
 
@@ -248,7 +244,7 @@ msgstr "Log:"
 msgid "Login Tasks"
 msgstr "Login-opgaver"
 
-#: lib/Chora.php:459
+#: lib/Chora.php:449
 msgid "Logs"
 msgstr "Logfiler"
 
@@ -276,7 +272,7 @@ msgstr "NoWhitespaceChanges"
 msgid "Other Options"
 msgstr "Andre indstillinger"
 
-#: lib/Chora.php:368
+#: lib/Chora.php:354
 msgid "Other Repositories"
 msgstr "Andre arkiver"
 
@@ -289,7 +285,7 @@ msgstr "For
 msgid "PatchSet %s</span> by %s"
 msgstr "Lappegrej %s</span> af %s"
 
-#: lib/Chora.php:462
+#: lib/Chora.php:452
 msgid "Patchsets"
 msgstr "Lappegrejer"
 
@@ -307,11 +303,15 @@ msgstr "Kontakt"
 msgid "Removed in v.%s"
 msgstr "Slette i v.%s"
 
+#: lib/api.php:30
+msgid "Repositories"
+msgstr "Arkiver"
+
 #: templates/log/request.inc:14
 msgid "Retrieve diffs between:"
-msgstr "Hent diff'er mellem:"
+msgstr "Fremskaf diff'er mellem:"
 
-#: templates/directory/header.inc:13 templates/annotate/header.inc:6
+#: templates/directory/header.inc:9 templates/annotate/header.inc:5
 msgid "Rev"
 msgstr "Rev"
 
@@ -322,8 +322,8 @@ msgstr "Revision %s for fil %s ikke fundet."
 
 #: templates/checkout/checkout.inc:4
 #, php-format
-msgid "Revision <b>%s</b>, <i>%s</i> (%s ago) by %s"
-msgstr "Revision <b>%s</b>, <i>%s</i> (%s siden) af %s"
+msgid "Revision <strong>%s</strong>, <em>%s</em> (%s ago) by %s"
+msgstr "Revision <strong>%s</strong>, <em>%s</em> (%s siden) af %s"
 
 #: templates/log/rev.inc:15
 msgid "Select for Diff"
@@ -341,12 +341,7 @@ msgstr "Vis slettede filer"
 msgid "Side-by-Side"
 msgstr "Side-om-side"
 
-#: templates/directory/header.inc:7 templates/directory/header.inc:12
-#: templates/directory/header.inc:17 templates/directory/header.inc:22
-msgid "Sort Order"
-msgstr "Sorteringsrækkefølge"
-
-#: annotate.php:27
+#: annotate.php:28
 #, php-format
 msgid "Source Annotation of %s for version %s"
 msgstr "Kilde-annotering af %s for version %s"
@@ -354,29 +349,29 @@ msgstr "Kilde-annotering af %s for version %s"
 #: history.php:160
 #, php-format
 msgid "Source Branching View for %s"
-msgstr "Kildeforgreningsvisning af %s"
+msgstr "Kildeforgreningsvisning for %s"
 
 #: browse.php:32
 #, php-format
 msgid "Source Directory of /%s"
 msgstr "Kildekatalog af /%s"
 
-#: browse.php:126
+#: browse.php:125
 #, php-format
 msgid "Source Log for %s"
 msgstr "Kildelog af %s"
 
-#: lib/Chora.php:182
+#: lib/Chora.php:175
 msgid ""
 "SourceRoot not found! This could be a misconfiguration by the server "
 "administrator, or the server could be having temporary problems. Please try "
 "again later."
 msgstr ""
-"Kilderod ikke fundet! Dette kan skyldes en fejlkonfiguration af server-"
-"administratoren, eller serveren kan have midlertidige problemer. Prøv igen "
-"senere."
+"Kilderod ikke fundet! Dette kan være en fejlkonfiguration af "
+"serveradministratoren, eller serveren kan være ude for midlertidige "
+"problemer. Prøv igen senere."
 
-#: lib/Chora.php:470
+#: lib/Chora.php:460
 msgid "Statistics"
 msgstr "Statistikker"
 
@@ -395,10 +390,10 @@ msgid ""
 "You may select a symbolic revision name using the selection box or you may "
 "type in a numeric name using the type-in text box."
 msgstr ""
-"Dette skema tillader dig at anmode om diff'er mellem vilkårligt to "
-"revisioner af en fil. Du kan vælge et symbolsk revisionsnavn med "
-"selektionsværktøjet eller du kan indtaste et numerisk navn ved brug af "
-"indtastningsfeltet. "
+"Dette skema tillader dig at anmode om diff'er mellem vilkårlige, to "
+"revisioner af en fil. Du kan vælge et symbolsk revisionsnavn ved brug af "
+"valg-kasserne eller du kan indtaste et numerisk navn ved brug af tekst-"
+"indtastningsfeltet."
 
 #: templates/headerbar.inc:16
 msgid "Tracking Branch"
@@ -414,13 +409,13 @@ msgstr "Sammensmeltet"
 
 #: templates/log/request.inc:18 templates/log/request.inc:38
 msgid "Use Text Field"
-msgstr "Anvend tekst-felt"
+msgstr "Brug tekst-felt"
 
 #: config/prefs.php.dist:37
 msgid "Use last viewed file or directory at login time"
-msgstr "Anvend sidst besete fil eller katalog ved logind."
+msgstr "Brug sidst sete fil eller katalog ved logind."
 
-#: annotate.php:29 templates/log/rev.inc:9
+#: annotate.php:30 templates/log/rev.inc:9
 msgid "View"
 msgstr "Se"
 
@@ -432,43 +427,43 @@ msgstr "Se gren"
 msgid "View revisions on:"
 msgstr "Se revisioner på:"
 
-#: lib/Chora.php:472
+#: lib/Chora.php:462
 msgid "View:"
 msgstr "Se:"
 
-#: templates/directory/header.inc:18
+#: templates/directory/header.inc:12
 msgid "_Author"
 msgstr "Forfatter"
 
-#: lib/Chora.php:465
+#: lib/Chora.php:455
 msgid "_Branches"
 msgstr "Foregreninger"
 
-#: lib/Chora.php:439
+#: lib/Chora.php:433
 msgid "_Browse"
 msgstr "Skim"
 
-#: templates/directory/header.inc:8
+#: templates/directory/header.inc:6
 msgid "_File"
 msgstr "Fil"
 
-#: lib/Chora.php:468
+#: lib/Chora.php:458
 msgid "_Graph"
 msgstr "Graf"
 
-#: lib/Chora.php:459
+#: lib/Chora.php:449
 msgid "_Logs"
 msgstr "Logninger"
 
-#: lib/Chora.php:462
+#: lib/Chora.php:452
 msgid "_Patchsets"
 msgstr "Lappesæt"
 
-#: templates/directory/header.inc:13
+#: templates/directory/header.inc:9
 msgid "_Rev"
 msgstr "Rev"
 
-#: lib/Chora.php:470
+#: lib/Chora.php:460
 msgid "_Statistics"
 msgstr "Statistikker"
 
diff --git a/po/de_DE.po b/po/de_DE.po
index 9a530cd..0698768 100644
--- a/po/de_DE.po
+++ b/po/de_DE.po
@@ -1,12 +1,13 @@
-# Chora German translation.
-# Copyright (C) 2001-2005 Jan Schneider.
-# Jan Schneider <jan at horde.org>, 2001-2005.
+# German translation for Chora.
+# Copyright 2001-2009 The Horde Project
+# This file is distributed under the same license as the Chora package.
+# Jan Schneider <jan at horde.org>, 2001-2008.
 msgid ""
 msgstr ""
-"Project-Id-Version: Chora 2.0-cvs\n"
+"Project-Id-Version: Chora 3.0-cvs\n"
 "Report-Msgid-Bugs-To: dev at lists.horde.org\n"
-"POT-Creation-Date: 2006-03-06 12:47+0100\n"
-"PO-Revision-Date: 2005-07-06 01:26+0200\n"
+"POT-Creation-Date: 2008-09-18 11:24+0200\n"
+"PO-Revision-Date: 2008-04-01 15:05+0200\n"
 "Last-Translator: Jan Schneider <jan at horde.org>\n"
 "Language-Team: German <dev at lists.horde.org>\n"
 "MIME-Version: 1.0\n"
@@ -17,43 +18,57 @@ msgstr ""
 msgid "#"
 msgstr "Nr."
 
-#: templates/patchsets/ps.inc:12 templates/log/rev.inc:26
+#: templates/log/header.inc:17
+#, php-format
+msgid ""
+"%1$s shows diffs to the previous revision. If you select a revision by "
+"clicking its row, %1$s will show the differences to the selected row."
+msgstr ""
+"%1$s zeigt die Unterschiede zur vorigen Version an. Wenn Sie eine Version "
+"durch Klicken auf deren Zeile ausgewählt haben, zeigt %1$s die Unterschiede "
+"zu der ausgewählten Version an."
+
+#: co.php:84
+#, php-format
+msgid "%s Revision %s (%s ago)"
+msgstr "%s Version %s (vor %s)"
+
+#: templates/annotate/footer.inc:22
 #, php-format
 msgid "%s ago"
 msgstr "vor %s"
 
-#: templates/diff/hr/footer.inc:19
-#, php-format
-msgid "Added in v.%s"
-msgstr "hinzugefügt in v.%s"
+#: templates/diff/hr/header.inc:25
+msgid "Added"
+msgstr "Hinzugefügt"
 
-#: templates/log/request.inc:57
+#: templates/log/header.inc:31
 msgid "All Branches"
 msgstr "Alle Branches"
 
-#: co.php:72 templates/log/rev.inc:8
+#: co.php:89
 msgid "Annotate"
 msgstr "Kommentiert"
 
-#: templates/directory/header.inc:18 templates/stats/stats.inc:3
-#: templates/annotate/header.inc:5
+#: templates/directory/header.inc:12 templates/patchsets/header.inc:15
+#: templates/stats/stats.inc:4 templates/annotate/header.inc:5
+#: templates/log/header.inc:49
 msgid "Author"
 msgstr "Autor"
 
-#: templates/directory/back.inc:3
+#: templates/checkout/checkout.inc:4 templates/diff/hr/header.inc:35
+msgid "Author:"
+msgstr "Autor:"
+
+#: templates/directory/back.inc:4
 msgid "Back"
 msgstr "Zurück"
 
-#: templates/checkout/checkout.inc:27 templates/log/rev.inc:31
-msgid "Branch Point for:"
-msgstr "Branch Stelle für:"
-
-#: templates/checkout/checkout.inc:11 templates/patchsets/ps.inc:14
-#: templates/log/rev.inc:28
+#: templates/checkout/checkout.inc:5 templates/diff/hr/header.inc:37
 msgid "Branch:"
 msgstr "Branch:"
 
-#: lib/Chora.php:465
+#: lib/Chora.php:502 lib/Chora.php:503 lib/Chora.php:507 lib/Chora.php:508
 msgid "Branches"
 msgstr "Branches"
 
@@ -61,27 +76,20 @@ msgstr "Branches"
 msgid "Branching to"
 msgstr "Branching nach"
 
-#: templates/checkout/checkout.inc:35
-#, php-format
-msgid "Changed since <b>%s</b>"
-msgstr "Geändert seit <b>%s</b>"
-
-#: templates/log/rev.inc:37
-#, php-format
-msgid "Changed since <strong>%s</strong>"
-msgstr "Geändert seit <strong>%s</strong>"
+#: lib/Chora.php:392
+msgid "Change repositories:"
+msgstr "Repository ändern:"
 
-#: templates/history/rev.inc:8
+#: templates/history/rev.inc:7
 #, php-format
 msgid "Changed: %s"
 msgstr "Geändert: %s"
 
-#: co.php:70
-#, php-format
-msgid "Checkout of %s (revision %s)"
-msgstr "Checkout von %s (Version %s)"
+#: templates/checkout/checkout.inc:12
+msgid "Checkout"
+msgstr "Checkout"
 
-#: templates/cvsgraph/cvsgraph.inc:11
+#: templates/cvsgraph/cvsgraph.inc:5
 msgid ""
 "Click on the links in between revisions to get a diff between those "
 "revisions."
@@ -89,23 +97,19 @@ msgstr ""
 "Klicken Sie auf die Links zwischen den Versionen, um den Unterschied "
 "zwischen diesen Versionen anzuzeigen."
 
-#: templates/cvsgraph/cvsgraph.inc:10
+#: templates/cvsgraph/cvsgraph.inc:4
 msgid "Click on the revisions and branches to display the file."
 msgstr "Klicken Sie auf die Versionen oder Branches, um die Datei anzuzeigen."
 
-#: templates/log/rev.inc:42 templates/log/rev.inc:50
-msgid "Colored"
-msgstr "Farbig"
-
-#: templates/checkout/checkout.inc:19 templates/patchsets/ps.inc:17
-msgid "Commit Tags:"
-msgstr "Commit Tags:"
+#: templates/diff/hr/header.inc:13
+msgid "Column"
+msgstr "Spalte"
 
-#: templates/stats/stats.inc:4
+#: templates/stats/stats.inc:5
 msgid "Commits"
 msgstr "Commits"
 
-#: templates/log/request.inc:28
+#: templates/diff/hr/header.inc:12
 msgid "Context"
 msgstr "Context"
 
@@ -114,58 +118,54 @@ msgid "Customize tasks to run upon logging in to Chora."
 msgstr ""
 "Legen Sie fest, was nach der Anmeldung bei Chora durchgeführt werden soll."
 
-#: templates/directory/header.inc:23
+#: templates/directory/header.inc:15
 msgid "Da_te"
 msgstr "Da_tum"
 
-#: templates/directory/header.inc:23
+#: templates/directory/header.inc:15 templates/patchsets/header.inc:14
+#: templates/log/header.inc:48
 msgid "Date"
 msgstr "Datum"
 
-#: patchsets.php:73
+#: templates/diff/hr/header.inc:36
+msgid "Date:"
+msgstr "Datum:"
+
+#: patchsets.php:79
 msgid "Deleted"
 msgstr "Gelöscht"
 
-#: templates/directory/file.inc:5 templates/directory/file.inc:17
+#: templates/directory/file.inc:5 templates/directory/file.inc:28
 msgid "Deleted File"
 msgstr "Gelöschte Dateien"
 
-#: templates/log/rev.inc:13
-msgid "Deselect"
-msgstr "Auswahl aufheben"
-
-#: patchsets.php:70
+#: patchsets.php:76 templates/log/header.inc:16
 msgid "Diff"
 msgstr "Unterschiede"
 
-#: patchsets.php:53
-msgid "Diff All Files"
-msgstr "Unterschiede aller Dateien"
-
-#: diff.php:69
+#: diff.php:71
 #, php-format
 msgid "Diff for %s between version %s and %s"
 msgstr "Unterschied von %s zwischen den Versionen %s und %s"
 
-#: templates/log/rev.inc:49
-#, php-format
-msgid "Diffs to <strong>%s</strong>"
-msgstr "Unterschiede zu <strong>%s</strong>"
-
-#: templates/log/rev.inc:41
-#, php-format
-msgid "Diffs to version %s"
-msgstr "Unterschiede zur Version %s"
-
 #: templates/directory/dir.inc:4 templates/directory/dir.inc:6
 msgid "Directory"
 msgstr "Verzeichnis"
 
-#: annotate.php:30 co.php:73 templates/log/rev.inc:10
+#: annotate.php:33 co.php:90
 msgid "Download"
 msgstr "Herunterladen"
 
-#: templates/log/request.inc:30
+#: templates/diff/hr/header.inc:9
+msgid "Download diff as: "
+msgstr "Unterschiede herunterladen als: "
+
+#: templates/checkout/checkout.inc:23
+#, php-format
+msgid "Download revision %s"
+msgstr "Version %s herunterladen"
+
+#: templates/diff/hr/header.inc:14
 msgid "Ed Script"
 msgstr "Ed Script"
 
@@ -177,120 +177,108 @@ msgstr "Fehler"
 msgid "Error Encountered"
 msgstr "Ein Fehler ist aufgetreten"
 
-#: templates/directory/file.inc:7 templates/directory/file.inc:19
-#: templates/directory/header.inc:8
+#: templates/directory/file.inc:7 templates/directory/file.inc:30
+#: templates/directory/header.inc:6
 msgid "File"
 msgstr "Datei"
 
-#: templates/patchsets/ps.inc:21
-msgid "Files Changed:"
-msgstr "Geänderte Dateien:"
-
-#: templates/log/request.inc:44
-msgid "Get Diffs"
-msgstr "Unterschiede anzeigen"
+#: templates/patchsets/header.inc:16
+msgid "Files"
+msgstr "Dateien"
 
-#: lib/Chora.php:468
-msgid "Graph"
-msgstr "Grafik"
+#: templates/diff/hr/header.inc:16
+msgid "Get Diff"
+msgstr "Unterschiede herunterladen"
 
-#: cvsgraph.php:48
+#: cvsgraph.php:51
 #, php-format
 msgid "Graph for %s"
 msgstr "Grafik für %s"
 
-#: browse.php:40
+#: browse.php:42
 msgid "Hide Deleted Files"
 msgstr "Gelöschte Dateien verstecken"
 
-#: browse.php:40
+#: browse.php:44
 msgid "Hide _Deleted Files"
 msgstr "_Gelöschte Dateien verstecken"
 
-#: templates/log/request.inc:26
-msgid "Human Readable"
-msgstr "Human Readable"
-
-#: templates/directory/header.inc:25
+#: templates/directory/header.inc:17
 msgid "Last Log"
 msgstr "Letzter Eintrag"
 
-#: templates/diff/hr/header.inc:5
-#, php-format
-msgid "Last Log Message for rev %s:"
-msgstr "Letzter Log-Eintrag für rev %s:"
-
-#: templates/diff/hr/footer.inc:5
-msgid "Legend:"
-msgstr "Legende:"
-
-#: templates/annotate/header.inc:7
+#: templates/annotate/header.inc:8
 msgid "Line"
 msgstr "Zeile"
 
-#: templates/diff/hr/row.inc:3 templates/diff/hr/row.inc:7
+#: templates/diff/hr/row.inc:3 templates/diff/hr/row.inc:6
 #, php-format
 msgid "Line %s"
 msgstr "Zeile %s"
 
-#: templates/headerbar.inc:11
+#: templates/headerbar.inc:5
 msgid "Location:"
 msgstr "Position:"
 
-#: templates/checkout/checkout.inc:42
-msgid "Log:"
-msgstr "Log:"
+#: templates/checkout/checkout.inc:1 templates/patchsets/header.inc:17
+#: templates/diff/hr/header.inc:30 templates/log/header.inc:50
+msgid "Log Message"
+msgstr "Änderungseintrag"
 
 #: config/prefs.php.dist:10
 msgid "Login Tasks"
 msgstr "Anmeldeaufgaben"
 
-#: lib/Chora.php:459
+#: lib/Chora.php:488 lib/Chora.php:489
 msgid "Logs"
 msgstr "Logs"
 
-#: templates/log/rev.inc:43 templates/log/rev.inc:51
-msgid "Long"
-msgstr "Lang"
-
-#: diff.php:61
+#: diff.php:44
 msgid "Malformed Query"
 msgstr "Ungültige Abfrage"
 
-#: patchsets.php:66
+#: lib/Block/tree_menu.php:3
+msgid "Menu List"
+msgstr "Menüliste"
+
+#: templates/diff/hr/header.inc:26
+msgid "Modified"
+msgstr "Geändert"
+
+#: patchsets.php:72
 msgid "New File"
 msgstr "Neue Datei"
 
-#: templates/diff/hr/nochange.inc:6
-msgid "No Viewable Change"
-msgstr "Keine sichtbare Änderung"
+#: templates/diff/hr/nochange.inc:3
+msgid "No Visible Changes"
+msgstr "Keine sichtbaren Änderungen"
 
-#: templates/log/rev.inc:44 templates/log/rev.inc:52
-msgid "NoWhitespaceChanges"
-msgstr "Ohne Leerzeichen-Änderungen"
+#: templates/log/footer.inc:8
+#, php-format
+msgid ""
+"Only showing the 100 latest revisions. %sShow all revisions?</a> (may take a "
+"while)."
+msgstr ""
+"Nur die letzten 100 Versionen werden angezeigt. %sAlle Versionen anzeigen?</"
+"a> (kann eine Weile dauern)."
 
 #: config/prefs.php.dist:9
 msgid "Other Options"
 msgstr "Andere Einstellungen"
 
-#: lib/Chora.php:368
-msgid "Other Repositories"
-msgstr "Andere Repositories"
-
-#: templates/directory/back.inc:3
+#: templates/directory/back.inc:4
 msgid "Parent Directory"
 msgstr "Verzeichnis hoch"
 
-#: templates/patchsets/ps.inc:6
-#, php-format
-msgid "PatchSet %s</span> by %s"
-msgstr "Patchsatz %s</span> von %s"
+#: templates/patchsets/header.inc:13
+msgid "Patchset"
+msgstr "Patchsatz"
 
-#: lib/Chora.php:462
+#: lib/Chora.php:495 lib/Chora.php:496
 msgid "Patchsets"
 msgstr "Patchsätze"
 
-#: patchsets.php:32
+#: patchsets.php:35
 #, php-format
 msgid "Patchsets for %s"
 msgstr "Patchsätze für %s"
@@ -299,71 +287,84 @@ msgstr "Patchs
 msgid "Please contact"
 msgstr "Bitte kontaktieren Sie"
 
-#: templates/diff/hr/footer.inc:11
-#, php-format
-msgid "Removed in v.%s"
-msgstr "gelöscht aus v.%s"
+#: templates/annotate/header.inc:7
+msgid "Prev"
+msgstr "Zurück"
+
+#: templates/diff/hr/header.inc:27
+msgid "Removed"
+msgstr "Entfernt"
 
-#: templates/log/request.inc:14
-msgid "Retrieve diffs between:"
-msgstr "Unterschiede anzeigen zwischen:"
+#: lib/api.php:30
+msgid "Repositories"
+msgstr "Repositories"
 
-#: templates/directory/header.inc:13 templates/annotate/header.inc:6
+#: templates/directory/header.inc:9 templates/annotate/header.inc:6
 msgid "Rev"
 msgstr "Rev"
 
-#: co.php:77
-#, php-format
-msgid "Revision %s for file %s not found."
-msgstr "Version %s der Datei %s nicht gefunden."
+#: templates/log/header.inc:47
+msgid "Revision"
+msgstr "Version"
 
-#: templates/checkout/checkout.inc:4
+#: browse.php:157
 #, php-format
-msgid "Revision <b>%s</b>, <i>%s</i> (%s ago) by %s"
-msgstr "Version <b>%s</b>, <i>%s</i> (vor %s) von %s"
+msgid "Revisions for %s"
+msgstr "Versionen für %s"
+
+#: templates/patchsets/header.inc:4
+msgid "Search Patchsets:"
+msgstr "Patchsätze suchen:"
+
+#: templates/log/header.inc:4
+msgid "Search Revisions:"
+msgstr "Versionen suchen:"
 
-#: templates/log/rev.inc:15
-msgid "Select for Diff"
-msgstr "Für das Anzeigen von Unterschieden auswählen"
+#: templates/log/header.inc:29
+msgid "Show Branch:"
+msgstr "Branch anzeigen:"
 
 #: browse.php:42
 msgid "Show Deleted Files"
 msgstr "Gelöschte Dateien anzeigen"
 
-#: browse.php:42
+#: templates/log/header.inc:18
+msgid "Show Differences"
+msgstr "Unterschiede anzeigen"
+
+#: browse.php:44
 msgid "Show _Deleted Files"
 msgstr "_Gelöschte Dateien anzeigen"
 
-#: templates/log/request.inc:29
-msgid "Side-by-Side"
-msgstr "Side-by-Side"
+#: templates/log/rev.inc:4
+msgid "Show changes to the previous revision"
+msgstr "Änderungen zur vorigen Version anzeigen"
+
+#: templates/log/rev.inc:6
+msgid "Show changes to the selected revision"
+msgstr "Änderungen zur ausgewählten Version anzeigen"
 
-#: templates/directory/header.inc:7 templates/directory/header.inc:12
-#: templates/directory/header.inc:17 templates/directory/header.inc:22
-msgid "Sort Order"
-msgstr "Sortierreihenfolge"
+#: templates/log/header.inc:13
+#, php-format
+msgid "Show diffs between %s and %s"
+msgstr "Unterschiede zwischen %s und %s anzeigen"
 
-#: annotate.php:27
+#: annotate.php:30
 #, php-format
-msgid "Source Annotation of %s for version %s"
-msgstr "Quell-Kommentar von %s für version %s"
+msgid "Source Annotation of %s (revision %s)"
+msgstr "Quell-Kommentar von %s (Version %s)"
 
-#: history.php:160
+#: history.php:163
 #, php-format
 msgid "Source Branching View for %s"
 msgstr "Branch Ansicht für %s"
 
-#: browse.php:32
+#: browse.php:35
 #, php-format
 msgid "Source Directory of /%s"
 msgstr "Verzeichnis von /%s"
 
-#: browse.php:126
-#, php-format
-msgid "Source Log for %s"
-msgstr "Eintrag für %s"
-
-#: lib/Chora.php:182
+#: lib/Chora.php:179
 msgid ""
 "SourceRoot not found! This could be a misconfiguration by the server "
 "administrator, or the server could be having temporary problems. Please try "
@@ -373,7 +374,7 @@ msgstr ""
 "durch den Administrator oder vorübergehende Probleme mit dem Server sein. "
 "Bitte versuchen Sie es später noch einmal."
 
-#: lib/Chora.php:470
+#: lib/Chora.php:513 lib/Chora.php:514
 msgid "Statistics"
 msgstr "Statistiken"
 
@@ -382,107 +383,81 @@ msgstr "Statistiken"
 msgid "Statistics for %s"
 msgstr "Statistik für %s"
 
-#: templates/log/rev.inc:34
+#: templates/patchsets/ps.inc:22 templates/log/rev.inc:14
+msgid "Tags"
+msgstr "Tags"
+
+#: templates/checkout/checkout.inc:6 templates/diff/hr/header.inc:38
 msgid "Tags:"
 msgstr "Tags:"
 
-#: templates/log/request.inc:5
-msgid ""
-"This form allows you to request diffs between any two revisions of a file.  "
-"You may select a symbolic revision name using the selection box or you may "
-"type in a numeric name using the type-in text box."
-msgstr ""
-"Dieses Formular erlaubt die Anzeige von Unterschieden zwischen zwei "
-"beliebigen Versionen einer Datei. Sie können einen symbolischen "
-"Versionsnamen über das Auswahlfeld auswählen oder eine Versionsnummer in das "
-"Textfeld eingeben."
-
-#: templates/headerbar.inc:16
+#: templates/headerbar.inc:9
 msgid "Tracking Branch"
-msgstr "Branch beobachten"
+msgstr "Angezeigter Branch:"
 
-#: templates/log/request.inc:23
-msgid "Type:"
-msgstr "Typ:"
-
-#: templates/log/request.inc:27
+#: templates/diff/hr/header.inc:11
 msgid "Unified"
 msgstr "Unified"
 
-#: templates/log/request.inc:18 templates/log/request.inc:38
-msgid "Use Text Field"
-msgstr "Eingabe in Textfeld:"
+#: templates/diff/hr/header.inc:24
+msgid "Unmodified"
+msgstr "Keine Änderung"
 
 #: config/prefs.php.dist:37
 msgid "Use last viewed file or directory at login time"
 msgstr ""
 "Zuletzt geöffnete Datei oder Verzeichnis nach der Anmeldung wieder verwenden"
 
-#: annotate.php:29 templates/log/rev.inc:9
+#: templates/diff/hr/header.inc:48 templates/diff/hr/header.inc:49
+#, php-format
+msgid "Version %s"
+msgstr "Version %s"
+
+#: annotate.php:32 templates/log/header.inc:34
 msgid "View"
 msgstr "Anzeigen"
 
-#: templates/log/request.inc:60
-msgid "View Branch"
-msgstr "Branch anzeigen"
-
-#: templates/log/request.inc:54
-msgid "View revisions on:"
-msgstr "Versionen anzeigen an:"
-
-#: lib/Chora.php:472
+#: lib/Chora.php:517
 msgid "View:"
 msgstr "Anzeigen:"
 
-#: templates/directory/header.inc:18
+#: templates/directory/header.inc:12
 msgid "_Author"
 msgstr "_Autor"
 
-#: lib/Chora.php:465
+#: lib/Chora.php:504 lib/Chora.php:509
 msgid "_Branches"
 msgstr "_Branches"
 
-#: lib/Chora.php:439
+#: lib/Chora.php:469
 msgid "_Browse"
 msgstr "_Liste"
 
-#: templates/directory/header.inc:8
+#: templates/directory/header.inc:6
 msgid "_File"
 msgstr "_Datei"
 
-#: lib/Chora.php:468
-msgid "_Graph"
-msgstr "_Grafik"
-
-#: lib/Chora.php:459
+#: lib/Chora.php:490
 msgid "_Logs"
 msgstr "_Logs"
 
-#: lib/Chora.php:462
+#: lib/Chora.php:497
 msgid "_Patchsets"
 msgstr "_Patchsätze"
 
-#: templates/directory/header.inc:13
+#: templates/directory/header.inc:9
 msgid "_Rev"
 msgstr "_Rev"
 
-#: lib/Chora.php:470
+#: lib/Chora.php:515
 msgid "_Statistics"
 msgstr "_Statistiken"
 
-#: templates/log/request.inc:35
-msgid "and:"
-msgstr "und:"
-
-#: templates/history/rev.inc:4 templates/log/rev.inc:5
+#: templates/history/rev.inc:3
 #, php-format
 msgid "by %s"
 msgstr "von %s"
 
-#: templates/diff/hr/footer.inc:15
-msgid "changed lines"
-msgstr "geänderte Zeilen"
-
 #: lib/Chora.php:31
 msgid "day"
 msgstr "Tag"
@@ -532,11 +507,6 @@ msgstr "Sekunde"
 msgid "seconds"
 msgstr "Sekunden"
 
-#: templates/diff/hr/header.inc:13 templates/diff/hr/header.inc:14
-#, php-format
-msgid "version %s"
-msgstr "Version %s"
-
 #: lib/Chora.php:42
 msgid "very little time"
 msgstr "sehr kurze Zeit"
diff --git a/po/es_ES.po b/po/es_ES.po
index c8141ab..9fde2e0 100644
--- a/po/es_ES.po
+++ b/po/es_ES.po
@@ -1,15 +1,15 @@
-# Spanish translations for horde package
-# Traducciones al español para el paquete horde.
-# Copyright (C) 2004 Horde Project
-# This file is distributed under the same license as the horde package.
-# Automatically generated, 2004.
+# Spanish translations for chora package
+# Traducciones al español para el paquete chora.
+# Copyright 2008-2009 The Horde Project
+# This file is distributed under the same license as the chora package.
+# Automatically generated, 2008.
 #
 msgid ""
 msgstr ""
-"Project-Id-Version: Chora 2.0-cvs\n"
+"Project-Id-Version: Chora 2.1-cvs\n"
 "Report-Msgid-Bugs-To: dev at lists.horde.org\n"
-"POT-Creation-Date: 2005-10-13 17:16+0200\n"
-"PO-Revision-Date: 2004-12-12 06:40+100\n"
+"POT-Creation-Date: 2009-02-20 08:18+0100\n"
+"PO-Revision-Date: 2009-02-20 08:18+0100\n"
 "Last-Translator: Manuel P. Ayala <mayala at unex.es>\n"
 "Language-Team: i18n at lists.horde.org\n"
 "MIME-Version: 1.0\n"
@@ -21,43 +21,57 @@ msgstr ""
 msgid "#"
 msgstr "#"
 
-#: templates/patchsets/ps.inc:12 templates/log/rev.inc:26
+#: templates/log/header.inc:17
+#, php-format
+msgid ""
+"%1$s shows diffs to the previous revision. If you select a revision by "
+"clicking its row, %1$s will show the differences to the selected row."
+msgstr ""
+"%1$s muestra las diferencias con la revisión anterior. Si selecciona una "
+"revisión pulsando su fila, %1$s mostrará las diferencias de la fila "
+"seleccionada."
+
+#: co.php:84
+#, php-format
+msgid "%s Revision %s (%s ago)"
+msgstr "%s Revisión %s (hace %s)"
+
+#: templates/annotate/footer.inc:22
 #, php-format
 msgid "%s ago"
 msgstr "hace %s"
 
-#: templates/diff/hr/footer.inc:19
-#, php-format
-msgid "Added in v.%s"
-msgstr "Añadido en la v.%s"
+#: templates/diff/hr/header.inc:25
+msgid "Added"
+msgstr "Añadido"
 
-#: templates/log/request.inc:57
+#: templates/log/header.inc:31
 msgid "All Branches"
 msgstr "Todas las ramas"
 
-#: co.php:72 templates/log/rev.inc:8
+#: co.php:89
 msgid "Annotate"
-msgstr "Anotar"
+msgstr "Anotaciones"
 
-#: templates/directory/header.inc:18 templates/stats/stats.inc:3
+#: templates/patchsets/header.inc:15 templates/directory/header.inc:12
+#: templates/stats/stats.inc:4 templates/log/header.inc:49
 #: templates/annotate/header.inc:5
 msgid "Author"
 msgstr "Autor"
 
+#: templates/diff/hr/header.inc:35 templates/checkout/checkout.inc:4
+msgid "Author:"
+msgstr "Autor:"
+
 #: templates/directory/back.inc:4
 msgid "Back"
 msgstr "Atrás"
 
-#: templates/checkout/checkout.inc:27 templates/log/rev.inc:31
-msgid "Branch Point for:"
-msgstr "Punto de ramificación de:"
-
-#: templates/checkout/checkout.inc:11 templates/patchsets/ps.inc:14
-#: templates/log/rev.inc:28
+#: templates/diff/hr/header.inc:37 templates/checkout/checkout.inc:5
 msgid "Branch:"
 msgstr "Rama:"
 
-#: lib/Chora.php:465
+#: lib/Chora.php:502 lib/Chora.php:503 lib/Chora.php:507 lib/Chora.php:508
 msgid "Branches"
 msgstr "Ramas"
 
@@ -65,27 +79,20 @@ msgstr "Ramas"
 msgid "Branching to"
 msgstr "Ramificando a"
 
-#: templates/checkout/checkout.inc:35
-#, php-format
-msgid "Changed since <b>%s</b>"
-msgstr "Cambiado desde <b>%s</b>"
-
-#: templates/log/rev.inc:37
-#, fuzzy, php-format
-msgid "Changed since <strong>%s</strong>"
-msgstr "Cambiado desde <b>%s</b>"
+#: lib/Chora.php:392
+msgid "Change repositories:"
+msgstr "Cambiar depósitos:"
 
-#: templates/history/rev.inc:8
+#: templates/history/rev.inc:7
 #, php-format
 msgid "Changed: %s"
 msgstr "Cambiado: %s"
 
-#: co.php:70
-#, php-format
-msgid "Checkout of %s (revision %s)"
-msgstr "Inspección del CVS de %s (revisión %s)"
+#: templates/checkout/checkout.inc:12
+msgid "Checkout"
+msgstr "Inspeccionar"
 
-#: templates/cvsgraph/cvsgraph.inc:11
+#: templates/cvsgraph/cvsgraph.inc:5
 msgid ""
 "Click on the links in between revisions to get a diff between those "
 "revisions."
@@ -93,83 +100,75 @@ msgstr ""
 "Haga click sobre los vínculos entre las revisiones para obtener las "
 "diferencias entre dichas revisiones."
 
-#: templates/cvsgraph/cvsgraph.inc:10
+#: templates/cvsgraph/cvsgraph.inc:4
 msgid "Click on the revisions and branches to display the file."
 msgstr "Haga click sobre las revisiones y ramas para mostrar el archivo."
 
-#: templates/log/rev.inc:42 templates/log/rev.inc:50
-msgid "Colored"
-msgstr "Coloreado"
+#: templates/diff/hr/header.inc:13
+msgid "Column"
+msgstr "Columna"
 
-#: templates/checkout/checkout.inc:19 templates/patchsets/ps.inc:17
-msgid "Commit Tags:"
-msgstr "Actualizar etiquetas:"
-
-#: templates/stats/stats.inc:4
+#: templates/stats/stats.inc:5
 msgid "Commits"
 msgstr "Actualizaciones"
 
-#: templates/log/request.inc:28
+#: templates/diff/hr/header.inc:12
 msgid "Context"
 msgstr "Contexto"
 
-#: config/prefs.php.dist:11
+#: config/.bak/prefs.php.dist:11
 msgid "Customize tasks to run upon logging in to Chora."
 msgstr "Personalice las tareas a ejecutar al iniciar sesión en Chora."
 
-#: templates/directory/header.inc:23
+#: templates/directory/header.inc:15
 msgid "Da_te"
 msgstr "_Fecha"
 
-#: templates/directory/header.inc:23
+#: templates/patchsets/header.inc:14 templates/directory/header.inc:15
+#: templates/log/header.inc:48
 msgid "Date"
 msgstr "Fecha"
 
-#: patchsets.php:66
+#: templates/diff/hr/header.inc:36
+msgid "Date:"
+msgstr "Fecha:"
+
+#: patchsets.php:79
 msgid "Deleted"
 msgstr "Eliminado"
 
-#: templates/directory/file.inc:5 templates/directory/file.inc:17
+#: templates/directory/file.inc:5 templates/directory/file.inc:28
 msgid "Deleted File"
 msgstr "Archivo eliminado"
 
-#: templates/log/rev.inc:13
-msgid "Deselect"
-msgstr "Deseleccionado"
-
-#: patchsets.php:63
+#: patchsets.php:76 templates/log/header.inc:16
 msgid "Diff"
 msgstr "Diferencias"
 
-#: patchsets.php:46
-msgid "Diff All Files"
-msgstr "Diferencias de todos los archivos"
-
-#: diff.php:69
+#: diff.php:71
 #, php-format
 msgid "Diff for %s between version %s and %s"
 msgstr "Diferencias de %s entre la versión %s y la %s"
 
-#: templates/log/rev.inc:49
-#, fuzzy, php-format
-msgid "Diffs to <strong>%s</strong>"
-msgstr "Diferencias con <b>%s</b>"
-
-#: templates/log/rev.inc:41
-#, php-format
-msgid "Diffs to version %s"
-msgstr "Diferencias con la versión %s"
-
-#: templates/directory/dir.inc:5 templates/directory/dir.inc:7
+#: templates/directory/dir.inc:4 templates/directory/dir.inc:6
 msgid "Directory"
 msgstr "Directorio"
 
-#: annotate.php:30 co.php:73 templates/log/rev.inc:10
+#: annotate.php:33 co.php:90
 msgid "Download"
 msgstr "Descargar"
 
+#: templates/diff/hr/header.inc:9
+msgid "Download diff as: "
+msgstr "Descargar diff como: "
+
+#: templates/checkout/checkout.inc:23
+#, php-format
+msgid "Download revision %s"
+msgstr "Descargar revisión %s"
+
 # ¿Dónde está?
-#: templates/log/request.inc:30
+#: templates/diff/hr/header.inc:14
 msgid "Ed Script"
 msgstr "Script Ed"
 
@@ -181,120 +180,108 @@ msgstr "Error"
 msgid "Error Encountered"
 msgstr "Se ha encontrado un error"
 
-#: templates/directory/file.inc:7 templates/directory/file.inc:19
-#: templates/directory/header.inc:8
+#: templates/directory/header.inc:6 templates/directory/file.inc:7
+#: templates/directory/file.inc:30
 msgid "File"
 msgstr "Archivo"
 
-#: templates/patchsets/ps.inc:21
-msgid "Files Changed:"
-msgstr "Archivos cambiados:"
+#: templates/patchsets/header.inc:16
+msgid "Files"
+msgstr "Archivos"
 
-#: templates/log/request.inc:44
-msgid "Get Diffs"
+#: templates/diff/hr/header.inc:16
+msgid "Get Diff"
 msgstr "Obtener diferencias"
 
-#: lib/Chora.php:468
-msgid "Graph"
-msgstr "Gráfico"
-
-#: cvsgraph.php:48
+#: cvsgraph.php:51
 #, php-format
 msgid "Graph for %s"
 msgstr "Gráfico de %s"
 
-#: browse.php:40
+#: browse.php:42
 msgid "Hide Deleted Files"
 msgstr "Ocultar archivos eliminados"
 
-#: browse.php:40
+#: browse.php:44
 msgid "Hide _Deleted Files"
 msgstr "_Ocultar archivos eliminados"
 
-#: templates/log/request.inc:26
-msgid "Human Readable"
-msgstr "Legible por las personas"
-
-#: templates/directory/header.inc:25
+#: templates/directory/header.inc:17
 msgid "Last Log"
 msgstr "Último registro"
 
-#: templates/diff/hr/header.inc:5
-#, php-format
-msgid "Last Log Message for rev %s:"
-msgstr "Último mensaje de la revisión %s en el registro:"
-
-#: templates/diff/hr/footer.inc:5
-msgid "Legend:"
-msgstr "Leyenda:"
-
-#: templates/annotate/header.inc:7
+#: templates/annotate/header.inc:8
 msgid "Line"
 msgstr "Línea"
 
-#: templates/diff/hr/row.inc:3 templates/diff/hr/row.inc:7
+#: templates/diff/hr/row.inc:3 templates/diff/hr/row.inc:6
 #, php-format
 msgid "Line %s"
 msgstr "Línea %s"
 
-#: templates/headerbar.inc:11
+#: templates/headerbar.inc:5
 msgid "Location:"
 msgstr "Ubicación:"
 
-#: templates/checkout/checkout.inc:42
-msgid "Log:"
-msgstr "Registro:"
+#: templates/patchsets/header.inc:17 templates/diff/hr/header.inc:30
+#: templates/log/header.inc:50 templates/checkout/checkout.inc:1
+msgid "Log Message"
+msgstr "Mensaje de registro"
 
-#: config/prefs.php.dist:10
+#: config/.bak/prefs.php.dist:10
 msgid "Login Tasks"
 msgstr "Tareas de inicio de sesión"
 
-#: lib/Chora.php:459
+#: lib/Chora.php:488 lib/Chora.php:489
 msgid "Logs"
 msgstr "Registros"
 
-#: templates/log/rev.inc:43 templates/log/rev.inc:51
-msgid "Long"
-msgstr "Largo"
-
-#: diff.php:61
+#: diff.php:44
 msgid "Malformed Query"
 msgstr "Consulta malformada"
 
-#: patchsets.php:59
+#: lib/Block/tree_menu.php:3
+msgid "Menu List"
+msgstr "Lista del menú"
+
+#: templates/diff/hr/header.inc:26
+msgid "Modified"
+msgstr "Modificado"
+
+#: patchsets.php:72
 msgid "New File"
 msgstr "Nuevo archivo"
 
-#: templates/diff/hr/nochange.inc:6
-msgid "No Viewable Change"
+#: templates/diff/hr/nochange.inc:3
+msgid "No Visible Changes"
 msgstr "Sin cambios visibles"
 
-#: templates/log/rev.inc:44 templates/log/rev.inc:52
-msgid "NoWhitespaceChanges"
-msgstr "Sin considerar espacios en blanco"
+#: templates/log/footer.inc:8
+#, php-format
+msgid ""
+"Only showing the 100 latest revisions. %sShow all revisions?</a> (may take a "
+"while)."
+msgstr ""
+"Sólo se muestran las 100 últimas revisiones. %s¿Mostrar todas las revisiones?"
+"</a> (puede llevar un rato)."
 
-#: config/prefs.php.dist:9
+#: config/.bak/prefs.php.dist:9
 msgid "Other Options"
 msgstr "Otras opciones"
 
-#: lib/Chora.php:368
-msgid "Other Repositories"
-msgstr "Otros depósitos"
-
 #: templates/directory/back.inc:4
 msgid "Parent Directory"
 msgstr "Directorio anterior"
 
-#: templates/patchsets/ps.inc:6
-#, php-format
-msgid "PatchSet %s</span> by %s"
-msgstr "Parche %s</span> de %s"
+#: templates/patchsets/header.inc:13
+msgid "Patchset"
+msgstr "Parche"
 
-#: lib/Chora.php:462
+#: lib/Chora.php:495 lib/Chora.php:496
 msgid "Patchsets"
 msgstr "Parches"
 
-#: patchsets.php:27
+#: patchsets.php:35
 #, php-format
 msgid "Patchsets for %s"
 msgstr "Parches de %s"
@@ -303,71 +290,84 @@ msgstr "Parches de %s"
 msgid "Please contact"
 msgstr "Póngase en contacto con"
 
-#: templates/diff/hr/footer.inc:11
-#, php-format
-msgid "Removed in v.%s"
-msgstr "Eliminado en v.%s"
+#: templates/annotate/header.inc:7
+msgid "Prev"
+msgstr "Ant"
+
+#: templates/diff/hr/header.inc:27
+msgid "Removed"
+msgstr "Eliminado"
 
-#: templates/log/request.inc:14
-msgid "Retrieve diffs between:"
-msgstr "Obtener diferencias entre:"
+#: lib/api.php:30
+msgid "Repositories"
+msgstr "Depósitos"
 
-#: templates/directory/header.inc:13 templates/annotate/header.inc:6
+#: templates/directory/header.inc:9 templates/annotate/header.inc:6
 msgid "Rev"
 msgstr "Rev"
 
-#: co.php:77
-#, php-format
-msgid "Revision %s for file %s not found."
-msgstr "No se encontró la revisión %s del archivo %s."
+#: templates/log/header.inc:47
+msgid "Revision"
+msgstr "Revisión"
 
-#: templates/checkout/checkout.inc:4
+#: browse.php:157
 #, php-format
-msgid "Revision <b>%s</b>, <i>%s</i> (%s ago) by %s"
-msgstr "Revisión <b>%s</b>, <i>%s</i> (desde %s) por %s"
+msgid "Revisions for %s"
+msgstr "Revisión de %s"
+
+#: templates/patchsets/header.inc:4
+msgid "Search Patchsets:"
+msgstr "Buscar parches:"
+
+#: templates/log/header.inc:4
+msgid "Search Revisions:"
+msgstr "Buscar revisiones:"
 
-#: templates/log/rev.inc:15
-msgid "Select for Diff"
-msgstr "Seleccione para diferencias"
+#: templates/log/header.inc:29
+msgid "Show Branch:"
+msgstr "Mostrar rama:"
 
 #: browse.php:42
 msgid "Show Deleted Files"
 msgstr "Mostrar archivos eliminados"
 
-#: browse.php:42
+#: templates/log/header.inc:18
+msgid "Show Differences"
+msgstr "Mostrar diferencias"
+
+#: browse.php:44
 msgid "Show _Deleted Files"
 msgstr "_Mostrar archivos eliminados"
 
-#: templates/log/request.inc:29
-msgid "Side-by-Side"
-msgstr "Lado-a-Lado"
+#: templates/log/rev.inc:4
+msgid "Show changes to the previous revision"
+msgstr "Mostrar cambios desde la revisión anterior"
 
-#: templates/directory/header.inc:7 templates/directory/header.inc:12
-#: templates/directory/header.inc:17 templates/directory/header.inc:22
-msgid "Sort Order"
-msgstr "Tipo de ordenación"
+#: templates/log/rev.inc:6
+msgid "Show changes to the selected revision"
+msgstr "Mostrar cambios con respecto a la revisión seleccionada"
+
+#: templates/log/header.inc:13
+#, php-format
+msgid "Show diffs between %s and %s"
+msgstr "Mostrar diferencias entre %s y %s"
 
-#: annotate.php:27
+#: annotate.php:30
 #, php-format
-msgid "Source Annotation of %s for version %s"
-msgstr "Anotación origen de %s, para la versión %s"
+msgid "Source Annotation of %s (revision %s)"
+msgstr "Anotación origen de %s (revisión %s)"
 
-#: history.php:160
+#: history.php:163
 #, php-format
 msgid "Source Branching View for %s"
 msgstr "Vista ramificada origen de %s"
 
-#: browse.php:32
+#: browse.php:35
 #, php-format
 msgid "Source Directory of /%s"
 msgstr "Directorio origen de /%s"
 
-#: browse.php:126
-#, php-format
-msgid "Source Log for %s"
-msgstr "Registro origen de %s"
-
-#: lib/Chora.php:182
+#: lib/Chora.php:179
 msgid ""
 "SourceRoot not found! This could be a misconfiguration by the server "
 "administrator, or the server could be having temporary problems. Please try "
@@ -377,7 +377,7 @@ msgstr ""
 "del administrador del servidor, o que el servidor tenga problemas "
 "temporales. Vuelva a probar más tarde."
 
-#: lib/Chora.php:470
+#: lib/Chora.php:513 lib/Chora.php:514
 msgid "Statistics"
 msgstr "Estadísticas"
 
@@ -386,106 +386,80 @@ msgstr "Estad
 msgid "Statistics for %s"
 msgstr "Estadísticas de %s"
 
-#: templates/log/rev.inc:34
+#: templates/patchsets/ps.inc:22 templates/log/rev.inc:14
+msgid "Tags"
+msgstr "Etiquetas"
+
+#: templates/diff/hr/header.inc:38 templates/checkout/checkout.inc:6
 msgid "Tags:"
 msgstr "Etiquetas:"
 
-#: templates/log/request.inc:5
-msgid ""
-"This form allows you to request diffs between any two revisions of a file.  "
-"You may select a symbolic revision name using the selection box or you may "
-"type in a numeric name using the type-in text box."
-msgstr ""
-" Este formulario le permite solicitar diferencias entre dos revisiones "
-"cualesquiera de un archivo. Puede elegir un nombre de revisión simbólico en "
-"la casilla de selección o puede escribir un nombre numérico en la casilla de "
-"texto."
-
-#: templates/headerbar.inc:16
+#: templates/headerbar.inc:9
 msgid "Tracking Branch"
 msgstr "Rama de seguimiento"
 
-#: templates/log/request.inc:23
-msgid "Type:"
-msgstr "Tipo:"
-
-#: templates/log/request.inc:27
+#: templates/diff/hr/header.inc:11
 msgid "Unified"
 msgstr "Unificado"
 
-#: templates/log/request.inc:18 templates/log/request.inc:38
-msgid "Use Text Field"
-msgstr "Usar campos de texto"
+#: templates/diff/hr/header.inc:24
+msgid "Unmodified"
+msgstr "Sin cambios"
 
-#: config/prefs.php.dist:37
+#: config/.bak/prefs.php.dist:37
 msgid "Use last viewed file or directory at login time"
 msgstr "Usar último archivo o directorio visto al iniciar sesión"
 
-#: annotate.php:29 templates/log/rev.inc:9
+#: templates/diff/hr/header.inc:48 templates/diff/hr/header.inc:49
+#, php-format
+msgid "Version %s"
+msgstr "Versión %s"
+
+#: annotate.php:32 templates/log/header.inc:34
 msgid "View"
 msgstr "Ver"
 
-#: templates/log/request.inc:60
-msgid "View Branch"
-msgstr "Ver rama"
-
-#: templates/log/request.inc:54
-msgid "View revisions on:"
-msgstr "Ver revisiones en:"
-
-#: lib/Chora.php:472
+#: lib/Chora.php:517
 msgid "View:"
 msgstr "Ver:"
 
-#: templates/directory/header.inc:18
+#: templates/directory/header.inc:12
 msgid "_Author"
 msgstr "A_utor"
 
-#: lib/Chora.php:465
+#: lib/Chora.php:504 lib/Chora.php:509
 msgid "_Branches"
 msgstr "_Ramas"
 
-#: lib/Chora.php:439
+#: lib/Chora.php:469
 msgid "_Browse"
 msgstr "_Examinar"
 
-#: templates/directory/header.inc:8
+#: templates/directory/header.inc:6
 msgid "_File"
 msgstr "_Archivo"
 
-#: lib/Chora.php:468
-msgid "_Graph"
-msgstr "_Gráfico"
-
-#: lib/Chora.php:459
+#: lib/Chora.php:490
 msgid "_Logs"
 msgstr "_Registros"
 
-#: lib/Chora.php:462
+#: lib/Chora.php:497
 msgid "_Patchsets"
 msgstr "_Parches"
 
-#: templates/directory/header.inc:13
+#: templates/directory/header.inc:9
 msgid "_Rev"
 msgstr "_Rev"
 
-#: lib/Chora.php:470
+#: lib/Chora.php:515
 msgid "_Statistics"
 msgstr "_Estadísticas"
 
-#: templates/log/request.inc:35
-msgid "and:"
-msgstr "y:"
-
-#: templates/history/rev.inc:4 templates/log/rev.inc:5
+#: templates/history/rev.inc:3
 #, php-format
 msgid "by %s"
 msgstr "por %s"
 
-#: templates/diff/hr/footer.inc:15
-msgid "changed lines"
-msgstr "líneas modificadas"
-
 #: lib/Chora.php:31
 msgid "day"
 msgstr "día"
@@ -535,11 +509,6 @@ msgstr "segundo"
 msgid "seconds"
 msgstr "segundos"
 
-#: templates/diff/hr/header.inc:13 templates/diff/hr/header.inc:14
-#, php-format
-msgid "version %s"
-msgstr "versión %s"
-
 #: lib/Chora.php:42
 msgid "very little time"
 msgstr "muy poco tiempo"
diff --git a/po/fi_FI.po b/po/fi_FI.po
index 883fe40..23d5b68 100644
--- a/po/fi_FI.po
+++ b/po/fi_FI.po
@@ -1,61 +1,76 @@
 # Finnish translation for Chora.
-# Copyright (C)
+# Copyright
 # Tero Matinlassi <terom at iki.fi>, 2002.
-# Leena Heino <liinu at uta.fi>, 2002-2004.
+# Leena Heino <liinu at uta.fi>, 2002-2008.
 #
 msgid ""
 msgstr ""
-"Project-Id-Version: Chora 2.0-cvs\n"
+"Project-Id-Version: Chora 2.1-cvs\n"
 "Report-Msgid-Bugs-To: dev at lists.horde.org\n"
-"POT-Creation-Date: 2006-03-09 10:26+0200\n"
-"PO-Revision-Date: 2006-01-05 12:59+0200\n"
+"POT-Creation-Date: 2008-11-03 12:17+0200\n"
+"PO-Revision-Date: 2008-11-03 12:59+0200\n"
 "Last-Translator: Leena Heino <liinu at uta.fi>\n"
 "Language-Team: Finnish <i18n at lists.horde.org>\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=iso-8859-1\n"
 "Content-Transfer-Encoding: 8-bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
 
 #: templates/annotate/header.inc:4
 msgid "#"
 msgstr "Nro"
 
-#: templates/patchsets/ps.inc:12 templates/log/rev.inc:26
+#: templates/log/header.inc:17
+#, php-format
+msgid ""
+"%1$s shows diffs to the previous revision. If you select a revision by "
+"clicking its row, %1$s will show the differences to the selected row."
+msgstr ""
+"%1$s näyttää muutokset edelliseen revisioon nähden. Jos haluat valita muun "
+"revision, napsauttamalla haluamaasi riviä, niin %1$s näyttää muutokset "
+"valitun rivin välillä."
+
+#: co.php:84
+#, php-format
+msgid "%s Revision %s (%s ago)"
+msgstr "%s Revisio %s (%s sitten)"
+
+#: templates/annotate/footer.inc:22
 #, php-format
 msgid "%s ago"
 msgstr "%s sitten"
 
-#: templates/diff/hr/footer.inc:19
-#, php-format
-msgid "Added in v.%s"
-msgstr "Lisätty v.%s"
+#: templates/diff/hr/header.inc:25
+msgid "Added"
+msgstr "Lisätty"
 
-#: templates/log/request.inc:57
+#: templates/log/header.inc:31
 msgid "All Branches"
 msgstr "Kaikki haarat"
 
-#: co.php:72 templates/log/rev.inc:8
+#: co.php:89
 msgid "Annotate"
 msgstr "Selitysmerkintä"
 
-#: templates/stats/stats.inc:3 templates/directory/header.inc:18
+#: templates/stats/stats.inc:4 templates/patchsets/header.inc:15
+#: templates/log/header.inc:49 templates/directory/header.inc:12
 #: templates/annotate/header.inc:5
 msgid "Author"
 msgstr "Tekijä"
 
-#: templates/directory/back.inc:3
+#: templates/diff/hr/header.inc:35 templates/checkout/checkout.inc:4
+msgid "Author:"
+msgstr "Tekijä:"
+
+#: templates/directory/back.inc:4
 msgid "Back"
 msgstr "Takaisin"
 
-#: templates/log/rev.inc:31 templates/checkout/checkout.inc:27
-msgid "Branch Point for:"
-msgstr "Haarautumispiste tiedostolle:"
-
-#: templates/patchsets/ps.inc:14 templates/log/rev.inc:28
-#: templates/checkout/checkout.inc:11
+#: templates/diff/hr/header.inc:37 templates/checkout/checkout.inc:5
 msgid "Branch:"
 msgstr "Haara:"
 
-#: lib/Chora.php:465
+#: lib/Chora.php:502 lib/Chora.php:503 lib/Chora.php:507 lib/Chora.php:508
 msgid "Branches"
 msgstr "Haarat"
 
@@ -63,51 +78,40 @@ msgstr "Haarat"
 msgid "Branching to"
 msgstr "Haarautumispiste:"
 
-#: templates/checkout/checkout.inc:35
-#, php-format
-msgid "Changed since <b>%s</b>"
-msgstr "Muutettu alkaen <b>%s</b>"
-
-#: templates/log/rev.inc:37
-#, php-format
-msgid "Changed since <strong>%s</strong>"
-msgstr "Muutettu alkaen <strong>%s</strong>"
+#: lib/Chora.php:392
+msgid "Change repositories:"
+msgstr "Vaihda varastoa:"
 
-#: templates/history/rev.inc:8
+#: templates/history/rev.inc:7
 #, php-format
 msgid "Changed: %s"
 msgstr "Muutettu: %s"
 
-#: co.php:70
-#, php-format
-msgid "Checkout of %s (revision %s)"
-msgstr "Checkout %s:lle (revisio %s)"
+#: templates/checkout/checkout.inc:12
+msgid "Checkout"
+msgstr "Tuo"
 
-#: templates/cvsgraph/cvsgraph.inc:11
+#: templates/cvsgraph/cvsgraph.inc:5
 msgid ""
 "Click on the links in between revisions to get a diff between those "
 "revisions."
 msgstr ""
-"Napsauta linkkiä revisioiden välillä saadaksesi eroavaisuudet näiden kahden "
+"Napsauta linkkia revisioiden välillä saadaksesi eroavaisuudet näiden kahden "
 "revision välillä."
 
-#: templates/cvsgraph/cvsgraph.inc:10
+#: templates/cvsgraph/cvsgraph.inc:4
 msgid "Click on the revisions and branches to display the file."
 msgstr "Napsauta revisiota tai haaraa nähdäksesi tiedoston."
 
-#: templates/log/rev.inc:42 templates/log/rev.inc:50
-msgid "Colored"
-msgstr "Värillinen"
+#: templates/diff/hr/header.inc:13
+msgid "Column"
+msgstr "Sarake"
 
-#: templates/patchsets/ps.inc:17 templates/checkout/checkout.inc:19
-msgid "Commit Tags:"
-msgstr "Kommitointi tagit:"
-
-#: templates/stats/stats.inc:4
+#: templates/stats/stats.inc:5
 msgid "Commits"
 msgstr "Kommitoinnit"
 
-#: templates/log/request.inc:28
+#: templates/diff/hr/header.inc:12
 msgid "Context"
 msgstr "Konteksti"
 
@@ -115,58 +119,54 @@ msgstr "Konteksti"
 msgid "Customize tasks to run upon logging in to Chora."
 msgstr "Aseta Choraan sisäänkirjautumisen yhteydessä tehtävät asiat."
 
-#: templates/directory/header.inc:23
+#: templates/directory/header.inc:15
 msgid "Da_te"
 msgstr "Päi_väys"
 
-#: templates/directory/header.inc:23
+#: templates/patchsets/header.inc:14 templates/log/header.inc:48
+#: templates/directory/header.inc:15
 msgid "Date"
 msgstr "Päiväys"
 
-#: patchsets.php:73
+#: templates/diff/hr/header.inc:36
+msgid "Date:"
+msgstr "Päiväys:"
+
+#: patchsets.php:79
 msgid "Deleted"
 msgstr "Poistettu"
 
-#: templates/directory/file.inc:5 templates/directory/file.inc:17
+#: templates/directory/file.inc:5 templates/directory/file.inc:28
 msgid "Deleted File"
 msgstr "Poistettu tiedosto"
 
-#: templates/log/rev.inc:13
-msgid "Deselect"
-msgstr "Poista valinta"
-
-#: patchsets.php:70
+#: patchsets.php:76 templates/log/header.inc:16
 msgid "Diff"
 msgstr "Muutokset"
 
-#: patchsets.php:53
-msgid "Diff All Files"
-msgstr "Muutokset kaikista tiedostoista"
-
-#: diff.php:69
+#: diff.php:71
 #, php-format
 msgid "Diff for %s between version %s and %s"
 msgstr "Muutokset %s:ssä versioiden %s ja %s välillä"
 
-#: templates/log/rev.inc:49
-#, php-format
-msgid "Diffs to <strong>%s</strong>"
-msgstr "Muutokset nähden <strong>%s</strong>"
-
-#: templates/log/rev.inc:41
-#, php-format
-msgid "Diffs to version %s"
-msgstr "Muutokset versioon %s"
-
 #: templates/directory/dir.inc:4 templates/directory/dir.inc:6
 msgid "Directory"
 msgstr "Hakemisto"
 
-#: annotate.php:30 co.php:73 templates/log/rev.inc:10
+#: annotate.php:33 co.php:90
 msgid "Download"
 msgstr "Tallenna"
 
-#: templates/log/request.inc:30
+#: templates/diff/hr/header.inc:9
+msgid "Download diff as: "
+msgstr "Tallenna diff nimellä:"
+
+#: templates/checkout/checkout.inc:23
+#, php-format
+msgid "Download revision %s"
+msgstr "Lataa revisio %s"
+
+#: templates/diff/hr/header.inc:14
 msgid "Ed Script"
 msgstr "Ed-skripti"
 
@@ -178,120 +178,108 @@ msgstr "Virhe"
 msgid "Error Encountered"
 msgstr "Tapahtui virhe"
 
-#: templates/directory/file.inc:7 templates/directory/file.inc:19
-#: templates/directory/header.inc:8
+#: templates/directory/file.inc:7 templates/directory/file.inc:30
+#: templates/directory/header.inc:6
 msgid "File"
 msgstr "Tiedosto"
 
-#: templates/patchsets/ps.inc:21
-msgid "Files Changed:"
-msgstr "Tiedostoja muuttunut:"
+#: templates/patchsets/header.inc:16
+msgid "Files"
+msgstr "Tiedostot"
 
-#: templates/log/request.inc:44
-msgid "Get Diffs"
+#: templates/diff/hr/header.inc:16
+msgid "Get Diff"
 msgstr "Hae muutokset"
 
-#: lib/Chora.php:468
-msgid "Graph"
-msgstr "Graafi"
-
-#: cvsgraph.php:48
+#: cvsgraph.php:51
 #, php-format
 msgid "Graph for %s"
 msgstr "Graafi %s:lle"
 
-#: browse.php:40
+#: browse.php:42
 msgid "Hide Deleted Files"
 msgstr "Piilota poistetut tiedostot"
 
-#: browse.php:40
+#: browse.php:44
 msgid "Hide _Deleted Files"
 msgstr "Piilota _poistetut tiedostot"
 
-#: templates/log/request.inc:26
-msgid "Human Readable"
-msgstr "Ihmiselle lukukelpoinen"
-
-#: templates/directory/header.inc:25
+#: templates/directory/header.inc:17
 msgid "Last Log"
 msgstr "Viimeisin lokiviesti"
 
-#: templates/diff/hr/header.inc:5
-#, php-format
-msgid "Last Log Message for rev %s:"
-msgstr "Viimeisin lokiviesti revisiolle %s:"
-
-#: templates/diff/hr/footer.inc:5
-msgid "Legend:"
-msgstr "Selite:"
-
-#: templates/annotate/header.inc:7
+#: templates/annotate/header.inc:8
 msgid "Line"
 msgstr "Rivi"
 
-#: templates/diff/hr/row.inc:3 templates/diff/hr/row.inc:7
+#: templates/diff/hr/row.inc:3 templates/diff/hr/row.inc:6
 #, php-format
 msgid "Line %s"
 msgstr "Rivi %s"
 
-#: templates/headerbar.inc:11
+#: templates/headerbar.inc:5
 msgid "Location:"
 msgstr "Paikka:"
 
-#: templates/checkout/checkout.inc:42
-msgid "Log:"
-msgstr "Loki:"
+#: templates/patchsets/header.inc:17 templates/log/header.inc:50
+#: templates/diff/hr/header.inc:30 templates/checkout/checkout.inc:1
+msgid "Log Message"
+msgstr "Kirjausviesti"
 
 #: config/prefs.php.dist:10
 msgid "Login Tasks"
 msgstr "Tehtävät sisäänkirjautumisessa"
 
-#: lib/Chora.php:459
+#: lib/Chora.php:488 lib/Chora.php:489
 msgid "Logs"
 msgstr "Lokit"
 
-#: templates/log/rev.inc:43 templates/log/rev.inc:51
-msgid "Long"
-msgstr "Pitkä"
-
-#: diff.php:61
+#: diff.php:44
 msgid "Malformed Query"
 msgstr "Epäkelpo kysely"
 
-#: patchsets.php:66
+#: lib/Block/tree_menu.php:3
+msgid "Menu List"
+msgstr "Valikkolista"
+
+#: templates/diff/hr/header.inc:26
+msgid "Modified"
+msgstr "Muokattu"
+
+#: patchsets.php:72
 msgid "New File"
 msgstr "Uusi tiedosto"
 
-#: templates/diff/hr/nochange.inc:6
-msgid "No Viewable Change"
-msgstr "Ei katsottavissa olevia muutoksia"
+#: templates/diff/hr/nochange.inc:3
+msgid "No Visible Changes"
+msgstr "Ei näkyviä muutoksia"
 
-#: templates/log/rev.inc:44 templates/log/rev.inc:52
-msgid "NoWhitespaceChanges"
-msgstr "EiVälilyontiMuutoksia"
+#: templates/log/footer.inc:8
+#, php-format
+msgid ""
+"Only showing the 100 latest revisions. %sShow all revisions?</a> (may take a "
+"while)."
+msgstr ""
+"Näytä vain viimeiset 100 revisiota.  %sNäytä kaikki revisiot?</a> (Tämä voi "
+"kestää kauan)."
 
 #: config/prefs.php.dist:9
 msgid "Other Options"
 msgstr "Muut asetukset"
 
-#: lib/Chora.php:368
-msgid "Other Repositories"
-msgstr "Muut varastot"
-
-#: templates/directory/back.inc:3
+#: templates/directory/back.inc:4
 msgid "Parent Directory"
 msgstr "Edellinen hakemisto"
 
-#: templates/patchsets/ps.inc:6
-#, php-format
-msgid "PatchSet %s</span> by %s"
-msgstr "Paikkajoukko %s</span> by %s:llä"
+#: templates/patchsets/header.inc:13
+msgid "Patchset"
+msgstr "Paikkajoukko"
 
-#: lib/Chora.php:462
+#: lib/Chora.php:495 lib/Chora.php:496
 msgid "Patchsets"
-msgstr "Paikkajoukko"
+msgstr "Paikkajoukkot"
 
-#: patchsets.php:32
+#: patchsets.php:35
 #, php-format
 msgid "Patchsets for %s"
 msgstr "Paikkajoukko %s:lle"
@@ -300,71 +288,84 @@ msgstr "Paikkajoukko %s:lle"
 msgid "Please contact"
 msgstr "Ota yhteys"
 
-#: templates/diff/hr/footer.inc:11
-#, php-format
-msgid "Removed in v.%s"
-msgstr "Poistettu versiossa %s"
+#: templates/annotate/header.inc:7
+msgid "Prev"
+msgstr "Edel"
 
-#: templates/log/request.inc:14
-msgid "Retrieve diffs between:"
-msgstr "Hae muutokset välillä:"
+#: templates/diff/hr/header.inc:27
+msgid "Removed"
+msgstr "Poistettu"
+
+#: lib/api.php:30
+msgid "Repositories"
+msgstr "Varastot"
 
-#: templates/directory/header.inc:13 templates/annotate/header.inc:6
+#: templates/directory/header.inc:9 templates/annotate/header.inc:6
 msgid "Rev"
 msgstr "Rev."
 
-#: co.php:77
-#, php-format
-msgid "Revision %s for file %s not found."
-msgstr "Revisiota %s tiedostosta %s ei löytynyt."
+#: templates/log/header.inc:47
+msgid "Revision"
+msgstr "Revisio"
 
-#: templates/checkout/checkout.inc:4
+#: browse.php:157
 #, php-format
-msgid "Revision <b>%s</b>, <i>%s</i> (%s ago) by %s"
-msgstr "Revisio <b>%s</b>, <i>%s</i> (%s sitten) tekijä %s"
+msgid "Revisions for %s"
+msgstr "Revisiot %s:lle"
+
+#: templates/patchsets/header.inc:4
+msgid "Search Patchsets:"
+msgstr "Hae paaikkajoukkosta:"
 
-#: templates/log/rev.inc:15
-msgid "Select for Diff"
-msgstr "Valitse muutosvertailuun"
+#: templates/log/header.inc:4
+msgid "Search Revisions:"
+msgstr "Hae revisioista:"
+
+#: templates/log/header.inc:29
+msgid "Show Branch:"
+msgstr "Näytä Haara:"
 
 #: browse.php:42
 msgid "Show Deleted Files"
 msgstr "Näytä poistetut tiedostot"
 
-#: browse.php:42
+#: templates/log/header.inc:18
+msgid "Show Differences"
+msgstr "Näyt eroavaisuudet"
+
+#: browse.php:44
 msgid "Show _Deleted Files"
 msgstr "Näytä _poistetut tiedostot"
 
-#: templates/log/request.inc:29
-msgid "Side-by-Side"
-msgstr "Vierekkäin"
+#: templates/log/rev.inc:4
+msgid "Show changes to the previous revision"
+msgstr "Näytä muutokset edelliseen revisioon nähden"
+
+#: templates/log/rev.inc:6
+msgid "Show changes to the selected revision"
+msgstr "Näytä muutokset valittujen revisioiden välillä"
 
-#: templates/directory/header.inc:7 templates/directory/header.inc:12
-#: templates/directory/header.inc:17 templates/directory/header.inc:22
-msgid "Sort Order"
-msgstr "Järjestyksen suunta"
+#: templates/log/header.inc:13
+#, php-format
+msgid "Show diffs between %s and %s"
+msgstr "Näytä muutokset versioiden %s ja %s välillä"
 
-#: annotate.php:27
+#: annotate.php:30
 #, php-format
-msgid "Source Annotation of %s for version %s"
-msgstr "Lähdekoodin selitysmerkinnät tiedostolle %s versiolle %s"
+msgid "Source Annotation of %s (revision %s)"
+msgstr "Lähdekoodin selitysmerkinnät %s (revisio %s)"
 
-#: history.php:160
+#: history.php:163
 #, php-format
 msgid "Source Branching View for %s"
 msgstr "Näkymä lähdekoodin haarautumiseen %s:lle"
 
-#: browse.php:32
+#: browse.php:35
 #, php-format
 msgid "Source Directory of /%s"
 msgstr "Lähdehakemisto /%s"
 
-#: browse.php:126
-#, php-format
-msgid "Source Log for %s"
-msgstr "Lähdetiedoston lokitiedot %s:lle"
-
-#: lib/Chora.php:182
+#: lib/Chora.php:179
 msgid ""
 "SourceRoot not found! This could be a misconfiguration by the server "
 "administrator, or the server could be having temporary problems. Please try "
@@ -374,7 +375,7 @@ msgstr ""
 "palvelimen asetuksissa, tai palvelimella on väliaikainen virhetilanne. Yritä "
 "uudestaan myöhemmin."
 
-#: lib/Chora.php:470
+#: lib/Chora.php:513 lib/Chora.php:514
 msgid "Statistics"
 msgstr "Tilastot"
 
@@ -383,104 +384,79 @@ msgstr "Tilastot"
 msgid "Statistics for %s"
 msgstr "Tilastot %s:lle"
 
-#: templates/log/rev.inc:34
+#: templates/patchsets/ps.inc:22 templates/log/rev.inc:14
+msgid "Tags"
+msgstr "Tagit"
+
+#: templates/diff/hr/header.inc:38 templates/checkout/checkout.inc:6
 msgid "Tags:"
 msgstr "Tagit:"
 
-#: templates/log/request.inc:5
-msgid ""
-"This form allows you to request diffs between any two revisions of a file.  "
-"You may select a symbolic revision name using the selection box or you may "
-"type in a numeric name using the type-in text box."
-msgstr ""
-"Tällä lomakkeella voi pyytää vertailuja minkä tahansa kahden tiedoston "
-"revision välillä. Voit valita symbolisen revision nimen käyttäen "
-"valintalaatikkoa tai voit syöttää numeerisen nimen tekstikenttään."
-
-#: templates/headerbar.inc:16
+#: templates/headerbar.inc:9
 msgid "Tracking Branch"
 msgstr "Seurattava haara"
 
-#: templates/log/request.inc:23
-msgid "Type:"
-msgstr "Tyyppi:"
-
-#: templates/log/request.inc:27
+#: templates/diff/hr/header.inc:11
 msgid "Unified"
 msgstr "Yhdistetty"
 
-#: templates/log/request.inc:18 templates/log/request.inc:38
-msgid "Use Text Field"
-msgstr "Käytä tekstikenttää"
+#: templates/diff/hr/header.inc:24
+msgid "Unmodified"
+msgstr "Muokkaamaton"
 
 #: config/prefs.php.dist:37
 msgid "Use last viewed file or directory at login time"
 msgstr "Käytä viimeksi katsottua tiedostoa tai hakemistoa sisäänkirjautuessa"
 
-#: annotate.php:29 templates/log/rev.inc:9
+#: templates/diff/hr/header.inc:48 templates/diff/hr/header.inc:49
+#, php-format
+msgid "Version %s"
+msgstr "Versio %s"
+
+#: annotate.php:32 templates/log/header.inc:34
 msgid "View"
 msgstr "Näytä"
 
-#: templates/log/request.inc:60
-msgid "View Branch"
-msgstr "Näytä haara"
-
-#: templates/log/request.inc:54
-msgid "View revisions on:"
-msgstr "Näytä revisiot:"
-
-#: lib/Chora.php:472
+#: lib/Chora.php:517
 msgid "View:"
 msgstr "Näytä:"
 
-#: templates/directory/header.inc:18
+#: templates/directory/header.inc:12
 msgid "_Author"
 msgstr "_Tekijä"
 
-#: lib/Chora.php:465
+#: lib/Chora.php:504 lib/Chora.php:509
 msgid "_Branches"
 msgstr "_Haarat"
 
-#: lib/Chora.php:439
+#: lib/Chora.php:469
 msgid "_Browse"
 msgstr "_Selaa"
 
-#: templates/directory/header.inc:8
+#: templates/directory/header.inc:6
 msgid "_File"
 msgstr "_Tiedosto"
 
-#: lib/Chora.php:468
-msgid "_Graph"
-msgstr "_Graafi"
-
-#: lib/Chora.php:459
+#: lib/Chora.php:490
 msgid "_Logs"
 msgstr "_Lokitiedot"
 
-#: lib/Chora.php:462
+#: lib/Chora.php:497
 msgid "_Patchsets"
 msgstr "_Korjausjoukot"
 
-#: templates/directory/header.inc:13
+#: templates/directory/header.inc:9
 msgid "_Rev"
 msgstr "_Rev"
 
-#: lib/Chora.php:470
+#: lib/Chora.php:515
 msgid "_Statistics"
 msgstr "T_ilastot"
 
-#: templates/log/request.inc:35
-msgid "and:"
-msgstr "ja:"
-
-#: templates/log/rev.inc:5 templates/history/rev.inc:4
+#: templates/history/rev.inc:3
 #, php-format
 msgid "by %s"
-msgstr "%s:llä"
-
-#: templates/diff/hr/footer.inc:15
-msgid "changed lines"
-msgstr "muutettua riviä"
+msgstr "%s"
 
 #: lib/Chora.php:31
 msgid "day"
@@ -531,11 +507,6 @@ msgstr "sekunti"
 msgid "seconds"
 msgstr "sekuntia"
 
-#: templates/diff/hr/header.inc:13 templates/diff/hr/header.inc:14
-#, php-format
-msgid "version %s"
-msgstr "versio %s"
-
 #: lib/Chora.php:42
 msgid "very little time"
 msgstr "hyvin vähän aikaa"
diff --git a/po/fr_FR.po b/po/fr_FR.po
index 4c9646c..2563871 100644
--- a/po/fr_FR.po
+++ b/po/fr_FR.po
@@ -1,14 +1,14 @@
 # French translation for Chora
-# Copyright (C) 2001 Free Software Foundation, Inc.
+# Copyright 2001 Free Software Foundation, Inc.
 # Mathieu Arnold <mat at absolight.net>, 2001
-# Copyright (C) 2002 Thierry Thomas.
+# Copyright 2002, 2007 Thierry Thomas.
 #
 msgid ""
 msgstr ""
-"Project-Id-Version: Chora 2.0-cvs\n"
+"Project-Id-Version: Chora 2.1-cvs\n"
 "Report-Msgid-Bugs-To: dev at lists.horde.org\n"
-"POT-Creation-Date: 2005-10-13 17:16+0200\n"
-"PO-Revision-Date: 2003-01-04 14:46+0100\n"
+"POT-Creation-Date: 2007-02-21 23:55+0100\n"
+"PO-Revision-Date: 2007-02-21 21:14+0100\n"
 "Last-Translator: Thierry Thomas <thierry at pompo.net>\n"
 "Language-Team: French <i18n at lists.horde.org>\n"
 "MIME-Version: 1.0\n"
@@ -20,43 +20,57 @@ msgstr ""
 msgid "#"
 msgstr "N°"
 
-#: templates/patchsets/ps.inc:12 templates/log/rev.inc:26
+#: templates/log/header.inc:16
+#, php-format
+msgid ""
+"%1$s shows diffs to the previous revision. If you select a revision by "
+"clicking its row, %1$s will show the differences to the selected row."
+msgstr ""
+"%1$s montre les différences avec la version précédente. If vous sélectionnez "
+"une révision en cliquant sur sa ligne, %1$s montrera le delta avec la "
+"version sélectionnée."
+
+#: co.php:82
+#, php-format
+msgid "%s Revision %s (%s ago)"
+msgstr "%s Révision %s (il y a %s)"
+
+#: templates/annotate/footer.inc:22
 #, php-format
 msgid "%s ago"
 msgstr "il y a %s"
 
-#: templates/diff/hr/footer.inc:19
-#, php-format
-msgid "Added in v.%s"
-msgstr "Ajouté en v.%s"
+#: templates/diff/hr/header.inc:24
+msgid "Added"
+msgstr "Ajouté"
 
-#: templates/log/request.inc:57
+#: templates/log/header.inc:28
 msgid "All Branches"
 msgstr "Toutes les branches"
 
-#: co.php:72 templates/log/rev.inc:8
+#: co.php:87
 msgid "Annotate"
 msgstr "Annoter"
 
-#: templates/directory/header.inc:18 templates/stats/stats.inc:3
+#: templates/stats/stats.inc:4 templates/patchsets/header.inc:15
+#: templates/log/header.inc:57 templates/directory/header.inc:12
 #: templates/annotate/header.inc:5
 msgid "Author"
 msgstr "Auteur"
 
+#: templates/checkout/checkout.inc:4
+msgid "Author:"
+msgstr "Auteur :"
+
 #: templates/directory/back.inc:4
 msgid "Back"
 msgstr "Retour"
 
-#: templates/checkout/checkout.inc:27 templates/log/rev.inc:31
-msgid "Branch Point for:"
-msgstr "Embranchement pour :"
-
-#: templates/checkout/checkout.inc:11 templates/patchsets/ps.inc:14
-#: templates/log/rev.inc:28
+#: templates/checkout/checkout.inc:5
 msgid "Branch:"
 msgstr "Branche :"
 
-#: lib/Chora.php:465
+#: lib/Chora.php:449
 msgid "Branches"
 msgstr "Branches"
 
@@ -64,50 +78,36 @@ msgstr "Branches"
 msgid "Branching to"
 msgstr "Embranchement pour"
 
-#: templates/checkout/checkout.inc:35
-#, php-format
-msgid "Changed since <b>%s</b>"
-msgstr "Modifications depuis <b>%s</b> "
-
-#: templates/log/rev.inc:37
-#, fuzzy, php-format
-msgid "Changed since <strong>%s</strong>"
-msgstr "Modifications depuis <b>%s</b> "
-
-#: templates/history/rev.inc:8
+#: templates/history/rev.inc:7
 #, php-format
 msgid "Changed: %s"
 msgstr "Modifications : %s"
 
-#: co.php:70
-#, fuzzy, php-format
-msgid "Checkout of %s (revision %s)"
-msgstr "Checkout de %s (révision %s)"
+#: templates/checkout/checkout.inc:12
+msgid "Checkout"
+msgstr "Récupérer"
 
-#: templates/cvsgraph/cvsgraph.inc:11
+#: templates/cvsgraph/cvsgraph.inc:5
 msgid ""
 "Click on the links in between revisions to get a diff between those "
 "revisions."
 msgstr ""
+"Cliquer sur les liens entre les révisions pour obtenir le delta entre ces "
+"révisions."
 
-#: templates/cvsgraph/cvsgraph.inc:10
+#: templates/cvsgraph/cvsgraph.inc:4
 msgid "Click on the revisions and branches to display the file."
-msgstr ""
-
-#: templates/log/rev.inc:42 templates/log/rev.inc:50
-msgid "Colored"
-msgstr ""
+msgstr "Cliquer sur les révisions et les branches pour afficher le fichier."
 
-#: templates/checkout/checkout.inc:19 templates/patchsets/ps.inc:17
-#, fuzzy
-msgid "Commit Tags:"
-msgstr "Tags :"
+#: templates/diff/hr/header.inc:12
+msgid "Column"
+msgstr "Colonne"
 
-#: templates/stats/stats.inc:4
+#: templates/stats/stats.inc:5
 msgid "Commits"
-msgstr ""
+msgstr "Validations"
 
-#: templates/log/request.inc:28
+#: templates/diff/hr/header.inc:11
 msgid "Context"
 msgstr "Contexte"
 
@@ -115,62 +115,50 @@ msgstr "Contexte"
 msgid "Customize tasks to run upon logging in to Chora."
 msgstr "Paramétrer les tâches à exécuter lors de la connexion à Chora."
 
-#: templates/directory/header.inc:23
-#, fuzzy
+#: templates/directory/header.inc:15
 msgid "Da_te"
-msgstr "Date"
+msgstr "Da_te"
 
-#: templates/directory/header.inc:23
+#: templates/patchsets/header.inc:14 templates/log/header.inc:56
+#: templates/directory/header.inc:15
 msgid "Date"
 msgstr "Date"
 
-#: patchsets.php:66
-#, fuzzy
+#: patchsets.php:83
 msgid "Deleted"
-msgstr "Fichier supprimé"
+msgstr "Supprimé"
 
 #: templates/directory/file.inc:5 templates/directory/file.inc:17
 msgid "Deleted File"
 msgstr "Fichier supprimé"
 
-#: templates/log/rev.inc:13
-#, fuzzy
-msgid "Deselect"
-msgstr "Dé-sélectionner"
-
-#: patchsets.php:63
-#, fuzzy
+#: patchsets.php:80 templates/log/header.inc:15
 msgid "Diff"
-msgstr "Récupérer le delta"
+msgstr "Delta"
 
-#: patchsets.php:46
-msgid "Diff All Files"
-msgstr ""
-
-#: diff.php:69
+#: diff.php:51
 #, php-format
 msgid "Diff for %s between version %s and %s"
 msgstr "Delta de %s entre les versions %s et %s"
 
-#: templates/log/rev.inc:49
-#, php-format
-msgid "Diffs to <strong>%s</strong>"
-msgstr ""
-
-#: templates/log/rev.inc:41
-#, php-format
-msgid "Diffs to version %s"
-msgstr "Delta avec la version %s"
-
-#: templates/directory/dir.inc:5 templates/directory/dir.inc:7
+#: templates/directory/dir.inc:4 templates/directory/dir.inc:6
 msgid "Directory"
 msgstr "Répertoire"
 
-#: annotate.php:30 co.php:73 templates/log/rev.inc:10
+#: annotate.php:31 co.php:88
 msgid "Download"
 msgstr "Télécharger"
 
-#: templates/log/request.inc:30
+#: templates/diff/hr/header.inc:8
+msgid "Download diff as: "
+msgstr "Télécharger le delta en tant que : "
+
+#: templates/checkout/checkout.inc:23
+#, php-format
+msgid "Download revision %s"
+msgstr "Télécharger la révision %s"
+
+#: templates/diff/hr/header.inc:13
 msgid "Ed Script"
 msgstr "Script Ed"
 
@@ -183,108 +171,99 @@ msgid "Error Encountered"
 msgstr "Une erreur est survenue"
 
 #: templates/directory/file.inc:7 templates/directory/file.inc:19
-#: templates/directory/header.inc:8
+#: templates/directory/header.inc:6
 msgid "File"
 msgstr "Fichier"
 
-#: templates/patchsets/ps.inc:21
-#, fuzzy
-msgid "Files Changed:"
-msgstr "Modifications : %s"
+#: templates/patchsets/header.inc:16
+msgid "Files"
+msgstr "Fichiers"
 
-#: templates/log/request.inc:44
-msgid "Get Diffs"
+#: templates/diff/hr/header.inc:15
+msgid "Get Diff"
 msgstr "Récupérer le delta"
 
-#: lib/Chora.php:468
+#: lib/Chora.php:452
 msgid "Graph"
-msgstr ""
+msgstr "Graphe"
 
 #: cvsgraph.php:48
 #, php-format
 msgid "Graph for %s"
-msgstr ""
+msgstr "Graphe de %s"
 
-#: browse.php:40
+#: browse.php:55
 msgid "Hide Deleted Files"
 msgstr "Cacher les fichiers supprimés"
 
-#: browse.php:40
-#, fuzzy
+#: browse.php:55
 msgid "Hide _Deleted Files"
-msgstr "Cacher les fichiers supprimés"
-
-#: templates/log/request.inc:26
-msgid "Human Readable"
-msgstr "Embellie"
+msgstr "Cacher les fichiers _supprimés"
 
-#: templates/directory/header.inc:25
+#: templates/directory/header.inc:17
 msgid "Last Log"
-msgstr "Dernier message du journal"
+msgstr "Dernier journal"
 
-#: templates/diff/hr/header.inc:5
-#, php-format
-msgid "Last Log Message for rev %s:"
-msgstr "Dernier message du journal pour la révision %s :"
-
-#: templates/diff/hr/footer.inc:5
-msgid "Legend:"
-msgstr ""
-
-#: templates/annotate/header.inc:7
+#: templates/annotate/header.inc:8
 msgid "Line"
 msgstr "Ligne"
 
-#: templates/diff/hr/row.inc:3 templates/diff/hr/row.inc:7
+#: templates/diff/hr/row.inc:3 templates/diff/hr/row.inc:6
 #, php-format
 msgid "Line %s"
 msgstr "Ligne %s"
 
-#: templates/headerbar.inc:11
+#: templates/headerbar.inc:5
 msgid "Location:"
 msgstr "Emplacement :"
 
-#: templates/checkout/checkout.inc:42
-#, fuzzy
-msgid "Log:"
-msgstr "Journaux"
+#: templates/patchsets/header.inc:17 templates/log/header.inc:58
+#: templates/checkout/checkout.inc:1
+msgid "Log Message"
+msgstr "Message du journal"
 
 #: config/prefs.php.dist:10
 msgid "Login Tasks"
 msgstr "Tâches à la connexion"
 
-#: lib/Chora.php:459
+#: lib/Chora.php:443
 msgid "Logs"
 msgstr "Journaux"
 
-#: templates/log/rev.inc:43 templates/log/rev.inc:51
-#, fuzzy
-msgid "Long"
-msgstr "Journaux"
-
-#: diff.php:61
+#: diff.php:25
 msgid "Malformed Query"
-msgstr ""
+msgstr "Requête mal formée"
 
-#: patchsets.php:59
-#, fuzzy
+#: lib/Block/tree_menu.php:3
+msgid "Menu List"
+msgstr "Menu"
+
+#: templates/diff/hr/header.inc:25
+msgid "Modified"
+msgstr "Modifié"
+
+#: patchsets.php:76
 msgid "New File"
-msgstr "Fichier"
+msgstr "Nouveau fichier"
 
-#: templates/diff/hr/nochange.inc:6
-msgid "No Viewable Change"
+#: templates/diff/hr/nochange.inc:3
+msgid "No Visible Changes"
 msgstr "Aucun changement visible"
 
-#: templates/log/rev.inc:44 templates/log/rev.inc:52
-#, fuzzy
-msgid "NoWhitespaceChanges"
-msgstr "Aucun changement visible"
+#: templates/log/header.inc:44
+#, php-format
+msgid ""
+"Only showing the 100 latest revisions. %sShow all revisions?</a> (may take a "
+"while)."
+msgstr ""
+"Seules les 100 dernières révisions sont présentées. %sMontrer toutes les "
+"révisions ?</a> (peu prendre un certain temps)."
 
 #: config/prefs.php.dist:9
 msgid "Other Options"
 msgstr "Autres options"
 
-#: lib/Chora.php:368
+#: lib/Chora.php:346
 msgid "Other Repositories"
 msgstr "Autres dépôts"
 
@@ -292,213 +271,196 @@ msgstr "Autres d
 msgid "Parent Directory"
 msgstr "Répertoire parent"
 
-#: templates/patchsets/ps.inc:6
-#, php-format
-msgid "PatchSet %s</span> by %s"
-msgstr ""
+#: templates/patchsets/header.inc:13
+msgid "Patchset"
+msgstr "Jeu de patches"
 
-#: lib/Chora.php:462
+#: lib/Chora.php:446
 msgid "Patchsets"
-msgstr ""
+msgstr "Jeux de patches"
 
-#: patchsets.php:27
-#, fuzzy, php-format
+#: patchsets.php:32
+#, php-format
 msgid "Patchsets for %s"
-msgstr "Statistiques CVS de %s"
+msgstr "Jeux de patches pour %s"
 
 #: templates/error_page.inc:13
 msgid "Please contact"
-msgstr "Veuillez contactez"
+msgstr "Veuillez contacter"
 
-#: templates/diff/hr/footer.inc:11
-#, php-format
-msgid "Removed in v.%s"
-msgstr "Supprimé en v.%s"
+#: templates/annotate/header.inc:7
+msgid "Prev"
+msgstr "Préc"
+
+#: templates/diff/hr/header.inc:26
+msgid "Removed"
+msgstr "Retiré"
 
-#: templates/log/request.inc:14
-msgid "Retrieve diffs between:"
-msgstr "Récupérer les deltas entre :"
+#: lib/api.php:30
+msgid "Repositories"
+msgstr "Dépôts"
 
-#: templates/directory/header.inc:13 templates/annotate/header.inc:6
+#: templates/directory/header.inc:9 templates/annotate/header.inc:6
 msgid "Rev"
 msgstr "Rév"
 
-#: co.php:77
-#, php-format
-msgid "Revision %s for file %s not found."
-msgstr ""
+#: templates/log/header.inc:55
+msgid "Revision"
+msgstr "Révision"
 
-#: templates/checkout/checkout.inc:4
+#: browse.php:150
 #, php-format
-msgid "Revision <b>%s</b>, <i>%s</i> (%s ago) by %s"
-msgstr ""
+msgid "Revisions for %s"
+msgstr "Révision pour %s"
 
-#: templates/log/rev.inc:15
-msgid "Select for Diff"
-msgstr "Sélect. pour delta"
+#: templates/patchsets/header.inc:4
+msgid "Search Patchsets:"
+msgstr "Rechercher des jeux de patches :"
 
-#: browse.php:42
+#: templates/log/header.inc:4
+msgid "Search Revisions:"
+msgstr "Rechercher des révisions :"
+
+#: templates/log/header.inc:26
+msgid "Show Branch:"
+msgstr "Montrer la branche :"
+
+#: browse.php:57
 msgid "Show Deleted Files"
 msgstr "Afficher les fichiers supprimés"
 
-#: browse.php:42
-#, fuzzy
+#: templates/log/header.inc:17
+msgid "Show Differences"
+msgstr "Montrer les différences"
+
+#: browse.php:57
 msgid "Show _Deleted Files"
-msgstr "Afficher les fichiers supprimés"
+msgstr "Afficher les fichiers _supprimés"
+
+#: templates/log/rev.inc:4
+msgid "Show changes to the previous revision"
+msgstr "Montrer les changements par rapport à la révision précédente"
 
-#: templates/log/request.inc:29
-msgid "Side-by-Side"
-msgstr "Côte à côte"
+#: templates/log/rev.inc:6
+msgid "Show changes to the selected revision"
+msgstr "Montrer les changements par rapport à la révision sélectionnée"
 
-#: templates/directory/header.inc:7 templates/directory/header.inc:12
-#: templates/directory/header.inc:17 templates/directory/header.inc:22
-msgid "Sort Order"
-msgstr "Ordre de tri"
+#: templates/log/header.inc:12
+#, php-format
+msgid "Show diffs between %s and %s"
+msgstr "Montrer les deltas entre %s et %s"
 
-#: annotate.php:27
-#, fuzzy, php-format
-msgid "Source Annotation of %s for version %s"
-msgstr "Annotations de %s pour la version %s"
+#: annotate.php:28
+#, php-format
+msgid "Source Annotation of %s (revision %s)"
+msgstr "Annotation de %s (révision %s)"
 
-#: history.php:160
-#, fuzzy, php-format
+#: history.php:161
+#, php-format
 msgid "Source Branching View for %s"
 msgstr "Vue par branches pour %s"
 
-#: browse.php:32
-#, fuzzy, php-format
+#: browse.php:49
+#, php-format
 msgid "Source Directory of /%s"
-msgstr "Répertoire /%s"
+msgstr "Répertoire source de /%s"
 
-#: browse.php:126
-#, fuzzy, php-format
-msgid "Source Log for %s"
-msgstr "Journal CVS de %s"
-
-#: lib/Chora.php:182
+#: lib/Chora.php:176
 msgid ""
 "SourceRoot not found! This could be a misconfiguration by the server "
 "administrator, or the server could be having temporary problems. Please try "
 "again later."
 msgstr ""
+"Racine non trouvée ! Ça peut être dû à une mauvaise configuration du "
+"serveur, ou à un problème temporaire. Veuillez réessayer plus tard."
 
-#: lib/Chora.php:470
+#: lib/Chora.php:454
 msgid "Statistics"
 msgstr "Statistiques"
 
-#: stats.php:27
-#, fuzzy, php-format
+#: stats.php:25
+#, php-format
 msgid "Statistics for %s"
-msgstr "Statistiques CVS de %s"
+msgstr "Statistiques de %s"
+
+#: templates/patchsets/ps.inc:22 templates/log/rev.inc:14
+msgid "Tags"
+msgstr "Tags"
 
-#: templates/log/rev.inc:34
-#, fuzzy
+#: templates/checkout/checkout.inc:6
 msgid "Tags:"
 msgstr "Tags :"
 
-#: templates/log/request.inc:5
-#, fuzzy
-msgid ""
-"This form allows you to request diffs between any two revisions of a file.  "
-"You may select a symbolic revision name using the selection box or you may "
-"type in a numeric name using the type-in text box."
-msgstr ""
-" Ce formulaire vous permet de demander des deltas entre n'importe quelle "
-"révision d'un fichier. Vous pouvez sélectionner un nom de révision "
-"symbolique en utilisant le menu de sélection ou vous pouvez inscrire un "
-"numéro de révision dans le champ texte."
-
-#: templates/headerbar.inc:16
+#: templates/headerbar.inc:9
 msgid "Tracking Branch"
 msgstr "Branche suivie"
 
-#: templates/log/request.inc:23
-msgid "Type:"
-msgstr "Type :"
-
-#: templates/log/request.inc:27
+#: templates/diff/hr/header.inc:10
 msgid "Unified"
 msgstr "Unifié"
 
-#: templates/log/request.inc:18 templates/log/request.inc:38
-msgid "Use Text Field"
-msgstr "Utiliser le champ texte"
+#: templates/diff/hr/header.inc:23
+msgid "Unmodified"
+msgstr "Inchangé"
 
 #: config/prefs.php.dist:37
 msgid "Use last viewed file or directory at login time"
 msgstr "Revenir au dernier endroit visité lors de la connexion"
 
-#: annotate.php:29 templates/log/rev.inc:9
+#: templates/diff/hr/header.inc:30 templates/diff/hr/header.inc:31
+#, php-format
+msgid "Version %s"
+msgstr "Version %s"
+
+#: annotate.php:30 templates/log/header.inc:31
 msgid "View"
 msgstr "Voir"
 
-#: templates/log/request.inc:60
-msgid "View Branch"
-msgstr "Voir les branches"
-
-#: templates/log/request.inc:54
-msgid "View revisions on:"
-msgstr "Voir les révisions pour :"
-
-#: lib/Chora.php:472
+#: lib/Chora.php:456
 msgid "View:"
 msgstr "Voir :"
 
-#: templates/directory/header.inc:18
-#, fuzzy
+#: templates/directory/header.inc:12
 msgid "_Author"
-msgstr "Auteur"
+msgstr "_Auteur"
 
-#: lib/Chora.php:465
-#, fuzzy
+#: lib/Chora.php:449
 msgid "_Branches"
-msgstr "Branches"
+msgstr "_Branches"
 
-#: lib/Chora.php:439
+#: lib/Chora.php:425
 msgid "_Browse"
-msgstr ""
+msgstr "_Lister"
 
-#: templates/directory/header.inc:8
-#, fuzzy
+#: templates/directory/header.inc:6
 msgid "_File"
-msgstr "Fichier"
+msgstr "_Fichier"
 
-#: lib/Chora.php:468
+#: lib/Chora.php:452
 msgid "_Graph"
-msgstr ""
+msgstr "_Graphe"
 
-#: lib/Chora.php:459
-#, fuzzy
+#: lib/Chora.php:443
 msgid "_Logs"
-msgstr "Journaux"
+msgstr "_Journaux"
 
-#: lib/Chora.php:462
+#: lib/Chora.php:446
 msgid "_Patchsets"
-msgstr ""
+msgstr "Jeux de _patches"
 
-#: templates/directory/header.inc:13
-#, fuzzy
+#: templates/directory/header.inc:9
 msgid "_Rev"
-msgstr "Rév"
+msgstr "_Rév"
 
-#: lib/Chora.php:470
-#, fuzzy
+#: lib/Chora.php:454
 msgid "_Statistics"
-msgstr "Statistiques"
-
-#: templates/log/request.inc:35
-msgid "and:"
-msgstr "et :"
+msgstr "_Statistiques"
 
-#: templates/history/rev.inc:4 templates/log/rev.inc:5
+#: templates/history/rev.inc:3
 #, php-format
 msgid "by %s"
 msgstr "par %s"
 
-#: templates/diff/hr/footer.inc:15
-msgid "changed lines"
-msgstr "lignes modifiées"
-
 #: lib/Chora.php:31
 msgid "day"
 msgstr "jour"
@@ -548,11 +510,6 @@ msgstr "seconde"
 msgid "seconds"
 msgstr "secondes"
 
-#: templates/diff/hr/header.inc:13 templates/diff/hr/header.inc:14
-#, php-format
-msgid "version %s"
-msgstr "version %s"
-
 #: lib/Chora.php:42
 msgid "very little time"
 msgstr "très peu de temps"
diff --git a/po/it_IT.po b/po/it_IT.po
new file mode 100644
index 0000000..5e072bd
--- /dev/null
+++ b/po/it_IT.po
@@ -0,0 +1,528 @@
+# Italian translations for chora package.
+# Copyright 2008-2009 The Horde Project
+# This file is distributed under the same license as the chora package.
+# Fabio Pedretti <fabio.pedretti at ing.unibs.it>, 2008.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: chora v2.1-cvs\n"
+"Report-Msgid-Bugs-To: dev at lists.horde.org\n"
+"POT-Creation-Date: 2008-09-05 18:12+0200\n"
+"PO-Revision-Date: 2008-09-05 19:15+0200\n"
+"Last-Translator: Fabio Pedretti <fabio.pedretti at ing.unibs.it>\n"
+"Language-Team: i18n at lists.horde.org\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=ISO-8859-1\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: templates/annotate/header.inc:4
+msgid "#"
+msgstr "#"
+
+#: templates/log/header.inc:17
+#, php-format
+msgid ""
+"%1$s shows diffs to the previous revision. If you select a revision by "
+"clicking its row, %1$s will show the differences to the selected row."
+msgstr ""
+"%1$s mostra le differenze con la revisione precedente. Se scegi una "
+"revisione cliccando la sua riga, %1$s ti mostrerà le differenze con la riga "
+"selezionata."
+
+#: co.php:84
+#, php-format
+msgid "%s Revision %s (%s ago)"
+msgstr "%s revisione %s (%s prima)"
+
+#: templates/annotate/footer.inc:22
+#, php-format
+msgid "%s ago"
+msgstr "%s prima"
+
+#: templates/diff/hr/header.inc:25
+msgid "Added"
+msgstr "Aggiunto"
+
+#: templates/log/header.inc:31
+msgid "All Branches"
+msgstr "Tutti i rami"
+
+#: co.php:89
+msgid "Annotate"
+msgstr "Annotato"
+
+#: templates/stats/stats.inc:4 templates/patchsets/header.inc:15
+#: templates/log/header.inc:49 templates/directory/header.inc:12
+#: templates/annotate/header.inc:5
+msgid "Author"
+msgstr "Autore"
+
+#: templates/diff/hr/header.inc:35 templates/checkout/checkout.inc:4
+msgid "Author:"
+msgstr "Autore:"
+
+#: templates/directory/back.inc:4
+msgid "Back"
+msgstr "Indietro"
+
+#: templates/diff/hr/header.inc:37 templates/checkout/checkout.inc:5
+msgid "Branch:"
+msgstr "Ramo:"
+
+#: lib/Chora.php:498 lib/Chora.php:499 lib/Chora.php:503 lib/Chora.php:504
+msgid "Branches"
+msgstr "Rami"
+
+#: templates/history/branch_cell.inc:2
+msgid "Branching to"
+msgstr "Ramificato da"
+
+#: lib/Chora.php:388
+msgid "Change repositories:"
+msgstr "Cambia deposito:"
+
+#: templates/history/rev.inc:7
+#, php-format
+msgid "Changed: %s"
+msgstr "Cambiato: %s"
+
+#: templates/checkout/checkout.inc:12
+msgid "Checkout"
+msgstr "Controllo"
+
+#: templates/cvsgraph/cvsgraph.inc:5
+msgid ""
+"Click on the links in between revisions to get a diff between those "
+"revisions."
+msgstr ""
+"Clicca sul collegamento tra le revisioni per ottenere una la differenza tra "
+"loro."
+
+#: templates/cvsgraph/cvsgraph.inc:4
+msgid "Click on the revisions and branches to display the file."
+msgstr "Clicca sulle revisioni e ramifica per vedere il file."
+
+#: templates/diff/hr/header.inc:13
+msgid "Column"
+msgstr "Colonna"
+
+#: templates/stats/stats.inc:5
+msgid "Commits"
+msgstr "Assegnare"
+
+#: templates/diff/hr/header.inc:12
+msgid "Context"
+msgstr "Contesto"
+
+#: config/prefs.php.dist:11
+msgid "Customize tasks to run upon logging in to Chora."
+msgstr "Personalizza le operazioni da eseguire quando entri in Chora."
+
+#: templates/directory/header.inc:15
+msgid "Da_te"
+msgstr "Da_ta"
+
+#: templates/patchsets/header.inc:14 templates/log/header.inc:48
+#: templates/directory/header.inc:15
+msgid "Date"
+msgstr "Data"
+
+#: templates/diff/hr/header.inc:36
+msgid "Date:"
+msgstr "Data:"
+
+#: patchsets.php:79
+msgid "Deleted"
+msgstr "Eliminato"
+
+#: templates/directory/file.inc:5 templates/directory/file.inc:28
+msgid "Deleted File"
+msgstr "File eliminato"
+
+#: patchsets.php:76 templates/log/header.inc:16
+msgid "Diff"
+msgstr "Differenza"
+
+#: diff.php:71
+#, php-format
+msgid "Diff for %s between version %s and %s"
+msgstr "Differenza per %s tra le versioni %s e %s"
+
+#: templates/directory/dir.inc:4 templates/directory/dir.inc:6
+msgid "Directory"
+msgstr "Directory"
+
+#: annotate.php:33 co.php:90
+msgid "Download"
+msgstr "Scarica"
+
+#: templates/diff/hr/header.inc:9
+msgid "Download diff as: "
+msgstr "Scarica differenza come: "
+
+#: templates/checkout/checkout.inc:23
+#, php-format
+msgid "Download revision %s"
+msgstr "Scarica revisione %s"
+
+#: templates/diff/hr/header.inc:14
+msgid "Ed Script"
+msgstr "Ed Script"
+
+#: templates/error_page.inc:4
+msgid "Error"
+msgstr "Errore"
+
+#: templates/error_page.inc:7
+msgid "Error Encountered"
+msgstr "Si è verificato un errore"
+
+#: templates/directory/file.inc:7 templates/directory/file.inc:30
+#: templates/directory/header.inc:6
+msgid "File"
+msgstr "File"
+
+#: templates/patchsets/header.inc:16
+msgid "Files"
+msgstr "File"
+
+#: templates/diff/hr/header.inc:16
+msgid "Get Diff"
+msgstr "Ottieni differenze"
+
+#: cvsgraph.php:51
+#, php-format
+msgid "Graph for %s"
+msgstr "Grafico di %s"
+
+#: browse.php:42
+msgid "Hide Deleted Files"
+msgstr "Nascondi File eliminati"
+
+#: browse.php:44
+msgid "Hide _Deleted Files"
+msgstr "_Nascondi File Eliminati"
+
+#: templates/directory/header.inc:17
+msgid "Last Log"
+msgstr "Ultima autenticazione"
+
+#: templates/annotate/header.inc:8
+msgid "Line"
+msgstr "Riga"
+
+#: templates/diff/hr/row.inc:3 templates/diff/hr/row.inc:6
+#, php-format
+msgid "Line %s"
+msgstr "Riga %s"
+
+#: templates/headerbar.inc:5
+msgid "Location:"
+msgstr "Luogo:"
+
+#: templates/patchsets/header.inc:17 templates/log/header.inc:50
+#: templates/diff/hr/header.inc:30 templates/checkout/checkout.inc:1
+msgid "Log Message"
+msgstr "Messaggio utente"
+
+#: config/prefs.php.dist:10
+msgid "Login Tasks"
+msgstr "Operazioni di autenticazione"
+
+#: lib/Chora.php:484 lib/Chora.php:485
+msgid "Logs"
+msgstr "Utenti"
+
+#: diff.php:44
+msgid "Malformed Query"
+msgstr "Richiesta malformulata"
+
+#: lib/Block/tree_menu.php:3
+msgid "Menu List"
+msgstr "Menu Lista"
+
+#: templates/diff/hr/header.inc:26
+msgid "Modified"
+msgstr "Modificato"
+
+#: patchsets.php:72
+msgid "New File"
+msgstr "Nuovo File"
+
+#: templates/diff/hr/nochange.inc:3
+msgid "No Visible Changes"
+msgstr "Nessun cambiamento visibile"
+
+#: templates/log/footer.inc:8
+#, php-format
+msgid ""
+"Only showing the 100 latest revisions. %sShow all