Commit 3694fcf8 authored by Sean Madsen's avatar Sean Madsen

Merge branch 'master' into book-home-pages

Conflicts:
	src/AppBundle/Resources/views/Read/book_list.html.twig
	src/AppBundle/Resources/views/Read/home.html.twig
parents 14a8f5a7 bc33e12c
# Learn more about services, parameters and containers at
# http://symfony.com/doc/current/book/service_container.html
parameters:
# parameter_name: value
books_dir: %kernel.root_dir%/../books
publish_path_root: %kernel.root_dir%/../web
services:
library:
class: AppBundle\Model\Library
arguments:
- %kernel.root_dir%/../books
- %books_dir%
github.hook.processor:
class: AppBundle\Utils\GitHubHookProcessor
mkdocs:
class: AppBundle\Utils\MkDocs
arguments:
- '@filesystem'
- '@file_locator'
publisher:
class: AppBundle\Utils\Publisher
arguments:
......@@ -21,7 +28,8 @@ services:
- '@filesystem'
- '@library'
- %publisher_repos_dir%
- %kernel.root_dir%/../web
- %publish_path_root%
- '@mkdocs'
publish.logger:
class: Monolog\Logger
......
name: Developer guide
weight: 10
description: For CiviCRM developers
category: Core
langs:
en:
repo: 'https://github.com/civicrm/civicrm-dev-docs'
......
name: User guide
weight: -100
description: Aimed at day to day users of CiviCRM.
category: Core
langs:
en:
repo: 'https://github.com/civicrm/civicrm-user-guide'
......
......@@ -11,9 +11,14 @@ class ReadController extends Controller {
* @Route("/")
*/
public function HomeAction() {
/** @var \AppBundle\Model\Library $library */
$library = $this->get('library');
return $this->render(
'AppBundle:Read:home.html.twig',
array('library' => $this->get('library'))
'AppBundle:Read:home.html.twig', array(
'core_books' => $library->getBooksByCategory('Core'),
'extensions_books' => $library->getBooksByCategory('Extensions'),
)
);
}
......
......@@ -33,6 +33,12 @@ class Book {
*/
public $weight;
/**
*
* @var string (e.g. "Core", "Extensions") Should be in sentence case
*/
public $category;
/**
* Creates a book based on a yaml conf file
*
......@@ -49,6 +55,8 @@ class Book {
foreach ($yaml['langs'] as $code => $languageData) {
$this->languages[] = new Language($code, $languageData);
}
$category = isset($yaml['category']) ? $yaml['category'] : "Extensions";
$this->category = ucwords($category);
}
/**
......
......@@ -99,6 +99,23 @@ class Library {
return $chosen;
}
/**
* Gives an array of book objects which match a given category
*
* @param string $category
*
* @return array of Book objects
*/
public function getBooksByCategory($category) {
$books = array();
foreach ($this->books as $book) {
if ($book->category == $category) {
$books[] = $book;
}
}
return $books;
}
/**
* See which books/languages are using a given repository.
*
......
$( document ).ready(function() {
$("dt").wrapInner("<span class='inner'></span>");
});
/* make header non-fixed */
.md-container,
.md-main__inner {
padding-top: 0;
}
.md-header {
position: static;
height: auto;
}
.md-sidebar[data-md-state=lock] {
top: 0;
}
/* Header tweaks */
header a.md-source:hover {
text-decoration: none;
}
/* Logo */
header.md-header .civi-logo {
float: left;
padding: 5px;
}
header.md-header .civi-logo a {
margin: 0;
padding: 0;
}
/* Nav for CiviCRM / Documentation */
header.md-header .civi-header {
padding-top: 0.8rem;
padding-left: 2rem;
font-size: 1.4rem;
font-style: italic;
}
header.md-header .civi-header span {
color: white;
}
header.md-header .edition, header.md-header .other-editions {
color: #A5CCDC !important;
}
header.md-header .other-editions {
padding-left: 2rem;
font-style: normal;
}
/* Custom colors */
button[data-md-color-primary=indigo]{background-color:#3387ac}
[data-md-color-primary=indigo] .md-typeset a{color:#3387ac}
[data-md-color-primary=indigo] .md-header{background-color:#3387ac}
[data-md-color-primary=indigo] .md-nav__item--active > .md-nav__link,[data-md-color-primary=indigo] .md-nav__link:active{color:#3387ac}
button[data-md-color-accent=green]{background-color:#81c459}
[data-md-color-accent=green] .md-typeset a:active,[data-md-color-accent=green] .md-typeset a:hover{color:#81c459}
[data-md-color-accent=green] .md-typeset .codehilite::-webkit-scrollbar-thumb:hover,[data-md-color-accent=green] .md-typeset pre::-webkit-scrollbar-thumb:hover{background-color:#81c459}
[data-md-color-accent=green] .md-nav__link:hover,[data-md-color-accent=green] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=green] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=green] .md-typeset [id] .headerlink:focus,[data-md-color-accent=green] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=green] .md-typeset [id]:target .headerlink{color:#81c459}
[data-md-color-accent=green] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#81c459}
[data-md-color-accent=green] .md-search-result__link:hover{background-color:rgba(129,196,89,.1)}
[data-md-color-accent=green] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#81c459}
@media only screen and (max-width:59.9375em){[data-md-color-primary=indigo] .md-nav__source{background-color:rgba(51,135,172,.9675)}}
@media only screen and (max-width:76.1875em){html [data-md-color-primary=indigo] .md-nav--primary .md-nav__title--site{background-color:#3387ac}}
@media only screen and (min-width:60em){[data-md-color-primary=indigo] .md-nav--secondary{border-left:.4rem solid #3387ac}}
/* definition lists */
dt .inner {
border-bottom: solid 5px #B6D8E6;
}
/* hyperlinks */
a:hover {text-decoration: underline; }
{% extends "base.html" %}
{% block htmltitle %}
{% if page.title %}
<title>{{ page.title }} - {{ config.site_name }} - CiviCRM documentation</title>
{% elif config.site_description %}
<title>{{ config.site_name }} - {{ config.site_description }} - CiviCRM documentation</title>
{% else %}
<title>{{ config.site_name }} - CiviCRM documentation</title>
{% endif %}
{% endblock %}
{% block extrahead %}
<link rel="stylesheet" href="{{ base_url }}/assets/stylesheets/extra.css">
<script src="/static/js/jquery-3.1.1.min.js"></script>
<script src="{{ base_url }}/assets/javascripts/extra.js"></script>
{% endblock %}
{% import "partials/language.html" as lang %}
<footer class="md-footer">
{% if page.previous_page or page.next_page %}
<div class="md-footer-nav">
<nav class="md-footer-nav__inner md-grid">
{% if page.previous_page %}
<a href="{{ page.previous_page.url }}" title="{{ page.previous_page.title }}" class="md-flex md-footer-nav__link md-footer-nav__link--prev" rel="prev">
<div class="md-flex__cell md-flex__cell--shrink">
<i class="md-icon md-icon--arrow-back md-footer-nav__button"></i>
</div>
<div class="md-flex__cell md-flex__cell--stretch md-footer-nav__title">
<span class="md-flex__ellipsis">
<span class="md-footer-nav__direction">
{{ lang.t('footer.previous') }}
</span>
{{ page.previous_page.title }}
</span>
</div>
</a>
{% endif %}
{% if page.next_page %}
<a href="{{ page.next_page.url }}" title="{{ page.next_page.title }}" class="md-flex md-footer-nav__link md-footer-nav__link--next" rel="next">
<div class="md-flex__cell md-flex__cell--stretch md-footer-nav__title">
<span class="md-flex__ellipsis">
<span class="md-footer-nav__direction">
{{ lang.t('footer.next') }}
</span>
{{ page.next_page.title }}
</span>
</div>
<div class="md-flex__cell md-flex__cell--shrink">
<i class="md-icon md-icon--arrow-forward md-footer-nav__button"></i>
</div>
</a>
{% endif %}
</nav>
</div>
{% endif %}
<div class="md-footer-meta md-typeset">
<div class="md-footer-meta__inner md-grid">
<div class="md-footer-copyright">
{% if config.copyright %}
<div class="md-footer-copyright__highlight">
{{ config.copyright }}
</div>
{% endif %}
<a href="https://civicrm.org">CiviCRM</a>
&raquo;
<a href="https://docs.civicrm.org">Documentation</a>
</div>
{% block social %}
{% include "partials/social.html" %}
{% endblock %}
</div>
</div>
</footer>
<header class="md-header" data-md-component="header">
<nav class="md-header-nav md-grid">
<div class="md-flex civi-logo">
<div class="md-flex__cell md-flex__cell--shrink">
<a class="md-logo md-header-nav__button" href="https://civicrm.org" title="CiviCRM home">
<img src="/static/images/logo.svg" height="60" alt="logo" />
</a>
</div>
<div class="md-flex__cell md-flex__cell--shrink">
<label class="md-icon md-icon--menu md-header-nav__button" for="drawer"></label>
</div>
</div>
<div class="md-flex civi-header">
<div class="md-flex__cell md-flex__cell--stretch">
<span class="md-header-nav__parent">
<a href="https://civicrm.org">CiviCRM</a>
</span>
<span class="md-header-nav__parent">
<a href="/">Documentation</a>
</span>
<a href="{{ base_url }}">
{{ config.site_name }}
<span class="edition">({{ config.extra.edition }})</span>
</a>
<a href="{{ config.extra.book_home }}" class="other-editions">
(Other editions)
</a>
</div>
</div>
<div class="md-flex">
<div class="md-flex__cell md-flex__cell--stretch">
<span class="md-flex__ellipsis md-header-nav__title">
{% block site_name %}
{% if page %}
{% for parent in page.ancestors %}
<span class="md-header-nav__parent">
{{ parent.title }}
</span>
{% endfor %}
{% endif %}
{{ page.title | default(config.site_name, true) }}
{% endblock %}
</span>
</div>
<div class="md-flex__cell md-flex__cell--shrink">
{% block search_box %}
<label class="md-icon md-icon--search md-header-nav__button" for="search"></label>
{% include "partials/search.html" %}
{% endblock %}
</div>
<div class="md-flex__cell md-flex__cell--shrink">
<div class="md-header-nav__source">
{% if config.repo_url %}
{% include "partials/source.html" %}
{% endif %}
</div>
</div>
</div>
</nav>
</header>
......@@ -16,7 +16,6 @@
<span class="other-editions">
<a href="/{{ book.slug }}">(Other editions)</a>
</span>
</a>
</div>
<div class="description">
{{ book.description }}
......
......@@ -15,8 +15,12 @@
installation and upgrades</a>
is still on the wiki.</em></p>
<h2>Books</h2>
<h2>Core Books</h2>
{% include 'AppBundle:Read:book_list.html.twig' with {books: library.books} %}
{% include 'AppBundle:Read:book_list.html.twig' with {books: core_books} %}
<h2>Extensions Books</h2>
{% include 'AppBundle:Read:book_list.html.twig' with {books: extensions_books} %}
{% endblock %}
<?php
namespace AppBundle\Utils;
use Symfony\Component\Yaml\Parser;
use Symfony\Component\Yaml\Dumper;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Config\FileLocatorInterface as FileLocator;
use Symfony\Component\Process\Process;
class MkDocs {
/**
* @var Filesystem
*/
private $fs;
/**
* @var FileLocator
*/
private $fileLocator;
/**
* @var string The full filesystem path to the directory containing the
* markdown files
*/
private $sourcePath;
/**
* @var string $destinationPath The full filesystem path to the directory
* where we want the published content to go
*/
private $destinationPath;
/**
* @var string The full filesystem path to the directory which stores
* different possible theme customizations. Within this directory,
* separate directories should exist, per theme, for the
* customizations, named with the same name as the theme.
*/
private $themeCustomPathRoot;
/**
* @var string The full filesystem path to the directory
*/
private $themeCustomPath;
/**
* @var array A associative array with config values to put in the 'extra'
* setting when building the book
*/
private $extraConfig;
/**
* @var string The full filesystem location of the mkdocs.yml config file to
* use when building the book. This is the file as it's stored
* after adjustments we make to it.
*/
private $configFile;
/**
* @param Filesystem $fs
* @param FileLocator $fileLocator
*/
public function __construct(Filesystem $fs, FileLocator $fileLocator) {
$this->fs = $fs;
$this->fileLocator = $fileLocator;
$this->themeCustomPathRoot = $this->fileLocator->locate(
'@AppBundle/Resources/theme-customizations');
}
/**
* Reads the mkdocs.yml config file from the book source. Makes some
* customizations to it, and the write the file into the directory where
* we're going to publish the book. Why put it there? It doesn't need to be in
* the publish destination, but it seems like as good a place as any. It just
* needs to be stored somewhere so that mkdocs can read it while building the
* book.
*/
private function customizeConfig() {
// Read config in
$inFile = "{$this->sourcePath}/mkdocs.yml";
$parser = new Parser();
$config = $parser->parse(file_get_contents($inFile));
// If we have a theme-cumstomization directory which matches the theme used
// in the book, then use these theme customizations when building.
$theme = $config['theme'];
$this->themeCustomPath = "{$this->themeCustomPathRoot}/$theme";
if ($this->fs->exists($this->themeCustomPath)) {
$config['theme_dir'] = $this->themeCustomPath;
}
// Set extra config which was passed into build()
foreach ($this->extraConfig as $key => $val) {
$config['extra'][$key] = $val;
}
// Set up custom config for our Material theme extension
if ($theme == 'material') {
$config['extra']['palette']['primary'] = 'indigo';
$config['extra']['palette']['accent'] = 'green';
if (!isset($config['extra']['edition'])) {
$config['extra']['edition'] = "English / Latest";
}
if (!isset($config['extra']['book_home'])) {
$config['extra']['book_home'] = "/";
}
}
// Dump config out
$dumper = new Dumper();
$this->configFile = dirname($this->destinationPath) . "/"
. basename($this->destinationPath) . "-mkdocs.yml";
$this->fs->dumpFile($this->configFile, $dumper->dump($config, 4));
}
private function getOptions() {
// discard existing build files -- build site from scratch
$opts[] = "--clean";
// abort the build if any errors occur
$opts[] = "--strict";
// use our customized config file
$opts[] = "--config-file {$this->configFile}";
// this is where the finished site should go
$opts[] = "--site-dir {$this->destinationPath}";
return implode(" ", $opts);
}
/**
* Run MkDocs to build a book
*
* @param string $sourcePath The full filesystem path to the directory
* containing the markdown files
*
* @param string $destinationPath The full filesystem path to the directory
* where we want the published content to go
*
* @param array $extraConfig A associative array with config values to put in
* the 'extra' setting when building the book
*/
public function build($sourcePath, $destinationPath, $extraConfig = array()) {
$this->sourcePath = $sourcePath;
$this->destinationPath = $destinationPath;
$this->extraConfig = $extraConfig;
$this->customizeConfig();
$buildCommand = "mkdocs build " . $this->getOptions();
$mkdocs = new Process($buildCommand, $this->sourcePath);
$mkdocs->run();
if (!$mkdocs->isSuccessful()) {
throw new \Exception("MkDocs was unable to build the book. "
. "MkDocs command output: "
. $mkdocs->getErrorOutput());
}
}
}
......@@ -102,6 +102,11 @@ class Publisher {
*/
public $repoURL;
/**
* @var \AppBundle\Utils\MkDocs
*/
private $mkDocs;
/**
*
* @param RequestStack $requestStack
......@@ -110,6 +115,7 @@ class Publisher {
* @param Library $library
* @param string $reposPathRoot
* @param string $publishPathRoot
* @param \AppBundle\Utils\MkDocs $mkDocs
*/
public function __construct(
$requestStack,
......@@ -117,12 +123,14 @@ class Publisher {
$fs,
$library,
$reposPathRoot,
$publishPathRoot) {
$publishPathRoot,
$mkDocs) {
$this->logger = $logger;
$this->fs = $fs;
$this->library = $library;
$this->repoPathRoot = realpath($reposPathRoot);
$this->publishPathRoot = realpath($publishPathRoot);
$this->mkDocs = $mkDocs;
if ($requestStack->getCurrentRequest()) {
$this->publishURLBase
= $requestStack->getCurrentRequest()->getUriForPath('');
......@@ -170,7 +178,7 @@ class Publisher {
try {
$this->book->validate();
}
catch (Exception $e) {
catch (\Exception $e) {
$this->addMessage('CRITICAL', "The book settings for {$this->book->name}"
. "failed validation. Validation error is: " . $e->getMessage());
return FALSE;
......@@ -343,22 +351,16 @@ class Publisher {
* @return boolean TRUE if success
*/
private function build() {
$buildCommand = "mkdocs build "
. "--clean --strict --site-dir {$this->publishPath}";
$this->addMessage('INFO', "Running '{$buildCommand}'");
$mkdocs = new Process($buildCommand, $this->repoPath);
$mkdocs->run();
$mkdocsLogMessages = explode("\n", trim($mkdocs->getErrorOutput()));
$this->addMessage('INFO', "mkdocs output: '{$mkdocs->getErrorOutput()}'");
$mkdocsErrors = FALSE;
foreach ($mkdocsLogMessages as $mkdocsLogMessage) {
if (substr($mkdocsLogMessage, 0, 4) != 'INFO') {
$mkdocsErrors = TRUE;
}
$extraConfig['edition']
= "{$this->language->nativeName()} / {$this->version->name}";
$extraConfig['book_home'] = "/{$this->book->slug}";
try {
$this->mkDocs->build($this->repoPath, $this->publishPath, $extraConfig);
}
if ($mkdocsErrors) {
catch (\Exception $e) {
$this->addMessage('CRITICAL',
"MkDocs build errors encountered. Book not published.");
"Build errors encountered. Book not published. Build error message: "
. $e->getMessage());
return FALSE;
}
$this->addMessage('INFO', "Book published successfully at "
......
/*! jQuery v3.1.1 | (c) jQuery Foundation | jquery.org/license */
!function(a,b){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){"use strict";var c=[],d=a.document,e=Object.getPrototypeOf,f=c.slice,g=c.concat,h=c.push,i=c.indexOf,j={},k=j.toString,l=j.hasOwnProperty,m=l.toString,n=m.call(Object),o={};function p(a,b){b=b||d;var c=b.createElement("script");c.text=a,b.head.appendChild(c).parentNode.removeChild(c)}var q="3.1.1",r=function(a,b){return new r.fn.init(a,b)},s=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,t=/^-ms-/,u=/-([a-z])/g,v=function(a,b){return b.toUpperCase()};r.fn=r.prototype={jquery:q,constructor:r,length:0,toArray:function(){return f.call(this)},get:function(a){return null==a?f.call(this):a<0?this[a+this.length]:this[a]},pushStack:function(a){var b=r.merge(this.constructor(),a);return b.prevObject=this,b},each:function(a){return r.each(this,a)},map:function(a){return this.pushStack(r.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(f.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(a<0?b:0);return this.pushStack(c>=0&&c<b?[this[c]]:[])},end:function(){return this.prevObject||this.constructor()},push:h,sort:c.sort,splice:c.splice},r.extend=r.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||r.isFunction(g)||(g={}),h===i&&(g=this,h--);h<i;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(r.isPlainObject(d)||(e=r.isArray(d)))?(e?(e=!1,f=c&&r.isArray(c)?c:[]):f=c&&r.isPlainObject(c)?c:{},g[b]=r.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},r.extend({expando:"jQuery"+(q+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===r.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){var b=r.type(a);return("number"===b||"string"===b)&&!isNaN(a-parseFloat(a))},isPlainObject:function(a){var b,c;return!(!a||"[object Object]"!==k.call(a))&&(!(b=e(a))||(c=l.call(b,"constructor")&&b.constructor,"function"==typeof c&&m.call(c)===n))},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?j[k.call(a)]||"object":typeof a},globalEval:function(a){p(a)},camelCase:function(a){return a.replace(t,"ms-").replace(u,v)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b){var c,d=0;if(w(a)){for(c=a.length;d<c;d++)if(b.call(a[d],d,a[d])===!1)break}else for(d in a)if(b.call(a[d],d,a[d])===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(s,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(w(Object(a))?r.merge(c,"string"==typeof a?[a]:a):h.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:i.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;d<c;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;f<g;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,e,f=0,h=[];if(w(a))for(d=a.length;f<d;f++)e=b(a[f],f,c),null!=e&&h.push(e);else for(f in a)e=b(a[f],f,c),null!=e&&h.push(e);return g.apply([],h)},guid:1,proxy:function(a,b){var c,d,e;if("string"==typeof b&&(c=a[b],b=a,a=c),r.isFunction(a))return d=f.call(arguments,2),e=function(){return a.apply(b||this,d.concat(f.call(arguments)))},e.guid=a.guid=a.guid||r.guid++,e},now:Date.now,support:o}),"function"==typeof Symbol&&(r.fn[Symbol.iterator]=c[Symbol.iterator]),r.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(a,b){j["[object "+b+"]"]=b.toLowerCase()});function w(a){var b=!!a&&"length"in a&&a.length,c=r.type(a);return"function"!==c&&!r.isWindow(a)&&("array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a)}var x=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C={}.hasOwnProperty,D=[],E=D.pop,F=D.push,G=D.push,H=D.slice,I=function(a,b){for(var c=0,d=a.length;c<d;c++)if(a[c]===b)return c;return-1},J="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",K="[\\x20\\t\\r\\n\\f]",L="(?:\\\\.|[\\w-]|[^\0-\\xa0])+",M="\\["+K+"*("+L+")(?:"+K+"*([*^$|!~]?=)"+K+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+L+"))|)"+K+"*\\]",N=":("+L+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+M+")*)|.*)\\)|)",O=new RegExp(K+"+","g"),P=new RegExp("^"+K+"+|((?:^|[^\\\\])(?:\\\\.)*)"+K+"+$","g"),Q=new RegExp("^"+K+"*,"+K+"*"),R=new RegExp("^"+K+"*([>+~]|"+K+")"+K+"*"),S=new RegExp("="+K+"*([^\\]'\"]*?)"+K+"*\\]","g"),T=new RegExp(N),U=new RegExp("^"+L+"$"),V={ID:new RegExp("^#("+L+")"),CLASS:new RegExp("^\\.("+L+")"),TAG:new RegExp("^("+L+"|[*])"),ATTR:new RegExp("^"+M),PSEUDO:new RegExp("^"+N),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+K+"*(even|odd|(([+-]|)(\\d*)n|)"+K+"*(?:([+-]|)"+K+"*(\\d+)|))"+K+"*\\)|)","i"),bool:new RegExp("^(?:"+J+")$","i"),needsContext:new RegExp("^"+K+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+K+"*((?:-\\d)?\\d*)"+K+"*\\)|)(?=[^-]|$)","i")},W=/^(?:input|select|textarea|button)$/i,X=/^h\d$/i,Y=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,$=/[+~]/,_=new RegExp("\\\\([\\da-f]{1,6}"+K+"?|("+K+")|.)","ig"),aa=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:d<0?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ba=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ca=function(a,b){return b?"\0"===a?"\ufffd":a.slice(0,-1)+"\\"+a.charCodeAt(a.length-1).toString(16)+" ":"\\"+a},da=function(){m()},ea=ta(function(a){return a.disabled===!0&&("form"in a||"label"in a)},{dir:"parentNode",next:"legend"});try{G.apply(D=H.call(v.childNodes),v.childNodes),D[v.childNodes.length].nodeType}catch(fa){G={apply:D.length?function(a,b){F.apply(a,H.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s=b&&b.ownerDocument,w=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==w&&9!==w&&11!==w)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==w&&(l=Z.exec(a)))if(f=l[1]){if(9===w){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(s&&(j=s.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(l[2])return G.apply(d,b.getElementsByTagName(a)),d;if((f=l[3])&&c.getElementsByClassName&&b.getElementsByClassName)return G.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==w)s=b,r=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(ba,ca):b.setAttribute("id",k=u),o=g(a),h=o.length;while(h--)o[h]="#"+k+" "+sa(o[h]);r=o.join(","),s=$.test(a)&&qa(b.parentNode)||b}if(r)try{return G.apply(d,s.querySelectorAll(r)),d}catch(x){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(P,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("fieldset");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&a.sourceIndex-b.sourceIndex;if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return function(b){return"form"in b?b.parentNode&&b.disabled===!1?"label"in b?"label"in b.parentNode?b.parentNode.disabled===a:b.disabled===a:b.isDisabled===a||b.isDisabled!==!a&&ea(b)===a:b.disabled===a:"label"in b&&b.disabled===a}}function pa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function qa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return!!b&&"HTML"!==b.nodeName},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),v!==n&&(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Y.test(n.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){return a.getAttribute("id")===b}},d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}}):(d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}},d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c,d,e,f=b.getElementById(a);if(f){if(c=f.getAttributeNode("id"),c&&c.value===a)return[f];e=b.getElementsByName(a),d=0;while(f=e[d++])if(c=f.getAttributeNode("id"),c&&c.value===a)return[f]}return[]}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){if("undefined"!=typeof b.getElementsByClassName&&p)return b.getElementsByClassName(a)},r=[],q=[],(c.qsa=Y.test(n.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="<a id='"+u+"'></a><select id='"+u+"-\r\\' msallowcapture=''><option selected=''></option></select>",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+K+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+K+"*(?:value|"+J+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){a.innerHTML="<a href='' disabled='disabled'></a><select disabled='disabled'><option/></select>";var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+K+"*[*^$|!~]?="),2!==a.querySelectorAll(":enabled").length&&q.push(":enabled",":disabled"),o.appendChild(a).disabled=!0,2!==a.querySelectorAll(":disabled").length&&q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Y.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"*"),s.call(a,"[s!='']:x"),r.push("!=",N)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Y.test(o.compareDocumentPosition),t=b||Y.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?I(k,a)-I(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?I(k,a)-I(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?la(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(S,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&C.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.escape=function(a){return(a+"").replace(ba,ca)},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," <