Commits

Bryan O'Sullivan  committed ad304b6

Initial cut at web comment system import

  • Participants
  • Parent commits 4002538

Comments (0)

Files changed (42)

 *.pdf
 *.png
 *.ps
+*.pyc
 *.rej
 *.run
 *.tmp
 .run
 xsl/system-xsl
 .validated-00book.xml
+web/hgbook/secrets.py
+This directory contains web-related files.  Surprise!
+
+javascript - files used by the comment system, based on jQuery
+rwh        - Django app that acts as the comment back end
+styles.css - style file

File web/hgbook.conf

+# -*- apache -*-
+
+<VirtualHost *:80>
+    ServerName www.hgbook.org
+    ServerAdmin bos@serpentine.com
+    ErrorLog logs/hgbook-error_log
+    # Debian:
+    # CustomLog logs/hgbook-access_log full
+    # Fedora:
+    CustomLog logs/hgbook-access_log combined
+    Options +MultiViews
+    DirectoryIndex index.html.var index.html
+    DocumentRoot "/home/bos/hg/hgbook/en/html"
+
+    # Actively redirect requests via a ServerAlias to the canonical hostname.
+    RewriteEngine On
+    RewriteCond %{HTTP_HOST} !=www.hgbook.org
+    RewriteRule ^(.*) http://www.hgbook.org$1 [R]
+
+    <Location "/">
+        SetHandler python-program
+	# hg clone http://bitbucket.org/mirror/django-trunk/
+        PythonPath "['/home/bos/hg/django-trunk', '/home/bos/hg/hgbook/web'] + sys.path"
+        PythonHandler django.core.handlers.modpython
+        PythonAutoReload Off
+        SetEnv DJANGO_SETTINGS_MODULE hgbook.settings
+        PythonDebug Off
+    </Location>
+
+    <Location ~ "^/$">
+        SetHandler None
+        DirectoryIndex index.html
+    </Location>
+
+    <Location ~ "^/index.html">
+        SetHandler None
+    </Location>
+
+    <Location ~ "^/robots.txt">
+        SetHandler None
+    </Location>
+
+    <Location "/read">
+        SetHandler None
+    </Location>
+
+    <Location "/support">
+        SetHandler None
+    </Location>
+
+    <Location "/media">
+        SetHandler None
+    </Location>
+
+    Alias /media /home/bos/hg/django-trunk/django/contrib/admin/media
+
+    <Directory "/home/bos/hg/hgbook/en/html">
+        Options Indexes FollowSymlinks
+        AllowOverride None
+        Order allow,deny
+        Allow from all
+    </Directory>
+
+    <Directory "/home/bos/hg/hgbook/en/html">
+        AllowOverride AuthConfig
+    </Directory>
+
+    <Directory "/home/bos/hg/hgbook/en/html/support">
+        Options None
+    </Directory>
+</VirtualHost>
+
+<Directory "/home/bos/hg/django-trunk/django/contrib/admin/media">
+    Options None
+    AllowOverride None
+    Order allow,deny
+    Allow from all
+</Directory>

File web/hgbook/__init__.py

Empty file added.

File web/hgbook/comments/__init__.py

Empty file added.

File web/hgbook/comments/feeds.py

+from django.core.exceptions import ObjectDoesNotExist
+from django.utils.feedgenerator import Atom1Feed
+from django.contrib.syndication.feeds import Feed
+from hgbook.comments.models import Comment, Element
+
+class MyAtomFeed(Atom1Feed):
+    title_type = u'html'
+    
+class Comments(Feed):
+    feed_type = MyAtomFeed
+    title = 'Real World Haskell: recent comments'
+    subtitle = ('Recent comments on the text of &#8220;Real World '
+                'Haskell&#8221;, from our readers')
+    link = '/feeds/comments/'
+    author_name = 'Our readers'
+
+    def feedfilter(self, queryset):
+        return queryset.order_by('-date')[:20]
+
+    def items(self):
+        return self.feedfilter(Comment.objects)
+
+    def item_author_name(self, obj):
+        return obj.submitter_name
+
+    def item_pubdate(self, obj):
+        return obj.date
+
+    def get_object(self, bits):
+        if len(bits) == 0:
+            return self.items()
+        elif len(bits) > 1:
+            raise ObjectDoesNotExist
+        return self.feedfilter(Comment.objects.filter(element__chapter=bits[0],
+                                                      hidden=False))

File web/hgbook/comments/models.py

+from django.db import models
+import sha
+
+mutable = True
+
+class Element(models.Model):
+    class Admin:
+        search_fields = ['id', 'chapter']
+        list_filter = ['chapter', 'title']
+
+    id = models.CharField('ID attribute', max_length=64, editable=False,
+                          primary_key=True)
+    chapter = models.CharField('Chapter ID', max_length=64, editable=False,
+                               db_index=True)
+    title = models.CharField('Section title', max_length=256, editable=False)
+
+    def __unicode__(self):
+        return self.id
+    
+class Comment(models.Model):
+    class Admin:
+        list_display = ['element', 'submitter_name', 'comment', 'reviewed',
+                        'hidden', 'date']
+        search_fields = ['comment']
+        date_hierarchy = 'date'
+        list_filter = ['date', 'submitter_name']
+        search_fields = ['title', 'submitter_name', 'submitter_url']
+        fields = (
+            (None, {'fields': ('submitter_name', 'element', 'comment')}),
+            ('Review and presentation state',
+             {'fields': ('reviewed', 'hidden')}),
+            ('Other info', {'fields': ('date', 'submitter_url', 'ip')}),
+            )
+            
+    element = models.ForeignKey(Element,
+        help_text='ID of paragraph that was commented on')
+    comment = models.TextField(editable=mutable,
+        help_text='Text of submitted comment (please do not modify)')
+    submitter_name = models.CharField('Submitter', max_length=64,
+        help_text='Self-reported name of submitter (may be bogus)')
+    submitter_url = models.URLField('URL', blank=True, editable=mutable,
+        help_text='Self-reported URL of submitter (may be empty or bogus)')
+    ip = models.IPAddressField('IP address', editable=mutable,
+        help_text='IP address from which comment was submitted')
+    date = models.DateTimeField('date submitted', auto_now=True,
+                                auto_now_add=True)
+    reviewed = models.BooleanField(default=False, db_index=True,
+        help_text='Has this comment been reviewed by an author?')
+    hidden = models.BooleanField(default=False, db_index=True,
+        help_text='Has this comment been hidden from public display?')
+
+    def __unicode__(self):
+        return self.comment[:32]
+
+    def get_absolute_url(self):
+        s = sha.new()
+        s.update(repr(self.comment))
+        s.update(repr(self.submitter_name))
+        s.update(str(self.date))
+        return '/read/%s.html#%s?comment=%s&uuid=%s' % (
+            self.element.chapter, self.element.id, self.id, s.hexdigest()[:20]
+            )

File web/hgbook/comments/sql/comment.mysql.sql

+alter table comments_comment convert to character set utf8 collate utf8_bin;
+alter table comments_comment default character set utf8 collate utf8_bin;

File web/hgbook/comments/sql/element.mysql.sql

+alter table comments_element convert to character set utf8 collate utf8_bin;
+alter table comments_element default character set utf8 collate utf8_bin;

File web/hgbook/comments/urls.py

+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('',
+    (r'chapter/(?P<id>[^/]+)/?$', 'hgbook.comments.views.chapter'),
+    (r'chapter/(?P<id>[^/]+)/count/?$', 'hgbook.comments.views.chapter_count'),
+    (r'single/(?P<id>[^/]+)/?$', 'hgbook.comments.views.single'),
+    (r'submit/(?P<id>[^/]+)/?$', 'hgbook.comments.views.submit')
+)

File web/hgbook/comments/views.py

+import django.newforms as forms
+from django.db import connection
+from django.http import HttpResponse
+from hgbook.comments.models import Comment, Element
+from django.shortcuts import get_object_or_404, render_to_response
+from django.template import Context
+from django.template.loader import get_template
+from django.utils.simplejson import dumps 
+
+def dump_queries():
+    # requires settings.DEBUG to be set to True in order to work
+    if len(connection.queries) == 1:
+        print connection.queries
+    else:
+        qs = {}
+        for q in connection.queries:
+            qs[q['sql']] = qs.setdefault(q['sql'], 0) + 1
+        for q in sorted(qs.items(), key=lambda x: x[1], reverse=True):
+            print q
+        print len(connection.queries)
+
+class CommentForm(forms.Form):
+    id = forms.CharField(widget=forms.HiddenInput)
+    name = forms.CharField(max_length=64)
+    url = forms.URLField(max_length=128, required=False)
+    comment = forms.CharField(widget=forms.Textarea(attrs={
+        'rows': 8, 'cols': 60
+        }))
+    remember = forms.BooleanField(initial=True, required=False)
+
+def comments_by_chapter(id):
+    objs = {}
+    for c in Comment.objects.filter(element__chapter=id, hidden=False).order_by('date'):
+        objs.setdefault(c.element_id, []).append(c)
+    return objs
+
+def chapter(request, id):
+    template = get_template('comment.html')
+    resp = {}
+    for elt, comments in comments_by_chapter(id).iteritems():
+        form = CommentForm(initial={
+            'id': elt,
+            'name': request.session.get('name', ''),
+            })
+        resp[elt] = template.render(Context({
+            'id': elt,
+            'form': form,
+            'length': len(comments),
+            'query': comments,
+            }))
+    return HttpResponse(dumps(resp), mimetype='application/json')
+
+def chapter_count(request, id):
+    resp = comments_by_chapter(id)
+    for elt, comments in resp.iteritems():
+        resp[elt] = len(comments)
+    return HttpResponse(dumps(resp), mimetype='application/json')
+    
+def single(request, id, form=None, newid=None):
+    queryset = Comment.objects.filter(element=id, hidden=False).order_by('date')
+    if form is None:
+        form = CommentForm(initial={
+            'id': id,
+            'name': request.session.get('name', ''),
+            })
+    try:
+        error = form.errors[0]
+    except:
+        error = ''
+    return render_to_response('comment.html', {
+        'id': id,
+        'form': form,
+        'length': len(queryset),
+        'query': queryset,
+        'newid': newid or True,
+        'error': error,
+        })
+
+def submit(request, id):
+    element = get_object_or_404(Element, id=id)
+    form = None
+    newid = None
+    if request.method == 'POST':
+        form = CommentForm(request.POST)
+        if form.is_valid():
+            data = form.cleaned_data
+            if data.get('remember'):
+                request.session['name'] = data['name']
+                request.session['url'] = data['url']
+            else:
+                request.session.pop('name', None)
+                request.session.pop('url', None)
+            c = Comment(element=element,
+                        comment=data['comment'],
+                        submitter_name=data['name'],
+                        submitter_url=data['url'],
+                        ip=request.META.get('REMOTE_ADDR'))
+            c.save()
+            newid = c.id
+            form = None
+    return single(request, id, form, newid)

File web/hgbook/dbutil.py

+import MySQLdb as mysql
+
+def connect():
+    try:
+        import secrets
+    except ImportError:
+        print >> sys.stderr, 'Decrypt secrets.py.gpg or create a new copy!'
+        sys.exit(1)
+
+    if secrets.DATABASE_ENGINE != 'mysql':
+        print >> sys.stderr, ('You are using a %s database' %
+                              secrets.DATABASE_ENGINE)
+        sys.exit(1)
+
+    kwargs = {
+        'charset': 'utf8',
+        'use_unicode': True,
+        }
+    if secrets.DATABASE_USER:
+        kwargs['user'] = secrets.DATABASE_USER
+    if secrets.DATABASE_NAME:
+        kwargs['db'] = secrets.DATABASE_NAME
+    if secrets.DATABASE_PASSWORD:
+        kwargs['passwd'] = secrets.DATABASE_PASSWORD
+    if secrets.DATABASE_HOST.startswith('/'):
+        kwargs['unix_socket'] = secrets.DATABASE_HOST
+    elif secrets.DATABASE_HOST:
+        kwargs['host'] = secrets.DATABASE_HOST
+    if secrets.DATABASE_PORT:
+        kwargs['port'] = int(secrets.DATABASE_PORT)
+    return mysql.connect(**kwargs)

File web/hgbook/load_elements.py

+#!/usr/bin/env python
+#
+# This script updates the contents of the comments_element table.
+# It's fugly, but a lot less painful than trying to use Django's
+# fixtures system.
+
+import os, sys
+sys.path.append(os.path.dirname(__file__))
+import dbutil
+
+os.system('make -C ../../en ids')
+
+conn = dbutil.connect()
+c = conn.cursor()
+c.execute('''load data local infile "../../en/all-ids.dat" replace
+             into table comments_element
+             fields terminated by "|"''')
+print 'Database updated'

File web/hgbook/manage.py

+#!/usr/bin/env python
+from django.core.management import execute_manager
+try:
+    import settings # Assumed to be in the same directory.
+except ImportError:
+    import sys
+    sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
+    sys.exit(1)
+
+if __name__ == "__main__":
+    execute_manager(settings)

File web/hgbook/reviewers.py

+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import os, sys
+sys.path.append(os.path.dirname(__file__))
+import dbutil
+
+conn = dbutil.connect()
+c = conn.cursor()
+
+c.execute('''select submitter_name from comments_comment''')
+
+reviewers = {}
+
+mappings = {
+    u'alejandro "tab-lover" dubrovsky': u'Alejandro Dubrovsky',
+    u'alex hirzel <ahirzel@mtu.edu>': u'Alex Hirzel',
+    u'anonymous coward': u'Anonymous',
+    u'arthur van leeuwen': u'Arthur van Leeuwen',
+    u'augustss': u'Lennart Augustsson',
+    u'ed t': u'Anonymous',
+    u'geogre moschovitis': u'George Moschovitis',
+    u'george m': u'George Moschovitis',
+    u'haskell newb': u'Anonymous',
+    u'j. pablo fernandez': u'J. Pablo Fernández',
+    u'kamal al-marhoobi': u'Kamal Al-Marhubi',
+    u'kevin w.': u'Kevin Watters',
+    u'max cantor (#haskell - mxc)': u'Max Cantor',
+    u'michael campbell': u'Michael Campbell',
+    u'mike btauwerman': u'Mike Brauwerman',
+    u'no credit necessary': u'Anonymous',
+    u'nykänen, matti': u'Matti Nykänen',
+    u'omar antolin camarena': u'Omar Antolín Camarena',
+    u'ryan t mulligan': u'Ryan T. Mulligan',
+    u'sengan baring-gould': u'Sengan Baring-Gould',
+    u'some guy': u'Anonymous',
+    u'tomas janousek': u'Tomáš Janoušek',
+    u'william halchin': u'William N. Halchin',
+    }
+
+def fixup(s):
+    try:
+        return s.encode('ascii')
+    except UnicodeEncodeError:
+        def f(c):
+            o = ord(c)
+            if o < 128:
+                return c
+            return '&#%d;' % o
+        return ''.join(map(f, s))
+
+total = 0
+for r in c.fetchall():
+    r = r[0].decode('utf-8')
+    if r in ("Bryan O'Sullivan", 'John Goerzen', 'Don Stewart'):
+        continue
+    total += 1
+    m = mappings.get(r.lower())
+    if m:
+        r = m
+    elif len(r) < 2 or ' ' not in r:
+        r = 'Anonymous'
+    reviewers.setdefault(r, 0)
+    reviewers[r] += 1
+
+reviewers = sorted(reviewers.iteritems(), key=lambda x: x[0])
+
+cohorts = [(.01,1),(.002,.01)]
+
+for (lo,hi) in cohorts:
+    lo = total * lo
+    hi = total * hi
+    for r in [n for n in reviewers if lo <= n[1] < hi]:
+        if r[1] > 3:
+            print '%s,' % fixup(r[0])
+    print
+
+lo = total * .002
+for n in reviewers:
+    if n[1] < lo:
+        print '%s,' % fixup(n[0])

File web/hgbook/secrets.py.gpg

Binary file added.

File web/hgbook/settings.py

+# Django settings for hgbook project.
+
+import os, sys
+
+DEBUG = False
+TEMPLATE_DEBUG = DEBUG
+
+ADMINS = (
+    ("Bryan O'Sullivan", 'bos@serpentine.com'),
+)
+
+MANAGERS = ADMINS
+
+ROOT = os.path.dirname(sys.modules[__name__].__file__)
+
+try:
+    from secrets import DATABASE_ENGINE, DATABASE_NAME, DATABASE_USER, \
+         DATABASE_PASSWORD, DATABASE_HOST, DATABASE_PORT, SECRET_KEY
+except ImportError:
+    print >> sys.stderr, 'Faking up some database configuration for you'
+    DATABASE_ENGINE = 'sqlite3'
+    DATABASE_NAME = os.path.join(ROOT, '.database.sqlite3')
+    DATABASE_USER = ''
+    DATABASE_PASSWORD = ''
+    DATABASE_HOST = ''
+    DATABASE_PORT = ''
+    SECRET_KEY = ''
+
+# Local time zone for this installation. Choices can be found here:
+# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
+# although not all choices may be avilable on all operating systems.
+# If running in a Windows environment this must be set to the same as your
+# system time zone.
+TIME_ZONE = 'America/Los_Angeles'
+
+# Language code for this installation. All choices can be found here:
+# http://www.w3.org/TR/REC-html40/struct/dirlang.html#langcodes
+LANGUAGE_CODE = 'en-us'
+
+SITE_ID = 1
+
+# If you set this to False, Django will make some optimizations so as not
+# to load the internationalization machinery.
+USE_I18N = True
+
+# Absolute path to the directory that holds media.
+# Example: "/home/media/media.lawrence.com/"
+MEDIA_ROOT = ''
+
+# URL that handles the media served from MEDIA_ROOT. Make sure to use a
+# trailing slash if there is a path component (optional in other cases).
+# Examples: "http://media.lawrence.com", "http://example.com/media/"
+MEDIA_URL = ''
+
+# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
+# trailing slash.
+# Examples: "http://foo.com/media/", "/media/".
+ADMIN_MEDIA_PREFIX = '/media/'
+
+# List of callables that know how to import templates from various sources.
+TEMPLATE_LOADERS = (
+    'django.template.loaders.filesystem.load_template_source',
+    'django.template.loaders.app_directories.load_template_source',
+#     'django.template.loaders.eggs.load_template_source',
+)
+
+MIDDLEWARE_CLASSES = (
+    'django.middleware.common.CommonMiddleware',
+    'django.contrib.sessions.middleware.SessionMiddleware',
+    'django.contrib.auth.middleware.AuthenticationMiddleware',
+    'django.middleware.doc.XViewMiddleware',
+)
+
+ROOT_URLCONF = 'hgbook.urls'
+
+TEMPLATE_DIRS = (
+    os.path.join(ROOT, 'templates')
+)
+
+INSTALLED_APPS = (
+    'django.contrib.admin',
+    'django.contrib.auth',
+    'django.contrib.contenttypes',
+    'django.contrib.sessions',
+    'django.contrib.sites',
+    'hgbook.comments',
+)

File web/hgbook/templates/404.html

+{% extends "simple.html" %}
+
+{% block title %}Page Not Found{% endblock %}
+
+{% block body %}
+<p>Sorry, we hit <a href="http://www.haskell.org/haskellwiki/Bottom">&perp;</a> when trying to find the
+page you requested.</p>
+{% endblock %}

File web/hgbook/templates/500.html

+{% extends "simple.html" %}
+
+{% block title %}Internal Server Error{% endblock %}
+
+{% block body %}
+<p>Sorry, we hit <a
+href="http://www.haskell.org/haskellwiki/Bottom">&perp;</a> when
+trying to process your request.  If possible, please let <a
+href="mailto:bos@serpentine.com">Bryan</a> know that this problem happened,
+and what you were doing when it occurred.</p>
+{% endblock %}

File web/hgbook/templates/boilerplate.html

+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+  <head>
+    <title>{% block pagetitle %}Real World Haskell{% endblock %}</title>
+    <link rel="stylesheet" href="/support/styles.css" type="text/css"/>
+    <link rel="alternate" type="application/atom+xml" title="Comments"
+      href="/feeds/comments/"/>
+    <link rel="shortcut icon" type="image/png" href="/support/figs/favicon.png"/>
+    <script type="text/javascript" src="/support/jquery.js"></script>
+      <script type="text/javascript" src="/support/form.js"></script>
+    <script type="text/javascript" src="/support/hsbook.js"></script>
+  </head>
+
+  <body>
+    {% block bodycontent %}{% endblock %}
+
+    <div class="hgbookfooter"> <p><img src="/support/figs/rss.png"> Want to stay
+	up to date? Subscribe to comment feeds for any chapter, or
+	the <a class="feed"
+	  href="/feeds/comments/">entire book</a>.</p> <p>Copyright
+	2007, 2008 Bryan O'Sullivan, Don Stewart, and John Goerzen. This
+	work is licensed under a <a rel="license" href=
+	  "http://creativecommons.org/licenses/by-nc/3.0/">Creative
+	  Commons Attribution-Noncommercial 3.0 License</a>. Icons by
+	  <a href="mailto:mattahan@gmail.com">Paul Davey</a> aka <a
+	  href="http://mattahan.deviantart.com/">Mattahan</a>.</p>
+    </div>
+
+    <script src="http://www.google-analytics.com/urchin.js"
+      type="text/javascript"></script>
+    <script type="text/javascript">_uacct = "UA-1805907-3";
+      urchinTracker();</script>
+  </body>
+</html>

File web/hgbook/templates/comment.html

+{% ifequal length 1 %}
+  <a class="commenttoggle" id="toggle_{{ id }}"
+      onclick="return toggleComment('{{ id }}')"
+      href="comments: show / hide">One comment</a>
+{% else %}
+  {% if length %}
+    <a class="commenttoggle" id="toggle_{{ id }}"
+      onclick="return toggleComment('{{ id }}')"
+      href="comments: show / hide">{{ length }} comments</a>
+  {% else %}
+    <a class="commenttoggle" id="toggle_{{ id }}"
+      onclick="return toggleComment('{{ id }}')"
+      href="comments: show / hide">No comments</a>
+    <div class="comment" {% if not newid %} style="display: none;" {% endif %}>
+      <div class="comment_body">Be the first to comment on this paragraph!</div>
+    </div>
+  {% endif %}
+{% endifequal %}
+{% for c in query %}
+  <div class="{% ifequal c.id newid %}new_{% endifequal %}comment"
+      {% if not newid %} style="display: none;" {% endif %}
+    id="comment{{ c.id }}">
+    <a name="comment{{ c.id }}"/>
+    <div class="comment_header">
+	<span class="comment_id"><a href="/admin/comments/comment/{{ c.id }}/">{{ c.id }}</a></span>
+      {% if c.submitter_url %}
+	<span class="comment_name"><a rel="nofollow"
+	  href="{{ c.submitter_url }}">{{ c.submitter_name|escape }}</a></span>
+      {% else %}
+	<span class="comment_name">{{ c.submitter_name|escape }}</span>
+      {% endif %}
+      <span class="comment_date">{{ c.date|date:"Y-m-d" }}</span>
+      {% if c.reviewed %}
+	<span class="comment_reviewed">(reviewed)</span>
+      {% endif %}
+      {% ifequal c.id newid %}
+	<span class="comment_thanks">thank you for your comment!</span>
+      {% endifequal %}
+    </div>
+    <div class="comment_body">{{ c.comment|escape|linebreaks }}</div>
+  </div>
+{% endfor %}
+<form class="comment" id="form_{{ id }}" action="/comments/submit/{{ id }}/"
+    method="post" {% if not newid %} style="display: none;" {% endif %}>
+  {{ form.id }}
+  <table>
+    <tbody>
+      <tr><td align="right" valign="top">Comment<br><a class="comment_help"
+		href="web.html#web.comment">[ help ]</a></td>
+	  <td>{{ form.comment }}</td></tr>
+      <tr><td align="right">Your name</td><td>{{ form.name }}
+	    <span class="comment_help"><b>Required</b> so we can <a
+		href="web.html#web.comment.name">give you credit</a></span></td></tr>
+      <tr><td align="right">Your URL</td><td>{{ form.url }}
+	    <span class="comment_help"><b>Optional</b> link to blog, home page,
+	      <i>etc</i>.</span></td></tr>
+      <tr><td align="right">Remember you?</td><td>{{ form.remember }}</td></tr>
+      <tr><td/><td><input name="submit" type="submit"
+	      value="Submit Comment"/><span class="comment_error">{{ error }}</span></td></tr>
+    </tbody>
+  </table>
+</form>

File web/hgbook/templates/feeds/comments_description.html

+<p>On {{ obj.date|date:"Y-m-d" }},
+      {% if obj.submitter_url %}
+	<a rel="nofollow" href="{{ obj.submitter_url }}">{{ obj.submitter_name|escape }}</a>
+      {% else %}
+	{{ obj.submitter_name|escape }}
+      {% endif %}
+commented on &#8220;{{ obj.element.title|escape }}&#8221;:</p>
+<blockquote>
+{{ obj.comment|escape|linebreaks }}
+</blockquote>
+<p>To see this comment in context or to respond, visit <a
+	href="http://{{ site.domain }}{{ obj.get_absolute_url }}">{{ site.domain }}</a></p>

File web/hgbook/templates/feeds/comments_title.html

+Comment on &#8220;{{ obj.element.title|escape }}&#8221;

File web/hgbook/templates/simple.html

+{% extends "boilerplate.html" %}
+
+{% block bodycontent %}
+<div class="navheader"><h1>{% block title %}{% endblock %}</h1></div>
+
+<div class="basetemplate">{% block body %}{% endblock %}</div>
+{% endblock %}

File web/hgbook/urls.py

+import os
+from django.conf.urls.defaults import *
+import hgbook.comments.feeds as feeds
+
+feeds = {
+    'comments': feeds.Comments,
+    }
+
+urlpatterns = patterns('',
+    (r'^comments/', include('hgbook.comments.urls')),
+
+    (r'^feeds/(?P<url>.*)/$', 'django.contrib.syndication.views.feed',
+     {'feed_dict': feeds}),          
+
+    # Only uncomment this for local testing without Apache.
+    # (r'^html/(?P<path>.*)$', 'django.views.static.serve',
+    # {'document_root': os.path.realpath(os.path.dirname(
+    #    sys.modules[__name__].__file__) + '/../../en/html'),
+
+    # Uncomment this for admin:
+    (r'^admin/', include('django.contrib.admin.urls')),
+)

File web/icons/caution.png

Added
New image

File web/icons/favicon.png

Added
New image

File web/icons/important.png

Added
New image

File web/icons/note.png

Added
New image

File web/icons/remark.png

Added
New image

File web/icons/rss.png

Added
New image

File web/icons/shell.png

Added
New image

File web/icons/source.png

Added
New image

File web/icons/tip.png

Added
New image

File web/icons/warning.png

Added
New image

File web/javascript/form-min.js

+(function($){$.fn.ajaxSubmit=function(_2){if(typeof _2=="function"){_2={success:_2};}_2=$.extend({url:this.attr("action")||window.location,type:this.attr("method")||"GET"},_2||{});var _3={};$.event.trigger("form.pre.serialize",[this,_2,_3]);if(_3.veto){return this;}var a=this.formToArray(_2.semantic);if(_2.data){for(var n in _2.data){a.push({name:n,value:_2.data[n]});}}if(_2.beforeSubmit&&_2.beforeSubmit(a,this,_2)===false){return this;}$.event.trigger("form.submit.validate",[a,this,_2,_3]);if(_3.veto){return this;}var q=$.param(a);if(_2.type.toUpperCase()=="GET"){_2.url+=(_2.url.indexOf("?")>=0?"&":"?")+q;_2.data=null;}else{_2.data=q;}var _7=this,callbacks=[];if(_2.resetForm){callbacks.push(function(){_7.resetForm();});}if(_2.clearForm){callbacks.push(function(){_7.clearForm();});}if(!_2.dataType&&_2.target){var _8=_2.success||function(){};callbacks.push(function(_9){if(this.evalScripts){$(_2.target).attr("innerHTML",_9).evalScripts().each(_8,arguments);}else{$(_2.target).html(_9).each(_8,arguments);}});}else{if(_2.success){callbacks.push(_2.success);}}_2.success=function(_a,_b){for(var i=0,max=callbacks.length;i<max;i++){callbacks[i](_a,_b,_7);}};var _d=$("input:file",this).fieldValue();var _e=false;for(var j=0;j<_d.length;j++){if(_d[j]){_e=true;}}if(_2.iframe||_e){fileUpload();}else{$.ajax(_2);}$.event.trigger("form.submit.notify",[this,_2]);return this;function fileUpload(){var _10=_7[0];var _11=$.extend({},$.ajaxSettings,_2);var id="jqFormIO"+$.fn.ajaxSubmit.counter++;var $io=$("<iframe id=\""+id+"\" name=\""+id+"\" />");var io=$io[0];var op8=$.browser.opera&&window.opera.version()<9;if($.browser.msie||op8){io.src="javascript:false;document.write(\"\");";}$io.css({position:"absolute",top:"-1000px",left:"-1000px"});var xhr={responseText:null,responseXML:null,status:0,statusText:"n/a",getAllResponseHeaders:function(){},getResponseHeader:function(){},setRequestHeader:function(){}};var g=_11.global;if(g&&!$.active++){$.event.trigger("ajaxStart");}if(g){$.event.trigger("ajaxSend",[xhr,_11]);}var _18=0;var _19=0;setTimeout(function(){$io.appendTo("body");io.attachEvent?io.attachEvent("onload",cb):io.addEventListener("load",cb,false);var _1a=_10.encoding?"encoding":"enctype";var t=_7.attr("target");_7.attr({target:id,method:"POST",action:_11.url});_10[_1a]="multipart/form-data";if(_11.timeout){setTimeout(function(){_19=true;cb();},_11.timeout);}_10.submit();_7.attr("target",t);},10);function cb(){if(_18++){return;}io.detachEvent?io.detachEvent("onload",cb):io.removeEventListener("load",cb,false);var ok=true;try{if(_19){throw "timeout";}var _1d,doc;doc=io.contentWindow?io.contentWindow.document:io.contentDocument?io.contentDocument:io.document;xhr.responseText=doc.body?doc.body.innerHTML:null;xhr.responseXML=doc.XMLDocument?doc.XMLDocument:doc;if(_11.dataType=="json"||_11.dataType=="script"){var ta=doc.getElementsByTagName("textarea")[0];_1d=ta?ta.value:xhr.responseText;if(_11.dataType=="json"){eval("data = "+_1d);}else{$.globalEval(_1d);}}else{if(_11.dataType=="xml"){_1d=xhr.responseXML;if(!_1d&&xhr.responseText!=null){_1d=toXml(xhr.responseText);}}else{_1d=xhr.responseText;}}}catch(e){ok=false;$.handleError(_11,xhr,"error",e);}if(ok){_11.success(_1d,"success");if(g){$.event.trigger("ajaxSuccess",[xhr,_11]);}}if(g){$.event.trigger("ajaxComplete",[xhr,_11]);}if(g&&!--$.active){$.event.trigger("ajaxStop");}if(_11.complete){_11.complete(xhr,ok?"success":"error");}setTimeout(function(){$io.remove();xhr.responseXML=null;},100);}function toXml(s,doc){if(window.ActiveXObject){doc=new ActiveXObject("Microsoft.XMLDOM");doc.async="false";doc.loadXML(s);}else{doc=(new DOMParser()).parseFromString(s,"text/xml");}return (doc&&doc.documentElement&&doc.documentElement.tagName!="parsererror")?doc:null;}}};$.fn.ajaxSubmit.counter=0;$.fn.ajaxForm=function(_21){return this.ajaxFormUnbind().submit(submitHandler).each(function(){this.formPluginId=$.fn.ajaxForm.counter++;$.fn.ajaxForm.optionHash[this.formPluginId]=_21;$(":submit,input:image",this).click(clickHandler);});};$.fn.ajaxForm.counter=1;$.fn.ajaxForm.optionHash={};function clickHandler(e){var _23=this.form;_23.clk=this;if(this.type=="image"){if(e.offsetX!=undefined){_23.clk_x=e.offsetX;_23.clk_y=e.offsetY;}else{if(typeof $.fn.offset=="function"){var _24=$(this).offset();_23.clk_x=e.pageX-_24.left;_23.clk_y=e.pageY-_24.top;}else{_23.clk_x=e.pageX-this.offsetLeft;_23.clk_y=e.pageY-this.offsetTop;}}}setTimeout(function(){_23.clk=_23.clk_x=_23.clk_y=null;},10);}function submitHandler(){var id=this.formPluginId;var _26=$.fn.ajaxForm.optionHash[id];$(this).ajaxSubmit(_26);return false;}$.fn.ajaxFormUnbind=function(){this.unbind("submit",submitHandler);return this.each(function(){$(":submit,input:image",this).unbind("click",clickHandler);});};$.fn.formToArray=function(_27){var a=[];if(this.length==0){return a;}var _29=this[0];var els=_27?_29.getElementsByTagName("*"):_29.elements;if(!els){return a;}for(var i=0,max=els.length;i<max;i++){var el=els[i];var n=el.name;if(!n){continue;}if(_27&&_29.clk&&el.type=="image"){if(!el.disabled&&_29.clk==el){a.push({name:n+".x",value:_29.clk_x},{name:n+".y",value:_29.clk_y});}continue;}var v=$.fieldValue(el,true);if(v&&v.constructor==Array){for(var j=0,jmax=v.length;j<jmax;j++){a.push({name:n,value:v[j]});}}else{if(v!==null&&typeof v!="undefined"){a.push({name:n,value:v});}}}if(!_27&&_29.clk){var _30=_29.getElementsByTagName("input");for(var i=0,max=_30.length;i<max;i++){var _32=_30[i];var n=_32.name;if(n&&!_32.disabled&&_32.type=="image"&&_29.clk==_32){a.push({name:n+".x",value:_29.clk_x},{name:n+".y",value:_29.clk_y});}}}return a;};$.fn.formSerialize=function(_34){return $.param(this.formToArray(_34));};$.fn.fieldSerialize=function(_35){var a=[];this.each(function(){var n=this.name;if(!n){return;}var v=$.fieldValue(this,_35);if(v&&v.constructor==Array){for(var i=0,max=v.length;i<max;i++){a.push({name:n,value:v[i]});}}else{if(v!==null&&typeof v!="undefined"){a.push({name:this.name,value:v});}}});return $.param(a);};$.fn.fieldValue=function(_3a){for(var val=[],i=0,max=this.length;i<max;i++){var el=this[i];var v=$.fieldValue(el,_3a);if(v===null||typeof v=="undefined"||(v.constructor==Array&&!v.length)){continue;}v.constructor==Array?$.merge(val,v):val.push(v);}return val;};$.fieldValue=function(el,_3f){var n=el.name,t=el.type,tag=el.tagName.toLowerCase();if(typeof _3f=="undefined"){_3f=true;}if(_3f&&(!n||el.disabled||t=="reset"||t=="button"||(t=="checkbox"||t=="radio")&&!el.checked||(t=="submit"||t=="image")&&el.form&&el.form.clk!=el||tag=="select"&&el.selectedIndex==-1)){return null;}if(tag=="select"){var _41=el.selectedIndex;if(_41<0){return null;}var a=[],ops=el.options;var one=(t=="select-one");var max=(one?_41+1:ops.length);for(var i=(one?_41:0);i<max;i++){var op=ops[i];if(op.selected){var v=$.browser.msie&&!(op.attributes["value"].specified)?op.text:op.value;if(one){return v;}a.push(v);}}return a;}return el.value;};$.fn.clearForm=function(){return this.each(function(){$("input,select,textarea",this).clearFields();});};$.fn.clearFields=$.fn.clearInputs=function(){return this.each(function(){var t=this.type,tag=this.tagName.toLowerCase();if(t=="text"||t=="password"||tag=="textarea"){this.value="";}else{if(t=="checkbox"||t=="radio"){this.checked=false;}else{if(tag=="select"){this.selectedIndex=-1;}}}});};$.fn.resetForm=function(){return this.each(function(){if(typeof this.reset=="function"||(typeof this.reset=="object"&&!this.reset.nodeType)){this.reset();}});};})(jQuery);

File web/javascript/form.js

+/*
+ * jQuery Form Plugin
+ * @requires jQuery v1.1 or later
+ *
+ * Examples at: http://malsup.com/jquery/form/
+ * Dual licensed under the MIT and GPL licenses:
+ *   http://www.opensource.org/licenses/mit-license.php
+ *   http://www.gnu.org/licenses/gpl.html
+ *
+ * Revision: $Id$
+ */
+ (function($) {
+/**
+ * ajaxSubmit() provides a mechanism for submitting an HTML form using AJAX.
+ *
+ * ajaxSubmit accepts a single argument which can be either a success callback function
+ * or an options Object.  If a function is provided it will be invoked upon successful
+ * completion of the submit and will be passed the response from the server.
+ * If an options Object is provided, the following attributes are supported:
+ *
+ *  target:   Identifies the element(s) in the page to be updated with the server response.
+ *            This value may be specified as a jQuery selection string, a jQuery object,
+ *            or a DOM element.
+ *            default value: null
+ *
+ *  url:      URL to which the form data will be submitted.
+ *            default value: value of form's 'action' attribute
+ *
+ *  type:     The method in which the form data should be submitted, 'GET' or 'POST'.
+ *            default value: value of form's 'method' attribute (or 'GET' if none found)
+ *
+ *  data:     Additional data to add to the request, specified as key/value pairs (see $.ajax).
+ *
+ *  beforeSubmit:  Callback method to be invoked before the form is submitted.
+ *            default value: null
+ *
+ *  success:  Callback method to be invoked after the form has been successfully submitted
+ *            and the response has been returned from the server
+ *            default value: null
+ *
+ *  dataType: Expected dataType of the response.  One of: null, 'xml', 'script', or 'json'
+ *            default value: null
+ *
+ *  semantic: Boolean flag indicating whether data must be submitted in semantic order (slower).
+ *            default value: false
+ *
+ *  resetForm: Boolean flag indicating whether the form should be reset if the submit is successful
+ *
+ *  clearForm: Boolean flag indicating whether the form should be cleared if the submit is successful
+ *
+ *
+ * The 'beforeSubmit' callback can be provided as a hook for running pre-submit logic or for
+ * validating the form data.  If the 'beforeSubmit' callback returns false then the form will
+ * not be submitted. The 'beforeSubmit' callback is invoked with three arguments: the form data
+ * in array format, the jQuery object, and the options object passed into ajaxSubmit.
+ * The form data array takes the following form:
+ *
+ *     [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
+ *
+ * If a 'success' callback method is provided it is invoked after the response has been returned
+ * from the server.  It is passed the responseText or responseXML value (depending on dataType).
+ * See jQuery.ajax for further details.
+ *
+ *
+ * The dataType option provides a means for specifying how the server response should be handled.
+ * This maps directly to the jQuery.httpData method.  The following values are supported:
+ *
+ *      'xml':    if dataType == 'xml' the server response is treated as XML and the 'success'
+ *                   callback method, if specified, will be passed the responseXML value
+ *      'json':   if dataType == 'json' the server response will be evaluted and passed to
+ *                   the 'success' callback, if specified
+ *      'script': if dataType == 'script' the server response is evaluated in the global context
+ *
+ *
+ * Note that it does not make sense to use both the 'target' and 'dataType' options.  If both
+ * are provided the target will be ignored.
+ *
+ * The semantic argument can be used to force form serialization in semantic order.
+ * This is normally true anyway, unless the form contains input elements of type='image'.
+ * If your form must be submitted with name/value pairs in semantic order and your form
+ * contains an input of type='image" then pass true for this arg, otherwise pass false
+ * (or nothing) to avoid the overhead for this logic.
+ *
+ *
+ * When used on its own, ajaxSubmit() is typically bound to a form's submit event like this:
+ *
+ * $("#form-id").submit(function() {
+ *     $(this).ajaxSubmit(options);
+ *     return false; // cancel conventional submit
+ * });
+ *
+ * When using ajaxForm(), however, this is done for you.
+ *
+ * @example
+ * $('#myForm').ajaxSubmit(function(data) {
+ *     alert('Form submit succeeded! Server returned: ' + data);
+ * });
+ * @desc Submit form and alert server response
+ *
+ *
+ * @example
+ * var options = {
+ *     target: '#myTargetDiv'
+ * };
+ * $('#myForm').ajaxSubmit(options);
+ * @desc Submit form and update page element with server response
+ *
+ *
+ * @example
+ * var options = {
+ *     success: function(responseText) {
+ *         alert(responseText);
+ *     }
+ * };
+ * $('#myForm').ajaxSubmit(options);
+ * @desc Submit form and alert the server response
+ *
+ *
+ * @example
+ * var options = {
+ *     beforeSubmit: function(formArray, jqForm) {
+ *         if (formArray.length == 0) {
+ *             alert('Please enter data.');
+ *             return false;
+ *         }
+ *     }
+ * };
+ * $('#myForm').ajaxSubmit(options);
+ * @desc Pre-submit validation which aborts the submit operation if form data is empty
+ *
+ *
+ * @example
+ * var options = {
+ *     url: myJsonUrl.php,
+ *     dataType: 'json',
+ *     success: function(data) {
+ *        // 'data' is an object representing the the evaluated json data
+ *     }
+ * };
+ * $('#myForm').ajaxSubmit(options);
+ * @desc json data returned and evaluated
+ *
+ *
+ * @example
+ * var options = {
+ *     url: myXmlUrl.php,
+ *     dataType: 'xml',
+ *     success: function(responseXML) {
+ *        // responseXML is XML document object
+ *        var data = $('myElement', responseXML).text();
+ *     }
+ * };
+ * $('#myForm').ajaxSubmit(options);
+ * @desc XML data returned from server
+ *
+ *
+ * @example
+ * var options = {
+ *     resetForm: true
+ * };
+ * $('#myForm').ajaxSubmit(options);
+ * @desc submit form and reset it if successful
+ *
+ * @example
+ * $('#myForm).submit(function() {
+ *    $(this).ajaxSubmit();
+ *    return false;
+ * });
+ * @desc Bind form's submit event to use ajaxSubmit
+ *
+ *
+ * @name ajaxSubmit
+ * @type jQuery
+ * @param options  object literal containing options which control the form submission process
+ * @cat Plugins/Form
+ * @return jQuery
+ */
+$.fn.ajaxSubmit = function(options) {
+    if (typeof options == 'function')
+        options = { success: options };
+
+    options = $.extend({
+        url:  this.attr('action') || window.location,
+        type: this.attr('method') || 'GET'
+    }, options || {});
+
+    // hook for manipulating the form data before it is extracted;
+    // convenient for use with rich editors like tinyMCE or FCKEditor
+    var veto = {};
+    $.event.trigger('form.pre.serialize', [this, options, veto]);
+    if (veto.veto) return this;
+
+    var a = this.formToArray(options.semantic);
+	if (options.data) {
+	    for (var n in options.data)
+	        a.push( { name: n, value: options.data[n] } );
+	}
+
+    // give pre-submit callback an opportunity to abort the submit
+    if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) return this;
+
+    // fire vetoable 'validate' event
+    $.event.trigger('form.submit.validate', [a, this, options, veto]);
+    if (veto.veto) return this;
+
+    var q = $.param(a);//.replace(/%20/g,'+');
+
+    if (options.type.toUpperCase() == 'GET') {
+        options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q;
+        options.data = null;  // data is null for 'get'
+    }
+    else
+        options.data = q; // data is the query string for 'post'
+
+    var $form = this, callbacks = [];
+    if (options.resetForm) callbacks.push(function() { $form.resetForm(); });
+    if (options.clearForm) callbacks.push(function() { $form.clearForm(); });
+
+    // perform a load on the target only if dataType is not provided
+    if (!options.dataType && options.target) {
+        var oldSuccess = options.success || function(){};
+        callbacks.push(function(data) {
+            if (this.evalScripts)
+                $(options.target).attr("innerHTML", data).evalScripts().each(oldSuccess, arguments);
+            else // jQuery v1.1.4
+                $(options.target).html(data).each(oldSuccess, arguments);
+        });
+    }
+    else if (options.success)
+        callbacks.push(options.success);
+
+    options.success = function(data, status) {
+        for (var i=0, max=callbacks.length; i < max; i++)
+            callbacks[i](data, status, $form);
+    };
+
+    // are there files to upload?
+    var files = $('input:file', this).fieldValue();
+    var found = false;
+    for (var j=0; j < files.length; j++)
+        if (files[j])
+            found = true;
+
+    if (options.iframe || found) // options.iframe allows user to force iframe mode
+        fileUpload();
+    else
+        $.ajax(options);
+
+    // fire 'notify' event
+    $.event.trigger('form.submit.notify', [this, options]);
+    return this;
+
+
+    // private function for handling file uploads (hat tip to YAHOO!)
+    function fileUpload() {
+        var form = $form[0];
+        var opts = $.extend({}, $.ajaxSettings, options);
+
+        var id = 'jqFormIO' + $.fn.ajaxSubmit.counter++;
+        var $io = $('<iframe id="' + id + '" name="' + id + '" />');
+        var io = $io[0];
+        var op8 = $.browser.opera && window.opera.version() < 9;
+        if ($.browser.msie || op8) io.src = 'javascript:false;document.write("");';
+        $io.css({ position: 'absolute', top: '-1000px', left: '-1000px' });
+
+        var xhr = { // mock object
+            responseText: null,
+            responseXML: null,
+            status: 0,
+            statusText: 'n/a',
+            getAllResponseHeaders: function() {},
+            getResponseHeader: function() {},
+            setRequestHeader: function() {}
+        };
+
+        var g = opts.global;
+        // trigger ajax global events so that activity/block indicators work like normal
+        if (g && ! $.active++) $.event.trigger("ajaxStart");
+        if (g) $.event.trigger("ajaxSend", [xhr, opts]);
+
+        var cbInvoked = 0;
+        var timedOut = 0;
+
+        // take a breath so that pending repaints get some cpu time before the upload starts
+        setTimeout(function() {
+            $io.appendTo('body');
+            // jQuery's event binding doesn't work for iframe events in IE
+            io.attachEvent ? io.attachEvent('onload', cb) : io.addEventListener('load', cb, false);
+
+            // make sure form attrs are set
+            var encAttr = form.encoding ? 'encoding' : 'enctype';
+            var t = $form.attr('target');
+            $form.attr({
+                target:   id,
+                method:  'POST',
+                action:   opts.url
+            });
+            form[encAttr] = 'multipart/form-data';
+
+            // support timout
+            if (opts.timeout)
+                setTimeout(function() { timedOut = true; cb(); }, opts.timeout);
+
+            form.submit();
+            $form.attr('target', t); // reset target
+        }, 10);
+
+        function cb() {
+            if (cbInvoked++) return;
+
+            io.detachEvent ? io.detachEvent('onload', cb) : io.removeEventListener('load', cb, false);
+
+            var ok = true;
+            try {
+                if (timedOut) throw 'timeout';
+                // extract the server response from the iframe
+                var data, doc;
+                doc = io.contentWindow ? io.contentWindow.document : io.contentDocument ? io.contentDocument : io.document;
+                xhr.responseText = doc.body ? doc.body.innerHTML : null;
+                xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc;
+
+                if (opts.dataType == 'json' || opts.dataType == 'script') {
+                    var ta = doc.getElementsByTagName('textarea')[0];
+                    data = ta ? ta.value : xhr.responseText;
+                    if (opts.dataType == 'json')
+                        eval("data = " + data);
+                    else
+                        $.globalEval(data);
+                }
+                else if (opts.dataType == 'xml') {
+                    data = xhr.responseXML;
+                    if (!data && xhr.responseText != null)
+                        data = toXml(xhr.responseText);
+                }
+                else {
+                    data = xhr.responseText;
+                }
+            }
+            catch(e){
+                ok = false;
+                $.handleError(opts, xhr, 'error', e);
+            }
+
+            // ordering of these callbacks/triggers is odd, but that's how $.ajax does it
+            if (ok) {
+                opts.success(data, 'success');
+                if (g) $.event.trigger("ajaxSuccess", [xhr, opts]);
+            }
+            if (g) $.event.trigger("ajaxComplete", [xhr, opts]);
+            if (g && ! --$.active) $.event.trigger("ajaxStop");
+            if (opts.complete) opts.complete(xhr, ok ? 'success' : 'error');
+
+            // clean up
+            setTimeout(function() {
+                $io.remove();
+                xhr.responseXML = null;
+            }, 100);
+        };
+
+        function toXml(s, doc) {
+            if (window.ActiveXObject) {
+                doc = new ActiveXObject('Microsoft.XMLDOM');
+                doc.async = 'false';
+                doc.loadXML(s);
+            }
+            else
+                doc = (new DOMParser()).parseFromString(s, 'text/xml');
+            return (doc && doc.documentElement && doc.documentElement.tagName != 'parsererror') ? doc : null;
+        };
+    };
+};
+$.fn.ajaxSubmit.counter = 0; // used to create unique iframe ids
+
+/**
+ * ajaxForm() provides a mechanism for fully automating form submission.
+ *
+ * The advantages of using this method instead of ajaxSubmit() are:
+ *
+ * 1: This method will include coordinates for <input type="image" /> elements (if the element
+ *    is used to submit the form).
+ * 2. This method will include the submit element's name/value data (for the element that was
+ *    used to submit the form).
+ * 3. This method binds the submit() method to the form for you.
+ *
+ * Note that for accurate x/y coordinates of image submit elements in all browsers
+ * you need to also use the "dimensions" plugin (this method will auto-detect its presence).
+ *
+ * The options argument for ajaxForm works exactly as it does for ajaxSubmit.  ajaxForm merely
+ * passes the options argument along after properly binding events for submit elements and
+ * the form itself.  See ajaxSubmit for a full description of the options argument.
+ *
+ *
+ * @example
+ * var options = {
+ *     target: '#myTargetDiv'
+ * };
+ * $('#myForm').ajaxSForm(options);
+ * @desc Bind form's submit event so that 'myTargetDiv' is updated with the server response
+ *       when the form is submitted.
+ *
+ *
+ * @example
+ * var options = {
+ *     success: function(responseText) {
+ *         alert(responseText);
+ *     }
+ * };
+ * $('#myForm').ajaxSubmit(options);
+ * @desc Bind form's submit event so that server response is alerted after the form is submitted.
+ *
+ *
+ * @example
+ * var options = {
+ *     beforeSubmit: function(formArray, jqForm) {
+ *         if (formArray.length == 0) {
+ *             alert('Please enter data.');
+ *             return false;
+ *         }
+ *     }
+ * };
+ * $('#myForm').ajaxSubmit(options);
+ * @desc Bind form's submit event so that pre-submit callback is invoked before the form
+ *       is submitted.
+ *
+ *
+ * @name   ajaxForm
+ * @param  options  object literal containing options which control the form submission process
+ * @return jQuery
+ * @cat    Plugins/Form
+ * @type   jQuery
+ */
+$.fn.ajaxForm = function(options) {
+    return this.ajaxFormUnbind().submit(submitHandler).each(function() {
+        // store options in hash
+        this.formPluginId = $.fn.ajaxForm.counter++;
+        $.fn.ajaxForm.optionHash[this.formPluginId] = options;
+        $(":submit,input:image", this).click(clickHandler);
+    });
+};
+
+$.fn.ajaxForm.counter = 1;
+$.fn.ajaxForm.optionHash = {};
+
+function clickHandler(e) {
+    var $form = this.form;
+    $form.clk = this;
+    if (this.type == 'image') {
+        if (e.offsetX != undefined) {
+            $form.clk_x = e.offsetX;
+            $form.clk_y = e.offsetY;
+        } else if (typeof $.fn.offset == 'function') { // try to use dimensions plugin
+            var offset = $(this).offset();
+            $form.clk_x = e.pageX - offset.left;
+            $form.clk_y = e.pageY - offset.top;
+        } else {
+            $form.clk_x = e.pageX - this.offsetLeft;
+            $form.clk_y = e.pageY - this.offsetTop;
+        }
+    }
+    // clear form vars
+    setTimeout(function() { $form.clk = $form.clk_x = $form.clk_y = null; }, 10);
+};
+
+function submitHandler() {
+    // retrieve options from hash
+    var id = this.formPluginId;
+    var options = $.fn.ajaxForm.optionHash[id];
+    $(this).ajaxSubmit(options);
+    return false;
+};
+
+/**
+ * ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm
+ *
+ * @name   ajaxFormUnbind
+ * @return jQuery
+ * @cat    Plugins/Form
+ * @type   jQuery
+ */
+$.fn.ajaxFormUnbind = function() {
+    this.unbind('submit', submitHandler);
+    return this.each(function() {
+        $(":submit,input:image", this).unbind('click', clickHandler);
+    });
+
+};
+
+/**
+ * formToArray() gathers form element data into an array of objects that can
+ * be passed to any of the following ajax functions: $.get, $.post, or load.
+ * Each object in the array has both a 'name' and 'value' property.  An example of
+ * an array for a simple login form might be:
+ *
+ * [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
+ *
+ * It is this array that is passed to pre-submit callback functions provided to the
+ * ajaxSubmit() and ajaxForm() methods.
+ *
+ * The semantic argument can be used to force form serialization in semantic order.
+ * This is normally true anyway, unless the form contains input elements of type='image'.
+ * If your form must be submitted with name/value pairs in semantic order and your form
+ * contains an input of type='image" then pass true for this arg, otherwise pass false
+ * (or nothing) to avoid the overhead for this logic.
+ *
+ * @example var data = $("#myForm").formToArray();
+ * $.post( "myscript.cgi", data );
+ * @desc Collect all the data from a form and submit it to the server.
+ *
+ * @name formToArray
+ * @param semantic true if serialization must maintain strict semantic ordering of elements (slower)
+ * @type Array<Object>
+ * @cat Plugins/Form
+ */
+$.fn.formToArray = function(semantic) {
+    var a = [];
+    if (this.length == 0) return a;
+
+    var form = this[0];
+    var els = semantic ? form.getElementsByTagName('*') : form.elements;
+    if (!els) return a;
+    for(var i=0, max=els.length; i < max; i++) {
+        var el = els[i];
+        var n = el.name;
+        if (!n) continue;
+
+        if (semantic && form.clk && el.type == "image") {
+            // handle image inputs on the fly when semantic == true
+            if(!el.disabled && form.clk == el)
+                a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
+            continue;
+        }
+
+        var v = $.fieldValue(el, true);
+        if (v && v.constructor == Array) {
+            for(var j=0, jmax=v.length; j < jmax; j++)
+                a.push({name: n, value: v[j]});
+        }
+        else if (v !== null && typeof v != 'undefined')
+            a.push({name: n, value: v});
+    }
+
+    if (!semantic && form.clk) {
+        // input type=='image' are not found in elements array! handle them here
+        var inputs = form.getElementsByTagName("input");
+        for(var i=0, max=inputs.length; i < max; i++) {
+            var input = inputs[i];
+            var n = input.name;
+            if(n && !input.disabled && input.type == "image" && form.clk == input)
+                a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
+        }
+    }
+    return a;
+};
+
+
+/**
+ * Serializes form data into a 'submittable' string. This method will return a string
+ * in the format: name1=value1&amp;name2=value2
+ *
+ * The semantic argument can be used to force form serialization in semantic order.
+ * If your form must be submitted with name/value pairs in semantic order then pass
+ * true for this arg, otherwise pass false (or nothing) to avoid the overhead for
+ * this logic (which can be significant for very large forms).
+ *
+ * @example var data = $("#myForm").formSerialize();
+ * $.ajax('POST', "myscript.cgi", data);
+ * @desc Collect all the data from a form into a single string
+ *
+ * @name formSerialize
+ * @param semantic true if serialization must maintain strict semantic ordering of elements (slower)
+ * @type String
+ * @cat Plugins/Form
+ */
+$.fn.formSerialize = function(semantic) {
+    //hand off to jQuery.param for proper encoding
+    return $.param(this.formToArray(semantic));
+};
+
+
+/**
+ * Serializes all field elements in the jQuery object into a query string.
+ * This method will return a string in the format: name1=value1&amp;name2=value2
+ *
+ * The successful argument controls whether or not serialization is limited to
+ * 'successful' controls (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
+ * The default value of the successful argument is true.
+ *
+ * @example var data = $("input").formSerialize();
+ * @desc Collect the data from all successful input elements into a query string
+ *
+ * @example var data = $(":radio").formSerialize();
+ * @desc Collect the data from all successful radio input elements into a query string
+ *
+ * @example var data = $("#myForm :checkbox").formSerialize();
+ * @desc Collect the data from all successful checkbox input elements in myForm into a query string
+ *
+ * @example var data = $("#myForm :checkbox").formSerialize(false);
+ * @desc Collect the data from all checkbox elements in myForm (even the unchecked ones) into a query string
+ *
+ * @example var data = $(":input").formSerialize();
+ * @desc Collect the data from all successful input, select, textarea and button elements into a query string
+ *
+ * @name fieldSerialize
+ * @param successful true if only successful controls should be serialized (default is true)
+ * @type String
+ * @cat Plugins/Form
+ */
+$.fn.fieldSerialize = function(successful) {
+    var a = [];
+    this.each(function() {
+        var n = this.name;
+        if (!n) return;
+        var v = $.fieldValue(this, successful);
+        if (v && v.constructor == Array) {
+            for (var i=0,max=v.length; i < max; i++)
+                a.push({name: n, value: v[i]});
+        }
+        else if (v !== null && typeof v != 'undefined')
+            a.push({name: this.name, value: v});
+    });
+    //hand off to jQuery.param for proper encoding
+    return $.param(a);
+};
+
+
+/**
+ * Returns the value(s) of the element in the matched set.  For example, consider the following form:
+ *
+ *  <form><fieldset>
+ *      <input name="A" type="text" />
+ *      <input name="A" type="text" />
+ *      <input name="B" type="checkbox" value="B1" />
+ *      <input name="B" type="checkbox" value="B2"/>
+ *      <input name="C" type="radio" value="C1" />
+ *      <input name="C" type="radio" value="C2" />
+ *  </fieldset></form>
+ *
+ *  var v = $(':text').fieldValue();
+ *  // if no values are entered into the text inputs
+ *  v == ['','']
+ *  // if values entered into the text inputs are 'foo' and 'bar'
+ *  v == ['foo','bar']
+ *
+ *  var v = $(':checkbox').fieldValue();
+ *  // if neither checkbox is checked
+ *  v === undefined
+ *  // if both checkboxes are checked
+ *  v == ['B1', 'B2']
+ *
+ *  var v = $(':radio').fieldValue();
+ *  // if neither radio is checked
+ *  v === undefined
+ *  // if first radio is checked
+ *  v == ['C1']
+ *
+ * The successful argument controls whether or not the field element must be 'successful'
+ * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
+ * The default value of the successful argument is true.  If this value is false the value(s)
+ * for each element is returned.
+ *
+ * Note: This method *always* returns an array.  If no valid value can be determined the
+ *       array will be empty, otherwise it will contain one or more values.
+ *
+ * @example var data = $("#myPasswordElement").fieldValue();
+ * alert(data[0]);
+ * @desc Alerts the current value of the myPasswordElement element
+ *
+ * @example var data = $("#myForm :input").fieldValue();
+ * @desc Get the value(s) of the form elements in myForm
+ *
+ * @example var data = $("#myForm :checkbox").fieldValue();
+ * @desc Get the value(s) for the successful checkbox element(s) in the jQuery object.
+ *
+ * @example var data = $("#mySingleSelect").fieldValue();
+ * @desc Get the value(s) of the select control
+ *
+ * @example var data = $(':text').fieldValue();
+ * @desc Get the value(s) of the text input or textarea elements
+ *
+ * @example var data = $("#myMultiSelect").fieldValue();
+ * @desc Get the values for the select-multiple control
+ *
+ * @name fieldValue
+ * @param Boolean successful true if only the values for successful controls should be returned (default is true)
+ * @type Array<String>
+ * @cat Plugins/Form
+ */
+$.fn.fieldValue = function(successful) {
+    for (var val=[], i=0, max=this.length; i < max; i++) {
+        var el = this[i];
+        var v = $.fieldValue(el, successful);
+        if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length))
+            continue;
+        v.constructor == Array ? $.merge(val, v) : val.push(v);
+    }
+    return val;
+};
+
+/**
+ * Returns the value of the field element.
+ *
+ * The successful argument controls whether or not the field element must be 'successful'
+ * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
+ * The default value of the successful argument is true.  If the given element is not
+ * successful and the successful arg is not false then the returned value will be null.
+ *
+ * Note: If the successful flag is true (default) but the element is not successful, the return will be null
+ * Note: The value returned for a successful select-multiple element will always be an array.
+ * Note: If the element has no value the return value will be undefined.
+ *
+ * @example var data = jQuery.fieldValue($("#myPasswordElement")[0]);
+ * @desc Gets the current value of the myPasswordElement element
+ *
+ * @name fieldValue
+ * @param Element el The DOM element for which the value will be returned
+ * @param Boolean successful true if value returned must be for a successful controls (default is true)
+ * @type String or Array<String> or null or undefined
+ * @cat Plugins/Form
+ */
+$.fieldValue = function(el, successful) {
+    var n = el.name, t = el.type, tag = el.tagName.toLowerCase();
+    if (typeof successful == 'undefined') successful = true;
+
+    if (successful && (!n || el.disabled || t == 'reset' || t == 'button' ||
+        (t == 'checkbox' || t == 'radio') && !el.checked ||
+        (t == 'submit' || t == 'image') && el.form && el.form.clk != el ||
+        tag == 'select' && el.selectedIndex == -1))
+            return null;
+
+    if (tag == 'select') {
+        var index = el.selectedIndex;
+        if (index < 0) return null;
+        var a = [], ops = el.options;
+        var one = (t == 'select-one');
+        var max = (one ? index+1 : ops.length);
+        for(var i=(one ? index : 0); i < max; i++) {
+            var op = ops[i];
+            if (op.selected) {
+                // extra pain for IE...
+                var v = $.browser.msie && !(op.attributes['value'].specified) ? op.text : op.value;
+                if (one) return v;
+                a.push(v);
+            }
+        }
+        return a;
+    }
+    return el.value;
+};
+
+
+/**
+ * Clears the form data.  Takes the following actions on the form's input fields:
+ *  - input text fields will have their 'value' property set to the empty string
+ *  - select elements will have their 'selectedIndex' property set to -1
+ *  - checkbox and radio inputs will have their 'checked' property set to false
+ *  - inputs of type submit, button, reset, and hidden will *not* be effected
+ *  - button elements will *not* be effected
+ *
+ * @example $('form').clearForm();
+ * @desc Clears all forms on the page.
+ *
+ * @name clearForm
+ * @type jQuery
+ * @cat Plugins/Form
+ */
+$.fn.clearForm = function() {
+    return this.each(function() {
+        $('input,select,textarea', this).clearFields();
+    });
+};
+
+/**
+ * Clears the selected form elements.  Takes the following actions on the matched elements:
+ *  - input text fields will have their 'value' property set to the empty string
+ *  - select elements will have their 'selectedIndex' property set to -1
+ *  - checkbox and radio inputs will have their 'checked' property set to false
+ *  - inputs of type submit, button, reset, and hidden will *not* be effected
+ *  - button elements will *not* be effected
+ *
+ * @example $('.myInputs').clearFields();
+ * @desc Clears all inputs with class myInputs
+ *
+ * @name clearFields
+ * @type jQuery
+ * @cat Plugins/Form
+ */
+$.fn.clearFields = $.fn.clearInputs = function() {
+    return this.each(function() {
+        var t = this.type, tag = this.tagName.toLowerCase();
+        if (t == 'text' || t == 'password' || tag == 'textarea')
+            this.value = '';
+        else if (t == 'checkbox' || t == 'radio')
+            this.checked = false;
+        else if (tag == 'select')
+            this.selectedIndex = -1;
+    });
+};
+
+
+/**
+ * Resets the form data.  Causes all form elements to be reset to their original value.
+ *
+ * @example $('form').resetForm();
+ * @desc Resets all forms on the page.
+ *
+ * @name resetForm
+ * @type jQuery
+ * @cat Plugins/Form
+ */
+$.fn.resetForm = function() {
+    return this.each(function() {
+        // guard against an input with the name of 'reset'
+        // note that IE reports the reset function as an 'object'
+        if (typeof this.reset == 'function' || (typeof this.reset == 'object' && !this.reset.nodeType))
+            this.reset();
+    });
+};
+
+})(jQuery);

File web/javascript/hsbook.js

+function qid(id) {
+  return id.replace(/([.:])/g, "\\$1");
+}
+
+function beforeComment(formData, jqForm, options) {
+  var form = jqForm[0];
+  if (!form.comment.value) {
+    $(options.target + " span.comment_error").empty().append(
+      "<span class=\"comment_error\">Your comment is empty</span>");
+    return false;
+  }
+  if (!form.name.value) {
+    $(options.target + " span.comment_error").empty().append(
+      "<span class=\"comment_error\">Please provide a name</span>");
+    return false;
+  }
+  $(options.target + " span.comment_error").empty().after(
+    "<img src=\"figs/throbber.gif\" style=\"vertical-align: middle\"/>");
+  $(options.target + " input[@name=submit]").attr("disabled", true);
+}
+
+function ajaxifyForm(id) {
+  var q = qid(id);
+  
+  $("#form_" + q).ajaxForm({ beforeSubmit: beforeComment,
+			     success: function() { ajaxifyForm(id); },
+			     target: "#comments_" + q });
+}
+
+function toggleComment(id) {
+  $("#toggle_" + qid(id)).nextAll().toggle();
+  return false;
+}
+
+function loadComments(id) {
+  $("#comments_" + qid(id)).load(location.protocol + "//" + location.host +
+				 "/comments/single/" + id + "/", function() {
+    ajaxifyForm(id);
+  });
+  return false;
+}
+
+function loadAllComments() {
+  $("a.commenttoggle").each(function() {
+    var id = $(this).attr("pid");
+    if (id) {
+      loadComments(id);
+    }
+  });
+}
+
+$(document).ready(function() {
+  function loading(id) {
+    return " <span id=\"comments_" + id + "\" class=\"comment\">" +
+      "<span pid=\"" + id + "\" class=\"commenttoggle\">Loading..." +
+      "</span></span>";
+  }
+  $("div.toc>p")
+    .after("<p style='display: none;'><a onclick='return loadAllComments()'>" +
+	   "Load all comments (<b>slow</b>)</a></p>")
+    .toggle(function() { $(this).nextAll().show("normal"); },
+	    function() { $(this).nextAll().hide("normal"); })
+    .hover(function() { $(this).fadeTo("normal", 0.8); },
+	   function() { $(this).fadeTo("normal", 0.35); });
+  $("p[@id]").each(function() {
+    $(this).append(loading($(this).attr("id")));
+  });
+  $("pre[@id]").each(function() {
+    $(this).after(loading($(this).attr("id")));
+  });
+  var chapid = $("div.preface, div.chapter, div.appendix, div.bibliography").attr("id");
+  $("#chapterfeed").attr("href",
+			 $("#chapterfeed").attr("href") + chapid + "/");
+  $.getJSON(location.protocol + "//" + location.host + "/comments/chapter/" +
+	    chapid + "/count/", function(data) {
+    $.each(data, function(id, item) {
+      var s = item == 1 ? "" : "s";
+      $("#comments_" + qid(id) + " span.commenttoggle").replaceWith(
+        "<a class='commenttoggle' id='toggle_" + id + "' " +
+	"pid='" + id + "' " +
+	"onclick='return loadComments(\"" + id + "\")' " +
+	"href='comments: show / hide'>" + item + " comment" + s + "</a>");
+    });
+    $("span.commenttoggle").each(function() {
+      var id = $(this).attr("pid");
+      $(this).replaceWith("<a class='commenttoggle' id='toggle_" + id + "' " +
+			  "onclick='return loadComments(\"" + id + "\")' " +
+			  "href='comment: add'>No comments</a>");
+    });
+  });
+});

File web/javascript/jquery-min.js

+/*
+ * jQuery 1.2.1 - New Wave Javascript
+ *
+ * Copyright (c) 2007 John Resig (jquery.com)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * $Date: 2007-09-16 23:42:06 -0400 (Sun, 16 Sep 2007) $
+ * $Rev: 3353 $
+ */
+(function(){if(typeof jQuery!="undefined")var _jQuery=jQuery;var jQuery=window.jQuery=function(selector,context){return this instanceof jQuery?this.init(selector,context):new jQuery(selector,context);};if(typeof $!="undefined")var _$=$;window.$=jQuery;var quickExpr=/^[^<]*(<(.|\s)+>)[^>]*$|^#(\w+)$/;jQuery.fn=jQuery.prototype={init:function(selector,context){selector=selector||document;if(typeof selector=="string"){var m=quickExpr.exec(selector);if(m&&(m[1]||!context)){if(m[1])selector=jQuery.clean([m[1]],context);else{var tmp=document.getElementById(m[3]);if(tmp)if(tmp.id!=m[3])return jQuery().find(selector);else{this[0]=tmp;this.length=1;return this;}else
+selector=[];}}else
+return new jQuery(context).find(selector);}else if(jQuery.isFunction(selector))return new jQuery(document)[jQuery.fn.ready?"ready":"load"](selector);return this.setArray(selector.constructor==Array&&selector||(selector.jquery||selector.length&&selector!=window&&!selector.nodeType&&selector[0]!=undefined&&selector[0].nodeType)&&jQuery.makeArray(selector)||[selector]);},jquery:"1.2.1",size:function(){return this.length;},length:0,get:function(num){return num==undefined?jQuery.makeArray(this):this[num];},pushStack:function(a){var ret=jQuery(a);ret.prevObject=this;return ret;},setArray:function(a){this.length=0;Array.prototype.push.apply(this,a);return this;},each:function(fn,args){return jQuery.each(this,fn,args);},index:function(obj){var pos=-1;this.each(function(i){if(this==obj)pos=i;});return pos;},attr:function(key,value,type){var obj=key;if(key.constructor==String)if(value==undefined)return this.length&&jQuery[type||"attr"](this[0],key)||undefined;else{obj={};obj[key]=value;}return this.each(function(index){for(var prop in obj)jQuery.attr(type?this.style:this,prop,jQuery.prop(this,obj[prop],type,index,prop));});},css:function(key,value){return this.attr(key,value,"curCSS");},text:function(e){if(typeof e!="object"&&e!=null)return this.empty().append(document.createTextNode(e));var t="";jQuery.each(e||this,function(){jQuery.each(this.childNodes,function(){if(this.nodeType!=8)t+=this.nodeType!=1?this.nodeValue:jQuery.fn.text([this]);});});return t;},wrapAll:function(html){if(this[0])jQuery(html,this[0].ownerDocument).clone().insertBefore(this[0]).map(function(){var elem=this;while(elem.firstChild)elem=elem.firstChild;return elem;}).append(this);return this;},wrapInner:function(html){return this.each(function(){jQuery(this).contents().wrapAll(html);});},wrap:function(html){return this.each(function(){jQuery(this).wrapAll(html);});},append:function(){return this.domManip(arguments,true,1,function(a){this.appendChild(a);});},prepend:function(){return this.domManip(arguments,true,-1,function(a){this.insertBefore(a,this.firstChild);});},before:function(){return this.domManip(arguments,false,1,function(a){this.parentNode.insertBefore(a,this);});},after:function(){return this.domManip(arguments,false,-1,function(a){this.parentNode.insertBefore(a,this.nextSibling);});},end:function(){return this.prevObject||jQuery([]);},find:function(t){var data=jQuery.map(this,function(a){return jQuery.find(t,a);});return this.pushStack(/[^+>] [^+>]/.test(t)||t.indexOf("..")>-1?jQuery.unique(data):data);},clone:function(events){var ret=this.map(function(){return this.outerHTML?jQuery(this.outerHTML)[0]:this.cloneNode(true);});var clone=ret.find("*").andSelf().each(function(){if(this[expando]!=undefined)this[expando]=null;});if(events===true)this.find("*").andSelf().each(function(i){var events=jQuery.data(this,"events");for(var type in events)for(var handler in events[type])jQuery.event.add(clone[i],type,events[type][handler],events[type][handler].data);});return ret;},filter:function(t){return this.pushStack(jQuery.isFunction(t)&&jQuery.grep(this,function(el,index){return t.apply(el,[index]);})||jQuery.multiFilter(t,this));},not:function(t){return this.pushStack(t.constructor==String&&jQuery.multiFilter(t,this,true)||jQuery.grep(this,function(a){return(t.constructor==Array||t.jquery)?jQuery.inArray(a,t)<0:a!=t;}));},add:function(t){return this.pushStack(jQuery.merge(this.get(),t.constructor==String?jQuery(t).get():t.length!=undefined&&(!t.nodeName||jQuery.nodeName(t,"form"))?t:[t]));},is:function(expr){return expr?jQuery.multiFilter(expr,this).length>0:false;},hasClass:function(expr){return this.is("."+expr);},val:function(val){if(val==undefined){if(this.length){var elem=this[0];if(jQuery.nodeName(elem,"select")){var index=elem.selectedIndex,a=[],options=elem.options,one=elem.type=="select-one";if(index<0)return null;for(var i=one?index:0,max=one?index+1:options.length;i<max;i++){var option=options[i];if(option.selected){var val=jQuery.browser.msie&&!option.attributes["value"].specified?option.text:option.value;if(one)return val;a.push(val);}}return a;}else
+return this[0].value.replace(/\r/g,"");}}else
+return this.each(function(){if(val.constructor==Array&&/radio|checkbox/.test(this.type))this.checked=(jQuery.inArray(this.value,val)>=0||jQuery.inArray(this.name,val)>=0);else if(jQuery.nodeName(this,"select")){var tmp=val.constructor==Array?val:[val];jQuery("option",this).each(function(){this.selected=(jQuery.inArray(this.value,tmp)>=0||jQuery.inArray(this.text,tmp)>=0);});if(!tmp.length)this.selectedIndex=-1;}else
+this.value=val;});},html:function(val){return val==undefined?(this.length?this[0].innerHTML:null):this.empty().append(val);},replaceWith:function(val){return this.after(val).remove();},eq:function(i){return this.slice(i,i+1);},slice:function(){return this.pushStack(Array.prototype.slice.apply(this,arguments));},map:function(fn){return this.pushStack(jQuery.map(this,function(elem,i){return fn.call(elem,i,elem);}));},andSelf:function(){return this.add(this.prevObject);},domManip:function(args,table,dir,fn){var clone=this.length>1,a;return this.each(function(){if(!a){a=jQuery.clean(args,this.ownerDocument);if(dir<0)a.reverse();}var obj=this;if(table&&jQuery.nodeName(this,"table")&&jQuery.nodeName(a[0],"tr"))obj=this.getElementsByTagName("tbody")[0]||this.appendChild(document.createElement("tbody"));jQuery.each(a,function(){var elem=clone?this.cloneNode(true):this;if(!evalScript(0,elem))fn.call(obj,elem);});});}};function evalScript(i,elem){var script=jQuery.nodeName(elem,"script");if(script){if(elem.src)jQuery.ajax({url:elem.src,async:false,dataType:"script"});else
+jQuery.globalEval(elem.text||elem.textContent||elem.innerHTML||"");if(elem.parentNode)elem.parentNode.removeChild(elem);}else if(elem.nodeType==1)jQuery("script",elem).each(evalScript);return script;}jQuery.extend=jQuery.fn.extend=function(){var target=arguments[0]||{},a=1,al=arguments.length,deep=false;if(target.constructor==Boolean){deep=target;target=arguments[1]||{};}if(al==1){target=this;a=0;}var prop;for(;a<al;a++)if((prop=arguments[a])!=null)for(var i in prop){if(target==prop[i])continue;if(deep&&typeof prop[i]=='object'&&target[i])jQuery.extend(target[i],prop[i]);else if(prop[i]!=undefined)target[i]=prop[i];}return target;};var expando="jQuery"+(new Date()).getTime(),uuid=0,win={};jQuery.extend({noConflict:function(deep){window.$=_$;if(deep)window.jQuery=_jQuery;return jQuery;},isFunction:function(fn){return!!fn&&typeof fn!="string"&&!fn.nodeName&&fn.constructor!=Array&&/function/i.test(fn+"");},isXMLDoc:function(elem){return elem.documentElement&&!elem.body||elem.tagName&&elem.ownerDocument&&!elem.ownerDocument.body;},globalEval:function(data){data=jQuery.trim(data);if(data){if(window.execScript)window.execScript(data);else if(jQuery.browser.safari)window.setTimeout(data,0);else
+eval.call(window,data);}},nodeName:function(elem,name){return elem.nodeName&&elem.nodeName.toUpperCase()==name.toUpperCase();},cache:{},data:function(elem,name,data){elem=elem==window?win:elem;var id=elem[expando];if(!id)id=elem[expando]=++uuid;if(name&&!jQuery.cache[id])jQuery.cache[id]={};if(data!=undefined)jQuery.cache[id][name]=data;return name?jQuery.cache[id][name]:id;},removeData:function(elem,name){elem=elem==window?win:elem;var id=elem[expando];if(name){if(jQuery.cache[id]){delete jQuery.cache[id][name];name="";for(name in jQuery.cache[id])break;if(!name)jQuery.removeData(elem);}}else{try{delete elem[expando];}catch(e){if(elem.removeAttribute)elem.removeAttribute(expando);}delete jQuery.cache[id];}},each:function(obj,fn,args){if(args){if(obj.length==undefined)for(var i in obj)fn.apply(obj[i],args);else
+for(var i=0,ol=obj.length;i<ol;i++)if(fn.apply(obj[i],args)===false)break;}else{if(obj.length==undefined)for(var i in obj)fn.call(obj[i],i,obj[i]);else
+for(var i=0,ol=obj.length,val=obj[0];i<ol&&fn.call(val,i,val)!==false;val=obj[++i]){}}return obj;},prop:function(elem,value,type,index,prop){if(jQuery.isFunction(value))value=value.call(elem,[index]);var exclude=/z-?index|font-?weight|opacity|zoom|line-?height/i;return value&&value.constructor==Number&&type=="curCSS"&&!exclude.test(prop)?value+"px":value;},className:{add:function(elem,c){jQuery.each((c||"").split(/\s+/),function(i,cur){if(!jQuery.className.has(elem.className,cur))elem.className+=(elem.className?" ":"")+cur;});},remove:function(elem,c){elem.className=c!=undefined?jQuery.grep(elem.className.split(/\s+/),function(cur){return!jQuery.className.has(c,cur);}).join(" "):"";},has:function(t,c){return jQuery.inArray(c,(t.className||t).toString().split(/\s+/))>-1;}},swap:function(e,o,f){for(var i in o){e.style["old"+i]=e.style[i];e.style[i]=o[i];}f.apply(e,[]);for(var i in o)e.style[i]=e.style["old"+i];},css:function(e,p){if(p=="height"||p=="width"){var old={},oHeight,oWidth,d=["Top","Bottom","Right","Left"];jQuery.each(d,function(){old["padding"+this]=0;old["border"+this+"Width"]=0;});jQuery.swap(e,old,function(){if(jQuery(e).is(':visible')){oHeight=e.offsetHeight;oWidth=e.offsetWidth;}else{e=jQuery(e.cloneNode(true)).find(":radio").removeAttr("checked").end().css({visibility:"hidden",position:"absolute",display:"block",right:"0",left:"0"}).appendTo(e.parentNode)[0];var parPos=jQuery.css(e.parentNode,"position")||"static";if(parPos=="static")e.parentNode.style.position="relative";oHeight=e.clientHeight;oWidth=e.clientWidth;if(parPos=="static")e.parentNode.style.position="static";e.parentNode.removeChild(e);}});return p=="height"?oHeight:oWidth;}return jQuery.curCSS(e,p);},curCSS:function(elem,prop,force){var ret,stack=[],swap=[];function color(a){if(!jQuery.browser.safari)return false;var ret=document.defaultView.getComputedStyle(a,null);return!ret||ret.getPropertyValue("color")=="";}if(prop=="opacity"&&jQuery.browser.msie){ret=jQuery.attr(elem.style,"opacity");return ret==""?"1":ret;}if(prop.match(/float/i))prop=styleFloat;if(!force&&elem.style[prop])ret=elem.style[prop];else if(document.defaultView&&document.defaultView.getComputedStyle){if(prop.match(/float/i))prop="float";prop=prop.replace(/([A-Z])/g,"-$1").toLowerCase();var cur=document.defaultView.getComputedStyle(elem,null);if(cur&&!color(elem))ret=cur.getPropertyValue(prop);else{for(var a=elem;a&&color(a);a=a.parentNode)stack.unshift(a);for(a=0;a<stack.length;a++)if(color(stack[a])){swap[a]=stack[a].style.display;stack[a].style.display="block";}ret=prop=="display"&&swap[stack.length-1]!=null?"none":document.defaultView.getComputedStyle(elem,null).getPropertyValue(prop)||"";for(a=0;a<swap.length;a++)if(swap[a]!=null)stack[a].style.display=swap[a];}if(prop=="opacity"&&ret=="")ret="1";}else if(elem.currentStyle){var newProp=prop.replace(/\-(\w)/g,function(m,c){return c.toUpperCase();});ret=elem.currentStyle[prop]||elem.currentStyle[newProp];if(!/^\d+(px)?$/i.test(ret)&&/^\d/.test(ret)){var style=elem.style.left;var runtimeStyle=elem.runtimeStyle.left;elem.runtimeStyle.left=elem.currentStyle.left;elem.style.left=ret||0;ret=elem.style.pixelLeft+"px";elem.style.left=style;elem.runtimeStyle.left=runtimeStyle;}}return ret;},clean:function(a,doc){var r=[];doc=doc||document;jQuery.each(a,function(i,arg){if(!arg)return;if(arg.constructor==Number)arg=arg.toString();if(typeof arg=="string"){arg=arg.replace(/(<(\w+)[^>]*?)\/>/g,function(m,all,tag){return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area)$/i)?m:all+"></"+tag+">";});var s=jQuery.trim(arg).toLowerCase(),div=doc.createElement("div"),tb=[];var wrap=!s.indexOf("<opt")&&[1,"<select>","</select>"]||!s.indexOf("<leg")&&[1,"<fieldset>","</fieldset>"]||s.match(/^<(thead|tbody|tfoot|colg|cap)/)&&[1,"<table>","</table>"]||!s.indexOf("<tr")&&[2,"<table><tbody>","</tbody></table>"]||(!s.indexOf("<td")||!s.indexOf("<th"))&&[3,"<table><tbody><tr>","</tr></tbody></table>"]||!s.indexOf("<col")&&[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"]||jQuery.browser.msie&&[1,"div<div>","</div>"]||[0,"",""];div.innerHTML=wrap[1]+arg+wrap[2];while(wrap[0]--)div=div.lastChild;if(jQuery.browser.msie){if(!s.indexOf("<table")&&s.indexOf("<tbody")<0)tb=div.firstChild&&div.firstChild.childNodes;else if(wrap[1]=="<table>"&&s.indexOf("<tbody")<0)tb=div.childNodes;for(var n=tb.length-1;n>=0;--n)if(jQuery.nodeName(tb[n],"tbody")&&!tb[n].childNodes.length)tb[n].parentNode.removeChild(tb[n]);if(/^\s/.test(arg))div.insertBefore(doc.createTextNode(arg.match(/^\s*/)[0]),div.firstChild);}arg=jQuery.makeArray(div.childNodes);}if(0===arg.length&&(!jQuery.nodeName(arg,"form")&&!jQuery.nodeName(arg,"select")))return;if(arg[0]==undefined||jQuery.nodeName(arg,"form")||arg.options)r.push(arg);else
+r=jQuery.merge(r,arg);});return r;},attr:function(elem,name,value){var fix=jQuery.isXMLDoc(elem)?{}:jQuery.props;if(name=="selected"&&jQuery.browser.safari)elem.parentNode.selectedIndex;if(fix[name]){if(value!=undefined)elem[fix[name]]=value;return elem[fix[name]];}else if(jQuery.browser.msie&&name=="style")return jQuery.attr(elem.style,"cssText",value);else if(value==undefined&&jQuery.browser.msie&&jQuery.nodeName(elem,"form")&&(name=="action"||name=="method"))return elem.getAttributeNode(name).nodeValue;else if(elem.tagName){if(value!=undefined){if(name=="type"&&jQuery.nodeName(elem,"input")&&elem.parentNode)throw"type property can't be changed";elem.setAttribute(name,value);}if(jQuery.browser.msie&&/href|src/.test(name)&&!jQuery.isXMLDoc(elem))return elem.getAttribute(name,2);return elem.getAttribute(name);}else{if(name=="opacity"&&jQuery.browser.msie){if(value!=undefined){elem.zoom=1;elem.filter=(elem.filter||"").replace(/alpha\([^)]*\)/,"")+(parseFloat(value).toString()=="NaN"?"":"alpha(opacity="+value*100+")");}return elem.filter?(parseFloat(elem.filter.match(/opacity=([^)]*)/)[1])/100).toString():"";}name=name.replace(/-([a-z])/ig,function(z,b){return b.toUpperCase();});if(value!=undefined)elem[name]=value;return elem[name];}},trim:function(t){return(t||"").replace(/^\s+|\s+$/g,"");},makeArray:function(a){var r=[];if(typeof a!="array")for(var i=0,al=a.length;i<al;i++)r.push(a[i]);else
+r=a.slice(0);return r;},inArray:function(b,a){for(var i=0,al=a.length;i<al;i++)if(a[i]==b)return i;return-1;},merge:function(first,second){if(jQuery.browser.msie){for(var i=0;second[i];i++)if(second[i].nodeType!=8)first.push(second[i]);}else
+for(var i=0;second[i];i++)first.push(second[i]);return first;},unique:function(first){var r=[],done={};try{for(var i=0,fl=first.length;i<fl;i++){var id=jQuery.data(first[i]);if(!done[id]){done[id]=true;r.push(first[i]);}}}catch(e){r=first;}return r;},grep:function(elems,fn,inv){if(typeof fn=="string")fn=eval("false||function(a,i){return "+fn+"}");var result=[];for(var i=0,el=elems.length;i<el;i++)if(!inv&&fn(elems[i],i)||inv&&!fn(elems[i],i))result.push(elems[i]);return result;},map:function(elems,fn){if(typeof fn=="string")fn=eval("false||function(a){return "+fn+"}");var result=[];for(var i=0,el=elems.length;i<el;i++){var val=fn(elems[i],i);if(val!==null&&val!=undefined){if(val.constructor!=Array)val=[val];result=result.concat(val);}}return result;}});var userAgent=navigator.userAgent.toLowerCase();jQuery.browser={version:(userAgent.match(/.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/)||[])[1],safari:/webkit/.test(userAgent),opera:/opera/.test(userAgent),msie:/msie/.test(userAgent)&&!/opera/.test(userAgent),mozilla:/mozilla/.test(userAgent)&&!/(compatible|webkit)/.test(userAgent)};var styleFloat=jQuery.browser.msie?"styleFloat":"cssFloat";jQuery.extend({boxModel:!jQuery.browser.msie||document.compatMode=="CSS1Compat",styleFloat:jQuery.browser.msie?"styleFloat":"cssFloat",props:{"for":"htmlFor","class":"className","float":styleFloat,cssFloat:styleFloat,styleFloat:styleFloat,innerHTML:"innerHTML",className:"className",value:"value",disabled:"disabled",checked:"checked",readonly:"readOnly",selected:"selected",maxlength:"maxLength"}});jQuery.each({parent:"a.parentNode",parents:"jQuery.dir(a,'parentNode')",next:"jQuery.nth(a,2,'nextSibling')",prev:"jQuery.nth(a,2,'previousSibling')",nextAll:"jQuery.dir(a,'nextSibling')",prevAll:"jQuery.dir(a,'previousSibling')",siblings:"jQuery.sibling(a.parentNode.firstChild,a)",children:"jQuery.sibling(a.firstChild)",contents:"jQuery.nodeName(a,'iframe')?a.contentDocument||a.contentWindow.document:jQuery.makeArray(a.childNodes)"},function(i,n){jQuery.fn[i]=function(a){var ret=jQuery.map(this,n);if(a&&typeof a=="string")ret=jQuery.multiFilter(a,ret);return this.pushStack(jQuery.unique(ret));};});jQuery.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(i,n){jQuery.fn[i]=function(){var a=arguments;return this.each(function(){for(var j=0,al=a.length;j<al;j++)jQuery(a[j])[n](this);});};});jQuery.each({removeAttr:function(key){jQuery.attr(this,key,"");this.removeAttribute(key);},addClass:function(c){jQuery.className.add(this,c);},removeClass:function(c){jQuery.className.remove(this,c);},toggleClass:function(c){jQuery.className[jQuery.className.has(this,c)?"remove":"add"](this,c);},remove:function(a){if(!a||jQuery.filter(a,[this]).r.length){jQuery.removeData(this);this.parentNode.removeChild(this);}},empty:function(){jQuery("*",this).each(function(){jQuery.removeData(this);});while(this.firstChild)this.removeChild(this.firstChild);}},function(i,n){jQuery.fn[i]=function(){return this.each(n,arguments);};});jQuery.each(["Height","Width"],function(i,name){var n=name.toLowerCase();jQuery.fn[n]=function(h){return this[0]==window?jQuery.browser.safari&&self["inner"+name]||jQuery.boxModel&&Math.max(document.documentElement["client"+name],document.body["client"+name])||document.body["client"+name]:this[0]==document?Math.max(document.body["scroll"+name],document.body["offset"+name]):h==undefined?(this.length?jQuery.css(this[0],n):null):this.css(n,h.constructor==String?h:h+"px");};});var chars=jQuery.browser.safari&&parseInt(jQuery.browser.version)<417?"(?:[\\w*_-]|\\\\.)":"(?:[\\w\u0128-\uFFFF*_-]|\\\\.)",quickChild=new RegExp("^>\\s*("+chars+"+)"),quickID=new RegExp("^("+chars+"+)(#)("+chars+"+)"),quickClass=new RegExp("^([#.]?)("+chars+"*)");jQuery.extend({expr:{"":"m[2]=='*'||jQuery.nodeName(a,m[2])","#":"a.getAttribute('id')==m[2]",":":{lt:"i<m[3]-0",gt:"i>m[3]-0",nth:"m[3]-0==i",eq:"m[3]-0==i",first:"i==0",last:"i==r.length-1",even:"i%2==0",odd:"i%2","first-child":"a.parentNode.getElementsByTagName('*')[0]==a","last-child":"jQuery.nth(a.parentNode.lastChild,1,'previousSibling')==a","only-child":"!jQuery.nth(a.parentNode.lastChild,2,'previousSibling')",parent:"a.firstChild",empty:"!a.firstChild",contains:"(a.textContent||a.innerText||jQuery(a).text()||'').indexOf(m[3])>=0",visible:'"hidden"!=a.type&&jQuery.css(a,"display")!="none"&&jQuery.css(a,"visibility")!="hidden"',hidden:'"hidden"==a.type||jQuery.css(a,"display")=="none"||jQuery.css(a,"visibility")=="hidden"',enabled:"!a.disabled",disabled:"a.disabled",checked:"a.checked",selected:"a.selected||jQuery.attr(a,'selected')",text:"'text'==a.type",radio:"'radio'==a.type",checkbox:"'checkbox'==a.type",file:"'file'==a.type",password:"'password'==a.type",submit:"'submit'==a.type",image:"'image'==a.type",reset:"'reset'==a.type",button:'"button"==a.type||jQuery.nodeName(a,"button")',input:"/input|select|textarea|button/i.test(a.nodeName)",has:"jQuery.find(m[3],a).length",header:"/h\\d/i.test(a.nodeName)",animated:"jQuery.grep(jQuery.timers,function(fn){return a==fn.elem;}).length"}},parse:[/^(\[) *@?([\w-]+) *([!*$^~=]*) *('?"?)(.*?)\4 *\]/,/^(:)([\w-]+)\("?'?(.*?(\(.*?\))?[^(]*?)"?'?\)/,new RegExp("^([:.#]*)("+chars+"+)")],multiFilter:function(expr,elems,not){var old,cur=[];while(expr&&expr!=old){old=expr;var f=jQuery.filter(expr,elems,not);expr=f.t.replace(/^\s*,\s*/,"");cur=not?elems=f.r:jQuery.merge(cur,f.r);}return cur;},find:function(t,context){if(typeof t!="string")return[t];if(context&&!context.nodeType)context=null;context=context||document;var ret=[context],done=[],last;while(t&&last!=t){var r=[];last=t;t=jQuery.trim(t);var foundToken=false;var re=quickChild;var m=re.exec(t);if(m){var nodeName=m[1].toUpperCase();for(var i=0;ret[i];i++)for(var c=ret[i].firstChild;c;c=c.nextSibling)if(c.nodeType==1&&(nodeName=="*"||c.nodeName.toUpperCase()==nodeName.toUpperCase()))r.push(c);ret=r;t=t.replace(re,"");if(t.indexOf(" ")==0)continue;foundToken=true;}else{re=/^([>+~])\s*(\w*)/i;if((m=re.exec(t))!=null){r=[];var nodeName=m[2],merge={};m=m[1];for(var j=0,rl=ret.length;j<rl;j++){var n=m=="~"||m=="+"?ret[j].nextSibling:ret[j].firstChild;for(;n;n=n.nextSibling)if(n.nodeType==1){var id=jQuery.data(n);if(m=="~"&&merge[id])break;if(!nodeName||n.nodeName.toUpperCase()==nodeName.toUpperCase()){if(m=="~")merge[id]=true;r.push(n);}if(m=="+")break;}}ret=r;t=jQuery.trim(t.replace(re,""));foundToken=true;}}if(t&&!foundToken){if(!t.indexOf(",")){if(context==ret[0])ret.shift();done=jQuery.merge(done,ret);r=ret=[context];t=" "+t.substr(1,t.length);}else{var re2=quickID;var m=re2.exec(t);if(m){m=[0,m[2],m[3],m[1]];}else{re2=quickClass;m=re2.exec(t);}m[2]=m[2].replace(/\\/g,"");var elem=ret[ret.length-1];if(m[1]=="#"&&elem&&elem.getElementById&&!jQuery.isXMLDoc(elem)){var oid=elem.getElementById(m[2]);if((jQuery.browser.msie||jQuery.browser.opera)&&oid&&typeof oid.id=="string"&&oid.id!=m[2])oid=jQuery('[@id="'+m[2]+'"]',elem)[0];ret=r=oid&&(!m[3]||jQuery.nodeName(oid,m[3]))?[oid]:[];}else{for(var i=0;ret[i];i++){var tag=m[1]=="#"&&m[3]?m[3]:m[1]!=""||m[0]==""?"*":m[2];if(tag=="*"&&ret[i].nodeName.toLowerCase()=="object")tag="param";r=jQuery.merge(r,ret[i].getElementsByTagName(tag));}if(m[1]==".")r=jQuery.classFilter(r,m[2]);if(m[1]=="#"){var tmp=[];for(var i=0;r[i];i++)if(r[i].getAttribute("id")==m[2]){tmp=[r[i]];break;}r=tmp;}ret=r;}t=t.replace(re2,"");}}if(t){var val=jQuery.filter(t,r);ret=r=val.r;t=jQuery.trim(val.t);}}if(t)ret=[];if(ret&&context==ret[0])ret.shift();done=jQuery.merge(done,ret);return done;},classFilter:function(r,m,not){m=" "+m+" ";var tmp=[];for(var i=0;r[i];i++){var pass=(" "+r[i].className+" ").indexOf(m)>=0;if(!not&&pass||not&&!pass)tmp.push(r[i]);}return tmp;},filter:function(t,r,not){var last;while(t&&t!=last){last=t;var p=jQuery.parse,m;for(var i=0;p[i];i++){m=p[i].exec(t);if(m){t=t.substring(m[0].length);m[2]=m[2].replace(/\\/g,"");break;}}if(!m)break;if(m[1]==":"&&m[2]=="not")r=jQuery.filter(m[3],r,true).r;else if(m[1]==".")r=jQuery.classFilter(r,m[2],not);else if(m[1]=="["){var tmp=[],type=m[3];for(var i=0,rl=r.length;i<rl;i++){var a=r[i],z=a[jQuery.props[m[2]]||m[2]];if(z==null||/href|src|selected/.test(m[2]))z=jQuery.attr(a,m[2])||'';if((type==""&&!!z||type=="="&&z==m[5]||type=="!="&&z!=m[5]||type=="^="&&z&&!z.indexOf(m[5])||type=="$="&&z.substr(z.length-m[5].length)==m[5]||(type=="*="||type=="~=")&&z.indexOf(m[5])>=0)^not)tmp.push(a);}r=tmp;}else if(m[1]==":"&&m[2]=="nth-child"){var merge={},tmp=[],test=/(\d*)n\+?(\d*)/.exec(m[3]=="even"&&"2n"||m[3]=="odd"&&"2n+1"||!/\D/.test(m[3])&&"n+"+m[3]||m[3]),first=(test[1]||1)-0,last=test[2]-0;for(var i=0,rl=r.length;i<rl;i++){var node=r[i],parentNode=node.parentNode,id=jQuery.data(parentNode);if(!merge[id]){var c=1;for(var n=parentNode.firstChild;n;n=n.nextSibling)if(n.nodeType==1)n.nodeIndex=c++;merge[id]=true;}var add=false;if(first==1){if(last==0||node.nodeIndex==last)add=true;}else if((node.nodeIndex+last)%first==0)add=true;if(add^not)tmp.push(node);}r=tmp;}else{var f=jQuery.expr[m[1]];if(typeof f!="string")f=jQuery.expr[m[1]][m[2]];f=eval("false||function(a,i){return "+f+"}");r=jQuery.grep(r,f,not);}}return{r:r,t:t};},dir:function(elem,dir){var matched=[];var cur=elem[dir];while(cur&&cur!=document){if(cur.nodeType==1)matched.push(cur);cur=cur[dir];}return matched;},nth:function(cur,result,dir,elem){result=result||1;var num=0;for(;cur;cur=cur[dir])if(cur.nodeType==1&&++num==result)break;return cur;},sibling:function(n,elem){var r=[];for(;n;n=n.nextSibling){if(n.nodeType==1&&(!elem||n!=elem))r.push(n);}return r;}});jQuery.event={add:function(element,type,handler,data){if(jQuery.browser.msie&&element.setInterval!=undefined)element=window;if(!handler.guid)handler.guid=this.guid++;if(data!=undefined){var fn=handler;handler=function(){return fn.apply(this,arguments);};handler.data=data;handler.guid=fn.guid;}var parts=type.split(".");type=parts[0];handler.type=parts[1];var events=jQuery.data(element,"events")||jQuery.data(element,"events",{});var handle=jQuery.data(element,"handle",function(){var val;if(typeof jQuery=="undefined"||jQuery.event.triggered)return val;val=jQuery.event.handle.apply(element,arguments);return val;});var handlers=events[type];if(!handlers){handlers=events[type]={};if(element.addEventListener)element.addEventListener(type,handle,false);else
+element.attachEvent("on"+type,handle);}handlers[handler.guid]=handler;this.global[type]=true;},guid:1,global:{},remove:function(element,type,handler){var events=jQuery.data(element,"events"),ret,index;if(typeof type=="string"){var parts=type.split(".");type=parts[0];}if(events){if(type&&type.type){handler=type.handler;type=type.type;}if(!type){for(type in events)this.remove(element,type);}else if(events[type]){if(handler)delete events[type][handler.guid];else
+for(handler in events[type])if(!parts[1]||events[type][handler].type==parts[1])delete events[type][handler];for(ret in events[type])break;if(!ret){if(element.removeEventListener)element.removeEventListener(type,jQuery.data(element,"handle"),false);else
+element.detachEvent("on"+type,jQuery.data(element,"handle"));ret=null;delete events[type];}}for(ret in events)break;if(!ret){jQuery.removeData(element,"events");jQuery.removeData(element,"handle");}}},trigger:function(type,data,element,donative,extra){data=jQuery.makeArray(data||[]);if(!element){if(this.global[type])jQuery("*").add([window,document]).trigger(type,data);}else{var val,ret,fn=jQuery.isFunction(element[type]||null),evt=!data[0]||!data[0].preventDefault;if(evt)data.unshift(this.fix({type:type,target:element}));data[0].type=type;if(jQuery.isFunction(jQuery.data(element,"handle")))val=jQuery.data(element,"handle").apply(element,data);if(!fn&&element["on"+type]&&element["on"+type].apply(element,data)===false)val=false;if(evt)data.shift();if(extra&&extra.apply(element,data)===false)val=false;if(fn&&donative!==false&&val!==false&&!(jQuery.nodeName(element,'a')&&type=="click")){this.triggered=true;element[type]();}this.triggered=false;}return val;},handle:function(event){var val;event=jQuery.event.fix(event||window.event||{});var parts=event.type.split(".");event.type=parts[0];var c=jQuery.data(this,"events")&&jQuery.data(this,"events")[event.type],args=Array.prototype.slice.call(arguments,1);args.unshift(event);for(var j in c){args[0].handler=c[j];args[0].data=c[j].data;if(!parts[1]||c[j].type==parts[1]){var tmp=c[j].apply(this,args);if(val!==false)val=tmp;if(tmp===false){event.preventDefault();event.stopPropagation();}}}if(jQuery.browser.msie)event.target=event.preventDefault=event.stopPropagation=event.handler=event.data=null;return val;},fix:function(event){var originalEvent=event;event=jQuery.extend({},originalEvent);event.preventDefault=function(){if(originalEvent.preventDefault)originalEvent.preventDefault();originalEvent.returnValue=false;};event.stopPropagation=function(){if(originalEvent.stopPropagation)originalEvent.stopPropagation();originalEvent.cancelBubble=true;};if(!event.target&&event.srcElement)event.target=event.srcElement;if(jQuery.browser.safari&&event.target.nodeType==3)event.target=originalEvent.target.parentNode;if(!event.relatedTarget&&event.fromElement)event.relatedTarget=event.fromElement==event.target?event.toElement:event.fromElement;if(event.pageX==null&&event.clientX!=null){var e=document.documentElement,b=document.body;event.pageX=event.clientX+(e&&e.scrollLeft||b.scrollLeft||0);event.pageY=event.clientY+(e&&e.scrollTop||b.scrollTop||0);}if(!event.which&&(event.charCode||event.keyCode))event.which=event.charCode||event.keyCode;if(!event.metaKey&&event.ctrlKey)event.metaKey=event.ctrlKey;if(!event.which&&event.button)event.which=(event.button&1?1:(event.button&2?3:(event.button&4?2:0)));return event;}};jQuery.fn.extend({bind:function(type,data,fn){return type=="unload"?this.one(type,data,fn):this.each(function(){jQuery.event.add(this,type,fn||data,fn&&data);});},one:function(type,data,fn){return this.each(function(){jQuery.event.add(this,type,function(event){jQuery(this).unbind(event);return(fn||data).apply(this,arguments);},fn&&data);});},unbind:function(type,fn){return this.each(function(){jQuery.event.remove(this,type,fn);});},trigger:function(type,data,fn){return this.each(function(){jQuery.event.trigger(type,data,this,true,fn);});},triggerHandler:function(type,data,fn){if(this[0])return jQuery.event.trigger(type,data,this[0],false,fn);},toggle:function(){var a=arguments;return this.click(function(e){this.lastToggle=0==this.lastToggle?1:0;e.preventDefault();return a[this.lastToggle].apply(this,[e])||false;});},hover:function(f,g){function handleHover(e){var p=e.relatedTarget;while(p&&p!=this)try{p=p.parentNode;}catch(e){p=this;};if(p==this)return false;return(e.type=="mouseover"?f:g).apply(this,[e]);}return this.mouseover(handleHover).mouseout(handleHover);},ready:function(f){bindReady();if(jQuery.isReady)f.apply(document,[jQuery]);else
+jQuery.readyList.push(function(){return f.apply(this,[jQuery]);});return this;}});jQuery.extend({isReady:false,readyList:[],ready:function(){if(!jQuery.isReady){jQuery.isReady=true;if(jQuery.readyList){jQuery.each(jQuery.readyList,function(){this.apply(document);});jQuery.readyList=null;}if(jQuery.browser.mozilla||jQuery.browser.opera)document.removeEventListener("DOMContentLoaded",jQuery.ready,false);if(!window.frames.length)jQuery(window).load(function(){jQuery("#__ie_init").remove();});}}});jQuery.each(("blur,focus,load,resize,scroll,unload,click,dblclick,"+"mousedown,mouseup,mousemove,mouseover,mouseout,change,select,"+"submit,keydown,keypress,keyup,error").split(","),function(i,o){jQuery.fn[o]=function(f){return f?this.bind(o,f):this.trigger(o);};});var readyBound=false;function bindReady(){if(readyBound)return;readyBound=true;if(jQuery.browser.mozilla||jQuery.browser.opera)document.addEventListener("DOMContentLoaded",jQuery.ready,false);else if(jQuery.browser.msie){document.write("<scr"+"ipt id=__ie_init defer=true "+"src=//:><\/script>");var script=document.getElementById("__ie_init");if(script)script.onreadystatechange=function(){if(this.readyState!="complete")return;jQuery.ready();};script=null;}else if(jQuery.browser.safari)jQuery.safariTimer=setInterval(function(){if(document.readyState=="loaded"||document.readyState=="complete"){clearInterval(jQuery.safariTimer);jQuery.safariTimer=null;jQuery.ready();}},10);jQuery.event.add(window,"load",jQuery.ready);}jQuery.fn.extend({load:function(url,params,callback){if(jQuery.isFunction(url))return this.bind("load",url);var off=url.indexOf(" ");if(off>=0){var selector=url.slice(off,url.length);url=url.slice(0,off);}callback=callback||function(){};var type="GET";if(params)if(jQuery.isFunction(params)){callback=params;params=null;}else{params=jQuery.param(params);type="POST";}var self=this;jQuery.ajax({url:url,type:type,data:params,complete:function(res,status){if(status=="success"||status=="notmodified")self.html(selector?jQuery("<div/>").append(res.responseText.replace(/<script(.|\s)*?\/script>/g,"")).find(selector):res.responseText);setTimeout(function(){self.each(callback,[res.responseText,status,res]);},13);}});return this;},serialize:function(){return jQuery.param(this.serializeArray());},serializeArray:function(){return this.map(function(){return jQuery.nodeName(this,"form")?jQuery.makeArray(this.elements):this;}).filter(function(){return this.name&&!this.disabled&&(this.checked||/select|textarea/i.test(this.nodeName)||/text|hidden|password/i.test(this.type));}).map(function(i,elem){var val=jQuery(this).val();return val==null?null:val.constructor==Array?jQuery.map(val,function(val,i){return{name:elem.name,value:val};}):{name:elem.name,value:val};}).get();}});jQuery.each("ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","),function(i,o){jQuery.fn[o]=function(f){return this.bind(o,f);};});var jsc=(new Date).getTime();jQuery.extend({get:function(url,data,callback,type){if(jQuery.isFunction(data)){callback=data;data=null;}return jQuery.ajax({type:"GET",url:url,data:data,success:callback,dataType:type});},getScript:function(url,callback){return jQuery.get(url,null,callback,"script");},getJSON:function(url,data,callback){return jQuery.get(url,data,callback,"json");},post:function(url,data,callback,type){if(jQuery.isFunction(data)){callback=data;data={};}return jQuery.ajax({type:"POST",url:url,data:data,success:callback,dataType:type});},ajaxSetup:function(settings){jQuery.extend(jQuery.ajaxSettings,settings);},ajaxSettings:{global:true,type:"GET",timeout:0,contentType:"application/x-www-form-urlencoded",processData:true,async:true,data:null},lastModified:{},ajax:function(s){var jsonp,jsre=/=(\?|%3F)/g,status,data;s=jQuery.extend(true,s,jQuery.extend(true,{},jQuery.ajaxSettings,s));if(s.data&&s.processData&&typeof s.data!="string")s.data=jQuery.param(s.data);if(s.dataType=="jsonp"){if(s.type.toLowerCase()=="get"){if(!s.url.match(jsre))s.url+=(s.url.match(/\?/)?"&":"?")+(s.jsonp||"callback")+"=?";}else if(!s.data||!s.data.match(jsre))s.data=(s.data?s.data+"&":"")+(s.jsonp||"callback")+"=?";s.dataType="json";}if(s.dataType=="json"&&(s.data&&s.data.match(jsre)||s.url.match(jsre))){jsonp="jsonp"+jsc++;if(s.data)s.data=s.data.replace(jsre,"="+jsonp);s.url=s.url.replace(jsre,"="+jsonp);s.dataType="script";window[jsonp]=function(tmp){data=tmp;success();complete();window[jsonp]=undefined;try{delete window[jsonp];}catch(e){}};}if(s.dataType=="script"&&s.cache==null)s.cache=false;if(s.cache===false&&s.type.toLowerCase()=="get")s.url+=(s.url.match(/\?/)?"&":"?")+"_="+(new Date()).getTime();if(s.data&&s.type.toLowerCase()=="get"){s.url+=(s.url.match(/\?/)?"&":"?")+s.data;s.data=null;}if(s.global&&!jQuery.active++)jQuery.event.trigger("ajaxStart");if(!s.url.indexOf("http")&&s.dataType=="script"){var head=document.getElementsByTagName("head")[0];var script=document.createElement("script");script.src=s.url;if(!jsonp&&(s.success||s.complete)){var done=false;script.onload=script.onreadystatechange=function(){if(!done&&(!this.readyState||this.readyState=="loaded"||this.readyState=="complete")){done=true;success();complete();head.removeChild(script);}};}head.appendChild(script);return;}var requestDone=false;var xml=window.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest();xml.open(s.type,s.url,s.async);if(s.data)xml.setRequestHeader("Content-Type",s.contentType);if(s.ifModified)xml.setRequestHeader("If-Modified-Since",jQuery.lastModified[s.url]||"Thu, 01 Jan 1970 00:00:00 GMT");xml.setRequestHeader("X-Requested-With","XMLHttpRequest");if(s.beforeSend)s.beforeSend(xml);if(s.global)jQuery.event.trigger("ajaxSend",[xml,s]);var onreadystatechange=function(isTimeout){if(!requestDone&&xml&&(xml.readyState==4||isTimeout=="timeout")){requestDone=true;if(ival){clearInterval(ival);ival=null;}status=isTimeout=="timeout"&&"timeout"||!jQuery.httpSuccess(xml)&&"error"||s.ifModified&&jQuery.httpNotModified(xml,s.url)&&"notmodified"||"success";if(status=="success"){try{data=jQuery.httpData(xml,s.dataType);}catch(e){status="parsererror";}}if(status=="success"){var modRes;try{modRes=xml.getResponseHeader("Last-Modified");}catch(e){}if(s.ifModified&&modRes)jQuery.lastModified[s.url]=modRes;if(!jsonp)success();}else
+jQuery.handleError(s,xml,status);complete();if(s.async)xml=null;}};if(s.async){var ival=setInterval(onreadystatechange,13);if(s.timeout>0)setTimeout(function(){if(xml){xml.abort();if(!requestDone)onreadystatechange("timeout");}},s.timeout);}try{xml.send(s.data);}catch(e){jQuery.handleError(s,xml,null,e);}if(!s.async)onreadystatechange();return xml;function success(){if(s.success)s.success(data,status);if(s.global)jQuery.event.trigger("ajaxSuccess",[xml,s]);}function complete(){if(s.complete)s.complete(xml,status);if(s.global)jQuery.event.trigger("ajaxComplete",[xml,s]);if(s.global&&!--jQuery.active)jQuery.event.trigger("ajaxStop");}},handleError:function(s,xml,status,e){if(s.error)s.error(xml,status,e);if(s.global)jQuery.event.trigger("ajaxError",[xml,s,e]);},active:0,httpSuccess:function(r){try{return!r.status&&location.protocol=="file:"||(r.status>=200&&r.status<300)||r.status==304||jQuery.browser.safari&&r.status==undefined;}catch(e){}return false;},httpNotModified:function(xml,url){try{var xmlRes=xml.getResponseHeader("Last-Modified");return xml.status==304||xmlRes==jQuery.lastModified[url]||jQuery.browser.safari&&xml.status==undefined;}catch(e){}return false;},httpData:function(r,type){var ct=r.getResponseHeader("content-type");var xml=type=="xml"||!type&&ct&&ct.indexOf("xml")>=0;var data=xml?r.responseXML:r.responseText;if(xml&&data.documentElement.tagName=="parsererror")throw"parsererror";if(type=="script")jQuery.globalEval(data);if(type=="json")data=eval("("+data+")");return data;},param:function(a){var s=[];if(a.constructor==Array||a.jquery)jQuery.each(a,function(){s.push(encodeURIComponent(this.name)+"="+encodeURIComponent(this.value));});else
+for(var j in a)if(a[j]&&a[j].constructor==Array)jQuery.each(a[j],function(){s.push(encodeURIComponent(j)+"="+encodeURIComponent(this));});else
+s.push(encodeURIComponent(j)+"="+encodeURIComponent(a[j]));return s.join("&").replace(/%20/g,"+");}});jQuery.fn.extend({show:function(speed,callback){return speed?this.animate({height:"show",width:"show",opacity:"show"},speed,callback):this.filter(":hidden").each(function(){this.style.display=this.oldblock?this.oldblock:"";if(jQuery.css(this,"display")=="none")this.style.display="block";}).end();},hide:function(speed,callback){return speed?this.animate({height:"hide",width:"hide",opacity:"hide"},speed,callback):this.filter(":visible").each(function(){this.oldblock=this.oldblock||jQuery.css(this,"display");if(this.oldblock=="none")this.oldblock="block";this.style.display="none";}).end();},_toggle:jQuery.fn.toggle,toggle:function(fn,fn2){return jQuery.isFunction(fn)&&jQuery.isFunction(fn2)?this._toggle(fn,fn2):fn?this.animate({height:"toggle",width:"toggle",opacity:"toggle"},fn,fn2):this.each(function(){jQuery(this)[jQuery(this).is(":hidden")?"show":"hide"]();});},slideDown:function(speed,callback){return this.animate({height:"show"},speed,callback);},slideUp:function(speed,callback){return this.animate({height:"hide"},speed,callback);},slideToggle:function(speed,callback){return this.animate({height:"toggle"},speed,callback);},fadeIn:function(speed,callback){return this.animate({opacity:"show"},speed,callback);},fadeOut:function(speed,callback){return this.animate({opacity:"hide"},speed,callback);},fadeTo:function(speed,to,callback){return this.animate({opacity:to},speed,callback);},animate:function(prop,speed,easing,callback){var opt=jQuery.speed(speed,easing,callback);return this[opt.queue===false?"each":"queue"](function(){opt=jQuery.extend({},opt);var hidden=jQuery(this).is(":hidden"),self=this;for(var p in prop){if(prop[p]=="hide"&&hidden||prop[p]=="show"&&!hidden)return jQuery.isFunction(opt.complete)&&opt.complete.apply(this);if(p=="height"||p=="width"){opt.display=jQuery.css(this,"display");opt.overflow=this.style.overflow;}}if(opt.overflow!=null)this.style.overflow="hidden";opt.curAnim=jQuery.extend({},prop);jQuery.each(prop,function(name,val){var e=new jQuery.fx(self,opt,name);if(/toggle|show|hide/.test(val))e[val=="toggle"?hidden?"show":"hide":val](prop);else{var parts=val.toString().match(/^([+-]=)?([\d+-.]+)(.*)$/),start=e.cur(true)||0;if(parts){var end=parseFloat(parts[2]),unit=parts[3]||"px";if(unit!="px"){self.style[name]=(end||1)+unit;start=((end||1)/e.cur(true))*start;self.style[name]=start+unit;}if(parts[1])end=((parts[1]=="-="?-1:1)*end)+start;e.custom(start,end,unit);}else
+e.custom(start,val,"");}});return true;});},queue:function(type,fn){if(jQuery.isFunction(type)){fn=type;type="fx";}if(!type||(typeof type=="string"&&!fn))return queue(this[0],type);return this.each(function(){if(fn.constructor==Array)queue(this,type,fn);else{queue(this,type).push(fn);if(queue(this,type).length==1)fn.apply(this);}});},stop:function(){var timers=jQuery.timers;return this.each(function(){for(var i=0;i<timers.length;i++)if(timers[i].elem==this)timers.splice(i--,1);}).dequeue();}});var queue=function(elem,type,array){if(!elem)return;var q=jQuery.data(elem,type+"queue");if(!q||array)q=jQuery.data(elem,type+"queue",array?jQuery.makeArray(array):[]);return q;};jQuery.fn.dequeue=function(type){type=type||"fx";return this.each(function(){var q=queue(this,type);q.shift();if(q.length)q[0].apply(this);});};jQuery.extend({speed:function(speed,easing,fn){var opt=speed&&speed.constructor==Object?speed:{complete:fn||!fn&&easing||jQuery.isFunction(speed)&&speed,duration:speed,easing:fn&&easing||easing&&easing.constructor!=Function&&easing};opt.duration=(opt.duration&&opt.duration.constructor==Number?opt.duration:{slow:600,fast:200}[opt.duration])||400;opt.old=opt.complete;opt.complete=function(){jQuery(this).dequeue();if(jQuery.isFunction(opt.old))opt.old.apply(this);};return opt;},easing:{linear:function(p,n,firstNum,diff){return firstNum+diff*p;},swing:function(p,n,firstNum,diff){return((-Math.cos(p*Math.PI)/2)+0.5)*diff+firstNum;}},timers:[],fx:function(elem,options,prop){this.options=options;this.elem=elem;this.prop=prop;if(!options.orig)options.orig={};}});jQuery.fx.prototype={update:function(){if(this.options.step)this.options.step.apply(this.elem,[this.now,this]);(jQuery.fx.step[this.prop]||jQuery.fx.step._default)(this);if(this.prop=="height"||this.prop=="width")this.elem.style.display="block";},cur:function(force){if(this.elem[this.prop]!=null&&this.elem.style[this.prop]==null)return this.elem[this.prop];var r=parseFloat(jQuery.curCSS(this.elem,this.prop,force));return r&&r>-10000?r:parseFloat(jQuery.css(this.elem,this.prop))||0;},custom:function(from,to,unit){this.startTime=(new Date()).getTime();this.start=from;this.end=to;this.unit=unit||this.unit||"px";this.now=this.start;this.pos=this.state=0;this.update();var self=this;function t(){return self.step();}t.elem=this.elem;jQuery.timers.push(t);if(jQuery.timers.length==1){var timer=setInterval(function(){var timers=jQuery.timers;for(var i=0;i<timers.length;i++)if(!timers[i]())timers.splice(i--,1);if(!timers.length)clearInterval(timer);},13);}},show:function(){this.options.orig[this.prop]=jQuery.attr(this.elem.style,this.prop);this.options.show=true;this.custom(0,this.cur());if(this.prop=="width"||this.prop=="height")this.elem.style[this.prop]="1px";jQuery(this.elem).show();},hide:function(){this.options.orig[this.prop]=jQuery.attr(this.elem.style,this.prop);this.options.hide=true;this.custom(this.cur(),0);},step:function(){var t=(new Date()).getTime();if(t>this.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;var done=true;for(var i in this.options.curAnim)if(this.options.curAnim[i]!==true)done=false;if(done){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;this.elem.style.display=this.options.display;if(jQuery.css(this.elem,"display")=="none")this.elem.style.display="block";}if(this.options.hide)this.elem.style.display="none";if(this.options.hide||this.options.show)for(var p in this.options.curAnim)jQuery.attr(this.elem.style,p,this.options.orig[p]);}if(done&&jQuery.isFunction(this.options.complete))this.options.complete.apply(this.elem);return false;}else{var n=t-this.startTime;this.state=n/this.options.duration;this.pos=jQuery.easing[this.options.easing||(jQuery.easing.swing?"swing":"linear")](this.state,n,0,1,this.options.duration);this.now=this.start+((this.end-this.start)*this.pos);this.update();}return true;}};jQuery.fx.step={scrollLeft:function(fx){fx.elem.scrollLeft=fx.now;},scrollTop:function(fx){fx.elem.scrollTop=fx.now;},opacity:function(fx){jQuery.attr(fx.elem.style,"opacity",fx.now);},_default:function(fx){fx.elem.style[fx.prop]=fx.now+fx.unit;}};jQuery.fn.offset=function(){var left=0,top=0,elem=this[0],results;if(elem)with(jQuery.browser){var absolute=jQuery.css(elem,"position")=="absolute",parent=elem.parentNode,offsetParent=elem.offsetParent,doc=elem.ownerDocument,safari2=safari&&parseInt(version)<522;if(elem.getBoundingClientRect){box=elem.getBoundingClientRect();add(box.left+Math.max(doc.documentElement.scrollLeft,doc.body.scrollLeft),box.top+Math.max(doc.documentElement.scrollTop,doc.body.scrollTop));if(msie){var border=jQuery("html").css("borderWidth");border=(border=="medium"||jQuery.boxModel&&parseInt(version)>=7)&&2||border;add(-border,-border);}}else{add(elem.offsetLeft,elem.offsetTop);while(offsetParent){add(offsetParent.offsetLeft,offsetParent.offsetTop);if(mozilla&&/^t[d|h]$/i.test(parent.tagName)||!safari2)border(offsetParent);if(safari2&&!absolute&&jQuery.css(offsetParent,"position")=="absolute")absolute=true;offsetParent=offsetParent.offsetParent;}while(parent.tagName&&!/^body|html$/i.test(parent.tagName)){if(!/^inline|table-row.*$/i.test(jQuery.css(parent,"display")))add(-parent.scrollLeft,-parent.scrollTop);if(mozilla&&jQuery.css(parent,"overflow")!="visible")border(parent);parent=parent.parentNode;}if(safari2&&absolute)add(-doc.body.offsetLeft,-doc.body.offsetTop);}results={top:top,left:left};}return results;function border(elem){add(jQuery.css(elem,"borderLeftWidth"),jQuery.css(elem,"borderTopWidth"));}function add(l,t){left+=parseInt(l)||0;top+=parseInt(t)||0;}};})();

File web/javascript/jquery.js

+(function(){
+/*
+ * jQuery 1.2.1 - New Wave Javascript
+ *
+ * Copyright (c) 2007 John Resig (jquery.com)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * $Date: 2007-09-16 23:42:06 -0400 (Sun, 16 Sep 2007) $
+ * $Rev: 3353 $
+ */
+
+// Map over jQuery in case of overwrite
+if ( typeof jQuery != "undefined" )
+	var _jQuery = jQuery;
+
+var jQuery = window.jQuery = function(selector, context) {
+	// If the context is a namespace object, return a new object
+	return this instanceof jQuery ?
+		this.init(selector, context) :
+		new jQuery(selector, context);
+};
+
+// Map over the $ in case of overwrite
+if ( typeof $ != "undefined" )
+	var _$ = $;
+	
+// Map the jQuery namespace to the '$' one
+window.$ = jQuery;
+
+var quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#(\w+)$/;
+
+jQuery.fn = jQuery.prototype = {
+	init: function(selector, context) {
+		// Make sure that a selection was provided
+		selector = selector || document;
+
+		// Handle HTML strings
+		if ( typeof selector  == "string" ) {
+			var m = quickExpr.exec(selector);
+			if ( m && (m[1] || !context) ) {
+				// HANDLE: $(html) -> $(array)
+				if ( m[1] )
+					selector = jQuery.clean( [ m[1] ], context );
+
+				// HANDLE: $("#id")
+				else {
+					var tmp = document.getElementById( m[3] );
+					if ( tmp )
+						// Handle the case where IE and Opera return items
+						// by name instead of ID
+						if ( tmp.id != m[3] )
+							return jQuery().find( selector );
+						else {
+							this[0] = tmp;
+							this.length = 1;
+							return this;
+						}
+					else
+						selector = [];
+				}
+
+			// HANDLE: $(expr)
+			} else
+				return new jQuery( context ).find( selector );
+
+		// HANDLE: $(function)
+		// Shortcut for document ready
+		} else if ( jQuery.isFunction(selector) )
+			return new jQuery(document)[ jQuery.fn.ready ? "ready" : "load" ]( selector );
+
+		return this.setArray(
+			// HANDLE: $(array)
+			selector.constructor == Array && selector ||
+
+			// HANDLE: $(arraylike)
+			// Watch for when an array-like object is passed as the selector
+			(selector.jquery || selector.length && selector != window && !selector.nodeType && selector[0] != undefined && selector[0].nodeType) && jQuery.makeArray( selector ) ||
+
+			// HANDLE: $(*)
+			[ selector ] );
+	},
+	
+	jquery: "1.2.1",
+
+	size: function() {
+		return this.length;
+	},
+	
+	length: 0,
+
+	get: function( num ) {
+		return num == undefined ?
+
+			// Return a 'clean' array
+			jQuery.makeArray( this ) :
+
+			// Return just the object
+			this[num];
+	},
+	
+	pushStack: function( a ) {
+		var ret = jQuery(a);
+		ret.prevObject = this;
+		return ret;
+	},
+	
+	setArray: function( a ) {
+		this.length = 0;
+		Array.prototype.push.apply( this, a );
+		return this;
+	},
+
+	each: function( fn, args ) {
+		return jQuery.each( this, fn, args );
+	},
+
+	index: function( obj ) {
+		var pos = -1;
+		this.each(function(i){
+			if ( this == obj ) pos = i;
+		});
+		return pos;
+	},
+
+	attr: function( key, value, type ) {
+		var obj = key;
+		
+		// Look for the case where we're accessing a style value
+		if ( key.constructor == String )
+			if ( value == undefined )
+				return this.length && jQuery[ type || "attr" ]( this[0], key ) || undefined;
+			else {
+				obj = {};
+				obj[ key ] = value;
+			}
+		
+		// Check to see if we're setting style values
+		return this.each(function(index){
+			// Set all the styles
+			for ( var prop in obj )
+				jQuery.attr(
+					type ? this.style : this,
+					prop, jQuery.prop(this, obj[prop], type, index, prop)
+				);