changeset 0:b0942f44413f

import from git://github.com/mozilla/toolbox.git
author Jeff Hammel <k0scist@gmail.com>
date Sun, 11 May 2014 09:15:35 -0700
parents
children 1047e058103d
files ABOUT.txt INSTALL.sh README.txt TODO.txt paste.ini plugin-model.gv.txt relocator.ini sample/Bugzilla_Helper.json sample/CrossWeave.json sample/GetLatestTinderbox.json sample/JetCrawl.json sample/Mozuuid.json sample/NightlyTesterTools.json sample/ProfileManager.json sample/Self-serve_in_bulk.json sample/TBPL_Commit_Message_Copier.json sample/TidyBox.json sample/TryChooser_Syntax_Builder.json sample/Updating_uuids_for_lots_of_related_interfaces.json sample/archer-mozilla.json sample/bugzilla-tweaks.json sample/bzconsole.json sample/bzexport.json sample/bztools.json sample/find-roots.pl.json sample/github-tools.json sample/hg_bisect_aid.json sample/hgactivity.json sample/hgtool.json sample/lithium.json sample/logparser.json sample/memory-profiler.json sample/mozInstall.json sample/mozmill-automation.json sample/mozprocess.json sample/mozprofile.json sample/mozregression.json sample/mozrunner.json sample/pybugzilla.json sample/python-profilemanager.json sample/pyxpt.json sample/qimportbz.json sample/reverse-edges.pl.json sample/screenshot-tool.json sample/self-serve-tools.json sample/sendchanges.json sample/tinderstatus.json sample/unpack-diskimage.json scripts/README.txt scripts/greasemonkey_scraper.py scripts/html2json.py scripts/setup_scraper.py setup.py test/test.ini test/test.py test/test_json.txt test/test_search.txt toolbox/__init__.py toolbox/dispatcher.py toolbox/factory.py toolbox/handlers.py toolbox/model.py toolbox/search.py toolbox/static/css/about.less toolbox/static/css/html5boilerplate.css toolbox/static/css/new.less toolbox/static/css/project.less toolbox/static/css/style.less toolbox/static/css/token-input-facebook.css toolbox/static/css/token-input.css toolbox/static/img/UEB16.png toolbox/static/img/favicon.ico toolbox/static/img/indicator.gif toolbox/static/img/search.png toolbox/static/js/field.js toolbox/static/js/jquery-1.6.min.js toolbox/static/js/jquery.autolink.js toolbox/static/js/jquery.jeditable.js toolbox/static/js/jquery.js toolbox/static/js/jquery.timeago.js toolbox/static/js/jquery.tokeninput.js toolbox/static/js/less-1.0.41.min.js toolbox/static/js/main.js toolbox/static/js/new.js toolbox/static/js/project.js toolbox/static/js/queryString.js toolbox/templates/about.html toolbox/templates/fields.html toolbox/templates/index.html toolbox/templates/main.html toolbox/templates/new.html toolbox/templates/tags.html toolbox/util.py
diffstat 93 files changed, 5682 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ABOUT.txt	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,103 @@
+toolbox
+=======
+
+*an index of Mozilla software tools*
+
+
+The Story of Toolbox
+--------------------
+
+Tools are only useful if you know where they exists and can find them.
+Toolbox is an index of tools developed by and
+for the Mozilla community.  Toolbox is not a hosting service -- it is a
+listing of tools which can live anywhere that are of use to Mozillians.
+
+It can also be used to track:
+
+* smart bookmarks
+* code snippets
+* webapps
+
+Each tool in the listing must provide the following attributes:
+
+* a *name* that uniquely identifies the tool
+* a text *description* of the tool
+* a canonical *URL* where you can find the tool
+
+Toolbox also tracks several optional classifiers for each tool in its
+database.   The classifiers are described below.
+
+
+How to use Toolbox
+------------------
+
+The `index page <./>`_ of toolbox lists all tools with the most
+recently updated first. All fields on a tool are clickable.  Clicking on the
+description lets you edit the description which will be saved after
+you finish editing it. Hovering over the tool title or URL will display an
+`edit button <http://universaleditbutton.org/>`_ which on clicking
+will allow you to edit the appropriate data.
+Clicking a URL, like `?author=harth <./?author=harth>`_ will give
+you the tools that ``harth`` wrote. There is also full text search
+using the ``?q=`` parameter (like `?q=firefox <./?q=firefox>`_ ) which
+will search both the descriptions and all of the fields.
+
+You can also display results by a particular field by going to that
+field name.  For example, to display tools by author, go to 
+`/author <author>`_ .  You can add a new tool at 
+`/new <new>`_ by providing its name, description, and URL. Upon
+creation, you'll be redirected to the tool's index page where you can
+add whatever classifiers you want.
+
+
+Classifiers
+-----------
+
+Outside of the required fields (name, description, and URL), a tool
+has a number of classifier tags.  These fields are:
+
+* usage: what the tool is for
+* type: is the tool a particular kind of software such as an addon or a script?
+* language: which programming language the tool is written in
+* author: who wrote and/or maintains the software?
+
+You can freely add and remove classifiers for each project.
+Autocomplete is enabled to help you find the classifier you want.
+
+
+TODO
+----
+
+There is much more that we plan to add to Toolbox. The project source
+code may be found at https://github.com/mozilla/toolbox .
+
+* add scrapers for hosted tools to automatically seed toolbox with data
+* integrate author with community phonebook and bugzilla id
+* the first time someone edits a description (etc.) from a pointed-to
+  file (e.g. a setup.py) then the project should be notified
+* you should be able to edit a field, e.g. author.  Changing one field
+  value should give the option to change all similar field values.
+* a "Request a tool" link that functions like stack overflow; users
+  can request a tool. If it does not exist, it gets turned into a
+  bug. Users should also be able to point to a tool to answer the
+  question. Similarly, developers should be able to see a list of
+  requested tools and take ownership of them if desired
+* Similarly, users should be able to note similarity of tools and
+  propose a consolidation strategy
+
+Oustanding issues are listed at
+https://bugzilla.mozilla.org/buglist.cgi?resolution=---&component=Toolbox&product=Webtools
+. Please file new bugs or feature requests at
+https://bugzilla.mozilla.org/enter_bug.cgi?product=Webtools&component=Toolbox
+or contact jhammel@mozilla.org or discuss in #ateam at irc.mozilla.org.
+
+
+Other Resources
+---------------
+
+Mozilla tools are recorded on other sites too.
+
+* http://www.mozdev.org/
+* https://wiki.mozilla.org/User:Jprosevear/Tools
+* http://infomonkey.cdleary.com/
+* http://userscripts.org/
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/INSTALL.sh	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+# Installs toolbox in a virtualenv
+VIRTUALENV=$(which virtualenv)
+${VIRTUALENV} toolbox
+cd toolbox
+. bin/activate
+mkdir src
+git clone git://github.com/mozilla/toolbox.git
+cd toolbox
+python setup.py develop
+# now run:
+# paster serve paste.ini
+# from the toolbox virtualenv to serve the sample app
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/README.txt	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,343 @@
+The Story of Toolbox
+====================
+
+Toolbox is fundamentally a document-oriented approach to resource
+indexing.  A "tool" consists three mandatory string fields -- name,
+description, and URL -- that are generic to the large class of problems
+of web resources, as well as classifiers, such as author, usage, type,
+etc. A tool may have an arbitrary number of classifier fields as
+needed.  Each classifier consists of a set of values with which a tool
+is tagged. This gives toolbox the flexibility to fit a large number of
+data models, such as PYPI, DOAP, and others.
+
+
+Running Toolbox
+---------------
+
+You can download and run the toolbox software yourself:
+http://github.com/k0s/toolbox
+
+To serve in baseline mode, install the software and run::
+
+ paster serve paste.ini
+
+This will serve the handlers and static content using the paste
+(http://pythonpaste.org) webserver using ``README.txt`` as the
+``/about`` page and serving the data in ``sample``.
+
+The dispatcher (``toolbox.dispatcher:Dispatcher``) is the central (WSGI)
+webapp that designates per-request to a number of handlers (from
+``handlers.py``).  The dispatcher has a few options:
+
+* about: path to a restructured text file to serve at ``/about``
+* model_type: name of the backend to use (memory_cache, file_cache, or couch)
+* template_dir: extra directory to look for templates
+
+These may be configured in the ``paste.ini`` file in the
+``[app:toolbox]`` section by prepending with the namespace
+``toolbox.``. It is advisable that you copy the example ``paste.ini``
+file for your own usage needs.  Additional ``toolbox.``-namespaced
+arguments will be passed to the model.  For instance, to specify the
+directory for the ``file_cache`` model, the provided ``paste.ini`` uses
+``toolbox.directory = %(here)s/sample``.
+
+
+Architecture
+------------
+
+Toolbox uses a fairly simple architecture with a single abstract data
+model allowing an arbitrary number of implementations to be constructed::
+
+ Interfaces            Implementations
+
+ +----+              +-+-----+
+ |HTTP|              | |files|
+ +----+---\  +-----+ | +-----+
+           |-|model|-+-+-----+
+ +------+-/  +-----+ | |couch|
+ |script|            | +-----+
+ +------+            +-+------+
+                     | |memory|
+                     | +------+
+                     +-+---+
+                       |...|
+                       +---+
+
+Toolbox was originally intended to use a directory of files, one per project,
+as the backend. These were originally intended to be HTML files as the
+above model may be clearly mapped as HTML::
+
+ <div class="project"><h1><a href="{{url}}">{{name}}</a></h1>
+ <p class="description">{{description}}</p>
+ {{for field in fields}}
+  <ul class="{{field}}">
+  {{for value in values[field]}}
+   <li>{{value}}</li>
+  {{endfor}}
+ {{endfor}}
+ </div>
+
+This microformat approach allows not only easy editing of the HTML
+documents, but the documents may be indepently served and displayed
+without the toolbox server-side. 
+
+The HTML microformat was never implemented (though, since the model
+backend is pluggable, it easily could be). Instead, the original
+implementation used JSON blobs stored in one file per tool. This
+approach loses the displayable aspect, though since JSON is a defined
+format with several good tools for exploring and manipulating the data
+perhaps this disavantage is offset.
+
+A couch backend was also written.
+
+      +------------+-----------+------------+
+      |Displayable?|File-based?|Concurrency?|
++-----+------------+-----------+------------+
+|HTML |Yes         |Yes        |No          |
++-----+------------+-----------+------------+
+|JSON |Not really  |Yes        |No          |
++-----+------------+-----------+------------+
+|Couch|No          |No         |Yes?        |
++-----+------------+-----------+------------+
+
+The concurrency issue with file-based documennt backends may be
+overcome by using locked files.  Ideally, this is accomplished at the
+filesystem level.  If your filesystem does not promote this
+functionality, it may be introduced programmatically.  A rough cartoon
+of a good implementation is as follows:
+
+1. A worker thread is spawned to write the data asynchronously. The
+data is sent to the worker thread.
+
+2. The worker checks for the presence of a lockfile (herein further
+detailed). If the lockfile exists and is owned by an active process,
+the worker waits until said process is done with it. (For a more
+robust implementation, the worker sends a request to write the file to
+some controller.)
+
+3. The worker owns a lockfile based on its PID in some directory
+parallel to the directory root under consideration (for example,
+``/tmp/toolbox/lock/${PID}-${filename}.lck``).
+
+4. The worker writes to the file.
+
+5. The worker removes the lock
+
+The toolbox web service uses a dispatcher->handler framework.  The
+handlers are loosely pluggable (they are assigned in the dispatcher),
+but could (and probably should) be made completely pluggable.  That
+said, the toolbox web system features an integration of templates,
+static resources (javascript, css, images), and handlers, so true
+pluggability is further away than just supporting pluggable handlers
+in the dispatcher.
+
+Deployment, however, may be tailored as desired.  Any of the given
+templates may be overridden via passing a ``template_dir`` parameter
+with a path to a directory that have templates of the appropriate
+names as found in toolbox's ``templates`` directory. 
+
+Likewise, the static files (css, js, etc.) are served using ``paste``'s 
+``StaticURLParser`` out of toolbox's ``static`` directory. (See
+toolbox's ``factory.py``.) Notably this is *not* done using the WSGI
+app itself.  Doing it with middleware allows the deployment to be
+customizable by writing your own factory.  For example, instead of
+using the ``paste`` webserver and the included ``paste.ini``, you
+could use nginx or apache and ``mod_wsgi`` with a factory file
+invoking ``Dispatcher`` with the desired arguments and serving the
+static files with an arbitrary static file server.
+
+It is common sense, if rarely followed, that deployment should be
+simple.  If you want to get toolbox running on your desktop and/or for
+testing, you should be able to do this easily (see the ``INSTALL.sh``
+for a simple installation using ``bash``; you'll probably want to
+perform these steps by hand for any sort of real-world deployment).
+If you want a highly customized deployment, then this will require
+more expertise and manual setup.
+
+The template data and the JSON are closely tied together.  This has the
+distinct advantage of avoiding data translation steps and avoiding
+code duplication.
+
+Toolbox uses several light-footprint libraries:
+
+* webob for Request/Response handling: http://pythonpaste.org/webob/
+
+* tempita for (HTML) templates: http://pythonpaste.org/tempita/
+
+* whoosh for search.  This pure-python implementation of full-text
+  search is relatively fast (for python) and should scale decently to
+  the target scale of toolbox (1000s or 10000s of tools). While not as
+  fast as lucene, whoosh is easy to deploy and has a good API and
+  preserves toolbox as a deployable software product versus an
+  instance that requires the expert configuration, maintainence, and
+  tuning of several disparate software products that is both
+  non-automatable (cannot be installed with a script) and
+  time-consuming. http://packages.python.org/Whoosh/
+
+* jQuery: jQuery is the best JavaScript library and everyone
+  should use it. http://jquery.com/
+
+* jeditable for AJAXy editing: http://www.appelsiini.net/projects/jeditable
+
+* jquery-token for autocomplete: http://loopj.com/jquery-tokeninput/
+
+* less for dynamic stylesheets: http://lesscss.org/
+
+
+User Interaction
+----------------
+
+A user will typically interact with Toolbox through the AJAX web
+interface.  The server side returns relatively simple (HTML) markup,
+but structured in such a way that JavaScript may be utilized to
+promote rich interaction.  The simple HTML + complex JS manifests
+several things:
+
+1. The document is a document. The tools HTML presented to the user (with
+the current objectionable exception of the per-project Delete button)
+is a document form of the data. It can be clearly and easily
+translated to data (for e.g. import/export) or simply marked up using
+(e.g.) JS to add functionality. By keeping concerns seperate
+(presentation layer vs. interaction layer) a self-evident clarity is
+maintained.
+
+2. Computation is shifted client-side. Often, an otherwise lightweight
+webapp loses considerable performance rendering complex templates. By
+keeping the templates light-weight and doing control presentation and
+handling in JS, high performance is preserved.
+
+
+What Toolbox Doesn't Do
+-----------------------
+
+* versioning: toolbox exposes editing towards a canonical document.
+  It doesn't do versioning.  A model instance may do whatever
+  versioning it desires, and since the models are pluggable, it would
+  be relatively painless to subclass e.g. the file-based model and
+  have a post-save hook which does an e.g. ``hg commit``. Customized
+  templates could be used to display this information.
+
+* authentication: the information presented by toolbox is freely
+  readable and editable. This is by intention, as by going to a "wiki"
+  model and presenting a easy to use, context-switching-free interface
+  curation is encouraged (ignoring the possibly imaginary problem of
+  wiki-spam). Access-level auth could be implemented using WSGI
+  middleware (e.g. repoze.who or bitsyauth) or through a front end
+  "webserver" integration layer such as Apache or nginx. Finer grained
+  control of the presentation layer could be realized by using custom
+  templates.
+
+
+What Toolbox Would Like To Do
+-----------------------------
+
+Ultimately, toolbox should be as federated as possible.  The basic
+architecture of toolbox as a web service + supporting scripts makes
+this feasible and more self-contained than most proposed federated
+services.  The basic federated model has proved, in practice,
+difficult to achieve through purely the (HTTP) client-server model, as
+without complete federation and adherence to protocol offline cron
+jobs should be utilized to pull external data sources. If a webservice
+only desires to talk to others of its own type and are willing to keep
+a queue of requests for when hosts are offline, entire HTTP federation
+may be implemented with only a configuration-specified discovery
+service to find the nodes.
+
+
+Evolution
+---------
+
+Often, a piece software is presented as a state out of context (that
+is minus the evolution which led it to be and led it to look further
+out towards beyond the horizon).  While this is an interesting special
+effect for an art project, software being communication this
+is only conducive to software in the darkest of black-box approaches.
+
+"Beers are like web frameworks: if they're not micro, you don't know
+what you're talking about." - hipsterhacker
+
+For sites that fit the architecture of a given framework, it may be
+advisable to make use of them.  However, for most webapp/webservice
+categories which have a finite scope and definitive intent, it is
+often easier, more maintainable, and more legible to build a complete
+HTTP->WSGI->app architecture than to try to hammer a framework into
+fitting your problem or redefining the problem to fit the framework.
+This approach was used for toolbox.
+
+The GenshiView template, http://k0s.org/hg/GenshiView, was invoked to
+generate a basic dispatcher->handler system.  The cruft was removed,
+leaving only the basic structure and the TempitaHandler since tempita
+is lightweight and it was envisioned that filesystem tempita templates
+(MakeItSo!) would be used elsewhere in the project.  The basic
+handlers (projects views, field-sorted view, new, etc.) were written
+and soon a usable interface was constructed.
+
+A ``sample`` directory was created to hold the JSON blobs. Because
+this was done early on, two goals were achieved: 
+
+1. the software could be dogfooded immediately using actual applicable
+data. This helped expose a number of issues concerning the data format
+right away.
+
+2. There was a place to put tools before the project reached a
+deployable state (previously, a few had lived in a static state using
+a rough sketch of the HTML microformat discussed above on
+k0s.org). Since the main point of toolbox is to record Mozilla tools,
+the wealth of references mentioned in passing could be put somewhere,
+instead of passed by and forgotten.  One wishes that they do not miss
+the train while purchasing a ticket.
+
+The original intent, when the file-based JSON blob approach was to be
+the deployed backend, was to have two repositories: one for the code
+and one for the JSON blobs.  When this approach was scrapped, the
+file-based JSON blobs were relegated to the ``sample`` directory, with
+the intent to be to import them into e.g. a couch database on actual
+deployment (using an import script). The samples could then be used
+for testing.
+
+The model has a single "setter" function, ``def update``, used for
+both creating and updating projects.  Due to this and due to the fact
+the model was ABC/pluggable from the beginning, a converter ``export``
+function could be trivially written at the ABC-level::
+
+    def export(self, other):
+        """export the current model to another model instance"""
+        for project in self.get():
+            other.update(project)
+
+This with an accompanying CLI utility was used to migrate from JSON
+blob files in the ``sample`` directory to the couch instance.  This
+particular methodology as applied to an unexpected problem (the
+unanticipated switch from JSON blobs to couch) is a good example of
+the power of using a problem to drive the software forward (in this
+case, creation of a universal export function and associated command
+line utility). The alternative, a one-off manual data migration, would
+have been just as time consuming, would not be repeatable, would not
+have extended toolbox, and may have (like many one-offs do) infected
+the code base with associated semi-permanant vestiges.  In general,
+problems should be used to drive innovation.  This can only be done if
+the software is kept in a reasonably good state.  Otherwise
+considerable (though probably worthwhile) refactoring should be done
+prior to feature extension which will become cost-prohibitive in
+time-critical situations where a one-off is (more) likely to be employed.
+
+
+Use Cases
+---------
+
+The target use-case is software tools for Mozilla, or, more generally,
+a software index.  For this case, the default fields uses are given in
+the paste.ini file: usage, author, type, language. More fields may be
+added to the running instance in the future.
+
+However, the classifier classification can be used for a wide variety
+of web-locatable resources.  A few examples:
+
+* songs: artist, album, genre, instruments
+* de.li.cio.us: type, media, author, site
+
+
+Resources
+---------
+
+* http://readthedocs.org/
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/TODO.txt	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,112 @@
+TODO: "plain text" base format
+------------------------------
+
+Add an import/export model a la
+http://k0s.org/portfolio/python/python-tools.txt; example:
+
+"""
+* https://pypi.python.org/pypi/bidict
+  bidirectional (one-to-one) mapping data structure
+  {data structure}
+
+* https://pypi.python.org/pypi/etherpad_lite
+  Python interface for Etherpad-Lite's HTTP API
+  https://github.com/devjones/PyEtherpadLite
+
+* https://pypi.python.org/pypi/gitdb
+  git object database
+  {storage, versioning}
+"""
+
+
+TODO: Tool Sources
+------------------
+
+In addition to manually indexed tools, toolbox is intended to harvest
+index data from distributed sources.  Several scrapers should be
+written and run on a scheduled basis (i.e. with a cron job or
+preferably something that could actually be reliably depended on and
+automatable via python).  Useful project sources are:
+
+* setup.py for python projects
+* addons.mozilla.org pages
+* OpenWebApps: https://developer.mozilla.org/en/OpenWebApps/The_Manifest
+* userscripts: e.g. https://www.squarefree.com/userscripts/tidybox.user.js
+
+
+TODO: (Alternate) Links
+-----------------------
+
+Currently, each tool has one canonical URL.
+Since toolbox is an index, this has the distinct advantage of
+associating a single URL with the project.  It is assumed that the
+linked-to resource should point to auxilliary resources as necessary.
+
+However, as an index is useful for correlating information --
+connecting the dots -- allowing a variety of links both allows the
+browser to have information at their fingertips, but also to allow
+mapping and intelligent manipulation of tools by their link types.
+Several types of links may be recorded:
+
+* repository
+* how to report bugs
+* wiki
+* pypi
+
+
+TODO: Directory Structure
+-------------------------
+
+Each function should live in its own module::
+
+ .
+ +-README.txt
+ +-ABOUT.txt
+ +-INSTALL.sh
+ +-setup.py
+ +-paste.ini
+ |
+ toolbox
+  |
+  +-web.py
+  +-factory.py
+  +-json.py
+  |
+  handler
+  ||
+  |...
+  |
+  model
+  ||
+  |...
+  |
+  static
+  ||
+  |...
+  |
+  templates
+
+
+URLs
+----
+
+A more RESTful proposed URL schema:
+
+/{{project}}
+* PUT: replace the project
+* GET: return the project
+* POST: update the project
+* DELETE: remove the project
+
+/{{project}}/{{field}}
+* PUT: replace all field values
+* POST: for lists, add field values
+* GET: return field value(s)
+
+/{{project}}/{{field}}/{{value}}
+* DELETE: remove value from a list field
+
+/{{field}}
+* POST: rename a field value: /{{field}}?jahml=jhammel
+* should also take a description
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/paste.ini	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,31 @@
+#!/usr/bin/env paster
+
+# sample config file for the paste webserver
+
+[DEFAULT]
+debug = true
+email_to = jhammel@mozilla.com
+smtp_server = localhost
+error_email_from = paste@localhost
+
+[exe]
+command = serve
+
+[server:main]
+use = egg:Paste#http
+host = 0.0.0.0
+port = 8080
+
+[composite:main]
+use = egg:Paste#urlmap
+/ = toolbox
+
+set debug = false
+
+[app:toolbox]
+paste.app_factory = toolbox.factory:paste_factory
+toolbox.about = %(here)s/ABOUT.txt
+toolbox.directory = %(here)s/sample
+toolbox.fields = usage, author, type, language
+toolbox.model_type = toolbox.model:FileCache
+toolbox.reload = false
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugin-model.gv.txt	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,14 @@
+#!/usr/bin/fdp -Tpng
+
+graph architecture {
+ rankdir=LR;
+ dispatcher -- templates;
+ dispatcher -- about;
+ dispatcher -- model;
+ model -- model_backends [label="plugin"];
+ dispatcher -- handlers [label="plugin"];
+ templates [shape=folder];
+ about [shape=note];
+ model_backends [shape=record label="memory_cache|file_cache|couch" rankdir=TB];
+ handlers [shape=record label="handlers|...|...|"];
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/relocator.ini	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,32 @@
+#!/usr/bin/env paster
+
+# sample config file for toolbox mounted at /toolbox
+
+[DEFAULT]
+debug = true
+email_to = jhammel@mozilla.com
+smtp_server = localhost
+error_email_from = paste@localhost
+
+[exe]
+command = serve
+
+[server:main]
+use = egg:Paste#http
+host = 0.0.0.0
+port = 8080
+
+[composite:main]
+use = egg:Paste#urlmap
+/ = toolbox
+
+set debug = false
+
+[app:toolbox]
+paste.app_factory = toolbox.factory:relocator_factory
+toolbox.about = %(here)s/ABOUT.txt
+toolbox.directory = %(here)s/sample
+toolbox.fields = usage, author, type, language
+toolbox.model_type = toolbox.model:FileCache
+toolbox.reload = false
+baseurl = http://127.0.0.1/toolbox
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sample/Bugzilla_Helper.json	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,1 @@
+{"description": "Quickly reply to your bugmail on bugzilla.mozilla.org without leaving Thunderbird", "url": "https://addons.mozilla.org/en-US/thunderbird/addon/bugzilla-helper/", "modified": 1298415219.4090421, "usage": ["bugzilla", "thunderbird"], "type": ["addon"], "name": "Bugzilla Helper"}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sample/CrossWeave.json	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,1 @@
+{"description": "CrossWeave populates Firefox profiles based on data in a file in a YAML-like format.  Right now it populates temporary profiles which are deleted after CrossWeave completes, but it would be easy to allow it to allow it to populate data in an existing profile.", "author": ["jgriffin"], "url": "https://wiki.mozilla.org/Auto-tools/Projects/CrossWeave", "modified": 1298943796.7334161, "usage": ["profiles"], "name": "CrossWeave"}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sample/GetLatestTinderbox.json	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,1 @@
+{"name": "GetLatestTinderbox", "language": ["python"], "author": ["jhammel"], "url": "http://hg.mozilla.org/automation/getlatest-tinderbox/", "modified": 1298581466.0599027, "description": "gets the latest builds and logs from staging.mozilla.org"}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sample/JetCrawl.json	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,1 @@
+{"description": "make history and bookmarks data more realistic for new profiles with this jetpack", "author": ["ddahl"], "url": "http://daviddahl.blogspot.com/2010/01/jetcrawl.html", "modified": 1298917305.2201991, "usage": ["profiles"], "type": ["jetpack"], "name": "JetCrawl"}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sample/Mozuuid.json	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,1 @@
+{"name": "Mozuuid", "language": ["perl"], "author": ["smaug"], "url": "http://mozilla.pettay.fi/cgi-bin/mozuuid.pl", "modified": 1302953422.0, "usage": ["uuid"], "description": "Generates a new UUID, both in .idl and .h format"}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sample/NightlyTesterTools.json	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,1 @@
+{"description": "Nightly Tester Tools is an addon for aiding testers of nightly builds of Mozilla apps like Firefox and Thunderbird.", "author": ["mossop", "harth"], "url": "https://addons.mozilla.org/en-US/firefox/addon/6543/", "modified": 1301358703.815768, "usage": ["nightlies"], "type": ["addon"], "name": "Nightly Tester Tools"}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sample/ProfileManager.json	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,1 @@
+{"description": "Manage profiles for Firefox and other XULRunner applications", "author": ["jgriffin", "jhammel"], "url": "http://hg.mozilla.org/automation/profilemanager/", "modified": 1298418052.5133755, "usage": ["profiles"], "name": "ProfileManager"}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sample/Self-serve_in_bulk.json	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,1 @@
+{"name": "Self-serve in bulk", "language": ["python"], "author": ["jdm"], "url": "http://www.joshmatthews.net/blog/2011/03/self-serve-now-in-bulk/", "modified": 1304783745.91575, "description": "Allows you to cancel all builds for a push"}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sample/TBPL_Commit_Message_Copier.json	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,1 @@
+{"description": "This addon lets you copy pre-formatted commit messages to your clipboard right from TBPL.", "author": ["KWierso"], "url": "https://addons.mozilla.org/en-US/firefox/addon/269586/", "modified": 1298421199.7653754, "usage": ["tbpl"], "type": ["addon"], "name": "TBPL Commit Message Copier"}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sample/TidyBox.json	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,1 @@
+{"name": "TidyBox", "author": ["jesse"], "url": "https://www.squarefree.com/userscripts/tidybox.user.js", "modified": 1302279703.049041, "type": ["greasemonkey script"], "usage": ["tinderbox"], "description": "Shrinks Tinderbox, letting you hover instead of scrolling to see information.  If you want to click a link in a popup, click to freeze the popup in place."}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sample/TryChooser_Syntax_Builder.json	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,1 @@
+{"name": "TryChooser Syntax Builder", "language": ["JavaScript"], "author": ["lsblakk"], "url": "http://people.mozilla.org/~lsblakk/trychooser/trychooser.html", "modified": 1304793532.876296, "type": ["tryserver"], "description": "Allows you to generate the \"try:\" line you need"}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sample/Updating_uuids_for_lots_of_related_interfaces.json	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,1 @@
+{"name": "Updating uuids for lots of related interfaces", "language": ["perl"], "author": ["sfink"], "url": "http://people.mozilla.org/~sfink/uploads/update-uuids", "modified": 1302953542.0, "usage": ["uuid"], "type": ["script"], "description": "This script updates IDL files with new UUIDs for the given interfaces as well as any interfaces affected by any of those interfaces (recursively)."}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sample/archer-mozilla.json	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,1 @@
+{"description": "This tree holds Python code to customize Archer, the GDB branch extensible via Python, for debugging Mozilla. ", "language": ["python"], "url": "http://hg.mozilla.org/users/jblandy_mozilla.com/archer-mozilla/", "modified": 1301956344.8905699, "usage": ["gdb"], "name": "archer-mozilla"}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sample/bugzilla-tweaks.json	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,1 @@
+{"name": "bugzilla-tweaks", "author": ["harth", "ehsan"], "url": "https://bitbucket.org/ehsan/bugzilla-tweaks", "modified": 1385649107.143126, "usage": ["bugzilla"], "type": ["addon"], "description": "The Bugzilla Tweaks extensions, aiming to make bugzilla.mozilla.org usage easier."}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sample/bzconsole.json	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,1 @@
+{"name": "bzconsole", "language": ["python"], "author": ["jhammel"], "url": "http://k0s.org/mozilla/hg/bzconsole", "modified": 1298414293.0170419, "usage": ["bugzilla"], "description": "console API to bugzilla"}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sample/bzexport.json	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,1 @@
+{"name": "bzexport", "language": ["python"], "author": ["ted"], "url": "http://hg.mozilla.org/users/tmielczarek_mozilla.com/bzexport/", "modified": 1298666218.418088, "usage": ["bugzilla", "hg", "mq"], "type": ["mercurial-extension"], "description": "bzexport: a Mercurial extension for attaching patches from a Mercurial repository to bugzilla from the command line."}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sample/bztools.json	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,1 @@
+{"name": "bztools", "language": ["python"], "author": ["LegNeato"], "url": "https://github.com/LegNeato/bztools", "modified": 1298423387.0853753, "usage": ["bugzilla"], "description": "Models and scripts to access the Bugzilla REST API"}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sample/find-roots.pl.json	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,1 @@
+{"description": "Perl script to analyze cycle collector edge log", "language": ["perl"], "author": ["peterv"], "url": "https://bug466157.bugzilla.mozilla.org/attachment.cgi?id=468818", "modified": 1302553294.18314, "type": ["script"], "name": "find-roots.pl"}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sample/github-tools.json	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,1 @@
+{"name": "github-tools", "author": ["bhearsum"], "url": "https://github.com/bhearsum/github-tools", "modified": 1302714285.0, "usage": ["github"], "description": "Repository for scripts related to github\n"}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sample/hg_bisect_aid.json	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,1 @@
+{"name": "hg_bisect_aid", "author": ["standard8"], "url": "http://hg.mozilla.org/users/bugzilla_standard8.plus.com/useful-tools/file/tip/hg_bisect_aid", "modified": 1298581662.1799026, "usage": ["hg"], "description": "aid to bisect with hg"}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sample/hgactivity.json	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,1 @@
+{"name": "hgactivity", "author": ["dmose"], "url": "https://bitbucket.org/dmose/hgactivity", "modified": 1298581934.1279025, "usage": ["hg"], "description": "(Temporary, I hope!) fork of http://labs.freehackers.org/projects/hgactivity... with the ability to generate a monthly summary of checkins per-committer in CSV format, suitable for spreadsheet import."}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sample/hgtool.json	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,1 @@
+{"description": "releng tool to do safe operations with hg. revision/branch on commandline will override those in props-file", "language": ["python"], "url": "http://hg.mozilla.org/build/tools/file/tip/buildfarm/utils/hgtool.py", "modified": 1298582024.0199025, "usage": ["hg"], "name": "hgtool"}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sample/lithium.json	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,1 @@
+{"name": "lithium", "language": ["python"], "author": ["jesse"], "url": "http://www.squarefree.com/lithium/", "modified": 1302295843.993751, "description": "Lithium is an automated testcase reduction tool developed by Jesse Ruderman."}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sample/logparser.json	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,1 @@
+{"description": "\nparses test logs into JSON and scrapes logs from stage.mozilla.org\n", "language": ["python"], "author": ["jgriffin", "jhammel"], "url": "http://hg.mozilla.org/automation/logparser/", "modified": 1298417685.9583635, "name": "logparser"}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sample/memory-profiler.json	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,1 @@
+{"description": "Memory Profiler Firefox extension: https://wiki.mozilla.org/Labs/Memory_Profiler\n", "author": ["atul"], "url": "https://github.com/toolness/memory-profiler", "modified": 1302551634.8831401, "type": ["addon"], "name": "memory-profiler"}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sample/mozInstall.json	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,1 @@
+{"name": "mozInstall", "language": ["python"], "author": ["ctalbert"], "url": "https://github.com/harthur/mozregression/blob/master/mozregression/mozInstall.py", "modified": 1299024970.4532111, "usage": ["dmg"], "description": "installs Firefox"}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sample/mozmill-automation.json	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,1 @@
+{"description": "This repository contains automation scripts for a couple of different test-runs. For use with Mozmill", "language": ["python"], "author": ["whimboo"], "url": "http://hg.mozilla.org/qa/mozmill-automation", "modified": 1299025362.369211, "usage": ["dmg"], "type": ["harness"], "name": "mozmill-automation"}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sample/mozprocess.json	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,1 @@
+{"description": "killable process and process management utilities", "language": ["python"], "author": ["ctalbert"], "url": "https://github.com/mozautomation/mozmill/tree/master/mozprocess", "modified": 1298516361.223263, "name": "mozprocess"}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sample/mozprofile.json	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,1 @@
+{"name": "mozprofile", "language": ["python"], "author": ["ctalbert"], "url": "https://github.com/mozautomation/mozmill/tree/master/mozprofile", "modified": 1298516189.463263, "usage": ["profiles", "addons"], "description": "mozprofile manages profiles for automation and test harnesses"}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sample/mozregression.json	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,1 @@
+{"name": "mozregression", "language": ["python"], "author": ["harth"], "url": "http://harthur.github.com/mozregression/", "modified": 1299025056.385211, "usage": ["regression", "nightlies"], "description": "interactive regression range finder for Firefox nightlies"}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sample/mozrunner.json	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,1 @@
+{"description": "mozrunner runs XUL-based applications such as Firefox and Thunderbird", "language": ["python"], "author": ["ctalbert"], "url": "https://github.com/mozautomation/mozmill/tree/master/mozrunner", "modified": 1298516027.8272631, "name": "mozrunner"}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sample/pybugzilla.json	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,1 @@
+{"description": "Python library and command-line tools for interacting with Bugzilla via Gervase Markham's REST API", "language": ["python"], "author": ["atul"], "url": "https://github.com/toolness/pybugzilla", "modified": 1301358703.815768, "usage": ["bugzilla"], "name": "pybugzilla"}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sample/python-profilemanager.json	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,1 @@
+{"description": "A python version of profile manager; see also http://hg.mozilla.org/automation/profilemanager/", "language": ["python"], "author": ["jhammel"], "url": "http://k0s.org/mozilla/hg/ProfileManager/", "modified": 1298420504.6333754, "usage": ["profiles"], "name": "python-profilemanager"}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sample/pyxpt.json	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,1 @@
+{"description": "A module for working with XPCOM Type Libraries.", "language": ["python"], "author": ["ted"], "url": "http://hg.mozilla.org/users/tmielczarek_mozilla.com/pyxpt", "modified": 1301102002.2795377, "name": "pyxpt"}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sample/qimportbz.json	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,1 @@
+{"name": "qimportbz", "language": ["python"], "author": ["robarnold"], "url": "http://hg.mozilla.org/users/robarnold_cmu.edu/qimportbz", "modified": 1298666299.5580881, "usage": ["bugzilla", "hg", "mq"], "type": ["mercurial-extension"], "description": "hg qimportbz gives you a list of the patches with a descripting and some flags and you select which number you want. handy for pushing other people's patches"}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sample/reverse-edges.pl.json	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,1 @@
+{"name": "reverse-edges.pl", "language": ["perl"], "author": ["peterv"], "url": "https://bug466157.bugzilla.mozilla.org/attachment.cgi?id=468816", "modified": 1302553208.4951401, "description": "Perl script to process cycle collector edge log"}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sample/screenshot-tool.json	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,1 @@
+{"description": "screenshot program", "author": ["ted"], "url": "http://hg.mozilla.org/users/tmielczarek_mozilla.com/screenshot-tools", "modified": 1298420830.4173753, "usage": ["screenshots"], "name": "screenshot-tool"}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sample/self-serve-tools.json	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,1 @@
+{"name": "self-serve-tools", "language": ["python"], "author": ["Josh Matthews"], "url": "http://hg.mozilla.org/users/josh_joshmatthews.net/self-serve-tools/", "modified": 1302545970.29514, "description": "Python API to access the self-serve REST API."}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sample/sendchanges.json	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,1 @@
+{"name": "sendchanges", "language": ["python"], "author": ["jhammel"], "url": "http://k0s.org/mozilla/hg/sendchanges", "modified": 1298398495.6554279, "dependencies": ["GetLatestTinderbox"], "usage": ["buildbot"], "description": "send changes to a Mozilla buildmaster for testing"}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sample/tinderstatus.json	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,1 @@
+{"name": "tinderstatus", "author": ["Alexander J. Vincent"], "url": "http://tinderstatus.mozdev.org/", "modified": 1300321728.3247271, "usage": ["tinderbox"], "type": ["addon"], "description": "Tinderstatus is a small Firefox/SeaMonkey extension that displays the current status of the SeaMonkey, Firefox, Thunderbird, XULRunner, and Camino tinderboxen and whether the tree is open or closed in an icon on the browser's status bar, enabling you to keep close watch on the tree if you have recently checked in code, are sheriffing for the day, or are just interested in keeping tabs on how the code is doing."}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sample/unpack-diskimage.json	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,1 @@
+{"description": "Unpacks and mounts a .dmg file", "author": ["catlee", "kairo"], "url": "http://mxr.mozilla.org/mozilla-central/source/build/package/mac_osx/unpack-diskimage", "modified": 1298421274.8493755, "usage": ["dmg"], "name": "unpack-diskimage"}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/README.txt	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,1 @@
+Scripts associated with toolbox but not part of the essential web service
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/greasemonkey_scraper.py	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,22 @@
+"""
+// ==UserScript==
+// @name           TidyBox
+// @namespace      http://squarefree.com/userscripts
+// @description    Shrinks Tinderbox, letting you hover instead of scrolling to see information.  If you want to click a link in a popup, click to freeze the popup in place.
+// @include        http://tinderbox.mozilla.org/*
+// @author         Jesse Ruderman - http://www.squarefree.com/
+// ==/UserScript==
+
+// Version history:
+
+// 2008-02-23     Initial release
+// 2008-05-19     Updated for http://tinderbox.mozilla.org/showbuilds.cgi?tree=Mozilla2
+// 2008-06-27     Detect "OS X" in addition to "Mac OS X".  Detect "leak test".
+// 2008-11-23     Make it work on static pages and on the Camino page.  Add MIN_COLUMNS.
+// 2009-02-04     Inline the log links and "add comment" button (contributed by Jonas Sicking)
+//                  Also, add C links (contributed by Boris Zbarsky), and add links to the top-right.
+// 2009-02-14     Fix a bug where the 'pushlog + tinderbox' link was missing on calm trees.
+// 2009-08-08     Add 'M', 'E', 's', 'X' box types.
+// 2009-08-18     Hide animated flames (patch from cjones).
+// 2010-02-16     Update for split unit tests.  Show opt vs debug.  Fix animated-flame hiding.
+"""
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/html2json.py	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,82 @@
+#!/usr/bin/env python
+
+"""
+script to convert HTML microformat files to JSON:
+
+<div class="project">
+<h1><a href="${URL}">${PROJECT}</a></h1>
+<p class="description">${DESCRIPTION}</p>
+
+<!-- fields (lists) -->
+<ul class="author"><li>${AUTHOR}</li></ul>
+<ul class="usage"><li>${USAGE}</li></ul>
+</div>
+"""
+
+### imports
+
+import os
+
+try:
+    from lxml import etree
+except ImportError:
+    raise ImportError("""You need lxml to run this script. Try running
+    `easy_install lxml`
+    It will work if you're lucky""")
+
+try:
+    import json
+except ImportError:
+    import simplejson as json
+
+### parse command line
+
+from optparse import OptionParser
+
+usage = '%prog file'
+parser = OptionParser(usage=usage, description=__doc__)
+parser.add_option('--pprint', dest='pprint',
+                  action='store_true', default=False,
+                  help="pretty-print the json")
+                  
+options, args = parser.parse_args()
+
+if not len(args) == 1:
+    parser.print_help()
+    parser.exit()
+filename = args[0]
+assert os.path.exists(filename), "%s not found" % filename
+
+### parse teh file
+document = etree.parse(filename)
+elements = document.findall(".//div[@class='project']")
+if not elements:
+    root = document.getroot()
+    if root.tag == 'div' and 'project' in root.attrib.get('class', '').split():
+        elements = [root]
+if not elements:
+    parser.error('No <div class="project"> found')
+
+# print teh projects
+for element in elements:
+    project = {}
+    header = element.find('.//h1')
+    link = header.find('a')
+    if link is not None:
+        project['name'] = link.text
+        project['url'] = link.attrib['href']
+    else:
+        project['name'] = header.text
+    project['name'] = ' '.join(project['name'].strip().split())
+    description = element.find("p[@class='description']")
+    if description is not None:
+        project['description'] = description.text or ''
+        project['description'] = ' '.join(project['description'].strip().split())
+    for field in ('author', 'usage', 'language', 'type'):
+        e = element.find("ul[@class='%s']" % field)
+        if e is not None:
+            values = e.findall('li')
+            for value in values:
+                project.setdefault(field, []).append(value.text)
+    indent = options.pprint and 2 or None
+    print json.dumps(project, indent=indent)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/setup_scraper.py	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,28 @@
+"""
+scrapes metadata from a python setup.py file
+"""
+
+import imp
+import sys
+import urllib2
+
+# dictionary of 
+data = {}
+
+def mock_setup(**kwargs):
+    """
+    mock the distutils/setuptools setup function
+    """
+    import pdb; pdb.set_trace()
+
+def setuppy2tool(url):
+    """
+    reads a file path or URL (TODO)
+    returns a dict in the appropriate format
+    """
+    
+
+
+if __name__ == '__main__':
+    for arg in sys.argv[1:]:
+        print setuppy2tool(arg)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/setup.py	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,47 @@
+from setuptools import setup, find_packages
+
+try:
+    description = file('README.txt').read()
+except IOError:
+    description = ''
+
+version = "0.3"
+
+# dependencies
+dependencies = [
+    'WebOb',
+    'tempita',
+    'paste',
+    'pastescript', # technically optional, but here for ease of install
+    'whoosh >= 2.5',
+    'couchdb',
+    'docutils',
+    'pyloader',
+    'theslasher',
+    'pyes == 0.15',
+    ]
+
+setup(name='toolbox',
+      version=version,
+      description="a place to list Mozilla software tools",
+      long_description=description,
+      classifiers=[], # Get strings from http://www.python.org/pypi?%3Aaction=list_classifiers
+      author='Jeff Hammel',
+      author_email='jhammel@mozilla.com',
+      url='https://github.com/mozilla/toolbox',
+      license="MPL",
+      packages=['toolbox'],
+      include_package_data=True,
+      zip_safe=False,
+      install_requires=dependencies,
+      entry_points="""
+      # -*- Entry points: -*-
+      [console_scripts]
+      toolbox-convert-model = toolbox.model:convert
+      toolbox-serve = toolbox.factory:main
+
+      [paste.app_factory]
+      toolbox = toolbox.factory:paste_factory
+      """,
+      )
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/test.ini	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,28 @@
+#!/usr/bin/env paster
+
+# config for the testing webserver
+
+[DEFAULT]
+debug = true
+email_to = jhammel@example.com
+smtp_server = localhost
+error_email_from = paste@localhost
+
+[exe]
+command = serve
+
+[server:main]
+use = egg:Paste#http
+host = 0.0.0.0
+port = 9090
+
+[composite:main]
+use = egg:Paste#urlmap
+/ = toolbox
+
+set debug = false
+
+[app:toolbox]
+paste.app_factory = toolbox.factory:paste_factory
+toolbox.directory = %(here)s/test_json
+toolbox.fields = usage author type language dependencies
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/test.py	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,145 @@
+#!/usr/bin/env python
+
+"""
+doctest runner for toolbox
+"""
+
+import doctest
+import json
+import os
+import shutil
+import sys
+from cgi import escape
+from optparse import OptionParser
+from paste.fixture import TestApp
+from time import time
+from toolbox.dispatcher import Dispatcher
+
+# global
+directory = os.path.dirname(os.path.abspath(__file__))
+
+
+class ToolboxTestApp(TestApp):
+    """WSGI app wrapper for testing JSON responses"""
+
+    def __init__(self, **kw):
+        dispatcher_args = dict(model_type='memory_cache', fields=('usage', 'author', 'type', 'language', 'dependencies'))
+        dispatcher_args.update(kw)
+        app = Dispatcher(**dispatcher_args)
+        TestApp.__init__(self, app)
+
+    def get(self, url='/', **kwargs):
+        kwargs.setdefault('params', {})['format'] = 'json'
+        response = TestApp.get(self, url, **kwargs)
+        return json.loads(response.body)
+
+    def new(self, **kwargs):
+        kwargs['form-render-date'] = str(time())
+        return self.post('/new', params=kwargs)
+
+    def cleanup(self):
+        pass
+
+
+class FileCacheTestApp(ToolboxTestApp):
+    """test the MemoryCache file-backed backend"""
+
+    def __init__(self):
+        self.json_dir = os.path.join(directory, 'test_json')
+        shutil.rmtree(self.json_dir, ignore_errors=True)
+        os.makedirs(self.json_dir)
+        ToolboxTestApp.__init__(self, model_type='file_cache', directory=self.json_dir)
+
+    def cleanup(self):
+        shutil.rmtree(self.json_dir, ignore_errors=True)
+
+
+class CouchTestApp(ToolboxTestApp):
+    """test the MemoryCache file-backed backend"""
+
+    def __init__(self):
+        ToolboxTestApp.__init__(self, model_type='couch', dbname='test_json')
+        for project in self.app.model.projects():
+            self.app.model.delete(project)
+
+    def cleanup(self):
+        for project in self.app.model.projects():
+            self.app.model.delete(project)
+
+
+app_classes = {'memory_cache': ToolboxTestApp,
+               'file_cache': FileCacheTestApp,
+               'couch': CouchTestApp}
+
+
+def run_tests(app_cls,
+              raise_on_error=False,
+              cleanup=True,
+              report_first=False,
+              output=sys.stdout):
+
+    results = {}
+
+    # gather tests
+    tests =  [ test for test in os.listdir(directory)
+               if test.endswith('.txt') ]
+    output.write("Tests:\n%s\n" % '\n'.join(tests))
+
+    for test in tests:
+
+        # create an app
+        app = app_cls()
+
+        # doctest arguments
+        extraglobs = {'here': directory, 'app': app, 'urlescape': escape}
+        doctest_args = dict(extraglobs=extraglobs, raise_on_error=raise_on_error)
+        if report_first:
+            doctest_args['optionflags'] = doctest.REPORT_ONLY_FIRST_FAILURE
+
+        # run the test
+        try:
+            results[test] = doctest.testfile(test, **doctest_args)
+        except doctest.DocTestFailure, failure:
+            raise
+        except doctest.UnexpectedException, failure:
+            raise failure.exc_info[0], failure.exc_info[1], failure.exc_info[2]
+        finally:
+            if cleanup:
+                app.cleanup()
+
+    return results
+
+
+def main(args=sys.argv[1:]):
+
+    # parse command line args
+    parser = OptionParser()
+    parser.add_option('--no-cleanup', dest='cleanup',
+                      default=True, action='store_false',
+                      help="cleanup following the tests")
+    parser.add_option('--raise', dest='raise_on_error',
+                      default=False, action='store_true',
+                      help="raise on first error")
+    parser.add_option('--report-first', dest='report_first',
+                      default=False, action='store_true',
+                      help="report the first error only (all tests will still run)")
+    parser.add_option('--model', dest='model', default='file_cache',
+                      help="model to use")
+    options, args = parser.parse_args(args)
+
+    # get arguments to run_tests
+    kw = dict([(i, getattr(options, i)) for i in ('raise_on_error', 'cleanup', 'report_first')])
+    if options.model is not None:
+        try:
+            kw['app_cls'] = app_classes[options.model]
+        except KeyError:
+            parser.error("Model '%s' unknown (choose from: %s)" % (options.model, app_classes.keys()))
+
+    # run the tests
+    results = run_tests(**kw)
+    if sum([i.failed for i in results.values()]):
+        sys.exit(1) # error
+
+
+if __name__ == '__main__':
+    main()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/test_json.txt	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,274 @@
+Test toolbox JSON handling
+==========================
+
+Ensure we have no projects::
+
+    >>> app.get('/')
+    []
+
+Also no authors::
+
+    >>> app.get('/author')
+    {}
+
+Make a project::
+
+    >>> project = {'name': 'foo', 'description': 'foo description', 'url': 'http://example.com'}
+    >>> response = app.new(**project)
+    >>> response.status
+    303
+    >>> newproject = app.get('/')[0]
+    >>> modified = newproject.pop('modified')
+    >>> project == newproject
+    True
+
+Get the project by name::
+
+    >>> foo = app.get('/foo')
+    >>> modified == foo.pop('modified')
+    True
+    >>> foo == project
+    True
+
+Add some fields to it::
+
+    >>> fields = {'author': 'jhammel'}
+    >>> url = '/' + project['name']
+    >>> response = app.post(url, params=fields)
+    >>> app.get('/')[0]['author']
+    [u'jhammel']
+    >>> app.get('/author')
+    {u'jhammel': [u'foo']}
+    >>> fields = {'author': 'turing'}
+    >>> response = app.post(url, params=fields)
+    >>> sorted(app.get(url)['author'])
+    [u'jhammel', u'turing']
+    >>> sorted(app.get('/')[0]['author'])
+    [u'jhammel', u'turing']
+
+Now let's search for the project!::
+
+    >>> authors = app.get('/author')
+    >>> len(authors)
+    2
+    >>> authors['jhammel']
+    [u'foo']
+    >>> authors['turing']
+    [u'foo']
+
+Let's search for it a different way::
+
+    >>> project = app.get('/')[0]
+    >>> projects = app.get('/', params={'author': 'jhammel'})
+    >>> newproject = projects[0]
+    >>> newproject == project
+    True
+
+Just to show that the search is doing something::
+
+    >>> app.get('/', params={'author': 'sauron'})
+    []
+
+Now lets add another project::
+    
+    >>> project2 = {'name': 'bar', 'description': 'a bar downtown', 'url': 'http://www.example.com'}
+    >>> response = app.new(**project2)
+    >>> projects = app.get('/')
+    >>> len(projects)
+    2
+    >>> projects[0]['name']
+    u'bar'
+    >>> projects[1]['name']
+    u'foo'
+    >>> jhammels_projects = app.get('/', params={'author': 'jhammel'})
+    >>> len(jhammels_projects)
+    1
+    >>> jhammels_projects[0]['name']
+    u'foo'
+    
+Test search::
+
+    >>> projects = app.get('/', params={'q': 'jhammel'})
+    >>> len(projects)
+    1
+    >>> projects[0]['name']
+    u'foo'
+    >>> projects = app.get('/', params={'q': 'downtown'})
+    >>> len(projects)
+    1
+    >>> projects[0]['name']
+    u'bar'
+
+Add some metadata. Make sure we see it::
+
+    >>> url = '/bar'
+    >>> response = app.post(url, {'author': 'turing'})
+    >>> len(app.get())
+    2
+    >>> len(app.get('/', params={'author': 'jhammel'}))
+    1
+    >>> len(app.get('/', params={'q': 'jhammel'}))
+    1
+    >>> len(app.get('/', params={'author': 'turing'}))
+    2
+    >>> len(app.get('/', params={'q': 'turing'}))
+    2
+    >>> projects = app.get('/', params={'author': 'turing', 'q': 'downtown'})
+    >>> len(projects)
+    1
+    >>> projects[0]['name'] 
+    u'bar'
+
+Add a third project, just for variety::
+
+    >>> response = app.new(name='fleem', description='fleem in a building downtown', url='http://example.net')
+    >>> projects = app.get('/')
+    >>> len(projects)
+    3
+    >>> sorted([i['name'] for i in app.get('/', params=dict(q='downtown'))])
+    [u'bar', u'fleem']
+    >>> [i['name'] for i in app.get('/', params=dict(q='building'))]
+    [u'fleem']
+    
+Delete some metadata::
+
+    >>> response = app.post('/bar', params={'action': 'delete', 'author': 'turing'})
+    >>> projects = app.get('/', params={'author': 'turing'})
+    >>> len(projects)
+    1
+    >>> projects[0]['name']
+    u'foo'
+    >>> projects = app.get('/', params={'q': 'turing'})
+    >>> len(projects)
+    1
+    >>> projects[0]['name']
+    u'foo'
+
+Delete a project::
+
+    >>> response = app.post('/delete', params={'project': 'foo'})
+    >>> len(app.get('/'))
+    2
+    >>> len(app.get('/', params={'author': 'jhammel'}))
+    0
+    >>> results = app.get('/', params={'q': 'jhammel'})
+    >>> len(results)
+    0
+
+You're back to two basic projects without much metadata.  Let's give them some!::
+
+   >>> projects = app.get('/')
+   >>> [sorted(project.keys()) for project in projects]
+   [[u'description', u'modified', u'name', u'url'], [u'description', u'modified', u'name', u'url']]
+   >>> bar_modified_last = projects[0]['modified'] 
+   >>> fleem_modified_earlier = projects[1]['modified']
+   >>> bar_modified_last > fleem_modified_earlier
+   True
+   >>> [project['name'] for project in projects]
+   [u'bar', u'fleem']
+   >>> description = 'You could be swining on a star'
+   >>> response = app.post('/bar', params=dict(description=description))
+   >>> projects = app.get('/', params={'q': 'star'})
+   >>> len(projects)
+   1
+   >>> projects[0]['description'] == description
+   True
+   >>> response = app.post('/bar', params={'type': 'song', 'usage': 'music', 'author': 'Sinatra'})
+   >>> songs = app.get('/', params={'type': 'song'})
+   >>> len(songs)
+   1
+   >>> songs[0]['name'] == 'bar'
+   True
+   >>> songs = app.get('/', params={'q': 'song'})
+   >>> len(songs)
+   1
+   >>> songs[0]['name'] == 'bar'
+   True
+   >>> response = app.post('/fleem', params={'type': 'song', 'description': 'Cotton Eye Joe', 'author': 'Rednex'})
+   >>> songs = app.get('/', params={'type': 'song'})
+   >>> len(songs)
+   2
+   >>> songs = app.get('/', params={'q': 'song'})
+   >>> len(songs)
+   2
+   >>> songs = app.get('/', params={'type': 'song', 'q': 'star'})
+   >>> len(songs)
+   1
+   >>> songs[0]['name']
+   u'bar'
+   >>> songs = app.get('/', params={'type': 'song', 'author': 'Sinatra'})
+   >>> len(songs)
+   1
+   >>> songs[0]['name']
+   u'bar'
+
+Now try renaming a tool::
+   >>> [i['name'] for i in app.get('/')]
+   [u'fleem', u'bar']
+   >>> response = app.post('/bar', params={'name': 'star'})
+   >>> songs = app.get('/')
+   >>> len(songs)
+   2
+   >>> projects = app.get('/', params={'q': 'star'})
+   >>> len(projects)
+   1
+   >>> star = projects[0]
+   >>> star['name']
+   u'star'
+   >>> star['type']
+   [u'song']
+
+You should not be allowed to rename a tool if another tool has the
+same name::
+
+   >>> sorted([i['name'] for i in app.get('/')])
+   [u'fleem', u'star']
+   >>> response = app.post('/star', params={'name': 'fleem'}, status=403) # Forbidden
+   >>> sorted([i['name'] for i in app.get('/')])
+   [u'fleem', u'star']
+
+You should not be allowed to have multiple identical item in the same
+field::
+
+   >>> app.get('/star')['author']
+   [u'Sinatra']
+   >>> response = app.post('/star', params={'action': 'replace', 'author': 'Sinatra,Sinatra'})
+   >>> app.get('/star')['author']
+   [u'Sinatra']
+
+You can rename an entire set of fields::
+
+   >>> [project['type'] for project in app.get('/')]
+   [[u'song'], [u'song']]
+   >>> response = app.post('/type', params={'song': 'number one hit'})
+   >>> [project['type'] for project in app.get('/')]
+   [[u'number one hit'], [u'number one hit']]
+
+Fields in the request should be comma-separated and stripped of whitespace::
+
+    >>> project = {'name': 'A New Project', 'description': 'new description', 'url': 'http://example.com'}
+    >>> project_url = '/' + urlescape(project['name'])
+    >>> response = app.new(**project)
+    >>> fields = {'type': 'song, project, something special'}
+    >>> response = app.post(project_url, params=fields)
+    >>> sorted(app.get(project_url)['type'])
+    [u'project', u'something special', u'song']
+    
+You won't be able to have multiple identical field values or empty values::
+
+    >>> response = app.post(project_url, params=dict(author=' john, , , fielding, the third,,'))
+    >>> sorted(app.get(project_url)['author'])
+    [u'fielding', u'john', u'the third']
+
+You should not be able to rename a project::
+
+    >>> sorted([project['name'] for project in app.get('/')])
+    [u'A New Project', u'fleem', u'star']
+    >>> response = app.post('/star', params=dict(name=''), status=403)
+    >>> response.status
+    403
+    >>> response = app.post('/fleem', params=dict(name='       '), status=403)
+    >>> response.status
+    403
+    >>> sorted([project['name'] for project in app.get('/')])
+    [u'A New Project', u'fleem', u'star']
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/test_search.txt	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,26 @@
+Test toolbox search
+===================
+
+Ensure toolbox search does what we want it to.  First, create some
+projects::
+
+    >>> app.get('/')
+    []
+    >>> project = {'name': 'bzconsole', 'description': 'interact with bugzilla from the command line', 'url': 'http://k0s.org/mozilla/hg/bzconsole'}
+    >>> response = app.new(**project)
+    >>> project = {'name': 'my crazy addon', 'description': 'a crazy addon i made', 'url': 'http://a.m.o'}
+    >>> response = app.new(**project)
+    >>> [i['name'] for i in app.get('/')]
+    [u'my crazy addon', u'bzconsole']
+
+Define a search interface for our convenience::
+
+    >>> def search(query):
+    ...     return [i['name'] for i in app.get('/', params=dict(q=query))]
+    >>> search('addon')
+    [u'my crazy addon']
+
+You should be able to search for a name::
+
+    >>> search('bzconsole')
+    [u'bzconsole']
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/toolbox/__init__.py	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,1 @@
+#
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/toolbox/dispatcher.py	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,124 @@
+"""
+request dispatcher WSGI app:
+data persisting across requests should go here
+"""
+
+import os
+
+from handlers import CreateProjectView
+from handlers import DeleteProjectHandler
+from handlers import FieldView
+from handlers import ProjectView
+from handlers import QueryView
+from handlers import TagsView
+from handlers import AboutView
+from handlers import NotFound
+
+from model import models
+from util import strsplit
+from webob import Request, Response, exc
+
+here = os.path.dirname(os.path.abspath(__file__))
+
+class Dispatcher(object):
+    """toolbox WSGI app which dispatchers to associated handlers"""
+
+    # class defaults
+    defaults = { 'about': None, # file path to ReST about page
+                 'model_type': 'memory_cache', # type of model to use
+                 'handlers': None,
+                 'reload': True, # reload templates
+                 'reserved': None, # reserved URL namespaces
+                 'template_dir': None, # directory for template overrides
+                 'baseurl': '', # base URL for redirects
+
+                 # branding variables
+                 'site_name': 'toolbox', # name of the site
+                 'item_name': 'tool', # name of a single item
+                 'item_plural': None, # item_name's plural, or None for item_name + 's'
+                 }
+
+    def __init__(self, **kw):
+        """
+        **kw arguments used to override defaults
+        additional **kw are passed to the model
+        """
+
+        # set instance parameters from kw and defaults
+        for key in self.defaults:
+            setattr(self, key, kw.pop(key, self.defaults[key]))
+        if self.item_plural is None:
+            self.item_plural = self.item_name + 's'
+
+        # should templates be reloaded?
+        if isinstance(self.reload, basestring):
+            self.reload = self.reload.lower() == 'true'
+
+        # model: backend storage and associated methods
+        if 'fields' in kw and isinstance(kw['fields'], basestring):
+            # split fields if given as a string
+            kw['fields'] = strsplit(kw['fields'])
+        if hasattr(self.model_type, '__call__'):
+            model = self.model_type
+        elif self.model_type in models:
+            model = models[self.model_type]
+        else:
+            try:
+                import pyloader
+                model = pyloader.load(self.model_type)
+            except:
+                raise AssertionError("model_type '%s' not found in %s" % (self.model_type, models.keys()))
+        self.model = model(**kw)
+
+        # add an about view if file specified
+        if self.about:
+            about = file(self.about).read()
+            import docutils.core
+            about = docutils.core.publish_parts(about, writer_name='html')['body']
+            self.about = about
+
+
+        # request handlers in order they will be tried
+        if self.handlers is None:
+            self.handlers = [ TagsView,
+                              CreateProjectView,
+                              FieldView,
+                              QueryView,
+                              DeleteProjectHandler,
+                              ProjectView]
+            if self.about:
+                self.handlers.append(AboutView)
+
+        # extend reserved URLS from handlers
+        if self.reserved is None:
+            self.reserved = set(['css', 'js', 'img'])
+            for handler in self.handlers:
+                if handler.handler_path:
+                    self.reserved.add(handler.handler_path[0])
+
+    def __call__(self, environ, start_response):
+
+        # get a request object
+        request = Request(environ)
+
+        # get the path 
+        path = request.path_info.strip('/').split('/')
+        if path == ['']:
+            path = []
+        request.environ['path'] = path
+
+        # load any new data
+        self.model.load()
+
+        # match the request to a handler
+        for h in self.handlers:
+            handler = h.match(self, request)
+            if handler is not None:
+                break
+        else:
+            # our 404 handler
+            handler = NotFound(self, request)
+
+        # get response
+        res = handler()
+        return res(environ, start_response)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/toolbox/factory.py	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,80 @@
+#!/usr/bin/env python
+
+"""
+WSGI -> HTTP server factories for toolbox
+"""
+
+import os
+import sys
+
+from dispatcher import Dispatcher
+from paste.urlparser import StaticURLParser
+from pkg_resources import resource_filename
+from theslasher import TheSlasher
+
+class PassthroughFileserver(object):
+    """serve files if they exist"""
+
+    def __init__(self, app, directory):
+        self.app = app
+        self.directory = directory
+        self.fileserver = StaticURLParser(self.directory)
+
+    def __call__(self, environ, start_response):
+        path = environ['PATH_INFO'].strip('/')
+        if path and os.path.exists(os.path.join(self.directory, path)) and '..' not in path:
+            return self.fileserver(environ, start_response)
+        return self.app(environ, start_response)
+
+
+def paste_factory(global_conf=None, **app_conf):
+    """create a webob view and wrap it in middleware"""
+
+    keystr = 'toolbox.'
+    static_directory = app_conf.pop('static',
+                                    resource_filename(__name__, 'static'))
+    args = dict([(key.split(keystr, 1)[-1], value)
+                 for key, value in app_conf.items()
+                 if key.startswith(keystr) ])
+    app = TheSlasher(Dispatcher(**args)) # kill slashes
+    return PassthroughFileserver(app, static_directory)
+
+def wsgiref_factory(host='0.0.0.0', port=8080):
+    """wsgiref factory; for testing only"""
+
+    from wsgiref import simple_server
+    app = Dispatcher()
+    app = PassthroughFileserver(app, resource_filename(__name__, 'static'))
+    server = simple_server.make_server(host=host, port=int(port), app=app)
+    fqdn = '127.0.0.1' if host =='0.0.0.0' else host
+    print "Serving toolbox at http://%s:%d/" % (fqdn, port)
+    server.serve_forever()
+
+
+# WSGI factories available
+factories = {'paste': paste_factory,
+             'wsgiref': wsgiref_factory}
+
+def main(args=sys.argv[1:]):
+    """CLI entry point"""
+
+    # parse command line
+    usage = '%prog [options]'
+    import argparse
+    parser = argparse.ArgumentParser(usage=usage, description=__doc__.strip())
+    parser.add_argument('--factory', default='wsgiref',
+                        choices=factories.keys(),
+                        help="factory to use")
+    parser.add_argument('--port', type=int, default=8080,
+                        help="port to serve on")
+    args = parser.parse_args()
+
+    # serve toolbox
+    factory = factories[args.__dict__.pop('factory')]
+    factory_args = args.__dict__
+    print "Serving using factory: %s" % getattr(factory, '__name__', str(factory))
+    print "Factory arguments: %s" % factory_args
+    factory(**factory_args)
+
+if __name__ == '__main__':
+    main()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/toolbox/handlers.py	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,616 @@
+"""
+request handlers:
+these are instantiated for every request, then called
+"""
+
+import cgi
+import os
+from datetime import datetime
+from pkg_resources import resource_filename
+from urllib import quote as _quote
+from urlparse import urlparse
+from util import strsplit
+from util import JSONEncoder
+from webob import Response, exc
+from tempita import HTMLTemplate
+from time import time
+
+# this is necessary because WSGI stupidly follows the CGI convention wrt encoding slashes
+# http://comments.gmane.org/gmane.comp.web.pylons.general/5922
+encoded_slash = '%25%32%66'
+
+def quote(s, safe='/'):
+    if isinstance(s, unicode):
+        s = s.encode('utf-8', 'ignore') # hope we're using utf-8!
+    return _quote(s, safe).replace('/', encoded_slash)
+
+try:
+    import json
+except ImportError:
+    import simplejson as json
+
+class HandlerMatchException(Exception):
+    """the handler doesn't match the request"""
+
+class Handler(object):
+    """general purpose request handler (view)"""
+
+    methods = set(['GET']) # methods to listen to
+    handler_path = [] # path elements to match
+
+    @classmethod
+    def match(cls, app, request):
+
+        # check the method
+        if request.method not in cls.methods:
+            return None
+
+        # check the path
+        if request.environ['path'] != cls.handler_path:
+            return None
+
+        # check the constructor
+        try:
+            return cls(app, request)
+        except HandlerMatchException:
+            return None
+    
+    def __init__(self, app, request):
+        self.app = app
+        self.request = request
+        self.check_json() # is this a JSON request?
+
+    def __call__(self):
+        return getattr(self, self.request.method.title())()
+
+    def link(self, path=None):
+        """
+        link relative to the site root
+        """
+        path_info = self.request.path_info
+        segments = path_info.split('/')
+        if segments[0]:
+            segments.insert(0, '')
+
+        if len(segments) <3:
+            if not path or path == '/':
+                return './'
+            return path
+
+        nlayers = len(segments[2:])
+        string = '../' * nlayers
+
+        if not path or path == '/':
+            return string
+        return string + path
+
+    def redirect(self, location, query=None, anchor=None):
+        return exc.HTTPSeeOther(location=self.app.baseurl + '/' + location
+                                + (query and self.query_string(query) or '')
+                                + (anchor and ('#' + anchor) or ''))
+
+    def query_string(self, query):
+        """
+        generate a query string; query is a list of 2-tuples
+        """
+        return '?' + '&'.join(['%s=%s' % (i,j)
+                               for i, j in query])
+
+    # methods for JSON
+
+    def check_json(self):
+        """check to see if the request is for JSON"""
+        self.json = self.request.GET.pop('format', '') == 'json'
+
+    def post_data(self):
+        """python dict from POST request"""
+        if self.json:
+            return json.loads(self.request.body)
+        else:
+            retval = self.request.POST.mixed()
+            for key in retval:
+                value = retval[key]
+                if isinstance(value, basestring):
+                    retval[key] = value.strip()
+                else:
+                    # TODO[?]: just throw away all empty values here
+                    retval[key] = [i.strip() for i in value]
+            return retval
+
+    def get_json(self):
+        """JSON to serialize if requested for GET"""
+        raise NotImplementedError # abstract base class
+
+
+class TempitaHandler(Handler):
+    """handler for tempita templates"""
+
+    template_dirs = [ resource_filename(__name__, 'templates') ]
+
+    template_cache = {}
+
+    css = ['css/html5boilerplate.css']
+
+    less = ['css/style.less']
+
+    js = ['js/jquery-1.6.min.js',
+          'js/less-1.0.41.min.js',
+          'js/jquery.timeago.js',
+          'js/main.js']
+    
+    def __init__(self, app, request):
+        Handler.__init__(self, app, request)
+
+        # add application template_dir if specified
+        if app.template_dir:
+            self.template_dirs = self.template_dirs[:] + [app.template_dir]
+            
+        self.data = { 'request': request,
+                      'css': self.css,
+                      'item_name': self.app.item_name,
+                      'item_plural': self.app.item_plural,
+                      'less': self.less,
+                      'js':  self.js,
+                      'site_name': app.site_name,
+                      'title': self.__class__.__name__,
+                      'hasAbout': bool(app.about),
+                      'urlescape': quote,
+                      'link': self.link}
+
+    def find_template(self, name):
+        """find a template of a given name"""
+        # the application caches a dict of the templates if app.reload is False
+        if name in self.template_cache:
+            return self.template_cache[name]
+        
+        for d in self.template_dirs:
+            path = os.path.join(d, name)
+            if os.path.exists(path):
+                template = HTMLTemplate.from_filename(path)
+                if not self.app.reload:
+                    self.template_cache[name] = template
+                return template
+
+    def render(self, template, **data):
+        template = self.find_template(template)
+        if template is None:
+            raise Exception("I can't find your template")
+        return template.substitute(**data)
+
+    def Get(self):
+        # needs to have self.template set
+        if self.json:
+            return Response(content_type='application/json',
+                            body=json.dumps(self.get_json(), cls=JSONEncoder))
+        self.data['content'] = self.render(self.template, **self.data)
+        return Response(content_type='text/html',
+                        body=self.render('main.html', **self.data))
+
+class ProjectsView(TempitaHandler):
+    """abstract base class for views of projects"""
+
+    js = TempitaHandler.js[:]
+    js.extend(['js/jquery.tokeninput.js',
+               'js/jquery.jeditable.js',
+               'js/jquery.autolink.js',
+               'js/project.js'])
+               
+    less = TempitaHandler.less[:]
+    less.extend(['css/project.less'])
+
+    css = TempitaHandler.css[:]
+    css.extend(['css/token-input.css',
+                'css/token-input-facebook.css'])
+
+    def __init__(self, app, request):
+        """project views specific init"""
+        TempitaHandler.__init__(self, app, request)
+        self.data['fields'] = self.app.model.fields()
+        self.data['error'] = None
+        if not self.json:
+            self.data['format_date'] = self.format_date
+
+    def get_json(self):
+        """JSON to serialize if requested"""
+        return self.data['projects']
+
+    def sort(self, field):
+        reverse = False
+        if field.startswith('-'):
+            field = field[1:]
+            reverse = True
+        if field == 'name':
+            self.data['projects'].sort(key=lambda value: value[field].lower(), reverse=reverse)
+        else:
+            self.data['projects'].sort(key=lambda value: value[field], reverse=reverse)
+
+    def format_date(self, timestamp):
+        """return a string representation of a timestamp"""
+        format_string = '%Y-%m-%dT%H:%M:%SZ'
+        return datetime.utcfromtimestamp(timestamp).strftime(format_string)
+
+
+class QueryView(ProjectsView):
+    """general index view to query projects"""
+    
+    template = 'index.html'
+    methods = set(['GET'])
+
+    def __init__(self, app, request):
+        ProjectsView.__init__(self, app, request)
+
+        # pop non-query parameters;
+        # sort is popped first so that it does go in the query
+        sort_type = self.request.GET.pop('sort', None)
+        query = self.request.GET.mixed()
+        self.data['query'] = query
+        search = query.pop('q', None)
+        self.data['search'] = search
+
+        # query for tools
+        self.data['projects']= self.app.model.get(search, **query)
+
+        # order the results
+        self.data['sort_types'] = [('name', 'name'), ('-modified', 'last updated')]
+        if search:
+            self.data['sort_types'].insert(0, ('search', 'search rank'))
+        if sort_type is None:
+            if search:
+                sort_type = 'search'
+            else:
+                # default
+                sort_type = '-modified'
+        self.data['sort_type'] = sort_type
+        if sort_type != 'search':
+            # preserve search order results 
+            self.sort(sort_type)
+            
+        self.data['fields'] = self.app.model.fields()
+        self.data['title'] = self.app.site_name
+
+
+class ProjectView(ProjectsView):
+    """view of a particular project"""
+
+    template = 'index.html'
+    methods=set(['GET', 'POST'])
+
+    @classmethod
+    def match(cls, app, request):
+
+        # check the method
+        if request.method not in cls.methods:
+            return None
+
+        # the path should match a project
+        if not len(request.environ['path']) == 1:
+            return None
+
+        # get the project if it exists
+        projectname = request.environ['path'][0].replace('%2f', '/')  # double de-escape slashes, see top of file
+        try:
+            # if its utf-8, we should try to keep it utf-8
+            projectname = projectname.decode('utf-8')
+        except UnicodeDecodeError:
+            pass
+        project = app.model.project(projectname)
+        if not project:
+            return None
+
+        # check the constructor
+        try:
+            return cls(app, request, project)
+        except HandlerMatchException:
+            return None
+
+    def __init__(self, app, request, project):
+        ProjectsView.__init__(self, app, request)
+        self.data['fields'] = self.app.model.fields()
+        self.data['projects'] = [project]
+        self.data['title'] = project['name']
+
+    def get_json(self):
+        return self.data['projects'][0]
+
+    def Post(self):
+
+        # data
+        post_data = self.post_data()
+        project = self.data['projects'][0]
+
+        # insist that you have a name
+        if 'name' in post_data and not post_data['name'].strip():
+            self.data['title'] = 'Rename error'
+            self.data['error'] = 'Cannot give a project an empty name'
+            self.data['content'] = self.render(self.template, **self.data)
+            return Response(content_type='text/html',
+                            status=403,
+                            body=self.render('main.html', **self.data))
+
+        # don't allow overiding other projects with your fancy rename
+        if 'name' in post_data and post_data['name'] != project['name']:
+            if self.app.model.project(post_data['name']):
+                self.data['title'] = '%s -> %s: Rename error' % (project['name'], post_data['name'])
+                self.data['error'] = 'Cannot rename over existing project: <a href="%s">%s</a>' % (post_data['name'], post_data['name'] )
+                self.data['content'] = self.render(self.template, **self.data)
+                return Response(content_type='text/html',
+                                status=403,
+                                body=self.render('main.html', **self.data))
+
+        # XXX for compatability with jeditable:
+        id = post_data.pop('id', None)
+
+        action = post_data.pop('action', None)
+        old_name = project['name']
+        if action == 'delete':
+            for field in self.app.model.fields():
+                if field in post_data and field in project:
+                    values = post_data.pop(field)
+                    if isinstance(values, basestring):
+                        values = [values]
+                    for value in values:
+                        project[field].remove(value)
+                    if not project[field]:
+                        project.pop(field)
+        else:
+            for field in self.app.model.required:
+                if field in post_data:
+                    project[field] = post_data[field]
+            for field in self.app.model.fields():
+                if field in post_data:
+                    value = post_data[field]
+                    if isinstance(value, basestring):
+                        value = strsplit(value)
+                    if action == 'replace':
+                        # replace the field from the POST request
+                        project[field] = value
+                    else:
+                        # append the items....the default action
+                        project.setdefault(field, []).extend(value)
+
+        # rename handling
+        if 'name' in post_data and post_data['name'] != old_name:
+            self.app.model.delete(old_name)
+            self.app.model.update(project)
+            return self.redirect(quote(project['name']))
+
+        self.app.model.update(project)
+
+        # XXX for compatability with jeditable:
+        if id is not None:
+            return Response(content_type='text/plain',
+                            body=cgi.escape(project['description']))
+
+        # XXX should redirect instead
+        return self.Get()
+
+
+class FieldView(ProjectsView):
+    """view of projects sorted by a field"""
+
+    template = 'fields.html'
+    methods=set(['GET', 'POST'])
+    js = TempitaHandler.js[:] + ['js/field.js']
+
+    @classmethod
+    def match(cls, app, request):
+
+        # check the method
+        if request.method not in cls.methods:
+            return None
+
+        # the path should match a project
+        if len(request.environ['path']) != 1:
+            return None
+
+        # ensure the field exists
+        field = request.environ['path'][0]
+        if field not in app.model.fields():
+            return None
+
+        # check the constructor
+        try:
+            return cls(app, request, field)
+        except HandlerMatchException:
+            return None
+
+    def __init__(self, app, request, field):
+        ProjectsView.__init__(self, app, request)
+        projects = self.app.model.field_query(field)
+        if projects is None:
+            projects = {}
+        self.data['field'] = field
+        self.data['values'] = projects
+        self.data['title'] = app.item_plural + ' by %s' % field
+        if self.request.method == 'GET':
+            # get project descriptions for tooltips
+            descriptions = {}
+            project_set = set()
+            for values in projects.values():
+                project_set.update(values)
+            self.data['projects'] = dict([(name, self.app.model.project(name))
+                                          for name in project_set])
+
+    def Post(self):
+        field = self.data['field']
+        for key in self.request.POST.iterkeys():
+            value = self.request.POST[key]
+            self.app.model.rename_field_value(field, key, value)
+        
+        return self.redirect(field, anchor=value)
+        
+    def get_json(self):
+        return self.data['values']
+
+        
+class CreateProjectView(TempitaHandler):
+    """view to create a new project"""
+
+    template = 'new.html'
+    methods = set(['GET', 'POST'])
+    handler_path = ['new']
+    js = TempitaHandler.js[:]
+    js.extend(['js/jquery.tokeninput.js',
+               'js/queryString.js',
+               'js/new.js'])
+               
+    less = TempitaHandler.less[:]
+    less.extend(['css/new.less'])
+    
+    css = TempitaHandler.css[:]
+    css.extend(['css/token-input.css',
+                'css/token-input-facebook.css'])
+
+    def __init__(self, app, request):
+        TempitaHandler.__init__(self, app, request)
+        self.data['title'] = 'Add a ' + app.item_name
+        self.data['fields'] = self.app.model.fields()
+
+    def check_name(self, name):
+        """
+        checks a project name for validity
+        returns None on success or an error message if invalid
+        """
+        reserved = self.app.reserved.copy()
+        if name in reserved or name in self.app.model.fields(): # check application-level reserved URLS
+            return 'reserved'
+        if self.app.model.project(name): # check projects for conflict
+            return 'conflict'
+
+    def Post(self):
+
+        # get some data
+        required = self.app.model.required
+        post_data = self.post_data()
+
+        # ensure the form isn't over 24 hours old
+        day = 24*3600
+        form_date = post_data.pop('form-render-date', -day)
+        try:
+            form_date = float(form_date)
+        except ValueError:
+            form_date = -day
+        if abs(form_date - time()) > day:
+            # if more than a day old, don't honor the request
+            return Response(content_type='text/plain',
+                            status=400,
+                            body="Your form is over a day old or you don't have Javascript enabled")
+
+        # build up a project dict
+        project = dict([(i, post_data.get(i, '').strip())
+                        for i in required])
+
+        # check for errors
+        errors = {}
+        missing = set([i for i in required if not project[i]])
+        if missing: # missing required fields
+            errors['missing'] = missing
+        # TODO check for duplicate project name
+        # and other url namespace collisions
+        name_conflict = self.check_name(project['name'])
+        if name_conflict:
+            errors[name_conflict] = [project['name']]
+        if errors:
+            error_list = []
+            for key in errors:
+                # flatten the error dict into a list
+                error_list.extend([(key, i) for i in errors[key]])
+            return self.redirect(self.request.path_info.strip('/'), error_list)
+
+        # add fields to the project
+        for field in self.app.model.fields():
+            value = post_data.get(field, '').strip()
+            values = strsplit(value)
+            if not value:
+                continue
+            project[field] = values or value
+
+        self.app.model.update(project)
+        return self.redirect(quote(project['name']))
+
+
+class DeleteProjectHandler(Handler):
+
+    methods = set(['POST'])
+    handler_path = ['delete']
+
+    def Post(self):        
+        post_data = self.post_data()
+        project = post_data.get('project')
+        if project:
+            try:
+                self.app.model.delete(project)
+            except:
+                pass # XXX better than internal server error
+
+        # redirect to query view
+        return self.redirect('')
+
+
+class TagsView(TempitaHandler):
+    """view most popular tags"""
+    methods = set(['GET'])
+    handler_path = ['tags']
+    template = 'tags.html'
+
+    def __init__(self, app, request):
+        TempitaHandler.__init__(self, app, request)
+        self.data['fields'] = self.app.model.fields()
+        fields = self.request.GET.getall('field') or self.data['fields']
+        query = self.request.GET.get('q', '')
+        self.data['title'] = 'Tags'
+        field_tags = dict((i, {}) for i in fields)
+        omit = self.request.GET.getall('omit')
+        ommitted = dict([(field, set()) for field in fields])
+        for name in omit:
+            project = self.app.model.project(name)
+            if not project:
+                continue
+            for field in fields:
+                ommitted[field].update(project.get(field, []))
+            
+        for project in self.app.model.get():
+            if project in omit:
+                continue
+            # TODO: cache this for speed somehow
+            # possibly at the model level
+            for field in fields:
+                for value in project.get(field, []):
+                    if value in ommitted[field] or query not in value:
+                        continue
+                    count = field_tags[field].get(value, 0) + 1
+                    field_tags[field][value] = count
+        tags = []
+        for field in field_tags:
+            for value, count in field_tags[field].items():
+                tags.append({'field': field, 'value': value, 'count': count, 'id': value, 'name': value})
+        tags.sort(key=lambda x: x['count'], reverse=True)
+
+        self.data['tags'] = tags
+
+    def get_json(self):
+        return self.data['tags']
+
+
+class AboutView(TempitaHandler):
+    """the obligatory about page"""
+    methods = set(['GET'])
+    handler_path = ['about']
+    template = 'about.html'
+    less = TempitaHandler.less[:] + ['css/about.less']
+    def __init__(self, app, request):
+        TempitaHandler.__init__(self, app, request)
+        self.data['fields'] = self.app.model.fields()
+        self.data['title'] = 'about:' + self.app.site_name
+        self.data['about'] = self.app.about
+
+class NotFound(TempitaHandler):
+    def __init__(self, app, request):
+        TempitaHandler.__init__(self, app, request)
+        self.data['fields'] = self.app.model.fields()
+
+    def __call__(self):
+        self.data['content'] = '<h1 id="title">Not Found</h1>'
+        return Response(content_type='text/html',
+                        status=404,
+                        body=self.render('main.html', **self.data))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/toolbox/model.py	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,456 @@
+"""
+models for toolbox
+"""
+
+import couchdb
+import os
+import pyes
+import sys
+from copy import deepcopy
+from search import WhooshSearch
+from time import time
+from util import str2filename
+
+try:
+    import json
+except ImportError:
+    import simplejson as json
+
+# TODO: types of fields:
+# - string: a single string: {'type': 'string', 'name': 'name', 'required': True}
+# - field: a list of strings: {'type': 'field', 'name', 'usage'}
+# - dict: a subclassifier: {'type': '???', 'name': 'url', 'required': True}
+# - computed values, such as modified
+
+class ProjectsModel(object):
+    """
+    abstract base class for toolbox tools
+    """
+
+    def __init__(self, fields=None, required=('name', 'description', 'url'),
+                 whoosh_index=None):
+        """
+        - fields : list of fields to use, or None to calculate dynamically
+        - required : required data (strings)
+        - whoosh_index : directory to keep whoosh index in
+        """
+        self.required = set(required)
+
+        # reserved fields
+        self.reserved = self.required.copy()
+        self.reserved.update(['modified']) # last modified, a computed value
+        self.search = WhooshSearch(whoosh_index=whoosh_index)
+
+        # classifier fields
+        self._fields = fields
+        self.field_set = set(fields or ())
+
+    def update_search(self, project):
+        """update the search index"""
+        assert self.required.issubset(project.keys()) # XXX should go elsewhere
+        fields = dict([(field, project[field])
+                       for field in self.fields()
+                       if field in project])
+
+        # keys must be strings, not unicode, on some systems
+        f = dict([(str(i), j) for i, j in fields.items()])
+
+        self.search.update(name=project['name'], description=project['description'], **f)
+
+    def fields(self):
+        """what fields does the model support?"""
+        if self._fields is not None:
+            return self._fields
+        return list(self.field_set)
+
+    def projects(self):
+        """list of all projects"""
+        return [i['name'] for i in self.get()]
+
+    def export(self, other):
+        """export the current model to another model instance"""
+        for project in self.get():
+            other.update(project)
+
+    def rename_field_value(self, field, from_value, to_value):
+        projects = self.get(None, **{field: from_value})
+        for project in projects:
+            project[field].remove(from_value)
+            project[field].append(to_value)
+            self.update(project)
+
+    ### implementor methods
+
+    def update(self, project):
+        """update a project"""
+        raise NotImplementedError
+
+    def get(self, search=None, **query):
+        """
+        get a list of projects matching a query
+        the query should be key, value pairs to match;
+        if the value is single, it should be a string;
+        if the value is multiple, it should be a set which will be
+        ANDed together
+        """
+        raise NotImplementedError
+
+    def project(self, name):
+        """get a project of a particular name, or None if there is none"""
+        raise NotImplementedError
+
+    def field_query(self, field):
+        """get projects according to a particular field, or None"""
+        raise NotImplementedError
+
+    def delete(self, project):
+        raise NotImplementedError
+
+
+class MemoryCache(ProjectsModel):
+    """
+    sample implementation keeping everything in memory
+    """
+
+    def __init__(self, fields=None, whoosh_index=None):
+        
+        ProjectsModel.__init__(self, fields=fields, whoosh_index=whoosh_index)
+
+        # indices
+        self._projects = {}
+        self.index = {}
+        
+        self.load()
+
+    def update(self, project, load=False):
+        
+        if project['name'] in self._projects and project == self._projects[project['name']]:
+            return # nothing to do
+        if not load:
+            project['modified'] = time()
+        if self._fields is None:
+            fields = [i for i in project if i not in self.reserved]
+            self.field_set.update(fields)
+        else:
+            fields = self._fields
+        for field in fields:
+            for key, _set in self.index.get(field, {}).items():
+                _set.discard(project['name'])
+                if not _set:
+                    self.index[field].pop(key)
+            if field not in project:
+                continue
+            project[field] = list(set([i.strip() for i in project[field] if i.strip()]))
+            index = self.index.setdefault(field, {})
+            values = project[field]
+            if isinstance(values, basestring):
+                values = [values]
+            for value in values:
+                index.setdefault(value, set()).update([project['name']])
+        self._projects[project['name']] = deepcopy(project)
+        self.update_search(project)
+        if not load:
+            self.save(project)
+
+    def get(self, search=None, **query):
+        """
+        - search: text search
+        - query: fields to match
+        """
+        order = None
+        if search:
+            results = self.search(search)
+            order = dict([(j,i) for i,j in enumerate(results)])
+        else:
+            results = self._projects.keys()
+        results = set(results)
+        for key, values in query.items():
+            if isinstance(values, basestring):
+                values = [values]
+            for value in values:
+                results.intersection_update(self.index.get(key, {}).get(value, set()))
+        if order:
+            # preserve search order
+            results = sorted(list(results), key=lambda x: order[x])
+        return [deepcopy(self._projects[project]) for project in results]
+
+
+    def project(self, name):
+        if name in self._projects:
+            return deepcopy(self._projects[name])
+
+    def field_query(self, field):
+        if field in self.index:
+            return deepcopy(self.index.get(field))
+
+    def delete(self, project):
+        """
+        delete a project
+        - project : name of the project
+        """
+        if project not in self._projects:
+            return
+        del self._projects[project]
+        for field, classifiers in self.index.items():
+            for key, values in classifiers.items():
+                classifiers[key].discard(project)
+                if not classifiers[key]:
+                    del classifiers[key]
+        self.search.delete(project)
+        
+    def load(self):
+        """for subclasses; in memory, load nothing"""
+
+    def save(self, project):
+        """for subclasses; in memory, save nothing"""
+
+
+class FileCache(MemoryCache):
+    """save in JSON blob directory"""
+
+    def __init__(self, directory, fields=None, whoosh_index=None):
+        """
+        - directory: directory of .json tool files
+        """
+        # JSON blob directory
+        if not os.path.exists(directory):
+            os.makedirs(directory)
+        assert os.path.isdir(directory)
+        self.directory = directory
+
+        self.files = {}
+        MemoryCache.__init__(self, fields=fields, whoosh_index=whoosh_index)
+
+    def delete(self, project):
+        MemoryCache.delete(self, project)
+        os.remove(os.path.join(self.directory, self.files.pop(project)))
+
+    def load(self):
+        """load JSON from the directory"""
+        for i in os.listdir(self.directory):
+            if not i.endswith('.json'):
+                continue
+            filename = os.path.join(self.directory, i)
+            try:
+                project = json.loads(file(filename).read())
+            except:
+                print 'File: ' + i
+                raise
+            self.files[project['name']] = i
+            self.update(project, load='modified' in project)
+
+    def save(self, project):
+
+        filename = self.files.get(project['name'])
+        if not filename:
+            filename = str2filename(project['name']) + '.json'
+        filename = filename.encode('ascii', 'ignore')
+        filename = os.path.join(self.directory, filename)
+        try:
+            f = file(filename, 'w')
+        except Exception, e:
+            print filename, repr(filename)
+            raise
+        f.write(json.dumps(project))
+        f.close()
+
+
+class ElasticSearchCache(MemoryCache):
+    """
+    store json in ElasticSearch
+    """
+
+    def __init__(self,
+                 server="localhost:9200",
+                 es_index="toolbox",
+                 doc_type="projects",
+                 fields=None,
+                 whoosh_index=None):
+        self.es_index = es_index
+        self.doc_type = doc_type
+
+        try:
+            self.es = pyes.ES([server])
+            self.es.create_index(self.es_index)
+        except pyes.urllib3.connectionpool.MaxRetryError:
+            raise Exception("Could not connect to ES instance")
+        except pyes.exceptions.ElasticSearchException:
+            # this just means the index already exists
+            pass
+        MemoryCache.__init__(self, fields=fields, whoosh_index=whoosh_index)
+
+    def es_query(self, query):
+        """make an ElasticSearch query and return the results"""
+        search = pyes.Search(query)
+        results = self.es.search(query=search,
+                                 indexes=[self.es_index],
+                                 doc_types=[self.doc_type],
+                                 size=0)
+
+        # the first query is just used to determine the size of the set
+        if not 'hits' in results and not 'total' in results['hits']:
+            raise Exception("bad ES response %s" % json.dumps(results))
+        total = results['hits']['total']
+
+        # repeat the query to retrieve the entire set
+        results = self.es.search(query=search,
+                                 indexes=[self.es_index],
+                                 doc_types=[self.doc_type],
+                                 size=total)
+
+        if not 'hits' in results and not 'hits' in results['hits']:
+            raise Exception("bad ES response %s" % json.dumps(results))
+
+        return results
+
+    def load(self):
+        """load all json documents from ES:toolbox/projects"""
+        query = pyes.MatchAllQuery()
+        results = self.es_query(query)
+
+        for hit in results['hits']['hits']:
+            self.update(hit['_source'], True)
+
+    def save(self, project):
+        query = pyes.FieldQuery()
+        query.add('name', project['name'])
+        results = self.es_query(query)
+
+        # If there is an existing records in ES with the same
+        # project name, update that record.  Otherwise create a new record.
+        id = None
+        if results['hits']['hits']:
+            id = results['hits']['hits'][0]['_id']
+
+        self.es.index(project, self.es_index, self.doc_type, id)
+
+    def delete(self, project):
+        MemoryCache.delete(self, project)
+        query = pyes.FieldQuery()
+        query.add('name', project['name'])
+        results = self.es_query(query)
+        if results['hits']['hits']:
+            id = results['hits']['hits'][0]['_id']
+            self.es.delete(self.es_index, self.doc_type, id)
+
+
+class CouchCache(MemoryCache):
+    """
+    store json files in couchdb
+    """
+
+    def __init__(self,
+                 server="http://127.0.0.1:5984",
+                 dbname="toolbox",
+                 fields=None,
+                 whoosh_index=None):
+
+        # TODO: check if server is running
+        couchserver = couchdb.Server(server)
+        try:
+            self.db = couchserver[dbname]
+        except couchdb.ResourceNotFound: # XXX should not be a blanket except!
+            self.db = couchserver.create(dbname)
+        except:
+            raise Exception("Could not connect to couch instance. Make sure that you have couch running at %s and that you have database create priveleges if '%s' does not exist" % (server, dbname))
+        MemoryCache.__init__(self, fields=fields, whoosh_index=whoosh_index)
+
+    def load(self):
+        """load JSON objects from CouchDB docs"""
+        for id in self.db:
+            doc = self.db[id]
+            try:
+                project = doc['project']
+            except KeyError:
+                continue   # it's prob a design doc
+            self.update(project, load=True)
+            
+    def save(self, project):
+        name = project['name']
+        try:
+             updated = self.db[name]
+        except:
+             updated = {}
+        updated['project'] = project
+        self.db[name] = updated
+
+    def delete(self, project):
+        MemoryCache.delete(self, project)
+        del self.db[project]
+
+# directory of available models
+models = {'memory_cache': MemoryCache,
+          'file_cache': FileCache,
+          'couch': CouchCache,
+          'es': ElasticSearchCache}
+
+def convert(args=sys.argv[1:]):
+    """CLI front-end for model conversion"""
+    from optparse import OptionParser
+    usage = '%prog [global-options] from_model [options] to_model [options]'
+    description = "export data from one model to another"
+    parser = OptionParser(usage=usage, description=description)
+    parser.disable_interspersed_args()
+    parser.add_option('-l', '--list-models', dest='list_models',
+                      action='store_true', default=False,
+                      help="list available models")
+    parser.add_option('-a', '--list-args', dest='list_args',
+                      metavar='MODEL',
+                      help="list arguments for a model")
+
+    options, args = parser.parse_args(args)
+
+    # process global options
+    if options.list_models:
+        for name in sorted(models.keys()):
+            print name # could conceivably print docstring
+        parser.exit()
+    if options.list_args:
+        if not options.list_args in models:
+            parser.error("Model '%s' not found. (Choose from: %s)" % (options.list_args, models.keys()))
+        ctor = models[options.list_args].__init__
+        import inspect
+        argspec = inspect.getargspec(ctor)
+        defaults = [[i, None] for i in argspec.args[1:]] # ignore self
+        for index, value in enumerate(reversed(argspec.defaults), 1):
+            defaults[-index][-1] = value
+        defaults = [[i,j] for i, j in defaults if i != 'fields']
+        print '%s arguments:' % options.list_args
+        for arg, value in defaults:
+            print ' -%s %s' % (arg, value or '')
+        parser.exit()
+
+    # parse models and their ctor args
+    sects = []
+    _models = []
+    for arg in args:
+        if arg.startswith('-'):
+            sects[-1].append(arg)
+        else:
+            _models.append(arg)
+            sects.append([])
+
+    # check models
+    if len(_models) != 2:
+        parser.error("Please provide two models. (You gave: %s)" % _models)
+    if not set(_models).issubset(models):
+        parser.error("Please use these models: %s (You gave: %s)" % (models, _models))
+
+    sects = [ [i.lstrip('-') for i in sect ] for sect in sects ]
+
+    # require an equals sign
+    # XXX hacky but much easier to parse
+    if [ True for sect in sects
+         if [i for i in sect if '=' not in i] ]:
+        parser.error("All arguments must be `key=value`")
+    sects = [dict([i.split('=', 1) for i in sect]) for sect in sects]
+
+    # instantiate models
+    from_model = models[_models[0]](**sects[0])
+    to_model = models[_models[1]](**sects[1])
+
+    # convert the data
+    from_model.export(to_model)
+
+if __name__ == '__main__':
+    convert()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/toolbox/search.py	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,107 @@
+import os
+import shutil
+import tempfile
+
+from time import sleep
+from whoosh import fields
+from whoosh import index
+from whoosh.query import And
+from whoosh.query import Or
+from whoosh.query import Term
+from whoosh.qparser import QueryParser
+from whoosh.index import LockError
+
+class WhooshSearch(object):
+    """full-text search"""
+
+    def __init__(self, whoosh_index=None):
+        """
+        - whoosh_index : whoosh index directory
+        """
+        self.schema = fields.Schema(name=fields.ID(unique=True, stored=True),
+                                    description=fields.TEXT)
+        self.keywords = set([])
+        self.tempdir = False
+        if whoosh_index is None:
+            whoosh_index = tempfile.mkdtemp()
+            self.tempdir = True
+        if not os.path.exists(whoosh_index):
+            os.makedirs(whoosh_index)
+        self.index = whoosh_index
+        self.ix = index.create_in(self.index, self.schema)
+
+    def update(self, name, description, **kw):
+        """update a document"""
+
+        # forgivingly get the writer
+        timeout = 3. # seconds
+        ctr = 0.
+        incr = 0.2
+        while ctr < timeout:
+            try:
+                writer = self.ix.writer()
+                break
+            except LockError:
+                ctr += incr
+                sleep(incr)
+        else:
+            raise
+
+        # add keywords
+        for key in kw:
+            if key not in self.keywords:
+                writer.add_field(key, fields.KEYWORD)
+                self.keywords.add(key)
+            if not isinstance(kw[key], basestring):
+                kw[key] = ' '.join(kw[key])
+            kw[key] = unicode(kw[key])
+
+        # convert to unicode for whoosh
+        # really whoosh should do this for us
+        # and really python should be unicode-based :(
+        name = unicode(name)
+        description = unicode(description)
+
+        writer.update_document(name=name, description=description, **kw)
+        writer.commit()
+
+    def delete(self, name):
+        """delete a document of a given name"""
+        writer = self.ix.writer()
+        name = unicode(name)
+        writer.delete_by_term('name', name)
+        writer.commit()
+
+    def __call__(self, query):
+        """search"""
+        query = unicode(query)
+        query_parser = QueryParser("description", schema=self.ix.schema)
+        myquery = query_parser.parse(query)
+
+# Old code: too strict
+#        extendedquery = Or([myquery] +
+#                           [Term(field, query) for field in self.keywords])
+
+
+        # New code: too permissive
+#        extendedquery = [myquery]
+        excluded = set(['AND', 'OR', 'NOT'])
+        terms = [i for i in query.split() if i not in excluded]
+#        for field in self.keywords:
+#            extendedquery.extend([Term(field, term) for term in terms])
+#        extendedquery = Or(extendedquery)
+
+        # Code should look something like
+        #Or([myquery] + [Or(
+        # extendedquery = [myquery]
+        extendedquery = And([Or([myquery] + [Term('description', term), Term('name', term)] +
+                                [Term(field, term) for field in self.keywords]) for term in terms])
+
+        # perform the search
+        searcher = self.ix.searcher()
+        return [i['name'] for i in searcher.search(extendedquery, limit=None)]
+        
+    def __del__(self):
+        if self.tempdir:
+            # delete the temporary directory, if present
+            shutil.rmtree(self.index)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/toolbox/static/css/about.less	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,14 @@
+p {
+margin: 1.0em;
+}
+
+ul {
+list-style-type: disc;
+margin-bottom: 1.0em;
+margin-top: 1.0em;
+}
+
+em {
+font-style: italic;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/toolbox/static/css/html5boilerplate.css	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,136 @@
+/* HTML5 ✰ Boilerplate */
+
+html, body, div, span, object, iframe,
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+abbr, address, cite, code, del, dfn, em, img, ins, kbd, q, samp,
+small, strong, sub, sup, var, b, i, dl, dt, dd, ol, ul, li,
+fieldset, form, label, legend,
+table, caption, tbody, tfoot, thead, tr, th, td,
+article, aside, canvas, details, figcaption, figure,
+footer, header, hgroup, menu, nav, section, summary,
+time, mark, audio, video {
+  margin: 0;
+  padding: 0;
+  border: 0;
+  font-size: 100%;
+  font: inherit;
+  vertical-align: baseline;
+}
+
+article, aside, details, figcaption, figure,
+footer, header, hgroup, menu, nav, section {
+  display: block;
+}
+
+blockquote, q { quotes: none; }
+blockquote:before, blockquote:after,
+q:before, q:after { content: ""; content: none; }
+ins { background-color: #ff9; color: #000; text-decoration: none; }
+mark { background-color: #ff9; color: #000; font-style: italic; font-weight: bold; }
+del { text-decoration: line-through; }
+abbr[title], dfn[title] { border-bottom: 1px dotted; cursor: help; }
+table { border-collapse: collapse; border-spacing: 0; }
+hr { display: block; height: 1px; border: 0; border-top: 1px solid #ccc; margin: 1em 0; padding: 0; }
+input, select { vertical-align: middle; }
+
+body { font:13px/1.231 sans-serif; *font-size:small; }
+select, input, textarea, button { font:99% sans-serif; }
+pre, code, kbd, samp { font-family: monospace, sans-serif; }
+
+html { overflow-y: scroll; }
+a:hover, a:active { outline: none; }
+ul, ol { margin-left: 2em; }
+ol { list-style-type: decimal; }
+nav ul, nav li { margin: 0; list-style:none; list-style-image: none; }
+small { font-size: 85%; }
+strong, th { font-weight: bold; }
+td { vertical-align: top; }
+sub, sup { font-size: 75%; line-height: 0; position: relative; }
+sup { top: -0.5em; }
+sub { bottom: -0.25em; }
+
+pre { white-space: pre; white-space: pre-wrap; word-wrap: break-word; padding: 15px; }
+textarea { overflow: auto; } 
+.ie6 legend, .ie7 legend { margin-left: -7px; } 
+input[type="radio"] { vertical-align: text-bottom; }
+input[type="checkbox"] { vertical-align: bottom; }
+.ie7 input[type="checkbox"] { vertical-align: baseline; }
+.ie6 input { vertical-align: text-bottom; }
+label, input[type="button"], input[type="submit"], input[type="image"], button { cursor: pointer; }
+button, input, select, textarea { margin: 0; }
+input:valid, textarea:valid   {  }
+input:invalid, textarea:invalid { border-radius: 1px; -moz-box-shadow: 0px 0px 5px red; -webkit-box-shadow: 0px 0px 5px red; box-shadow: 0px 0px 5px red; }
+.no-boxshadow input:invalid, .no-boxshadow textarea:invalid { background-color: #f0dddd; }
+
+
+::-moz-selection{ background: #FF5E99; color:#fff; text-shadow: none; }
+::selection { background:#FF5E99; color:#fff; text-shadow: none; }
+a:link { -webkit-tap-highlight-color: #FF5E99; }
+button {  width: auto; overflow: visible; }
+.ie7 img { -ms-interpolation-mode: bicubic; }
+
+body, select, input, textarea { color: #444; }
+h1, h2, h3, h4, h5, h6 { font-weight: bold; }
+a, a:active, a:visited { color: #607890; }
+a:hover { color: #036; }
+
+
+/**
+ * Primary styles
+ *
+ * Author: 
+ */
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+.ir { display: block; text-indent: -999em; overflow: hidden; background-repeat: no-repeat; text-align: left; direction: ltr; }
+.hidden { display: none; visibility: hidden; }
+.visuallyhidden { border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; }
+.visuallyhidden.focusable:active,
+.visuallyhidden.focusable:focus { clip: auto; height: auto; margin: 0; overflow: visible; position: static; width: auto; }
+.invisible { visibility: hidden; }
+.clearfix:before, .clearfix:after { content: "\0020"; display: block; height: 0; overflow: hidden; }
+.clearfix:after { clear: both; }
+.clearfix { zoom: 1; }
+
+
+@media all and (orientation:portrait) {
+
+}
+
+@media all and (orientation:landscape) {
+
+}
+
+@media screen and (max-device-width: 480px) {
+  
+  /* html { -webkit-text-size-adjust:none; -ms-text-size-adjust:none; } */
+}
+
+
+@media print {
+  * { background: transparent !important; color: black !important; text-shadow: none !important; filter:none !important;
+  -ms-filter: none !important; } 
+  a, a:visited { color: #444 !important; text-decoration: underline; }
+  a[href]:after { content: " (" attr(href) ")"; }
+  abbr[title]:after { content: " (" attr(title) ")"; }
+  .ir a:after, a[href^="javascript:"]:after, a[href^="#"]:after { content: ""; }  
+  pre, blockquote { border: 1px solid #999; page-break-inside: avoid; }
+  thead { display: table-header-group; } 
+  tr, img { page-break-inside: avoid; }
+  @page { margin: 0.5cm; }
+  p, h2, h3 { orphans: 3; widows: 3; }
+  h2, h3{ page-break-after: avoid; }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/toolbox/static/css/new.less	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,55 @@
+#new-container {
+  margin-top: 20px;
+  width: 600px;
+}
+
+#new {
+  margin: 20px auto;
+}
+
+table {
+  text-transform: lowercase;
+}
+
+td {
+  padding: .5em .5em;
+  
+  &.field-name {
+    text-align: right;
+  }
+
+  input {
+    padding: .2em;
+    
+    &:not(.submit) {
+      width: 250px;
+    }
+    
+    &[name="name"] {
+      width: 140px;
+    }
+    
+    &[name="description"] {
+      width: 320px;
+    }
+  }
+  
+  .token-input-list-facebook {
+    width: 240px;
+  }
+}
+
+input[type="submit"] {
+  float: right;
+}
+
+.button {
+  background-color: #F7F7F7;
+  border-radius: 8px 8px 8px 8px;
+  box-shadow: 0 1px 3px #999999;
+  color: #222222;
+  cursor: pointer;
+  display: inline-block;
+  margin: 0.3em;
+  padding: 0.4em 0.7em;
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/toolbox/static/css/project.less	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,150 @@
+@greyed: #999;
+@greyed-out: #aaa;
+
+.query {
+  font-size: 65%;
+  font-weight: normal;
+}
+
+.query-value {
+  font-weight: bold;
+}
+
+.query-search {
+  &:before, &:after {
+    content: '"';
+  }
+}
+
+.project {
+  .description {
+    margin-top: .2em;
+  }
+}
+
+.project-title {
+  font-size: 1.4em;
+}
+
+.date {
+  font-style: italic;
+  float: right;
+
+  &:before {
+    content: "updated ";
+  }
+}
+
+.fields {
+  margin-top: 10px;
+  line-height: 18px;
+}
+
+.field-name {
+  display: inline;
+  float: left;
+  width: 14%;
+  font-weight: normal;
+  a {
+    color: inherit;
+  }
+}
+
+.field-none {
+  color: @greyed-out;
+}
+
+.field-value-container {
+  width: 86%;
+
+  &:hover {
+    background-color: #D9FFD1;
+  }
+}
+
+.field-value-item:not(:last-child) {
+  &:after {
+    content: ", ";
+    color: @greyed-out;
+  }
+}
+
+.field-edit {
+  width: 86%;
+  display: inline-block;
+
+  input {
+    display: inline-block;
+  }
+}
+
+.field-value {
+  margin: 0;
+  display: inline;
+
+  li {
+    display: inline;
+  }
+}
+
+.edit-value {
+  display: inline-block;
+  width: 60%;
+
+  &:hover {
+    cursor: pointer;
+  }
+}
+
+.edit-message {
+  display: inline-block;
+  width: 100%;
+
+   &:hover {
+    &:before {
+       content: "edit field";
+       margin-left: 24px;
+       color: @greyed-out;
+    }
+  }
+}
+
+.comma {
+  color: @greyed;
+}
+
+.delete {
+  float: right;
+  cursor: pointer;
+}
+
+.UEB {
+  margin-left: 0.5em;
+}
+
+#sort-legend {
+  color: black;
+  font-size: small;
+  margin-right: 0.5em;
+}
+
+#sort-order { 
+  float: right;
+  font-size: 100%;
+  margin-top: 16px;
+
+  ul {
+    float: left;
+    display: inline;
+
+    li {
+      float: left;
+  
+      &:not(:last-child) &:not(:first-child) {
+        &:after {
+          content: "|"; 
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/toolbox/static/css/style.less	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,85 @@
+html {
+  font-family: 'Helvetica Neue', Helvetica, Arial,sans-serif;
+  background: -moz-linear-gradient(center top, #BFD6F3, #CDE0F1) no-repeat;
+  background: -webkit-gradient(linear,0% 0,0% 100%,from(#BFD6F3),to(#CDE0F1));
+  min-height: 100%;
+  min-width: 100%;
+}
+
+a {
+  text-decoration: none;
+
+  &:hover {
+    color: #625;
+  }
+}
+
+ul {
+  list-style: none;
+}
+
+nav {
+  margin: 0.3em 0.5em;
+  font-size: 1.2em;
+  overflow: auto;
+
+  li {
+    display: inline;
+    color: #625;
+    float: left;
+    margin: 0 0.3em;
+  }
+
+  a, a:active, a:visited {
+    color: #3E4D5E;
+  }
+ 
+  a:hover {
+    color: black;
+  }
+}
+
+header {
+  overflow: auto;
+}
+
+#title {
+  font-size: 2em;
+  display: inline-block;
+  margin-left: 3px;
+}
+
+#container {
+  width: 780px;
+  margin: 2em auto;
+}
+
+#search {
+  text-align: right;
+}
+
+#search-submit {
+  background: url(../img/search.png);
+  width: 24px;
+  height: 24px;
+  border: none;
+}
+
+#search-text {
+  width: 300px;
+  border-radius: 5px;
+}
+
+#content > div {
+  background-color: white;
+  border: thin solid black;
+  border-radius: 1em;
+  margin-top: 0.5em;
+  box-shadow: 0em 0.2em 0.3em 0.0em black;
+  padding: 1em;
+  overflow: auto;
+}
+
+.error {
+  color: red;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/toolbox/static/css/token-input-facebook.css	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,122 @@
+/* Example tokeninput style #2: Facebook style */
+ul.token-input-list-facebook {
+    overflow: hidden; 
+    height: auto !important; 
+    height: 1%;
+    width: 400px;
+    border: 1px solid #8496ba;
+    cursor: text;
+    font-size: 12px;
+    font-family: Verdana;
+    min-height: 1px;
+    z-index: 999;
+    margin: 0;
+    padding: 0;
+    background-color: #fff;
+    list-style-type: none;
+    clear: left;
+}
+
+ul.token-input-list-facebook li input {
+    border: 0;
+    width: 100px;
+    padding: 3px 8px;
+    background-color: white;
+    margin: 2px 0;
+    -webkit-appearance: caret;
+}
+
+li.token-input-token-facebook {
+    overflow: hidden; 
+    height: auto !important; 
+    height: 15px;
+    margin: 3px;
+    padding: 1px 3px;
+    background-color: #eff2f7;
+    color: #000;
+    cursor: default;
+    border: 1px solid #ccd5e4;
+    font-size: 11px;
+    border-radius: 5px;
+    -moz-border-radius: 5px;
+    -webkit-border-radius: 5px;
+    float: left;
+    white-space: nowrap;
+}
+
+li.token-input-token-facebook p {
+    display: inline;
+    padding: 0;
+    margin: 0;
+}
+
+li.token-input-token-facebook span {
+    color: #a6b3cf;
+    margin-left: 5px;
+    font-weight: bold;
+    cursor: pointer;
+}
+
+li.token-input-selected-token-facebook {
+    background-color: #5670a6;
+    border: 1px solid #3b5998;
+    color: #fff;
+}
+
+li.token-input-input-token-facebook {
+    float: left;
+    margin: 0;
+    padding: 0;
+    list-style-type: none;
+}
+
+div.token-input-dropdown-facebook {
+    position: absolute;
+    width: 400px;
+    background-color: #fff;
+    overflow: hidden;
+    border-left: 1px solid #ccc;
+    border-right: 1px solid #ccc;
+    border-bottom: 1px solid #ccc;
+    cursor: default;
+    font-size: 11px;
+    font-family: Verdana;
+    z-index: 1;
+}
+
+div.token-input-dropdown-facebook p {
+    margin: 0;
+    padding: 5px;
+    font-weight: bold;
+    color: #777;
+}
+
+div.token-input-dropdown-facebook ul {
+    margin: 0;
+    padding: 0;
+}
+
+div.token-input-dropdown-facebook ul li {
+    background-color: #fff;
+    padding: 3px;
+    margin: 0;
+    list-style-type: none;
+}
+
+div.token-input-dropdown-facebook ul li.token-input-dropdown-item-facebook {
+    background-color: #fff;
+}
+
+div.token-input-dropdown-facebook ul li.token-input-dropdown-item2-facebook {
+    background-color: #fff;
+}
+
+div.token-input-dropdown-facebook ul li em {
+    font-weight: bold;
+    font-style: normal;
+}
+
+div.token-input-dropdown-facebook ul li.token-input-selected-dropdown-item-facebook {
+    background-color: #3b5998;
+    color: #fff;
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/toolbox/static/css/token-input.css	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,113 @@
+/* Example tokeninput style #1: Token vertical list*/
+ul.token-input-list {
+    overflow: hidden; 
+    height: auto !important; 
+    height: 1%;
+    width: 400px;
+    border: 1px solid #999;
+    cursor: text;
+    font-size: 12px;
+    font-family: Verdana;
+    z-index: 999;
+    margin: 0;
+    padding: 0;
+    background-color: #fff;
+    list-style-type: none;
+    clear: left;
+}
+
+ul.token-input-list li {
+    list-style-type: none;
+}
+
+ul.token-input-list li input {
+    border: 0;
+    width: 350px;
+    padding: 3px 8px;
+    background-color: white;
+    -webkit-appearance: caret;
+}
+
+li.token-input-token {
+    overflow: hidden; 
+    height: auto !important; 
+    height: 1%;
+    margin: 3px;
+    padding: 3px 5px;
+    background-color: #d0efa0;
+    color: #000;
+    font-weight: bold;
+    cursor: default;
+    display: block;
+}
+
+li.token-input-token p {
+    float: left;
+    padding: 0;
+    margin: 0;
+}
+
+li.token-input-token span {
+    float: right;
+    color: #777;
+    cursor: pointer;
+}
+
+li.token-input-selected-token {
+    background-color: #08844e;
+    color: #fff;
+}
+
+li.token-input-selected-token span {
+    color: #bbb;
+}
+
+div.token-input-dropdown {
+    position: absolute;
+    width: 400px;
+    background-color: #fff;
+    overflow: hidden;
+    border-left: 1px solid #ccc;
+    border-right: 1px solid #ccc;
+    border-bottom: 1px solid #ccc;
+    cursor: default;
+    font-size: 12px;
+    font-family: Verdana;
+    z-index: 1;
+}
+
+div.token-input-dropdown p {
+    margin: 0;
+    padding: 5px;
+    font-weight: bold;
+    color: #777;
+}
+
+div.token-input-dropdown ul {
+    margin: 0;
+    padding: 0;
+}
+
+div.token-input-dropdown ul li {
+    background-color: #fff;
+    padding: 3px;
+    list-style-type: none;
+}
+
+div.token-input-dropdown ul li.token-input-dropdown-item {
+    background-color: #fafafa;
+}
+
+div.token-input-dropdown ul li.token-input-dropdown-item2 {
+    background-color: #fff;
+}
+
+div.token-input-dropdown ul li em {
+    font-weight: bold;
+    font-style: normal;
+}
+
+div.token-input-dropdown ul li.token-input-selected-dropdown-item {
+    background-color: #d0efa0;
+}
+
Binary file toolbox/static/img/UEB16.png has changed
Binary file toolbox/static/img/favicon.ico has changed
Binary file toolbox/static/img/indicator.gif has changed
Binary file toolbox/static/img/search.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/toolbox/static/js/field.js	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,57 @@
+$(document).ready(function(){
+
+    var fieldname = $('#field-name').text();
+
+    // enable editing of field values
+    $('div.field').each(function() {
+        var fielddiv = $(this);
+        var field = $(this).attr('id');
+        $(this).children('h2').each(function() {
+             var header = $(this);
+             var value = $(this).children('a').text();
+             var UEB = $('<img class="UEB" src="img/UEB16.png"/>');
+             $(UEB).attr('title', 'rename ' + fieldname + ': ' + field);
+             $(UEB).css('visibility', 'hidden');
+             var editField = function() {
+                var input = $('<input class="text"/>');
+                $(input).val(field);
+                var submitHandler = function () {
+                    var newvalue = $(this).val();
+                    if (newvalue != value) {
+                        var hiddeninput = $('<input type="hidden"/>');
+                        $(hiddeninput).attr('name', value);
+                        $(hiddeninput).val(newvalue);
+                        var form = $('<form method="POST"></form>');
+                        form.append(hiddeninput);
+                        $(this).after(form);
+                        $(form).submit();
+                        $(this).replaceWith('<img class="throbber" src="img/indicator.gif"/>');
+                        return;
+                    }
+                    $(this).blur(function() {});
+                    $(this).replaceWith(header);
+                    $(header).hover(function(eventObject) { $(this).children('img.UEB').css('visibility', 'visible'); },
+                                    function(eventObject) { $(this).children('img.UEB').css('visibility', 'hidden'); });
+
+                    $(header).find('img.UEB').each(function() {
+                        $(this).css('visibility', 'hidden');
+                        $(this).click(editField);
+                    });
+                }
+                $(header).replaceWith(input);
+                $(input).blur(submitHandler);
+                $(input).keypress(function(event) {
+                    if (event.which == 13) {
+                        $(this).blur();
+                    }
+                });
+                $(input).focus();
+            }
+            $(UEB).click(editField);
+             $(this).append(UEB);
+             $(this).hover(function(eventObject) { $(this).children('img.UEB').css('visibility', 'visible'); },
+                           function(eventObject) { $(this).children('img.UEB').css('visibility', 'hidden'); });
+
+        });
+    });
+});
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/toolbox/static/js/jquery-1.6.min.js	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,16 @@
+/*!
+ * jQuery JavaScript Library v1.6
+ * http://jquery.com/
+ *
+ * Copyright 2011, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ * Copyright 2011, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ *
+ * Date: Mon May 2 13:50:00 2011 -0400
+ */
+(function(a,b){function cw(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function ct(a){if(!ch[a]){var b=f("<"+a+">").appendTo("body"),d=b.css("display");b.remove();if(d==="none"||d===""){ci||(ci=c.createElement("iframe"),ci.frameBorder=ci.width=ci.height=0),c.body.appendChild(ci);if(!cj||!ci.createElement)cj=(ci.contentWindow||ci.contentDocument).document,cj.write("<!doctype><html><body></body></html>");b=cj.createElement(a),cj.body.appendChild(b),d=f.css(b,"display"),c.body.removeChild(ci)}ch[a]=d}return ch[a]}function cs(a,b){var c={};f.each(cn.concat.apply([],cn.slice(0,b)),function(){c[this]=a});return c}function cr(){co=b}function cq(){setTimeout(cr,0);return co=f.now()}function cg(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function cf(){try{return new a.XMLHttpRequest}catch(b){}}function b_(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g<i;g++){if(g===1)for(h in a.converters)typeof h=="string"&&(e[h.toLowerCase()]=a.converters[h]);l=k,k=d[g];if(k==="*")k=l;else if(l!=="*"&&l!==k){m=l+" "+k,n=e[m]||e["* "+k];if(!n){p=b;for(o in e){j=o.split(" ");if(j[0]===l||j[0]==="*"){p=e[j[1]+" "+k];if(p){o=e[o],o===!0?n=p:p===!0&&(n=o);break}}}}!n&&!p&&f.error("No conversion from "+m.replace(" "," to ")),n!==!0&&(c=n?n(c):p(o(c)))}}return c}function b$(a,c,d){var e=a.contents,f=a.dataTypes,g=a.responseFields,h,i,j,k;for(i in g)i in d&&(c[g[i]]=d[i]);while(f[0]==="*")f.shift(),h===b&&(h=a.mimeType||c.getResponseHeader("content-type"));if(h)for(i in e)if(e[i]&&e[i].test(h)){f.unshift(i);break}if(f[0]in d)j=f[0];else{for(i in d){if(!f[0]||a.converters[i+" "+f[0]]){j=i;break}k||(k=i)}j=j||k}if(j){j!==f[0]&&f.unshift(j);return d[j]}}function bZ(a,b,c,d){if(f.isArray(b))f.each(b,function(b,e){c||bD.test(a)?d(a,e):bZ(a+"["+(typeof e=="object"||f.isArray(e)?b:"")+"]",e,c,d)});else if(!c&&b!=null&&typeof b=="object")for(var e in b)bZ(a+"["+e+"]",b[e],c,d);else d(a,b)}function bY(a,c,d,e,f,g){f=f||c.dataTypes[0],g=g||{},g[f]=!0;var h=a[f],i=0,j=h?h.length:0,k=a===bS,l;for(;i<j&&(k||!l);i++)l=h[i](c,d,e),typeof l=="string"&&(!k||g[l]?l=b:(c.dataTypes.unshift(l),l=bY(a,c,d,e,l,g)));(k||!l)&&!g["*"]&&(l=bY(a,c,d,e,"*",g));return l}function bX(a){return function(b,c){typeof b!="string"&&(c=b,b="*");if(f.isFunction(c)){var d=b.toLowerCase().split(bO),e=0,g=d.length,h,i,j;for(;e<g;e++)h=d[e],j=/^\+/.test(h),j&&(h=h.substr(1)||"*"),i=a[h]=a[h]||[],i[j?"unshift":"push"](c)}}}function bB(a,b,c){var d=b==="width"?bv:bw,e=b==="width"?a.offsetWidth:a.offsetHeight;if(c==="border")return e;f.each(d,function(){c||(e-=parseFloat(f.css(a,"padding"+this))||0),c==="margin"?e+=parseFloat(f.css(a,"margin"+this))||0:e-=parseFloat(f.css(a,"border"+this+"Width"))||0});return e}function bl(a,b){b.src?f.ajax({url:b.src,async:!1,dataType:"script"}):f.globalEval(b.text||b.textContent||b.innerHTML||""),b.parentNode&&b.parentNode.removeChild(b)}function bk(a){f.nodeName(a,"input")?bj(a):a.getElementsByTagName&&f.grep(a.getElementsByTagName("input"),bj)}function bj(a){if(a.type==="checkbox"||a.type==="radio")a.defaultChecked=a.checked}function bi(a){return"getElementsByTagName"in a?a.getElementsByTagName("*"):"querySelectorAll"in a?a.querySelectorAll("*"):[]}function bh(a,b){var c;if(b.nodeType===1){b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase();if(c==="object")b.outerHTML=a.outerHTML;else if(c!=="input"||a.type!=="checkbox"&&a.type!=="radio"){if(c==="option")b.selected=a.defaultSelected;else if(c==="input"||c==="textarea")b.defaultValue=a.defaultValue}else a.checked&&(b.defaultChecked=b.checked=a.checked),b.value!==a.value&&(b.value=a.value);b.removeAttribute(f.expando)}}function bg(a,b){if(b.nodeType===1&&!!f.hasData(a)){var c=f.expando,d=f.data(a),e=f.data(b,d);if(d=d[c]){var g=d.events;e=e[c]=f.extend({},d);if(g){delete e.handle,e.events={};for(var h in g)for(var i=0,j=g[h].length;i<j;i++)f.event.add(b,h+(g[h][i].namespace?".":"")+g[h][i].namespace,g[h][i],g[h][i].data)}}}}function bf(a,b){return f.nodeName(a,"table")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function W(a,b,c){b=b||0;if(f.isFunction(b))return f.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return f.grep(a,function(a,d){return a===b===c});if(typeof b=="string"){var d=f.grep(a,function(a){return a.nodeType===1});if(R.test(b))return f.filter(b,d,!c);b=f.filter(b,d)}return f.grep(a,function(a,d){return f.inArray(a,b)>=0===c})}function V(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function N(a,b){return(a&&a!=="*"?a+".":"")+b.replace(z,"`").replace(A,"&")}function M(a){var b,c,d,e,g,h,i,j,k,l,m,n,o,p=[],q=[],r=f._data(this,"events");if(!(a.liveFired===this||!r||!r.live||a.target.disabled||a.button&&a.type==="click")){a.namespace&&(n=new RegExp("(^|\\.)"+a.namespace.split(".").join("\\.(?:.*\\.)?")+"(\\.|$)")),a.liveFired=this;var s=r.live.slice(0);for(i=0;i<s.length;i++)g=s[i],g.origType.replace(x,"")===a.type?q.push(g.selector):s.splice(i--,1);e=f(a.target).closest(q,a.currentTarget);for(j=0,k=e.length;j<k;j++){m=e[j];for(i=0;i<s.length;i++){g=s[i];if(m.selector===g.selector&&(!n||n.test(g.namespace))&&!m.elem.disabled){h=m.elem,d=null;if(g.preType==="mouseenter"||g.preType==="mouseleave")a.type=g.preType,d=f(a.relatedTarget).closest(g.selector)[0],d&&f.contains(h,d)&&(d=h);(!d||d!==h)&&p.push({elem:h,handleObj:g,level:m.level})}}}for(j=0,k=p.length;j<k;j++){e=p[j];if(c&&e.level>c)break;a.currentTarget=e.elem,a.data=e.handleObj.data,a.handleObj=e.handleObj,o=e.handleObj.origHandler.apply(e.elem,arguments);if(o===!1||a.isPropagationStopped()){c=e.level,o===!1&&(b=!1);if(a.isImmediatePropagationStopped())break}}return b}}function K(a,c,d){var e=f.extend({},d[0]);e.type=a,e.originalEvent={},e.liveFired=b,f.event.handle.call(c,e),e.isDefaultPrevented()&&d[0].preventDefault()}function E(){return!0}function D(){return!1}function m(a,c,d){var e=c+"defer",g=c+"queue",h=c+"mark",i=f.data(a,e,b,!0);i&&(d==="queue"||!f.data(a,g,b,!0))&&(d==="mark"||!f.data(a,h,b,!0))&&setTimeout(function(){!f.data(a,g,b,!0)&&!f.data(a,h,b,!0)&&(f.removeData(a,e,!0),i.resolve())},0)}function l(a){for(var b in a)if(b!=="toJSON")return!1;return!0}function k(a,c,d){if(d===b&&a.nodeType===1){name="data-"+c.replace(j,"$1-$2").toLowerCase(),d=a.getAttribute(name);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNaN(d)?i.test(d)?f.parseJSON(d):d:parseFloat(d)}catch(e){}f.data(a,c,d)}else d=b}return d}var c=a.document,d=a.navigator,e=a.location,f=function(){function H(){if(!e.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(H,1);return}e.ready()}}var e=function(a,b){return new e.fn.init(a,b,h)},f=a.jQuery,g=a.$,h,i=/^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/\d/,n=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,o=/^[\],:{}\s]*$/,p=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,q=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,r=/(?:^|:|,)(?:\s*\[)+/g,s=/(webkit)[ \/]([\w.]+)/,t=/(opera)(?:.*version)?[ \/]([\w.]+)/,u=/(msie) ([\w.]+)/,v=/(mozilla)(?:.*? rv:([\w.]+))?/,w=d.userAgent,x,y,z,A=Object.prototype.toString,B=Object.prototype.hasOwnProperty,C=Array.prototype.push,D=Array.prototype.slice,E=String.prototype.trim,F=Array.prototype.indexOf,G={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)==="<"&&a.charAt(a.length-1)===">"&&a.length>=3?g=[null,a,null]:g=i.exec(a);if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=n.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.6",length:0,size:function(){return this.length},toArray:function(){return D.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?C.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),y.done(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(D.apply(this,arguments),"slice",D.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:C,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j<k;j++)if((a=arguments[j])!=null)for(c in a){d=i[c],f=a[c];if(i===f)continue;l&&f&&(e.isPlainObject(f)||(g=e.isArray(f)))?(g?(g=!1,h=d&&e.isArray(d)?d:[]):h=d&&e.isPlainObject(d)?d:{},i[c]=e.extend(l,h,f)):f!==b&&(i[c]=f)}return i},e.extend({noConflict:function(b){a.$===e&&(a.$=g),b&&a.jQuery===e&&(a.jQuery=f);return e},isReady:!1,readyWait:1,holdReady:function(a){a?e.readyWait++:e.ready(!0)},ready:function(a){if(a===!0&&!--e.readyWait||a!==!0&&!e.isReady){if(!c.body)return setTimeout(e.ready,1);e.isReady=!0;if(a!==!0&&--e.readyWait>0)return;y.resolveWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").unbind("ready")}},bindReady:function(){if(!y){y=e._Deferred();if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",z,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",z),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&H()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a&&typeof a=="object"&&"setInterval"in a},isNaN:function(a){return a==null||!m.test(a)||isNaN(a)},type:function(a){return a==null?String(a):G[A.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;if(a.constructor&&!B.call(a,"constructor")&&!B.call(a.constructor.prototype,"isPrototypeOf"))return!1;var c;for(c in a);return c===b||B.call(a,c)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw a},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(o.test(b.replace(p,"@").replace(q,"]").replace(r,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(b,c,d){a.DOMParser?(d=new DOMParser,c=d.parseFromString(b,"text/xml")):(c=new ActiveXObject("Microsoft.XMLDOM"),c.async="false",c.loadXML(b)),d=c.documentElement,(!d||!d.nodeName||d.nodeName==="parsererror")&&e.error("Invalid XML: "+b);return c},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g<h;)if(c.apply(a[g++],d)===!1)break}else if(i){for(f in a)if(c.call(a[f],f,a[f])===!1)break}else for(;g<h;)if(c.call(a[g],g,a[g++])===!1)break;return a},trim:E?function(a){return a==null?"":E.call(a)}:function(a){return a==null?"":(a+"").replace(k,"").replace(l,"")},makeArray:function(a,b){var c=b||[];if(a!=null){var d=e.type(a);a.length==null||d==="string"||d==="function"||d==="regexp"||e.isWindow(a)?C.call(c,a):e.merge(c,a)}return c},inArray:function(a,b){if(F)return F.call(b,a);for(var c=0,d=b.length;c<d;c++)if(b[c]===a)return c;return-1},merge:function(a,c){var d=a.length,e=0;if(typeof c.length=="number")for(var f=c.length;e<f;e++)a[d++]=c[e];else while(c[e]!==b)a[d++]=c[e++];a.length=d;return a},grep:function(a,b,c){var d=[],e;c=!!c;for(var f=0,g=a.length;f<g;f++)e=!!b(a[f],f),c!==e&&d.push(a[f]);return d},map:function(a,c,d){var f,g,h=[],i=0,j=a.length,k=a instanceof e||j!==b&&typeof j=="number"&&(j>0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i<j;i++)f=c(a[i],i,d),f!=null&&(h[h.length]=f);else for(g in a)f=c(a[g],g,d),f!=null&&(h[h.length]=f);return h.concat.apply([],h)},guid:1,proxy:function(a,c){if(typeof c=="string"){var d=a[c];c=a,a=d}if(!e.isFunction(a))return b;var f=D.call(arguments,2),g=function(){return a.apply(c,f.concat(D.call(arguments)))};g.guid=a.guid=a.guid||g.guid||e.guid++;return g},access:function(a,c,d,f,g,h){var i=a.length;if(typeof c=="object"){for(var j in c)e.access(a,j,c[j],f,g,d);return a}if(d!==b){f=!h&&f&&e.isFunction(d);for(var k=0;k<i;k++)g(a[k],c,f?d.call(a[k],k,g(a[k],c)):d,h);return a}return i?g(a[0],c):b},now:function(){return(new Date).getTime()},uaMatch:function(a){a=a.toLowerCase();var b=s.exec(a)||t.exec(a)||u.exec(a)||a.indexOf("compatible")<0&&v.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},sub:function(){function a(b,c){return new a.fn.init(b,c)}e.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function(c,d){d&&d instanceof e&&!(d instanceof a)&&(d=a(d));return e.fn.init.call(this,c,d,b)},a.fn.init.prototype=a.fn;var b=a(c);return a},browser:{}}),e.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(a,b){G["[object "+b+"]"]=b.toLowerCase()}),x=e.uaMatch(w),x.browser&&(e.browser[x.browser]=!0,e.browser.version=x.version),e.browser.webkit&&(e.browser.safari=!0),j.test(" ")&&(k=/^[\s\xA0]+/,l=/[\s\xA0]+$/),h=e(c),c.addEventListener?z=function(){c.removeEventListener("DOMContentLoaded",z,!1),e.ready()}:c.attachEvent&&(z=function(){c.readyState==="complete"&&(c.detachEvent("onreadystatechange",z),e.ready())});return e}(),g="done fail isResolved isRejected promise then always pipe".split(" "),h=[].slice;f.extend({_Deferred:function(){var a=[],b,c,d,e={done:function(){if(!d){var c=arguments,g,h,i,j,k;b&&(k=b,b=0);for(g=0,h=c.length;g<h;g++)i=c[g],j=f.type(i),j==="array"?e.done.apply(e,i):j==="function"&&a.push(i);k&&e.resolveWith(k[0],k[1])}return this},resolveWith:function(e,f){if(!d&&!b&&!c){f=f||[],c=1;try{while(a[0])a.shift().apply(e,f)}finally{b=[e,f],c=0}}return this},resolve:function(){e.resolveWith(this,arguments);return this},isResolved:function(){return!!c||!!b},cancel:function(){d=1,a=[];return this}};return e},Deferred:function(a){var b=f._Deferred(),c=f._Deferred(),d;f.extend(b,{then:function(a,c){b.done(a).fail(c);return this},always:function(){return b.done.apply(b,arguments).fail.apply(this,arguments)},fail:c.done,rejectWith:c.resolveWith,reject:c.resolve,isRejected:c.isResolved,pipe:function(a,c){return f.Deferred(function(d){f.each({done:[a,"resolve"],fail:[c,"reject"]},function(a,c){var e=c[0],g=c[1],h;f.isFunction(e)?b[a](function(){h=e.apply(this,arguments),f.isFunction(h.promise)?h.promise().then(d.resolve,d.reject):d[g](h)}):b[a](d[g])})}).promise()},promise:function(a){if(a==null){if(d)return d;d=a={}}var c=g.length;while(c--)a[g[c]]=b[g[c]];return a}}),b.done(c.cancel).fail(b.cancel),delete b.cancel,a&&a.call(b,b);return b},when:function(a){function i(a){return function(c){b[a]=arguments.length>1?h.call(arguments,0):c,--e||g.resolveWith(g,h.call(b,0))}}var b=arguments,c=0,d=b.length,e=d,g=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred();if(d>1){for(;c<d;c++)b[c]&&f.isFunction(b[c].promise)?b[c].promise().then(i(c),g.reject):--e;e||g.resolveWith(g,b)}else g!==a&&g.resolveWith(g,d?[a]:[]);return g.promise()}}),f.support=function(){var a=c.createElement("div"),b,d,e,f,g,h,i,j,k,l,m,n,o,p,q;a.setAttribute("className","t"),a.innerHTML="   <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/>",b=a.getElementsByTagName("*"),d=a.getElementsByTagName("a")[0];if(!b||!b.length||!d)return{};e=c.createElement("select"),f=e.appendChild(c.createElement("option")),g=a.getElementsByTagName("input")[0],i={leadingWhitespace:a.firstChild.nodeType===3,tbody:!a.getElementsByTagName("tbody").length,htmlSerialize:!!a.getElementsByTagName("link").length,style:/top/.test(d.getAttribute("style")),hrefNormalized:d.getAttribute("href")==="/a",opacity:/^0.55$/.test(d.style.opacity),cssFloat:!!d.style.cssFloat,checkOn:g.value==="on",optSelected:f.selected,getSetAttribute:a.className!=="t",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0},g.checked=!0,i.noCloneChecked=g.cloneNode(!0).checked,e.disabled=!0,i.optDisabled=!f.disabled;try{delete a.test}catch(r){i.deleteExpando=!1}!a.addEventListener&&a.attachEvent&&a.fireEvent&&(a.attachEvent("onclick",function click(){i.noCloneEvent=!1,a.detachEvent("onclick",click)}),a.cloneNode(!0).fireEvent("onclick")),g=c.createElement("input"),g.value="t",g.setAttribute("type","radio"),i.radioValue=g.value==="t",g.setAttribute("checked","checked"),a.appendChild(g),j=c.createDocumentFragment(),j.appendChild(a.firstChild),i.checkClone=j.cloneNode(!0).cloneNode(!0).lastChild.checked,a.innerHTML="",a.style.width=a.style.paddingLeft="1px",k=c.createElement("body"),l={visibility:"hidden",width:0,height:0,border:0,margin:0,background:"none"};for(p in l)k.style[p]=l[p];k.appendChild(a),c.documentElement.appendChild(k),i.appendChecked=g.checked,i.boxModel=a.offsetWidth===2,"zoom"in a.style&&(a.style.display="inline",a.style.zoom=1,i.inlineBlockNeedsLayout=a.offsetWidth===2,a.style.display="",a.innerHTML="<div style='width:4px;'></div>",i.shrinkWrapBlocks=a.offsetWidth!==2),a.innerHTML="<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>",m=a.getElementsByTagName("td"),q=m[0].offsetHeight===0,m[0].style.display="",m[1].style.display="none",i.reliableHiddenOffsets=q&&m[0].offsetHeight===0,a.innerHTML="",c.defaultView&&c.defaultView.getComputedStyle&&(h=c.createElement("div"),h.style.width="0",h.style.marginRight="0",a.appendChild(h),i.reliableMarginRight=(parseInt(c.defaultView.getComputedStyle(h,null).marginRight,10)||0)===0),k.innerHTML="",c.documentElement.removeChild(k);if(a.attachEvent)for(p in{submit:1,change:1,focusin:1})o="on"+p,q=o in a,q||(a.setAttribute(o,"return;"),q=typeof a[o]=="function"),i[p+"Bubbles"]=q;return i}(),f.boxModel=f.support.boxModel;var i=/^(?:\{.*\}|\[.*\])$/,j=/([a-z])([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!l(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g=f.expando,h=typeof c=="string",i,j=a.nodeType,k=j?f.cache:a,l=j?a[f.expando]:a[f.expando]&&f.expando;if((!l||e&&l&&!k[l][g])&&h&&d===b)return;l||(j?a[f.expando]=l=++f.uuid:l=f.expando),k[l]||(k[l]={},j||(k[l].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?k[l][g]=f.extend(k[l][g],c):k[l]=f.extend(k[l],c);i=k[l],e&&(i[g]||(i[g]={}),i=i[g]),d!==b&&(i[c]=d);if(c==="events"&&!i[c])return i[g]&&i[g].events;return h?i[c]:i}},removeData:function(b,c,d){if(!!f.acceptData(b)){var e=f.expando,g=b.nodeType,h=g?f.cache:b,i=g?b[f.expando]:f.expando;if(!h[i])return;if(c){var j=d?h[i][e]:h[i];if(j){delete j[c];if(!l(j))return}}if(d){delete h[i][e];if(!l(h[i]))return}var k=h[i][e];f.support.deleteExpando||h!=a?delete h[i]:h[i]=null,k?(h[i]={},g||(h[i].toJSON=f.noop),h[i][e]=k):g&&(f.support.deleteExpando?delete b[f.expando]:b.removeAttribute?b.removeAttribute(f.expando):b[f.expando]=null)}},_data:function(a,b,c){return f.data(a,b,c,!0)},acceptData:function(a){if(a.nodeName){var b=f.noData[a.nodeName.toLowerCase()];if(b)return b!==!0&&a.getAttribute("classid")===b}return!0}}),f.fn.extend({data:function(a,c){var d=null;if(typeof a=="undefined"){if(this.length){d=f.data(this[0]);if(this[0].nodeType===1){var e=this[0].attributes,g;for(var h=0,i=e.length;h<i;h++)g=e[h].name,g.indexOf("data-")===0&&(g=f.camelCase(g.substring(5)),k(this[0],g,d[g]))}}return d}if(typeof a=="object")return this.each(function(){f.data(this,a)});var j=a.split(".");j[1]=j[1]?"."+j[1]:"";if(c===b){d=this.triggerHandler("getData"+j[1]+"!",[j[0]]),d===b&&this.length&&(d=f.data(this[0],a),d=k(this[0],a,d));return d===b&&j[1]?this.data(j[0]):d}return this.each(function(){var b=f(this),d=[j[0],c];b.triggerHandler("setData"+j[1]+"!",d),f.data(this,a,c),b.triggerHandler("changeData"+j[1]+"!",d)})},removeData:function(a){return this.each(function(){f.removeData(this,a)})}}),f.extend({_mark:function(a,c){a&&(c=(c||"fx")+"mark",f.data(a,c,(f.data(a,c,b,!0)||0)+1,!0))},_unmark:function(a,c,d){a!==!0&&(d=c,c=a,a=!1);if(c){d=d||"fx";var e=d+"mark",g=a?0:(f.data(c,e,b,!0)||1)-1;g?f.data(c,e,g,!0):(f.removeData(c,e,!0),m(c,d,"mark"))}},queue:function(a,c,d){if(a){c=(c||"fx")+"queue";var e=f.data(a,c,b,!0);d&&(!e||f.isArray(d)?e=f.data(a,c,f.makeArray(d),!0):e.push(d));return e||[]}},dequeue:function(a,b){b=b||"fx";var c=f.queue(a,b),d=c.shift(),e;d==="inprogress"&&(d=c.shift()),d&&(b==="fx"&&c.unshift("inprogress"),d.call(a,function(){f.dequeue(a,b)})),c.length||(f.removeData(a,b+"queue",!0),m(a,b,"queue"))}}),f.fn.extend({queue:function(a,c){typeof a!="string"&&(c=a,a="fx");if(c===b)return f.queue(this[0],a);return this.each(function(){var b=f.queue(this,a,c);a==="fx"&&b[0]!=="inprogress"&&f.dequeue(this,a)})},dequeue:function(a){return this.each(function(){f.dequeue(this,a)})},delay:function(a,b){a=f.fx?f.fx.speeds[a]||a:a,b=b||"fx";return this.queue(b,function(){var c=this;setTimeout(function(){f.dequeue(c,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,c){function l(){--h||d.resolveWith(e,[e])}typeof a!="string"&&(c=a,a=b),a=a||"fx";var d=f.Deferred(),e=this,g=e.length,h=1,i=a+"defer",j=a+"queue",k=a+"mark";while(g--)if(tmp=f.data(e[g],i,b,!0)||(f.data(e[g],j,b,!0)||f.data(e[g],k,b,!0))&&f.data(e[g],i,f._Deferred(),!0))h++,tmp.done(l);l();return d.promise()}});var n=/[\n\t\r]/g,o=/\s+/,p=/\r/g,q=/^(?:button|input)$/i,r=/^(?:button|input|object|select|textarea)$/i,s=/^a(?:rea)?$/i,t=/^(?:data-|aria-)/,u=/\:/,v;f.fn.extend({attr:function(a,b){return f.access(this,a,b,!0,f.attr)},removeAttr:function(a){return this.each(function(){f.removeAttr(this,a)})},prop:function(a,b){return f.access(this,a,b,!0,f.prop)},removeProp:function(a){return this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.addClass(a.call(this,b,c.attr("class")||""))});if(a&&typeof a=="string"){var b=(a||"").split(o);for(var c=0,d=this.length;c<d;c++){var e=this[c];if(e.nodeType===1)if(!e.className)e.className=a;else{var g=" "+e.className+" ",h=e.className;for(var i=0,j=b.length;i<j;i++)g.indexOf(" "+b[i]+" ")<0&&(h+=" "+b[i]);e.className=f.trim(h)}}}return this},removeClass:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.removeClass(a.call(this,b,c.attr("class")))});if(a&&typeof a=="string"||a===b){var c=(a||"").split(o);for(var d=0,e=this.length;d<e;d++){var g=this[d];if(g.nodeType===1&&g.className)if(a){var h=(" "+g.className+" ").replace(n," ");for(var i=0,j=c.length;i<j;i++)h=h.replace(" "+c[i]+" "," ");g.className=f.trim(h)}else g.className=""}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";if(f.isFunction(a))return this.each(function(c){var d=f(this);d.toggleClass(a.call(this,c,d.attr("class"),b),b)});return this.each(function(){if(c==="string"){var e,g=0,h=f(this),i=b,j=a.split(o);while(e=j[g++])i=d?i:!h.hasClass(e),h[i?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&f._data(this,"__className__",this.className),this.className=this.className||a===!1?"":f._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ";for(var c=0,d=this.length;c<d;c++)if((" "+this[c].className+" ").replace(n," ").indexOf(b)>-1)return!0;return!1},val:function(a){var c,d,e=this[0];if(!arguments.length){if(e){c=f.valHooks[e.nodeName.toLowerCase()]||f.valHooks[e.type];if(c&&"get"in c&&(d=c.get(e,"value"))!==b)return d;return(e.value||"").replace(p,"")}return b}var g=f.isFunction(a);return this.each(function(d){var e=f(this),h;if(this.nodeType===1){g?h=a.call(this,d,e.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.nodeName.toLowerCase()]||f.valHooks[this.type];if(!c||"set"in c&&c.set(this,h,"value")===b)this.value=h}})}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b=a.selectedIndex,c=[],d=a.options,e=a.type==="select-one";if(b<0)return null;for(var g=e?b:0,h=e?b+1:d.length;g<h;g++){var i=d[g];if(i.selected&&(f.support.optDisabled?!i.disabled:i.getAttribute("disabled")===null)&&(!i.parentNode.disabled||!f.nodeName(i.parentNode,"optgroup"))){value=f(i).val();if(e)return value;c.push(value)}}if(e&&!c.length&&d.length)return f(d[b]).val();return c},set:function(a,b){var c=f.makeArray(b);f(a).find("option").each(function(){this.selected=f.inArray(f(this).val(),c)>=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attrFix:{tabindex:"tabIndex",readonly:"readOnly"},attr:function(a,c,d,e){var g=a.nodeType;if(!a||g===3||g===8||g===2)return b;if(e&&c in f.attrFn)return f(a)[c](d);var h,i,j=g!==1||!f.isXMLDoc(a);c=j&&f.attrFix[c]||c,i=f.attrHooks[c]||(v&&(f.nodeName(a,"form")||u.test(c))?v:b);if(d!==b){if(d===null||d===!1&&!t.test(c)){f.removeAttr(a,c);return b}if(i&&"set"in i&&j&&(h=i.set(a,d,c))!==b)return h;d===!0&&!t.test(c)&&(d=c),a.setAttribute(c,""+d);return d}if(i&&"get"in i&&j)return i.get(a,c);h=a.getAttribute(c);return h===null?b:h},removeAttr:function(a,b){a.nodeType===1&&(b=f.attrFix[b]||b,f.support.getSetAttribute?a.removeAttribute(b):(f.attr(a,b,""),a.removeAttributeNode(a.getAttributeNode(b))))},attrHooks:{type:{set:function(a,b){if(q.test(a.nodeName)&&a.parentNode)f.error("type property can't be changed");else if(!f.support.radioValue&&b==="radio"&&f.nodeName(a,"input")){var c=a.getAttribute("value");a.setAttribute("type",b),c&&(a.value=c);return b}}},tabIndex:{get:function(a){var c=a.getAttributeNode("tabIndex");return c&&c.specified?parseInt(c.value,10):r.test(a.nodeName)||s.test(a.nodeName)&&a.href?0:b}}},propFix:{},prop:function(a,c,d){var e=a.nodeType;if(!a||e===3||e===8||e===2)return b;var g,h,i=e!==1||!f.isXMLDoc(a);c=i&&f.propFix[c]||c,h=f.propHooks[c];return d!==b?h&&"set"in h&&(g=h.set(a,d,c))!==b?g:a[c]=d:h&&"get"in h&&(g=h.get(a,c))!==b?g:a[c]},propHooks:{}}),f.support.getSetAttribute||(f.attrFix=f.extend(f.attrFix,{"for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder"}),v=f.attrHooks.name=f.attrHooks.value=f.valHooks.button={get:function(a,c){var d;if(c==="value"&&!f.nodeName(a,"button"))return a.getAttribute(c);d=a.getAttributeNode(c);return d&&d.specified?d.nodeValue:b},set:function(a,b,c){var d=a.getAttributeNode(c);if(d){d.nodeValue=b;return b}}},f.each(["width","height"],function(a,b){f.attrHooks[b]=f.extend(f.attrHooks[b],{set:function(a,c){if(c===""){a.setAttribute(b,"auto");return c}}})})),f.support.hrefNormalized||f.each(["href","src","width","height"],function(a,c){f.attrHooks[c]=f.extend(f.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),f.support.style||(f.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=""+b}}),f.support.optSelected||(f.propHooks.selected=f.extend(f.propHooks.selected,{get:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}})),f.support.checkOn||f.each(["radio","checkbox"],function(){f.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),f.each(["radio","checkbox"],function(){f.valHooks[this]=f.extend(f.valHooks[this],{set:function(a,b){if(f.isArray(b))return a.checked=f.inArray(f(a).val(),b)>=0}})});var w=Object.prototype.hasOwnProperty,x=/\.(.*)$/,y=/^(?:textarea|input|select)$/i,z=/\./g,A=/ /g,B=/[^\w\s.|`]/g,C=function(a){return a.replace(B,"\\$&")};f.event={add:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){if(d===!1)d=D;else if(!d)return;var g,h;d.handler&&(g=d,d=g.handler),d.guid||(d.guid=f.guid++);var i=f._data(a);if(!i)return;var j=i.events,k=i.handle;j||(i.events=j={}),k||(i.handle=k=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.handle.apply(k.elem,arguments):b}),k.elem=a,c=c.split(" ");var l,m=0,n;while(l=c[m++]){h=g?f.extend({},g):{handler:d,data:e},l.indexOf(".")>-1?(n=l.split("."),l=n.shift(),h.namespace=n.slice(0).sort().join(".")):(n=[],h.namespace=""),h.type=l,h.guid||(h.guid=d.guid);var o=j[l],p=f.event.special[l]||{};if(!o){o=j[l]=[];if(!p.setup||p.setup.call(a,e,n,k)===!1)a.addEventListener?a.addEventListener(l,k,!1):a.attachEvent&&a.attachEvent("on"+l,k)}p.add&&(p.add.call(a,h),h.handler.guid||(h.handler.guid=d.guid)),o.push(h),f.event.global[l]=!0}a=null}},global:{},remove:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){d===!1&&(d=D);var g,h,i,j,k=0,l,m,n,o,p,q,r,s=f.hasData(a)&&f._data(a),t=s&&s.events;if(!s||!t)return;c&&c.type&&(d=c.handler,c=c.type);if(!c||typeof c=="string"&&c.charAt(0)==="."){c=c||"";for(h in t)f.event.remove(a,h+c);return}c=c.split(" ");while(h=c[k++]){r=h,q=null,l=h.indexOf(".")<0,m=[],l||(m=h.split("."),h=m.shift(),n=new RegExp("(^|\\.)"+f.map(m.slice(0).sort(),C).join("\\.(?:.*\\.)?")+"(\\.|$)")),p=t[h];if(!p)continue;if(!d){for(j=0;j<p.length;j++){q=p[j];if(l||n.test(q.namespace))f.event.remove(a,r,q.handler,j),p.splice(j--,1)}continue}o=f.event.special[h]||{};for(j=e||0;j<p.length;j++){q=p[j];if(d.guid===q.guid){if(l||n.test(q.namespace))e==null&&p.splice(j--,1),o.remove&&o.remove.call(a,q);if(e!=null)break}}if(p.length===0||e!=null&&p.length===1)(!o.teardown||o.teardown.call(a,m)===!1)&&f.removeEvent(a,h,s.handle),g=null,delete t[h]}if(f.isEmptyObject(t)){var u=s.handle;u&&(u.elem=null),delete s.events,delete s.handle,f.isEmptyObject(s)&&f.removeData(a,b,!0)}}},customEvent:{getData:!0,setData:!0,changeData:!0},trigger:function(c,d,e,g){var h=c.type||c,i=[],j;h.indexOf("!")>=0&&(h=h.slice(0,-1),j=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if(!!e&&!f.event.customEvent[h]||!!f.event.global[h]){c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.exclusive=j,c.namespace=i.join("."),c.namespace_re=new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)");if(g||!e)c.preventDefault(),c.stopPropagation();if(!e){f.each(f.cache,function(){var a=f.expando,b=this[a];b&&b.events&&b.events[h]&&f.event.trigger(c,d,b.handle.elem)});return}if(e.nodeType===3||e.nodeType===8)return;c.result=b,c.target=e,d=d?f.makeArray(d):[],d.unshift(c);var k=e,l=h.indexOf(":")<0?"on"+h:"";do{var m=f._data(k,"handle");c.currentTarget=k,m&&m.apply(k,d),l&&f.acceptData(k)&&k[l]&&k[l].apply(k,d)===!1&&(c.result=!1,c.preventDefault()),k=k.parentNode||k.ownerDocument||k===c.target.ownerDocument&&a}while(k&&!c.isPropagationStopped());if(!c.isDefaultPrevented()){var n,o=f.event.special[h]||{};if((!o._default||o._default.call(e.ownerDocument,c)===!1)&&(h!=="click"||!f.nodeName(e,"a"))&&f.acceptData(e)){try{l&&e[h]&&(n=e[l],n&&(e[l]=null),f.event.triggered=h,e[h]())}catch(p){}n&&(e[l]=n),f.event.triggered=b}}return c.result}},handle:function(c){c=f.event.fix(c||a.event);var d=((f._data(this,"events")||{})[c.type]||[]).slice(0),e=!c.exclusive&&!c.namespace,g=Array.prototype.slice.call(arguments,0);g[0]=c,c.currentTarget=this;for(var h=0,i=d.length;h<i;h++){var j=d[h];if(e||c.namespace_re.test(j.namespace)){c.handler=j.handler,c.data=j.data,c.handleObj=j;var k=j.handler.apply(this,g);k!==b&&(c.result=k,k===!1&&(c.preventDefault(),c.stopPropagation()));if(c.isImmediatePropagationStopped())break}}return c.result},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),fix:function(a){if(a[f.expando])return a;var d=a;a=f.Event(d);for(var e=this.props.length,g;e;)g=this.props[--e],a[g]=d[g];a.target||(a.target=a.srcElement||c),a.target.nodeType===3&&(a.target=a.target.parentNode),!a.relatedTarget&&a.fromElement&&(a.relatedTarget=a.fromElement===a.target?a.toElement:a.fromElement);if(a.pageX==null&&a.clientX!=null){var h=a.target.ownerDocument||c,i=h.documentElement,j=h.body;a.pageX=a.clientX+(i&&i.scrollLeft||j&&j.scrollLeft||0)-(i&&i.clientLeft||j&&j.clientLeft||0),a.pageY=a.clientY+(i&&i.scrollTop||j&&j.scrollTop||0)-(i&&i.clientTop||j&&j.clientTop||0)}a.which==null&&(a.charCode!=null||a.keyCode!=null)&&(a.which=a.charCode!=null?a.charCode:a.keyCode),!a.metaKey&&a.ctrlKey&&(a.metaKey=a.ctrlKey),!a.which&&a.button!==b&&(a.which=a.button&1?1:a.button&2?3:a.button&4?2:0);return a},guid:1e8,proxy:f.proxy,special:{ready:{setup:f.bindReady,teardown:f.noop},live:{add:function(a){f.event.add(this,N(a.origType,a.selector),f.extend({},a,{handler:M,guid:a.handler.guid}))},remove:function(a){f.event.remove(this,N(a.origType,a.selector),a)}},beforeunload:{setup:function(a,b,c){f.isWindow(this)&&(this.onbeforeunload=c)},teardown:function(a,b){this.onbeforeunload===b&&(this.onbeforeunload=null)}}}},f.removeEvent=c.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){a.detachEvent&&a.detachEvent("on"+b,c)},f.Event=function(a,b){if(!this.preventDefault)return new f.Event(a,b);a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||a.returnValue===!1||a.getPreventDefault&&a.getPreventDefault()?E:D):this.type=a,b&&f.extend(this,b),this.timeStamp=f.now(),this[f.expando]=!0},f.Event.prototype={preventDefault:function(){this.isDefaultPrevented=E;var a=this.originalEvent;!a||(a.preventDefault?a.preventDefault():a.returnValue=!1)},stopPropagation:function(){this.isPropagationStopped=E;var a=this.originalEvent;!a||(a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=E,this.stopPropagation()},isDefaultPrevented:D,isPropagationStopped:D,isImmediatePropagationStopped:D};var F=function(a){var b=a.relatedTarget;try{if(b&&b!==c&&!b.parentNode)return;while(b&&b!==this)b=b.parentNode;b!==this&&(a.type=a.data,f.event.handle.apply(this,arguments))}catch(d){}},G=function(a){a.type=a.data,f.event.handle.apply(this,arguments)};f.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){f.event.special[a]={setup:function(c){f.event.add(this,b,c&&c.selector?G:F,a)},teardown:function(a){f.event.remove(this,b,a&&a.selector?G:F)}}}),f.support.submitBubbles||(f.event.special.submit={setup:function(a,b){if(!f.nodeName(this,"form"))f.event.add(this,"click.specialSubmit",function(a){var b=a.target,c=b.type;(c==="submit"||c==="image")&&f(b).closest("form").length&&K("submit",this,arguments)}),f.event.add(this,"keypress.specialSubmit",function(a){var b=a.target,c=b.type;(c==="text"||c==="password")&&f(b).closest("form").length&&a.keyCode===13&&K("submit",this,arguments)});else return!1},teardown:function(a){f.event.remove(this,".specialSubmit")}});if(!f.support.changeBubbles){var H,I=function(a){var b=a.type,c=a.value;b==="radio"||b==="checkbox"?c=a.checked:b==="select-multiple"?c=a.selectedIndex>-1?f.map(a.options,function(a){return a.selected}).join("-"):"":f.nodeName(a,"select")&&(c=a.selectedIndex);return c},J=function J(a){var c=a.target,d,e;if(!!y.test(c.nodeName)&&!c.readOnly){d=f._data(c,"_change_data"),e=I(c),(a.type!=="focusout"||c.type!=="radio")&&f._data(c,"_change_data",e);if(d===b||e===d)return;if(d!=null||e)a.type="change",a.liveFired=b,f.event.trigger(a,arguments[1],c)}};f.event.special.change={filters:{focusout:J,beforedeactivate:J,click:function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(c==="radio"||c==="checkbox"||f.nodeName(b,"select"))&&J.call(this,a)},keydown:function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(a.keyCode===13&&!f.nodeName(b,"textarea")||a.keyCode===32&&(c==="checkbox"||c==="radio")||c==="select-multiple")&&J.call(this,a)},beforeactivate:function(a){var b=a.target;f._data(b,"_change_data",I(b))}},setup:function(a,b){if(this.type==="file")return!1;for(var c in H)f.event.add(this,c+".specialChange",H[c]);return y.test(this.nodeName)},teardown:function(a){f.event.remove(this,".specialChange");return y.test(this.nodeName)}},H=f.event.special.change.filters,H.focus=H.beforeactivate}f.support.focusinBubbles||f.each({focus:"focusin",blur:"focusout"},function(a,b){function e(a){var c=f.event.fix(a);c.type=b,c.originalEvent={},f.event.trigger(c,null,c.target),c.isDefaultPrevented()&&a.preventDefault()}var d=0;f.event.special[b]={setup:function(){d++===0&&c.addEventListener(a,e,!0)},teardown:function(){--d===0&&c.removeEventListener(a,e,!0)}}}),f.each(["bind","one"],function(a,c){f.fn[c]=function(a,d,e){var g;if(typeof a=="object"){for(var h in a)this[c](h,d,a[h],e);return this}if(arguments.length===2||d===!1)e=d,d=b;c==="one"?(g=function(a){f(this).unbind(a,g);return e.apply(this,arguments)},g.guid=e.guid||f.guid++):g=e;if(a==="unload"&&c!=="one")this.one(a,d,e);else for(var i=0,j=this.length;i<j;i++)f.event.add(this[i],a,g,d);return this}}),f.fn.extend({unbind:function(a,b){if(typeof a=="object"&&!a.preventDefault)for(var c in a)this.unbind(c,a[c]);else for(var d=0,e=this.length;d<e;d++)f.event.remove(this[d],a,b);return this},delegate:function(a,b,c,d){return this.live(b,c,d,a)},undelegate:function(a,b,c){return arguments.length===0?this.unbind("live"):this.die(b,null,c,a)},trigger:function(a,b){return this.each(function(){f.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0])return f.event.trigger(a,b,this[0],!0)},toggle:function(a){var b=arguments,c=a.guid||f.guid++,d=0,e=function(c){var e=(f.data(this,"lastToggle"+a.guid)||0)%d;f.data(this,"lastToggle"+a.guid,e+1),c.preventDefault();return b[e].apply(this,arguments)||!1};e.guid=c;while(d<b.length)b[d++].guid=c;return this.click(e)},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}});var L={focus:"focusin",blur:"focusout",mouseenter:"mouseover",mouseleave:"mouseout"};f.each(["live","die"],function(a,c){f.fn[c]=function(a,d,e,g){var h,i=0,j,k,l,m=g||this.selector,n=g?this:f(this.context);if(typeof a=="object"&&!a.preventDefault){for(var o in a)n[c](o,d,a[o],m);return this}if(c==="die"&&!a&&g&&g.charAt(0)==="."){n.unbind(g);return this}if(d===!1||f.isFunction(d))e=d||D,d=b;a=(a||"").split(" ");while((h=a[i++])!=null){j=x.exec(h),k="",j&&(k=j[0],h=h.replace(x,""));if(h==="hover"){a.push("mouseenter"+k,"mouseleave"+k);continue}l=h,L[h]?(a.push(L[h]+k),h=h+k):h=(L[h]||h)+k;if(c==="live")for(var p=0,q=n.length;p<q;p++)f.event.add(n[p],"live."+N(h,m),{data:d,selector:m,handler:e,origType:h,origHandler:e,preType:l});else n.unbind("live."+N(h,m),e)}return this}}),f.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error".split(" "),function(a,b){f.fn[b]=function(a,c){c==null&&(c=a,a=null);return arguments.length>0?this.bind(b,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0)}),function(){function u(a,b,c,d,e,f){for(var g=0,h=d.length;g<h;g++){var i=d[g];if(i){var j=!1;i=i[a];while(i){if(i.sizcache===c){j=d[i.sizset];break}if(i.nodeType===1){f||(i.sizcache=c,i.sizset=g);if(typeof b!="string"){if(i===b){j=!0;break}}else if(k.filter(b,[i]).length>0){j=i;break}}i=i[a]}d[g]=j}}}function t(a,b,c,d,e,f){for(var g=0,h=d.length;g<h;g++){var i=d[g];if(i){var j=!1;i=i[a];while(i){if(i.sizcache===c){j=d[i.sizset];break}i.nodeType===1&&!f&&(i.sizcache=c,i.sizset=g);if(i.nodeName.toLowerCase()===b){j=i;break}i=i[a]}d[g]=j}}}var a=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d=0,e=Object.prototype.toString,g=!1,h=!0,i=/\\/g,j=/\W/;[0,0].sort(function(){h=!1;return 0});var k=function(b,d,f,g){f=f||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return f;var i,j,n,o,q,r,s,t,u=!0,w=k.isXML(d),x=[],y=b;do{a.exec(""),i=a.exec(y);if(i){y=i[3],x.push(i[1]);if(i[2]){o=i[3];break}}}while(i);if(x.length>1&&m.exec(b))if(x.length===2&&l.relative[x[0]])j=v(x[0]+x[1],d);else{j=l.relative[x[0]]?[d]:k(x.shift(),d);while(x.length)b=x.shift(),l.relative[b]&&(b+=x.shift()),j=v(b,j)}else{!g&&x.length>1&&d.nodeType===9&&!w&&l.match.ID.test(x[0])&&!l.match.ID.test(x[x.length-1])&&(q=k.find(x.shift(),d,w),d=q.expr?k.filter(q.expr,q.set)[0]:q.set[0]);if(d){q=g?{expr:x.pop(),set:p(g)}:k.find(x.pop(),x.length===1&&(x[0]==="~"||x[0]==="+")&&d.parentNode?d.parentNode:d,w),j=q.expr?k.filter(q.expr,q.set):q.set,x.length>0?n=p(j):u=!1;while(x.length)r=x.pop(),s=r,l.relative[r]?s=x.pop():r="",s==null&&(s=d),l.relative[r](n,s,w)}else n=x=[]}n||(n=j),n||k.error(r||b);if(e.call(n)==="[object Array]")if(!u)f.push.apply(f,n);else if(d&&d.nodeType===1)for(t=0;n[t]!=null;t++)n[t]&&(n[t]===!0||n[t].nodeType===1&&k.contains(d,n[t]))&&f.push(j[t]);else for(t=0;n[t]!=null;t++)n[t]&&n[t].nodeType===1&&f.push(j[t]);else p(n,f);o&&(k(o,h,f,g),k.uniqueSort(f));return f};k.uniqueSort=function(a){if(r){g=h,a.sort(r);if(g)for(var b=1;b<a.length;b++)a[b]===a[b-1]&&a.splice(b--,1)}return a},k.matches=function(a,b){return k(a,null,null,b)},k.matchesSelector=function(a,b){return k(b,null,null,[a]).length>0},k.find=function(a,b,c){var d;if(!a)return[];for(var e=0,f=l.order.length;e<f;e++){var g,h=l.order[e];if(g=l.leftMatch[h].exec(a)){var j=g[1];g.splice(1,1);if(j.substr(j.length-1)!=="\\"){g[1]=(g[1]||"").replace(i,""),d=l.find[h](g,b,c);if(d!=null){a=a.replace(l.match[h],"");break}}}}d||(d=typeof b.getElementsByTagName!="undefined"?b.getElementsByTagName("*"):[]);return{set:d,expr:a}},k.filter=function(a,c,d,e){var f,g,h=a,i=[],j=c,m=c&&c[0]&&k.isXML(c[0]);while(a&&c.length){for(var n in l.filter)if((f=l.leftMatch[n].exec(a))!=null&&f[2]){var o,p,q=l.filter[n],r=f[1];g=!1,f.splice(1,1);if(r.substr(r.length-1)==="\\")continue;j===i&&(i=[]);if(l.preFilter[n]){f=l.preFilter[n](f,j,d,i,e,m);if(!f)g=o=!0;else if(f===!0)continue}if(f)for(var s=0;(p=j[s])!=null;s++)if(p){o=q(p,f,s,j);var t=e^!!o;d&&o!=null?t?g=!0:j[s]=!1:t&&(i.push(p),g=!0)}if(o!==b){d||(j=i),a=a.replace(l.match[n],"");if(!g)return[];break}}if(a===h)if(g==null)k.error(a);else break;h=a}return j},k.error=function(a){throw"Syntax error, unrecognized expression: "+a};var l=k.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(a){return a.getAttribute("href")},type:function(a){return a.getAttribute("type")}},relative:{"+":function(a,b){var c=typeof b=="string",d=c&&!j.test(b),e=c&&!d;d&&(b=b.toLowerCase());for(var f=0,g=a.length,h;f<g;f++)if(h=a[f]){while((h=h.previousSibling)&&h.nodeType!==1);a[f]=e||h&&h.nodeName.toLowerCase()===b?h||!1:h===b}e&&k.filter(b,a,!0)},">":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!j.test(b)){b=b.toLowerCase();for(;e<f;e++){c=a[e];if(c){var g=c.parentNode;a[e]=g.nodeName.toLowerCase()===b?g:!1}}}else{for(;e<f;e++)c=a[e],c&&(a[e]=d?c.parentNode:c.parentNode===b);d&&k.filter(b,a,!0)}},"":function(a,b,c){var e,f=d++,g=u;typeof b=="string"&&!j.test(b)&&(b=b.toLowerCase(),e=b,g=t),g("parentNode",b,f,a,e,c)},"~":function(a,b,c){var e,f=d++,g=u;typeof b=="string"&&!j.test(b)&&(b=b.toLowerCase(),e=b,g=t),g("previousSibling",b,f,a,e,c)}},find:{ID:function(a,b,c){if(typeof b.getElementById!="undefined"&&!c){var d=b.getElementById(a[1]);return d&&d.parentNode?[d]:[]}},NAME:function(a,b){if(typeof b.getElementsByName!="undefined"){var c=[],d=b.getElementsByName(a[1]);for(var e=0,f=d.length;e<f;e++)d[e].getAttribute("name")===a[1]&&c.push(d[e]);return c.length===0?null:c}},TAG:function(a,b){if(typeof b.getElementsByTagName!="undefined")return b.getElementsByTagName(a[1])}},preFilter:{CLASS:function(a,b,c,d,e,f){a=" "+a[1].replace(i,"")+" ";if(f)return a;for(var g=0,h;(h=b[g])!=null;g++)h&&(e^(h.className&&(" "+h.className+" ").replace(/[\t\n\r]/g," ").indexOf(a)>=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(i,"")},TAG:function(a,b){return a[1].replace(i,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||k.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&k.error(a[0]);a[0]=d++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(i,"");!f&&l.attrMap[g]&&(a[1]=l.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(i,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=k(b[3],null,null,c);else{var g=k.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(l.match.POS.test(b[0])||l.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!k(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){return a.nodeName.toLowerCase()==="input"&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return b<c[3]-0},gt:function(a,b,c){return b>c[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=l.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||k.getText([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h<i;h++)if(g[h]===a)return!1;return!0}k.error(e)},CHILD:function(a,b){var c=b[1],d=a;switch(c){case"only":case"first":while(d=d.previousSibling)if(d.nodeType===1)return!1;if(c==="first")return!0;d=a;case"last":while(d=d.nextSibling)if(d.nodeType===1)return!1;return!0;case"nth":var e=b[2],f=b[3];if(e===1&&f===0)return!0;var g=b[0],h=a.parentNode;if(h&&(h.sizcache!==g||!a.nodeIndex)){var i=0;for(d=h.firstChild;d;d=d.nextSibling)d.nodeType===1&&(d.nodeIndex=++i);h.sizcache=g}var j=a.nodeIndex-f;return e===0?j===0:j%e===0&&j/e>=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=l.attrHandle[c]?l.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=l.setFilters[e];if(f)return f(a,c,b,d)}}},m=l.match.POS,n=function(a,b){return"\\"+(b-0+1)};for(var o in l.match)l.match[o]=new RegExp(l.match[o].source+/(?![^\[]*\])(?![^\(]*\))/.source),l.leftMatch[o]=new RegExp(/(^(?:.|\r|\n)*?)/.source+l.match[o].source.replace(/\\(\d+)/g,n));var p=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(q){p=function(a,b){var c=0,d=b||[];if(e.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var f=a.length;c<f;c++)d.push(a[c]);else for(;a[c];c++)d.push(a[c]);return d}}var r,s;c.documentElement.compareDocumentPosition?r=function(a,b){if(a===b){g=!0;return 0}if(!a.compareDocumentPosition||!b.compareDocumentPosition)return a.compareDocumentPosition?-1:1;return a.compareDocumentPosition(b)&4?-1:1}:(r=function(a,b){var c,d,e=[],f=[],h=a.parentNode,i=b.parentNode,j=h;if(a===b){g=!0;return 0}if(h===i)return s(a,b);if(!h)return-1;if(!i)return 1;while(j)e.unshift(j),j=j.parentNode;j=i;while(j)f.unshift(j),j=j.parentNode;c=e.length,d=f.length;for(var k=0;k<c&&k<d;k++)if(e[k]!==f[k])return s(e[k],f[k]);return k===c?s(a,f[k],-1):s(e[k],b,1)},s=function(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}),k.getText=function(a){var b="",c;for(var d=0;a[d];d++)c=a[d],c.nodeType===3||c.nodeType===4?b+=c.nodeValue:c.nodeType!==8&&(b+=k.getText(c.childNodes));return b},function(){var a=c.createElement("div"),d="script"+(new Date).getTime(),e=c.documentElement;a.innerHTML="<a name='"+d+"'/>",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(l.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},l.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(l.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="<a href='#'></a>",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(l.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=k,b=c.createElement("div"),d="__sizzle__";b.innerHTML="<p class='TEST'></p>";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){k=function(b,e,f,g){e=e||c;if(!g&&!k.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return p(e.getElementsByTagName(b),f);if(h[2]&&l.find.CLASS&&e.getElementsByClassName)return p(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return p([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return p([],f);if(i.id===h[3])return p([i],f)}try{return p(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var m=e,n=e.getAttribute("id"),o=n||d,q=e.parentNode,r=/^\s*[+~]/.test(b);n?o=o.replace(/'/g,"\\$&"):e.setAttribute("id",o),r&&q&&(e=e.parentNode);try{if(!r||q)return p(e.querySelectorAll("[id='"+o+"'] "+b),f)}catch(s){}finally{n||m.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)k[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}k.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!k.isXML(a))try{if(e||!l.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return k(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="<div class='test e'></div><div class='test'></div>";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;l.order.splice(1,0,"CLASS"),l.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?k.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?k.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:k.contains=function(){return!1},k.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var v=function(a,b){var c,d=[],e="",f=b.nodeType?[b]:b;while(c=l.match.PSEUDO.exec(a))e+=c[0],a=a.replace(l.match.PSEUDO,"");a=l.relative[a]?a+"*":a;for(var g=0,h=f.length;g<h;g++)k(a,f[g],d);return k.filter(e,d)};f.find=k,f.expr=k.selectors,f.expr[":"]=f.expr.filters,f.unique=k.uniqueSort,f.text=k.getText,f.isXMLDoc=k.isXML,f.contains=k.contains}();var O=/Until$/,P=/^(?:parents|prevUntil|prevAll)/,Q=/,/,R=/^.[^:#\[\.,]*$/,S=Array.prototype.slice,T=f.expr.match.POS,U={children:!0,contents:!0,next:!0,prev:!0};f.fn.extend({find:function(a){var b=this,c,d;if(typeof a!="string")return f(a).filter(function(){for(c=0,d=b.length;c<d;c++)if(f.contains(b[c],this))return!0});var e=this.pushStack("","find",a),g,h,i;for(c=0,d=this.length;c<d;c++){g=e.length,f.find(a,this[c],e);if(c>0)for(h=g;h<e.length;h++)for(i=0;i<g;i++)if(e[i]===e[h]){e.splice(h--,1);break}}return e},has:function(a){var b=f(a);return this.filter(function(){for(var a=0,c=b.length;a<c;a++)if(f.contains(this,b[a]))return!0})},not:function(a){return this.pushStack(W(this,a,!1),"not",a)},filter:function(a){return this.pushStack(W(this,a,!0),"filter",a)},is:function(a){return!!a&&(typeof a=="string"?f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h,i,j={},k=1;if(g&&a.length){for(d=0,e=a.length;d<e;d++)i=a[d],j[i]||(j[i]=T.test(i)?f(i,b||this.context):i);while(g&&g.ownerDocument&&g!==b){for(i in j)h=j[i],(h.jquery?h.index(g)>-1:f(g).is(h))&&c.push({selector:i,elem:g,level:k});g=g.parentNode,k++}}return c}var l=T.test(a)||typeof a!="string"?f(a,b||this.context):0;for(d=0,e=this.length;d<e;d++){g=this[d];while(g){if(l?l.index(g)>-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a||typeof a=="string")return f.inArray(this[0],a?f(a):this.parent().children());return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(V(c[0])||V(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling(a.parentNode.firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c),g=S.call(arguments);O.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!U[a]?f.unique(e):e,(this.length>1||Q.test(d))&&P.test(a)&&(e=e.reverse());return this.pushStack(e,a,g.join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var X=/ jQuery\d+="(?:\d+|null)"/g,Y=/^\s+/,Z=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,$=/<([\w:]+)/,_=/<tbody/i,ba=/<|&#?\w+;/,bb=/<(?:script|object|embed|option|style)/i,bc=/checked\s*(?:[^=]|=\s*.checked.)/i,bd=/\/(java|ecma)script/i,be={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]};be.optgroup=be.option,be.tbody=be.tfoot=be.colgroup=be.caption=be.thead,be.th=be.td,f.support.htmlSerialize||(be._default=[1,"div<div>","</div>"]),f.fn.extend({text:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.text(a.call(this,b,c.text()))});if(typeof a!="object"&&a!==b)return this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a));return f.text(this)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){f(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f(arguments[0]).toArray());return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(X,""):null;if(typeof a=="string"&&!bb.test(a)&&(f.support.leadingWhitespace||!Y.test(a))&&!be[($.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Z,"<$1></$2>");try{for(var c=0,d=this.length;c<d;c++)this[c].nodeType===1&&(f.cleanData(this[c].getElementsByTagName("*")),this[c].innerHTML=a)}catch(e){this.empty().append(a)}}else f.isFunction(a)?this.each(function(b){var c=f(this);c.html(a.call(this,b,c.html()))}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&this[0].parentNode){if(f.isFunction(a))return this.each(function(b){var c=f(this),d=c.html();c.replaceWith(a.call(this,b,d))});typeof a!="string"&&(a=f(a).detach());return this.each(function(){var b=this.nextSibling,c=this.parentNode;f(this).remove(),b?f(b).before(a):f(c).append(a)})}return this.length?this.pushStack(f(f.isFunction(a)?a():a),"replaceWith",a):this},detach:function(a){return this.remove(a,!0)},domManip:function(a,c,d){var e,g,h,i,j=a[0],k=[];if(!f.support.checkClone&&arguments.length===3&&typeof j=="string"&&bc.test(j))return this.each(function(){f(this).domManip(a,c,d,!0)});if(f.isFunction(j))return this.each(function(e){var g=f(this);a[0]=j.call(this,e,c?g.html():b),g.domManip(a,c,d)});if(this[0]){i=j&&j.parentNode,f.support.parentNode&&i&&i.nodeType===11&&i.childNodes.length===this.length?e={fragment:i}:e=f.buildFragment(a,this,k),h=e.fragment,h.childNodes.length===1?g=h=h.firstChild:g=h.firstChild;if(g){c=c&&f.nodeName(g,"tr");for(var l=0,m=this.length,n=m-1;l<m;l++)d.call(c?bf(this[l],g):this[l],e.cacheable||m>1&&l<n?f.clone(h,!0,!0):h)}k.length&&f.each(k,bl)}return this}}),f.buildFragment=function(a,b,d){var e,g,h,i=b&&b[0]?b[0].ownerDocument||b[0]:c;a.length===1&&typeof a[0]=="string"&&a[0].length<512&&i===c&&a[0].charAt(0)==="<"&&!bb.test(a[0])&&(f.support.checkClone||!bc.test(a[0]))&&(g=!0,h=f.fragments[a[0]],h&&h!==1&&(e=h)),e||(e=i.createDocumentFragment(),f.clean(a,i,e,d)),g&&(f.fragments[a[0]]=h?e:1);return{fragment:e,cacheable:g}},f.fragments={},f.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){f.fn[a]=function(c){var d=[],e=f(c),g=this.length===1&&this[0].parentNode;if(g&&g.nodeType===11&&g.childNodes.length===1&&e.length===1){e[b](this[0]);return this}for(var h=0,i=e.length;h<i;h++){var j=(h>0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d=a.cloneNode(!0),e,g,h;if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bh(a,d),e=bi(a),g=bi(d);for(h=0;e[h];++h)bh(e[h],g[h])}if(b){bg(a,d);if(c){e=bi(a),g=bi(d);for(h=0;e[h];++h)bg(e[h],g[h])}}return d},clean:function(a,b,d,e){var g;b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);var h=[];for(var i=0,j;(j=a[i])!=null;i++){typeof j=="number"&&(j+="");if(!j)continue;if(typeof j=="string")if(!ba.test(j))j=b.createTextNode(j);else{j=j.replace(Z,"<$1></$2>");var k=($.exec(j)||["",""])[1].toLowerCase(),l=be[k]||be._default,m=l[0],n=b.createElement("div");n.innerHTML=l[1]+j+l[2];while(m--)n=n.lastChild;if(!f.support.tbody){var o=_.test(j),p=k==="table"&&!o?n.firstChild&&n.firstChild.childNodes:l[1]==="<table>"&&!o?n.childNodes:[];for(var q=p.length-1;q>=0;--q)f.nodeName(p[q],"tbody")&&!p[q].childNodes.length&&p[q].parentNode.removeChild(p[q])}!f.support.leadingWhitespace&&Y.test(j)&&n.insertBefore(b.createTextNode(Y.exec(j)[0]),n.firstChild),j=n.childNodes}var r;if(!f.support.appendChecked)if(j[0]&&typeof (r=j.length)=="number")for(i=0;i<r;i++)bk(j[i]);else bk(j);j.nodeType?h.push(j):h=f.merge(h,j)}if(d){g=function(a){return!a.type||bd.test(a.type)};for(i=0;h[i];i++)if(e&&f.nodeName(h[i],"script")&&(!h[i].type||h[i].type.toLowerCase()==="text/javascript"))e.push(h[i].parentNode?h[i].parentNode.removeChild(h[i]):h[i]);else{if(h[i].nodeType===1){var s=f.grep(h[i].getElementsByTagName("script"),g);h.splice.apply(h,[i+1,0].concat(s))}d.appendChild(h[i])}}return h},cleanData:function(a){var b,c,d=f.cache,e=f.expando,g=f.event.special,h=f.support.deleteExpando;for(var i=0,j;(j=a[i])!=null;i++){if(j.nodeName&&f.noData[j.nodeName.toLowerCase()])continue;c=j[f.expando];if(c){b=d[c]&&d[c][e];if(b&&b.events){for(var k in b.events)g[k]?f.event.remove(j,k):f.removeEvent(j,k,b.handle);b.handle&&(b.handle.elem=null)}h?delete j[f.expando]:j.removeAttribute&&j.removeAttribute(f.expando),delete d[c]}}}});var bm=/alpha\([^)]*\)/i,bn=/opacity=([^)]*)/,bo=/-([a-z])/ig,bp=/([A-Z]|^ms)/g,bq=/^-?\d+(?:px)?$/i,br=/^-?\d/,bs=/^[+\-]=/,bt=/[^+\-\.\de]+/g,bu={position:"absolute",visibility:"hidden",display:"block"},bv=["Left","Right"],bw=["Top","Bottom"],bx,by,bz,bA=function(a,b){return b.toUpperCase()};f.fn.css=function(a,c){if(arguments.length===2&&c===b)return this;return f.access(this,a,c,!0,function(a,c,d){return d!==b?f.style(a,c,d):f.css(a,c)})},f.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bx(a,"opacity","opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{zIndex:!0,fontWeight:!0,opacity:!0,zoom:!0,lineHeight:!0,widows:!0,orphans:!0},cssProps:{"float":f.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!!a&&a.nodeType!==3&&a.nodeType!==8&&!!a.style){var g,h,i=f.camelCase(c),j=a.style,k=f.cssHooks[i];c=f.cssProps[i]||i;if(d===b){if(k&&"get"in k&&(g=k.get(a,!1,e))!==b)return g;return j[c]}h=typeof d;if(h==="number"&&isNaN(d)||d==null)return;h==="string"&&bs.test(d)&&(d=+d.replace(bt,"")+parseFloat(f.css(a,c))),h==="number"&&!f.cssNumber[i]&&(d+="px");if(!k||!("set"in k)||(d=k.set(a,d))!==b)try{j[c]=d}catch(l){}}},css:function(a,c,d){var e,g;c=f.camelCase(c),g=f.cssHooks[c],c=f.cssProps[c]||c,c==="cssFloat"&&(c="float");if(g&&"get"in g&&(e=g.get(a,!0,d))!==b)return e;if(bx)return bx(a,c)},swap:function(a,b,c){var d={};for(var e in b)d[e]=a.style[e],a.style[e]=b[e];c.call(a);for(e in b)a.style[e]=d[e]},camelCase:function(a){return a.replace(bo,bA)}}),f.curCSS=f.css,f.each(["height","width"],function(a,b){f.cssHooks[b]={get:function(a,c,d){var e;if(c){a.offsetWidth!==0?e=bB(a,b,d):f.swap(a,bu,function(){e=bB(a,b,d)});if(e<=0){e=bx(a,b,b),e==="0px"&&bz&&(e=bz(a,b,b));if(e!=null)return e===""||e==="auto"?"0px":e}if(e<0||e==null){e=a.style[b];return e===""||e==="auto"?"0px":e}return typeof e=="string"?e:e+"px"}},set:function(a,b){if(!bq.test(b))return b;b=parseFloat(b);if(b>=0)return b+"px"}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return bn.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle;c.zoom=1;var e=f.isNaN(b)?"":"alpha(opacity="+b*100+")",g=d&&d.filter||c.filter||"";c.filter=bm.test(g)?g.replace(bm,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){var c;f.swap(a,{display:"inline-block"},function(){b?c=bx(a,"margin-right","marginRight"):c=a.style.marginRight});return c}})}),c.defaultView&&c.defaultView.getComputedStyle&&(by=function(a,c){var d,e,g;c=c.replace(bp,"-$1").toLowerCase();if(!(e=a.ownerDocument.defaultView))return b;if(g=e.getComputedStyle(a,null))d=g.getPropertyValue(c),d===""&&!f.contains(a.ownerDocument.documentElement,a)&&(d=f.style(a,c));return d}),c.documentElement.currentStyle&&(bz=function(a,b){var c,d=a.currentStyle&&a.currentStyle[b],e=a.runtimeStyle&&a.runtimeStyle[b],f=a.style;!bq.test(d)&&br.test(d)&&(c=f.left,e&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":d||0,d=f.pixelLeft+"px",f.left=c,e&&(a.runtimeStyle.left=e));return d===""?"auto":d}),bx=by||bz,f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)});var bC=/%20/g,bD=/\[\]$/,bE=/\r?\n/g,bF=/#.*$/,bG=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bH=/^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bI=/^(?:about|app|app\-storage|.+\-extension|file|widget):$/,bJ=/^(?:GET|HEAD)$/,bK=/^\/\//,bL=/\?/,bM=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,bN=/^(?:select|textarea)/i,bO=/\s+/,bP=/([?&])_=[^&]*/,bQ=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bR=f.fn.load,bS={},bT={},bU,bV;try{bU=e.href}catch(bW){bU=c.createElement("a"),bU.href="",bU=bU.href}bV=bQ.exec(bU.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bR)return bR.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("<div>").append(c.replace(bM,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bN.test(this.nodeName)||bH.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bE,"\r\n")}}):{name:b.name,value:c.replace(bE,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.bind(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?f.extend(!0,a,f.ajaxSettings,b):(b=a,a=f.extend(!0,f.ajaxSettings,b));for(var c in{context:1,url:1})c in b?a[c]=b[c]:c in f.ajaxSettings&&(a[c]=f.ajaxSettings[c]);return a},ajaxSettings:{url:bU,isLocal:bI.test(bV[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":"*/*"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML}},ajaxPrefilter:bX(bS),ajaxTransport:bX(bT),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a?4:0;var o,r,u,w=l?b$(d,v,l):b,x,y;if(a>=200&&a<300||a===304){if(d.ifModified){if(x=v.getResponseHeader("Last-Modified"))f.lastModified[k]=x;if(y=v.getResponseHeader("Etag"))f.etag[k]=y}if(a===304)c="notmodified",o=!0;else try{r=b_(d,w),c="success",o=!0}catch(z){c="parsererror",u=z}}else{u=c;if(!c||a)c="error",a<0&&(a=0)}v.status=a,v.statusText=c,o?h.resolveWith(e,[r,c,v]):h.rejectWith(e,[v,c,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.resolveWith(e,[v,c]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f._Deferred(),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bG.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.done,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bF,"").replace(bK,bV[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bO),d.crossDomain==null&&(r=bQ.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bV[1]&&r[2]==bV[2]&&(r[3]||(r[1]==="http:"?80:443))==(bV[3]||(bV[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),bY(bS,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bJ.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bL.test(d.url)?"&":"?")+d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bP,"$1_="+x);d.url=y+(y===d.url?(bL.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", */*; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=bY(bT,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){status<2?w(-1,z):f.error(z)}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)bZ(g,a[g],c,e);return d.join("&").replace(bC,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var ca=f.now(),cb=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+ca++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=b.contentType==="application/x-www-form-urlencoded"&&typeof b.data=="string";if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(cb.test(b.url)||e&&cb.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(cb,l),b.url===j&&(e&&(k=k.replace(cb,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var cc=a.ActiveXObject?function(){for(var a in ce)ce[a](0,1)}:!1,cd=0,ce;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&cf()||cg()}:cf,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,cc&&delete ce[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n),m.text=h.responseText;try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cd,cc&&(ce||(ce={},f(a).unload(cc)),ce[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var ch={},ci,cj,ck=/^(?:toggle|show|hide)$/,cl=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,cm,cn=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],co,cp=a.webkitRequestAnimationFrame||a.mozRequestAnimationFrame||a.oRequestAnimationFrame;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(cs("show",3),a,b,c);for(var g=0,h=this.length;g<h;g++)d=this[g],d.style&&(e=d.style.display,!f._data(d,"olddisplay")&&e==="none"&&(e=d.style.display=""),e===""&&f.css(d,"display")==="none"&&f._data(d,"olddisplay",ct(d.nodeName)));for(g=0;g<h;g++){d=this[g];if(d.style){e=d.style.display;if(e===""||e==="none")d.style.display=f._data(d,"olddisplay")||""}}return this},hide:function(a,b,c){if(a||a===0)return this.animate(cs("hide",3),a,b,c);for(var d=0,e=this.length;d<e;d++)if(this[d].style){var g=f.css(this[d],"display");g!=="none"&&!f._data(this[d],"olddisplay")&&f._data(this[d],"olddisplay",g)}for(d=0;d<e;d++)this[d].style&&(this[d].style.display="none");return this},_toggle:f.fn.toggle,toggle:function(a,b,c){var d=typeof a=="boolean";f.isFunction(a)&&f.isFunction(b)?this._toggle.apply(this,arguments):a==null||d?this.each(function(){var b=d?a:f(this).is(":hidden");f(this)[b?"show":"hide"]()}):this.animate(cs("toggle",3),a,b,c);return this},fadeTo:function(a,b,c,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=f.speed(b,c,d);if(f.isEmptyObject(a))return this.each(e.complete,[!1]);return this[e.queue===!1?"each":"queue"](function(){e.queue===!1&&f._mark(this);var b=f.extend({},e),c=this.nodeType===1,d=c&&f(this).is(":hidden"),g,h,i,j,k,l,m,n,o;b.animatedProperties={};for(i in a){g=f.camelCase(i),i!==g&&(a[g]=a[i],delete a[i]),h=a[g];if(h==="hide"&&d||h==="show"&&!d)return b.complete.call(this);c&&(g==="height"||g==="width")&&(b.overflow=[this.style.overflow,this.style.overflowX,this.style.overflowY],f.css(this,"display")==="inline"&&f.css(this,"float")==="none"&&(f.support.inlineBlockNeedsLayout?(j=ct(this.nodeName),j==="inline"?this.style.display="inline-block":(this.style.display="inline",this.style.zoom=1)):this.style.display="inline-block")),b.animatedProperties[g]=f.isArray(h)?h[1]:b.specialEasing&&b.specialEasing[g]||b.easing||"swing"}b.overflow!=null&&(this.style.overflow="hidden");for(i in a)k=new f.fx(this,b,i),h=a[i],ck.test(h)?k[h==="toggle"?d?"show":"hide":h]():(l=cl.exec(h),m=k.cur(),l?(n=parseFloat(l[2]),o=l[3]||(f.cssNumber[g]?"":"px"),o!=="px"&&(f.style(this,i,(n||1)+o),m=(n||1)/k.cur()*m,f.style(this,i,m+o)),l[1]&&(n=(l[1]==="-="?-1:1)*n+m),k.custom(m,n,o)):k.custom(m,h,""));return!0})},stop:function(a,b){a&&this.queue([]),this.each(function(){var a=f.timers,c=a.length;b||f._unmark(!0,this);while(c--)a[c].elem===this&&(b&&a[c](!0),a.splice(c,1))}),b||this.dequeue();return this}}),f.each({slideDown:cs("show",1),slideUp:cs("hide",1),slideToggle:cs("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){f.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),f.extend({speed:function(a,b,c){var d=a&&typeof a=="object"?f.extend({},a):{complete:c||!c&&b||f.isFunction(a)&&a,duration:a,easing:c&&b||b&&!f.isFunction(b)&&b};d.duration=f.fx.off?0:typeof d.duration=="number"?d.duration:d.duration in f.fx.speeds?f.fx.speeds[d.duration]:f.fx.speeds._default,d.old=d.complete,d.complete=function(a){d.queue!==!1?f.dequeue(this):a!==!1&&f._unmark(this),f.isFunction(d.old)&&d.old.call(this)};return d},easing:{linear:function(a,b,c,d){return c+d*a},swing:function(a,b,c,d){return(-Math.cos(a*Math.PI)/2+.5)*d+c}},timers:[],fx:function(a,b,c){this.options=b,this.elem=a,this.prop=c,b.orig=b.orig||{}}}),f.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this),(f.fx.step[this.prop]||f.fx.step._default)(this)},cur:function(){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];var a,b=f.css(this.elem,this.prop);return isNaN(a=parseFloat(b))?!b||b==="auto"?0:b:a},custom:function(a,b,c){function h(a){return d.step(a)}var d=this,e=f.fx,g;this.startTime=co||cq(),this.start=a,this.end=b,this.unit=c||this.unit||(f.cssNumber[this.prop]?"":"px"),this.now=this.start,this.pos=this.state=0,h.elem=this.elem,h()&&f.timers.push(h)&&!cm&&(cp?(cm=1,g=function(){cm&&(cp(g),e.tick())},cp(g)):cm=setInterval(e.tick,e.interval))},show:function(){this.options.orig[this.prop]=f.style(this.elem,this.prop),this.options.show=!0,this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur()),f(this.elem).show()},hide:function(){this.options.orig[this.prop]=f.style(this.elem,this.prop),this.options.hide=!0,this.custom(this.cur(),0)},step:function(a){var b=co||cq(),c=!0,d=this.elem,e=this.options,g,h;if(a||b>=e.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),e.animatedProperties[this.prop]=!0;for(g in e.animatedProperties)e.animatedProperties[g]!==!0&&(c=!1);if(c){e.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){d.style["overflow"+b]=e.overflow[a]}),e.hide&&f(d).hide();if(e.hide||e.show)for(var i in e.animatedProperties)f.style(d,i,e.orig[i]);e.complete.call(d)}return!1}e.duration==Infinity?this.now=b:(h=b-this.startTime,this.state=h/e.duration,this.pos=f.easing[e.animatedProperties[this.prop]](this.state,h,0,1,e.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){var a=f.timers,b=a.length;while(b--)a[b]()||a.splice(b,1);a.length||f.fx.stop()},interval:13,stop:function(){clearInterval(cm),cm=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){f.style(a.elem,"opacity",a.now)},_default:function(a){a.elem.style&&a.elem.style[a.prop]!=null?a.elem.style[a.prop]=(a.prop==="width"||a.prop==="height"?Math.max(0,a.now):a.now)+a.unit:a.elem[a.prop]=a.now}}}),f.expr&&f.expr.filters&&(f.expr.filters.animated=function(a){return f.grep(f.timers,function(b){return a===b.elem}).length});var cu=/^t(?:able|d|h)$/i,cv=/^(?:body|html)$/i;"getBoundingClientRect"in c.documentElement?f.fn.offset=function(a){var b=this[0],c;if(a)return this.each(function(b){f.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return f.offset.bodyOffset(b);try{c=b.getBoundingClientRect()}catch(d){}var e=b.ownerDocument,g=e.documentElement;if(!c||!f.contains(g,b))return c?{top:c.top,left:c.left}:{top:0,left:0};var h=e.body,i=cw(e),j=g.clientTop||h.clientTop||0,k=g.clientLeft||h.clientLeft||0,l=i.pageYOffset||f.support.boxModel&&g.scrollTop||h.scrollTop,m=i.pageXOffset||f.support.boxModel&&g.scrollLeft||h.scrollLeft,n=c.top+l-j,o=c.left+m-k;return{top:n,left:o}}:f.fn.offset=function(a){var b=this[0];if(a)return this.each(function(b){f.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return f.offset.bodyOffset(b);f.offset.initialize();var c,d=b.offsetParent,e=b,g=b.ownerDocument,h=g.documentElement,i=g.body,j=g.defaultView,k=j?j.getComputedStyle(b,null):b.currentStyle,l=b.offsetTop,m=b.offsetLeft;while((b=b.parentNode)&&b!==i&&b!==h){if(f.offset.supportsFixedPosition&&k.position==="fixed")break;c=j?j.getComputedStyle(b,null):b.currentStyle,l-=b.scrollTop,m-=b.scrollLeft,b===d&&(l+=b.offsetTop,m+=b.offsetLeft,f.offset.doesNotAddBorder&&(!f.offset.doesAddBorderForTableAndCells||!cu.test(b.nodeName))&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),e=d,d=b.offsetParent),f.offset.subtractsBorderForOverflowNotVisible&&c.overflow!=="visible"&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),k=c}if(k.position==="relative"||k.position==="static")l+=i.offsetTop,m+=i.offsetLeft;f.offset.supportsFixedPosition&&k.position==="fixed"&&(l+=Math.max(h.scrollTop,i.scrollTop),m+=Math.max(h.scrollLeft,i.scrollLeft));return{top:l,left:m}},f.offset={initialize:function(){var a=c.body,b=c.createElement("div"),d,e,g,h,i=parseFloat(f.css(a,"marginTop"))||0,j="<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";f.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"}),b.innerHTML=j,a.insertBefore(b,a.firstChild),d=b.firstChild,e=d.firstChild,h=d.nextSibling.firstChild.firstChild,this.doesNotAddBorder=e.offsetTop!==5,this.doesAddBorderForTableAndCells=h.offsetTop===5,e.style.position="fixed",e.style.top="20px",this.supportsFixedPosition=e.offsetTop===20||e.offsetTop===15,e.style.position=e.style.top="",d.style.overflow="hidden",d.style.position="relative",this.subtractsBorderForOverflowNotVisible=e.offsetTop===-5,this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==i,a.removeChild(b),f.offset.initialize=f.noop},bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;f.offset.initialize(),f.offset.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(f.css(a,"marginTop"))||0,c+=parseFloat(f.css(a,"marginLeft"))||0);return{top:b,left:c}},setOffset:function(a,b,c){var d=f.css(a,"position");d==="static"&&(a.style.position="relative");var e=f(a),g=e.offset(),h=f.css(a,"top"),i=f.css(a,"left"),j=(d==="absolute"||d==="fixed")&&f.inArray("auto",[h,i])>-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cv.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cv.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each(["Left","Top"],function(a,c){var d="scroll"+c;f.fn[d]=function(c){var e,g;if(c===b){e=this[0];if(!e)return null;g=cw(e);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:f.support.boxModel&&g.document.documentElement[d]||g.document.body[d]:e[d]}return this.each(function(){g=cw(this),g?g.scrollTo(a?f(g).scrollLeft():c,a?c:f(g).scrollTop()):this[d]=c})}}),f.each(["Height","Width"],function(a,c){var d=c.toLowerCase();f.fn["inner"+c]=function(){return this[0]?parseFloat(f.css(this[0],d,"padding")):null},f.fn["outer"+c]=function(a){return this[0]?parseFloat(f.css(this[0],d,a?"margin":"border")):null},f.fn[d]=function(a){var e=this[0];if(!e)return a==null?null:this;if(f.isFunction(a))return this.each(function(b){var c=f(this);c[d](a.call(this,b,c[d]()))});if(f.isWindow(e)){var g=e.document.documentElement["client"+c];return e.document.compatMode==="CSS1Compat"&&g||e.document.body["client"+c]||g}if(e.nodeType===9)return Math.max(e.documentElement["client"+c],e.body["scroll"+c],e.documentElement["scroll"+c],e.body["offset"+c],e.documentElement["offset"+c]);if(a===b){var h=f.css(e,d),i=parseFloat(h);return f.isNaN(i)?h:i}return this.css(d,typeof a=="string"?a:a+"px")}}),a.jQuery=a.$=f})(window);
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/toolbox/static/js/jquery.autolink.js	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,20 @@
+jQuery.fn.highlight = function (text, o) {
+	return this.each( function(){
+		var replace = o || '<span class="highlight">$1</span>';
+		$(this).html( $(this).html().replace( new RegExp('('+text+'(?![\\w\\s?&.\\/;#~%"=-]*>))', "ig"), replace) );
+	});
+}
+
+jQuery.fn.autolink = function () {
+	return this.each( function(){
+		var re = /((http|https|ftp):\/\/[\w?=&.\/-;#~%-]+(?![\w\s?&.\/;#~%"=-]*>)[^.])/g;
+		$(this).html( $(this).html().replace(re, '<a href="$1">$1</a> ') );
+	});
+}
+
+jQuery.fn.mailto = function () {
+	return this.each( function() {
+		var re = /(([a-z0-9*._+]){1,}\@(([a-z0-9]+[-]?){1,}[a-z0-9]+\.){1,}([a-z]{2,4}|museum)(?![\w\s?&.\/;#~%"=-]*>))/g
+		$(this).html( $(this).html().replace( re, '<a href="mailto:$1">$1</a>' ) );
+	});
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/toolbox/static/js/jquery.jeditable.js	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,549 @@
+/*
+ * Jeditable - jQuery in place edit plugin
+ *
+ * Copyright (c) 2006-2009 Mika Tuupola, Dylan Verheul
+ *
+ * Licensed under the MIT license:
+ *   http://www.opensource.org/licenses/mit-license.php
+ *
+ * Project home:
+ *   http://www.appelsiini.net/projects/jeditable
+ *
+ * Based on editable by Dylan Verheul <dylan_at_dyve.net>:
+ *    http://www.dyve.net/jquery/?editable
+ *
+ */
+
+/**
+  * Version 1.7.2-dev
+  *
+  * ** means there is basic unit tests for this parameter. 
+  *
+  * @name  Jeditable
+  * @type  jQuery
+  * @param String  target             (POST) URL or function to send edited content to **
+  * @param Hash    options            additional options 
+  * @param String  options[method]    method to use to send edited content (POST or PUT) **
+  * @param Function options[callback] Function to run after submitting edited content **
+  * @param String  options[name]      POST parameter name of edited content
+  * @param String  options[id]        POST parameter name of edited div id
+  * @param Hash    options[submitdata] Extra parameters to send when submitting edited content.
+  * @param String  options[type]      text, textarea or select (or any 3rd party input type) **
+  * @param Integer options[rows]      number of rows if using textarea ** 
+  * @param Integer options[cols]      number of columns if using textarea **
+  * @param Mixed   options[height]    'auto', 'none' or height in pixels **
+  * @param Mixed   options[width]     'auto', 'none' or width in pixels **
+  * @param String  options[loadurl]   URL to fetch input content before editing **
+  * @param String  options[loadtype]  Request type for load url. Should be GET or POST.
+  * @param String  options[loadtext]  Text to display while loading external content.
+  * @param Mixed   options[loaddata]  Extra parameters to pass when fetching content before editing.
+  * @param Mixed   options[data]      Or content given as paramameter. String or function.**
+  * @param String  options[indicator] indicator html to show when saving
+  * @param String  options[tooltip]   optional tooltip text via title attribute **
+  * @param String  options[event]     jQuery event such as 'click' of 'dblclick' **
+  * @param String  options[submit]    submit button value, empty means no button **
+  * @param String  options[cancel]    cancel button value, empty means no button **
+  * @param String  options[cssclass]  CSS class to apply to input form. 'inherit' to copy from parent. **
+  * @param String  options[style]     Style to apply to input form 'inherit' to copy from parent. **
+  * @param String  options[select]    true or false, when true text is highlighted ??
+  * @param String  options[placeholder] Placeholder text or html to insert when element is empty. **
+  * @param String  options[onblur]    'cancel', 'submit', 'ignore' or function ??
+  *             
+  * @param Function options[onsubmit] function(settings, original) { ... } called before submit
+  * @param Function options[onreset]  function(settings, original) { ... } called before reset
+  * @param Function options[onerror]  function(settings, original, xhr) { ... } called on error
+  *             
+  * @param Hash    options[ajaxoptions]  jQuery Ajax options. See docs.jquery.com.
+  *             
+  */
+
+(function($) {
+
+    $.fn.editable = function(target, options) {
+            
+        if ('disable' == target) {
+            $(this).data('disabled.editable', true);
+            return;
+        }
+        if ('enable' == target) {
+            $(this).data('disabled.editable', false);
+            return;
+        }
+        if ('destroy' == target) {
+            $(this)
+                .unbind($(this).data('event.editable'))
+                .removeData('disabled.editable')
+                .removeData('event.editable');
+            return;
+        }
+        
+        var settings = $.extend({}, $.fn.editable.defaults, {target:target}, options);
+        
+        /* setup some functions */
+        var plugin   = $.editable.types[settings.type].plugin || function() { };
+        var submit   = $.editable.types[settings.type].submit || function() { };
+        var buttons  = $.editable.types[settings.type].buttons 
+                    || $.editable.types['defaults'].buttons;
+        var content  = $.editable.types[settings.type].content 
+                    || $.editable.types['defaults'].content;
+        var element  = $.editable.types[settings.type].element 
+                    || $.editable.types['defaults'].element;
+        var reset    = $.editable.types[settings.type].reset 
+                    || $.editable.types['defaults'].reset;
+        var callback = settings.callback || function() { };
+        var beforeedit   = settings.beforeedit   || function() { };
+        var onedit   = settings.onedit   || function() { };
+        var onsubmit = settings.onsubmit || function() { };
+        var onreset  = settings.onreset  || function() { };
+        var onerror  = settings.onerror  || reset;
+          
+        /* Show tooltip. */
+        if (settings.tooltip) {
+            $(this).attr('title', settings.tooltip);
+        }
+        
+        settings.autowidth  = 'auto' == settings.width;
+        settings.autoheight = 'auto' == settings.height;
+        
+        return this.each(function() {
+                        
+            /* Save this to self because this changes when scope changes. */
+            var self = this;  
+                   
+            /* Inlined block elements lose their width and height after first edit. */
+            /* Save them for later use as workaround. */
+            var savedwidth  = $(self).width();
+            var savedheight = $(self).height();
+
+            /* Save so it can be later used by $.editable('destroy') */
+            $(this).data('event.editable', settings.event);
+            
+            /* If element is empty add something clickable (if requested) */
+            if (!$.trim($(this).html())) {
+                $(this).html(settings.placeholder);
+            }
+            
+            $(this).bind(settings.event, function(e) {
+                
+                /* Abort if element is disabled. */
+                if (true === $(this).data('disabled.editable')) {
+                    return;
+                }
+                
+                /* Prevent throwing an exeption if edit field is clicked again. */
+                if (self.editing) {
+                    return;
+                }
+                
+                /* Abort if onedit hook returns false. */
+                if (false === beforeedit.apply(this, [settings, self, e])) {
+                   return;
+                }
+                
+                /* Prevent default action and bubbling. */
+                e.preventDefault();
+                e.stopPropagation();
+                
+                /* Remove tooltip. */
+                if (settings.tooltip) {
+                    $(self).removeAttr('title');
+                }
+                
+                /* Figure out how wide and tall we are, saved width and height. */
+                /* Workaround for http://dev.jquery.com/ticket/2190 */
+                if (0 == $(self).width()) {
+                    settings.width  = savedwidth;
+                    settings.height = savedheight;
+                } else {
+                    if (settings.width != 'none') {
+                        settings.width = 
+                            settings.autowidth ? $(self).width()  : settings.width;
+                    }
+                    if (settings.height != 'none') {
+                        settings.height = 
+                            settings.autoheight ? $(self).height() : settings.height;
+                    }
+                }
+                
+                /* Remove placeholder text, replace is here because of IE. */
+                if ($(this).html().toLowerCase().replace(/(;|"|\/)/g, '') == 
+                    settings.placeholder.toLowerCase().replace(/(;|"|\/)/g, '')) {
+                        $(this).html('');
+                }
+                                
+                self.editing    = true;
+                self.revert     = $(self).text().replace(/\s+/g, " ");
+                $(self).html('');
+
+                /* Create the form object. */
+                var form = $('<form />');
+                
+                /* Apply css or style or both. */
+                if (settings.cssclass) {
+                    if ('inherit' == settings.cssclass) {
+                        form.attr('class', $(self).attr('class'));
+                    } else {
+                        form.attr('class', settings.cssclass);
+                    }
+                }
+
+                if (settings.style) {
+                    if ('inherit' == settings.style) {
+                        form.attr('style', $(self).attr('style'));
+                        /* IE needs the second line or display wont be inherited. */
+                        form.css('display', $(self).css('display'));                
+                    } else {
+                        form.attr('style', settings.style);
+                    }
+                }
+
+                /* Add main input element to form and store it in input. */
+                var input = element.apply(form, [settings, self]);
+
+                /* Set input content via POST, GET, given data or existing value. */
+                var input_content;
+                
+                if (settings.loadurl) {
+                    var t = setTimeout(function() {
+                        input.disabled = true;
+                        content.apply(form, [settings.loadtext, settings, self]);
+                    }, 100);
+
+                    var loaddata = {};
+                    loaddata[settings.id] = self.id;
+                    if ($.isFunction(settings.loaddata)) {
+                        $.extend(loaddata, settings.loaddata.apply(self, [self.revert, settings]));
+                    } else {
+                        $.extend(loaddata, settings.loaddata);
+                    }
+                    $.ajax({
+                       type : settings.loadtype,
+                       url  : settings.loadurl,
+                       data : loaddata,
+                       async : false,
+                       success: function(result) {
+                          window.clearTimeout(t);
+                          input_content = result;
+                          input.disabled = false;
+                       }
+                    });
+                } else if (settings.data) {
+                    input_content = settings.data;
+                    if ($.isFunction(settings.data)) {
+                        input_content = settings.data.apply(self, [self.revert, settings]);
+                    }
+                } else {
+                    input_content = self.revert; 
+                }
+                content.apply(form, [input_content, settings, self]);
+
+                input.attr('name', settings.name);
+        
+                /* Add buttons to the form. */
+                buttons.apply(form, [settings, self]);
+         
+                /* Add created form to self. */
+                $(self).append(form);
+         
+                /* Attach 3rd party plugin if requested. */
+                plugin.apply(form, [settings, self]);
+                
+                onedit.apply(this, [settings, self, e])
+                
+                /* Focus to first visible form element. */
+                $(':input:visible:enabled:first', form).focus();
+
+                /* Highlight input contents when requested. */
+                if (settings.select) {
+                    input.select();
+                }
+        
+                /* discard changes if pressing esc */
+                input.keydown(function(e) {
+                    if (e.keyCode == 27) {
+                        e.preventDefault();
+                        reset.apply(form, [settings, self]);
+                    }
+                });
+
+                /* Discard, submit or nothing with changes when clicking outside. */
+                /* Do nothing is usable when navigating with tab. */
+                var t;
+                if ('cancel' == settings.onblur) {
+                    input.blur(function(e) {
+                        /* Prevent canceling if submit was clicked. */
+                        t = setTimeout(function() {
+                            reset.apply(form, [settings, self]);
+                        }, 500);
+                    });
+                } else if ('submit' == settings.onblur) {
+                    input.blur(function(e) {
+                        /* Prevent double submit if submit was clicked. */
+                        t = setTimeout(function() {
+                            form.submit();
+                        }, 200);
+                    });
+                } else if ($.isFunction(settings.onblur)) {
+                    input.blur(function(e) {
+                        settings.onblur.apply(self, [input.val(), settings]);
+                    });
+                } else {
+                    input.blur(function(e) {
+                      /* TODO: maybe something here */
+                    });
+                }
+
+                form.submit(function(e) {
+
+                    if (t) { 
+                        clearTimeout(t);
+                    }
+
+                    /* Do no submit. */
+                    e.preventDefault(); 
+            
+                    /* Call before submit hook. */
+                    /* If it returns false abort submitting. */                    
+                    if (false !== onsubmit.apply(form, [settings, self])) { 
+                        /* Custom inputs call before submit hook. */
+                        /* If it returns false abort submitting. */
+                        if (false !== submit.apply(form, [settings, self])) { 
+
+                          /* Check if given target is function */
+                          if ($.isFunction(settings.target)) {
+                              var str = settings.target.apply(self, [input.val(), settings]);
+                              $(self).html(str);
+                              self.editing = false;
+                              callback.apply(self, [self.innerHTML, settings]);
+                              /* TODO: this is not dry */                              
+                              if (!$.trim($(self).html())) {
+                                  $(self).html(settings.placeholder);
+                              }
+                          } else {
+                              /* Add edited content and id of edited element to POST. */
+                              var submitdata = {};
+                              submitdata[settings.name] = input.val();
+                              submitdata[settings.id] = self.id;
+                              /* Add extra data to be POST:ed. */
+                              if ($.isFunction(settings.submitdata)) {
+                                  $.extend(submitdata, settings.submitdata.apply(self, [self.revert, settings]));
+                              } else {
+                                  $.extend(submitdata, settings.submitdata);
+                              }
+
+                              /* Quick and dirty PUT support. */
+                              if ('PUT' == settings.method) {
+                                  submitdata['_method'] = 'put';
+                              }
+
+                              /* Show the saving indicator. */
+                              $(self).html(settings.indicator);
+                              
+                              /* Defaults for ajaxoptions. */
+                              var ajaxoptions = {
+                                  type    : 'POST',
+                                  data    : submitdata,
+                                  dataType: 'html',
+                                  url     : settings.target,
+                                  success : function(result, status) {
+                                      if (ajaxoptions.dataType == 'html') {
+                                        $(self).html(result);
+                                      }
+                                      self.editing = false;
+                                      callback.apply(self, [result, settings]);
+                                      if (!$.trim($(self).html())) {
+                                          $(self).html(settings.placeholder);
+                                      }
+                                  },
+                                  error   : function(xhr, status, error) {
+                                      onerror.apply(form, [settings, self, xhr]);
+                                  }
+                              };
+                              
+                              /* Override with what is given in settings.ajaxoptions. */
+                              $.extend(ajaxoptions, settings.ajaxoptions);   
+                              $.ajax(ajaxoptions);          
+                              
+                            }
+                        }
+                    }
+                    
+                    /* Show tooltip again. */
+                    $(self).attr('title', settings.tooltip);
+                    
+                    return false;
+                });
+            });
+            
+            /* Privileged methods */
+            this.reset = function(form) {
+                /* Prevent calling reset twice when blurring. */
+                if (this.editing) {
+                    /* Before reset hook, if it returns false abort reseting. */
+                    if (false !== onreset.apply(form, [settings, self])) { 
+                        $(self).html(self.revert);
+                        self.editing   = false;
+                        if (!$.trim($(self).html())) {
+                            $(self).html(settings.placeholder);
+                        }
+                        /* Show tooltip again. */
+                        if (settings.tooltip) {
+                            $(self).attr('title', settings.tooltip);                
+                        }
+                    }                    
+                }
+            };            
+        });
+
+    };
+
+
+    $.editable = {
+        types: {
+            defaults: {
+                element : function(settings, original) {
+                    var input = $('<input type="hidden"></input>');                
+                    $(this).append(input);
+                    return(input);
+                },
+                content : function(string, settings, original) {
+                    $(':input:first', this).val(string);
+                },
+                reset : function(settings, original) {
+                  original.reset(this);
+                },
+                buttons : function(settings, original) {
+                    var form = this;
+                    if (settings.submit) {
+                        /* If given html string use that. */
+                        if (settings.submit.match(/>$/)) {
+                            var submit = $(settings.submit).click(function() {
+                                if (submit.attr("type") != "submit") {
+                                    form.submit();
+                                }
+                            });
+                        /* Otherwise use button with given string as text. */
+                        } else {
+                            var submit = $('<button type="submit" />');
+                            submit.html(settings.submit);                            
+                        }
+                        $(this).append(submit);
+                    }
+                    if (settings.cancel) {
+                        /* If given html string use that. */
+                        if (settings.cancel.match(/>$/)) {
+                            var cancel = $(settings.cancel);
+                        /* otherwise use button with given string as text */
+                        } else {
+                            var cancel = $('<button type="cancel" />');
+                            cancel.html(settings.cancel);
+                        }
+                        $(this).append(cancel);
+
+                        $(cancel).click(function(event) {
+                            if ($.isFunction($.editable.types[settings.type].reset)) {
+                                var reset = $.editable.types[settings.type].reset;                                                                
+                            } else {
+                                var reset = $.editable.types['defaults'].reset;                                
+                            }
+                            reset.apply(form, [settings, original]);
+                            return false;
+                        });
+                    }
+                }
+            },
+            text: {
+                element : function(settings, original) {
+                    var input = $('<input />');
+                    if (settings.width  != 'none') { input.attr('width', settings.width);  }
+                    if (settings.height != 'none') { input.attr('height', settings.height); }
+                    /* https://bugzilla.mozilla.org/show_bug.cgi?id=236791 */
+                    //input[0].setAttribute('autocomplete','off');
+                    input.attr('autocomplete','off');
+                    $(this).append(input);
+                    return(input);
+                }
+            },
+            textarea: {
+                element : function(settings, original) {
+                    var textarea = $('<textarea />');
+                    if (settings.rows) {
+                        textarea.attr('rows', settings.rows);
+                    } else if (settings.height != "none") {
+                        textarea.height(settings.height);
+                    }
+                    if (settings.cols) {
+                        textarea.attr('cols', settings.cols);
+                    } else if (settings.width != "none") {
+                        textarea.width(settings.width);
+                    }
+                    $(this).append(textarea);
+                    return(textarea);
+                }
+            },
+            select: {
+               element : function(settings, original) {
+                    var select = $('<select />');
+                    $(this).append(select);
+                    return(select);
+                },
+                content : function(data, settings, original) {
+                    /* If it is string assume it is json. */
+                    if (String == data.constructor) {      
+                        eval ('var json = ' + data);
+                    } else {
+                    /* Otherwise assume it is a hash already. */
+                        var json = data;
+                    }
+                    for (var key in json) {
+                        if (!json.hasOwnProperty(key)) {
+                            continue;
+                        }
+                        if ('selected' == key) {
+                            continue;
+                        } 
+                        var option = $('<option />').val(key).append(json[key]);
+                        $('select', this).append(option);    
+                    }                    
+                    /* Loop option again to set selected. IE needed this... */ 
+                    $('select', this).children().each(function() {
+                        if ($(this).val() == json['selected'] || 
+                            $(this).text() == $.trim(original.revert)) {
+                                $(this).attr('selected', 'selected');
+                        }
+                    });
+                    /* Submit on change if no submit button defined. */
+                    if (!settings.submit) {
+                        var form = this;
+                        $('select', this).change(function() {
+                            form.submit();
+                        });
+                    }
+                }
+            }
+        },
+
+        /* Add new input type */
+        addInputType: function(name, input) {
+            $.editable.types[name] = input;
+        }
+    };
+
+    /* Publicly accessible defaults. */
+    $.fn.editable.defaults = {
+        name       : 'value',
+        id         : 'id',
+        type       : 'text',
+        width      : 'auto',
+        height     : 'auto',
+        event      : 'click.editable',
+        onblur     : 'cancel',
+        loadtype   : 'GET',
+        loadtext   : 'Loading...',
+        placeholder: 'Click to edit',
+        loaddata   : {},
+        submitdata : {},
+        ajaxoptions: {}
+    };
+
+})(jQuery);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/toolbox/static/js/jquery.js	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,19 @@
+/*
+ * jQuery JavaScript Library v1.3.2
+ * http://jquery.com/
+ *
+ * Copyright (c) 2009 John Resig
+ * Dual licensed under the MIT and GPL licenses.
+ * http://docs.jquery.com/License
+ *
+ * Date: 2009-02-19 17:34:21 -0500 (Thu, 19 Feb 2009)
+ * Revision: 6246
+ */
+(function(){var l=this,g,y=l.jQuery,p=l.$,o=l.jQuery=l.$=function(E,F){return new o.fn.init(E,F)},D=/^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/,f=/^.[^:#\[\.,]*$/;o.fn=o.prototype={init:function(E,H){E=E||document;if(E.nodeType){this[0]=E;this.length=1;this.context=E;return this}if(typeof E==="string"){var G=D.exec(E);if(G&&(G[1]||!H)){if(G[1]){E=o.clean([G[1]],H)}else{var I=document.getElementById(G[3]);if(I&&I.id!=G[3]){return o().find(E)}var F=o(I||[]);F.context=document;F.selector=E;return F}}else{return o(H).find(E)}}else{if(o.isFunction(E)){return o(document).ready(E)}}if(E.selector&&E.context){this.selector=E.selector;this.context=E.context}return this.setArray(o.isArray(E)?E:o.makeArray(E))},selector:"",jquery:"1.3.2",size:function(){return this.length},get:function(E){return E===g?Array.prototype.slice.call(this):this[E]},pushStack:function(F,H,E){var G=o(F);G.prevObject=this;G.context=this.context;if(H==="find"){G.selector=this.selector+(this.selector?" ":"")+E}else{if(H){G.selector=this.selector+"."+H+"("+E+")"}}return G},setArray:function(E){this.length=0;Array.prototype.push.apply(this,E);return this},each:function(F,E){return o.each(this,F,E)},index:function(E){return o.inArray(E&&E.jquery?E[0]:E,this)},attr:function(F,H,G){var E=F;if(typeof F==="string"){if(H===g){return this[0]&&o[G||"attr"](this[0],F)}else{E={};E[F]=H}}return this.each(function(I){for(F in E){o.attr(G?this.style:this,F,o.prop(this,E[F],G,I,F))}})},css:function(E,F){if((E=="width"||E=="height")&&parseFloat(F)<0){F=g}return this.attr(E,F,"curCSS")},text:function(F){if(typeof F!=="object"&&F!=null){return this.empty().append((this[0]&&this[0].ownerDocument||document).createTextNode(F))}var E="";o.each(F||this,function(){o.each(this.childNodes,function(){if(this.nodeType!=8){E+=this.nodeType!=1?this.nodeValue:o.fn.text([this])}})});return E},wrapAll:function(E){if(this[0]){var F=o(E,this[0].ownerDocument).clone();if(this[0].parentNode){F.insertBefore(this[0])}F.map(function(){var G=this;while(G.firstChild){G=G.firstChild}return G}).append(this)}return this},wrapInner:function(E){return this.each(function(){o(this).contents().wrapAll(E)})},wrap:function(E){return this.each(function(){o(this).wrapAll(E)})},append:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.appendChild(E)}})},prepend:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.insertBefore(E,this.firstChild)}})},before:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this)})},after:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this.nextSibling)})},end:function(){return this.prevObject||o([])},push:[].push,sort:[].sort,splice:[].splice,find:function(E){if(this.length===1){var F=this.pushStack([],"find",E);F.length=0;o.find(E,this[0],F);return F}else{return this.pushStack(o.unique(o.map(this,function(G){return o.find(E,G)})),"find",E)}},clone:function(G){var E=this.map(function(){if(!o.support.noCloneEvent&&!o.isXMLDoc(this)){var I=this.outerHTML;if(!I){var J=this.ownerDocument.createElement("div");J.appendChild(this.cloneNode(true));I=J.innerHTML}return o.clean([I.replace(/ jQuery\d+="(?:\d+|null)"/g,"").replace(/^\s*/,"")])[0]}else{return this.cloneNode(true)}});if(G===true){var H=this.find("*").andSelf(),F=0;E.find("*").andSelf().each(function(){if(this.nodeName!==H[F].nodeName){return}var I=o.data(H[F],"events");for(var K in I){for(var J in I[K]){o.event.add(this,K,I[K][J],I[K][J].data)}}F++})}return E},filter:function(E){return this.pushStack(o.isFunction(E)&&o.grep(this,function(G,F){return E.call(G,F)})||o.multiFilter(E,o.grep(this,function(F){return F.nodeType===1})),"filter",E)},closest:function(E){var G=o.expr.match.POS.test(E)?o(E):null,F=0;return this.map(function(){var H=this;while(H&&H.ownerDocument){if(G?G.index(H)>-1:o(H).is(E)){o.data(H,"closest",F);return H}H=H.parentNode;F++}})},not:function(E){if(typeof E==="string"){if(f.test(E)){return this.pushStack(o.multiFilter(E,this,true),"not",E)}else{E=o.multiFilter(E,this)}}var F=E.length&&E[E.length-1]!==g&&!E.nodeType;return this.filter(function(){return F?o.inArray(this,E)<0:this!=E})},add:function(E){return this.pushStack(o.unique(o.merge(this.get(),typeof E==="string"?o(E):o.makeArray(E))))},is:function(E){return !!E&&o.multiFilter(E,this).length>0},hasClass:function(E){return !!E&&this.is("."+E)},val:function(K){if(K===g){var E=this[0];if(E){if(o.nodeName(E,"option")){return(E.attributes.value||{}).specified?E.value:E.text}if(o.nodeName(E,"select")){var I=E.selectedIndex,L=[],M=E.options,H=E.type=="select-one";if(I<0){return null}for(var F=H?I:0,J=H?I+1:M.length;F<J;F++){var G=M[F];if(G.selected){K=o(G).val();if(H){return K}L.push(K)}}return L}return(E.value||"").replace(/\r/g,"")}return g}if(typeof K==="number"){K+=""}return this.each(function(){if(this.nodeType!=1){return}if(o.isArray(K)&&/radio|checkbox/.test(this.type)){this.checked=(o.inArray(this.value,K)>=0||o.inArray(this.name,K)>=0)}else{if(o.nodeName(this,"select")){var N=o.makeArray(K);o("option",this).each(function(){this.selected=(o.inArray(this.value,N)>=0||o.inArray(this.text,N)>=0)});if(!N.length){this.selectedIndex=-1}}else{this.value=K}}})},html:function(E){return E===g?(this[0]?this[0].innerHTML.replace(/ jQuery\d+="(?:\d+|null)"/g,""):null):this.empty().append(E)},replaceWith:function(E){return this.after(E).remove()},eq:function(E){return this.slice(E,+E+1)},slice:function(){return this.pushStack(Array.prototype.slice.apply(this,arguments),"slice",Array.prototype.slice.call(arguments).join(","))},map:function(E){return this.pushStack(o.map(this,function(G,F){return E.call(G,F,G)}))},andSelf:function(){return this.add(this.prevObject)},domManip:function(J,M,L){if(this[0]){var I=(this[0].ownerDocument||this[0]).createDocumentFragment(),F=o.clean(J,(this[0].ownerDocument||this[0]),I),H=I.firstChild;if(H){for(var G=0,E=this.length;G<E;G++){L.call(K(this[G],H),this.length>1||G>0?I.cloneNode(true):I)}}if(F){o.each(F,z)}}return this;function K(N,O){return M&&o.nodeName(N,"table")&&o.nodeName(O,"tr")?(N.getElementsByTagName("tbody")[0]||N.appendChild(N.ownerDocument.createElement("tbody"))):N}}};o.fn.init.prototype=o.fn;function z(E,F){if(F.src){o.ajax({url:F.src,async:false,dataType:"script"})}else{o.globalEval(F.text||F.textContent||F.innerHTML||"")}if(F.parentNode){F.parentNode.removeChild(F)}}function e(){return +new Date}o.extend=o.fn.extend=function(){var J=arguments[0]||{},H=1,I=arguments.length,E=false,G;if(typeof J==="boolean"){E=J;J=arguments[1]||{};H=2}if(typeof J!=="object"&&!o.isFunction(J)){J={}}if(I==H){J=this;--H}for(;H<I;H++){if((G=arguments[H])!=null){for(var F in G){var K=J[F],L=G[F];if(J===L){continue}if(E&&L&&typeof L==="object"&&!L.nodeType){J[F]=o.extend(E,K||(L.length!=null?[]:{}),L)}else{if(L!==g){J[F]=L}}}}}return J};var b=/z-?index|font-?weight|opacity|zoom|line-?height/i,q=document.defaultView||{},s=Object.prototype.toString;o.extend({noConflict:function(E){l.$=p;if(E){l.jQuery=y}return o},isFunction:function(E){return s.call(E)==="[object Function]"},isArray:function(E){return s.call(E)==="[object Array]"},isXMLDoc:function(E){return E.nodeType===9&&E.documentElement.nodeName!=="HTML"||!!E.ownerDocument&&o.isXMLDoc(E.ownerDocument)},globalEval:function(G){if(G&&/\S/.test(G)){var F=document.getElementsByTagName("head")[0]||document.documentElement,E=document.createElement("script");E.type="text/javascript";if(o.support.scriptEval){E.appendChild(document.createTextNode(G))}else{E.text=G}F.insertBefore(E,F.firstChild);F.removeChild(E)}},nodeName:function(F,E){return F.nodeName&&F.nodeName.toUpperCase()==E.toUpperCase()},each:function(G,K,F){var E,H=0,I=G.length;if(F){if(I===g){for(E in G){if(K.apply(G[E],F)===false){break}}}else{for(;H<I;){if(K.apply(G[H++],F)===false){break}}}}else{if(I===g){for(E in G){if(K.call(G[E],E,G[E])===false){break}}}else{for(var J=G[0];H<I&&K.call(J,H,J)!==false;J=G[++H]){}}}return G},prop:function(H,I,G,F,E){if(o.isFunction(I)){I=I.call(H,F)}return typeof I==="number"&&G=="curCSS"&&!b.test(E)?I+"px":I},className:{add:function(E,F){o.each((F||"").split(/\s+/),function(G,H){if(E.nodeType==1&&!o.className.has(E.className,H)){E.className+=(E.className?" ":"")+H}})},remove:function(E,F){if(E.nodeType==1){E.className=F!==g?o.grep(E.className.split(/\s+/),function(G){return !o.className.has(F,G)}).join(" "):""}},has:function(F,E){return F&&o.inArray(E,(F.className||F).toString().split(/\s+/))>-1}},swap:function(H,G,I){var E={};for(var F in G){E[F]=H.style[F];H.style[F]=G[F]}I.call(H);for(var F in G){H.style[F]=E[F]}},css:function(H,F,J,E){if(F=="width"||F=="height"){var L,G={position:"absolute",visibility:"hidden",display:"block"},K=F=="width"?["Left","Right"]:["Top","Bottom"];function I(){L=F=="width"?H.offsetWidth:H.offsetHeight;if(E==="border"){return}o.each(K,function(){if(!E){L-=parseFloat(o.curCSS(H,"padding"+this,true))||0}if(E==="margin"){L+=parseFloat(o.curCSS(H,"margin"+this,true))||0}else{L-=parseFloat(o.curCSS(H,"border"+this+"Width",true))||0}})}if(H.offsetWidth!==0){I()}else{o.swap(H,G,I)}return Math.max(0,Math.round(L))}return o.curCSS(H,F,J)},curCSS:function(I,F,G){var L,E=I.style;if(F=="opacity"&&!o.support.opacity){L=o.attr(E,"opacity");return L==""?"1":L}if(F.match(/float/i)){F=w}if(!G&&E&&E[F]){L=E[F]}else{if(q.getComputedStyle){if(F.match(/float/i)){F="float"}F=F.replace(/([A-Z])/g,"-$1").toLowerCase();var M=q.getComputedStyle(I,null);if(M){L=M.getPropertyValue(F)}if(F=="opacity"&&L==""){L="1"}}else{if(I.currentStyle){var J=F.replace(/\-(\w)/g,function(N,O){return O.toUpperCase()});L=I.currentStyle[F]||I.currentStyle[J];if(!/^\d+(px)?$/i.test(L)&&/^\d/.test(L)){var H=E.left,K=I.runtimeStyle.left;I.runtimeStyle.left=I.currentStyle.left;E.left=L||0;L=E.pixelLeft+"px";E.left=H;I.runtimeStyle.left=K}}}}return L},clean:function(F,K,I){K=K||document;if(typeof K.createElement==="undefined"){K=K.ownerDocument||K[0]&&K[0].ownerDocument||document}if(!I&&F.length===1&&typeof F[0]==="string"){var H=/^<(\w+)\s*\/?>$/.exec(F[0]);if(H){return[K.createElement(H[1])]}}var G=[],E=[],L=K.createElement("div");o.each(F,function(P,S){if(typeof S==="number"){S+=""}if(!S){return}if(typeof S==="string"){S=S.replace(/(<(\w+)[^>]*?)\/>/g,function(U,V,T){return T.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i)?U:V+"></"+T+">"});var O=S.replace(/^\s+/,"").substring(0,10).toLowerCase();var Q=!O.indexOf("<opt")&&[1,"<select multiple='multiple'>","</select>"]||!O.indexOf("<leg")&&[1,"<fieldset>","</fieldset>"]||O.match(/^<(thead|tbody|tfoot|colg|cap)/)&&[1,"<table>","</table>"]||!O.indexOf("<tr")&&[2,"<table><tbody>","</tbody></table>"]||(!O.indexOf("<td")||!O.indexOf("<th"))&&[3,"<table><tbody><tr>","</tr></tbody></table>"]||!O.indexOf("<col")&&[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"]||!o.support.htmlSerialize&&[1,"div<div>","</div>"]||[0,"",""];L.innerHTML=Q[1]+S+Q[2];while(Q[0]--){L=L.lastChild}if(!o.support.tbody){var R=/<tbody/i.test(S),N=!O.indexOf("<table")&&!R?L.firstChild&&L.firstChild.childNodes:Q[1]=="<table>"&&!R?L.childNodes:[];for(var M=N.length-1;M>=0;--M){if(o.nodeName(N[M],"tbody")&&!N[M].childNodes.length){N[M].parentNode.removeChild(N[M])}}}if(!o.support.leadingWhitespace&&/^\s/.test(S)){L.insertBefore(K.createTextNode(S.match(/^\s*/)[0]),L.firstChild)}S=o.makeArray(L.childNodes)}if(S.nodeType){G.push(S)}else{G=o.merge(G,S)}});if(I){for(var J=0;G[J];J++){if(o.nodeName(G[J],"script")&&(!G[J].type||G[J].type.toLowerCase()==="text/javascript")){E.push(G[J].parentNode?G[J].parentNode.removeChild(G[J]):G[J])}else{if(G[J].nodeType===1){G.splice.apply(G,[J+1,0].concat(o.makeArray(G[J].getElementsByTagName("script"))))}I.appendChild(G[J])}}return E}return G},attr:function(J,G,K){if(!J||J.nodeType==3||J.nodeType==8){return g}var H=!o.isXMLDoc(J),L=K!==g;G=H&&o.props[G]||G;if(J.tagName){var F=/href|src|style/.test(G);if(G=="selected"&&J.parentNode){J.parentNode.selectedIndex}if(G in J&&H&&!F){if(L){if(G=="type"&&o.nodeName(J,"input")&&J.parentNode){throw"type property can't be changed"}J[G]=K}if(o.nodeName(J,"form")&&J.getAttributeNode(G)){return J.getAttributeNode(G).nodeValue}if(G=="tabIndex"){var I=J.getAttributeNode("tabIndex");return I&&I.specified?I.value:J.nodeName.match(/(button|input|object|select|textarea)/i)?0:J.nodeName.match(/^(a|area)$/i)&&J.href?0:g}return J[G]}if(!o.support.style&&H&&G=="style"){return o.attr(J.style,"cssText",K)}if(L){J.setAttribute(G,""+K)}var E=!o.support.hrefNormalized&&H&&F?J.getAttribute(G,2):J.getAttribute(G);return E===null?g:E}if(!o.support.opacity&&G=="opacity"){if(L){J.zoom=1;J.filter=(J.filter||"").replace(/alpha\([^)]*\)/,"")+(parseInt(K)+""=="NaN"?"":"alpha(opacity="+K*100+")")}return J.filter&&J.filter.indexOf("opacity=")>=0?(parseFloat(J.filter.match(/opacity=([^)]*)/)[1])/100)+"":""}G=G.replace(/-([a-z])/ig,function(M,N){return N.toUpperCase()});if(L){J[G]=K}return J[G]},trim:function(E){return(E||"").replace(/^\s+|\s+$/g,"")},makeArray:function(G){var E=[];if(G!=null){var F=G.length;if(F==null||typeof G==="string"||o.isFunction(G)||G.setInterval){E[0]=G}else{while(F){E[--F]=G[F]}}}return E},inArray:function(G,H){for(var E=0,F=H.length;E<F;E++){if(H[E]===G){return E}}return -1},merge:function(H,E){var F=0,G,I=H.length;if(!o.support.getAll){while((G=E[F++])!=null){if(G.nodeType!=8){H[I++]=G}}}else{while((G=E[F++])!=null){H[I++]=G}}return H},unique:function(K){var F=[],E={};try{for(var G=0,H=K.length;G<H;G++){var J=o.data(K[G]);if(!E[J]){E[J]=true;F.push(K[G])}}}catch(I){F=K}return F},grep:function(F,J,E){var G=[];for(var H=0,I=F.length;H<I;H++){if(!E!=!J(F[H],H)){G.push(F[H])}}return G},map:function(E,J){var F=[];for(var G=0,H=E.length;G<H;G++){var I=J(E[G],G);if(I!=null){F[F.length]=I}}return F.concat.apply([],F)}});var C=navigator.userAgent.toLowerCase();o.browser={version:(C.match(/.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/)||[0,"0"])[1],safari:/webkit/.test(C),opera:/opera/.test(C),msie:/msie/.test(C)&&!/opera/.test(C),mozilla:/mozilla/.test(C)&&!/(compatible|webkit)/.test(C)};o.each({parent:function(E){return E.parentNode},parents:function(E){return o.dir(E,"parentNode")},next:function(E){return o.nth(E,2,"nextSibling")},prev:function(E){return o.nth(E,2,"previousSibling")},nextAll:function(E){return o.dir(E,"nextSibling")},prevAll:function(E){return o.dir(E,"previousSibling")},siblings:function(E){return o.sibling(E.parentNode.firstChild,E)},children:function(E){return o.sibling(E.firstChild)},contents:function(E){return o.nodeName(E,"iframe")?E.contentDocument||E.contentWindow.document:o.makeArray(E.childNodes)}},function(E,F){o.fn[E]=function(G){var H=o.map(this,F);if(G&&typeof G=="string"){H=o.multiFilter(G,H)}return this.pushStack(o.unique(H),E,G)}});o.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(E,F){o.fn[E]=function(G){var J=[],L=o(G);for(var K=0,H=L.length;K<H;K++){var I=(K>0?this.clone(true):this).get();o.fn[F].apply(o(L[K]),I);J=J.concat(I)}return this.pushStack(J,E,G)}});o.each({removeAttr:function(E){o.attr(this,E,"");if(this.nodeType==1){this.removeAttribute(E)}},addClass:function(E){o.className.add(this,E)},removeClass:function(E){o.className.remove(this,E)},toggleClass:function(F,E){if(typeof E!=="boolean"){E=!o.className.has(this,F)}o.className[E?"add":"remove"](this,F)},remove:function(E){if(!E||o.filter(E,[this]).length){o("*",this).add([this]).each(function(){o.event.remove(this);o.removeData(this)});if(this.parentNode){this.parentNode.removeChild(this)}}},empty:function(){o(this).children().remove();while(this.firstChild){this.removeChild(this.firstChild)}}},function(E,F){o.fn[E]=function(){return this.each(F,arguments)}});function j(E,F){return E[0]&&parseInt(o.curCSS(E[0],F,true),10)||0}var h="jQuery"+e(),v=0,A={};o.extend({cache:{},data:function(F,E,G){F=F==l?A:F;var H=F[h];if(!H){H=F[h]=++v}if(E&&!o.cache[H]){o.cache[H]={}}if(G!==g){o.cache[H][E]=G}return E?o.cache[H][E]:H},removeData:function(F,E){F=F==l?A:F;var H=F[h];if(E){if(o.cache[H]){delete o.cache[H][E];E="";for(E in o.cache[H]){break}if(!E){o.removeData(F)}}}else{try{delete F[h]}catch(G){if(F.removeAttribute){F.removeAttribute(h)}}delete o.cache[H]}},queue:function(F,E,H){if(F){E=(E||"fx")+"queue";var G=o.data(F,E);if(!G||o.isArray(H)){G=o.data(F,E,o.makeArray(H))}else{if(H){G.push(H)}}}return G},dequeue:function(H,G){var E=o.queue(H,G),F=E.shift();if(!G||G==="fx"){F=E[0]}if(F!==g){F.call(H)}}});o.fn.extend({data:function(E,G){var H=E.split(".");H[1]=H[1]?"."+H[1]:"";if(G===g){var F=this.triggerHandler("getData"+H[1]+"!",[H[0]]);if(F===g&&this.length){F=o.data(this[0],E)}return F===g&&H[1]?this.data(H[0]):F}else{return this.trigger("setData"+H[1]+"!",[H[0],G]).each(function(){o.data(this,E,G)})}},removeData:function(E){return this.each(function(){o.removeData(this,E)})},queue:function(E,F){if(typeof E!=="string"){F=E;E="fx"}if(F===g){return o.queue(this[0],E)}return this.each(function(){var G=o.queue(this,E,F);if(E=="fx"&&G.length==1){G[0].call(this)}})},dequeue:function(E){return this.each(function(){o.dequeue(this,E)})}});
+/*
+ * Sizzle CSS Selector Engine - v0.9.3
+ *  Copyright 2009, The Dojo Foundation
+ *  Released under the MIT, BSD, and GPL Licenses.
+ *  More information: http://sizzlejs.com/
+ */
+(function(){var R=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?/g,L=0,H=Object.prototype.toString;var F=function(Y,U,ab,ac){ab=ab||[];U=U||document;if(U.nodeType!==1&&U.nodeType!==9){return[]}if(!Y||typeof Y!=="string"){return ab}var Z=[],W,af,ai,T,ad,V,X=true;R.lastIndex=0;while((W=R.exec(Y))!==null){Z.push(W[1]);if(W[2]){V=RegExp.rightContext;break}}if(Z.length>1&&M.exec(Y)){if(Z.length===2&&I.relative[Z[0]]){af=J(Z[0]+Z[1],U)}else{af=I.relative[Z[0]]?[U]:F(Z.shift(),U);while(Z.length){Y=Z.shift();if(I.relative[Y]){Y+=Z.shift()}af=J(Y,af)}}}else{var ae=ac?{expr:Z.pop(),set:E(ac)}:F.find(Z.pop(),Z.length===1&&U.parentNode?U.parentNode:U,Q(U));af=F.filter(ae.expr,ae.set);if(Z.length>0){ai=E(af)}else{X=false}while(Z.length){var ah=Z.pop(),ag=ah;if(!I.relative[ah]){ah=""}else{ag=Z.pop()}if(ag==null){ag=U}I.relative[ah](ai,ag,Q(U))}}if(!ai){ai=af}if(!ai){throw"Syntax error, unrecognized expression: "+(ah||Y)}if(H.call(ai)==="[object Array]"){if(!X){ab.push.apply(ab,ai)}else{if(U.nodeType===1){for(var aa=0;ai[aa]!=null;aa++){if(ai[aa]&&(ai[aa]===true||ai[aa].nodeType===1&&K(U,ai[aa]))){ab.push(af[aa])}}}else{for(var aa=0;ai[aa]!=null;aa++){if(ai[aa]&&ai[aa].nodeType===1){ab.push(af[aa])}}}}}else{E(ai,ab)}if(V){F(V,U,ab,ac);if(G){hasDuplicate=false;ab.sort(G);if(hasDuplicate){for(var aa=1;aa<ab.length;aa++){if(ab[aa]===ab[aa-1]){ab.splice(aa--,1)}}}}}return ab};F.matches=function(T,U){return F(T,null,null,U)};F.find=function(aa,T,ab){var Z,X;if(!aa){return[]}for(var W=0,V=I.order.length;W<V;W++){var Y=I.order[W],X;if((X=I.match[Y].exec(aa))){var U=RegExp.leftContext;if(U.substr(U.length-1)!=="\\"){X[1]=(X[1]||"").replace(/\\/g,"");Z=I.find[Y](X,T,ab);if(Z!=null){aa=aa.replace(I.match[Y],"");break}}}}if(!Z){Z=T.getElementsByTagName("*")}return{set:Z,expr:aa}};F.filter=function(ad,ac,ag,W){var V=ad,ai=[],aa=ac,Y,T,Z=ac&&ac[0]&&Q(ac[0]);while(ad&&ac.length){for(var ab in I.filter){if((Y=I.match[ab].exec(ad))!=null){var U=I.filter[ab],ah,af;T=false;if(aa==ai){ai=[]}if(I.preFilter[ab]){Y=I.preFilter[ab](Y,aa,ag,ai,W,Z);if(!Y){T=ah=true}else{if(Y===true){continue}}}if(Y){for(var X=0;(af=aa[X])!=null;X++){if(af){ah=U(af,Y,X,aa);var ae=W^!!ah;if(ag&&ah!=null){if(ae){T=true}else{aa[X]=false}}else{if(ae){ai.push(af);T=true}}}}}if(ah!==g){if(!ag){aa=ai}ad=ad.replace(I.match[ab],"");if(!T){return[]}break}}}if(ad==V){if(T==null){throw"Syntax error, unrecognized expression: "+ad}else{break}}V=ad}return aa};var I=F.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF_-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF_-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF_-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF_-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*_-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF_-]|\\.)+)(?:\((['"]*)((?:\([^\)]+\)|[^\2\(\)]*)+)\2\))?/},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(T){return T.getAttribute("href")}},relative:{"+":function(aa,T,Z){var X=typeof T==="string",ab=X&&!/\W/.test(T),Y=X&&!ab;if(ab&&!Z){T=T.toUpperCase()}for(var W=0,V=aa.length,U;W<V;W++){if((U=aa[W])){while((U=U.previousSibling)&&U.nodeType!==1){}aa[W]=Y||U&&U.nodeName===T?U||false:U===T}}if(Y){F.filter(T,aa,true)}},">":function(Z,U,aa){var X=typeof U==="string";if(X&&!/\W/.test(U)){U=aa?U:U.toUpperCase();for(var V=0,T=Z.length;V<T;V++){var Y=Z[V];if(Y){var W=Y.parentNode;Z[V]=W.nodeName===U?W:false}}}else{for(var V=0,T=Z.length;V<T;V++){var Y=Z[V];if(Y){Z[V]=X?Y.parentNode:Y.parentNode===U}}if(X){F.filter(U,Z,true)}}},"":function(W,U,Y){var V=L++,T=S;if(!U.match(/\W/)){var X=U=Y?U:U.toUpperCase();T=P}T("parentNode",U,V,W,X,Y)},"~":function(W,U,Y){var V=L++,T=S;if(typeof U==="string"&&!U.match(/\W/)){var X=U=Y?U:U.toUpperCase();T=P}T("previousSibling",U,V,W,X,Y)}},find:{ID:function(U,V,W){if(typeof V.getElementById!=="undefined"&&!W){var T=V.getElementById(U[1]);return T?[T]:[]}},NAME:function(V,Y,Z){if(typeof Y.getElementsByName!=="undefined"){var U=[],X=Y.getElementsByName(V[1]);for(var W=0,T=X.length;W<T;W++){if(X[W].getAttribute("name")===V[1]){U.push(X[W])}}return U.length===0?null:U}},TAG:function(T,U){return U.getElementsByTagName(T[1])}},preFilter:{CLASS:function(W,U,V,T,Z,aa){W=" "+W[1].replace(/\\/g,"")+" ";if(aa){return W}for(var X=0,Y;(Y=U[X])!=null;X++){if(Y){if(Z^(Y.className&&(" "+Y.className+" ").indexOf(W)>=0)){if(!V){T.push(Y)}}else{if(V){U[X]=false}}}}return false},ID:function(T){return T[1].replace(/\\/g,"")},TAG:function(U,T){for(var V=0;T[V]===false;V++){}return T[V]&&Q(T[V])?U[1]:U[1].toUpperCase()},CHILD:function(T){if(T[1]=="nth"){var U=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(T[2]=="even"&&"2n"||T[2]=="odd"&&"2n+1"||!/\D/.test(T[2])&&"0n+"+T[2]||T[2]);T[2]=(U[1]+(U[2]||1))-0;T[3]=U[3]-0}T[0]=L++;return T},ATTR:function(X,U,V,T,Y,Z){var W=X[1].replace(/\\/g,"");if(!Z&&I.attrMap[W]){X[1]=I.attrMap[W]}if(X[2]==="~="){X[4]=" "+X[4]+" "}return X},PSEUDO:function(X,U,V,T,Y){if(X[1]==="not"){if(X[3].match(R).length>1||/^\w/.test(X[3])){X[3]=F(X[3],null,null,U)}else{var W=F.filter(X[3],U,V,true^Y);if(!V){T.push.apply(T,W)}return false}}else{if(I.match.POS.test(X[0])||I.match.CHILD.test(X[0])){return true}}return X},POS:function(T){T.unshift(true);return T}},filters:{enabled:function(T){return T.disabled===false&&T.type!=="hidden"},disabled:function(T){return T.disabled===true},checked:function(T){return T.checked===true},selected:function(T){T.parentNode.selectedIndex;return T.selected===true},parent:function(T){return !!T.firstChild},empty:function(T){return !T.firstChild},has:function(V,U,T){return !!F(T[3],V).length},header:function(T){return/h\d/i.test(T.nodeName)},text:function(T){return"text"===T.type},radio:function(T){return"radio"===T.type},checkbox:function(T){return"checkbox"===T.type},file:function(T){return"file"===T.type},password:function(T){return"password"===T.type},submit:function(T){return"submit"===T.type},image:function(T){return"image"===T.type},reset:function(T){return"reset"===T.type},button:function(T){return"button"===T.type||T.nodeName.toUpperCase()==="BUTTON"},input:function(T){return/input|select|textarea|button/i.test(T.nodeName)}},setFilters:{first:function(U,T){return T===0},last:function(V,U,T,W){return U===W.length-1},even:function(U,T){return T%2===0},odd:function(U,T){return T%2===1},lt:function(V,U,T){return U<T[3]-0},gt:function(V,U,T){return U>T[3]-0},nth:function(V,U,T){return T[3]-0==U},eq:function(V,U,T){return T[3]-0==U}},filter:{PSEUDO:function(Z,V,W,aa){var U=V[1],X=I.filters[U];if(X){return X(Z,W,V,aa)}else{if(U==="contains"){return(Z.textContent||Z.innerText||"").indexOf(V[3])>=0}else{if(U==="not"){var Y=V[3];for(var W=0,T=Y.length;W<T;W++){if(Y[W]===Z){return false}}return true}}}},CHILD:function(T,W){var Z=W[1],U=T;switch(Z){case"only":case"first":while(U=U.previousSibling){if(U.nodeType===1){return false}}if(Z=="first"){return true}U=T;case"last":while(U=U.nextSibling){if(U.nodeType===1){return false}}return true;case"nth":var V=W[2],ac=W[3];if(V==1&&ac==0){return true}var Y=W[0],ab=T.parentNode;if(ab&&(ab.sizcache!==Y||!T.nodeIndex)){var X=0;for(U=ab.firstChild;U;U=U.nextSibling){if(U.nodeType===1){U.nodeIndex=++X}}ab.sizcache=Y}var aa=T.nodeIndex-ac;if(V==0){return aa==0}else{return(aa%V==0&&aa/V>=0)}}},ID:function(U,T){return U.nodeType===1&&U.getAttribute("id")===T},TAG:function(U,T){return(T==="*"&&U.nodeType===1)||U.nodeName===T},CLASS:function(U,T){return(" "+(U.className||U.getAttribute("class"))+" ").indexOf(T)>-1},ATTR:function(Y,W){var V=W[1],T=I.attrHandle[V]?I.attrHandle[V](Y):Y[V]!=null?Y[V]:Y.getAttribute(V),Z=T+"",X=W[2],U=W[4];return T==null?X==="!=":X==="="?Z===U:X==="*="?Z.indexOf(U)>=0:X==="~="?(" "+Z+" ").indexOf(U)>=0:!U?Z&&T!==false:X==="!="?Z!=U:X==="^="?Z.indexOf(U)===0:X==="$="?Z.substr(Z.length-U.length)===U:X==="|="?Z===U||Z.substr(0,U.length+1)===U+"-":false},POS:function(X,U,V,Y){var T=U[2],W=I.setFilters[T];if(W){return W(X,V,U,Y)}}}};var M=I.match.POS;for(var O in I.match){I.match[O]=RegExp(I.match[O].source+/(?![^\[]*\])(?![^\(]*\))/.source)}var E=function(U,T){U=Array.prototype.slice.call(U);if(T){T.push.apply(T,U);return T}return U};try{Array.prototype.slice.call(document.documentElement.childNodes)}catch(N){E=function(X,W){var U=W||[];if(H.call(X)==="[object Array]"){Array.prototype.push.apply(U,X)}else{if(typeof X.length==="number"){for(var V=0,T=X.length;V<T;V++){U.push(X[V])}}else{for(var V=0;X[V];V++){U.push(X[V])}}}return U}}var G;if(document.documentElement.compareDocumentPosition){G=function(U,T){var V=U.compareDocumentPosition(T)&4?-1:U===T?0:1;if(V===0){hasDuplicate=true}return V}}else{if("sourceIndex" in document.documentElement){G=function(U,T){var V=U.sourceIndex-T.sourceIndex;if(V===0){hasDuplicate=true}return V}}else{if(document.createRange){G=function(W,U){var V=W.ownerDocument.createRange(),T=U.ownerDocument.createRange();V.selectNode(W);V.collapse(true);T.selectNode(U);T.collapse(true);var X=V.compareBoundaryPoints(Range.START_TO_END,T);if(X===0){hasDuplicate=true}return X}}}}(function(){var U=document.createElement("form"),V="script"+(new Date).getTime();U.innerHTML="<input name='"+V+"'/>";var T=document.documentElement;T.insertBefore(U,T.firstChild);if(!!document.getElementById(V)){I.find.ID=function(X,Y,Z){if(typeof Y.getElementById!=="undefined"&&!Z){var W=Y.getElementById(X[1]);return W?W.id===X[1]||typeof W.getAttributeNode!=="undefined"&&W.getAttributeNode("id").nodeValue===X[1]?[W]:g:[]}};I.filter.ID=function(Y,W){var X=typeof Y.getAttributeNode!=="undefined"&&Y.getAttributeNode("id");return Y.nodeType===1&&X&&X.nodeValue===W}}T.removeChild(U)})();(function(){var T=document.createElement("div");T.appendChild(document.createComment(""));if(T.getElementsByTagName("*").length>0){I.find.TAG=function(U,Y){var X=Y.getElementsByTagName(U[1]);if(U[1]==="*"){var W=[];for(var V=0;X[V];V++){if(X[V].nodeType===1){W.push(X[V])}}X=W}return X}}T.innerHTML="<a href='#'></a>";if(T.firstChild&&typeof T.firstChild.getAttribute!=="undefined"&&T.firstChild.getAttribute("href")!=="#"){I.attrHandle.href=function(U){return U.getAttribute("href",2)}}})();if(document.querySelectorAll){(function(){var T=F,U=document.createElement("div");U.innerHTML="<p class='TEST'></p>";if(U.querySelectorAll&&U.querySelectorAll(".TEST").length===0){return}F=function(Y,X,V,W){X=X||document;if(!W&&X.nodeType===9&&!Q(X)){try{return E(X.querySelectorAll(Y),V)}catch(Z){}}return T(Y,X,V,W)};F.find=T.find;F.filter=T.filter;F.selectors=T.selectors;F.matches=T.matches})()}if(document.getElementsByClassName&&document.documentElement.getElementsByClassName){(function(){var T=document.createElement("div");T.innerHTML="<div class='test e'></div><div class='test'></div>";if(T.getElementsByClassName("e").length===0){return}T.lastChild.className="e";if(T.getElementsByClassName("e").length===1){return}I.order.splice(1,0,"CLASS");I.find.CLASS=function(U,V,W){if(typeof V.getElementsByClassName!=="undefined"&&!W){return V.getElementsByClassName(U[1])}}})()}function P(U,Z,Y,ad,aa,ac){var ab=U=="previousSibling"&&!ac;for(var W=0,V=ad.length;W<V;W++){var T=ad[W];if(T){if(ab&&T.nodeType===1){T.sizcache=Y;T.sizset=W}T=T[U];var X=false;while(T){if(T.sizcache===Y){X=ad[T.sizset];break}if(T.nodeType===1&&!ac){T.sizcache=Y;T.sizset=W}if(T.nodeName===Z){X=T;break}T=T[U]}ad[W]=X}}}function S(U,Z,Y,ad,aa,ac){var ab=U=="previousSibling"&&!ac;for(var W=0,V=ad.length;W<V;W++){var T=ad[W];if(T){if(ab&&T.nodeType===1){T.sizcache=Y;T.sizset=W}T=T[U];var X=false;while(T){if(T.sizcache===Y){X=ad[T.sizset];break}if(T.nodeType===1){if(!ac){T.sizcache=Y;T.sizset=W}if(typeof Z!=="string"){if(T===Z){X=true;break}}else{if(F.filter(Z,[T]).length>0){X=T;break}}}T=T[U]}ad[W]=X}}}var K=document.compareDocumentPosition?function(U,T){return U.compareDocumentPosition(T)&16}:function(U,T){return U!==T&&(U.contains?U.contains(T):true)};var Q=function(T){return T.nodeType===9&&T.documentElement.nodeName!=="HTML"||!!T.ownerDocument&&Q(T.ownerDocument)};var J=function(T,aa){var W=[],X="",Y,V=aa.nodeType?[aa]:aa;while((Y=I.match.PSEUDO.exec(T))){X+=Y[0];T=T.replace(I.match.PSEUDO,"")}T=I.relative[T]?T+"*":T;for(var Z=0,U=V.length;Z<U;Z++){F(T,V[Z],W)}return F.filter(X,W)};o.find=F;o.filter=F.filter;o.expr=F.selectors;o.expr[":"]=o.expr.filters;F.selectors.filters.hidden=function(T){return T.offsetWidth===0||T.offsetHeight===0};F.selectors.filters.visible=function(T){return T.offsetWidth>0||T.offsetHeight>0};F.selectors.filters.animated=function(T){return o.grep(o.timers,function(U){return T===U.elem}).length};o.multiFilter=function(V,T,U){if(U){V=":not("+V+")"}return F.matches(V,T)};o.dir=function(V,U){var T=[],W=V[U];while(W&&W!=document){if(W.nodeType==1){T.push(W)}W=W[U]}return T};o.nth=function(X,T,V,W){T=T||1;var U=0;for(;X;X=X[V]){if(X.nodeType==1&&++U==T){break}}return X};o.sibling=function(V,U){var T=[];for(;V;V=V.nextSibling){if(V.nodeType==1&&V!=U){T.push(V)}}return T};return;l.Sizzle=F})();o.event={add:function(I,F,H,K){if(I.nodeType==3||I.nodeType==8){return}if(I.setInterval&&I!=l){I=l}if(!H.guid){H.guid=this.guid++}if(K!==g){var G=H;H=this.proxy(G);H.data=K}var E=o.data(I,"events")||o.data(I,"events",{}),J=o.data(I,"handle")||o.data(I,"handle",function(){return typeof o!=="undefined"&&!o.event.triggered?o.event.handle.apply(arguments.callee.elem,arguments):g});J.elem=I;o.each(F.split(/\s+/),function(M,N){var O=N.split(".");N=O.shift();H.type=O.slice().sort().join(".");var L=E[N];if(o.event.specialAll[N]){o.event.specialAll[N].setup.call(I,K,O)}if(!L){L=E[N]={};if(!o.event.special[N]||o.event.special[N].setup.call(I,K,O)===false){if(I.addEventListener){I.addEventListener(N,J,false)}else{if(I.attachEvent){I.attachEvent("on"+N,J)}}}}L[H.guid]=H;o.event.global[N]=true});I=null},guid:1,global:{},remove:function(K,H,J){if(K.nodeType==3||K.nodeType==8){return}var G=o.data(K,"events"),F,E;if(G){if(H===g||(typeof H==="string"&&H.charAt(0)==".")){for(var I in G){this.remove(K,I+(H||""))}}else{if(H.type){J=H.handler;H=H.type}o.each(H.split(/\s+/),function(M,O){var Q=O.split(".");O=Q.shift();var N=RegExp("(^|\\.)"+Q.slice().sort().join(".*\\.")+"(\\.|$)");if(G[O]){if(J){delete G[O][J.guid]}else{for(var P in G[O]){if(N.test(G[O][P].type)){delete G[O][P]}}}if(o.event.specialAll[O]){o.event.specialAll[O].teardown.call(K,Q)}for(F in G[O]){break}if(!F){if(!o.event.special[O]||o.event.special[O].teardown.call(K,Q)===false){if(K.removeEventListener){K.removeEventListener(O,o.data(K,"handle"),false)}else{if(K.detachEvent){K.detachEvent("on"+O,o.data(K,"handle"))}}}F=null;delete G[O]}}})}for(F in G){break}if(!F){var L=o.data(K,"handle");if(L){L.elem=null}o.removeData(K,"events");o.removeData(K,"handle")}}},trigger:function(I,K,H,E){var G=I.type||I;if(!E){I=typeof I==="object"?I[h]?I:o.extend(o.Event(G),I):o.Event(G);if(G.indexOf("!")>=0){I.type=G=G.slice(0,-1);I.exclusive=true}if(!H){I.stopPropagation();if(this.global[G]){o.each(o.cache,function(){if(this.events&&this.events[G]){o.event.trigger(I,K,this.handle.elem)}})}}if(!H||H.nodeType==3||H.nodeType==8){return g}I.result=g;I.target=H;K=o.makeArray(K);K.unshift(I)}I.currentTarget=H;var J=o.data(H,"handle");if(J){J.apply(H,K)}if((!H[G]||(o.nodeName(H,"a")&&G=="click"))&&H["on"+G]&&H["on"+G].apply(H,K)===false){I.result=false}if(!E&&H[G]&&!I.isDefaultPrevented()&&!(o.nodeName(H,"a")&&G=="click")){this.triggered=true;try{H[G]()}catch(L){}}this.triggered=false;if(!I.isPropagationStopped()){var F=H.parentNode||H.ownerDocument;if(F){o.event.trigger(I,K,F,true)}}},handle:function(K){var J,E;K=arguments[0]=o.event.fix(K||l.event);K.currentTarget=this;var L=K.type.split(".");K.type=L.shift();J=!L.length&&!K.exclusive;var I=RegExp("(^|\\.)"+L.slice().sort().join(".*\\.")+"(\\.|$)");E=(o.data(this,"events")||{})[K.type];for(var G in E){var H=E[G];if(J||I.test(H.type)){K.handler=H;K.data=H.data;var F=H.apply(this,arguments);if(F!==g){K.result=F;if(F===false){K.preventDefault();K.stopPropagation()}}if(K.isImmediatePropagationStopped()){break}}}},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),fix:function(H){if(H[h]){return H}var F=H;H=o.Event(F);for(var G=this.props.length,J;G;){J=this.props[--G];H[J]=F[J]}if(!H.target){H.target=H.srcElement||document}if(H.target.nodeType==3){H.target=H.target.parentNode}if(!H.relatedTarget&&H.fromElement){H.relatedTarget=H.fromElement==H.target?H.toElement:H.fromElement}if(H.pageX==null&&H.clientX!=null){var I=document.documentElement,E=document.body;H.pageX=H.clientX+(I&&I.scrollLeft||E&&E.scrollLeft||0)-(I.clientLeft||0);H.pageY=H.clientY+(I&&I.scrollTop||E&&E.scrollTop||0)-(I.clientTop||0)}if(!H.which&&((H.charCode||H.charCode===0)?H.charCode:H.keyCode)){H.which=H.charCode||H.keyCode}if(!H.metaKey&&H.ctrlKey){H.metaKey=H.ctrlKey}if(!H.which&&H.button){H.which=(H.button&1?1:(H.button&2?3:(H.button&4?2:0)))}return H},proxy:function(F,E){E=E||function(){return F.apply(this,arguments)};E.guid=F.guid=F.guid||E.guid||this.guid++;return E},special:{ready:{setup:B,teardown:function(){}}},specialAll:{live:{setup:function(E,F){o.event.add(this,F[0],c)},teardown:function(G){if(G.length){var E=0,F=RegExp("(^|\\.)"+G[0]+"(\\.|$)");o.each((o.data(this,"events").live||{}),function(){if(F.test(this.type)){E++}});if(E<1){o.event.remove(this,G[0],c)}}}}}};o.Event=function(E){if(!this.preventDefault){return new o.Event(E)}if(E&&E.type){this.originalEvent=E;this.type=E.type}else{this.type=E}this.timeStamp=e();this[h]=true};function k(){return false}function u(){return true}o.Event.prototype={preventDefault:function(){this.isDefaultPrevented=u;var E=this.originalEvent;if(!E){return}if(E.preventDefault){E.preventDefault()}E.returnValue=false},stopPropagation:function(){this.isPropagationStopped=u;var E=this.originalEvent;if(!E){return}if(E.stopPropagation){E.stopPropagation()}E.cancelBubble=true},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=u;this.stopPropagation()},isDefaultPrevented:k,isPropagationStopped:k,isImmediatePropagationStopped:k};var a=function(F){var E=F.relatedTarget;while(E&&E!=this){try{E=E.parentNode}catch(G){E=this}}if(E!=this){F.type=F.data;o.event.handle.apply(this,arguments)}};o.each({mouseover:"mouseenter",mouseout:"mouseleave"},function(F,E){o.event.special[E]={setup:function(){o.event.add(this,F,a,E)},teardown:function(){o.event.remove(this,F,a)}}});o.fn.extend({bind:function(F,G,E){return F=="unload"?this.one(F,G,E):this.each(function(){o.event.add(this,F,E||G,E&&G)})},one:function(G,H,F){var E=o.event.proxy(F||H,function(I){o(this).unbind(I,E);return(F||H).apply(this,arguments)});return this.each(function(){o.event.add(this,G,E,F&&H)})},unbind:function(F,E){return this.each(function(){o.event.remove(this,F,E)})},trigger:function(E,F){return this.each(function(){o.event.trigger(E,F,this)})},triggerHandler:function(E,G){if(this[0]){var F=o.Event(E);F.preventDefault();F.stopPropagation();o.event.trigger(F,G,this[0]);return F.result}},toggle:function(G){var E=arguments,F=1;while(F<E.length){o.event.proxy(G,E[F++])}return this.click(o.event.proxy(G,function(H){this.lastToggle=(this.lastToggle||0)%F;H.preventDefault();return E[this.lastToggle++].apply(this,arguments)||false}))},hover:function(E,F){return this.mouseenter(E).mouseleave(F)},ready:function(E){B();if(o.isReady){E.call(document,o)}else{o.readyList.push(E)}return this},live:function(G,F){var E=o.event.proxy(F);E.guid+=this.selector+G;o(document).bind(i(G,this.selector),this.selector,E);return this},die:function(F,E){o(document).unbind(i(F,this.selector),E?{guid:E.guid+this.selector+F}:null);return this}});function c(H){var E=RegExp("(^|\\.)"+H.type+"(\\.|$)"),G=true,F=[];o.each(o.data(this,"events").live||[],function(I,J){if(E.test(J.type)){var K=o(H.target).closest(J.data)[0];if(K){F.push({elem:K,fn:J})}}});F.sort(function(J,I){return o.data(J.elem,"closest")-o.data(I.elem,"closest")});o.each(F,function(){if(this.fn.call(this.elem,H,this.fn.data)===false){return(G=false)}});return G}function i(F,E){return["live",F,E.replace(/\./g,"`").replace(/ /g,"|")].join(".")}o.extend({isReady:false,readyList:[],ready:function(){if(!o.isReady){o.isReady=true;if(o.readyList){o.each(o.readyList,function(){this.call(document,o)});o.readyList=null}o(document).triggerHandler("ready")}}});var x=false;function B(){if(x){return}x=true;if(document.addEventListener){document.addEventListener("DOMContentLoaded",function(){document.removeEventListener("DOMContentLoaded",arguments.callee,false);o.ready()},false)}else{if(document.attachEvent){document.attachEvent("onreadystatechange",function(){if(document.readyState==="complete"){document.detachEvent("onreadystatechange",arguments.callee);o.ready()}});if(document.documentElement.doScroll&&l==l.top){(function(){if(o.isReady){return}try{document.documentElement.doScroll("left")}catch(E){setTimeout(arguments.callee,0);return}o.ready()})()}}}o.event.add(l,"load",o.ready)}o.each(("blur,focus,load,resize,scroll,unload,click,dblclick,mousedown,mouseup,mousemove,mouseover,mouseout,mouseenter,mouseleave,change,select,submit,keydown,keypress,keyup,error").split(","),function(F,E){o.fn[E]=function(G){return G?this.bind(E,G):this.trigger(E)}});o(l).bind("unload",function(){for(var E in o.cache){if(E!=1&&o.cache[E].handle){o.event.remove(o.cache[E].handle.elem)}}});(function(){o.support={};var F=document.documentElement,G=document.createElement("script"),K=document.createElement("div"),J="script"+(new Date).getTime();K.style.display="none";K.innerHTML='   <link/><table></table><a href="/a" style="color:red;float:left;opacity:.5;">a</a><select><option>text</option></select><object><param/></object>';var H=K.getElementsByTagName("*"),E=K.getElementsByTagName("a")[0];if(!H||!H.length||!E){return}o.support={leadingWhitespace:K.firstChild.nodeType==3,tbody:!K.getElementsByTagName("tbody").length,objectAll:!!K.getElementsByTagName("object")[0].getElementsByTagName("*").length,htmlSerialize:!!K.getElementsByTagName("link").length,style:/red/.test(E.getAttribute("style")),hrefNormalized:E.getAttribute("href")==="/a",opacity:E.style.opacity==="0.5",cssFloat:!!E.style.cssFloat,scriptEval:false,noCloneEvent:true,boxModel:null};G.type="text/javascript";try{G.appendChild(document.createTextNode("window."+J+"=1;"))}catch(I){}F.insertBefore(G,F.firstChild);if(l[J]){o.support.scriptEval=true;delete l[J]}F.removeChild(G);if(K.attachEvent&&K.fireEvent){K.attachEvent("onclick",function(){o.support.noCloneEvent=false;K.detachEvent("onclick",arguments.callee)});K.cloneNode(true).fireEvent("onclick")}o(function(){var L=document.createElement("div");L.style.width=L.style.paddingLeft="1px";document.body.appendChild(L);o.boxModel=o.support.boxModel=L.offsetWidth===2;document.body.removeChild(L).style.display="none"})})();var w=o.support.cssFloat?"cssFloat":"styleFloat";o.props={"for":"htmlFor","class":"className","float":w,cssFloat:w,styleFloat:w,readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",tabindex:"tabIndex"};o.fn.extend({_load:o.fn.load,load:function(G,J,K){if(typeof G!=="string"){return this._load(G)}var I=G.indexOf(" ");if(I>=0){var E=G.slice(I,G.length);G=G.slice(0,I)}var H="GET";if(J){if(o.isFunction(J)){K=J;J=null}else{if(typeof J==="object"){J=o.param(J);H="POST"}}}var F=this;o.ajax({url:G,type:H,dataType:"html",data:J,complete:function(M,L){if(L=="success"||L=="notmodified"){F.html(E?o("<div/>").append(M.responseText.replace(/<script(.|\s)*?\/script>/g,"")).find(E):M.responseText)}if(K){F.each(K,[M.responseText,L,M])}}});return this},serialize:function(){return o.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?o.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||/select|textarea/i.test(this.nodeName)||/text|hidden|password|search/i.test(this.type))}).map(function(E,F){var G=o(this).val();return G==null?null:o.isArray(G)?o.map(G,function(I,H){return{name:F.name,value:I}}):{name:F.name,value:G}}).get()}});o.each("ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","),function(E,F){o.fn[F]=function(G){return this.bind(F,G)}});var r=e();o.extend({get:function(E,G,H,F){if(o.isFunction(G)){H=G;G=null}return o.ajax({type:"GET",url:E,data:G,success:H,dataType:F})},getScript:function(E,F){return o.get(E,null,F,"script")},getJSON:function(E,F,G){return o.get(E,F,G,"json")},post:function(E,G,H,F){if(o.isFunction(G)){H=G;G={}}return o.ajax({type:"POST",url:E,data:G,success:H,dataType:F})},ajaxSetup:function(E){o.extend(o.ajaxSettings,E)},ajaxSettings:{url:location.href,global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:function(){return l.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest()},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},ajax:function(M){M=o.extend(true,M,o.extend(true,{},o.ajaxSettings,M));var W,F=/=\?(&|$)/g,R,V,G=M.type.toUpperCase();if(M.data&&M.processData&&typeof M.data!=="string"){M.data=o.param(M.data)}if(M.dataType=="jsonp"){if(G=="GET"){if(!M.url.match(F)){M.url+=(M.url.match(/\?/)?"&":"?")+(M.jsonp||"callback")+"=?"}}else{if(!M.data||!M.data.match(F)){M.data=(M.data?M.data+"&":"")+(M.jsonp||"callback")+"=?"}}M.dataType="json"}if(M.dataType=="json"&&(M.data&&M.data.match(F)||M.url.match(F))){W="jsonp"+r++;if(M.data){M.data=(M.data+"").replace(F,"="+W+"$1")}M.url=M.url.replace(F,"="+W+"$1");M.dataType="script";l[W]=function(X){V=X;I();L();l[W]=g;try{delete l[W]}catch(Y){}if(H){H.removeChild(T)}}}if(M.dataType=="script"&&M.cache==null){M.cache=false}if(M.cache===false&&G=="GET"){var E=e();var U=M.url.replace(/(\?|&)_=.*?(&|$)/,"$1_="+E+"$2");M.url=U+((U==M.url)?(M.url.match(/\?/)?"&":"?")+"_="+E:"")}if(M.data&&G=="GET"){M.url+=(M.url.match(/\?/)?"&":"?")+M.data;M.data=null}if(M.global&&!o.active++){o.event.trigger("ajaxStart")}var Q=/^(\w+:)?\/\/([^\/?#]+)/.exec(M.url);if(M.dataType=="script"&&G=="GET"&&Q&&(Q[1]&&Q[1]!=location.protocol||Q[2]!=location.host)){var H=document.getElementsByTagName("head")[0];var T=document.createElement("script");T.src=M.url;if(M.scriptCharset){T.charset=M.scriptCharset}if(!W){var O=false;T.onload=T.onreadystatechange=function(){if(!O&&(!this.readyState||this.readyState=="loaded"||this.readyState=="complete")){O=true;I();L();T.onload=T.onreadystatechange=null;H.removeChild(T)}}}H.appendChild(T);return g}var K=false;var J=M.xhr();if(M.username){J.open(G,M.url,M.async,M.username,M.password)}else{J.open(G,M.url,M.async)}try{if(M.data){J.setRequestHeader("Content-Type",M.contentType)}if(M.ifModified){J.setRequestHeader("If-Modified-Since",o.lastModified[M.url]||"Thu, 01 Jan 1970 00:00:00 GMT")}J.setRequestHeader("X-Requested-With","XMLHttpRequest");J.setRequestHeader("Accept",M.dataType&&M.accepts[M.dataType]?M.accepts[M.dataType]+", */*":M.accepts._default)}catch(S){}if(M.beforeSend&&M.beforeSend(J,M)===false){if(M.global&&!--o.active){o.event.trigger("ajaxStop")}J.abort();return false}if(M.global){o.event.trigger("ajaxSend",[J,M])}var N=function(X){if(J.readyState==0){if(P){clearInterval(P);P=null;if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}}else{if(!K&&J&&(J.readyState==4||X=="timeout")){K=true;if(P){clearInterval(P);P=null}R=X=="timeout"?"timeout":!o.httpSuccess(J)?"error":M.ifModified&&o.httpNotModified(J,M.url)?"notmodified":"success";if(R=="success"){try{V=o.httpData(J,M.dataType,M)}catch(Z){R="parsererror"}}if(R=="success"){var Y;try{Y=J.getResponseHeader("Last-Modified")}catch(Z){}if(M.ifModified&&Y){o.lastModified[M.url]=Y}if(!W){I()}}else{o.handleError(M,J,R)}L();if(X){J.abort()}if(M.async){J=null}}}};if(M.async){var P=setInterval(N,13);if(M.timeout>0){setTimeout(function(){if(J&&!K){N("timeout")}},M.timeout)}}try{J.send(M.data)}catch(S){o.handleError(M,J,null,S)}if(!M.async){N()}function I(){if(M.success){M.success(V,R)}if(M.global){o.event.trigger("ajaxSuccess",[J,M])}}function L(){if(M.complete){M.complete(J,R)}if(M.global){o.event.trigger("ajaxComplete",[J,M])}if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}return J},handleError:function(F,H,E,G){if(F.error){F.error(H,E,G)}if(F.global){o.event.trigger("ajaxError",[H,F,G])}},active:0,httpSuccess:function(F){try{return !F.status&&location.protocol=="file:"||(F.status>=200&&F.status<300)||F.status==304||F.status==1223}catch(E){}return false},httpNotModified:function(G,E){try{var H=G.getResponseHeader("Last-Modified");return G.status==304||H==o.lastModified[E]}catch(F){}return false},httpData:function(J,H,G){var F=J.getResponseHeader("content-type"),E=H=="xml"||!H&&F&&F.indexOf("xml")>=0,I=E?J.responseXML:J.responseText;if(E&&I.documentElement.tagName=="parsererror"){throw"parsererror"}if(G&&G.dataFilter){I=G.dataFilter(I,H)}if(typeof I==="string"){if(H=="script"){o.globalEval(I)}if(H=="json"){I=l["eval"]("("+I+")")}}return I},param:function(E){var G=[];function H(I,J){G[G.length]=encodeURIComponent(I)+"="+encodeURIComponent(J)}if(o.isArray(E)||E.jquery){o.each(E,function(){H(this.name,this.value)})}else{for(var F in E){if(o.isArray(E[F])){o.each(E[F],function(){H(F,this)})}else{H(F,o.isFunction(E[F])?E[F]():E[F])}}}return G.join("&").replace(/%20/g,"+")}});var m={},n,d=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];function t(F,E){var G={};o.each(d.concat.apply([],d.slice(0,E)),function(){G[this]=F});return G}o.fn.extend({show:function(J,L){if(J){return this.animate(t("show",3),J,L)}else{for(var H=0,F=this.length;H<F;H++){var E=o.data(this[H],"olddisplay");this[H].style.display=E||"";if(o.css(this[H],"display")==="none"){var G=this[H].tagName,K;if(m[G]){K=m[G]}else{var I=o("<"+G+" />").appendTo("body");K=I.css("display");if(K==="none"){K="block"}I.remove();m[G]=K}o.data(this[H],"olddisplay",K)}}for(var H=0,F=this.length;H<F;H++){this[H].style.display=o.data(this[H],"olddisplay")||""}return this}},hide:function(H,I){if(H){return this.animate(t("hide",3),H,I)}else{for(var G=0,F=this.length;G<F;G++){var E=o.data(this[G],"olddisplay");if(!E&&E!=="none"){o.data(this[G],"olddisplay",o.css(this[G],"display"))}}for(var G=0,F=this.length;G<F;G++){this[G].style.display="none"}return this}},_toggle:o.fn.toggle,toggle:function(G,F){var E=typeof G==="boolean";return o.isFunction(G)&&o.isFunction(F)?this._toggle.apply(this,arguments):G==null||E?this.each(function(){var H=E?G:o(this).is(":hidden");o(this)[H?"show":"hide"]()}):this.animate(t("toggle",3),G,F)},fadeTo:function(E,G,F){return this.animate({opacity:G},E,F)},animate:function(I,F,H,G){var E=o.speed(F,H,G);return this[E.queue===false?"each":"queue"](function(){var K=o.extend({},E),M,L=this.nodeType==1&&o(this).is(":hidden"),J=this;for(M in I){if(I[M]=="hide"&&L||I[M]=="show"&&!L){return K.complete.call(this)}if((M=="height"||M=="width")&&this.style){K.display=o.css(this,"display");K.overflow=this.style.overflow}}if(K.overflow!=null){this.style.overflow="hidden"}K.curAnim=o.extend({},I);o.each(I,function(O,S){var R=new o.fx(J,K,O);if(/toggle|show|hide/.test(S)){R[S=="toggle"?L?"show":"hide":S](I)}else{var Q=S.toString().match(/^([+-]=)?([\d+-.]+)(.*)$/),T=R.cur(true)||0;if(Q){var N=parseFloat(Q[2]),P=Q[3]||"px";if(P!="px"){J.style[O]=(N||1)+P;T=((N||1)/R.cur(true))*T;J.style[O]=T+P}if(Q[1]){N=((Q[1]=="-="?-1:1)*N)+T}R.custom(T,N,P)}else{R.custom(T,S,"")}}});return true})},stop:function(F,E){var G=o.timers;if(F){this.queue([])}this.each(function(){for(var H=G.length-1;H>=0;H--){if(G[H].elem==this){if(E){G[H](true)}G.splice(H,1)}}});if(!E){this.dequeue()}return this}});o.each({slideDown:t("show",1),slideUp:t("hide",1),slideToggle:t("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(E,F){o.fn[E]=function(G,H){return this.animate(F,G,H)}});o.extend({speed:function(G,H,F){var E=typeof G==="object"?G:{complete:F||!F&&H||o.isFunction(G)&&G,duration:G,easing:F&&H||H&&!o.isFunction(H)&&H};E.duration=o.fx.off?0:typeof E.duration==="number"?E.duration:o.fx.speeds[E.duration]||o.fx.speeds._default;E.old=E.complete;E.complete=function(){if(E.queue!==false){o(this).dequeue()}if(o.isFunction(E.old)){E.old.call(this)}};return E},easing:{linear:function(G,H,E,F){return E+F*G},swing:function(G,H,E,F){return((-Math.cos(G*Math.PI)/2)+0.5)*F+E}},timers:[],fx:function(F,E,G){this.options=E;this.elem=F;this.prop=G;if(!E.orig){E.orig={}}}});o.fx.prototype={update:function(){if(this.options.step){this.options.step.call(this.elem,this.now,this)}(o.fx.step[this.prop]||o.fx.step._default)(this);if((this.prop=="height"||this.prop=="width")&&this.elem.style){this.elem.style.display="block"}},cur:function(F){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null)){return this.elem[this.prop]}var E=parseFloat(o.css(this.elem,this.prop,F));return E&&E>-10000?E:parseFloat(o.curCSS(this.elem,this.prop))||0},custom:function(I,H,G){this.startTime=e();this.start=I;this.end=H;this.unit=G||this.unit||"px";this.now=this.start;this.pos=this.state=0;var E=this;function F(J){return E.step(J)}F.elem=this.elem;if(F()&&o.timers.push(F)&&!n){n=setInterval(function(){var K=o.timers;for(var J=0;J<K.length;J++){if(!K[J]()){K.splice(J--,1)}}if(!K.length){clearInterval(n);n=g}},13)}},show:function(){this.options.orig[this.prop]=o.attr(this.elem.style,this.prop);this.options.show=true;this.custom(this.prop=="width"||this.prop=="height"?1:0,this.cur());o(this.elem).show()},hide:function(){this.options.orig[this.prop]=o.attr(this.elem.style,this.prop);this.options.hide=true;this.custom(this.cur(),0)},step:function(H){var G=e();if(H||G>=this.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;var E=true;for(var F in this.options.curAnim){if(this.options.curAnim[F]!==true){E=false}}if(E){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;this.elem.style.display=this.options.display;if(o.css(this.elem,"display")=="none"){this.elem.style.display="block"}}if(this.options.hide){o(this.elem).hide()}if(this.options.hide||this.options.show){for(var I in this.options.curAnim){o.attr(this.elem.style,I,this.options.orig[I])}}this.options.complete.call(this.elem)}return false}else{var J=G-this.startTime;this.state=J/this.options.duration;this.pos=o.easing[this.options.easing||(o.easing.swing?"swing":"linear")](this.state,J,0,1,this.options.duration);this.now=this.start+((this.end-this.start)*this.pos);this.update()}return true}};o.extend(o.fx,{speeds:{slow:600,fast:200,_default:400},step:{opacity:function(E){o.attr(E.elem.style,"opacity",E.now)},_default:function(E){if(E.elem.style&&E.elem.style[E.prop]!=null){E.elem.style[E.prop]=E.now+E.unit}else{E.elem[E.prop]=E.now}}}});if(document.documentElement.getBoundingClientRect){o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}var G=this[0].getBoundingClientRect(),J=this[0].ownerDocument,F=J.body,E=J.documentElement,L=E.clientTop||F.clientTop||0,K=E.clientLeft||F.clientLeft||0,I=G.top+(self.pageYOffset||o.boxModel&&E.scrollTop||F.scrollTop)-L,H=G.left+(self.pageXOffset||o.boxModel&&E.scrollLeft||F.scrollLeft)-K;return{top:I,left:H}}}else{o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}o.offset.initialized||o.offset.initialize();var J=this[0],G=J.offsetParent,F=J,O=J.ownerDocument,M,H=O.documentElement,K=O.body,L=O.defaultView,E=L.getComputedStyle(J,null),N=J.offsetTop,I=J.offsetLeft;while((J=J.parentNode)&&J!==K&&J!==H){M=L.getComputedStyle(J,null);N-=J.scrollTop,I-=J.scrollLeft;if(J===G){N+=J.offsetTop,I+=J.offsetLeft;if(o.offset.doesNotAddBorder&&!(o.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(J.tagName))){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}F=G,G=J.offsetParent}if(o.offset.subtractsBorderForOverflowNotVisible&&M.overflow!=="visible"){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}E=M}if(E.position==="relative"||E.position==="static"){N+=K.offsetTop,I+=K.offsetLeft}if(E.position==="fixed"){N+=Math.max(H.scrollTop,K.scrollTop),I+=Math.max(H.scrollLeft,K.scrollLeft)}return{top:N,left:I}}}o.offset={initialize:function(){if(this.initialized){return}var L=document.body,F=document.createElement("div"),H,G,N,I,M,E,J=L.style.marginTop,K='<div style="position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;"><div></div></div><table style="position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;" cellpadding="0" cellspacing="0"><tr><td></td></tr></table>';M={position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"};for(E in M){F.style[E]=M[E]}F.innerHTML=K;L.insertBefore(F,L.firstChild);H=F.firstChild,G=H.firstChild,I=H.nextSibling.firstChild.firstChild;this.doesNotAddBorder=(G.offsetTop!==5);this.doesAddBorderForTableAndCells=(I.offsetTop===5);H.style.overflow="hidden",H.style.position="relative";this.subtractsBorderForOverflowNotVisible=(G.offsetTop===-5);L.style.marginTop="1px";this.doesNotIncludeMarginInBodyOffset=(L.offsetTop===0);L.style.marginTop=J;L.removeChild(F);this.initialized=true},bodyOffset:function(E){o.offset.initialized||o.offset.initialize();var G=E.offsetTop,F=E.offsetLeft;if(o.offset.doesNotIncludeMarginInBodyOffset){G+=parseInt(o.curCSS(E,"marginTop",true),10)||0,F+=parseInt(o.curCSS(E,"marginLeft",true),10)||0}return{top:G,left:F}}};o.fn.extend({position:function(){var I=0,H=0,F;if(this[0]){var G=this.offsetParent(),J=this.offset(),E=/^body|html$/i.test(G[0].tagName)?{top:0,left:0}:G.offset();J.top-=j(this,"marginTop");J.left-=j(this,"marginLeft");E.top+=j(G,"borderTopWidth");E.left+=j(G,"borderLeftWidth");F={top:J.top-E.top,left:J.left-E.left}}return F},offsetParent:function(){var E=this[0].offsetParent||document.body;while(E&&(!/^body|html$/i.test(E.tagName)&&o.css(E,"position")=="static")){E=E.offsetParent}return o(E)}});o.each(["Left","Top"],function(F,E){var G="scroll"+E;o.fn[G]=function(H){if(!this[0]){return null}return H!==g?this.each(function(){this==l||this==document?l.scrollTo(!F?H:o(l).scrollLeft(),F?H:o(l).scrollTop()):this[G]=H}):this[0]==l||this[0]==document?self[F?"pageYOffset":"pageXOffset"]||o.boxModel&&document.documentElement[G]||document.body[G]:this[0][G]}});o.each(["Height","Width"],function(I,G){var E=I?"Left":"Top",H=I?"Right":"Bottom",F=G.toLowerCase();o.fn["inner"+G]=function(){return this[0]?o.css(this[0],F,false,"padding"):null};o.fn["outer"+G]=function(K){return this[0]?o.css(this[0],F,false,K?"margin":"border"):null};var J=G.toLowerCase();o.fn[J]=function(K){return this[0]==l?document.compatMode=="CSS1Compat"&&document.documentElement["client"+G]||document.body["client"+G]:this[0]==document?Math.max(document.documentElement["client"+G],document.body["scroll"+G],document.documentElement["scroll"+G],document.body["offset"+G],document.documentElement["offset"+G]):K===g?(this.length?o.css(this[0],J):null):this.css(J,typeof K==="string"?K:K+"px")}})})();
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/toolbox/static/js/jquery.timeago.js	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,147 @@
+/*
+ * timeago: a jQuery plugin, version: 0.9.3 (2011-01-21)
+ * @requires jQuery v1.2.3 or later
+ *
+ * Timeago is a jQuery plugin that makes it easy to support automatically
+ * updating fuzzy timestamps (e.g. "4 minutes ago" or "about 1 day ago").
+ *
+ * For usage and examples, visit:
+ * http://timeago.yarp.com/
+ *
+ * Licensed under the MIT:
+ * http://www.opensource.org/licenses/mit-license.php
+ *
+ * Copyright (c) 2008-2011, Ryan McGeary (ryanonjavascript -[at]- mcgeary [*dot*] org)
+ */
+(function($) {
+  $.timeago = function(timestamp) {
+    if (timestamp instanceof Date) {
+      return inWords(timestamp);
+    } else if (typeof timestamp === "string") {
+      return inWords($.timeago.parse(timestamp));
+    } else {
+      return inWords($.timeago.datetime(timestamp));
+    }
+  };
+  var $t = $.timeago;
+
+  $.extend($.timeago, {
+    settings: {
+      refreshMillis: 60000,
+      allowFuture: false,
+      strings: {
+        prefixAgo: null,
+        prefixFromNow: null,
+        suffixAgo: "ago",
+        suffixFromNow: "from now",
+        seconds: "less than a minute",
+        minute: "about a minute",
+        minutes: "%d minutes",
+        hour: "about an hour",
+        hours: "about %d hours",
+        day: "a day",
+        days: "%d days",
+        month: "about a month",
+        months: "%d months",
+        year: "about a year",
+        years: "%d years",
+        numbers: []
+      }
+    },
+    inWords: function(distanceMillis) {
+      var $l = this.settings.strings;
+      var prefix = $l.prefixAgo;
+      var suffix = $l.suffixAgo;
+      if (this.settings.allowFuture) {
+        if (distanceMillis < 0) {
+          prefix = $l.prefixFromNow;
+          suffix = $l.suffixFromNow;
+        }
+        distanceMillis = Math.abs(distanceMillis);
+      }
+
+      var seconds = distanceMillis / 1000;
+      var minutes = seconds / 60;
+      var hours = minutes / 60;
+      var days = hours / 24;
+      var years = days / 365;
+
+      function substitute(stringOrFunction, number) {
+        var string = $.isFunction(stringOrFunction) ? stringOrFunction(number, distanceMillis) : stringOrFunction;
+        var value = ($l.numbers && $l.numbers[number]) || number;
+        return string.replace(/%d/i, value);
+      }
+
+      var words = seconds < 45 && substitute($l.seconds, Math.round(seconds)) ||
+        seconds < 90 && substitute($l.minute, 1) ||
+        minutes < 45 && substitute($l.minutes, Math.round(minutes)) ||
+        minutes < 90 && substitute($l.hour, 1) ||
+        hours < 24 && substitute($l.hours, Math.round(hours)) ||
+        hours < 48 && substitute($l.day, 1) ||
+        days < 30 && substitute($l.days, Math.floor(days)) ||
+        days < 60 && substitute($l.month, 1) ||
+        days < 365 && substitute($l.months, Math.floor(days / 30)) ||
+        years < 2 && substitute($l.year, 1) ||
+        substitute($l.years, Math.floor(years));
+
+      return $.trim([prefix, words, suffix].join(" "));
+    },
+    parse: function(iso8601) {
+      var s = $.trim(iso8601);
+      s = s.replace(/\.\d\d\d+/,""); // remove milliseconds
+      s = s.replace(/-/,"/").replace(/-/,"/");
+      s = s.replace(/T/," ").replace(/Z/," UTC");
+      s = s.replace(/([\+\-]\d\d)\:?(\d\d)/," $1$2"); // -04:00 -> -0400
+      return new Date(s);
+    },
+    datetime: function(elem) {
+      // jQuery's `is()` doesn't play well with HTML5 in IE
+      var isTime = $(elem).get(0).tagName.toLowerCase() === "time"; // $(elem).is("time");
+      var iso8601 = isTime ? $(elem).attr("datetime") : $(elem).attr("title");
+      return $t.parse(iso8601);
+    }
+  });
+
+  $.fn.timeago = function() {
+    var self = this;
+    self.each(refresh);
+
+    var $s = $t.settings;
+    if ($s.refreshMillis > 0) {
+      setInterval(function() { self.each(refresh); }, $s.refreshMillis);
+    }
+    return self;
+  };
+
+  function refresh() {
+    var data = prepareData(this);
+    if (!isNaN(data.datetime)) {
+      $(this).text(inWords(data.datetime));
+    }
+    return this;
+  }
+
+  function prepareData(element) {
+    element = $(element);
+    if (!element.data("timeago")) {
+      element.data("timeago", { datetime: $t.datetime(element) });
+      var text = $.trim(element.text());
+      if (text.length > 0) {
+        element.attr("title", text);
+      }
+    }
+    return element.data("timeago");
+  }
+
+  function inWords(date) {
+    return $t.inWords(distance(date));
+  }
+
+  function distance(date) {
+    return (new Date().getTime() - date.getTime());
+  }
+
+  // fix for IE6 suckage
+  document.createElement("abbr");
+  document.createElement("time");
+}(jQuery));
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/toolbox/static/js/jquery.tokeninput.js	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,776 @@
+/*
+ * jQuery Plugin: Tokenizing Autocomplete Text Entry
+ * Version 1.4.2
+ *
+ * Copyright (c) 2009 James Smith (http://loopj.com)
+ * Licensed jointly under the GPL and MIT licenses,
+ * choose which one suits your project best!
+ *
+ */
+
+function HTMLescape(value) {
+    return value.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
+}
+
+(function ($) {
+// Default settings
+var DEFAULT_SETTINGS = {
+    hintText: "Type in a search term",
+    noResultsText: "No results",
+    searchingText: "Searching...",
+    deleteText: "&times;",
+    searchDelay: 300,
+    minChars: 1,
+    tokenLimit: null,
+    jsonContainer: null,
+    method: "GET",
+    contentType: "json",
+    queryParam: "q",
+    tokenDelimiter: ",",
+    preventDuplicates: false,
+    prePopulate: null,
+    animateDropdown: true,
+    autoFocus: false,
+    onResult: null,
+    onAdd: null,
+    onDelete: null,
+    onSubmit: null,
+    submitOnEnter: true,
+    submitOnBlur: false,
+    closeOnBlur: true,
+    autoSelect: false,
+    canBlur: function() { return true; }
+};
+
+// Default classes to use when theming
+var DEFAULT_CLASSES = {
+    tokenList: "token-input-list",
+    token: "token-input-token",
+    tokenDelete: "token-input-delete-token",
+    selectedToken: "token-input-selected-token",
+    highlightedToken: "token-input-highlighted-token",
+    dropdown: "token-input-dropdown",
+    dropdownItem: "token-input-dropdown-item",
+    dropdownItem2: "token-input-dropdown-item2",
+    selectedDropdownItem: "token-input-selected-dropdown-item",
+    inputToken: "token-input-input-token"
+};
+
+// Input box position "enum"
+var POSITION = {
+    BEFORE: 0,
+    AFTER: 1,
+    END: 2
+};
+
+// Keys "enum"
+var KEY = {
+    BACKSPACE: 8,
+    TAB: 9,
+    ENTER: 13,
+    ESCAPE: 27,
+    SPACE: 32,
+    PAGE_UP: 33,
+    PAGE_DOWN: 34,
+    END: 35,
+    HOME: 36,
+    LEFT: 37,
+    UP: 38,
+    RIGHT: 39,
+    DOWN: 40,
+    NUMPAD_ENTER: 108,
+    COMMA: 188
+};
+
+
+// Expose the .tokenInput function to jQuery as a plugin
+$.fn.tokenInput = function (url_or_data, options) {
+    var settings = $.extend({}, DEFAULT_SETTINGS, options || {});
+
+    return this.each(function () {
+        new $.TokenList(this, url_or_data, settings);
+    });
+};
+
+
+// TokenList class for each input
+$.TokenList = function (input, url_or_data, settings) {
+    //
+    // Initialization
+    //
+
+    // Configure the data source
+    if($.type(url_or_data) === "string") {
+        // Set the url to query against
+        settings.url = url_or_data;
+
+        // Make a smart guess about cross-domain if it wasn't explicitly specified
+        if(settings.crossDomain === undefined) {
+            if(settings.url.indexOf("://") === -1) {
+                settings.crossDomain = false;
+            } else {
+                settings.crossDomain = (location.href.split(/\/+/g)[1] !== settings.url.split(/\/+/g)[1]);
+            }
+        }
+    } else if($.type(url_or_data) === "array") {
+        // Set the local data to search through
+        settings.local_data = url_or_data;
+    }
+
+    // Build class names
+    if(settings.classes) {
+        // Use custom class names
+        settings.classes = $.extend({}, DEFAULT_CLASSES, settings.classes);
+    } else if(settings.theme) {
+        // Use theme-suffixed default class names
+        settings.classes = {};
+        $.each(DEFAULT_CLASSES, function(key, value) {
+            settings.classes[key] = value + "-" + settings.theme;
+        });
+    } else {
+        settings.classes = DEFAULT_CLASSES;
+    }
+
+
+    // Save the tokens
+    var saved_tokens = [];
+
+    // Keep track of the number of tokens in the list
+    var token_count = 0;
+
+    // Basic cache to save on db hits
+    var cache = new $.TokenList.Cache();
+
+    // Keep track of the timeout, old vals
+    var timeout;
+    var input_val;
+
+    // Create a new text input an attach keyup events
+    var input_box = $("<input type=\"text\"  autocomplete=\"off\">")
+        .css({
+            outline: "none"
+        })
+        .focus(function () {
+            if (settings.tokenLimit === null || settings.tokenLimit !== token_count) {
+                show_dropdown_hint();
+            }
+        })
+        .bind("keyup keydown blur update", resize_input)
+        .keydown(function (event) {
+            var previous_token;
+            var next_token;
+            switch(event.keyCode) {
+                case KEY.LEFT:
+                case KEY.RIGHT:
+                case KEY.UP:
+                case KEY.DOWN:
+                    if(!$(this).val()) {                      
+                        previous_token = input_token.prev();
+                        next_token = input_token.next();
+
+                        if((previous_token.length && previous_token.get(0) === selected_token) || (next_token.length && next_token.get(0) === selected_token)) {
+                            // Check if there is a previous/next token and it is selected
+                            if(event.keyCode === KEY.LEFT || event.keyCode === KEY.UP) {
+                                deselect_token($(selected_token), POSITION.BEFORE);
+                            } else {
+                                deselect_token($(selected_token), POSITION.AFTER);
+                            }
+                        } else if((event.keyCode === KEY.LEFT || event.keyCode === KEY.UP) && previous_token.length) {
+                            // We are moving left, select the previous token if it exists
+                            select_token($(previous_token.get(0)));
+                        } else if((event.keyCode === KEY.RIGHT || event.keyCode === KEY.DOWN) && next_token.length) {
+                            // We are moving right, select the next token if it exists
+                            select_token($(next_token.get(0)));
+                        }
+                    } else {
+                        var dropdown_item = null;
+                        
+                        if(event.keyCode === KEY.DOWN || event.keyCode === KEY.RIGHT) {
+                          if($(selected_dropdown_item).length)
+                            dropdown_item = $(selected_dropdown_item).next();
+                          else
+                            dropdown_item = $($(dropdown).find('li').get(0));
+                        } else {
+                            dropdown_item = $(selected_dropdown_item).prev();
+                        }
+
+                        if(dropdown_item.length) {
+                            select_dropdown_item(dropdown_item);
+                        }
+                        return false;
+                    }
+                    break;
+
+                case KEY.BACKSPACE:
+                    previous_token = input_token.prev();
+
+                    if(!$(this).val().length) {
+                        if(selected_token) {
+                            delete_token($(selected_token));
+                        } else if(previous_token.length) {
+                            select_token($(previous_token.get(0)));
+                        }
+
+                        return false;
+                    } else if($(this).val().length === 1) {
+                        hide_dropdown();
+                    } else {
+                        // set a timeout just long enough to let this function finish.
+                        setTimeout(function(){do_search();}, 5);
+                    }
+                    break;
+
+                case KEY.TAB:
+                case KEY.ENTER:
+                case KEY.NUMPAD_ENTER:
+                case KEY.COMMA:
+                  if(selected_dropdown_item) {
+                    add_token($(selected_dropdown_item));
+                    return false;
+                  }
+                  else if(event.keyCode == KEY.COMMA) {
+                    return true;
+                  }
+                  else if(event.keyCode == KEY.ENTER && settings.submitOnEnter) {
+                    submit();
+                  }
+                  else if(event.keyCode == KEY.TAB || event.keyCode == KEY.ENTER)
+                    return true;
+                  
+                  break;
+
+                case KEY.ESCAPE:
+                  hide_dropdown();
+                  return true;
+
+                default:
+                    if(String.fromCharCode(event.which)) {
+                        // set a timeout just long enough to let this function finish.
+                        setTimeout(function(){do_search();}, 5);
+                    }
+                    break;
+            }
+        });
+
+    // Keep a reference to the original input box
+    var hidden_input = $(input)
+                           .hide()
+                           .val("")
+                           .focus(function () {
+                               input_box.focus();
+                           })
+                           .blur(function () {
+                               input_box.blur();
+                           });
+
+    // Keep a reference to the selected token and dropdown item
+    var selected_token = null;
+    var selected_dropdown_item = null;
+
+    // The list to store the token items in
+    var token_list = $("<ul />")
+        .addClass(settings.classes.tokenList)
+        .click(function (event) {
+            var li = $(event.target).closest("li");
+            if(li && li.get(0) && $.data(li.get(0), "tokeninput")) {
+                toggle_select_token(li);
+            } else {
+                // Deselect selected token
+                if(selected_token) {
+                    deselect_token($(selected_token), POSITION.END);
+                }
+
+                // Focus input box
+                input_box.focus();
+            }
+        })
+        .mouseover(function (event) {
+            var li = $(event.target).closest("li");
+            if(li && selected_token !== this) {
+                li.addClass(settings.classes.highlightedToken);
+            }
+        })
+        .mouseout(function (event) {
+            var li = $(event.target).closest("li");
+            if(li && selected_token !== this) {
+                li.removeClass(settings.classes.highlightedToken);
+            }
+        })
+        .insertBefore(hidden_input);
+
+    // The token holding the input box
+    var input_token = $("<li />")
+        .addClass(settings.classes.inputToken)
+        .appendTo(token_list)
+        .append(input_box);
+
+    // The list to store the dropdown items in
+    var dropdown = $("<div>")
+        .addClass(settings.classes.dropdown)
+        .appendTo("body")
+        .hide();
+
+    // Magic element to help us resize the text input
+    var input_resizer = $("<tester/>")
+        .insertAfter(input_box)
+        .css({
+            position: "absolute",
+            top: -9999,
+            left: -9999,
+            width: "auto",
+            fontSize: input_box.css("fontSize"),
+            fontFamily: input_box.css("fontFamily"),
+            fontWeight: input_box.css("fontWeight"),
+            letterSpacing: input_box.css("letterSpacing"),
+            whiteSpace: "nowrap"
+        });
+
+    // Pre-populate list if items exist
+    hidden_input.val("");
+    li_data = settings.prePopulate || hidden_input.data("pre");
+    if(li_data && li_data.length) {
+        $.each(li_data, function (index, value) {
+            insert_token(value.id, value.name);
+        });
+    }
+    if(settings.autoFocus)
+      input_box.focus();
+      
+    // submit on blur
+    if(settings.submitOnBlur || settings.closeOnBlur) {
+      $(document).click(function(event) {
+          if(!$(token_list).find($(event.target)).length &&
+             !$(dropdown).find($(event.target)).length &&
+             settings.canBlur(event.target)) 
+          {
+              if(settings.submitOnBlur) {
+                submit();
+                $(document).unbind(event);            
+              }
+              else if(settings.closeOnBlur) {
+                var val = hidden_input.val();
+                if(val && input_box.val())
+                  val += ",";
+                val += input_box.val();                
+                hidden_input.val(val);    
+            
+                hide_dropdown();
+              }
+          }
+      });
+    }
+    //
+    // Private functions
+    //
+
+    function submit() {
+      dropdown.remove();
+      token_list.remove();
+      var val = hidden_input.val();
+      if(val && input_box.val())
+        val += ",";
+      if(input_box.val())
+        val += input_box.val();
+      var vals = val ? val.split(",") : [];
+      settings.onSubmit(vals);
+    }
+
+    function resize_input() {
+        if(input_val === (input_val = input_box.val())) {return;}
+
+        // Enter new content into resizer and resize input accordingly
+        var escaped = input_val.replace(/&/g, '&amp;').replace(/\s/g,' ').replace(/</g, '&lt;').replace(/>/g, '&gt;');
+        input_resizer.html(escaped);
+        input_box.width(input_resizer.width() + 30);
+    }
+
+    function is_printable_character(keycode) {
+        return ((keycode >= 48 && keycode <= 90) ||     // 0-1a-z
+                (keycode >= 96 && keycode <= 111) ||    // numpad 0-9 + - / * .
+                (keycode >= 186 && keycode <= 192) ||   // ; = , - . / ^
+                (keycode >= 219 && keycode <= 222));    // ( \ ) '
+    }
+
+    // Inner function to a token to the list
+    function insert_token(id, value) {
+        var paragraph = $("<p></p>");
+        paragraph.text(value);
+        var this_token = $("<li></li>")
+          .addClass(settings.classes.token)
+          .insertBefore(input_token);
+        this_token.append(paragraph);
+
+        // The 'delete token' button
+        $("<span>" + settings.deleteText + "</span>")
+            .addClass(settings.classes.tokenDelete)
+            .appendTo(this_token)
+            .click(function () {
+                delete_token($(this).parent());
+                return false;
+            });
+
+        // Store data on the token
+        var token_data = {"id": id, "name": HTMLescape(value)};
+        $.data(this_token.get(0), "tokeninput", token_data);
+
+        // Save this token for duplicate checking
+        saved_tokens.push(token_data);
+
+        // Update the hidden input
+        var token_ids = $.map(saved_tokens, function (el) {
+            return el.id;
+        });
+        hidden_input.val(token_ids.join(settings.tokenDelimiter));
+
+        token_count += 1;
+
+        return this_token;
+    }
+
+    // Add a token to the token list based on user input
+    function add_token (item) {
+        var li_data = $.data(item.get(0), "tokeninput");
+        var callback = settings.onAdd;
+
+        // See if the token already exists and select it if we don't want duplicates
+        if(token_count > 0 && settings.preventDuplicates) {
+            var found_existing_token = null;
+            token_list.children().each(function () {
+                var existing_token = $(this);
+                var existing_data = $.data(existing_token.get(0), "tokeninput");
+                if(existing_data && existing_data.id === li_data.id) {
+                    found_existing_token = existing_token;
+                    return false;
+                }
+            });
+
+            if(found_existing_token) {
+                select_token(found_existing_token);
+                input_token.insertAfter(found_existing_token);
+                input_box.focus();
+                return;
+            }
+        }
+
+        // Insert the new tokens
+        insert_token(li_data.id, li_data.name);
+
+        // Check the token limit
+        if(settings.tokenLimit !== null && token_count >= settings.tokenLimit) {
+            input_box.hide();
+            hide_dropdown();
+            return;
+        } else {
+            input_box.focus();
+        }
+
+        // Clear input box
+        input_box.val("");
+
+        // Don't show the help dropdown, they've got the idea
+        hide_dropdown();
+
+        // Execute the onAdd callback if defined
+        if($.isFunction(callback)) {
+            callback(li_data);
+        }
+    }
+
+    // Select a token in the token list
+    function select_token (token) {
+        token.addClass(settings.classes.selectedToken);
+        selected_token = token.get(0);
+
+        // Hide input box
+        input_box.val("");
+
+        // Hide dropdown if it is visible (eg if we clicked to select token)
+        hide_dropdown();
+    }
+
+    // Deselect a token in the token list
+    function deselect_token (token, position) {
+        token.removeClass(settings.classes.selectedToken);
+        selected_token = null;
+
+        if(position === POSITION.BEFORE) {
+            input_token.insertBefore(token);
+        } else if(position === POSITION.AFTER) {
+            input_token.insertAfter(token);
+        } else {
+            input_token.appendTo(token_list);
+        }
+
+        // Show the input box and give it focus again
+        input_box.focus();
+    }
+
+    // Toggle selection of a token in the token list
+    function toggle_select_token(token) {
+        var previous_selected_token = selected_token;
+
+        if(selected_token) {
+            deselect_token($(selected_token), POSITION.END);
+        }
+
+        if(previous_selected_token === token.get(0)) {
+            deselect_token(token, POSITION.END);
+        } else {
+            select_token(token);
+        }
+    }
+
+    // Delete a token from the token list
+    function delete_token (token) {
+        // Remove the id from the saved list
+        var token_data = $.data(token.get(0), "tokeninput");
+        var callback = settings.onDelete;
+
+        // Delete the token
+        token.remove();
+        selected_token = null;
+
+        // Show the input box and give it focus again
+        input_box.focus();
+
+        // Remove this token from the saved list
+        saved_tokens = $.grep(saved_tokens, function (val) {
+            return (val.id !== token_data.id);
+        });
+
+        // Update the hidden input
+        var token_ids = $.map(saved_tokens, function (el) {
+            return el.id;
+        });
+        hidden_input.val(token_ids.join(settings.tokenDelimiter));
+
+        token_count -= 1;
+
+        if(settings.tokenLimit !== null) {
+            input_box
+                .show()
+                .val("")
+                .focus();
+        }
+
+        // Execute the onDelete callback if defined
+        if($.isFunction(callback)) {
+            callback(token_data);
+        }
+    }
+
+    // Hide and clear the results dropdown
+    function hide_dropdown () {
+        dropdown.hide().empty();
+        selected_dropdown_item = null;
+    }
+
+    function show_dropdown() {
+        dropdown
+            .css({
+                position: "absolute",
+                top: $(token_list).offset().top + $(token_list).outerHeight(),
+                left: $(token_list).offset().left,
+                zindex: 999
+            })
+            .show();
+    }
+
+    function show_dropdown_searching () {
+        if(settings.searchingText) {
+            dropdown.html("<p>"+settings.searchingText+"</p>");
+            show_dropdown();
+        }
+    }
+
+    function show_dropdown_hint () {
+        if(settings.hintText) {
+            dropdown.html("<p>"+settings.hintText+"</p>");
+            show_dropdown();
+        }
+    }
+
+    // Highlight the query part of the search term
+    function highlight_term(value, term) {
+        value = HTMLescape(value);
+        return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<b>$1</b>");
+    }
+
+    // Populate the results dropdown with some results
+    function populate_dropdown (query, results) {
+        if(results && results.length) {
+            dropdown.empty();
+            var dropdown_ul = $("<ul>")
+                .appendTo(dropdown)
+                .mouseover(function (event) {
+                    select_dropdown_item($(event.target).closest("li"));
+                })
+                .mousedown(function (event) {
+                    add_token($(event.target).closest("li"));
+                    return false;
+                })
+                .hide();
+
+            $.each(results, function(index, value) {
+                var this_li = $("<li>" + highlight_term(value.name, query) + "</li>")
+                                  .appendTo(dropdown_ul);
+
+                
+                if(index % 2) {
+                    this_li.addClass(settings.classes.dropdownItem);
+                } else {
+                    this_li.addClass(settings.classes.dropdownItem2);
+                }
+
+                if(index === 0 && settings.autoSelect) {   
+                    select_dropdown_item(this_li);
+                }
+
+                $.data(this_li.get(0), "tokeninput", {"id": value.id, "name": value.name});
+            });
+
+            show_dropdown();
+
+            if(settings.animateDropdown) {
+                dropdown_ul.slideDown("fast");
+            } else {
+                dropdown_ul.show();
+            }
+        } else {
+            if(settings.noResultsText) {
+                dropdown.html("<p>"+settings.noResultsText+"</p>");
+                show_dropdown();
+            }
+        }
+    }
+
+    // Highlight an item in the results dropdown
+    function select_dropdown_item (item) {
+        if(item) {
+            if(selected_dropdown_item) {
+                deselect_dropdown_item($(selected_dropdown_item));
+            }
+
+            item.addClass(settings.classes.selectedDropdownItem);
+            selected_dropdown_item = item.get(0);
+        }
+    }
+
+    // Remove highlighting from an item in the results dropdown
+    function deselect_dropdown_item (item) {
+        item.removeClass(settings.classes.selectedDropdownItem);
+        selected_dropdown_item = null;
+    }
+
+    // Do a search and show the "searching" dropdown if the input is longer
+    // than settings.minChars
+    function do_search() {
+        var query = input_box.val().toLowerCase();
+
+        if(query && query.length) {
+            if(selected_token) {
+                deselect_token($(selected_token), POSITION.AFTER);
+            }
+
+            if(query.length >= settings.minChars) {
+                show_dropdown_searching();
+                clearTimeout(timeout);
+
+                timeout = setTimeout(function(){
+                    run_search(query);
+                }, settings.searchDelay);
+            } else {
+                hide_dropdown();
+            }
+        }
+    }
+
+    // Do the actual search
+    function run_search(query) {
+        var cached_results = cache.get(query);
+        if(cached_results) {
+            populate_dropdown(query, cached_results);
+        } else {
+            // Are we doing an ajax search or local data search?
+            if(settings.url) {
+                // Extract exisiting get params
+                var ajax_params = {};
+                ajax_params.data = {};
+                if(settings.url.indexOf("?") > -1) {
+                    var parts = settings.url.split("?");
+                    ajax_params.url = parts[0];
+
+                    var param_array = parts[1].split("&");
+                    $.each(param_array, function (index, value) {
+                        var kv = value.split("=");
+                        ajax_params.data[kv[0]] = kv[1];
+                    });
+                } else {
+                    ajax_params.url = settings.url;
+                }
+
+                // Prepare the request
+                ajax_params.data[settings.queryParam] = query;
+                ajax_params.type = settings.method;
+                ajax_params.dataType = settings.contentType;
+                if(settings.crossDomain) {
+                    ajax_params.dataType = "jsonp";
+                }
+
+                // Attach the success callback
+                ajax_params.success = function(results) {
+                  if($.isFunction(settings.onResult)) {
+                      results = settings.onResult.call(this, results);
+                  }
+                  cache.add(query, settings.jsonContainer ? results[settings.jsonContainer] : results);
+
+                  // only populate the dropdown if the results are associated with the active search query
+                  if(input_box.val().toLowerCase() === query) {
+                      populate_dropdown(query, settings.jsonContainer ? results[settings.jsonContainer] : results);
+                  }
+                };
+
+                // Make the request
+                $.ajax(ajax_params);
+            } else if(settings.local_data) {
+                // Do the search through local data
+                var results = $.grep(settings.local_data, function (row) {
+                    return row.name.toLowerCase().indexOf(query.toLowerCase()) > -1;
+                });
+                populate_dropdown(query, results);
+            }
+        }
+    }
+};
+
+// Really basic cache for the results
+$.TokenList.Cache = function (options) {
+    var settings = $.extend({
+        max_size: 500
+    }, options);
+
+    var data = {};
+    var size = 0;
+
+    var flush = function () {
+        data = {};
+        size = 0;
+    };
+
+    this.add = function (query, results) {
+        if(size > settings.max_size) {
+            flush();
+        }
+
+        if(!data[query]) {
+            size += 1;
+        }
+
+        data[query] = results;
+    };
+
+    this.get = function (query) {
+        return data[query];
+    };
+};
+}(jQuery));
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/toolbox/static/js/less-1.0.41.min.js	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,69 @@
+//
+// LESS - Leaner CSS v1.0.41
+// http://lesscss.org
+// 
+// Copyright (c) 2010, Alexis Sellier
+// Licensed under the Apache 2.0 License.
+//
+(function(z){function s(d){return z.less[d.split("/")[1]]}function U(){for(var d=document.getElementsByTagName("style"),b=0;b<d.length;b++)if(d[b].type.match(V))(new o.Parser).parse(d[b].innerHTML||"",function(a,g){d[b].type="text/css";d[b].innerHTML=g.toCSS()})}function W(d,b){for(var a=0;a<o.sheets.length;a++)X(o.sheets[a],d,b,o.sheets.length-(a+1))}function X(d,b,a,g){var e=z.location.href.replace(/[#?].*$/,""),h=d.href.replace(/\?.*$/,""),i=B&&B.getItem(h),k=B&&B.getItem(h+":timestamp"),n={css:i,
+timestamp:k};/^(https?|file):/.test(h)||(h=e.slice(0,e.lastIndexOf("/")+1)+h);Z(d.href,d.type,function(r,p){if(!a&&n&&p&&(new Date(p)).valueOf()===(new Date(n.timestamp)).valueOf()){N(n.css,d);b(null,d,{local:true,remaining:g})}else try{(new o.Parser({optimization:o.optimization,paths:[h.replace(/[\w\.-]+$/,"")],mime:d.type})).parse(r,function(K,O){if(K)return Q(K,h);try{b(O,d,{local:false,lastModified:p,remaining:g});var F=document.getElementById("less-error-message:"+R(h));F&&F.parentNode.removeChild(F)}catch(f){Q(f,
+h)}})}catch(t){Q(t,h)}},function(r,p){throw Error("Couldn't load "+p+" ("+r+")");})}function R(d){return d.replace(/^[a-z]+:\/\/?[^\/]+/,"").replace(/^\//,"").replace(/\?.*$/,"").replace(/\.[^\.\/]+$/,"").replace(/[^\.\w-]+/g,"-").replace(/\./g,":")}function N(d,b,a){var g,e=b.href?b.href.replace(/\?.*$/,""):"",h="less:"+(b.title||R(e));if((g=document.getElementById(h))===null){g=document.createElement("style");g.type="text/css";g.media=b.media||"screen";g.id=h;document.getElementsByTagName("head")[0].appendChild(g)}if(g.styleSheet)try{g.styleSheet.cssText=
+d}catch(i){throw Error("Couldn't reassign styleSheet.cssText.");}else(function(k){if(g.childNodes.length>0)g.firstChild.nodeValue!==k.nodeValue&&g.replaceChild(k,g.firstChild);else g.appendChild(k)})(document.createTextNode(d));if(a&&B){H("saving "+e+" to cache.");B.setItem(e,d);B.setItem(e+":timestamp",a)}}function Z(d,b,a,g){function e(k,n,r){if(k.status>=200&&k.status<300)n(k.responseText,k.getResponseHeader("Last-Modified"));else typeof r==="function"&&r(k.status,d)}var h=$(),i=P?false:o.async;
+typeof h.overrideMimeType==="function"&&h.overrideMimeType("text/css");h.open("GET",d,i);h.setRequestHeader("Accept",b||"text/x-less, text/css; q=0.9, */*; q=0.5");h.send(null);if(P)h.status===0?a(h.responseText):g(h.status,d);else if(i)h.onreadystatechange=function(){h.readyState==4&&e(h,a,g)};else e(h,a,g)}function $(){if(z.XMLHttpRequest)return new XMLHttpRequest;else try{return new ActiveXObject("MSXML2.XMLHTTP.3.0")}catch(d){H("browser doesn't support AJAX.");return null}}function H(d){o.env==
+"development"&&typeof console!=="undefined"&&console.log("less: "+d)}function Q(d,b){var a="less-error-message:"+R(b),g=document.createElement("div"),e,h;g.id=a;g.className="less-error-message";h="<h3>"+(d.message||"There is an error in your .less file")+'</h3><p><a href="'+b+'">'+b+"</a> ";if(d.extract)h+="on line "+d.line+", column "+(d.column+1)+":</p>"+'<ul>\n<li><label>[-1]</label><pre class="ctx">{0}</pre></li>\n<li><label>[0]</label><pre>{current}</pre></li>\n<li><label>[1]</label><pre class="ctx">{2}</pre></li>\n</ul>'.replace(/\[(-?\d)\]/g,
+function(i,k){return parseInt(d.line)+parseInt(k)||""}).replace(/\{(\d)\}/g,function(i,k){return d.extract[parseInt(k)]||""}).replace(/\{current\}/,d.extract[1].slice(0,d.column)+'<span class="error">'+d.extract[1].slice(d.column)+"</span>");g.innerHTML=h;N(".less-error-message ul, .less-error-message li {\nlist-style-type: none;\nmargin-right: 15px;\npadding: 4px 0;\nmargin: 0;\n}\n.less-error-message label {\nfont-size: 12px;\nmargin-right: 15px;\npadding: 4px 0;\ncolor: #cc7777;\n}\n.less-error-message pre {\ncolor: #ee4444;\npadding: 4px 0;\nmargin: 0;\ndisplay: inline-block;\n}\n.less-error-message pre.ctx {\ncolor: #dd4444;\n}\n.less-error-message h3 {\nfont-size: 20px;\nfont-weight: bold;\npadding: 15px 0 5px 0;\nmargin: 0;\n}\n.less-error-message a {\ncolor: #10a\n}\n.less-error-message .error {\ncolor: red;\nfont-weight: bold;\npadding-bottom: 2px;\nborder-bottom: 1px dashed red;\n}",
+{title:"error-message"});g.style.cssText="font-family: Arial, sans-serif;border: 1px solid #e00;background-color: #eee;border-radius: 5px;-webkit-border-radius: 5px;-moz-border-radius: 5px;color: #e00;padding: 15px;margin-bottom: 15px";if(o.env=="development")e=setInterval(function(){if(document.body){document.getElementById(a)?document.body.replaceChild(g,document.getElementById(a)):document.body.insertBefore(g,document.body.firstChild);clearInterval(e)}},10)}if(!Array.isArray)Array.isArray=function(d){return Object.prototype.toString.call(d)===
+"[object Array]"||d instanceof Array};if(!Array.prototype.forEach)Array.prototype.forEach=function(d,b){for(var a=this.length>>>0,g=0;g<a;g++)g in this&&d.call(b,this[g],g,this)};if(!Array.prototype.map)Array.prototype.map=function(d,b){for(var a=this.length>>>0,g=Array(a),e=0;e<a;e++)if(e in this)g[e]=d.call(b,this[e],e,this);return g};if(!Array.prototype.filter)Array.prototype.filter=function(d,b){for(var a=[],g=0;g<this.length;g++)d.call(b,this[g])&&a.push(this[g]);return a};if(!Array.prototype.reduce)Array.prototype.reduce=
+function(d){var b=this.length>>>0,a=0;if(b===0&&arguments.length===1)throw new TypeError;if(arguments.length>=2)var g=arguments[1];else{do{if(a in this){g=this[a++];break}if(++a>=b)throw new TypeError;}while(1)}for(;a<b;a++)if(a in this)g=d.call(null,g,this[a],a,this);return g};if(!Array.prototype.indexOf)Array.prototype.indexOf=function(d,b){var a=this.length,g=b||0;if(!a)return-1;if(g>=a)return-1;if(g<0)g+=a;for(;g<a;g++)if(Object.prototype.hasOwnProperty.call(this,g))if(d===this[g])return g;return-1};
+if(!Object.keys)Object.keys=function(d){var b=[],a;for(a in d)Object.prototype.hasOwnProperty.call(d,a)&&b.push(a);return b};if(!String.prototype.trim)String.prototype.trim=function(){return String(this).replace(/^\s\s*/,"").replace(/\s\s*$/,"")};var o,m;if(typeof z==="undefined"){o=exports;m=s("less/tree")}else{if(typeof z.less==="undefined")z.less={};o=z.less;m=z.less.tree={}}o.Parser=function(d){function b(){if(h>t){p[i]=p[i].slice(h-t);t=h}}function a(f){var j,l,q;if(f instanceof Function)return f.call(K.parsers);
+else if(typeof f==="string"){f=e.charAt(h)===f?f:null;j=1;b()}else{b();if(f=f.exec(p[i]))j=f[0].length;else return null}if(f){mem=h+=j;for(q=h+p[i].length-j;h<q;){l=e.charCodeAt(h);if(!(l===32||l===10||l===9))break;h++}p[i]=p[i].slice(j+(h-mem));t=h;p[i].length===0&&i<p.length-1&&i++;return typeof f==="string"?f:f.length===1?f[0]:f}}function g(f){return typeof f==="string"?e.charAt(h)===f:f.test(p[i])?true:false}var e,h,i,k,n,r,p,t,K,O=function(){},F=this.imports={paths:d&&d.paths||[],queue:[],files:{},
+mime:d&&d.mime,push:function(f,j){var l=this;this.queue.push(f);o.Parser.importer(f,this.paths,function(q){l.queue.splice(l.queue.indexOf(f),1);l.files[f]=q;j(q);l.queue.length===0&&O()},d)}};this.env=d=d||{};this.optimization="optimization"in this.env?this.env.optimization:1;this.env.filename=this.env.filename||null;return K={imports:F,parse:function(f,j){var l,q,I,S=null;h=i=t=r=0;p=[];e=f.replace(/\r\n/g,"\n");p=function(L){for(var D=0,E=/[^"'`\{\}\/]+/g,G=/\/\*(?:[^*]|\*+[^\/*])*\*+\/|\/\/.*/g,
+A=0,w,x=L[0],y,u=0,v;u<e.length;u++){E.lastIndex=u;if(w=E.exec(e))if(w.index===u){u+=w[0].length;x.push(w[0])}v=e.charAt(u);G.lastIndex=u;if(!y&&v==="/"){w=e.charAt(u+1);if(w==="/"||w==="*")if(w=G.exec(e))if(w.index===u){u+=w[0].length;x.push(w[0]);v=e.charAt(u)}}if(v==="{"&&!y){A++;x.push(v)}else if(v==="}"&&!y){A--;x.push(v);L[++D]=x=[]}else{if(v==='"'||v==="'"||v==="`")y=y?y===v?false:y:v;x.push(v)}}if(A>0)throw{type:"Syntax",message:"Missing closing `}`",filename:d.filename};return L.map(function(C){return C.join("")})}([[]]);
+l=new m.Ruleset([],a(this.parsers.primary));l.root=true;l.toCSS=function(L){var D,E;return function(G,A){function w(v){return v?(e.slice(0,v).match(/\n/g)||"").length:null}var x=[];G=G||{};if(typeof A==="object"&&!Array.isArray(A)){A=Object.keys(A).map(function(v){var C=A[v];if(!(C instanceof m.Value)){C instanceof m.Expression||(C=new m.Expression([C]));C=new m.Value([C])}return new m.Rule("@"+v,C,false,0)});x=[new m.Ruleset(null,A)]}try{var y=L.call(this,{frames:x}).toCSS([],{compress:G.compress||
+false})}catch(u){E=e.split("\n");D=w(u.index);x=u.index;for(y=-1;x>=0&&e.charAt(x)!=="\n";x--)y++;throw{type:u.type,message:u.message,filename:d.filename,index:u.index,line:typeof D==="number"?D+1:null,callLine:u.call&&w(u.call)+1,callExtract:E[w(u.call)],stack:u.stack,column:y,extract:[E[D-1],E[D],E[D+1]]};}return G.compress?y.replace(/(\s)+/g,"$1"):y}}(l.eval);if(h<e.length-1){h=r;I=e.split("\n");q=(e.slice(0,h).match(/\n/g)||"").length+1;for(var T=h,Y=-1;T>=0&&e.charAt(T)!=="\n";T--)Y++;S={name:"ParseError",
+message:"Syntax Error on line "+q,filename:d.filename,line:q,column:Y,extract:[I[q-2],I[q-1],I[q]]}}if(this.imports.queue.length>0)O=function(){j(S,l)};else j(S,l)},parsers:{primary:function(){for(var f,j=[];(f=a(this.mixin.definition)||a(this.rule)||a(this.ruleset)||a(this.mixin.call)||a(this.comment)||a(this.directive))||a(/^[\s\n]+/);)f&&j.push(f);return j},comment:function(){var f;if(e.charAt(h)==="/")if(e.charAt(h+1)==="/")return new m.Comment(a(/^\/\/.*/),true);else if(f=a(/^\/\*(?:[^*]|\*+[^\/*])*\*+\/\n?/))return new m.Comment(f)},
+entities:{quoted:function(){var f;if(!(e.charAt(h)!=='"'&&e.charAt(h)!=="'"))if(f=a(/^"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'/))return new m.Quoted(f[0],f[1]||f[2])},keyword:function(){var f;if(f=a(/^[A-Za-z-]+/))return new m.Keyword(f)},call:function(){var f,j;if(f=/^([\w-]+|%)\(/.exec(p[i])){f=f[1].toLowerCase();if(f==="url")return null;else h+=f.length+1;if(f==="alpha")return a(this.alpha);j=a(this.entities.arguments);if(a(")"))if(f)return new m.Call(f,j)}},arguments:function(){for(var f=
+[],j;j=a(this.expression);){f.push(j);if(!a(","))break}return f},literal:function(){return a(this.entities.dimension)||a(this.entities.color)||a(this.entities.quoted)},url:function(){var f;if(!(e.charAt(h)!=="u"||!a(/^url\(/))){f=a(this.entities.quoted)||a(this.entities.variable)||a(this.entities.dataURI)||a(/^[-\w%@$\/.&=:;#+?]+/)||"";if(!a(")"))throw Error("missing closing ) for url()");return new m.URL(f.value||f.data||f instanceof m.Variable?f:new m.Anonymous(f),F.paths)}},dataURI:function(){var f;
+if(a(/^data:/)){f={};f.mime=a(/^[^\/]+\/[^,;)]+/)||"";f.charset=a(/^;\s*charset=[^,;)]+/)||"";f.base64=a(/^;\s*base64/)||"";f.data=a(/^,\s*[^)]+/);if(f.data)return f}},variable:function(){var f,j=h;if(e.charAt(h)==="@"&&(f=a(/^@[\w-]+/)))return new m.Variable(f,j)},color:function(){var f;if(e.charAt(h)==="#"&&(f=a(/^#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})/)))return new m.Color(f[1])},dimension:function(){var f;f=e.charCodeAt(h);if(!(f>57||f<45||f===47))if(f=a(/^(-?\d*\.?\d+)(px|%|em|pc|ex|in|deg|s|ms|pt|cm|mm|rad|grad|turn)?/))return new m.Dimension(f[1],
+f[2])},javascript:function(){var f;if(e.charAt(h)==="`")if(f=a(/^`([^`]*)`/))return new m.JavaScript(f[1],h)}},variable:function(){var f;if(e.charAt(h)==="@"&&(f=a(/^(@[\w-]+)\s*:/)))return f[1]},shorthand:function(){var f,j;if(g(/^[@\w.%-]+\/[@\w.-]+/))if((f=a(this.entity))&&a("/")&&(j=a(this.entity)))return new m.Shorthand(f,j)},mixin:{call:function(){var f=[],j,l,q,I=h;j=e.charAt(h);if(!(j!=="."&&j!=="#")){for(;j=a(/^[#.](?:[\w-]|\\(?:[a-fA-F0-9]{1,6} ?|[^a-fA-F0-9]))+/);){f.push(new m.Element(l,
+j));l=a(">")}a("(")&&(q=a(this.entities.arguments))&&a(")");if(f.length>0&&(a(";")||g("}")))return new m.mixin.Call(f,q,I)}},definition:function(){var f,j=[],l,q;if(!(e.charAt(h)!=="."&&e.charAt(h)!=="#"||g(/^[^{]*(;|})/)))if(f=a(/^([#.](?:[\w-]|\\(?:[a-fA-F0-9]{1,6} ?|[^a-fA-F0-9]))+)\s*\(/)){for(f=f[1];l=a(this.entities.variable)||a(this.entities.literal)||a(this.entities.keyword);){if(l instanceof m.Variable)if(a(":"))if(q=a(this.expression))j.push({name:l.name,value:q});else throw Error("Expected value");
+else j.push({name:l.name});else j.push({value:l});if(!a(","))break}if(!a(")"))throw Error("Expected )");if(l=a(this.block))return new m.mixin.Definition(f,j,l)}}},entity:function(){return a(this.entities.literal)||a(this.entities.variable)||a(this.entities.url)||a(this.entities.call)||a(this.entities.keyword)||a(this.entities.javascript)},end:function(){return a(";")||g("}")},alpha:function(){var f;if(a(/^opacity=/i))if(f=a(/^\d+/)||a(this.entities.variable)){if(!a(")"))throw Error("missing closing ) for alpha()");
+return new m.Alpha(f)}},element:function(){var f;c=a(this.combinator);if(f=a(/^(?:[.#]?|:*)(?:[\w-]|\\(?:[a-fA-F0-9]{1,6} ?|[^a-fA-F0-9]))+/)||a("*")||a(this.attribute)||a(/^\([^)@]+\)/))return new m.Element(c,f)},combinator:function(){var f=e.charAt(h);if(f===">"||f==="&"||f==="+"||f==="~"){for(h++;e.charAt(h)===" ";)h++;return new m.Combinator(f)}else if(f===":"&&e.charAt(h+1)===":"){for(h+=2;e.charAt(h)===" ";)h++;return new m.Combinator("::")}else return e.charAt(h-1)===" "?new m.Combinator(" "):
+new m.Combinator(null)},selector:function(){for(var f,j=[],l;f=a(this.element);){l=e.charAt(h);j.push(f);if(l==="{"||l==="}"||l===";"||l===",")break}if(j.length>0)return new m.Selector(j)},tag:function(){return a(/^[a-zA-Z][a-zA-Z-]*[0-9]?/)||a("*")},attribute:function(){var f="",j,l,q;if(a("[")){if(j=a(/^[a-zA-Z-]+/)||a(this.entities.quoted))f=(q=a(/^[|~*$^]?=/))&&(l=a(this.entities.quoted)||a(/^[\w-]+/))?[j,q,l.toCSS?l.toCSS():l].join(""):j;if(a("]"))if(f)return"["+f+"]"}},block:function(){var f;
+if(a("{")&&(f=a(this.primary))&&a("}"))return f},ruleset:function(){var f=[],j,l;k=p[i];t=n=h;if(j=/^([.#: \w-]+)[\s\n]*\{/.exec(p[i])){h+=j[0].length-1;f=[new m.Selector([new m.Element(null,j[1])])]}else{for(;j=a(this.selector);){f.push(j);if(!a(","))break}j&&a(this.comment)}if(f.length>0&&(l=a(this.block)))return new m.Ruleset(f,l);else{r=h;p[i]=k;t=h=n}},rule:function(){var f,j;f=e.charAt(h);var l;k=p[i];t=n=h;if(!(f==="."||f==="#"||f==="&"))if(f=a(this.variable)||a(this.property)){if(f.charAt(0)!=
+"@"&&(match=/^([^@+\/'"*`(;{}-]*);/.exec(p[i]))){h+=match[0].length-1;j=new m.Anonymous(match[1])}else j=f==="font"?a(this.font):a(this.value);l=a(this.important);if(j&&a(this.end))return new m.Rule(f,j,l,n);else{r=h;p[i]=k;t=h=n}}},"import":function(){var f;if(a(/^@import\s+/)&&(f=a(this.entities.quoted)||a(this.entities.url))&&a(";"))return new m.Import(f,F)},directive:function(){var f,j,l;if(e.charAt(h)==="@")if(j=a(this["import"]))return j;else if(f=a(/^@media|@page/)){l=(a(/^[^{]+/)||"").trim();
+if(j=a(this.block))return new m.Directive(f+" "+l,j)}else if(f=a(/^@[-a-z]+/))if(f==="@font-face"){if(j=a(this.block))return new m.Directive(f,j)}else if((j=a(this.entity))&&a(";"))return new m.Directive(f,j)},font:function(){for(var f=[],j=[],l;l=a(this.shorthand)||a(this.entity);)j.push(l);f.push(new m.Expression(j));if(a(","))for(;l=a(this.expression);){f.push(l);if(!a(","))break}return new m.Value(f)},value:function(){for(var f,j=[];f=a(this.expression);){j.push(f);if(!a(","))break}if(j.length>
+0)return new m.Value(j)},important:function(){if(e.charAt(h)==="!")return a(/^! *important/)},sub:function(){var f;if(a("(")&&(f=a(this.expression))&&a(")"))return f},multiplication:function(){var f,j,l,q;if(f=a(this.operand)){for(;(l=a("/")||a("*"))&&(j=a(this.operand));)q=new m.Operation(l,[q||f,j]);return q||f}},addition:function(){var f,j,l,q;if(f=a(this.multiplication)){for(;(l=a(/^[-+]\s+/)||e.charAt(h-1)!=" "&&(a("+")||a("-")))&&(j=a(this.multiplication));)q=new m.Operation(l,[q||f,j]);return q||
+f}},operand:function(){return a(this.sub)||a(this.entities.dimension)||a(this.entities.color)||a(this.entities.variable)||a(this.entities.call)},expression:function(){for(var f,j=[];f=a(this.addition)||a(this.entity);)j.push(f);if(j.length>0)return new m.Expression(j)},property:function(){var f;if(f=a(/^(\*?-?[-a-z_0-9]+)\s*:/))return f[1]}}}};if(typeof z!=="undefined")o.Parser.importer=function(d,b,a,g){if(d.charAt(0)!=="/"&&b.length>0)d=b[0]+d;X({href:d,title:d,type:g.mime},a,true)};(function(d){function b(e){return d.functions.hsla(e.h,
+e.s,e.l,e.a)}function a(e){if(e instanceof d.Dimension)return parseFloat(e.unit=="%"?e.value/100:e.value);else if(typeof e==="number")return e;else throw{error:"RuntimeError",message:"color functions take numbers as parameters"};}function g(e){return Math.min(1,Math.max(0,e))}d.functions={rgb:function(e,h,i){return this.rgba(e,h,i,1)},rgba:function(e,h,i,k){e=[e,h,i].map(function(n){return a(n)});k=a(k);return new d.Color(e,k)},hsl:function(e,h,i){return this.hsla(e,h,i,1)},hsla:function(e,h,i,k){function n(t){t=
+t<0?t+1:t>1?t-1:t;return t*6<1?p+(r-p)*t*6:t*2<1?r:t*3<2?p+(r-p)*(2/3-t)*6:p}e=a(e)%360/360;h=a(h);i=a(i);k=a(k);var r=i<=0.5?i*(h+1):i+h-i*h,p=i*2-r;return this.rgba(n(e+1/3)*255,n(e)*255,n(e-1/3)*255,k)},hue:function(e){return new d.Dimension(Math.round(e.toHSL().h))},saturation:function(e){return new d.Dimension(Math.round(e.toHSL().s*100),"%")},lightness:function(e){return new d.Dimension(Math.round(e.toHSL().l*100),"%")},alpha:function(e){return new d.Dimension(e.toHSL().a)},saturate:function(e,
+h){var i=e.toHSL();i.s+=h.value/100;i.s=g(i.s);return b(i)},desaturate:function(e,h){var i=e.toHSL();i.s-=h.value/100;i.s=g(i.s);return b(i)},lighten:function(e,h){var i=e.toHSL();i.l+=h.value/100;i.l=g(i.l);return b(i)},darken:function(e,h){var i=e.toHSL();i.l-=h.value/100;i.l=g(i.l);return b(i)},fadein:function(e,h){var i=e.toHSL();i.a+=h.value/100;i.a=g(i.a);return b(i)},fadeout:function(e,h){var i=e.toHSL();i.a-=h.value/100;i.a=g(i.a);return b(i)},spin:function(e,h){var i=e.toHSL(),k=(i.h+h.value)%
+360;i.h=k<0?360+k:k;return b(i)},mix:function(e,h,i){i=i.value/100;var k=i*2-1,n=e.toHSL().a-h.toHSL().a;k=((k*n==-1?k:(k+n)/(1+k*n))+1)/2;n=1-k;return new d.Color([e.rgb[0]*k+h.rgb[0]*n,e.rgb[1]*k+h.rgb[1]*n,e.rgb[2]*k+h.rgb[2]*n],e.alpha*i+h.alpha*(1-i))},greyscale:function(e){return this.desaturate(e,new d.Dimension(100))},e:function(e){return new d.Anonymous(e instanceof d.JavaScript?e.evaluated:e)},"%":function(e){for(var h=Array.prototype.slice.call(arguments,1),i=e.value,k=0;k<h.length;k++)i=
+i.replace(/%s/,h[k].value).replace(/%[da]/,h[k].toCSS());i=i.replace(/%%/g,"%");return new d.Quoted('"'+i+'"',i)}}})(s("less/tree"));(function(d){d.Alpha=function(b){this.value=b};d.Alpha.prototype={toCSS:function(){return"alpha(opacity="+(this.value.toCSS?this.value.toCSS():this.value)+")"},eval:function(){return this}}})(s("less/tree"));(function(d){d.Anonymous=function(b){this.value=b.value||b};d.Anonymous.prototype={toCSS:function(){return this.value},eval:function(){return this}}})(s("less/tree"));
+(function(d){d.Call=function(b,a){this.name=b;this.args=a};d.Call.prototype={eval:function(b){var a=this.args.map(function(g){return g.eval(b)});return this.name in d.functions?d.functions[this.name].apply(d.functions,a):new d.Anonymous(this.name+"("+a.map(function(g){return g.toCSS()}).join(", ")+")")},toCSS:function(b){return this.eval(b).toCSS()}}})(s("less/tree"));(function(d){d.Color=function(b,a){this.rgb=Array.isArray(b)?b:b.length==6?b.match(/.{2}/g).map(function(g){return parseInt(g,16)}):
+b.split("").map(function(g){return parseInt(g+g,16)});this.alpha=typeof a==="number"?a:1};d.Color.prototype={eval:function(){return this},toCSS:function(){return this.alpha<1?"rgba("+this.rgb.map(function(b){return Math.round(b)}).concat(this.alpha).join(", ")+")":"#"+this.rgb.map(function(b){b=Math.round(b);b=(b>255?255:b<0?0:b).toString(16);return b.length===1?"0"+b:b}).join("")},operate:function(b,a){var g=[];a instanceof d.Color||(a=a.toColor());for(var e=0;e<3;e++)g[e]=d.operate(b,this.rgb[e],
+a.rgb[e]);return new d.Color(g)},toHSL:function(){var b=this.rgb[0]/255,a=this.rgb[1]/255,g=this.rgb[2]/255,e=this.alpha,h=Math.max(b,a,g),i=Math.min(b,a,g),k,n=(h+i)/2,r=h-i;if(h===i)k=i=0;else{i=n>0.5?r/(2-h-i):r/(h+i);switch(h){case b:k=(a-g)/r+(a<g?6:0);break;case a:k=(g-b)/r+2;break;case g:k=(b-a)/r+4}k/=6}return{h:k*360,s:i,l:n,a:e}}}})(s("less/tree"));(function(d){d.Comment=function(b,a){this.value=b;this.silent=!!a};d.Comment.prototype={toCSS:function(b){return b.compress?"":this.value},eval:function(){return this}}})(s("less/tree"));
+(function(d){d.Dimension=function(b,a){this.value=parseFloat(b);this.unit=a||null};d.Dimension.prototype={eval:function(){return this},toColor:function(){return new d.Color([this.value,this.value,this.value])},toCSS:function(){return this.value+this.unit},operate:function(b,a){return new d.Dimension(d.operate(b,this.value,a.value),this.unit||a.unit)}}})(s("less/tree"));(function(d){d.Directive=function(b,a){this.name=b;if(Array.isArray(a))this.ruleset=new d.Ruleset([],a);else this.value=a};d.Directive.prototype=
+{toCSS:function(b,a){if(this.ruleset){this.ruleset.root=true;return this.name+(a.compress?"{":" {\n  ")+this.ruleset.toCSS(b,a).trim().replace(/\n/g,"\n  ")+(a.compress?"}":"\n}\n")}else return this.name+" "+this.value.toCSS()+";\n"},eval:function(b){b.frames.unshift(this);this.ruleset=this.ruleset&&this.ruleset.eval(b);b.frames.shift();return this},variable:function(b){return d.Ruleset.prototype.variable.call(this.ruleset,b)},find:function(){return d.Ruleset.prototype.find.apply(this.ruleset,arguments)},
+rulesets:function(){return d.Ruleset.prototype.rulesets.apply(this.ruleset)}}})(s("less/tree"));(function(d){d.Element=function(b,a){this.combinator=b instanceof d.Combinator?b:new d.Combinator(b);this.value=a.trim()};d.Element.prototype.toCSS=function(b){return this.combinator.toCSS(b||{})+this.value};d.Combinator=function(b){this.value=b===" "?" ":b?b.trim():""};d.Combinator.prototype.toCSS=function(b){return{"":""," ":" ","&":"",":":" :","::":"::","+":b.compress?"+":" + ","~":b.compress?"~":" ~ ",
+">":b.compress?">":" > "}[this.value]}})(s("less/tree"));(function(d){d.Expression=function(b){this.value=b};d.Expression.prototype={eval:function(b){return this.value.length>1?new d.Expression(this.value.map(function(a){return a.eval(b)})):this.value[0].eval(b)},toCSS:function(b){return this.value.map(function(a){return a.toCSS(b)}).join(" ")}}})(s("less/tree"));(function(d){d.Import=function(b,a){var g=this;this._path=b;this.path=b instanceof d.Quoted?/\.(le?|c)ss$/.test(b.value)?b.value:b.value+
+".less":b.value.value||b.value;(this.css=/css$/.test(this.path))||a.push(this.path,function(e){if(!e)throw Error("Error parsing "+g.path);g.root=e})};d.Import.prototype={toCSS:function(){return this.css?"@import "+this._path.toCSS()+";\n":""},eval:function(b){var a;if(this.css)return this;else{a=new d.Ruleset(null,this.root.rules.slice(0));for(var g=0;g<a.rules.length;g++)a.rules[g]instanceof d.Import&&Array.prototype.splice.apply(a.rules,[g,1].concat(a.rules[g].eval(b)));return a.rules}}}})(s("less/tree"));
+(function(d){d.JavaScript=function(b,a){this.expression=b;this.index=a};d.JavaScript.prototype={toCSS:function(){return JSON.stringify(this.evaluated)},eval:function(b){var a=new Function("return ("+this.expression+")"),g={},e;for(e in b.frames[0].variables())g[e.slice(1)]={value:b.frames[0].variables()[e].value,toJS:function(){return this.value.eval(b).toCSS()}};try{this.evaluated=a.call(g)}catch(h){throw{message:"JavaScript evaluation error: '"+h.name+": "+h.message+"'",index:this.index};}return this}}})(s("less/tree"));
+(function(d){d.Keyword=function(b){this.value=b};d.Keyword.prototype={eval:function(){return this},toCSS:function(){return this.value}}})(s("less/tree"));(function(d){d.mixin={};d.mixin.Call=function(b,a,g){this.selector=new d.Selector(b);this.arguments=a;this.index=g};d.mixin.Call.prototype={eval:function(b){for(var a,g=[],e=false,h=0;h<b.frames.length;h++)if((a=b.frames[h].find(this.selector)).length>0){for(h=0;h<a.length;h++)if(a[h].match(this.arguments,b))try{Array.prototype.push.apply(g,a[h].eval(b,
+this.arguments).rules);e=true}catch(i){throw{message:i.message,index:i.index,stack:i.stack,call:this.index};}if(e)return g;else throw{message:"No matching definition was found for `"+this.selector.toCSS().trim()+"("+this.arguments.map(function(k){return k.toCSS()}).join(", ")+")`",index:this.index};}throw{message:this.selector.toCSS().trim()+" is undefined",index:this.index};}};d.mixin.Definition=function(b,a,g){this.name=b;this.selectors=[new d.Selector([new d.Element(null,b)])];this.params=a;this.arity=
+a.length;this.rules=g;this._lookups={};this.required=a.reduce(function(e,h){return!h.name||h.name&&!h.value?e+1:e},0);this.parent=d.Ruleset.prototype;this.frames=[]};d.mixin.Definition.prototype={toCSS:function(){return""},variable:function(b){return this.parent.variable.call(this,b)},variables:function(){return this.parent.variables.call(this)},find:function(){return this.parent.find.apply(this,arguments)},rulesets:function(){return this.parent.rulesets.apply(this)},eval:function(b,a){for(var g=
+new d.Ruleset(null,[]),e=0,h;e<this.params.length;e++)if(this.params[e].name)if(h=a&&a[e]||this.params[e].value)g.rules.unshift(new d.Rule(this.params[e].name,h.eval(b)));else throw{message:"wrong number of arguments for "+this.name+" ("+a.length+" for "+this.arity+")"};return(new d.Ruleset(null,this.rules.slice(0))).eval({frames:[this,g].concat(this.frames,b.frames)})},match:function(b,a){var g=b&&b.length||0;if(g<this.required)return false;if(this.required>0&&g>this.params.length)return false;g=
+Math.min(g,this.arity);for(var e=0;e<g;e++)if(!this.params[e].name)if(b[e].eval(a).toCSS()!=this.params[e].value.eval(a).toCSS())return false;return true}}})(s("less/tree"));(function(d){d.Operation=function(b,a){this.op=b.trim();this.operands=a};d.Operation.prototype.eval=function(b){var a=this.operands[0].eval(b);b=this.operands[1].eval(b);var g;if(a instanceof d.Dimension&&b instanceof d.Color)if(this.op==="*"||this.op==="+"){g=b;b=a;a=g}else throw{name:"OperationError",message:"Can't substract or divide a color from a number"};
+return a.operate(this.op,b)};d.operate=function(b,a,g){switch(b){case "+":return a+g;case "-":return a-g;case "*":return a*g;case "/":return a/g}}})(s("less/tree"));(function(d){d.Quoted=function(b,a){this.value=a||"";this.quote=b.charAt(0)};d.Quoted.prototype={toCSS:function(){return this.quote+this.value+this.quote},eval:function(){return this}}})(s("less/tree"));(function(d){d.Rule=function(b,a,g,e){this.name=b;this.value=a instanceof d.Value?a:new d.Value([a]);this.important=g?" "+g.trim():"";
+this.index=e;this.variable=b.charAt(0)==="@"?true:false};d.Rule.prototype.toCSS=function(b){return this.variable?"":this.name+(b.compress?":":": ")+this.value.toCSS(b)+this.important+";"};d.Rule.prototype.eval=function(b){return new d.Rule(this.name,this.value.eval(b),this.important,this.index)};d.Shorthand=function(b,a){this.a=b;this.b=a};d.Shorthand.prototype={toCSS:function(b){return this.a.toCSS(b)+"/"+this.b.toCSS(b)},eval:function(){return this}}})(s("less/tree"));(function(d){d.Ruleset=function(b,
+a){this.selectors=b;this.rules=a;this._lookups={}};d.Ruleset.prototype={eval:function(b){var a=new d.Ruleset(this.selectors,this.rules.slice(0));a.root=this.root;b.frames.unshift(a);if(a.root)for(var g=0;g<a.rules.length;g++)a.rules[g]instanceof d.Import&&Array.prototype.splice.apply(a.rules,[g,1].concat(a.rules[g].eval(b)));for(g=0;g<a.rules.length;g++)if(a.rules[g]instanceof d.mixin.Definition)a.rules[g].frames=b.frames.slice(0);for(g=0;g<a.rules.length;g++)a.rules[g]instanceof d.mixin.Call&&Array.prototype.splice.apply(a.rules,
+[g,1].concat(a.rules[g].eval(b)));g=0;for(var e;g<a.rules.length;g++){e=a.rules[g];e instanceof d.mixin.Definition||(a.rules[g]=e.eval?e.eval(b):e)}b.frames.shift();return a},match:function(b){return!b||b.length===0},variables:function(){return this._variables?this._variables:this._variables=this.rules.reduce(function(b,a){if(a instanceof d.Rule&&a.variable===true)b[a.name]=a;return b},{})},variable:function(b){return this.variables()[b]},rulesets:function(){return this._rulesets?this._rulesets:this._rulesets=
+this.rules.filter(function(b){return b instanceof d.Ruleset||b instanceof d.mixin.Definition})},find:function(b,a){a=a||this;var g=[],e=b.toCSS();if(e in this._lookups)return this._lookups[e];this.rulesets().forEach(function(h){if(h!==a)for(var i=0;i<h.selectors.length;i++)if(b.match(h.selectors[i])){b.elements.length>1?Array.prototype.push.apply(g,h.find(new d.Selector(b.elements.slice(1)),a)):g.push(h);break}});return this._lookups[e]=g},toCSS:function(b,a){var g=[],e=[],h=[],i=[],k;if(!this.root)if(b.length===
+0)i=this.selectors.map(function(r){return[r]});else for(k=0;k<this.selectors.length;k++)for(var n=0;n<b.length;n++)i.push(b[n].concat([this.selectors[k]]));for(n=0;n<this.rules.length;n++){k=this.rules[n];if(k.rules||k instanceof d.Directive)h.push(k.toCSS(i,a));else if(k instanceof d.Comment)k.silent||(this.root?h.push(k.toCSS(a)):e.push(k.toCSS(a)));else if(k.toCSS&&!k.variable)e.push(k.toCSS(a));else k.value&&!k.variable&&e.push(k.value.toString())}h=h.join("");if(this.root)g.push(e.join(a.compress?
+"":"\n"));else if(e.length>0){i=i.map(function(r){return r.map(function(p){return p.toCSS(a)}).join("").trim()}).join(a.compress?",":i.length>3?",\n":", ");g.push(i,(a.compress?"{":" {\n  ")+e.join(a.compress?"":"\n  ")+(a.compress?"}":"\n}\n"))}g.push(h);return g.join("")+(a.compress?"\n":"")}}})(s("less/tree"));(function(d){d.Selector=function(b){this.elements=b;if(this.elements[0].combinator.value==="")this.elements[0].combinator.value=" "};d.Selector.prototype.match=function(b){return this.elements[0].value===
+b.elements[0].value?true:false};d.Selector.prototype.toCSS=function(b){if(this._css)return this._css;return this._css=this.elements.map(function(a){return typeof a==="string"?" "+a.trim():a.toCSS(b)}).join("")}})(s("less/tree"));(function(d){d.URL=function(b,a){if(b.data)this.attrs=b;else{if(!/^(?:https?:\/|file:\/)?\//.test(b.value)&&a.length>0&&typeof z!=="undefined")b.value=a[0]+(b.value.charAt(0)==="/"?b.value.slice(1):b.value);this.value=b;this.paths=a}};d.URL.prototype={toCSS:function(){return"url("+
+(this.attrs?"data:"+this.attrs.mime+this.attrs.charset+this.attrs.base64+this.attrs.data:this.value.toCSS())+")"},eval:function(b){return this.attrs?this:new d.URL(this.value.eval(b),this.paths)}}})(s("less/tree"));(function(d){d.Value=function(b){this.value=b;this.is="value"};d.Value.prototype={eval:function(b){return this.value.length===1?this.value[0].eval(b):new d.Value(this.value.map(function(a){return a.eval(b)}))},toCSS:function(b){return this.value.map(function(a){return a.toCSS(b)}).join(b.compress?
+",":", ")}}})(s("less/tree"));(function(d){d.Variable=function(b,a){this.name=b;this.index=a};d.Variable.prototype={eval:function(b){var a,g,e=this.name;if(a=d.find(b.frames,function(h){if(g=h.variable(e))return g.value.eval(b)}))return a;else throw{message:"variable "+this.name+" is undefined",index:this.index};}}})(s("less/tree"));s("less/tree").find=function(d,b){for(var a=0,g;a<d.length;a++)if(g=b.call(d,d[a]))return g;return null};var P=location.protocol==="file:"||location.protocol==="chrome:"||
+location.protocol==="resource:";o.env=o.env||(location.hostname=="127.0.0.1"||location.hostname=="0.0.0.0"||location.hostname=="localhost"||location.port.length>0||P?"development":"production");o.async=false;o.poll=o.poll||(P?1E3:1500);o.watch=function(){return this.watchMode=true};o.unwatch=function(){return this.watchMode=false};if(o.env==="development"){o.optimization=0;/!watch/.test(location.hash)&&o.watch();o.watchTimer=setInterval(function(){o.watchMode&&W(function(d,b,a){d&&N(d.toCSS(),b,a.lastModified)})},
+o.poll)}else o.optimization=3;var B;try{B=typeof z.localStorage==="undefined"?null:z.localStorage}catch(aa){B=null}var M=document.getElementsByTagName("link"),V=/^text\/(x-)?less$/;o.sheets=[];for(var J=0;J<M.length;J++)if(M[J].rel==="stylesheet/less"||M[J].rel.match(/stylesheet/)&&M[J].type.match(V))o.sheets.push(M[J]);o.refresh=function(d){var b=endTime=new Date;W(function(a,g,e){if(e.local)H("loading "+g.href+" from cache.");else{H("parsed "+g.href+" successfully.");N(a.toCSS(),g,e.lastModified)}H("css for "+
+g.href+" generated in "+(new Date-endTime)+"ms");e.remaining===0&&H("css generated in "+(new Date-b)+"ms");endTime=new Date},d);U()};o.refreshStyles=U;o.refresh(o.env==="development")})(window);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/toolbox/static/js/main.js	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,6 @@
+// focus the search element
+$(document).ready(function() {
+    if (!location.hash.length) {
+        $('#search-text').focus();
+    }
+});
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/toolbox/static/js/new.js	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,148 @@
+// javascript for the new tool view
+
+$(document).ready(function(){
+
+    var query = parseQueryString();
+
+    // error functions
+    function errorMissing() {
+        return 'Required';
+    }
+    function errorReserved() {
+        return 'Conflicts with a reserved URL';
+    }
+    function errorConflict() {
+        var tool = query['conflict'][0];
+        tool = $('<div></div>').text(tool).html();
+        return '<a href="' + escape(tool) + '">' + tool + '</a> already exists';
+    }
+    var queryStringErrors = {'missing': errorMissing,
+                             'reserved': errorReserved,
+                             'conflict': errorConflict}
+
+    // prepare the form
+    var form = $('<form id="new" method="post"></form>');
+    var table = $('<table></table>');    
+    function addRow(fieldName, rhs) {
+        var row = $('<tr id="' + fieldName + '-row"><td class="field-name" for="' + fieldName + '">' + fieldName + '</td><td class="input"></td></tr>');
+        $(row).find('td.input').append(rhs);
+
+        // add errors
+        var errors = $('<ul class="error"></ul>');
+        for (var key in query) {
+            var error = queryStringErrors[key];
+            if(error) {
+                if (jQuery.inArray(fieldName, query[key]) != -1) {
+                    errors.append('<li>' + error() + '</li>');
+                }
+                // name-specific errors
+                if ((key != 'missing') && (fieldName == 'name')) {
+                    errors.append('<li>' + error() + '</li>');
+                }
+            }
+        }
+        var cell = $('<td></td>');
+        cell.append(errors);
+        row.append(cell);
+
+        $(table).append(row)
+    }
+    function addTextInput(fieldName, isFieldInput) {
+        var value = null;
+        if (query) { 
+            value = query[fieldName];
+        }
+        var input = $('<input type="text"/>');
+        $(input).attr('name', fieldName);
+        if (isFieldInput) {
+            if (value) {
+                value = value.join(", "); 
+                // XXX for some reason, this doesnt get reflected in the observed input;
+                // an oversight with the token parser/autocomplete? ::shrug::
+            }
+            $(input).addClass('field-input');
+        } else {
+            if (value) {
+                value = value[0];
+            }
+        }
+        if (value) {
+            $(input).val(value);
+        }
+        addRow(fieldName, input);
+        return input;
+    }
+
+    // insert mandatory data: name, description, url
+    var nameInput = addTextInput('name', false);
+    var description = '';
+    if (query && query['description']) {
+        description = query['description'][0];
+    }
+    addRow('description', $('<textarea name="description">' + description + '</textarea>'));
+    var urlInput = addTextInput('url', false);
+    var urlValue = urlInput.val();
+    if (urlValue && !query['name']) {
+        if (urlValue.charAt(urlValue.length -1) == '/') {
+            urlValue = urlValue.slice(0, urlValue.length -1);
+        }
+        var index = urlValue.lastIndexOf('/');
+        if (index != -1) {
+            var name = urlValue.slice(index+1);
+            $(nameInput).val(name);
+        }
+    }
+
+    // find other fields
+    var fields = [];
+    $('.by-field').each(function() {
+        fields.push($(this).text());
+    });
+    for (var i in fields) {
+        addTextInput(fields[i], true);
+    }
+
+    // add the form to the DOM
+    $(form).append(table);
+    var now = new Date().getTime() / 1000; // seconds
+    $(form).append('<input type="hidden" name="form-render-date" value="' + now + '"/>');
+    $(form).append('<input id="submit-new-tool" class="submit button" type="submit" value="Add it!"/>');
+    $(form).submit(function() {
+        // submit guard
+        var retval = true;
+        $('ul.error').empty(); // remove existing errors
+        var required = ['name', 'description', 'url']; // required fields
+        for (var i in required) { // check required fields
+            var row = $(this).find('#' + required[i] + '-row').first();
+            var inputCell = $(row).find('td.input').first(); 
+            var inputElement = $(inputCell).children().last();
+            var value = $(inputElement).val()
+            var trimmed = value.trim();
+            if (trimmed.length == 0) {
+                $(row).find('ul.error').append('<li>' + errorMissing() + '</li>');
+                retval = false;
+            }
+            // no slashes in name
+            if (required[i] == 'name' && value.indexOf('/') != -1) {
+                    $(row).find('ul.error').append('<li>slashes are not allowed in names</li>');
+                    retval = false;
+            }
+        }
+        return retval;
+    });
+    $('#new-container').append(form);
+
+    // add autocomplete
+    $('input.field-input').each(function(index) {
+        var field = $(this).attr('name');
+        $(this).tokenInput("tags?format=json&field=" + field, {
+            theme: 'facebook',
+            submitOnEnter: false,
+            closeOnBlur: true,
+            hintText: false
+        });
+    });
+
+    // focus the first field: name
+    $("input[name=name]").focus();
+});
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/toolbox/static/js/project.js	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,206 @@
+$(document).ready(function(){
+    $(".date").timeago();
+    $(".field-edit").hide();
+    
+    $(".description").autolink();
+
+    // modify project div
+    $('div.project').each(function(){
+        var project = $(this).attr('id');
+        var url = escape(project).replace(/\//g, '%25%32%66'); // urlquote
+
+        // add a delete link
+        var deletelink = $('<a class="delete" title="permanantly remove">Delete</a>');
+        $(deletelink).click(function() {
+                if(confirm("Permanently remove: " + project + " ?")) {
+                    var form = $('<form class="delete" action="delete" method="POST"></form>')
+                    var input = $('<input type="hidden" name="project"/>');
+                    input.val(project);
+                    form.append(input);
+                    $(this).after(form);
+                    $(form).submit();
+                }
+            });
+        $(this).append(deletelink);
+
+        // make description editable with jeditable
+        $(this).find('p.description').editable(url, {
+          type: 'textarea',
+          rows: 7,
+          cols: 80,
+          indicator: '<img src="img/indicator.gif"/>',
+          onblur: 'submit',
+          name: 'description',
+          tooltip: 'click to edit description',
+          beforeedit: function (settings, jedit, event) {
+              if(event.target.nodeName == "A")
+                  return false; 
+          }
+        });
+
+        // make the name and url editable
+        var UEB = '<img class="UEB" src="img/UEB16.png" alt="click to edit" title="click to edit"/>'; // universal edit button
+        function nameHover(eventObject) {
+           var header = this;
+           var img = $(this).find('img.UEB');
+           $(img).css('visibility', 'visible');
+           $(img).click(function() {
+               var link = $(header).children('a');
+               var text = $(link).html();
+               var size = text.length;
+               var form = $('<form method="POST" action="' + url + '"><input type="text" name="name" value="' + text +'" size="' + size + '"/><button class="cancel">Cancel</button><input type="submit" value="Rename"/></form>');
+               $(form).submit(function() {
+                   var input = $(this).find('input[name="name"]')
+                   var name = $(input).val()
+                   name = name.trim();
+                   if (name.length == 0) {
+                       $(this).append('<div class="error">A project must have a name</span>');
+                       return false;
+                   }
+                   return true;
+               })
+               $(form).css('display', 'block');
+               $(header).replaceWith(form);
+               $(form).find('button.cancel').click(function(){
+                   $(header).find('img.UEB').css('visibility', 'hidden');
+                   $(header).hover(nameHover,
+                                   function(eventObject) { $(this).children('img.UEB').css('visibility', 'hidden'); });
+                   $(form).replaceWith(header);
+                   });
+               $(form).find('input[type=text]').keypress(function(event) {
+                       if (event.which == 13) {
+                           $(form).submit();
+                       }
+                   });
+               $(form).find('input[type=text]').focus();
+               });
+        }
+        var header = $(this).find('h1');
+        $(header).append(UEB);
+        $(header).find('img.UEB').each(function() {$(this).css('visibility', 'hidden'); });
+        $(header).hover(nameHover,
+                        function(eventObject) { $(this).children('img.UEB').css('visibility', 'hidden'); });
+        $(this).find('a.home').each(function() {
+                var home = this;
+                $(this).wrap('<span/>');
+                var wrapper = $(this).parent();
+                var img = $(UEB);
+                $(wrapper).append(img);
+                img.css('visibility', 'hidden');
+                function urlHover(eventObject) {
+                    var img = $(this).find('img.UEB');
+                    $(img).css('visibility', 'visible');
+                    $(img).click(function() {
+                        var link = $(home).attr('href');
+                        var size = link.length;
+                        var input = $('<input type="text" value="' + link + '" size="' + size + '"/>');
+                        $(wrapper).replaceWith(input);
+                        
+                        function urlEditBlur() {
+                            var newlink = $(this).val();
+                            var that = this;
+                            newlink = newlink.trim();
+                            if (newlink != link) {
+                                var throbber = $('<img class="throbber" src="img/indicator.gif"/>');
+                                $(this).after(throbber);
+                                $(this).hide();
+                                $.post(url, {"url": newlink}, function(data) {
+                                    $(throbber).remove();
+                                    var a = $(wrapper).children('a');
+                                    a.attr('href', newlink);
+                                    a.html(newlink);
+                                    $(wrapper).children('img.UEB').css('visibility', 'hidden');
+                                    wrapper.hover(urlHover, function(eventObject) { $(this).children('img.UEB').css('visibility', 'hidden'); });
+                                    $(that).replaceWith(wrapper);
+                                    });
+                            }
+                            else {
+                                $(wrapper).children('img.UEB').css('visibility', 'hidden');
+                                wrapper.hover(urlHover, function(eventObject) { $(this).children('img.UEB').css('visibility', 'hidden'); });
+                                $(this).replaceWith(wrapper);
+                            }
+                        }
+                        $(input).blur(urlEditBlur);
+                        $(input).keypress(function(event) {
+                                if (event.which == 13) {
+                                    $(this).blur();
+                                }
+                            });
+                        $(input).focus();
+                        });
+                }
+                wrapper.hover(urlHover, function(eventObject) { $(this).children('img.UEB').css('visibility', 'hidden'); });
+            });
+
+        // autocomplete
+        $(this).find(".edit-message").click(function() {
+          var container = $(this).parents('.field-value-container');
+          var edit = $(this).parents('.field').children('.field-edit');
+          var valueList = container.children('.field-values');
+
+          container.hide();
+          edit.show();
+
+          var items = valueList.children('.field-value-item');
+          var values = [];
+          for(var i = 0; i < items.length; i++) {
+              values.push($(items.get(i)).text().trim());
+          }
+
+          var tokenData = values.map(function(value) {
+                  return {id: value, name: escape(value)};
+              });
+        
+          var input = edit.children('input');
+          var field = $(this).parents(".field").attr('class').split(' ')[1];
+
+          input.tokenInput("tags?format=json&field=" + field + "&omit=" + project, {
+              theme: 'facebook',
+              prePopulate: tokenData,
+              autoFocus: true,
+              submitOnBlur: true,
+              canBlur: function(elem) {
+                  return !container.find($(elem)).length;
+                  },
+              hintText: false,
+              onSubmit: function(values) {
+                  edit.hide();
+                  container.show();
+                  valueList.empty();
+
+                  if(!values.length) {
+                      container.children(".field-value").remove();
+                      container.prepend($('<div class="field-none field-value">none</div>'));
+                  }
+                  else {
+                      values.forEach(function(value) {
+                         var li = $("<li></li>")
+                             .addClass("field-value-item");
+                         var a = $("<a></a>")
+                             .attr("href", "?" + field + "=" + value)
+                             .attr("title", "tools with " + field + " " + value)
+                             .text(value)
+                             .appendTo(li);
+                
+                         if(valueList.length == 0) {
+                             container.children(".field-value").remove();
+                             valueList = $("<ul class='field-values field-value'></ul>")
+                                 .prependTo(container);
+                         }
+                         valueList.append(li);
+                          });
+                  }
+            
+                  
+                  var data = {
+                      action: 'replace'
+                  };
+                  data[field] = values.join(",");
+                  
+                  $.post(url, data, function() {
+                      });
+                  }
+              });
+            });
+        });
+    });
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/toolbox/static/js/queryString.js	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,33 @@
+function parseQueryString() {
+    var args = {};
+    var searchString = document.location.search.slice(1);
+    if (!searchString.length) {
+        // no query string to speak of
+        return null;
+    }
+    if (searchString[searchString.length -1] == '&') {
+        // remove trailing '&'
+        searchString = searchString.substr(0, searchString.length-1);
+    }
+
+    // split by '&'
+    var params = document.location.search.slice(1).split("&");
+    
+    for (p in params) {
+        var l = params[p].split("=").map(function(x) {
+            try {
+                return decodeURIComponent(x); 
+            } catch(e) {
+                return x;
+            }
+            });
+        if (l.length != 2) {
+            continue;
+        }
+        if (!args[l[0]]) {
+            args[l[0]] = [];
+        }
+        args[l[0]].push(l[1]);
+    }
+    return args;
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/toolbox/templates/about.html	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,5 @@
+<h1 id="title">{{title}}</h1>
+
+<div class="about">
+{{about | html}}
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/toolbox/templates/fields.html	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,12 @@
+<h1 id="title">{{item_plural.title()}} by <span id="field-name">{{field}}</span></h1>
+
+{{for value in sorted(values.keys(), key=lambda x: x.lower())}}
+<div class="field" id="{{value}}"><a name="{{value}}"></a>
+<h2><a href="./?{{field}}={{value}}">{{value}}</a></h2>
+<ul>
+  {{for project in sorted(values[value])}}
+  <li><a href="{{urlescape(project)}}" title="{{projects[project]['description']}}">{{project}}</a></li>
+  {{endfor}}
+</ul>
+</div>
+{{endfor}}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/toolbox/templates/index.html	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,92 @@
+
+{{if len(projects) > 1}}
+<header>
+<h1 id="title">{{len(projects)}} {{item_plural}}
+<span class="query">
+{{if search}}
+  matching <span class="query-value query-search">{{search}}</span>
+{{endif}}
+{{for loop, key_value in looper(query.items())}}
+  {{if loop.first}} with {{endif}}
+  {{py:key, value = key_value}}
+  <span class="query-item">
+    <span class="query-key">{{key}}</span>
+    <span class="query-value">{{isinstance(value, basestring) and value or ', '.join(value)}}</span>
+  </span>
+  {{if not loop.last}}
+    and
+  {{endif}}
+{{endfor}}
+</span>
+</h1>
+<nav id="sort-order">
+  <ul>
+    <li><span id="sort-legend">sort by</span></li>
+    {{for sort_link, sort_description in sort_types}}
+    <li>
+      {{if sort_type == sort_link}}
+      <span id="search-type" title="sorted by {{sort_description}}">{{sort_description}}</span>
+      {{else}}
+      <a href="?{{if request.query_string}}{{request.query_string + '&'}}{{endif}}sort={{sort_link}}" title="sort by {{sort_description}}">{{sort_description}}</a>
+      {{endif}}
+    </li>
+    {{endfor}}
+  </ul>
+</nav>
+{{endif}}
+</header>
+
+{{if not len(projects)}}
+<h1 id="title">No {{item_plural}} found</h1>
+{{endif}}
+{{if error}}
+<h1 id="title">{{error | html}}</h1>
+{{endif}}
+
+{{for project in projects}}
+  <div class="project" id="{{project['name']}}">
+    <a name="{{project['name']}}"></a>
+    <span class="date" title="{{format_date(project['modified'])}}">
+      {{format_date(project['modified'])}}
+    </span>
+
+    <!-- title -->
+    <h1 class="project-title">
+      <a href="{{urlescape(project['name'])}}">{{project['name']}}</a>
+    </h1>
+    <!-- description -->
+    <p class="description">{{project.get('description', '')}}</p>
+    
+    <a class="home" target="_blank" href="{{project['url']}}">{{project['url']}}</a>
+
+    <!-- fields -->
+    <ul class="fields">
+      {{for field in fields}}
+      <li class="field {{field}}">
+        <h2 class="field-name"><a href="{{field}}" title="{{item_plural}} by {{field}}">{{field}}:</a></h2>
+        <span class="field-value-container">
+          {{if (not field in project) or not project[field]}}
+            <div class="field-none field-value">none</div>
+          {{else}}
+          <ul class="field-values field-value">
+            {{for entry in sorted(project[field], key=lambda x: x.lower())}}
+              <li class="field-value-item">
+               <a href="./?{{field}}={{entry}}" title="{{item_plural}} with {{field}}={{entry}}">{{entry}}</a>
+              </li>
+            {{endfor}}
+          </ul>
+          {{endif}}
+          <span class="edit-value">
+            <span class="edit-message">&nbsp;</span>
+          </span>
+         </span>
+         <span class="field-edit">
+           <input></input>
+         </span>
+      </li>
+      {{endfor}}
+    </ul>
+ 
+  </div><!-- project -->
+{{endfor}}  
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/toolbox/templates/main.html	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>{{title}}</title>
+<link rel="icon" href="{{link('img/favicon.ico')}}" />
+
+{{for sheet in css}}
+<link href="{{link(sheet)}}" rel="stylesheet" type="text/css" />
+{{endfor}}
+
+{{for sheet in less}}
+<link href="{{link(sheet)}}" rel="stylesheet/less" type="text/css" />
+{{endfor}}
+
+{{for script in js}}
+<script src="{{link(script)}}"></script>
+{{endfor}}
+
+</head>
+<body>
+  <!-- navigation menu -->
+  <nav>
+    <ul>
+      <li><a href="{{link()}}" title="{{site_name}}">{{site_name}}</a></li>
+      {{if hasAbout}}
+      <li><a href="{{link('about')}}" title="about:{{site_name}}">about</a></li>
+      {{endif}}
+      <li><a href="{{link('new')}}" title="add a {{item_name}}">new</a></li>
+      {{for field in fields}}
+      <li>
+        <a class="by-field" href="{{link(field)}}" title="{{item_plural}} by {{field}}">{{field}}</a>
+      </li>
+      {{endfor}}
+    </ul>
+  </nav>
+
+  <div id="container">
+    <!-- text search form -->
+    <form id="search" action="./">
+      <input id="search-text" type="text" name="q" value="{{request.GET.get('q', '')}}"/>
+      <input id="search-submit" type="submit" title="search" value=""/>
+    </form>
+    <div id="content">
+      {{content | html}}
+    </div>
+  </div>
+
+</body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/toolbox/templates/new.html	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,4 @@
+<div id="new-container">
+  <h1 id="title">Add a {{item_name}}</h1>
+  <!-- form from JS -->
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/toolbox/templates/tags.html	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,13 @@
+<h1 id="title">{{title}}</h1>
+
+<div class="tag">
+  <ul>
+    {{for tag in tags}}
+    <li>
+      <a href="./?{{tag['field']}}={{tag['value']}}" title="{{tag['count']}}">
+        {{tag['value']}}
+      </a>
+    </li>
+    {{endfor}}
+  </ul>
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/toolbox/util.py	Sun May 11 09:15:35 2014 -0700
@@ -0,0 +1,59 @@
+"""
+utilities for toolbox
+"""
+
+try:
+    import json
+except ImportError:
+    import simplejson as json
+
+def strsplit(string):
+    """sensibly split a comma-separated string"""
+    string = string.strip()
+    if not string:
+        return []
+    return [i.strip() for i in string.split(',')]
+
+def strreplace(string, translation):
+    """replace substrings from a translation matrix"""
+    for key, value in translation.items():
+        string = string.replace(key, value)
+    return string
+
+def str2filename(string):
+    """converts a string to an acceptable filename"""
+    matrix = {' ': '_',
+              '>': '',
+              '<': '',
+              "'": '',
+              '"': '',
+              '&': '+',
+              '\\': '',
+              '\x00': '',
+              '/': ''}
+    return strreplace(string, matrix)
+
+
+class JSONEncoder(json.JSONEncoder):
+    """provide additional serialization for JSON"""
+
+    def default(self, obj):
+        if hasattr(obj, 'isoformat'):
+            return obj.isoformat()
+        if isinstance(obj, set):
+            return list(obj)
+
+        return json.JSONEncoder.default(self, obj)
+
+if __name__ == '__main__':
+    # test the encoder
+    testjson = {}
+
+    # test date encoding
+    from datetime import datetime
+    testjson['date'] = datetime.now()
+
+    # test set encoding
+    testjson['set'] = set([1,2,3,2])
+
+    print json.dumps(testjson, cls=JSONEncoder)