Code

Add new tracker template.
authorstefan <stefan@57a73879-2fb5-44c3-a270-3262357dd7e2>
Tue, 12 Oct 2010 01:14:07 +0000 (01:14 +0000)
committerstefan <stefan@57a73879-2fb5-44c3-a270-3262357dd7e2>
Tue, 12 Oct 2010 01:14:07 +0000 (01:14 +0000)
git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/roundup/trunk@4543 57a73879-2fb5-44c3-a270-3262357dd7e2

62 files changed:
share/roundup/templates/devel/TEMPLATE-INFO.txt [new file with mode: 0644]
share/roundup/templates/devel/config.ini [new file with mode: 0644]
share/roundup/templates/devel/detectors/messagesummary.py [new file with mode: 0644]
share/roundup/templates/devel/detectors/no_texthtml.py [new file with mode: 0644]
share/roundup/templates/devel/detectors/nosyreaction.py [new file with mode: 0644]
share/roundup/templates/devel/detectors/patches.py [new file with mode: 0644]
share/roundup/templates/devel/detectors/severityauditor.py [new file with mode: 0644]
share/roundup/templates/devel/detectors/statusauditor.py [new file with mode: 0644]
share/roundup/templates/devel/detectors/userauditor.py [new file with mode: 0644]
share/roundup/templates/devel/extensions/local_replace.py [new file with mode: 0644]
share/roundup/templates/devel/extensions/spambayes.py [new file with mode: 0644]
share/roundup/templates/devel/extensions/timestamp.py [new file with mode: 0644]
share/roundup/templates/devel/extensions/timezone.py [new file with mode: 0644]
share/roundup/templates/devel/html/_generic.404.html [new file with mode: 0644]
share/roundup/templates/devel/html/_generic.calendar.html [new file with mode: 0644]
share/roundup/templates/devel/html/_generic.collision.html [new file with mode: 0644]
share/roundup/templates/devel/html/_generic.help-empty.html [new file with mode: 0644]
share/roundup/templates/devel/html/_generic.help-list.html [new file with mode: 0644]
share/roundup/templates/devel/html/_generic.help-search.html [new file with mode: 0644]
share/roundup/templates/devel/html/_generic.help-submit.html [new file with mode: 0644]
share/roundup/templates/devel/html/_generic.help.html [new file with mode: 0644]
share/roundup/templates/devel/html/_generic.index.html [new file with mode: 0644]
share/roundup/templates/devel/html/_generic.item.html [new file with mode: 0644]
share/roundup/templates/devel/html/bug.index.html [new file with mode: 0644]
share/roundup/templates/devel/html/bug.item.html [new file with mode: 0644]
share/roundup/templates/devel/html/bug.search.html [new file with mode: 0644]
share/roundup/templates/devel/html/favicon.ico [new file with mode: 0644]
share/roundup/templates/devel/html/file.index.html [new file with mode: 0644]
share/roundup/templates/devel/html/file.item.html [new file with mode: 0644]
share/roundup/templates/devel/html/help.css [new file with mode: 0644]
share/roundup/templates/devel/html/help.html [new file with mode: 0644]
share/roundup/templates/devel/html/help_controls.js [new file with mode: 0644]
share/roundup/templates/devel/html/home.classlist.html [new file with mode: 0644]
share/roundup/templates/devel/html/home.html [new file with mode: 0644]
share/roundup/templates/devel/html/jquery.js [new file with mode: 0644]
share/roundup/templates/devel/html/keyword.index.html [new file with mode: 0644]
share/roundup/templates/devel/html/keyword.item.html [new file with mode: 0644]
share/roundup/templates/devel/html/milestone.index.html [new file with mode: 0644]
share/roundup/templates/devel/html/milestone.item.html [new file with mode: 0644]
share/roundup/templates/devel/html/msg.index.html [new file with mode: 0644]
share/roundup/templates/devel/html/msg.item.html [new file with mode: 0644]
share/roundup/templates/devel/html/page.html [new file with mode: 0644]
share/roundup/templates/devel/html/patch.index.html [new file with mode: 0644]
share/roundup/templates/devel/html/patch.item.html [new file with mode: 0644]
share/roundup/templates/devel/html/query.edit.html [new file with mode: 0644]
share/roundup/templates/devel/html/query.item.html [new file with mode: 0644]
share/roundup/templates/devel/html/search.js [new file with mode: 0644]
share/roundup/templates/devel/html/style.css [new file with mode: 0644]
share/roundup/templates/devel/html/task.index.html [new file with mode: 0644]
share/roundup/templates/devel/html/task.item.html [new file with mode: 0644]
share/roundup/templates/devel/html/task.search.html [new file with mode: 0644]
share/roundup/templates/devel/html/tracker.css [new file with mode: 0644]
share/roundup/templates/devel/html/user.forgotten.html [new file with mode: 0644]
share/roundup/templates/devel/html/user.help-search.html [new file with mode: 0644]
share/roundup/templates/devel/html/user.help.html [new file with mode: 0644]
share/roundup/templates/devel/html/user.index.html [new file with mode: 0644]
share/roundup/templates/devel/html/user.item.html [new file with mode: 0644]
share/roundup/templates/devel/html/user.register.html [new file with mode: 0644]
share/roundup/templates/devel/html/user.rego_progress.html [new file with mode: 0644]
share/roundup/templates/devel/html/user_utils.js [new file with mode: 0644]
share/roundup/templates/devel/initial_data.py [new file with mode: 0644]
share/roundup/templates/devel/schema.py [new file with mode: 0644]

diff --git a/share/roundup/templates/devel/TEMPLATE-INFO.txt b/share/roundup/templates/devel/TEMPLATE-INFO.txt
new file mode 100644 (file)
index 0000000..e17f808
--- /dev/null
@@ -0,0 +1,7 @@
+Name: devel
+Description: This is a generic issue tracker that may be used to track bugs,
+             feature requests, project issues or any number of other types
+             of issues. Most users of Roundup will find that this template
+             suits them, with perhaps a few customisations.
+Intended-For: Developers
+
diff --git a/share/roundup/templates/devel/config.ini b/share/roundup/templates/devel/config.ini
new file mode 100644 (file)
index 0000000..ec78028
--- /dev/null
@@ -0,0 +1,383 @@
+# Roundup issue tracker configuration file
+# Autogenerated at Fri Nov 17 16:59:49 2006
+
+# WARNING! Following options need adjustments:
+#  [mail]: domain, host
+#  [tracker]: web
+
+[main]
+
+# Database directory path.
+# The path may be either absolute or relative
+# to the directory containig this config file.
+# Default: db
+database = db
+
+# Path to the HTML templates directory.
+# The path may be either absolute or relative
+# to the directory containig this config file.
+# Default: html
+templates = html
+
+# Path to directory holding additional static files
+# available via Web UI.  This directory may contain
+# sitewide images, CSS stylesheets etc. and is searched
+# for these files prior to the TEMPLATES directory
+# specified above.  If this option is not set, all static
+# files are taken from the TEMPLATES directory
+# The path may be either absolute or relative
+# to the directory containig this config file.
+# Default: 
+static_files = 
+
+# Email address that roundup will complain to if it runs into trouble.
+# Default: roundup-admin
+admin_email = roundup-admin
+
+# The 'dispatcher' is a role that can get notified
+# of new items to the database.
+# It is used by the ERROR_MESSAGES_TO config setting.
+# Default: roundup-admin
+dispatcher_email = roundup-admin
+
+# Additional text to include in the "name" part
+# of the From: address used in nosy messages.
+# If the sending user is "Foo Bar", the From: line
+# is usually: "Foo Bar" <issue_tracker@tracker.example>
+# the EMAIL_FROM_TAG goes inside the "Foo Bar" quotes like so:
+# "Foo Bar EMAIL_FROM_TAG" <issue_tracker@tracker.example>
+# Default: 
+email_from_tag = 
+
+# Roles that a user gets when they register with Web User Interface.
+# This is a comma-separated string of role names (e.g. 'Admin,User').
+# Default: User
+new_web_user_roles = User
+
+# Roles that a user gets when they register with Email Gateway.
+# This is a comma-separated string of role names (e.g. 'Admin,User').
+# Default: User
+new_email_user_roles = User
+
+# Send error message emails to the dispatcher, user, or both?
+# The dispatcher is configured using the DISPATCHER_EMAIL setting.
+# Default: user
+error_messages_to = user
+
+# HTML version to generate. The templates are html4 by default.
+# If you wish to make them xhtml, then you'll need to change this
+# var to 'xhtml' too so all auto-generated HTML is compliant.
+# Allowed values: html4, xhtml
+# Default: html4
+html_version = xhtml
+
+# Default timezone offset, applied when user's timezone is not set.
+# If pytz module is installed, value may be any valid
+# timezone specification (e.g. EET or Europe/Warsaw).
+# If pytz is not installed, value must be integer number
+# giving local timezone offset from UTC in hours.
+# Default: UTC
+timezone = UTC
+
+# Register new users instantly, or require confirmation via
+# email?
+# Allowed values: yes, no
+# Default: no
+instant_registration = no
+
+# Offer registration confirmation by email or only through the web?
+# Allowed values: yes, no
+# Default: yes
+email_registration_confirmation = yes
+
+# Additional stop-words for the full-text indexer specific to
+# your tracker. See the indexer source for the default list of
+# stop-words (eg. A,AND,ARE,AS,AT,BE,BUT,BY, ...)
+# Allowed values: comma-separated list of words
+# Default: 
+indexer_stopwords = 
+
+# Defines the file creation mode mask.
+# Default: 02
+umask = 02
+
+[tracker]
+
+# A descriptive name for your roundup instance.
+# Default: Roundup issue tracker
+name = Roundup tracker
+
+# The web address that the tracker is viewable at.
+# This will be included in information sent to users of the tracker.
+# The URL MUST include the cgi-bin part or anything else
+# that is required to get to the home page of the tracker.
+# You MUST include a trailing '/' in the URL.
+# Default: NO DEFAULT
+web = 
+
+# Email address that mail to roundup should go to.
+# Default: issue_tracker
+email = issue_tracker
+
+# Default locale name for this tracker.
+# If this option is not set, the language is determined
+# by OS environment variable LANGUAGE, LC_ALL, LC_MESSAGES,
+# or LANG, in that order of preference.
+# Default: 
+language = 
+
+[web]
+
+# Whether to use HTTP Basic Authentication, if present.
+# Roundup will use either the REMOTE_USER or HTTP_AUTHORIZATION
+# variables supplied by your web server (in that order).
+# Set this option to 'no' if you do not wish to use HTTP Basic
+# Authentication in your web interface.
+# Allowed values: yes, no
+# Default: yes
+http_auth = yes
+
+# Whether to use HTTP Accept-Language, if present.
+# Browsers send a language-region preference list.
+# It's usually set in the client's browser or in their
+# Operating System.
+# Set this option to 'no' if you want to ignore it.
+# Allowed values: yes, no
+# Default: yes
+use_browser_language = no
+
+# Setting this option makes Roundup display error tracebacks
+# in the user's browser rather than emailing them to the
+# tracker admin.
+# Allowed values: yes, no
+# Default: no
+debug = no
+
+# Settings in this section are used by Postgresql and MySQL backends only
+[rdbms]
+
+# Name of the database to use.
+# Default: roundup
+name = roundup_roundup_tracker
+
+# Database server host.
+# Default: localhost
+host = localhost
+
+# TCP port number of the database server.
+# Postgresql usually resides on port 5432 (if any),
+# for MySQL default port number is 3306.
+# Leave this option empty to use backend default
+# Default: 
+port = 
+
+# Database user name that Roundup should use.
+# Default: roundup
+user = roundup
+
+# Database user password.
+# Default: roundup
+password = roundup
+
+# Name of the MySQL defaults file.
+# Only used in MySQL connections.
+# Default: ~/.my.cnf
+read_default_file = ~/.my.cnf
+
+# Name of the group to use in the MySQL defaults file (.my.cnf).
+# Only used in MySQL connections.
+# Default: roundup
+read_default_group = roundup
+
+[logging]
+
+# Path to configuration file for standard Python logging module.
+# If this option is set, logging configuration is loaded
+# from specified file; options 'filename' and 'level'
+# in this section are ignored.
+# The path may be either absolute or relative
+# to the directory containig this config file.
+# Default: 
+config = 
+
+# Log file name for minimal logging facility built into Roundup.
+# If no file name specified, log messages are written on stderr.
+# If above 'config' option is set, this option has no effect.
+# The path may be either absolute or relative
+# to the directory containig this config file.
+# Default: 
+filename = 
+
+# Minimal severity level of messages written to log file.
+# If above 'config' option is set, this option has no effect.
+# Allowed values: DEBUG, INFO, WARNING, ERROR
+# Default: ERROR
+level = ERROR
+
+# Outgoing email options.
+# Used for nozy messages and approval requests
+[mail]
+
+# Domain name used for email addresses.
+# Default: NO DEFAULT
+#domain = NO DEFAULT
+domain = psf.upfronthosting.co.za
+
+# SMTP mail host that roundup will use to send mail
+# Default: NO DEFAULT
+#host = NO DEFAULT
+host = localhost
+
+# SMTP login name.
+# Set this if your mail host requires authenticated access.
+# If username is not empty, password (below) MUST be set!
+# Default: 
+username = 
+
+# SMTP login password.
+# Set this if your mail host requires authenticated access.
+# Default: NO DEFAULT
+#password = NO DEFAULT
+
+# If your SMTP mail host provides or requires TLS
+# (Transport Layer Security) then set this option to 'yes'.
+# Allowed values: yes, no
+# Default: no
+tls = no
+
+# If TLS is used, you may set this option to the name
+# of a PEM formatted file that contains your private key.
+# The path may be either absolute or relative
+# to the directory containig this config file.
+# Default: 
+tls_keyfile = 
+
+# If TLS is used, you may set this option to the name
+# of a PEM formatted certificate chain file.
+# The path may be either absolute or relative
+# to the directory containig this config file.
+# Default: 
+tls_certfile = 
+
+# Character set to encode email headers with.
+# We use utf-8 by default, as it's the most flexible.
+# Some mail readers (eg. Eudora) can't cope with that,
+# so you might need to specify a more limited character set
+# (eg. iso-8859-1).
+# Default: utf-8
+charset = utf-8
+
+# Setting this option makes Roundup to write all outgoing email
+# messages to this file *instead* of sending them.
+# This option has the same effect as environment variable SENDMAILDEBUG.
+# Environment variable takes precedence.
+# The path may be either absolute or relative
+# to the directory containig this config file.
+# Default: 
+#debug = /home/roundup/outgoing-mail
+debug = 
+
+# Roundup Mail Gateway options
+[mailgw]
+
+# Keep email citations when accepting messages.
+# Setting this to "no" strips out "quoted" text from the message.
+# Signatures are also stripped.
+# Allowed values: yes, no
+# Default: yes
+keep_quoted_text = yes
+
+# Preserve the email body as is - that is,
+# keep the citations _and_ signatures.
+# Allowed values: yes, no
+# Default: no
+leave_body_unchanged = no
+
+# Default class to use in the mailgw
+# if one isn't supplied in email subjects.
+# To disable, leave the value blank.
+# Default: issue
+default_class = issue
+
+# Default locale name for the tracker mail gateway.
+# If this option is not set, mail gateway will use
+# the language of the tracker instance.
+# Default: 
+language = 
+
+# Controls the parsing of the [prefix] on subject
+# lines in incoming emails. "strict" will return an
+# error to the sender if the [prefix] is not recognised.
+# "loose" will attempt to parse the [prefix] but just
+# pass it through as part of the issue title if not
+# recognised. "none" will always pass any [prefix]
+# through as part of the issue title.
+# Default: strict
+subject_prefix_parsing = strict
+
+# Controls the parsing of the [suffix] on subject
+# lines in incoming emails. "strict" will return an
+# error to the sender if the [suffix] is not recognised.
+# "loose" will attempt to parse the [suffix] but just
+# pass it through as part of the issue title if not
+# recognised. "none" will always pass any [suffix]
+# through as part of the issue title.
+# Default: strict
+subject_suffix_parsing = strict
+
+# Defines the brackets used for delimiting the prefix and 
+# suffix in a subject line. The presence of "suffix" in
+# the config option name is a historical artifact and may
+# be ignored.
+# Default: []
+subject_suffix_delimiters = []
+
+# Controls matching of the incoming email subject line
+# against issue titles in the case where there is no
+# designator [prefix]. "never" turns off matching.
+# "creation + interval" or "activity + interval"
+# will match an issue for the interval after the issue's
+# creation or last activity. The interval is a standard
+# Roundup interval.
+# Default: always
+subject_content_match = always
+
+# Nosy messages sending
+[nosy]
+
+# Send nosy messages to the author of the message.
+# Allowed values: yes, no, new
+# Default: no
+messages_to_author = yes
+
+# Where to place the email signature.
+# Allowed values: top, bottom, none
+# Default: bottom
+signature_position = bottom
+
+# Does the author of a message get placed on the nosy list
+# automatically?  If 'new' is used, then the author will
+# only be added when a message creates a new issue.
+# If 'yes', then the author will be added on followups too.
+# If 'no', they're never added to the nosy.
+# 
+# Allowed values: yes, no, new
+# Default: new
+add_author = yes
+
+# Do the recipients (To:, Cc:) of a message get placed on the
+# nosy list?  If 'new' is used, then the recipients will
+# only be added when a message creates a new issue.
+# If 'yes', then the recipients will be added on followups too.
+# If 'no', they're never added to the nosy.
+# 
+# Allowed values: yes, no, new
+# Default: new
+add_recipients = new
+
+# Controls the email sending from the nosy reactor. If
+# "multiple" then a separate email is sent to each
+# recipient. If "single" then a single email is sent with
+# each recipient as a CC address.
+# Default: single
+email_sending = multiple
diff --git a/share/roundup/templates/devel/detectors/messagesummary.py b/share/roundup/templates/devel/detectors/messagesummary.py
new file mode 100644 (file)
index 0000000..8d0349b
--- /dev/null
@@ -0,0 +1,20 @@
+#$Id: messagesummary.py,v 1.1 2009/02/01 22:23:25 stefan Exp $
+
+from roundup.mailgw import parseContent
+
+def summarygenerator(db, cl, nodeid, newvalues):
+    ''' If the message doesn't have a summary, make one for it.
+    '''
+    if newvalues.has_key('summary') or not newvalues.has_key('content'):
+        return
+
+    summary, content = parseContent(newvalues['content'], config=db.config)
+    newvalues['summary'] = summary
+
+
+def init(db):
+    # fire before changes are made
+    db.msg.audit('create', summarygenerator)
+
+# vim: set filetype=python ts=4 sw=4 et si
+#SHA: 538f90cb7f4eb63f77eca252b87afbe037d29c48
diff --git a/share/roundup/templates/devel/detectors/no_texthtml.py b/share/roundup/templates/devel/detectors/no_texthtml.py
new file mode 100644 (file)
index 0000000..cf47655
--- /dev/null
@@ -0,0 +1,9 @@
+
+def audit_html_files(db, cl, nodeid, newvalues):
+    if newvalues.has_key('type') and newvalues['type'] == 'text/html':
+        newvalues['type'] = 'text/plain'
+    
+
+def init(db):
+    db.file.audit('set', audit_html_files)
+    db.file.audit('create', audit_html_files)
diff --git a/share/roundup/templates/devel/detectors/nosyreaction.py b/share/roundup/templates/devel/detectors/nosyreaction.py
new file mode 100644 (file)
index 0000000..67380a0
--- /dev/null
@@ -0,0 +1,122 @@
+import sets
+
+from roundup import roundupdb, hyperdb
+
+def nosyreaction(db, cl, nodeid, oldvalues):
+    ''' A standard detector is provided that watches for additions to the
+        "messages" property.
+        
+        When a new message is added, the detector sends it to all the users on
+        the "nosy" list for the issue that are not already on the "recipients"
+        list of the message.
+        
+        Those users are then appended to the "recipients" property on the
+        message, so multiple copies of a message are never sent to the same
+        user.
+        
+        The journal recorded by the hyperdatabase on the "recipients" property
+        then provides a log of when the message was sent to whom. 
+    '''
+    # send a copy of all new messages to the nosy list
+    for msgid in determineNewMessages(cl, nodeid, oldvalues):
+        try:
+            cl.nosymessage(nodeid, msgid, oldvalues)
+        except roundupdb.MessageSendError, message:
+            raise roundupdb.DetectorError, message
+
+def determineNewMessages(cl, nodeid, oldvalues):
+    ''' Figure a list of the messages that are being added to the given
+        node in this transaction.
+    '''
+    messages = []
+    if oldvalues is None:
+        # the action was a create, so use all the messages in the create
+        messages = cl.get(nodeid, 'messages')
+    elif oldvalues.has_key('messages'):
+        # the action was a set (so adding new messages to an existing issue)
+        m = {}
+        for msgid in oldvalues['messages']:
+            m[msgid] = 1
+        messages = []
+        # figure which of the messages now on the issue weren't there before
+        for msgid in cl.get(nodeid, 'messages'):
+            if not m.has_key(msgid):
+                messages.append(msgid)
+    return messages
+
+def updatenosy(db, cl, nodeid, newvalues):
+    '''Update the nosy list for changes to the assignedto
+    '''
+    # nodeid will be None if this is a new node
+    current_nosy = sets.Set()
+    if nodeid is None:
+        ok = ('new', 'yes')
+    else:
+        ok = ('yes',)
+        # old node, get the current values from the node if they haven't
+        # changed
+        if not newvalues.has_key('nosy'):
+            nosy = cl.get(nodeid, 'nosy')
+            for value in nosy:
+                current_nosy.add(value)
+
+    # if the nosy list changed in this transaction, init from the new value
+    if newvalues.has_key('nosy'):
+        nosy = newvalues.get('nosy', [])
+        for value in nosy:
+            if not db.hasnode('user', value):
+                continue
+            current_nosy.add(value)
+
+    new_nosy = sets.Set(current_nosy)
+
+    # add assignedto(s) to the nosy list
+    if newvalues.has_key('assignedto') and newvalues['assignedto'] is not None:
+        propdef = cl.getprops()
+        if isinstance(propdef['assignedto'], hyperdb.Link):
+            assignedto_ids = [newvalues['assignedto']]
+        elif isinstance(propdef['assignedto'], hyperdb.Multilink):
+            assignedto_ids = newvalues['assignedto']
+        for assignedto_id in assignedto_ids:
+            new_nosy.add(assignedto_id)
+
+    # see if there's any new messages - if so, possibly add the author and
+    # recipient to the nosy
+    if newvalues.has_key('messages'):
+        if nodeid is None:
+            ok = ('new', 'yes')
+            messages = newvalues['messages']
+        else:
+            ok = ('yes',)
+            # figure which of the messages now on the issue weren't
+            oldmessages = cl.get(nodeid, 'messages')
+            messages = []
+            for msgid in newvalues['messages']:
+                if msgid not in oldmessages:
+                    messages.append(msgid)
+
+        # configs for nosy modifications
+        add_author = getattr(db.config, 'ADD_AUTHOR_TO_NOSY', 'new')
+        add_recips = getattr(db.config, 'ADD_RECIPIENTS_TO_NOSY', 'new')
+
+        # now for each new message:
+        msg = db.msg
+        for msgid in messages:
+            if add_author in ok:
+                authid = msg.get(msgid, 'author')
+                new_nosy.add(authid)
+
+            # add on the recipients of the message
+            if add_recips in ok:
+                for recipient in msg.get(msgid, 'recipients'):
+                    new_nosy.add(recipient)
+
+    if current_nosy != new_nosy:
+        # that's it, save off the new nosy list
+        newvalues['nosy'] = list(new_nosy)
+
+def init(db):
+    db.issue.react('create', nosyreaction)
+    db.issue.react('set', nosyreaction)
+    db.issue.audit('create', updatenosy)
+    db.issue.audit('set', updatenosy)
diff --git a/share/roundup/templates/devel/detectors/patches.py b/share/roundup/templates/devel/detectors/patches.py
new file mode 100644 (file)
index 0000000..d79f367
--- /dev/null
@@ -0,0 +1,45 @@
+# Auditor for patch files
+# Patches should be declared as text/plain (also .py files),
+# independent of what the browser says, and
+# the "patch" keyword should get set automatically.
+
+import posixpath
+
+patchtypes = ('.diff', '.patch')
+sourcetypes = ('.diff', '.patch', '.py')
+
+def ispatch(file, types):
+    return posixpath.splitext(file)[1] in types
+
+def patches_text_plain(db, cl, nodeid, newvalues):
+    if ispatch(newvalues['name'], sourcetypes):
+        newvalues['type'] = 'text/plain'
+
+def patches_keyword(db, cl, nodeid, newvalues):
+    # Check whether there are any new files
+    newfiles = set(newvalues.get('files',()))
+    if nodeid:
+        newfiles -= set(db.issue.get(nodeid, 'files'))
+    # Check whether any of these is a patch
+    newpatch = False
+    for fileid in newfiles:
+        if ispatch(db.file.get(fileid, 'name'), patchtypes):
+            newpatch = True
+            break
+    if newpatch:
+        # Add the patch keyword if its not already there
+        patchid = db.keyword.lookup("patch")
+        oldkeywords = []
+        if nodeid:
+            oldkeywords = db.issue.get(nodeid, 'keywords')
+            if patchid in oldkeywords:
+                # This is already marked as a patch
+                return
+        if not newvalues.has_key('keywords'):
+            newvalues['keywords'] = oldkeywords
+        newvalues['keywords'].append(patchid)
+
+def init(db): pass
+#    db.file.audit('create', patches_text_plain)
+#    db.issue.audit('create', patches_keyword)
+#    db.issue.audit('set', patches_keyword)
diff --git a/share/roundup/templates/devel/detectors/severityauditor.py b/share/roundup/templates/devel/detectors/severityauditor.py
new file mode 100644 (file)
index 0000000..721c179
--- /dev/null
@@ -0,0 +1,11 @@
+
+def init_severity(db, cl, nodeid, newvalues):
+    """Make sure severity is set on new issues"""
+    if newvalues.has_key('severity') and newvalues['severity']:
+        return
+
+    normal = db.severity.lookup('normal')
+    newvalues['severity'] = normal
+
+def init(db): pass
+    #db.issue.audit('create', init_severity)
diff --git a/share/roundup/templates/devel/detectors/statusauditor.py b/share/roundup/templates/devel/detectors/statusauditor.py
new file mode 100644 (file)
index 0000000..e68169f
--- /dev/null
@@ -0,0 +1,13 @@
+def preset_new(db, cl, nodeid, newvalues):
+    """ Make sure the status is set on new issues"""
+
+    if newvalues.has_key('status') and newvalues['status']:
+        return
+
+    new = db.status.lookup('new')
+    newvalues['status'] = new
+
+
+def init(db): pass
+    # fire before changes are made
+    #db.issue.audit('create', preset_new)
diff --git a/share/roundup/templates/devel/detectors/userauditor.py b/share/roundup/templates/devel/detectors/userauditor.py
new file mode 100644 (file)
index 0000000..b172b56
--- /dev/null
@@ -0,0 +1,71 @@
+import re
+
+# regular expression thanks to: http://www.regular-expressions.info/email.html
+# this is the "99.99% solution for syntax only".
+email_regexp = (r"[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*", r"(localhost|(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9]))")
+email_rfc = re.compile('^' + email_regexp[0] + '@' + email_regexp[1] + '$', re.IGNORECASE)
+email_local = re.compile('^' + email_regexp[0] + '$', re.IGNORECASE)
+
+def valid_address(address):
+    ''' If we see an @-symbol in the address then check against the full
+        RFC syntax. Otherwise it is a local-only address so only check
+        the local part of the RFC syntax.
+    '''
+    if '@' in address:
+        return email_rfc.match(address)
+    else:
+        return email_local.match(address)
+
+def get_addresses(user):
+    ''' iterate over all known addresses in a newvalues dict
+        this takes of the address/alterate_addresses handling
+    '''
+    if user.has_key('address'):
+        yield user['address']
+    if user.get('alternate_addresses', None):
+        for address in user['alternate_addresses'].split('\n'):
+            yield address
+
+def audit_user_fields(db, cl, nodeid, newvalues):
+    ''' Make sure user properties are valid.
+
+        - email address is syntactically valid
+        - email address is unique
+        - roles specified exist
+        - timezone is valid
+    '''
+
+    for address in get_addresses(newvalues):
+        if not valid_address(address):
+            print newvalues
+            raise ValueError, 'Email address syntax is invalid "%s"'%address
+
+        check_main = db.user.stringFind(address=address)
+        # make sure none of the alts are owned by anyone other than us (x!=nodeid)
+        check_alts = [x for x in db.user.filter(None, {'alternate_addresses' : address}) if x != nodeid]
+        if check_main or check_alts:
+            raise ValueError, 'Email address %s already in use' % address
+
+    for rolename in [r.lower().strip() for r in newvalues.get('roles', '').split(',')]:
+            if rolename and not db.security.role.has_key(rolename):
+                raise ValueError, 'Role "%s" does not exist'%rolename
+
+    tz = newvalues.get('timezone', None)
+    if tz:
+        # if they set a new timezone validate the timezone by attempting to
+        # use it before we store it to the db.
+        import roundup.date
+        import datetime
+        try:
+            TZ = roundup.date.get_timezone(tz)
+            dt = datetime.datetime.now()
+            local = TZ.localize(dt).utctimetuple()
+        except IOError:
+            raise ValueError, 'Timezone "%s" does not exist' % tz
+        except ValueError:
+            raise ValueError, 'Timezone "%s" exceeds valid range [-23...23]' % tz
+
+def init(db):
+    # fire before changes are made
+    db.user.audit('set', audit_user_fields)
+    db.user.audit('create', audit_user_fields)
diff --git a/share/roundup/templates/devel/extensions/local_replace.py b/share/roundup/templates/devel/extensions/local_replace.py
new file mode 100644 (file)
index 0000000..7523e16
--- /dev/null
@@ -0,0 +1,35 @@
+import re
+
+substitutions = [ (re.compile('debian:\#(?P<id>\d+)'),
+                   '<a href="http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=\g<id>">debian#\g<id></a>' ),
+                  (re.compile('\#(?P<ws>\s*)(?P<id>\d+)'),
+                   "<a href='issue\g<id>'>#\g<ws>\g<id></a>" ),
+                  (re.compile('(?P<prews>\s+)revision(?P<ws>\s*)(?P<revision>\d+)'),
+                   "\g<prews><a href='http://svn.roundup-tracker.org/view?rev=\g<revision>&view=rev'>revision\g<ws>\g<revision></a>"),
+                  (re.compile('(?P<prews>\s+)rev(?P<ws>\s*)(?P<revision>\d+)'),
+                   "\g<prews><a href='http://svn.roundup-tracker.org/view?rev=\g<revision>&view=rev'>rev\g<ws>\g<revision></a>"),
+                  (re.compile('(?P<prews>\s+)(?P<revstr>r|r\s+)(?P<revision>\d+)'),
+                   "\g<prews><a href='http://svn.roundup-tracker.org/view?rev=\g<revision>&view=rev'>\g<revstr>\g<revision></a>"),
+                  ]
+
+def local_replace(message):
+
+    for cre, replacement in substitutions:
+        message = cre.sub(replacement, message)
+
+    return message
+        
+    
+    
+def init(instance):
+    instance.registerUtil('localReplace', local_replace)
+    
+
+if "__main__" == __name__:
+    print " debian:#222", local_replace(" debian:#222")
+    print " revision 222", local_replace(" revision 222")
+    print " wordthatendswithr 222", local_replace(" wordthatendswithr 222")
+    print " r222", local_replace(" r222")
+    print " r 222", local_replace(" r 222")
+    print " #555", local_replace(" #555")
+    
diff --git a/share/roundup/templates/devel/extensions/spambayes.py b/share/roundup/templates/devel/extensions/spambayes.py
new file mode 100644 (file)
index 0000000..b6ab303
--- /dev/null
@@ -0,0 +1,84 @@
+import re, math
+from roundup.cgi.actions import Action
+from roundup.cgi.exceptions import *
+
+import xmlrpclib, socket
+
+REVPAT = re.compile(r'(r[0-9]+\b|rev(ision)? [0-9]+\b)')
+
+def extract_classinfo(db, classname, nodeid):
+    node = db.getnode(classname, nodeid)
+
+    authorage = node['creation'].timestamp() - \
+                db.getnode('user', node.get('author', node.get('creator')))['creation'].timestamp()
+
+    authorid = node.get('author', node.get('creator'))
+
+    content = db.getclass(classname).get(nodeid, 'content')
+
+    tokens = ["klass:%s" % classname,
+              "author:%s" % authorid,
+              "authorage:%d" % int(math.log(authorage)),
+              "hasrev:%s" % (REVPAT.search(content) is not None)]
+
+    return (content, tokens)
+
+def train_spambayes(db, content, tokens, is_spam):
+    spambayes_uri = db.config.detectors['SPAMBAYES_URI']
+
+    server = xmlrpclib.ServerProxy(spambayes_uri, verbose=False)
+    try:
+        server.train({'content':content}, tokens, {}, is_spam)
+        return (True, None)
+    except (socket.error, xmlrpclib.Error), e:
+        return (False, str(e))
+
+
+class SpambayesClassify(Action):
+    permissionType = 'SB: May Classify'
+    
+    def handle(self):
+        (content, tokens) = extract_classinfo(self.db,
+                                              self.classname, self.nodeid)
+
+        if self.form.has_key("trainspam"):
+            is_spam = True
+        elif self.form.has_key("trainham"):
+            is_spam = False
+
+        (status, errmsg) = train_spambayes(self.db, content, tokens,
+                                           is_spam)
+
+        node = self.db.getnode(self.classname, self.nodeid)
+        props = {}
+
+        if status:
+            if node.get('spambayes_misclassified', False):
+                props['spambayes_misclassified'] = True
+
+            props['spambayes_score'] = 1.0
+            
+            s = " SPAM"
+            if not is_spam:
+                props['spambayes_score'] = 0.0
+                s = " HAM"
+            self.client.ok_message.append(self._('Message classified as') + s)
+        else:
+            self.client.error_message.append(self._('Unable to classify message, got error:') + errmsg)
+
+        klass = self.db.getclass(self.classname)
+        klass.set(self.nodeid, **props)
+        self.db.commit()
+
+def sb_is_spam(obj):
+    cutoff_score = float(obj._db.config.detectors['SPAMBAYES_SPAM_CUTOFF'])
+    try:
+        score = obj['spambayes_score']
+    except KeyError:
+        return False
+    return score >= cutoff_score
+
+def init(instance):
+    instance.registerAction("spambayes_classify", SpambayesClassify)
+    instance.registerUtil('sb_is_spam', sb_is_spam)
+    
diff --git a/share/roundup/templates/devel/extensions/timestamp.py b/share/roundup/templates/devel/extensions/timestamp.py
new file mode 100644 (file)
index 0000000..270e480
--- /dev/null
@@ -0,0 +1,28 @@
+import time, struct, base64
+from roundup.cgi.actions import RegisterAction
+from roundup.cgi.exceptions import *
+
+def timestamp():
+    return base64.encodestring(struct.pack("i", time.time())).strip()
+
+def unpack_timestamp(s):
+    return struct.unpack("i",base64.decodestring(s))[0]
+
+class Timestamped:
+    def check(self):
+        try:
+            created = unpack_timestamp(self.form['opaque'].value)
+        except KeyError:
+            raise FormError, "somebody tampered with the form"
+        if time.time() - created < 4:
+            raise FormError, "responding to the form too quickly"
+        return True
+
+class TimestampedRegister(Timestamped, RegisterAction):
+    def permission(self):
+        self.check()
+        RegisterAction.permission(self)
+
+def init(instance):
+    instance.registerUtil('timestamp', timestamp)
+    instance.registerAction('register', TimestampedRegister)
diff --git a/share/roundup/templates/devel/extensions/timezone.py b/share/roundup/templates/devel/extensions/timezone.py
new file mode 100644 (file)
index 0000000..2a994c8
--- /dev/null
@@ -0,0 +1,37 @@
+# Utility for replacing the simple input field for the timezone with
+# a select-field that lists the available values.
+
+import cgi
+
+try:
+    import pytz
+except ImportError:
+    pytz = None
+
+
+def tzfield(prop, name, default):
+    if pytz:
+        value = prop.plain()        
+        if '' == value:
+            value = default
+        else:
+            try:
+                value = "Etc/GMT%+d" % int(value)
+            except ValueError:
+                pass
+
+        l = ['<select name="%s"' % name]
+        for zone in pytz.all_timezones:
+            s = ' '
+            if zone == value:
+                s = 'selected=selected '
+            z = cgi.escape(zone)
+            l.append('<option %svalue="%s">%s</option>' % (s, z, z))
+        l.append('</select>')
+        return '\n'.join(l)
+        
+    else:
+        return prop.field()
+
+def init(instance):
+    instance.registerUtil('tzfield', tzfield)
diff --git a/share/roundup/templates/devel/html/_generic.404.html b/share/roundup/templates/devel/html/_generic.404.html
new file mode 100644 (file)
index 0000000..71c9e0e
--- /dev/null
@@ -0,0 +1,9 @@
+<html>
+<head>
+<title>Item Not Found</title>
+</head>
+
+<body>
+There is no <span tal:content="context/_classname" /> with id <span tal:content="context/id"/>
+</body>
+</html>
diff --git a/share/roundup/templates/devel/html/_generic.calendar.html b/share/roundup/templates/devel/html/_generic.calendar.html
new file mode 100644 (file)
index 0000000..0f9cf40
--- /dev/null
@@ -0,0 +1,18 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html>
+ <head>
+  <link rel="stylesheet" type="text/css" href="@@file/style.css" />
+  <meta http-equiv="Content-Type" content="text/html; charset=utf-8;" />
+  <title tal:content="string:Roundup Calendar"></title>
+  <script language="Javascript"
+          type="text/javascript"
+          tal:content="structure string:
+          // this is the name of the field in the original form that we're working on
+          form  = window.opener.document.${request/form/form/value};
+          field = '${request/form/property/value}';" >
+  </script>
+ </head>
+ <body class="body"
+       tal:content="structure python:utils.html_calendar(request)">
+ </body>
+</html>
diff --git a/share/roundup/templates/devel/html/_generic.collision.html b/share/roundup/templates/devel/html/_generic.collision.html
new file mode 100644 (file)
index 0000000..a701c58
--- /dev/null
@@ -0,0 +1,16 @@
+<tal:block metal:use-macro="templates/page/macros/frame">
+<title metal:fill-slot="head_title" i18n:translate=""
+ ><span tal:replace="python:context._classname.capitalize()"
+ i18n:name="class" /> Edit Collision - <span i18n:name="tracker"
+ tal:replace="config/TRACKER_NAME" /></title>
+<tal:block metal:fill-slot="body_title" i18n:translate=""
+ ><span tal:replace="python:context._classname.capitalize()"
+ i18n:name="class" /> Edit Collision</tal:block>
+
+<td class="content" metal:fill-slot="content" i18n:translate="
+  There has been a collision. Another user updated this node
+  while you were editing. Please <a href='${context}'>reload</a>
+  the node and review your edits.
+"><span tal:replace="context/designator" i18n:name="context" />
+</td>
+</tal:block>
diff --git a/share/roundup/templates/devel/html/_generic.help-empty.html b/share/roundup/templates/devel/html/_generic.help-empty.html
new file mode 100644 (file)
index 0000000..65469fc
--- /dev/null
@@ -0,0 +1,8 @@
+<html>
+  <head>
+    <title>Empty page (no search performed yet)</title>
+  </head>
+  <body>
+    <p i18n:translate="">Please specify your search parameters!</p>
+  </body>
+</html>
diff --git a/share/roundup/templates/devel/html/_generic.help-list.html b/share/roundup/templates/devel/html/_generic.help-list.html
new file mode 100644 (file)
index 0000000..4c71338
--- /dev/null
@@ -0,0 +1,55 @@
+<tal:block tal:define="vok context/is_view_ok">
+  <p tal:condition="not:vok" i18n:translate="">You are not
+  allowed to view this page.</p>
+
+  <tal:if condition="context/is_view_ok">
+   <tal:def define="batch request/batch;">
+    <table width="100%"
+           tal:define="template string:help-list"
+           metal:use-macro="templates/help/macros/batch_navi">
+      <tr class="navigation">
+       <th><a href="#">&lt;&lt; previous</a></th>
+       <th i18n:translate="">1..25 out of 50</th>
+       <th><a href="#">next &gt;&gt;</a></th>
+      </tr>
+    </table>
+    <form>
+    <table class="classhelp"
+           tal:define="props python:request.form['properties'].value.split(',');
+                       legend templates/help/macros/legend;">
+      <thead>
+        <tr metal:use-macro="legend">
+          <th>&nbsp;<b>x</b></th>
+          <th tal:repeat="prop props" tal:content="prop" i18n:translate=""></th>
+        </tr>
+      </thead>
+      <tfoot tal:condition=true>
+        <tr metal:use-macro="legend" />
+      </tfoot>
+      <tbody>
+       <tr tal:repeat="item batch">
+         <tal:block tal:define="attr python:item[props[0]]" >
+           <td>
+             <input name="check"
+             onclick="switch_val(text_field, this);" type="checkbox"
+             tal:attributes="value attr; id string:id_$attr" />
+             </td>
+             <td tal:repeat="prop props">
+                 <label class="classhelp-label"
+                        tal:attributes="for string:id_$attr"
+                        tal:content="structure python:item[prop]"></label>
+             </td>
+           </tal:block>
+         </tr>
+       </tbody>
+     </table>
+   </form>
+     </tal:def>
+     </tal:if>
+     
+     <pre tal:content=request tal:condition=false />
+     <script type="text/javascript"><!--
+       parent.submit.document.frm_help.cb_listpresent.checked=true;
+       reviseList_framed(document.dummyform, text_field)
+     //--></script>
+</tal:block>
diff --git a/share/roundup/templates/devel/html/_generic.help-search.html b/share/roundup/templates/devel/html/_generic.help-search.html
new file mode 100644 (file)
index 0000000..01d657e
--- /dev/null
@@ -0,0 +1,13 @@
+<html>
+  <head>
+    <title>Frame for search input fields</title>
+  </head>
+  <body>
+    <p i18n:translate="">Generic template
+    <span tal:replace="request/template" i18n:name="template">help-search</span>
+    or version for class
+    <span tal:replace="request/classname" i18n:name="classname">user</span>
+    is not yet implemented</p>
+  </body>
+</html>
+
diff --git a/share/roundup/templates/devel/html/_generic.help-submit.html b/share/roundup/templates/devel/html/_generic.help-submit.html
new file mode 100644 (file)
index 0000000..b13aa4a
--- /dev/null
@@ -0,0 +1,73 @@
+<html>
+  <head>
+      <link rel="stylesheet" type="text/css" href="@@file/style.css" />
+      <meta http-equiv="Content-Type"
+       tal:attributes="content string:text/html;; charset=${request/client/charset}" />
+      <tal:block tal:condition="python:request.form.has_key('property')">
+      <title>Generic submit page for framed helper windows</title>
+      <script language="Javascript" type="text/javascript"
+          tal:content="structure string:<!--
+// this is the name of the field in the original form that we're working on
+form  = parent.opener.document.${request/form/form/value};
+callingform=form
+field  = '${request/form/property/value}';
+var listform = null
+function listPresent() {
+  return document.frm_help.cb_listpresent.checked
+}
+function getListForm() {
+  if (listPresent()) {
+    return parent.list.document.forms.dummyform
+  } else {
+    return null
+  }
+}
+
+
+function checkListForm() {
+  // global listform
+  if (listform != null)
+    if (parent.list.document.dummyform) {
+      listform = parent.list.document.dummyform
+      alert(listform)
+    }
+
+  var bol= listform != null
+  alert('checkListForm: bol='+bol)
+  return bol
+}
+//-->">
+      </script>
+      <script src="@@file/help_controls.js" type="text/javascript"></script>
+      </tal:block>
+  </head>
+ <body class="body" onload="parent.focus();" id="submit">
+ <pre tal:content="request/env/QUERY_STRING" tal:condition=false />
+ <form name="frm_help"
+       tal:define="batch request/batch;
+       props python:request.form['properties'].value.split(',')"
+       class="help-submit"
+       id="classhelp-controls">
+     <div style="width:100%;text-align:left;margin-bottom:0.2em">
+       <input type="text" name="text_preview" size="24" class="preview"
+       onchange="f=getListForm();if(f){ reviseList_framed(f, this)};"
+       />
+     </div>
+     <input type=checkbox name="cb_listpresent" readonly="readonly" style="display:none">
+     <input type="button" id="btn_cancel"
+            value=" Cancel " onclick="parent.close();return false;"
+            i18n:attributes="value" />
+     <input type="reset" id="btn_reset"
+     onclick="text_field.value=original_field;f=getListForm();if (f) {reviseList_framed(f, this)};return false"
+            />
+     <input type="submit" id="btn_apply" class="apply"
+            value=" Apply " onclick="callingform[field].value=text_field.value; parent.close();"
+            i18n:attributes="value" />
+ </form>
+ <script type="text/javascript"><!--
+var text_field = document.frm_help.text_preview;
+original_field=form[field].value;
+text_field.value=original_field;
+//--></script>
+ </body>
+</html>
diff --git a/share/roundup/templates/devel/html/_generic.help.html b/share/roundup/templates/devel/html/_generic.help.html
new file mode 100644 (file)
index 0000000..cfa941c
--- /dev/null
@@ -0,0 +1,140 @@
+<tal:block metal:use-macro="templates/help/macros/frame">
+<tal:block metal:fill-slot="content">
+<tal:block tal:condition="python:request.form.has_key('property')">
+ <form name="frm_help" action="#"
+       tal:define="batch request/batch;
+                   props python:request.form['properties'].value.split(',')">
+
+     <div id="classhelp-controls" tal:condition="context/is_edit_ok">
+       <!--input type="button" name="btn_clear"
+              value="Clear" onClick="clearList()"/ -->
+       <input type="text" name="text_preview" size="24" class="preview"
+              onchange="reviseList(this.value);"/>
+       <input type="button" name="btn_reset"
+              value=" Cancel " onclick="resetList(); parent.close();"
+              i18n:attributes="value" />
+       <input type="button" name="btn_apply" class="apply"
+              value=" Apply " onclick="updateList(); parent.close();"
+              i18n:attributes="value" />
+     </div>
+     <table width="100%">
+      <tr class="navigation">
+       <th>
+        <a tal:define="prev batch/previous" tal:condition="prev"
+           tal:attributes="href python:request.indexargs_url(request.classname,
+           {'@template':'help', 'property': request.form['property'].value,
+            'properties': request.form['properties'].value,
+            'form': request.form['form'].value,
+            'type': request.form['type'].value,
+            '@startwith':prev.first, '@pagesize':prev.size})"
+           i18n:translate="" >&lt;&lt; previous</a>
+        &nbsp;
+       </th>
+       <th i18n:translate=""><span tal:replace="batch/start" i18n:name="start"
+        />..<span tal:replace="python: batch.start + batch.length -1" i18n:name="end"
+        /> out of <span tal:replace="batch/sequence_length" i18n:name="total"
+        />
+       </th>
+       <th>
+        <a tal:define="next batch/next" tal:condition="next"
+           tal:attributes="href python:request.indexargs_url(request.classname,
+           {'@template':'help', 'property': request.form['property'].value,
+            'properties': request.form['properties'].value,
+            'form': request.form['form'].value,
+            'type': request.form['type'].value,
+            '@startwith':next.first, '@pagesize':next.size})"
+           i18n:translate="" >next &gt;&gt;</a>
+        &nbsp;
+       </th>
+      </tr>
+     </table>
+
+     <table class="classhelp">
+       <tr>
+           <th tal:condition="context/is_edit_ok">&nbsp;<b>x</b></th>
+           <th tal:repeat="prop props" tal:content="prop" i18n:translate=""></th>
+       </tr>
+       <tr tal:repeat="item batch">
+         <tal:block tal:define="attr python:item[props[0]]" >
+           <td tal:condition="context/is_edit_ok">
+             <input name="check"
+                 onclick="updatePreview();"
+                 tal:attributes="type python:request.form['type'].value;
+                                 value attr; id string:id_$attr" />
+             </td>
+             <td tal:repeat="prop props">
+                 <label class="classhelp-label"
+                        tal:attributes="for string:id_$attr"
+                        tal:content="structure python:item[prop]"></label>
+             </td>
+           </tal:block>
+       </tr>
+       <tr>
+           <th>&nbsp;<b>x</b></th>
+           <th tal:repeat="prop props" tal:content="prop" i18n:translate=""></th>
+       </tr>
+     </table>
+
+ </form>
+   <script type="text/javascript"><!--
+var text_field = document.frm_help.text_preview;
+original_field=form[field].value;
+text_field.value=original_field;
+//--></script>
+ </tal:block>
+ <tal:block tal:condition="python:not request.form.has_key('property')">
+ <tal:block tal:define="batch request/batch;
+                        props python:request.form['properties'].value.split(',')">
+     <table width="100%">
+      <tr class="navigation">
+       <th>
+        <a tal:define="prev batch/previous" tal:condition="prev"
+           tal:attributes="href python:request.indexargs_url(request.classname,
+           {'@template':'help', 'property': request.form['property'].value,
+            'properties': request.form['properties'].value,
+            'form': request.form['form'].value,
+            'type': request.form['type'].value,
+            '@startwith':prev.first, '@pagesize':prev.size})"
+           i18n:translate="" >&lt;&lt; previous</a>
+        &nbsp;
+       </th>
+       <th i18n:translate=""><span tal:replace="batch/start" i18n:name="start"
+        />..<span tal:replace="python: batch.start + batch.length -1" i18n:name="end"
+        /> out of <span tal:replace="batch/sequence_length" i18n:name="total"
+        />
+       </th>
+       <th>
+        <a tal:define="next batch/next" tal:condition="next"
+           tal:attributes="href python:request.indexargs_url(request.classname,
+           {'@template':'help', 'property': request.form['property'].value,
+            'properties': request.form['properties'].value,
+            'form': request.form['form'].value,
+            'type': request.form['type'].value,
+            '@startwith':next.first, '@pagesize':next.size})"
+           i18n:translate="" >next &gt;&gt;</a>
+        &nbsp;
+       </th>
+      </tr>
+     </table>
+
+     <table class="classhelp">
+       <tr>
+           <th tal:repeat="prop props" tal:content="prop" i18n:translate=""></th>
+       </tr>
+       <tr tal:repeat="item batch">
+         <tal:block tal:define="attr python:item[props[0]]" >
+             <td tal:repeat="prop props">
+                 <label class="classhelp-label"
+                        tal:attributes="for string:id_$attr"
+                        tal:content="structure python:item[prop]"></label>
+             </td>
+           </tal:block>
+       </tr>
+       <tr>
+           <th tal:repeat="prop props" tal:content="prop" i18n:translate=""></th>
+       </tr>
+     </table>
+ </tal:block>
+ </tal:block>
+</tal:block> <!-- content -->
+</tal:block>
diff --git a/share/roundup/templates/devel/html/_generic.index.html b/share/roundup/templates/devel/html/_generic.index.html
new file mode 100644 (file)
index 0000000..53b0a36
--- /dev/null
@@ -0,0 +1,68 @@
+<tal:block metal:use-macro="templates/page/macros/frame">
+<title metal:fill-slot="head_title" i18n:translate=""
+ ><span tal:replace="python:context._classname.capitalize()"
+ i18n:name="class" /> editing - <span i18n:name="tracker"
+ tal:replace="config/TRACKER_NAME" /></title>
+<tal:block metal:fill-slot="body_title" i18n:translate=""
+ ><span tal:replace="python:context._classname.capitalize()"
+ i18n:name="class" /> editing</tal:block>
+
+<td class="content" metal:fill-slot="content">
+
+<span tal:condition="python:not (context.is_view_ok() or context.is_edit_ok()
+ or request.user.hasRole('Anonymous'))"
+ tal:omit-tag="python:1" i18n:translate=""
+>You are not allowed to view this page.</span>
+
+<span tal:condition="python:not (context.is_view_ok() or context.is_edit_ok())
+ and request.user.hasRole('Anonymous')"
+ tal:omit-tag="python:1" i18n:translate=""
+>Please login with your username and password.</span>
+
+<tal:block tal:condition="context/is_edit_ok">
+<tal:block i18n:translate="">
+<p class="form-help">
+ You may edit the contents of the
+ <span tal:replace="request/classname" i18n:name="classname"/>
+ class using this form. Commas, newlines and double quotes (") must be
+ handled delicately. You may include commas and newlines by enclosing the
+ values in double-quotes ("). Double quotes themselves must be quoted by
+ doubling ("").
+</p>
+
+<p class="form-help">
+ Multilink properties have their multiple values colon (":") separated
+ (... ,"one:two:three", ...)
+</p>
+
+<p class="form-help">
+ Remove entries by deleting their line. Add new entries by appending
+ them to the table - put an X in the id column.
+</p>
+</tal:block>
+<form onSubmit="return submit_once()" method="POST"
+      tal:attributes="action context/designator">
+<textarea rows="15" style="width:90%" name="rows" tal:content="context/csv"></textarea>
+<br>
+<input type="hidden" name="@action" value="editCSV">
+<input type="submit" value="Edit Items" i18n:attributes="value">
+</form>
+</tal:block>
+
+<table tal:condition="context/is_only_view_ok" width="100%" class="list">
+ <tr>
+  <th tal:repeat="property context/propnames" tal:content="property">&nbsp;</th>
+ </tr>
+ <tal:block repeat="item context/list">
+ <tr tal:condition="item/is_view_ok"
+     tal:attributes="class python:['normal', 'alt'][repeat['item'].index%6/3]">
+  <td tal:repeat="property context/propnames"
+   tal:content="python: item[property] or default"
+  >&nbsp;</td>
+ </tr>
+ </tal:block>
+</table>
+
+</td>
+
+</tal:block>
diff --git a/share/roundup/templates/devel/html/_generic.item.html b/share/roundup/templates/devel/html/_generic.item.html
new file mode 100644 (file)
index 0000000..077225f
--- /dev/null
@@ -0,0 +1,53 @@
+<tal:block metal:use-macro="templates/page/macros/frame">
+<title metal:fill-slot="head_title" i18n:translate=""
+ ><span tal:replace="python:context._classname.capitalize()"
+ i18n:name="class" /> editing - <span i18n:name="tracker"
+ tal:replace="config/TRACKER_NAME" /></title>
+<tal:block metal:fill-slot="body_title" i18n:translate=""
+ ><span tal:replace="python:context._classname.capitalize()"
+ i18n:name="class" /> editing</tal:block>
+
+<td class="content" metal:fill-slot="content">
+
+<p tal:condition="python:not (context.is_view_ok()
+ or request.user.hasRole('Anonymous'))" i18n:translate="">
+ You are not allowed to view this page.</p>
+
+<p tal:condition="python:not context.is_view_ok()
+ and request.user.hasRole('Anonymous')" i18n:translate="">
+ Please login with your username and password.</p>
+
+<div tal:condition="context/is_view_ok">
+
+<form method="POST" onSubmit="return submit_once()"
+      enctype="multipart/form-data" tal:condition="context/is_view_ok"
+      tal:attributes="action context/designator">
+
+<input type="hidden" name="@template" value="item">
+
+<table class="form">
+
+<tr tal:repeat="prop python:db[context._classname].properties()">
+ <tal:block tal:condition="python:prop._name not in ('id',
+   'creator', 'creation', 'actor', 'activity')">
+  <th tal:content="prop/_name"></th>
+  <td tal:content="structure python:context[prop._name].field()"></td>
+ </tal:block>
+</tr>
+<tr>
+ <td>&nbsp;</td>
+ <td colspan=3 tal:content="structure context/submit">
+  submit button will go here
+ </td>
+</tr>
+</table>
+
+</form>
+
+<tal:block tal:condition="context/id" tal:replace="structure context/history" />
+
+</div>
+
+</td>
+
+</tal:block>
diff --git a/share/roundup/templates/devel/html/bug.index.html b/share/roundup/templates/devel/html/bug.index.html
new file mode 100644 (file)
index 0000000..377dc42
--- /dev/null
@@ -0,0 +1,179 @@
+<tal:block metal:use-macro="templates/page/macros/frame">
+<title metal:fill-slot="head_title" >
+  <span tal:omit-tag="true" i18n:translate="" >List of bugs</span>
+  <span tal:condition="request/dispname"
+   tal:replace="python:' - %s '%request.dispname"
+  /> - <span tal:replace="config/TRACKER_NAME" />
+</title>
+<span metal:fill-slot="body_title" tal:omit-tag="true">
+  <span tal:omit-tag="true" i18n:translate="" >List of bugs</span>
+  <span tal:condition="request/dispname"
+   tal:replace="python:' - %s' % request.dispname" />
+</span>
+<tal:block metal:fill-slot="content">
+
+<p tal:condition="python:not (context.is_view_ok()
+ or request.user.hasRole('Anonymous'))" i18n:translate="">
+ You are not allowed to view this page.</p>
+
+<p tal:condition="python:not context.is_view_ok()
+ and request.user.hasRole('Anonymous')" i18n:translate="">
+ Please login with your username and password.</p>
+
+<tal:block tal:define="batch request/batch" tal:condition="context/is_view_ok">
+ <table class="list">
+  <tr>
+   <th tal:condition="request/show/severity" i18n:translate="">Severity</th>
+   <th tal:condition="request/show/id" i18n:translate="">ID</th>
+   <th tal:condition="request/show/creation" i18n:translate="">Creation</th>
+   <th tal:condition="request/show/activity" i18n:translate="">Activity</th>
+   <th tal:condition="request/show/actor" i18n:translate="">Actor</th>
+   <th tal:condition="request/show/title" i18n:translate="">Title</th>
+   <th tal:condition="request/show/components"  i18n:translate="">Components</th>
+   <th tal:condition="request/show/versions" i18n:translate="">Versions</th>
+   <th tal:condition="request/show/status"    i18n:translate="">Status</th>
+   <th tal:condition="request/show/resolution" i18n:translate="">Resolution</th>
+   <th tal:condition="request/show/creator" i18n:translate="">Creator</th>
+   <th tal:condition="request/show/assignee" i18n:translate="">Assigned&nbsp;To</th>
+   <th tal:condition="request/show/keywords" i18n:translate="">Keywords</th>
+   <th tal:condition="request/show/dependencies" i18n:translate="">Depends On</th>
+   <th tal:condition="request/show/type" i18n:translate="">Type</th>
+  </tr>
+ <tal:block tal:repeat="i batch" condition="true">
+  <tr tal:define="group python:[r[1] for r in request.group]"
+      tal:condition="python:group and batch.propchanged(*group)">
+   <th tal:attributes="colspan python:len(request.columns)" class="group">
+    <tal:block tal:repeat="g group">
+     <tal:block tal:content="python:str(i[g]) or '(no %s set)'%g"/>
+    </tal:block>
+   </th>
+  </tr>
+
+  <tr tal:attributes="class python:['even','odd'][repeat['i'].even()]">
+   <td tal:condition="request/show/severity"
+       tal:content="python:i.severity.plain() or default">&nbsp;</td>
+   <td tal:condition="request/show/id" tal:content="i/id">&nbsp;</td>
+   <td class="date" tal:condition="request/show/creation"
+       tal:content="i/creation/reldate">&nbsp;</td>
+   <td class="date" tal:condition="request/show/activity"
+       tal:content="i/activity/reldate">&nbsp;</td>
+   <td tal:condition="request/show/actor"
+       tal:content="python:i.actor.plain() or default">&nbsp;</td>
+   <td tal:condition="request/show/title">
+    <a tal:attributes="href string:bug${i/id}"
+               tal:content="python:str(i.title.plain(hyperlink=0)) or '[no title]'">title</a>
+   </td>
+   <td tal:condition="request/show/components"
+       tal:content="python:i.components.plain() or default">&nbsp;</td>
+   <td tal:condition="request/show/versions"
+       tal:content="python:i.versions.plain() or default">&nbsp;</td>
+   <td tal:condition="request/show/status"
+       tal:content="python:i.status.plain() or default">&nbsp;</td>
+   <td tal:condition="request/show/resolution"
+       tal:content="python:i.resolution.plain() or default">&nbsp;</td>
+   <td tal:condition="request/show/creator"
+       tal:content="python:i.creator.plain() or default">&nbsp;</td>
+   <td tal:condition="request/show/assignee"
+       tal:content="python:i.assignee.plain() or default">&nbsp;</td>
+   <td tal:condition="request/show/keywords"
+       tal:content="python:i.keywords.plain() or default">&nbsp;</td>
+   <td tal:condition="request/show/dependencies"
+       tal:content="python:i.dependencies.plain() or default">&nbsp;</td>
+   <td tal:condition="request/show/type"
+       tal:content="python:i.type.plain() or default">&nbsp;</td>
+  </tr>
+
+ </tal:block>
+
+ <metal:index define-macro="batch-footer">
+ <tr tal:condition="batch">
+  <th tal:attributes="colspan python:len(request.columns)">
+   <table width="100%">
+    <tr class="navigation">
+     <th>
+      <a tal:define="prev batch/previous" tal:condition="prev"
+         tal:attributes="href python:request.indexargs_url(request.classname,
+         {'@startwith':prev.first, '@pagesize':prev.size})"
+         i18n:translate="">&lt;&lt; previous</a>
+      &nbsp;
+     </th>
+     <th i18n:translate=""><span tal:replace="batch/start" i18n:name="start"
+     />..<span tal:replace="python: batch.start + batch.length -1" i18n:name="end"
+     /> out of <span tal:replace="batch/sequence_length" i18n:name="total"
+     /></th>
+     <th>
+      <a tal:define="next batch/next" tal:condition="next"
+         tal:attributes="href python:request.indexargs_url(request.classname,
+         {'@startwith':next.first, '@pagesize':next.size})"
+         i18n:translate="">next &gt;&gt;</a>
+      &nbsp;
+     </th>
+    </tr>
+   </table>
+  </th>
+ </tr>
+ </metal:index>
+</table>
+<!--
+<a tal:attributes="href python:request.indexargs_url('bug',
+            {'@action':'export_csv'})" i18n:translate="">Download as CSV</a>
+-->
+<form method="get" class="index-controls"
+    tal:attributes="action request/classname">
+
+ <table class="form" tal:define="n_sort python:2">
+<!--
+<tal:block tal:repeat="n python:range(n_sort)" tal:condition="batch">
+  <tr tal:define="key python:len(request.sort)>n and request.sort[n]">
+   <th>
+    <tal:block tal:condition="not:n" i18n:translate="">Sort on:</tal:block>
+   </th>
+   <td>
+    <select tal:attributes="name python:'@sort%d'%n">
+     <option value="" i18n:translate="">- nothing -</option>
+     <option tal:repeat="col context/properties"
+             tal:attributes="value col/_name;
+                             selected python:key and col._name == key[1]"
+             tal:content="col/_name"
+             i18n:translate="">column</option>
+    </select>
+   </td>
+   <th i18n:translate="">Descending:</th>
+   <td><input type="checkbox" tal:attributes="name python:'@sortdir%d'%n;
+              checked python:key and key[0] == '-'"/>
+   </td>
+  </tr>
+  </tal:block>
+  -->
+  <tal:block tal:repeat="n python:range(n_sort)" tal:condition="batch">
+  <tr tal:define="key python:len(request.group)>n and request.group[n]">
+   <th>
+    <tal:block tal:condition="not:n" i18n:translate="">Group on:</tal:block>
+   </th>
+   <td>
+    <select tal:attributes="name python:'@group%d'%n">
+     <option value="" i18n:translate="">- nothing -</option>
+     <option tal:repeat="col context/properties"
+             tal:attributes="value col/_name;
+                             selected python:key and col._name == key[1]"
+             tal:content="col/_name"
+             i18n:translate="">column</option>
+    </select>
+   </td>
+   <th i18n:translate="">Descending:</th>
+   <td><input type="checkbox" tal:attributes="name python:'@groupdir%d'%n;
+              checked python:key and key[0] == '-'"/>
+   </td>
+  </tr>
+  </tal:block>
+  <tr><td colspan="4">
+              <input type="submit" value="Redisplay" i18n:attributes="value"/>
+              <tal:block tal:replace="structure
+                python:request.indexargs_form(sort=0, group=0)" />
+  </td></tr>
+ </table>
+</form>
+
+</tal:block>
+</tal:block>
+</tal:block>
diff --git a/share/roundup/templates/devel/html/bug.item.html b/share/roundup/templates/devel/html/bug.item.html
new file mode 100644 (file)
index 0000000..6fac895
--- /dev/null
@@ -0,0 +1,324 @@
+<tal:block metal:use-macro="templates/page/macros/frame">
+<title metal:fill-slot="head_title">
+<tal:block condition="context/id" i18n:translate=""
+ >Bug <span tal:replace="context/id" i18n:name="id"
+ />: <span tal:replace="context/title" i18n:name="title"
+ /> - <span tal:replace="config/TRACKER_NAME" i18n:name="tracker"
+/></tal:block>
+<tal:block condition="not:context/id" i18n:translate=""
+ >New Bug report - <span tal:replace="config/TRACKER_NAME" i18n:name="tracker"
+/></tal:block>
+</title>
+<tal:block metal:fill-slot="body_title">
+ <span tal:condition="python: not (context.id or context.is_edit_ok())"
+  tal:omit-tag="python:1" i18n:translate="">New Bug</span>
+ <span tal:condition="python: not context.id and context.is_edit_ok()"
+  tal:omit-tag="python:1" i18n:translate="">New Bug Editing</span>
+ <span tal:condition="python: context.id and not context.is_edit_ok()"
+  tal:omit-tag="python:1" i18n:translate="">Bug <tal:x
+  replace="context/id" i18n:name="id" /></span>
+ <span tal:condition="python: context.id and context.is_edit_ok()"
+  tal:omit-tag="python:1" i18n:translate="">Bug<tal:x
+  replace="context/id" i18n:name="id" /> Editing</span>
+</tal:block>
+
+<td class="content" metal:fill-slot="content">
+
+<p tal:condition="python:not (context.is_view_ok()
+ or request.user.hasRole('Anonymous'))" i18n:translate="">
+ You are not allowed to view this page.</p>
+
+<p tal:condition="python:not context.is_view_ok()
+ and request.user.hasRole('Anonymous')" i18n:translate="">
+ Please login with your username and password.</p>
+
+<div tal:condition="context/is_view_ok">
+
+<form method="POST" name="itemSynopsis"
+      onSubmit="return submit_once()" enctype="multipart/form-data"
+      tal:attributes="action context/designator">
+
+<fieldset><legend>classification</legend>
+<table class="form">
+<tr>
+ <th class="required" i18n:translate="">Title:</th>
+ <td colspan="3" tal:condition="context/title/is_edit_ok"
+     tal:content="structure python:context.title.field(size=40)">title</td>
+ <td colspan="3" tal:condition="not:context/title/is_edit_ok">
+  <span tal:content="structure context/title/plain"/>
+  <input type="hidden" name="title" tal:attributes="value context/title">
+ </td>
+</tr>
+
+<tr>
+ <th class="required" i18n:translate="">
+   <span tal:condition="context/type/is_edit_ok" 
+         tal:replace="structure python:db.bug_type.classhelp('id,name,description',property='type',label='Type')" />
+   <span tal:condition="not:context/type/is_edit_ok">Type</span>:
+ </th>
+ <td tal:content="structure context/type/menu">type</td>
+ <th i18n:translate="">
+   <span tal:condition="context/severity/is_edit_ok"
+         tal:replace="structure python:db.severity.classhelp('id,name,description',property='severity',label='Severity')" />
+   <span tal:condition="not:context/severity/is_edit_ok">Severity</span>:
+ </th>
+ <td tal:content="structure context/severity/menu">severity</td>
+</tr>
+
+<tr>
+ <th i18n:translate="">
+   <span tal:condition="context/components/is_edit_ok" 
+         tal:replace="structure python:db.component.classhelp('id,name,description',property='components',label='Components')" />
+   <span tal:condition="not:context/components/is_edit_ok">Components</span>:
+ </th>
+ <td tal:content="structure context/components/menu">components</td>
+ <th i18n:translate="">
+   <span tal:condition="context/versions/is_edit_ok" 
+         tal:replace="structure python:db.version.classhelp('id,name,description',property='versions',label='Versions')" />
+   <span tal:condition="not:context/versions/is_edit_ok">Versions</span>:
+ </th>
+ <td tal:content="structure context/versions/menu">versions</td>
+</tr>
+</table>
+</fieldset>
+
+<fieldset><legend>process</legend>
+<table class="form">
+<tr tal:condition="context/id">
+ <th i18n:translate="">
+   <span tal:condition="context/status/is_edit_ok" 
+         tal:replace="structure python:db.status.classhelp('id,name,description',property='status', label='Status')" />
+   <span tal:condition="not:context/status/is_edit_ok">Status</span>:
+ </th>
+ <td tal:content="structure context/status/menu">status</td>
+ <th i18n:translate="">
+   <span tal:condition="context/resolution/is_edit_ok" 
+         tal:replace="structure python:db.resolution.classhelp('id,name,description',property='resolution', label='Resolution')" />
+   <span tal:condition="not:context/resolution/is_edit_ok">Resolution</span>:
+ </th>
+ <td tal:content="structure context/resolution/menu">resolution</td>
+</tr>
+
+<tr tal:condition="context/id">
+ <th>
+  <tal:block i18n:translate="">Dependencies</tal:block>:
+  <span tal:condition="context/dependencies/is_edit_ok"
+        tal:replace="structure python:db.bug.classhelp('id,title', filter='status=0,1', property='dependencies')" />
+ </th>
+ <td>
+  <span tal:replace="structure python:context.dependencies.field(showid=1,size=20)" />
+  <span tal:condition="context/dependencies" tal:repeat="d python:context.dependencies.sorted('creation')">
+   <br/>View: <a tal:attributes="href string:bug${d/id}" tal:content="d/id"></a>
+  </span>
+ </td>
+ <th i18n:translate="">
+  <tal:block i18n:translate="">Superseder</tal:block>:
+  <span tal:condition="context/superseder/is_edit_ok"
+        tal:replace="structure python:db.bug.classhelp('id,title', filter='status=0,1', property='superseder')" />
+ </th>
+ <td>
+  <span tal:replace="structure python:context.superseder.field(showid=1, size=20)" />
+ <span tal:condition="context/superseder">
+<!--   <br><span i18n:translate="">View</span>:
+     <a tal:repeat="sup context/superseder"
+        tal:content="python:sup['id'] + ', '*(not repeat['sup'].end)"
+        tal:attributes="href string:bug${sup/id}; title sup/title;"></a> -->
+   <br><span i18n:translate="">View</span>:
+     <a tal:content="context/superseder/id"
+        tal:attributes="href string:bug${context/superseder/id}; title context/superseder/title;"></a> 
+  </span> 
+ </td>
+ </tr>
+ <tr>
+ <th><tal:block i18n:translate="">Assigned To</tal:block>:</th>
+ <td tal:content="structure context/assignee/menu">assignedto menu</td>
+ <th><tal:block i18n:translate="">Nosy List</tal:block>:
+  <span tal:condition="context/nosy/is_edit_ok"
+        tal:replace="structure python:db.user.classhelp('username,realname,address', property='nosy')" />
+ </th>
+ <td>
+  <span tal:replace="structure context/nosy/field" />
+ </td>
+</tr>
+<tr>
+ <th>
+   <span tal:condition="context/priority/is_edit_ok"
+         tal:replace="structure python:db.priority.classhelp('id,name,description',property='priority',label='Priority')" />
+   <span tal:condition="not:context/priority/is_edit_ok">Priority</span>:
+ </th>
+ <td tal:content="structure context/priority/menu">priority</td>
+ <th i18n:translate="">Keywords:</th>
+ <td tal:content="structure python:context['keywords'].menu(height=5)">keywords</td>
+
+
+</tr>
+<tr tal:condition="context/is_edit_ok">
+ <th><tal:block i18n:translate="">Comment</tal:block>:</th>
+ <td colspan="3">
+  <textarea tal:content="request/form/@note/value | default"
+            name="@note" wrap="hard" rows="10" cols="60"></textarea>
+ </td>
+</tr>
+
+<tr tal:condition="context/is_edit_ok">
+ <th><tal:block i18n:translate="">File</tal:block>:</th>
+ <td colspan="3">
+   <input type="hidden" name="@link@files" value="file-1">
+   <input type="file" name="file-1@content" size="35">
+ </td>
+</tr>
+<tr tal:condition="context/is_edit_ok">
+ <th><tal:block i18n:translate="">File Description</tal:block>:</th>
+ <td colspan=3><input type="edit" name="file-1@description" size="40"></td>
+</tr>
+
+<!--
+<tr tal:condition="context/is_edit_ok">
+ <th><tal:block i18n:translate="">Patch</tal:block>:</th>
+ <td colspan="3">
+   <input type="hidden" name="@link@patches" value="patch-1"/>
+   <input type="file" name="patch-1@content" size="35"/>
+ </td>
+</tr>
+<tr tal:condition="context/is_edit_ok">
+ <th><tal:block i18n:translate="">Patch Description</tal:block>:</th>
+ <td colspan=3><input type="edit" name="patch-1@description" size="40"></td>
+</tr>
+<tr tal:condition="context/is_edit_ok">
+ <th><tal:block i18n:translate="">Repository</tal:block>:</th>
+ <td colspan=3><input type="edit" name="patch-1@repository" size="40"></td>
+</tr>
+<tr tal:condition="context/is_edit_ok">
+ <th><tal:block i18n:translate="">Repo. Revision</tal:block>:</th>
+ <td colspan=3><input type="edit" name="patch-1@revision" size="40"></td>
+</tr>
+-->
+</table>
+</fieldset>
+<table class="form">
+<tr tal:condition="context/is_edit_ok">
+ <td>
+  &nbsp;
+  <input type="hidden" name="@template" value="item">
+  <input type="hidden" name="@required" value="title">
+ </td>
+ <td colspan=3>
+  <span tal:replace="structure context/submit">submit button</span>
+  <a tal:condition="context/id" tal:attributes="href context/copy_url"
+   i18n:translate="">Make a copy</a>
+ </td>
+</tr>
+</table>
+</form>
+
+<p tal:condition="context/id" i18n:translate="">
+ Created on <b><tal:x replace="python:context.creation.pretty('%Y-%m-%d %H:%M')" i18n:name="creation" /></b>
+ by <b><tal:x replace="context/creator" i18n:name="creator" /></b>,
+ last changed <b><tal:x replace="python:context.activity.pretty('%Y-%m-%d %H:%M')" i18n:name="activity" /></b>
+ by <b><tal:x replace="context/actor" i18n:name="actor" /></b>.
+</p>
+
+<table class="files" tal:condition="context/files">
+ <tr><th colspan="5" class="header" i18n:translate="">Files</th></tr>
+ <tr>
+  <th i18n:translate="">File name</th>
+  <th i18n:translate="">Uploaded</th>
+  <th i18n:translate="">Description</th>
+  <th i18n:translate="">Edit</th>
+  <th i18n:translate="">Remove</th>
+ </tr>
+ <tr tal:repeat="file python:context.files.sorted('creation')">
+  <td>
+   <a tal:attributes="href file/download_url"
+      tal:content="file/name">dld link</a>
+  </td>
+  <td>
+   <span tal:content="file/creator">creator's name</span>,
+   <span tal:content="python:file.creation.pretty('%Y-%m-%d %H:%M')">creation date</span>
+  </td>
+  <td tal:content="file/description" />
+  <td><a tal:condition="file/is_edit_ok"
+          tal:attributes="href string:file${file/id}">edit</a>
+  </td>
+  <td>
+   <form style="padding:0" tal:condition="file/is_edit_ok"
+         tal:attributes="action string:bug${context/id}">
+    <input type="hidden" name="@remove@files" tal:attributes="value file/id">
+    <input type="hidden" name="@action" value="edit">
+    <input type="submit" value="remove" i18n:attributes="value">
+   </form>
+  </td>
+ </tr>
+</table>
+<!--
+<table class="files" tal:condition="context/patches">
+ <tr><th colspan="5" class="header" i18n:translate="">Patches</th></tr>
+ <tr>
+  <th i18n:translate="">Patch name</th>
+  <th i18n:translate="">Uploaded</th>
+  <th i18n:translate="">Description</th>
+  <th i18n:translate="">View</th>
+  <th i18n:translate="">Edit</th>
+  <th i18n:translate="">Remove</th>
+ </tr>
+ <tr tal:repeat="patch python:context.patches.sorted('creation')">
+  <td>
+   <a tal:attributes="href patch/download_url"
+      tal:content="patch/name">dld link</a>
+  </td>
+  <td>
+   <span tal:content="patch/creator">creator's name</span>,
+   <span tal:content="python:patch.creation.pretty('%Y-%m-%d %H:%M')">creation date</span>
+  </td>
+  <td tal:content="patch/description" />
+  <td><a tal:attributes="href string:patch_view${patch/id}">Show patch</a>
+  </td>
+  <td><a tal:condition="patch/is_edit_ok"
+          tal:attributes="href string:patch${patch/id}">Edit</a>
+  </td>
+  <td>
+   <form style="padding:0" tal:condition="patch/is_edit_ok"
+         tal:attributes="action string:bug${context/id}">
+    <input type="hidden" name="@remove@patches" tal:attributes="value patch/id"/>
+    <input type="hidden" name="@action" value="edit"/>
+    <input type="submit" value="remove" i18n:attributes="value"/>
+   </form>
+  </td>
+ </tr>
+</table>
+-->
+<table class="messages" tal:condition="context/messages">
+ <tr><th colspan="4" class="header" i18n:translate="">Messages</th></tr>
+ <tal:block tal:repeat="msg context/messages">
+  <tr>
+   <th><a tal:attributes="href string:msg${msg/id}"
+    i18n:translate="">msg<tal:x replace="msg/id" i18n:name="id" /></a></th>
+   <th i18n:translate="">Author: <tal:x replace="python:msg.author.realname.plain()"
+       i18n:name="author" /> (<tal:x replace="msg/author"/>)</th>
+   <th i18n:translate="">Date: <tal:x replace="python:msg.date.pretty('%Y-%m-%d %H:%M')"
+       i18n:name="date" /></th>
+   <th>
+    <form style="padding:0" tal:condition="msg/is_edit_ok"
+          tal:attributes="action string:bug${context/id}">
+     <input type="hidden" name="@remove@messages" tal:attributes="value msg/id">
+     <input type="hidden" name="@action" value="edit">
+     <input type="submit" value="remove" i18n:attributes="value">
+    </form>
+   </th>
+  </tr>
+  <tr>
+   <td colspan="4" class="content">
+    <pre tal:condition="python:msg.content.is_view_ok()"
+         tal:content="structure python:utils.localReplace(msg.content.hyperlinked())">content</pre>
+   </td>
+  </tr>
+ </tal:block>
+</table>
+
+<tal:block tal:condition="context/id" tal:replace="structure context/history" />
+
+</div>
+
+</td>
+
+</tal:block>
diff --git a/share/roundup/templates/devel/html/bug.search.html b/share/roundup/templates/devel/html/bug.search.html
new file mode 100644 (file)
index 0000000..4bfd665
--- /dev/null
@@ -0,0 +1,311 @@
+<tal:block metal:use-macro="templates/page/macros/frame">
+<title metal:fill-slot="head_title" i18n:translate="">Bug searching - <span
+ i18n:name="tracker" tal:replace="config/TRACKER_NAME" /></title>
+<span metal:fill-slot="body_title" tal:omit-tag="python:1"
+ i18n:translate="">Bug searching</span>
+<td class="content" metal:fill-slot="content">
+
+<form method="get" name="itemSynopsis"
+      tal:attributes="action request/classname">
+      
+<table class="form" tal:define="
+   cols python:request.columns or 'id activity title status assignedto'.split();
+   sort_on python:request.sort and request.sort[0] or nothing;
+   sort_desc python:sort_on and sort_on[0] == '-';
+   sort_on python:(sort_on and sort_on[1]) or 'activity';
+   group_on python:request.group and request.group[0] or nothing;
+   group_desc python:group_on and group_on[0] == '-';
+   group_on python:(group_on and group_on[1]) or 'priority';
+
+   search_input templates/page/macros/search_input;
+   search_date templates/page/macros/search_date;
+   column_input templates/page/macros/column_input;
+   sort_input templates/page/macros/sort_input;
+   group_input templates/page/macros/group_input;
+   search_select templates/page/macros/search_select;
+   search_select_translated templates/page/macros/search_select_translated;
+   search_multiselect templates/page/macros/search_multiselect;">
+
+<tr>
+ <th class="header">&nbsp;</th>
+ <th class="header" i18n:translate="">Filter on</th>
+ <th class="header" i18n:translate="">Display</th>
+ <th class="header" i18n:translate="">Sort on</th>
+ <th class="header" i18n:translate="">Group on</th>
+</tr>
+
+<tr tal:define="name string:@search_text">
+  <th i18n:translate="">All text*:</th>
+  <td metal:use-macro="search_input"></td>
+  <td>&nbsp;</td>
+  <td>&nbsp;</td>
+  <td>&nbsp;</td>
+</tr>
+
+<tr tal:define="name string:title">
+  <th i18n:translate="">Title:</th>
+  <td metal:use-macro="search_input"></td>
+  <td metal:use-macro="column_input"></td>
+  <td metal:use-macro="sort_input"></td>
+  <td>&nbsp;</td>
+</tr>
+
+<tr tal:define="name string:id">
+  <th i18n:translate="">ID:</th>
+  <td metal:use-macro="search_input"></td>
+  <td metal:use-macro="column_input"></td>
+  <td metal:use-macro="sort_input"></td>
+  <td>&nbsp;</td>
+</tr>
+
+<tr tal:define="name string:creation">
+  <th i18n:translate="">Creation Date:</th>
+  <td metal:use-macro="search_date"></td>
+  <td metal:use-macro="column_input"></td>
+  <td metal:use-macro="sort_input"></td>
+  <td metal:use-macro="group_input"></td>
+</tr>
+
+<tr tal:define="name string:creator;
+                db_klass string:user;
+                db_content string:username;">
+  <th i18n:translate="">Creator:</th>
+  <td metal:use-macro="search_input">
+    <option metal:fill-slot="extra_options" i18n:translate=""
+            tal:attributes="value request/user/id">created by me</option>
+  </td>
+  <td metal:use-macro="column_input"></td>
+  <td metal:use-macro="sort_input"></td>
+  <td metal:use-macro="group_input"></td>
+</tr>
+
+<tr tal:define="name string:activity">
+  <th i18n:translate="">Activity:</th>
+  <td metal:use-macro="search_date"></td>
+  <td metal:use-macro="column_input"></td>
+  <td metal:use-macro="sort_input"></td>
+  <td>&nbsp;</td>
+</tr>
+
+<tr tal:define="name string:actor;
+                db_klass string:user;
+                db_content string:username;">
+  <th i18n:translate="">Last actor:</th>
+  <td metal:use-macro="search_input">
+    <option metal:fill-slot="extra_options" i18n:translate=""
+            tal:attributes="value request/user/id">done by me</option>
+  </td>
+  <td metal:use-macro="column_input"></td>
+  <td metal:use-macro="sort_input"></td>
+  <td>&nbsp;</td>
+</tr>
+
+<tr tal:define="name string:nosy">
+  <th i18n:translate="">Nosy list member:</th>
+  <td metal:use-macro="search_input"></td>
+  <td metal:use-macro="column_input"></td>
+  <td metal:use-macro="sort_input"></td>
+  <td>&nbsp;</td>
+</tr>
+
+<tr tal:define="name string:type;
+                db_klass string:bug_type;
+                db_content string:name;">
+  <th i18n:translate="">Type:</th>
+  <td metal:use-macro="search_select_translated">
+    <option metal:fill-slot="extra_options" value="-1" i18n:translate=""
+            tal:attributes="selected python:value == '-1'">not selected</option>
+  </td>
+  <td metal:use-macro="column_input"></td>
+  <td metal:use-macro="sort_input"></td>
+  <td metal:use-macro="group_input"></td>
+</tr>
+
+<tr tal:define="name string:components;
+                db_klass string:component;
+                db_content string:name;">
+  <th i18n:translate="">Components:</th>
+  <td metal:use-macro="search_select_translated">
+    <option metal:fill-slot="extra_options" value="-1" i18n:translate=""
+            tal:attributes="selected python:value == '-1'">not selected</option>
+  </td>
+  <td metal:use-macro="column_input"></td>
+  <td metal:use-macro="sort_input"></td>
+  <td metal:use-macro="group_input"></td>
+</tr>
+
+<tr tal:define="name string:versions;
+                db_klass string:version;
+                db_content string:name;">
+  <th i18n:translate="">Versions:</th>
+  <td metal:use-macro="search_select_translated">
+    <option metal:fill-slot="extra_options" value="-1" i18n:translate=""
+            tal:attributes="selected python:value == '-1'">not selected</option>
+  </td>
+  <td metal:use-macro="column_input"></td>
+  <td metal:use-macro="sort_input"></td>
+  <td metal:use-macro="group_input"></td>
+</tr>
+
+<!--tr tal:define="name string:severity;
+                db_klass string:severity;
+                db_content string:name;">
+  <th i18n:translate="">Severity:</th>
+  <td metal:use-macro="search_select_translated">
+    <option metal:fill-slot="extra_options" value="-1" i18n:translate=""
+            tal:attributes="selected python:value == '-1'">not selected</option>
+  </td>
+  <td metal:use-macro="column_input"></td>
+  <td metal:use-macro="sort_input"></td>
+  <td metal:use-macro="group_input"></td>
+</tr-->
+
+<tr tal:define="name string:dependencies;
+                db_klass string:bug;
+                db_content string:id;">
+  <th i18n:translate="">Depends on:</th>
+  <td metal:use-macro="search_input"/>
+  <td metal:use-macro="column_input"></td>
+  <td metal:use-macro="sort_input"></td>
+  <td metal:use-macro="group_input"></td>
+</tr>
+
+<tr tal:define="name string:assignee;
+                db_klass string:user;
+                db_content string:username;">
+  <th i18n:translate="">Assigned to:</th>
+  <td metal:use-macro="search_input">
+    <tal:block metal:fill-slot="extra_options">
+      <option tal:attributes="value request/user/id"
+       i18n:translate="">assigned to me</option>
+      <option value="-1" tal:attributes="selected python:value == '-1'"
+       i18n:translate="">unassigned</option>
+    </tal:block>
+  </td>
+  <td metal:use-macro="column_input"></td>
+  <td metal:use-macro="sort_input"></td>
+  <td metal:use-macro="group_input"></td>
+</tr>
+
+<tr tal:define="name string:keywords;
+                db_klass string:keyword;
+                db_content string:name;">
+  <th i18n:translate="">Keyword:</th>
+  <td metal:use-macro="search_select_translated">
+    <option metal:fill-slot="extra_options" value="-1" i18n:translate=""
+            tal:attributes="selected python:value == '-1'">not set</option>
+  </td>
+  <td metal:use-macro="column_input"></td>
+  <td metal:use-macro="sort_input"></td>
+  <td metal:use-macro="group_input"></td>
+</tr>
+
+<tr tal:define="name string:priority;
+                db_klass string:priority;
+                db_content string:name;">
+  <th i18n:translate="">Priority:</th>
+  <td metal:use-macro="search_select_translated">
+    <option metal:fill-slot="extra_options" value="-1" i18n:translate=""
+            tal:attributes="selected python:value == '-1'">not selected</option>
+  </td>
+  <td metal:use-macro="column_input"></td>
+  <td metal:use-macro="sort_input"></td>
+  <td metal:use-macro="group_input"></td>
+</tr>
+
+<tr tal:define="name string:status;
+                db_klass string:status;
+                db_content string:name;">
+  <th i18n:translate="">Status:</th>
+  <td metal:use-macro="search_select_translated">
+    <tal:block metal:fill-slot="extra_options">
+      <option value="-1,1,2" i18n:translate=""
+              tal:attributes="selected python:value == '-1,1,2'">not closed</option>
+      <option value="-1" i18n:translate=""
+              tal:attributes="selected python:value == '-1'">not selected</option>
+    </tal:block>
+  </td>
+  <td metal:use-macro="column_input"></td>
+  <td metal:use-macro="sort_input"></td>
+  <td metal:use-macro="group_input"></td>
+</tr>
+
+<tr tal:define="name string:resolution;
+                db_klass string:resolution;
+                db_content string:name;">
+  <th i18n:translate="">Resolution:</th>
+  <td metal:use-macro="search_select_translated"/>
+  <td metal:use-macro="column_input"></td>
+  <td metal:use-macro="sort_input"></td>
+  <td metal:use-macro="group_input"></td>
+</tr>
+
+<tr>
+ <th i18n:translate="">No Sort or group:</th>
+ <td>&nbsp;</td>
+ <td>&nbsp;</td>
+ <td><input type="radio" name="@sort" value=""/></td>
+ <td><input type="radio" name="@group" value=""/></td>
+</tr>
+
+<tr>
+<th i18n:translate="">Pagesize:</th>
+<td><input name="@pagesize" size="3" value="50"
+           tal:attributes="value request/form/@pagesize/value | default"/></td>
+</tr>
+
+<tr>
+<th i18n:translate="">Start With:</th>
+<td><input name="@startwith" size="3" value="0"
+           tal:attributes="value request/form/@startwith/value | default"/></td>
+</tr>
+
+<tr>
+<th i18n:translate="">Sort Descending:</th>
+<td><input type="checkbox" name="@sortdir"
+           tal:attributes="checked sort_desc"/>
+</td>
+</tr>
+
+<tr>
+<th i18n:translate="">Group Descending:</th>
+<td><input type="checkbox" name="@groupdir"
+           tal:attributes="checked group_desc"/>
+</td>
+</tr>
+
+<tr tal:condition="python:request.user.hasPermission('Edit', 'query')">
+ <th i18n:translate="">Query name**:</th>
+ <td tal:define="value request/form/@queryname/value | nothing">
+  <input name="@queryname" tal:attributes="value value"/>
+  <input type="hidden" name="@old-queryname" tal:attributes="value value"/>
+ </td>
+</tr>
+
+<tr>
+  <td>
+   &nbsp;
+   <input type="hidden" name="@action" value="search"/>
+  </td>
+  <td><input type="submit" value="Search" i18n:attributes="value"/></td>
+</tr>
+
+<tr><td>&nbsp;</td>
+ <td colspan="4" class="help">
+  <span i18n:translate="" tal:omit-tag="true">
+   *: The "all text" field will look in message bodies and bug titles
+  </span><br>
+  <span tal:condition="python:request.user.hasPermission('Edit', 'query')"
+   i18n:translate="" tal:omit-tag="true"
+  >
+   **: If you supply a name, the query will be saved off and available as a
+       link in the sidebar
+  </span>
+ </td>
+</tr>
+</table>
+
+</form>
+</td>
+
+</tal:block>
diff --git a/share/roundup/templates/devel/html/favicon.ico b/share/roundup/templates/devel/html/favicon.ico
new file mode 100644 (file)
index 0000000..c9efc58
Binary files /dev/null and b/share/roundup/templates/devel/html/favicon.ico differ
diff --git a/share/roundup/templates/devel/html/file.index.html b/share/roundup/templates/devel/html/file.index.html
new file mode 100644 (file)
index 0000000..7072172
--- /dev/null
@@ -0,0 +1,32 @@
+<tal:block metal:use-macro="templates/page/macros/frame">
+<title metal:fill-slot="head_title" i18n:translate=""
+ >List of files - <span tal:replace="config/TRACKER_NAME" i18n:name="tracker" /></title>
+<span metal:fill-slot="body_title" tal:omit-tag="python:1"
+  i18n:translate="">List of files</span>
+<td class="content" metal:fill-slot="content">
+
+<table class="otherinfo" tal:define="batch request/batch">
+ <tr><th style="padding-right: 10" i18n:translate="">Download</th>
+     <th style="padding-right: 10" i18n:translate="">Descriptoin</th>
+     <th style="padding-right: 10" i18n:translate="">Content Type</th>
+     <th style="padding-right: 10" i18n:translate="">Uploaded By</th>
+     <th style="padding-right: 10" i18n:translate="">Date</th>
+ </tr>
+ <tr tal:repeat="file batch" tal:attributes="class python:['normal', 'alt'][repeat['file'].index%6/3]">
+  <td>
+   <a tal:attributes="href string:file${file/id}/${file/name}"
+      tal:content="file/name">dld link</a>
+  </td>
+  <td tal:content="file/description">description</td>
+  <td tal:content="file/type">content type</td>
+  <td tal:content="file/creator">creator's name</td>
+  <td tal:content="file/creation">creation date</td>
+ </tr>
+
+ <metal:block use-macro="templates/issue.index/macros/batch-footer" />
+
+</table>
+
+</td>
+
+</tal:block>
diff --git a/share/roundup/templates/devel/html/file.item.html b/share/roundup/templates/devel/html/file.item.html
new file mode 100644 (file)
index 0000000..d6f4f7f
--- /dev/null
@@ -0,0 +1,78 @@
+<tal:block metal:use-macro="templates/page/macros/frame">
+<title metal:fill-slot="head_title" i18n:translate="">File display - <span
+ i18n:name="tracker" tal:replace="config/TRACKER_NAME" /></title>
+<span metal:fill-slot="body_title" tal:omit-tag="python:1"
+ i18n:translate="">File display</span>
+
+<td class="content" metal:fill-slot="content">
+
+<p tal:condition="python:not (context.is_view_ok()
+ or request.user.hasRole('Anonymous'))" i18n:translate="">
+ You are not allowed to view this page.</p>
+
+<p tal:condition="python:not context.is_view_ok()
+ and request.user.hasRole('Anonymous')" i18n:translate="">
+ Please login with your username and password.</p>
+
+<form method="POST" onSubmit="return submit_once()"
+      enctype="multipart/form-data" tal:condition="context/is_view_ok"
+      tal:attributes="action context/designator">
+
+<table class="form">
+ <tr>
+  <th i18n:translate="">Name</th>
+  <td tal:content="structure context/name/field"></td>
+ </tr>
+ <tr>
+  <th i18n:translate="">Description</th>
+  <td tal:content="structure context/description/field"></td>
+ </tr>
+ <tr>
+  <th i18n:translate="">Content Type</th>
+  <td tal:content="structure context/type/field"/>
+  <td style="border: none" tal:condition="python: context.is_edit_ok()">Please note that
+  for security reasons, it's not permitted to set content type to <i>text/html</i>.</td>
+ </tr>
+ <tr tal:condition="python:context.is_edit_ok()">
+  <td>
+   &nbsp;
+   <input type="hidden" name="@template" value="item">
+   <input type="hidden" name="@required" value="name,type">
+   <input type="hidden" name="@multilink"
+          tal:condition="python:request.form.has_key('@multilink')"
+          tal:attributes="value request/form/@multilink/value">
+  </td>
+  <td tal:content="structure context/submit">submit button here</td>
+ </tr>
+</table>
+</form>
+
+<!--<p tal:condition="python:utils.sb_is_spam(context)" class="error-message">
+   File has been classified as spam.</p>-->
+
+<a tal:condition="python:context.id and context.content.is_view_ok()"
+ tal:attributes="href string:file${context/id}/${context/name}"
+ i18n:translate="">download</a>
+
+<!--<p tal:condition="python:context.id and not context.content.is_view_ok()">
+   Files classified as spam are not available for download by
+   unathorized users. If you think the file has been misclassified,
+   please login and click on the button for reclassification.
+</p>-->
+
+
+<!--     <form method="POST" onSubmit="return submit_once()"
+       enctype="multipart/form-data"
+       tal:attributes="action context/designator"
+       tal:condition="python:request.user.hasPermission('SB: May Classify')">
+      <input type="hidden" name="@action" value="spambayes_classify">
+      <input type="submit" name="trainspam" value="Mark as SPAM" i18n:attributes="value">
+      <input type="submit" name="trainham" value="Mark as HAM (not SPAM)" i18n:attributes="value">
+     </form>-->
+
+<tal:block tal:condition="context/id" tal:replace="structure context/history" />
+
+</td>
+
+</tal:block>
diff --git a/share/roundup/templates/devel/html/help.css b/share/roundup/templates/devel/html/help.css
new file mode 100644 (file)
index 0000000..31f4ab1
--- /dev/null
@@ -0,0 +1,29 @@
+@import url(style.css);
+
+body { padding: 0;}
+body > .header,
+body > .footer
+{
+  margin: 0;
+}
+body .content { width: auto;}
+.header h1 { font-size: 110%; }
+
+table.classhelp th
+{
+  border-top: 1px solid #afafaf;
+  border-bottom: 1px solid #afafaf;
+  empty-cells: show;
+  text-align: left;
+  vertical-align: middle;
+  white-space: nowrap;
+}
+
+table.classhelp td
+{
+  border-bottom: 1px solid #efefef;
+  empty-cells: show;
+  text-align: left;
+  vertical-align: middle;
+  white-space: nowrap;
+}
diff --git a/share/roundup/templates/devel/html/help.html b/share/roundup/templates/devel/html/help.html
new file mode 100644 (file)
index 0000000..ba7f461
--- /dev/null
@@ -0,0 +1,69 @@
+<tal:block metal:define-macro="frame">
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html tal:define="what python:request.form.getvalue('property', request.classname);
+                  form request/form/form/value;
+                  field request/form/property/value">
+<head>
+  <meta http-equiv="Content-Type"
+        tal:attributes="content string:text/html;; charset=${request/client/charset}" />
+  <title i18n:translate="">
+    <tal:x i18n:name="what" tal:content="what" i18n:translate="" />
+    help - <span i18n:name="tracker" tal:replace="config/TRACKER_NAME" />
+  </title>
+  <script language="Javascript" type="text/javascript" tal:content="structure string:<!--
+    // this is the name of the field in the original form that we're working on
+    form  = window.opener.document.${form};
+    field  = '${field}';
+    //-->">
+  </script>
+  <link rel="stylesheet" type="text/css" href="@@file/help.css" />
+  <script src="@@file/help_controls.js" type="text/javascript"></script>
+  <metal:x define-slot="more-javascript" />
+</head>
+<body onload="resetList();">
+  <div class="header">
+    <h1>Roundup Issue Tracker: Help - <tal:block tal:content="what">item</tal:block></h1>
+  </div>
+  <div class="content">
+    <tal:block metal:define-slot="content">Page content goes here</tal:block>
+  </div> <!-- content -->
+  <div class="footer">
+    <tal:block metal:define-slot="footer"></tal:block>
+  </div> <!-- footer -->
+</body>
+</html>
+</tal:block>
+
+<!-- legend for helper search results -->
+<thead>
+<tr metal:define-macro="legend">
+  <th><b>x</b></th>
+  <th tal:repeat="prop props" tal:content="prop" i18n:translate=""></th>
+</tr>
+</thead>
+
+<table width="100%"
+       metal:define-macro="batch_navi"
+       tal:define="prev batch/previous;
+                   next batch/next;"
+       tal:condition="python:prev or next">
+  <tr class="navigation">
+   <th width="30%">
+    <a tal:condition="prev"
+       tal:attributes="href python:request.indexargs_url(request.classname, {'@template':'help-list', 'property': property, 'properties': request.form['properties'].value, 'form': request.form['form'].value, '@startwith':prev.first, '@pagesize':prev.size})"
+       i18n:translate="" >&lt;&lt; previous</a>
+    &nbsp;
+   </th>
+   <th i18n:translate="" width="40%"><span tal:replace="batch/start" i18n:name="start"
+    />..<span tal:replace="python: batch.start + batch.length -1" i18n:name="end"
+    /> out of <span tal:replace="batch/sequence_length" i18n:name="total"
+    />
+   </th>
+   <th width="30%">
+    <a tal:condition="next"
+       tal:attributes="href python:request.indexargs_url(request.classname, {'@template':'help-list', 'property': property, 'properties': request.form['properties'].value, 'form': request.form['form'].value, '@startwith':next.first, '@pagesize':next.size})"
+       i18n:translate="" >next &gt;&gt;</a>
+    &nbsp;
+   </th>
+  </tr>
+</table>
diff --git a/share/roundup/templates/devel/html/help_controls.js b/share/roundup/templates/devel/html/help_controls.js
new file mode 100644 (file)
index 0000000..775b578
--- /dev/null
@@ -0,0 +1,324 @@
+// initial values for either Nosy, Superseder, Topic and Waiting On,
+// depending on which has called
+original_field = form[field].value;
+
+// Some browsers (ok, IE) don't define the "undefined" variable.
+undefined = document.geez_IE_is_really_friggin_annoying;
+
+function trim(value) {
+  var temp = value;
+  var obj = /^(\s*)([\W\w]*)(\b\s*$)/;
+  if (obj.test(temp)) { temp = temp.replace(obj, '$2'); }
+  var obj = /  /g;
+  while (temp.match(obj)) { temp = temp.replace(obj, " "); }
+  return temp;
+}
+
+function determineList() {
+     // generate a comma-separated list of the checked items
+     var list = new String('');
+
+     // either a checkbox object or an array of checkboxes
+     var check = document.frm_help.check;
+
+     if ((check.length == undefined) && (check.checked != undefined)) {
+         // only one checkbox on page
+         if (check.checked) {
+             list = check.value;
+         }
+     } else {
+         // array of checkboxes
+         for (box=0; box < check.length; box++) {
+             if (check[box].checked) {
+                 if (list.length == 0) {
+                     separator = '';
+                 }
+                 else {
+                     separator = ',';
+                 }
+                 // we used to use an Array and push / join, but IE5.0 sux
+                 list = list + separator + check[box].value;
+             }
+         }
+     }
+     return list;
+}
+
+/**
+ * update the field in the opening window;
+ * the text_field variable must be set in the calling page
+ */
+function updateOpener() {
+  // write back to opener window
+  if (document.frm_help.check==undefined) { return; }
+  form[field].value = text_field.value;
+}
+
+function updateList() {
+  // write back to opener window
+  if (document.frm_help.check==undefined) { return; }
+  form[field].value = determineList();
+}
+
+function updatePreview() {
+  // update the preview box
+  if (document.frm_help.check==undefined) { return; }
+  writePreview(determineList());
+}
+
+function clearList() {
+  // uncheck all checkboxes
+  if (document.frm_help.check==undefined) { return; }
+  for (box=0; box < document.frm_help.check.length; box++) {
+      document.frm_help.check[box].checked = false;
+  }
+}
+
+function reviseList_framed(form, textfield) {
+  // update the checkboxes based on the preview field
+  // alert('reviseList_framed')
+  // alert(form)
+  if (form.check==undefined)
+      return;
+  // alert(textfield)
+  var to_check;
+  var list = textfield.value.split(",");
+  if (form.check.length==undefined) {
+      check = form.check;
+      to_check = false;
+      for (val in list) {
+          if (check.value==trim(list[val])) {
+              to_check = true;
+              break;
+          }
+      }
+      check.checked = to_check;
+  } else {
+    for (box=0; box < form.check.length; box++) {
+      check = form.check[box];
+      to_check = false;
+      for (val in list) {
+          if (check.value==trim(list[val])) {
+              to_check = true;
+              break;
+          }
+      }
+      check.checked = to_check;
+    }
+  }
+}
+
+function reviseList(vals) {
+  // update the checkboxes based on the preview field
+  if (document.frm_help.check==undefined) { return; }
+  var to_check;
+  var list = vals.split(",");
+  if (document.frm_help.check.length==undefined) {
+      check = document.frm_help.check;
+      to_check = false;
+      for (val in list) {
+          if (check.value==trim(list[val])) {
+              to_check = true;
+              break;
+          }
+      }
+      check.checked = to_check;
+  } else {
+    for (box=0; box < document.frm_help.check.length; box++) {
+      check = document.frm_help.check[box];
+      to_check = false;
+      for (val in list) {
+          if (check.value==trim(list[val])) {
+              to_check = true;
+              break;
+          }
+      }
+      check.checked = to_check;
+    }
+  }
+}
+
+function resetList() {
+  // reset preview and check boxes to initial values
+  if (document.frm_help.check==undefined) { return; }
+  writePreview(original_field);
+  reviseList(original_field);
+}
+
+function writePreview(val) {
+   // writes a value to the text_preview
+   document.frm_help.text_preview.value = val;
+}
+
+function focusField(name) {
+    for(i=0; i < document.forms.length; ++i) {
+      var obj = document.forms[i].elements[name];
+      if (obj && obj.focus) {obj.focus();}
+    }
+}
+
+function selectField(name) {
+    for(i=0; i < document.forms.length; ++i) {
+      var obj = document.forms[i].elements[name];
+      if (obj && obj.focus){obj.focus();}
+      if (obj && obj.select){obj.select();}
+    }
+}
+
+function checkRequiredFields(fields)
+{
+    var bonk='';
+    var res='';
+    var argv = checkRequiredFields.arguments;
+    var argc = argv.length;
+    var input = '';
+    var val='';
+
+    for (var i=0; i < argc; i++) {
+        fi = argv[i];
+        input = document.getElementById(fi);
+        if (input) {
+            val = input.value
+            if (val == '' || val == '-1' || val == -1) {
+                if (res == '') {
+                    res = fi;
+                    bonk = input;
+                } else {
+                    res += ', '+fi;
+                }
+            }
+        } else {
+            alert('Field with id='+fi+' not found!')
+        }
+    }
+    if (res == '') {
+        return submit_once();
+    } else {
+        alert('Missing value here ('+res+')!');
+        if (window.event && window.event.returnvalue) {
+            event.returnValue = 0;    // work-around for IE
+        }
+        bonk.focus();
+        return false;
+    }
+}
+
+/**
+ * seeks the given value (2nd argument)
+ * in the value of the given input element (1st argument),
+ * which is considered a list of values, separated by commas
+ */
+function has_value(input, val)
+{
+    var actval = input.value
+    var arr = feld.value.split(',');
+    var max = arr.length;
+    for (i=0;i<max;i++) {
+        if (trim(arr[i]) == val) {
+            return true
+        }
+    }
+    return false
+}
+
+/**
+ * Switch Value:
+ * change the value of the given input field (might be of type text or hidden),
+ * adding or removing the value of the given checkbox field (might be a radio
+ * button as well)
+ *
+ * This function doesn't care whether or not the checkboxes of all values of
+ * interest are present; but of course it doesn't have total control of the
+ * text field.
+ */
+function switch_val(text, check)
+{
+    var switched_val = check.value
+    var arr = text.value.split(',')
+    var max = arr.length
+    if (check.checked) {
+        for (i=0; i<max; i++) {
+            if (trim(arr[i]) == switched_val) {
+                return
+            }
+        }
+       if (text.value)
+            text.value = text.value+','+switched_val
+       else
+            text.value = switched_val
+    } else {
+        var neu = ''
+       var changed = false
+        for (i=0; i<max; i++) {
+            if (trim(arr[i]) == switched_val) {
+                changed=true
+            } else {
+                neu = neu+','+trim(arr[i])
+            }
+        }
+        if (changed) {
+            text.value = neu.substr(1)
+        }
+    }
+}
+
+/**
+ * append the given value (2nd argument) to an input field
+ * (1st argument) which contains comma-separated values;
+ * see --> remove_val()
+ *
+ * This will work nicely even for batched lists
+ */
+function append_val(name, val)
+{
+    var feld = document.itemSynopsis[name];
+    var actval = feld.value;
+    if (actval == '') {
+        feld.value = val
+    } else {
+        var arr = feld.value.split(',');
+        var max = arr.length;
+        for (i=0;i<max;i++) {
+            if (trim(arr[i]) == val) {
+                return
+            }
+        }
+        feld.value = actval+','+val
+    }
+}
+
+/**
+ * remove the given value (2nd argument) from the comma-separated values
+ * of the given input element (1st argument); see --> append_val()
+ */
+function remove_val(name, val)
+{
+    var feld = document.itemSynopsis[name];
+    var actval = feld.value;
+    var changed=false;
+    if (actval == '') {
+       return
+    } else {
+        var arr = feld.value.split(',');
+        var max = arr.length;
+        var neu = ''
+        for (i=0;i<max;i++) {
+            if (trim(arr[i]) == val) {
+                changed=true
+            } else {
+                neu = neu+','+trim(arr[i])
+            }
+        }
+        if (changed) {
+            feld.value = neu.substr(1)
+        }
+    }
+}
+
+/**
+ * give the focus to the element given by id
+ */
+function focus2id(name)
+{
+    document.getElementById(name).focus();
+}
diff --git a/share/roundup/templates/devel/html/home.classlist.html b/share/roundup/templates/devel/html/home.classlist.html
new file mode 100644 (file)
index 0000000..61ecbfc
--- /dev/null
@@ -0,0 +1,25 @@
+<tal:block metal:use-macro="templates/page/macros/frame">
+<title metal:fill-slot="head_title" i18n:translate="">List of classes - <span
+ i18n:name="tracker" tal:replace="config/TRACKER_NAME" /></title>
+<span metal:fill-slot="body_title" tal:omit-tag="python:1"
+ i18n:translate="">List of classes</span>
+<td class="content" metal:fill-slot="content">
+<table class="classlist">
+
+<tal:block tal:repeat="cl db/classes">
+ <tr>
+  <th class="header" colspan="2" align="left">
+   <a tal:attributes="href string:${cl/classname}"
+      tal:content="python:cl.classname.capitalize()">classname</a>
+  </th>
+ </tr>
+ <tr tal:repeat="prop cl/properties">
+  <th tal:content="prop/_name">name</th>
+  <td tal:content="prop/_prop">type</td>
+ </tr>
+</tal:block>
+
+</table>
+</td>
+
+</tal:block>
diff --git a/share/roundup/templates/devel/html/home.html b/share/roundup/templates/devel/html/home.html
new file mode 100644 (file)
index 0000000..09aadd9
--- /dev/null
@@ -0,0 +1,10 @@
+<!--
+ This is the default body that is displayed when people visit the
+ tracker. The tag below lists the currently open issues. You may
+ replace it with a greeting message, or a different list of issues or
+ whatever. It's a good idea to have the issues on the front page though
+-->
+<span tal:replace="structure python:db.bug.renderWith('index',
+    sort=[('-', 'activity')], filter=['status'],
+    columns=['activity','title','creator'],
+    filterspec={'status':['1','2','4']})" />
diff --git a/share/roundup/templates/devel/html/jquery.js b/share/roundup/templates/devel/html/jquery.js
new file mode 100644 (file)
index 0000000..9263574
--- /dev/null
@@ -0,0 +1,4376 @@
+/*!
+ * jQuery JavaScript Library v1.3.2
+ * http://jquery.com/
+ *
+ * Copyright (c) 2009 John Resig
+ * Dual licensed under the MIT and GPL licenses.
+ * http://docs.jquery.com/License
+ *
+ * Date: 2009-02-19 17:34:21 -0500 (Thu, 19 Feb 2009)
+ * Revision: 6246
+ */
+(function(){
+
+var 
+       // Will speed up references to window, and allows munging its name.
+       window = this,
+       // Will speed up references to undefined, and allows munging its name.
+       undefined,
+       // Map over jQuery in case of overwrite
+       _jQuery = window.jQuery,
+       // Map over the $ in case of overwrite
+       _$ = window.$,
+
+       jQuery = window.jQuery = window.$ = function( selector, context ) {
+               // The jQuery object is actually just the init constructor 'enhanced'
+               return new jQuery.fn.init( selector, context );
+       },
+
+       // A simple way to check for HTML strings or ID strings
+       // (both of which we optimize for)
+       quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/,
+       // Is it a simple selector
+       isSimple = /^.[^:#\[\.,]*$/;
+
+jQuery.fn = jQuery.prototype = {
+       init: function( selector, context ) {
+               // Make sure that a selection was provided
+               selector = selector || document;
+
+               // Handle $(DOMElement)
+               if ( selector.nodeType ) {
+                       this[0] = selector;
+                       this.length = 1;
+                       this.context = selector;
+                       return this;
+               }
+               // Handle HTML strings
+               if ( typeof selector === "string" ) {
+                       // Are we dealing with HTML string or an ID?
+                       var match = quickExpr.exec( selector );
+
+                       // Verify a match, and that no context was specified for #id
+                       if ( match && (match[1] || !context) ) {
+
+                               // HANDLE: $(html) -> $(array)
+                               if ( match[1] )
+                                       selector = jQuery.clean( [ match[1] ], context );
+
+                               // HANDLE: $("#id")
+                               else {
+                                       var elem = document.getElementById( match[3] );
+
+                                       // Handle the case where IE and Opera return items
+                                       // by name instead of ID
+                                       if ( elem && elem.id != match[3] )
+                                               return jQuery().find( selector );
+
+                                       // Otherwise, we inject the element directly into the jQuery object
+                                       var ret = jQuery( elem || [] );
+                                       ret.context = document;
+                                       ret.selector = selector;
+                                       return ret;
+                               }
+
+                       // HANDLE: $(expr, [context])
+                       // (which is just equivalent to: $(content).find(expr)
+                       } else
+                               return jQuery( context ).find( selector );
+
+               // HANDLE: $(function)
+               // Shortcut for document ready
+               } else if ( jQuery.isFunction( selector ) )
+                       return jQuery( document ).ready( selector );
+
+               // Make sure that old selector state is passed along
+               if ( selector.selector && selector.context ) {
+                       this.selector = selector.selector;
+                       this.context = selector.context;
+               }
+
+               return this.setArray(jQuery.isArray( selector ) ?
+                       selector :
+                       jQuery.makeArray(selector));
+       },
+
+       // Start with an empty selector
+       selector: "",
+
+       // The current version of jQuery being used
+       jquery: "1.3.2",
+
+       // The number of elements contained in the matched element set
+       size: function() {
+               return this.length;
+       },
+
+       // Get the Nth element in the matched element set OR
+       // Get the whole matched element set as a clean array
+       get: function( num ) {
+               return num === undefined ?
+
+                       // Return a 'clean' array
+                       Array.prototype.slice.call( this ) :
+
+                       // Return just the object
+                       this[ num ];
+       },
+
+       // Take an array of elements and push it onto the stack
+       // (returning the new matched element set)
+       pushStack: function( elems, name, selector ) {
+               // Build a new jQuery matched element set
+               var ret = jQuery( elems );
+
+               // Add the old object onto the stack (as a reference)
+               ret.prevObject = this;
+
+               ret.context = this.context;
+
+               if ( name === "find" )
+                       ret.selector = this.selector + (this.selector ? " " : "") + selector;
+               else if ( name )
+                       ret.selector = this.selector + "." + name + "(" + selector + ")";
+
+               // Return the newly-formed element set
+               return ret;
+       },
+
+       // Force the current matched set of elements to become
+       // the specified array of elements (destroying the stack in the process)
+       // You should use pushStack() in order to do this, but maintain the stack
+       setArray: function( elems ) {
+               // Resetting the length to 0, then using the native Array push
+               // is a super-fast way to populate an object with array-like properties
+               this.length = 0;
+               Array.prototype.push.apply( this, elems );
+
+               return this;
+       },
+
+       // Execute a callback for every element in the matched set.
+       // (You can seed the arguments with an array of args, but this is
+       // only used internally.)
+       each: function( callback, args ) {
+               return jQuery.each( this, callback, args );
+       },
+
+       // Determine the position of an element within
+       // the matched set of elements
+       index: function( elem ) {
+               // Locate the position of the desired element
+               return jQuery.inArray(
+                       // If it receives a jQuery object, the first element is used
+                       elem && elem.jquery ? elem[0] : elem
+               , this );
+       },
+
+       attr: function( name, value, type ) {
+               var options = name;
+
+               // Look for the case where we're accessing a style value
+               if ( typeof name === "string" )
+                       if ( value === undefined )
+                               return this[0] && jQuery[ type || "attr" ]( this[0], name );
+
+                       else {
+                               options = {};
+                               options[ name ] = value;
+                       }
+
+               // Check to see if we're setting style values
+               return this.each(function(i){
+                       // Set all the styles
+                       for ( name in options )
+                               jQuery.attr(
+                                       type ?
+                                               this.style :
+                                               this,
+                                       name, jQuery.prop( this, options[ name ], type, i, name )
+                               );
+               });
+       },
+
+       css: function( key, value ) {
+               // ignore negative width and height values
+               if ( (key == 'width' || key == 'height') && parseFloat(value) < 0 )
+                       value = undefined;
+               return this.attr( key, value, "curCSS" );
+       },
+
+       text: function( text ) {
+               if ( typeof text !== "object" && text != null )
+                       return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) );
+
+               var ret = "";
+
+               jQuery.each( text || this, function(){
+                       jQuery.each( this.childNodes, function(){
+                               if ( this.nodeType != 8 )
+                                       ret += this.nodeType != 1 ?
+                                               this.nodeValue :
+                                               jQuery.fn.text( [ this ] );
+                       });
+               });
+
+               return ret;
+       },
+
+       wrapAll: function( html ) {
+               if ( this[0] ) {
+                       // The elements to wrap the target around
+                       var wrap = jQuery( html, this[0].ownerDocument ).clone();
+
+                       if ( this[0].parentNode )
+                               wrap.insertBefore( this[0] );
+
+                       wrap.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, function(elem){
+                       if (this.nodeType == 1)
+                               this.appendChild( elem );
+               });
+       },
+
+       prepend: function() {
+               return this.domManip(arguments, true, function(elem){
+                       if (this.nodeType == 1)
+                               this.insertBefore( elem, this.firstChild );
+               });
+       },
+
+       before: function() {
+               return this.domManip(arguments, false, function(elem){
+                       this.parentNode.insertBefore( elem, this );
+               });
+       },
+
+       after: function() {
+               return this.domManip(arguments, false, function(elem){
+                       this.parentNode.insertBefore( elem, this.nextSibling );
+               });
+       },
+
+       end: function() {
+               return this.prevObject || jQuery( [] );
+       },
+
+       // For internal use only.
+       // Behaves like an Array's method, not like a jQuery method.
+       push: [].push,
+       sort: [].sort,
+       splice: [].splice,
+
+       find: function( selector ) {
+               if ( this.length === 1 ) {
+                       var ret = this.pushStack( [], "find", selector );
+                       ret.length = 0;
+                       jQuery.find( selector, this[0], ret );
+                       return ret;
+               } else {
+                       return this.pushStack( jQuery.unique(jQuery.map(this, function(elem){
+                               return jQuery.find( selector, elem );
+                       })), "find", selector );
+               }
+       },
+
+       clone: function( events ) {
+               // Do the clone
+               var ret = this.map(function(){
+                       if ( !jQuery.support.noCloneEvent && !jQuery.isXMLDoc(this) ) {
+                               // IE copies events bound via attachEvent when
+                               // using cloneNode. Calling detachEvent on the
+                               // clone will also remove the events from the orignal
+                               // In order to get around this, we use innerHTML.
+                               // Unfortunately, this means some modifications to
+                               // attributes in IE that are actually only stored
+                               // as properties will not be copied (such as the
+                               // the name attribute on an input).
+                               var html = this.outerHTML;
+                               if ( !html ) {
+                                       var div = this.ownerDocument.createElement("div");
+                                       div.appendChild( this.cloneNode(true) );
+                                       html = div.innerHTML;
+                               }
+
+                               return jQuery.clean([html.replace(/ jQuery\d+="(?:\d+|null)"/g, "").replace(/^\s*/, "")])[0];
+                       } else
+                               return this.cloneNode(true);
+               });
+
+               // Copy the events from the original to the clone
+               if ( events === true ) {
+                       var orig = this.find("*").andSelf(), i = 0;
+
+                       ret.find("*").andSelf().each(function(){
+                               if ( this.nodeName !== orig[i].nodeName )
+                                       return;
+
+                               var events = jQuery.data( orig[i], "events" );
+
+                               for ( var type in events ) {
+                                       for ( var handler in events[ type ] ) {
+                                               jQuery.event.add( this, type, events[ type ][ handler ], events[ type ][ handler ].data );
+                                       }
+                               }
+
+                               i++;
+                       });
+               }
+
+               // Return the cloned set
+               return ret;
+       },
+
+       filter: function( selector ) {
+               return this.pushStack(
+                       jQuery.isFunction( selector ) &&
+                       jQuery.grep(this, function(elem, i){
+                               return selector.call( elem, i );
+                       }) ||
+
+                       jQuery.multiFilter( selector, jQuery.grep(this, function(elem){
+                               return elem.nodeType === 1;
+                       }) ), "filter", selector );
+       },
+
+       closest: function( selector ) {
+               var pos = jQuery.expr.match.POS.test( selector ) ? jQuery(selector) : null,
+                       closer = 0;
+
+               return this.map(function(){
+                       var cur = this;
+                       while ( cur && cur.ownerDocument ) {
+                               if ( pos ? pos.index(cur) > -1 : jQuery(cur).is(selector) ) {
+                                       jQuery.data(cur, "closest", closer);
+                                       return cur;
+                               }
+                               cur = cur.parentNode;
+                               closer++;
+                       }
+               });
+       },
+
+       not: function( selector ) {
+               if ( typeof selector === "string" )
+                       // test special case where just one selector is passed in
+                       if ( isSimple.test( selector ) )
+                               return this.pushStack( jQuery.multiFilter( selector, this, true ), "not", selector );
+                       else
+                               selector = jQuery.multiFilter( selector, this );
+
+               var isArrayLike = selector.length && selector[selector.length - 1] !== undefined && !selector.nodeType;
+               return this.filter(function() {
+                       return isArrayLike ? jQuery.inArray( this, selector ) < 0 : this != selector;
+               });
+       },
+
+       add: function( selector ) {
+               return this.pushStack( jQuery.unique( jQuery.merge(
+                       this.get(),
+                       typeof selector === "string" ?
+                               jQuery( selector ) :
+                               jQuery.makeArray( selector )
+               )));
+       },
+
+       is: function( selector ) {
+               return !!selector && jQuery.multiFilter( selector, this ).length > 0;
+       },
+
+       hasClass: function( selector ) {
+               return !!selector && this.is( "." + selector );
+       },
+
+       val: function( value ) {
+               if ( value === undefined ) {                    
+                       var elem = this[0];
+
+                       if ( elem ) {
+                               if( jQuery.nodeName( elem, 'option' ) )
+                                       return (elem.attributes.value || {}).specified ? elem.value : elem.text;
+                               
+                               // We need to handle select boxes special
+                               if ( jQuery.nodeName( elem, "select" ) ) {
+                                       var index = elem.selectedIndex,
+                                               values = [],
+                                               options = elem.options,
+                                               one = elem.type == "select-one";
+
+                                       // Nothing was selected
+                                       if ( index < 0 )
+                                               return null;
+
+                                       // Loop through all the selected options
+                                       for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) {
+                                               var option = options[ i ];
+
+                                               if ( option.selected ) {
+                                                       // Get the specifc value for the option
+                                                       value = jQuery(option).val();
+
+                                                       // We don't need an array for one selects
+                                                       if ( one )
+                                                               return value;
+
+                                                       // Multi-Selects return an array
+                                                       values.push( value );
+                                               }
+                                       }
+
+                                       return values;                          
+                               }
+
+                               // Everything else, we just grab the value
+                               return (elem.value || "").replace(/\r/g, "");
+
+                       }
+
+                       return undefined;
+               }
+
+               if ( typeof value === "number" )
+                       value += '';
+
+               return this.each(function(){
+                       if ( this.nodeType != 1 )
+                               return;
+
+                       if ( jQuery.isArray(value) && /radio|checkbox/.test( this.type ) )
+                               this.checked = (jQuery.inArray(this.value, value) >= 0 ||
+                                       jQuery.inArray(this.name, value) >= 0);
+
+                       else if ( jQuery.nodeName( this, "select" ) ) {
+                               var values = jQuery.makeArray(value);
+
+                               jQuery( "option", this ).each(function(){
+                                       this.selected = (jQuery.inArray( this.value, values ) >= 0 ||
+                                               jQuery.inArray( this.text, values ) >= 0);
+                               });
+
+                               if ( !values.length )
+                                       this.selectedIndex = -1;
+
+                       } else
+                               this.value = value;
+               });
+       },
+
+       html: function( value ) {
+               return value === undefined ?
+                       (this[0] ?
+                               this[0].innerHTML.replace(/ jQuery\d+="(?:\d+|null)"/g, "") :
+                               null) :
+                       this.empty().append( value );
+       },
+
+       replaceWith: function( value ) {
+               return this.after( value ).remove();
+       },
+
+       eq: function( i ) {
+               return this.slice( i, +i + 1 );
+       },
+
+       slice: function() {
+               return this.pushStack( Array.prototype.slice.apply( this, arguments ),
+                       "slice", Array.prototype.slice.call(arguments).join(",") );
+       },
+
+       map: function( callback ) {
+               return this.pushStack( jQuery.map(this, function(elem, i){
+                       return callback.call( elem, i, elem );
+               }));
+       },
+
+       andSelf: function() {
+               return this.add( this.prevObject );
+       },
+
+       domManip: function( args, table, callback ) {
+               if ( this[0] ) {
+                       var fragment = (this[0].ownerDocument || this[0]).createDocumentFragment(),
+                               scripts = jQuery.clean( args, (this[0].ownerDocument || this[0]), fragment ),
+                               first = fragment.firstChild;
+
+                       if ( first )
+                               for ( var i = 0, l = this.length; i < l; i++ )
+                                       callback.call( root(this[i], first), this.length > 1 || i > 0 ?
+                                                       fragment.cloneNode(true) : fragment );
+               
+                       if ( scripts )
+                               jQuery.each( scripts, evalScript );
+               }
+
+               return this;
+               
+               function root( elem, cur ) {
+                       return table && jQuery.nodeName(elem, "table") && jQuery.nodeName(cur, "tr") ?
+                               (elem.getElementsByTagName("tbody")[0] ||
+                               elem.appendChild(elem.ownerDocument.createElement("tbody"))) :
+                               elem;
+               }
+       }
+};
+
+// Give the init function the jQuery prototype for later instantiation
+jQuery.fn.init.prototype = jQuery.fn;
+
+function evalScript( i, elem ) {
+       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 );
+}
+
+function now(){
+       return +new Date;
+}
+
+jQuery.extend = jQuery.fn.extend = function() {
+       // copy reference to target object
+       var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options;
+
+       // Handle a deep copy situation
+       if ( typeof target === "boolean" ) {
+               deep = target;
+               target = arguments[1] || {};
+               // skip the boolean and the target
+               i = 2;
+       }
+
+       // Handle case when target is a string or something (possible in deep copy)
+       if ( typeof target !== "object" && !jQuery.isFunction(target) )
+               target = {};
+
+       // extend jQuery itself if only one argument is passed
+       if ( length == i ) {
+               target = this;
+               --i;
+       }
+
+       for ( ; i < length; i++ )
+               // Only deal with non-null/undefined values
+               if ( (options = arguments[ i ]) != null )
+                       // Extend the base object
+                       for ( var name in options ) {
+                               var src = target[ name ], copy = options[ name ];
+
+                               // Prevent never-ending loop
+                               if ( target === copy )
+                                       continue;
+
+                               // Recurse if we're merging object values
+                               if ( deep && copy && typeof copy === "object" && !copy.nodeType )
+                                       target[ name ] = jQuery.extend( deep, 
+                                               // Never move original objects, clone them
+                                               src || ( copy.length != null ? [ ] : { } )
+                                       , copy );
+
+                               // Don't bring in undefined values
+                               else if ( copy !== undefined )
+                                       target[ name ] = copy;
+
+                       }
+
+       // Return the modified object
+       return target;
+};
+
+// exclude the following css properties to add px
+var    exclude = /z-?index|font-?weight|opacity|zoom|line-?height/i,
+       // cache defaultView
+       defaultView = document.defaultView || {},
+       toString = Object.prototype.toString;
+
+jQuery.extend({
+       noConflict: function( deep ) {
+               window.$ = _$;
+
+               if ( deep )
+                       window.jQuery = _jQuery;
+
+               return jQuery;
+       },
+
+       // See test/unit/core.js for details concerning isFunction.
+       // Since version 1.3, DOM methods and functions like alert
+       // aren't supported. They return false on IE (#2968).
+       isFunction: function( obj ) {
+               return toString.call(obj) === "[object Function]";
+       },
+
+       isArray: function( obj ) {
+               return toString.call(obj) === "[object Array]";
+       },
+
+       // check if an element is in a (or is an) XML document
+       isXMLDoc: function( elem ) {
+               return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" ||
+                       !!elem.ownerDocument && jQuery.isXMLDoc( elem.ownerDocument );
+       },
+
+       // Evalulates a script in a global context
+       globalEval: function( data ) {
+               if ( data && /\S/.test(data) ) {
+                       // Inspired by code by Andrea Giammarchi
+                       // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html
+                       var head = document.getElementsByTagName("head")[0] || document.documentElement,
+                               script = document.createElement("script");
+
+                       script.type = "text/javascript";
+                       if ( jQuery.support.scriptEval )
+                               script.appendChild( document.createTextNode( data ) );
+                       else
+                               script.text = data;
+
+                       // Use insertBefore instead of appendChild  to circumvent an IE6 bug.
+                       // This arises when a base node is used (#2709).
+                       head.insertBefore( script, head.firstChild );
+                       head.removeChild( script );
+               }
+       },
+
+       nodeName: function( elem, name ) {
+               return elem.nodeName && elem.nodeName.toUpperCase() == name.toUpperCase();
+       },
+
+       // args is for internal usage only
+       each: function( object, callback, args ) {
+               var name, i = 0, length = object.length;
+
+               if ( args ) {
+                       if ( length === undefined ) {
+                               for ( name in object )
+                                       if ( callback.apply( object[ name ], args ) === false )
+                                               break;
+                       } else
+                               for ( ; i < length; )
+                                       if ( callback.apply( object[ i++ ], args ) === false )
+                                               break;
+
+               // A special, fast, case for the most common use of each
+               } else {
+                       if ( length === undefined ) {
+                               for ( name in object )
+                                       if ( callback.call( object[ name ], name, object[ name ] ) === false )
+                                               break;
+                       } else
+                               for ( var value = object[0];
+                                       i < length && callback.call( value, i, value ) !== false; value = object[++i] ){}
+               }
+
+               return object;
+       },
+
+       prop: function( elem, value, type, i, name ) {
+               // Handle executable functions
+               if ( jQuery.isFunction( value ) )
+                       value = value.call( elem, i );
+
+               // Handle passing in a number to a CSS property
+               return typeof value === "number" && type == "curCSS" && !exclude.test( name ) ?
+                       value + "px" :
+                       value;
+       },
+
+       className: {
+               // internal only, use addClass("class")
+               add: function( elem, classNames ) {
+                       jQuery.each((classNames || "").split(/\s+/), function(i, className){
+                               if ( elem.nodeType == 1 && !jQuery.className.has( elem.className, className ) )
+                                       elem.className += (elem.className ? " " : "") + className;
+                       });
+               },
+
+               // internal only, use removeClass("class")
+               remove: function( elem, classNames ) {
+                       if (elem.nodeType == 1)
+                               elem.className = classNames !== undefined ?
+                                       jQuery.grep(elem.className.split(/\s+/), function(className){
+                                               return !jQuery.className.has( classNames, className );
+                                       }).join(" ") :
+                                       "";
+               },
+
+               // internal only, use hasClass("class")
+               has: function( elem, className ) {
+                       return elem && jQuery.inArray( className, (elem.className || elem).toString().split(/\s+/) ) > -1;
+               }
+       },
+
+       // A method for quickly swapping in/out CSS properties to get correct calculations
+       swap: function( elem, options, callback ) {
+               var old = {};
+               // Remember the old values, and insert the new ones
+               for ( var name in options ) {
+                       old[ name ] = elem.style[ name ];
+                       elem.style[ name ] = options[ name ];
+               }
+
+               callback.call( elem );
+
+               // Revert the old values
+               for ( var name in options )
+                       elem.style[ name ] = old[ name ];
+       },
+
+       css: function( elem, name, force, extra ) {
+               if ( name == "width" || name == "height" ) {
+                       var val, props = { position: "absolute", visibility: "hidden", display:"block" }, which = name == "width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ];
+
+                       function getWH() {
+                               val = name == "width" ? elem.offsetWidth : elem.offsetHeight;
+
+                               if ( extra === "border" )
+                                       return;
+
+                               jQuery.each( which, function() {
+                                       if ( !extra )
+                                               val -= parseFloat(jQuery.curCSS( elem, "padding" + this, true)) || 0;
+                                       if ( extra === "margin" )
+                                               val += parseFloat(jQuery.curCSS( elem, "margin" + this, true)) || 0;
+                                       else
+                                               val -= parseFloat(jQuery.curCSS( elem, "border" + this + "Width", true)) || 0;
+                               });
+                       }
+
+                       if ( elem.offsetWidth !== 0 )
+                               getWH();
+                       else
+                               jQuery.swap( elem, props, getWH );
+
+                       return Math.max(0, Math.round(val));
+               }
+
+               return jQuery.curCSS( elem, name, force );
+       },
+
+       curCSS: function( elem, name, force ) {
+               var ret, style = elem.style;
+
+               // We need to handle opacity special in IE
+               if ( name == "opacity" && !jQuery.support.opacity ) {
+                       ret = jQuery.attr( style, "opacity" );
+
+                       return ret == "" ?
+                               "1" :
+                               ret;
+               }
+
+               // Make sure we're using the right name for getting the float value
+               if ( name.match( /float/i ) )
+                       name = styleFloat;
+
+               if ( !force && style && style[ name ] )
+                       ret = style[ name ];
+
+               else if ( defaultView.getComputedStyle ) {
+
+                       // Only "float" is needed here
+                       if ( name.match( /float/i ) )
+                               name = "float";
+
+                       name = name.replace( /([A-Z])/g, "-$1" ).toLowerCase();
+
+                       var computedStyle = defaultView.getComputedStyle( elem, null );
+
+                       if ( computedStyle )
+                               ret = computedStyle.getPropertyValue( name );
+
+                       // We should always get a number back from opacity
+                       if ( name == "opacity" && ret == "" )
+                               ret = "1";
+
+               } else if ( elem.currentStyle ) {
+                       var camelCase = name.replace(/\-(\w)/g, function(all, letter){
+                               return letter.toUpperCase();
+                       });
+
+                       ret = elem.currentStyle[ name ] || elem.currentStyle[ camelCase ];
+
+                       // From the awesome hack by Dean Edwards
+                       // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
+
+                       // If we're not dealing with a regular pixel number
+                       // but a number that has a weird ending, we need to convert it to pixels
+                       if ( !/^\d+(px)?$/i.test( ret ) && /^\d/.test( ret ) ) {
+                               // Remember the original values
+                               var left = style.left, rsLeft = elem.runtimeStyle.left;
+
+                               // Put in the new values to get a computed value out
+                               elem.runtimeStyle.left = elem.currentStyle.left;
+                               style.left = ret || 0;
+                               ret = style.pixelLeft + "px";
+
+                               // Revert the changed values
+                               style.left = left;
+                               elem.runtimeStyle.left = rsLeft;
+                       }
+               }
+
+               return ret;
+       },
+
+       clean: function( elems, context, fragment ) {
+               context = context || document;
+
+               // !context.createElement fails in IE with an error but returns typeof 'object'
+               if ( typeof context.createElement === "undefined" )
+                       context = context.ownerDocument || context[0] && context[0].ownerDocument || document;
+
+               // If a single string is passed in and it's a single tag
+               // just do a createElement and skip the rest
+               if ( !fragment && elems.length === 1 && typeof elems[0] === "string" ) {
+                       var match = /^<(\w+)\s*\/?>$/.exec(elems[0]);
+                       if ( match )
+                               return [ context.createElement( match[1] ) ];
+               }
+
+               var ret = [], scripts = [], div = context.createElement("div");
+
+               jQuery.each(elems, function(i, elem){
+                       if ( typeof elem === "number" )
+                               elem += '';
+
+                       if ( !elem )
+                               return;
+
+                       // Convert html string into DOM nodes
+                       if ( typeof elem === "string" ) {
+                               // Fix "XHTML"-style tags in all browsers
+                               elem = elem.replace(/(<(\w+)[^>]*?)\/>/g, function(all, front, tag){
+                                       return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i) ?
+                                               all :
+                                               front + "></" + tag + ">";
+                               });
+
+                               // Trim whitespace, otherwise indexOf won't work as expected
+                               var tags = elem.replace(/^\s+/, "").substring(0, 10).toLowerCase();
+
+                               var wrap =
+                                       // option or optgroup
+                                       !tags.indexOf("<opt") &&
+                                       [ 1, "<select multiple='multiple'>", "</select>" ] ||
+
+                                       !tags.indexOf("<leg") &&
+                                       [ 1, "<fieldset>", "</fieldset>" ] ||
+
+                                       tags.match(/^<(thead|tbody|tfoot|colg|cap)/) &&
+                                       [ 1, "<table>", "</table>" ] ||
+
+                                       !tags.indexOf("<tr") &&
+                                       [ 2, "<table><tbody>", "</tbody></table>" ] ||
+
+                                       // <thead> matched above
+                                       (!tags.indexOf("<td") || !tags.indexOf("<th")) &&
+                                       [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ] ||
+
+                                       !tags.indexOf("<col") &&
+                                       [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ] ||
+
+                                       // IE can't serialize <link> and <script> tags normally
+                                       !jQuery.support.htmlSerialize &&
+                                       [ 1, "div<div>", "</div>" ] ||
+
+                                       [ 0, "", "" ];
+
+                               // Go to html and back, then peel off extra wrappers
+                               div.innerHTML = wrap[1] + elem + wrap[2];
+
+                               // Move to the right depth
+                               while ( wrap[0]-- )
+                                       div = div.lastChild;
+
+                               // Remove IE's autoinserted <tbody> from table fragments
+                               if ( !jQuery.support.tbody ) {
+
+                                       // String was a <table>, *may* have spurious <tbody>
+                                       var hasBody = /<tbody/i.test(elem),
+                                               tbody = !tags.indexOf("<table") && !hasBody ?
+                                                       div.firstChild && div.firstChild.childNodes :
+
+                                               // String was a bare <thead> or <tfoot>
+                                               wrap[1] == "<table>" && !hasBody ?
+                                                       div.childNodes :
+                                                       [];
+
+                                       for ( var j = tbody.length - 1; j >= 0 ; --j )
+                                               if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length )
+                                                       tbody[ j ].parentNode.removeChild( tbody[ j ] );
+
+                                       }
+
+                               // IE completely kills leading whitespace when innerHTML is used
+                               if ( !jQuery.support.leadingWhitespace && /^\s/.test( elem ) )
+                                       div.insertBefore( context.createTextNode( elem.match(/^\s*/)[0] ), div.firstChild );
+                               
+                               elem = jQuery.makeArray( div.childNodes );
+                       }
+
+                       if ( elem.nodeType )
+                               ret.push( elem );
+                       else
+                               ret = jQuery.merge( ret, elem );
+
+               });
+
+               if ( fragment ) {
+                       for ( var i = 0; ret[i]; i++ ) {
+                               if ( jQuery.nodeName( ret[i], "script" ) && (!ret[i].type || ret[i].type.toLowerCase() === "text/javascript") ) {
+                                       scripts.push( ret[i].parentNode ? ret[i].parentNode.removeChild( ret[i] ) : ret[i] );
+                               } else {
+                                       if ( ret[i].nodeType === 1 )
+                                               ret.splice.apply( ret, [i + 1, 0].concat(jQuery.makeArray(ret[i].getElementsByTagName("script"))) );
+                                       fragment.appendChild( ret[i] );
+                               }
+                       }
+                       
+                       return scripts;
+               }
+
+               return ret;
+       },
+
+       attr: function( elem, name, value ) {
+               // don't set attributes on text and comment nodes
+               if (!elem || elem.nodeType == 3 || elem.nodeType == 8)
+                       return undefined;
+
+               var notxml = !jQuery.isXMLDoc( elem ),
+                       // Whether we are setting (or getting)
+                       set = value !== undefined;
+
+               // Try to normalize/fix the name
+               name = notxml && jQuery.props[ name ] || name;
+
+               // Only do all the following if this is a node (faster for style)
+               // IE elem.getAttribute passes even for style
+               if ( elem.tagName ) {
+
+                       // These attributes require special treatment
+                       var special = /href|src|style/.test( name );
+
+                       // Safari mis-reports the default selected property of a hidden option
+                       // Accessing the parent's selectedIndex property fixes it
+                       if ( name == "selected" && elem.parentNode )
+                               elem.parentNode.selectedIndex;
+
+                       // If applicable, access the attribute via the DOM 0 way
+                       if ( name in elem && notxml && !special ) {
+                               if ( set ){
+                                       // We can't allow the type property to be changed (since it causes problems in IE)
+                                       if ( name == "type" && jQuery.nodeName( elem, "input" ) && elem.parentNode )
+                                               throw "type property can't be changed";
+
+                                       elem[ name ] = value;
+                               }
+
+                               // browsers index elements by id/name on forms, give priority to attributes.
+                               if( jQuery.nodeName( elem, "form" ) && elem.getAttributeNode(name) )
+                                       return elem.getAttributeNode( name ).nodeValue;
+
+                               // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
+                               // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
+                               if ( name == "tabIndex" ) {
+                                       var attributeNode = elem.getAttributeNode( "tabIndex" );
+                                       return attributeNode && attributeNode.specified
+                                               ? attributeNode.value
+                                               : elem.nodeName.match(/(button|input|object|select|textarea)/i)
+                                                       ? 0
+                                                       : elem.nodeName.match(/^(a|area)$/i) && elem.href
+                                                               ? 0
+                                                               : undefined;
+                               }
+
+                               return elem[ name ];
+                       }
+
+                       if ( !jQuery.support.style && notxml &&  name == "style" )
+                               return jQuery.attr( elem.style, "cssText", value );
+
+                       if ( set )
+                               // convert the value to a string (all browsers do this but IE) see #1070
+                               elem.setAttribute( name, "" + value );
+
+                       var attr = !jQuery.support.hrefNormalized && notxml && special
+                                       // Some attributes require a special call on IE
+                                       ? elem.getAttribute( name, 2 )
+                                       : elem.getAttribute( name );
+
+                       // Non-existent attributes return null, we normalize to undefined
+                       return attr === null ? undefined : attr;
+               }
+
+               // elem is actually elem.style ... set the style
+
+               // IE uses filters for opacity
+               if ( !jQuery.support.opacity && name == "opacity" ) {
+                       if ( set ) {
+                               // IE has trouble with opacity if it does not have layout
+                               // Force it by setting the zoom level
+                               elem.zoom = 1;
+
+                               // Set the alpha filter to set the opacity
+                               elem.filter = (elem.filter || "").replace( /alpha\([^)]*\)/, "" ) +
+                                       (parseInt( value ) + '' == "NaN" ? "" : "alpha(opacity=" + value * 100 + ")");
+                       }
+
+                       return elem.filter && elem.filter.indexOf("opacity=") >= 0 ?
+                               (parseFloat( elem.filter.match(/opacity=([^)]*)/)[1] ) / 100) + '':
+                               "";
+               }
+
+               name = name.replace(/-([a-z])/ig, function(all, letter){
+                       return letter.toUpperCase();
+               });
+
+               if ( set )
+                       elem[ name ] = value;
+
+               return elem[ name ];
+       },
+
+       trim: function( text ) {
+               return (text || "").replace( /^\s+|\s+$/g, "" );
+       },
+
+       makeArray: function( array ) {
+               var ret = [];
+
+               if( array != null ){
+                       var i = array.length;
+                       // The window, strings (and functions) also have 'length'
+                       if( i == null || typeof array === "string" || jQuery.isFunction(array) || array.setInterval )
+                               ret[0] = array;
+                       else
+                               while( i )
+                                       ret[--i] = array[i];
+               }
+
+               return ret;
+       },
+
+       inArray: function( elem, array ) {
+               for ( var i = 0, length = array.length; i < length; i++ )
+               // Use === because on IE, window == document
+                       if ( array[ i ] === elem )
+                               return i;
+
+               return -1;
+       },
+
+       merge: function( first, second ) {
+               // We have to loop this way because IE & Opera overwrite the length
+               // expando of getElementsByTagName
+               var i = 0, elem, pos = first.length;
+               // Also, we need to make sure that the correct elements are being returned
+               // (IE returns comment nodes in a '*' query)
+               if ( !jQuery.support.getAll ) {
+                       while ( (elem = second[ i++ ]) != null )
+                               if ( elem.nodeType != 8 )
+                                       first[ pos++ ] = elem;
+
+               } else
+                       while ( (elem = second[ i++ ]) != null )
+                               first[ pos++ ] = elem;
+
+               return first;
+       },
+
+       unique: function( array ) {
+               var ret = [], done = {};
+
+               try {
+
+                       for ( var i = 0, length = array.length; i < length; i++ ) {
+                               var id = jQuery.data( array[ i ] );
+
+                               if ( !done[ id ] ) {
+                                       done[ id ] = true;
+                                       ret.push( array[ i ] );
+                               }
+                       }
+
+               } catch( e ) {
+                       ret = array;
+               }
+
+               return ret;
+       },
+
+       grep: function( elems, callback, inv ) {
+               var ret = [];
+
+               // Go through the array, only saving the items
+               // that pass the validator function
+               for ( var i = 0, length = elems.length; i < length; i++ )
+                       if ( !inv != !callback( elems[ i ], i ) )
+                               ret.push( elems[ i ] );
+
+               return ret;
+       },
+
+       map: function( elems, callback ) {
+               var ret = [];
+
+               // Go through the array, translating each of the items to their
+               // new value (or values).
+               for ( var i = 0, length = elems.length; i < length; i++ ) {
+                       var value = callback( elems[ i ], i );
+
+                       if ( value != null )
+                               ret[ ret.length ] = value;
+               }
+
+               return ret.concat.apply( [], ret );
+       }
+});
+
+// Use of jQuery.browser is deprecated.
+// It's included for backwards compatibility and plugins,
+// although they should work to migrate away.
+
+var userAgent = navigator.userAgent.toLowerCase();
+
+// Figure out what browser is being used
+jQuery.browser = {
+       version: (userAgent.match( /.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/ ) || [0,'0'])[1],
+       safari: /webkit/.test( userAgent ),
+       opera: /opera/.test( userAgent ),
+       msie: /msie/.test( userAgent ) && !/opera/.test( userAgent ),
+       mozilla: /mozilla/.test( userAgent ) && !/(compatible|webkit)/.test( userAgent )
+};
+
+jQuery.each({
+       parent: function(elem){return elem.parentNode;},
+       parents: function(elem){return jQuery.dir(elem,"parentNode");},
+       next: function(elem){return jQuery.nth(elem,2,"nextSibling");},
+       prev: function(elem){return jQuery.nth(elem,2,"previousSibling");},
+       nextAll: function(elem){return jQuery.dir(elem,"nextSibling");},
+       prevAll: function(elem){return jQuery.dir(elem,"previousSibling");},
+       siblings: function(elem){return jQuery.sibling(elem.parentNode.firstChild,elem);},
+       children: function(elem){return jQuery.sibling(elem.firstChild);},
+       contents: function(elem){return jQuery.nodeName(elem,"iframe")?elem.contentDocument||elem.contentWindow.document:jQuery.makeArray(elem.childNodes);}
+}, function(name, fn){
+       jQuery.fn[ name ] = function( selector ) {
+               var ret = jQuery.map( this, fn );
+
+               if ( selector && typeof selector == "string" )
+                       ret = jQuery.multiFilter( selector, ret );
+
+               return this.pushStack( jQuery.unique( ret ), name, selector );
+       };
+});
+
+jQuery.each({
+       appendTo: "append",
+       prependTo: "prepend",
+       insertBefore: "before",
+       insertAfter: "after",
+       replaceAll: "replaceWith"
+}, function(name, original){
+       jQuery.fn[ name ] = function( selector ) {
+               var ret = [], insert = jQuery( selector );
+
+               for ( var i = 0, l = insert.length; i < l; i++ ) {
+                       var elems = (i > 0 ? this.clone(true) : this).get();
+                       jQuery.fn[ original ].apply( jQuery(insert[i]), elems );
+                       ret = ret.concat( elems );
+               }
+
+               return this.pushStack( ret, name, selector );
+       };
+});
+
+jQuery.each({
+       removeAttr: function( name ) {
+               jQuery.attr( this, name, "" );
+               if (this.nodeType == 1)
+                       this.removeAttribute( name );
+       },
+
+       addClass: function( classNames ) {
+               jQuery.className.add( this, classNames );
+       },
+
+       removeClass: function( classNames ) {
+               jQuery.className.remove( this, classNames );
+       },
+
+       toggleClass: function( classNames, state ) {
+               if( typeof state !== "boolean" )
+                       state = !jQuery.className.has( this, classNames );
+               jQuery.className[ state ? "add" : "remove" ]( this, classNames );
+       },
+
+       remove: function( selector ) {
+               if ( !selector || jQuery.filter( selector, [ this ] ).length ) {
+                       // Prevent memory leaks
+                       jQuery( "*", this ).add([this]).each(function(){
+                               jQuery.event.remove(this);
+                               jQuery.removeData(this);
+                       });
+                       if (this.parentNode)
+                               this.parentNode.removeChild( this );
+               }
+       },
+
+       empty: function() {
+               // Remove element nodes and prevent memory leaks
+               jQuery(this).children().remove();
+
+               // Remove any remaining nodes
+               while ( this.firstChild )
+                       this.removeChild( this.firstChild );
+       }
+}, function(name, fn){
+       jQuery.fn[ name ] = function(){
+               return this.each( fn, arguments );
+       };
+});
+
+// Helper function used by the dimensions and offset modules
+function num(elem, prop) {
+       return elem[0] && parseInt( jQuery.curCSS(elem[0], prop, true), 10 ) || 0;
+}
+var expando = "jQuery" + now(), uuid = 0, windowData = {};\r
+\r
+jQuery.extend({\r
+       cache: {},\r
+\r
+       data: function( elem, name, data ) {\r
+               elem = elem == window ?\r
+                       windowData :\r
+                       elem;\r
+\r
+               var id = elem[ expando ];\r
+\r
+               // Compute a unique ID for the element\r
+               if ( !id )\r
+                       id = elem[ expando ] = ++uuid;\r
+\r
+               // Only generate the data cache if we're\r
+               // trying to access or manipulate it\r
+               if ( name && !jQuery.cache[ id ] )\r
+                       jQuery.cache[ id ] = {};\r
+\r
+               // Prevent overriding the named cache with undefined values\r
+               if ( data !== undefined )\r
+                       jQuery.cache[ id ][ name ] = data;\r
+\r
+               // Return the named cache data, or the ID for the element\r
+               return name ?\r
+                       jQuery.cache[ id ][ name ] :\r
+                       id;\r
+       },\r
+\r
+       removeData: function( elem, name ) {\r
+               elem = elem == window ?\r
+                       windowData :\r
+                       elem;\r
+\r
+               var id = elem[ expando ];\r
+\r
+               // If we want to remove a specific section of the element's data\r
+               if ( name ) {\r
+                       if ( jQuery.cache[ id ] ) {\r
+                               // Remove the section of cache data\r
+                               delete jQuery.cache[ id ][ name ];\r
+\r
+                               // If we've removed all the data, remove the element's cache\r
+                               name = "";\r
+\r
+                               for ( name in jQuery.cache[ id ] )\r
+                                       break;\r
+\r
+                               if ( !name )\r
+                                       jQuery.removeData( elem );\r
+                       }\r
+\r
+               // Otherwise, we want to remove all of the element's data\r
+               } else {\r
+                       // Clean up the element expando\r
+                       try {\r
+                               delete elem[ expando ];\r
+                       } catch(e){\r
+                               // IE has trouble directly removing the expando\r
+                               // but it's ok with using removeAttribute\r
+                               if ( elem.removeAttribute )\r
+                                       elem.removeAttribute( expando );\r
+                       }\r
+\r
+                       // Completely remove the data cache\r
+                       delete jQuery.cache[ id ];\r
+               }\r
+       },\r
+       queue: function( elem, type, data ) {\r
+               if ( elem ){\r
+       \r
+                       type = (type || "fx") + "queue";\r
+       \r
+                       var q = jQuery.data( elem, type );\r
+       \r
+                       if ( !q || jQuery.isArray(data) )\r
+                               q = jQuery.data( elem, type, jQuery.makeArray(data) );\r
+                       else if( data )\r
+                               q.push( data );\r
+       \r
+               }\r
+               return q;\r
+       },\r
+\r
+       dequeue: function( elem, type ){\r
+               var queue = jQuery.queue( elem, type ),\r
+                       fn = queue.shift();\r
+               \r
+               if( !type || type === "fx" )\r
+                       fn = queue[0];\r
+                       \r
+               if( fn !== undefined )\r
+                       fn.call(elem);\r
+       }\r
+});\r
+\r
+jQuery.fn.extend({\r
+       data: function( key, value ){\r
+               var parts = key.split(".");\r
+               parts[1] = parts[1] ? "." + parts[1] : "";\r
+\r
+               if ( value === undefined ) {\r
+                       var data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]);\r
+\r
+                       if ( data === undefined && this.length )\r
+                               data = jQuery.data( this[0], key );\r
+\r
+                       return data === undefined && parts[1] ?\r
+                               this.data( parts[0] ) :\r
+                               data;\r
+               } else\r
+                       return this.trigger("setData" + parts[1] + "!", [parts[0], value]).each(function(){\r
+                               jQuery.data( this, key, value );\r
+                       });\r
+       },\r
+\r
+       removeData: function( key ){\r
+               return this.each(function(){\r
+                       jQuery.removeData( this, key );\r
+               });\r
+       },\r
+       queue: function(type, data){\r
+               if ( typeof type !== "string" ) {\r
+                       data = type;\r
+                       type = "fx";\r
+               }\r
+\r
+               if ( data === undefined )\r
+                       return jQuery.queue( this[0], type );\r
+\r
+               return this.each(function(){\r
+                       var queue = jQuery.queue( this, type, data );\r
+                       \r
+                        if( type == "fx" && queue.length == 1 )\r
+                               queue[0].call(this);\r
+               });\r
+       },\r
+       dequeue: function(type){\r
+               return this.each(function(){\r
+                       jQuery.dequeue( this, type );\r
+               });\r
+       }\r
+});/*!
+ * Sizzle CSS Selector Engine - v0.9.3
+ *  Copyright 2009, The Dojo Foundation
+ *  Released under the MIT, BSD, and GPL Licenses.
+ *  More information: http://sizzlejs.com/
+ */
+(function(){
+
+var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?/g,
+       done = 0,
+       toString = Object.prototype.toString;
+
+var Sizzle = function(selector, context, results, seed) {
+       results = results || [];
+       context = context || document;
+
+       if ( context.nodeType !== 1 && context.nodeType !== 9 )
+               return [];
+       
+       if ( !selector || typeof selector !== "string" ) {
+               return results;
+       }
+
+       var parts = [], m, set, checkSet, check, mode, extra, prune = true;
+       
+       // Reset the position of the chunker regexp (start from head)
+       chunker.lastIndex = 0;
+       
+       while ( (m = chunker.exec(selector)) !== null ) {
+               parts.push( m[1] );
+               
+               if ( m[2] ) {
+                       extra = RegExp.rightContext;
+                       break;
+               }
+       }
+
+       if ( parts.length > 1 && origPOS.exec( selector ) ) {
+               if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
+                       set = posProcess( parts[0] + parts[1], context );
+               } else {
+                       set = Expr.relative[ parts[0] ] ?
+                               [ context ] :
+                               Sizzle( parts.shift(), context );
+
+                       while ( parts.length ) {
+                               selector = parts.shift();
+
+                               if ( Expr.relative[ selector ] )
+                                       selector += parts.shift();
+
+                               set = posProcess( selector, set );
+                       }
+               }
+       } else {
+               var ret = seed ?
+                       { expr: parts.pop(), set: makeArray(seed) } :
+                       Sizzle.find( parts.pop(), parts.length === 1 && context.parentNode ? context.parentNode : context, isXML(context) );
+               set = Sizzle.filter( ret.expr, ret.set );
+
+               if ( parts.length > 0 ) {
+                       checkSet = makeArray(set);
+               } else {
+                       prune = false;
+               }
+
+               while ( parts.length ) {
+                       var cur = parts.pop(), pop = cur;
+
+                       if ( !Expr.relative[ cur ] ) {
+                               cur = "";
+                       } else {
+                               pop = parts.pop();
+                       }
+
+                       if ( pop == null ) {
+                               pop = context;
+                       }
+
+                       Expr.relative[ cur ]( checkSet, pop, isXML(context) );
+               }
+       }
+
+       if ( !checkSet ) {
+               checkSet = set;
+       }
+
+       if ( !checkSet ) {
+               throw "Syntax error, unrecognized expression: " + (cur || selector);
+       }
+
+       if ( toString.call(checkSet) === "[object Array]" ) {
+               if ( !prune ) {
+                       results.push.apply( results, checkSet );
+               } else if ( context.nodeType === 1 ) {
+                       for ( var i = 0; checkSet[i] != null; i++ ) {
+                               if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) {
+                                       results.push( set[i] );
+                               }
+                       }
+               } else {
+                       for ( var i = 0; checkSet[i] != null; i++ ) {
+                               if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
+                                       results.push( set[i] );
+                               }
+                       }
+               }
+       } else {
+               makeArray( checkSet, results );
+       }
+
+       if ( extra ) {
+               Sizzle( extra, context, results, seed );
+
+               if ( sortOrder ) {
+                       hasDuplicate = false;
+                       results.sort(sortOrder);
+
+                       if ( hasDuplicate ) {
+                               for ( var i = 1; i < results.length; i++ ) {
+                                       if ( results[i] === results[i-1] ) {
+                                               results.splice(i--, 1);
+                                       }
+                               }
+                       }
+               }
+       }
+
+       return results;
+};
+
+Sizzle.matches = function(expr, set){
+       return Sizzle(expr, null, null, set);
+};
+
+Sizzle.find = function(expr, context, isXML){
+       var set, match;
+
+       if ( !expr ) {
+               return [];
+       }
+
+       for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
+               var type = Expr.order[i], match;
+               
+               if ( (match = Expr.match[ type ].exec( expr )) ) {
+                       var left = RegExp.leftContext;
+
+                       if ( left.substr( left.length - 1 ) !== "\\" ) {
+                               match[1] = (match[1] || "").replace(/\\/g, "");
+                               set = Expr.find[ type ]( match, context, isXML );
+                               if ( set != null ) {
+                                       expr = expr.replace( Expr.match[ type ], "" );
+                                       break;
+                               }
+                       }
+               }
+       }
+
+       if ( !set ) {
+               set = context.getElementsByTagName("*");
+       }
+
+       return {set: set, expr: expr};
+};
+
+Sizzle.filter = function(expr, set, inplace, not){
+       var old = expr, result = [], curLoop = set, match, anyFound,
+               isXMLFilter = set && set[0] && isXML(set[0]);
+
+       while ( expr && set.length ) {
+               for ( var type in Expr.filter ) {
+                       if ( (match = Expr.match[ type ].exec( expr )) != null ) {
+                               var filter = Expr.filter[ type ], found, item;
+                               anyFound = false;
+
+                               if ( curLoop == result ) {
+                                       result = [];
+                               }
+
+                               if ( Expr.preFilter[ type ] ) {
+                                       match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
+
+                                       if ( !match ) {
+                                               anyFound = found = true;
+                                       } else if ( match === true ) {
+                                               continue;
+                                       }
+                               }
+
+                               if ( match ) {
+                                       for ( var i = 0; (item = curLoop[i]) != null; i++ ) {
+                                               if ( item ) {
+                                                       found = filter( item, match, i, curLoop );
+                                                       var pass = not ^ !!found;
+
+                                                       if ( inplace && found != null ) {
+                                                               if ( pass ) {
+                                                                       anyFound = true;
+                                                               } else {
+                                                                       curLoop[i] = false;
+                                                               }
+                                                       } else if ( pass ) {
+                                                               result.push( item );
+                                                               anyFound = true;
+                                                       }
+                                               }
+                                       }
+                               }
+
+                               if ( found !== undefined ) {
+                                       if ( !inplace ) {
+                                               curLoop = result;
+                                       }
+
+                                       expr = expr.replace( Expr.match[ type ], "" );
+
+                                       if ( !anyFound ) {
+                                               return [];
+                                       }
+
+                                       break;
+                               }
+                       }
+               }
+
+               // Improper expression
+               if ( expr == old ) {
+                       if ( anyFound == null ) {
+                               throw "Syntax error, unrecognized expression: " + expr;
+                       } else {
+                               break;
+                       }
+               }
+
+               old = expr;
+       }
+
+       return curLoop;
+};
+
+var Expr = Sizzle.selectors = {
+       order: [ "ID", "NAME", "TAG" ],
+       match: {
+               ID: /#((?:[\w\u00c0-\uFFFF_-]|\\.)+)/,
+               CLASS: /\.((?:[\w\u00c0-\uFFFF_-]|\\.)+)/,
+               NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF_-]|\\.)+)['"]*\]/,
+               ATTR: /\[\s*((?:[\w\u00c0-\uFFFF_-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,
+               TAG: /^((?:[\w\u00c0-\uFFFF\*_-]|\\.)+)/,
+               CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,
+               POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,
+               PSEUDO: /:((?:[\w\u00c0-\uFFFF_-]|\\.)+)(?:\((['"]*)((?:\([^\)]+\)|[^\2\(\)]*)+)\2\))?/
+       },
+       attrMap: {
+               "class": "className",
+               "for": "htmlFor"
+       },
+       attrHandle: {
+               href: function(elem){
+                       return elem.getAttribute("href");
+               }
+       },
+       relative: {
+               "+": function(checkSet, part, isXML){
+                       var isPartStr = typeof part === "string",
+                               isTag = isPartStr && !/\W/.test(part),
+                               isPartStrNotTag = isPartStr && !isTag;
+
+                       if ( isTag && !isXML ) {
+                               part = part.toUpperCase();
+                       }
+
+                       for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
+                               if ( (elem = checkSet[i]) ) {
+                                       while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
+
+                                       checkSet[i] = isPartStrNotTag || elem && elem.nodeName === part ?
+                                               elem || false :
+                                               elem === part;
+                               }
+                       }
+
+                       if ( isPartStrNotTag ) {
+                               Sizzle.filter( part, checkSet, true );
+                       }
+               },
+               ">": function(checkSet, part, isXML){
+                       var isPartStr = typeof part === "string";
+
+                       if ( isPartStr && !/\W/.test(part) ) {
+                               part = isXML ? part : part.toUpperCase();
+
+                               for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+                                       var elem = checkSet[i];
+                                       if ( elem ) {
+                                               var parent = elem.parentNode;
+                                               checkSet[i] = parent.nodeName === part ? parent : false;
+                                       }
+                               }
+                       } else {
+                               for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+                                       var elem = checkSet[i];
+                                       if ( elem ) {
+                                               checkSet[i] = isPartStr ?
+                                                       elem.parentNode :
+                                                       elem.parentNode === part;
+                                       }
+                               }
+
+                               if ( isPartStr ) {
+                                       Sizzle.filter( part, checkSet, true );
+                               }
+                       }
+               },
+               "": function(checkSet, part, isXML){
+                       var doneName = done++, checkFn = dirCheck;
+
+                       if ( !part.match(/\W/) ) {
+                               var nodeCheck = part = isXML ? part : part.toUpperCase();
+                               checkFn = dirNodeCheck;
+                       }
+
+                       checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML);
+               },
+               "~": function(checkSet, part, isXML){
+                       var doneName = done++, checkFn = dirCheck;
+
+                       if ( typeof part === "string" && !part.match(/\W/) ) {
+                               var nodeCheck = part = isXML ? part : part.toUpperCase();
+                               checkFn = dirNodeCheck;
+                       }
+
+                       checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML);
+               }
+       },
+       find: {
+               ID: function(match, context, isXML){
+                       if ( typeof context.getElementById !== "undefined" && !isXML ) {
+                               var m = context.getElementById(match[1]);
+                               return m ? [m] : [];
+                       }
+               },
+               NAME: function(match, context, isXML){
+                       if ( typeof context.getElementsByName !== "undefined" ) {
+                               var ret = [], results = context.getElementsByName(match[1]);
+
+                               for ( var i = 0, l = results.length; i < l; i++ ) {
+                                       if ( results[i].getAttribute("name") === match[1] ) {
+                                               ret.push( results[i] );
+                                       }
+                               }
+
+                               return ret.length === 0 ? null : ret;
+                       }
+               },
+               TAG: function(match, context){
+                       return context.getElementsByTagName(match[1]);
+               }
+       },
+       preFilter: {
+               CLASS: function(match, curLoop, inplace, result, not, isXML){
+                       match = " " + match[1].replace(/\\/g, "") + " ";
+
+                       if ( isXML ) {
+                               return match;
+                       }
+
+                       for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
+                               if ( elem ) {
+                                       if ( not ^ (elem.className && (" " + elem.className + " ").indexOf(match) >= 0) ) {
+                                               if ( !inplace )
+                                                       result.push( elem );
+                                       } else if ( inplace ) {
+                                               curLoop[i] = false;
+                                       }
+                               }
+                       }
+
+                       return false;
+               },
+               ID: function(match){
+                       return match[1].replace(/\\/g, "");
+               },
+               TAG: function(match, curLoop){
+                       for ( var i = 0; curLoop[i] === false; i++ ){}
+                       return curLoop[i] && isXML(curLoop[i]) ? match[1] : match[1].toUpperCase();
+               },
+               CHILD: function(match){
+                       if ( match[1] == "nth" ) {
+                               // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
+                               var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(
+                                       match[2] == "even" && "2n" || match[2] == "odd" && "2n+1" ||
+                                       !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
+
+                               // calculate the numbers (first)n+(last) including if they are negative
+                               match[2] = (test[1] + (test[2] || 1)) - 0;
+                               match[3] = test[3] - 0;
+                       }
+
+                       // TODO: Move to normal caching system
+                       match[0] = done++;
+
+                       return match;
+               },
+               ATTR: function(match, curLoop, inplace, result, not, isXML){
+                       var name = match[1].replace(/\\/g, "");
+                       
+                       if ( !isXML && Expr.attrMap[name] ) {
+                               match[1] = Expr.attrMap[name];
+                       }
+
+                       if ( match[2] === "~=" ) {
+                               match[4] = " " + match[4] + " ";
+                       }
+
+                       return match;
+               },
+               PSEUDO: function(match, curLoop, inplace, result, not){
+                       if ( match[1] === "not" ) {
+                               // If we're dealing with a complex expression, or a simple one
+                               if ( match[3].match(chunker).length > 1 || /^\w/.test(match[3]) ) {
+                                       match[3] = Sizzle(match[3], null, null, curLoop);
+                               } else {
+                                       var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
+                                       if ( !inplace ) {
+                                               result.push.apply( result, ret );
+                                       }
+                                       return false;
+                               }
+                       } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
+                               return true;
+                       }
+                       
+                       return match;
+               },
+               POS: function(match){
+                       match.unshift( true );
+                       return match;
+               }
+       },
+       filters: {
+               enabled: function(elem){
+                       return elem.disabled === false && elem.type !== "hidden";
+               },
+               disabled: function(elem){
+                       return elem.disabled === true;
+               },
+               checked: function(elem){
+                       return elem.checked === true;
+               },
+               selected: function(elem){
+                       // Accessing this property makes selected-by-default
+                       // options in Safari work properly
+                       elem.parentNode.selectedIndex;
+                       return elem.selected === true;
+               },
+               parent: function(elem){
+                       return !!elem.firstChild;
+               },
+               empty: function(elem){
+                       return !elem.firstChild;
+               },
+               has: function(elem, i, match){
+                       return !!Sizzle( match[3], elem ).length;
+               },
+               header: function(elem){
+                       return /h\d/i.test( elem.nodeName );
+               },
+               text: function(elem){
+                       return "text" === elem.type;
+               },
+               radio: function(elem){
+                       return "radio" === elem.type;
+               },
+               checkbox: function(elem){
+                       return "checkbox" === elem.type;
+               },
+               file: function(elem){
+                       return "file" === elem.type;
+               },
+               password: function(elem){
+                       return "password" === elem.type;
+               },
+               submit: function(elem){
+                       return "submit" === elem.type;
+               },
+               image: function(elem){
+                       return "image" === elem.type;
+               },
+               reset: function(elem){
+                       return "reset" === elem.type;
+               },
+               button: function(elem){
+                       return "button" === elem.type || elem.nodeName.toUpperCase() === "BUTTON";
+               },
+               input: function(elem){
+                       return /input|select|textarea|button/i.test(elem.nodeName);
+               }
+       },
+       setFilters: {
+               first: function(elem, i){
+                       return i === 0;
+               },
+               last: function(elem, i, match, array){
+                       return i === array.length - 1;
+               },
+               even: function(elem, i){
+                       return i % 2 === 0;
+               },
+               odd: function(elem, i){
+                       return i % 2 === 1;
+               },
+               lt: function(elem, i, match){
+                       return i < match[3] - 0;
+               },
+               gt: function(elem, i, match){
+                       return i > match[3] - 0;
+               },
+               nth: function(elem, i, match){
+                       return match[3] - 0 == i;
+               },
+               eq: function(elem, i, match){
+                       return match[3] - 0 == i;
+               }
+       },
+       filter: {
+               PSEUDO: function(elem, match, i, array){
+                       var name = match[1], filter = Expr.filters[ name ];
+
+                       if ( filter ) {
+                               return filter( elem, i, match, array );
+                       } else if ( name === "contains" ) {
+                               return (elem.textContent || elem.innerText || "").indexOf(match[3]) >= 0;
+                       } else if ( name === "not" ) {
+                               var not = match[3];
+
+                               for ( var i = 0, l = not.length; i < l; i++ ) {
+                                       if ( not[i] === elem ) {
+                                               return false;
+                                       }
+                               }
+
+                               return true;
+                       }
+               },
+               CHILD: function(elem, match){
+                       var type = match[1], node = elem;
+                       switch (type) {
+                               case 'only':
+                               case 'first':
+                                       while (node = node.previousSibling)  {
+                                               if ( node.nodeType === 1 ) return false;
+                                       }
+                                       if ( type == 'first') return true;
+                                       node = elem;
+                               case 'last':
+                                       while (node = node.nextSibling)  {
+                                               if ( node.nodeType === 1 ) return false;
+                                       }
+                                       return true;
+                               case 'nth':
+                                       var first = match[2], last = match[3];
+
+                                       if ( first == 1 && last == 0 ) {
+                                               return true;
+                                       }
+                                       
+                                       var doneName = match[0],
+                                               parent = elem.parentNode;
+       
+                                       if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {
+                                               var count = 0;
+                                               for ( node = parent.firstChild; node; node = node.nextSibling ) {
+                                                       if ( node.nodeType === 1 ) {
+                                                               node.nodeIndex = ++count;
+                                                       }
+                                               } 
+                                               parent.sizcache = doneName;
+                                       }
+                                       
+                                       var diff = elem.nodeIndex - last;
+                                       if ( first == 0 ) {
+                                               return diff == 0;
+                                       } else {
+                                               return ( diff % first == 0 && diff / first >= 0 );
+                                       }
+                       }
+               },
+               ID: function(elem, match){
+                       return elem.nodeType === 1 && elem.getAttribute("id") === match;
+               },
+               TAG: function(elem, match){
+                       return (match === "*" && elem.nodeType === 1) || elem.nodeName === match;
+               },
+               CLASS: function(elem, match){
+                       return (" " + (elem.className || elem.getAttribute("class")) + " ")
+                               .indexOf( match ) > -1;
+               },
+               ATTR: function(elem, match){
+                       var name = match[1],
+                               result = Expr.attrHandle[ name ] ?
+                                       Expr.attrHandle[ name ]( elem ) :
+                                       elem[ name ] != null ?
+                                               elem[ name ] :
+                                               elem.getAttribute( name ),
+                               value = result + "",
+                               type = match[2],
+                               check = match[4];
+
+                       return result == null ?
+                               type === "!=" :
+                               type === "=" ?
+                               value === check :
+                               type === "*=" ?
+                               value.indexOf(check) >= 0 :
+                               type === "~=" ?
+                               (" " + value + " ").indexOf(check) >= 0 :
+                               !check ?
+                               value && result !== false :
+                               type === "!=" ?
+                               value != check :
+                               type === "^=" ?
+                               value.indexOf(check) === 0 :
+                               type === "$=" ?
+                               value.substr(value.length - check.length) === check :
+                               type === "|=" ?
+                               value === check || value.substr(0, check.length + 1) === check + "-" :
+                               false;
+               },
+               POS: function(elem, match, i, array){
+                       var name = match[2], filter = Expr.setFilters[ name ];
+
+                       if ( filter ) {
+                               return filter( elem, i, match, array );
+                       }
+               }
+       }
+};
+
+var origPOS = Expr.match.POS;
+
+for ( var type in Expr.match ) {
+       Expr.match[ type ] = RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source );
+}
+
+var makeArray = function(array, results) {
+       array = Array.prototype.slice.call( array );
+
+       if ( results ) {
+               results.push.apply( results, array );
+               return results;
+       }
+       
+       return array;
+};
+
+// Perform a simple check to determine if the browser is capable of
+// converting a NodeList to an array using builtin methods.
+try {
+       Array.prototype.slice.call( document.documentElement.childNodes );
+
+// Provide a fallback method if it does not work
+} catch(e){
+       makeArray = function(array, results) {
+               var ret = results || [];
+
+               if ( toString.call(array) === "[object Array]" ) {
+                       Array.prototype.push.apply( ret, array );
+               } else {
+                       if ( typeof array.length === "number" ) {
+                               for ( var i = 0, l = array.length; i < l; i++ ) {
+                                       ret.push( array[i] );
+                               }
+                       } else {
+                               for ( var i = 0; array[i]; i++ ) {
+                                       ret.push( array[i] );
+                               }
+                       }
+               }
+
+               return ret;
+       };
+}
+
+var sortOrder;
+
+if ( document.documentElement.compareDocumentPosition ) {
+       sortOrder = function( a, b ) {
+               var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
+               if ( ret === 0 ) {
+                       hasDuplicate = true;
+               }
+               return ret;
+       };
+} else if ( "sourceIndex" in document.documentElement ) {
+       sortOrder = function( a, b ) {
+               var ret = a.sourceIndex - b.sourceIndex;
+               if ( ret === 0 ) {
+                       hasDuplicate = true;
+               }
+               return ret;
+       };
+} else if ( document.createRange ) {
+       sortOrder = function( a, b ) {
+               var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
+               aRange.selectNode(a);
+               aRange.collapse(true);
+               bRange.selectNode(b);
+               bRange.collapse(true);
+               var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
+               if ( ret === 0 ) {
+                       hasDuplicate = true;
+               }
+               return ret;
+       };
+}
+
+// Check to see if the browser returns elements by name when
+// querying by getElementById (and provide a workaround)
+(function(){
+       // We're going to inject a fake input element with a specified name
+       var form = document.createElement("form"),
+               id = "script" + (new Date).getTime();
+       form.innerHTML = "<input name='" + id + "'/>";
+
+       // Inject it into the root element, check its status, and remove it quickly
+       var root = document.documentElement;
+       root.insertBefore( form, root.firstChild );
+
+       // The workaround has to do additional checks after a getElementById
+       // Which slows things down for other browsers (hence the branching)
+       if ( !!document.getElementById( id ) ) {
+               Expr.find.ID = function(match, context, isXML){
+                       if ( typeof context.getElementById !== "undefined" && !isXML ) {
+                               var m = context.getElementById(match[1]);
+                               return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : [];
+                       }
+               };
+
+               Expr.filter.ID = function(elem, match){
+                       var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
+                       return elem.nodeType === 1 && node && node.nodeValue === match;
+               };
+       }
+
+       root.removeChild( form );
+})();
+
+(function(){
+       // Check to see if the browser returns only elements
+       // when doing getElementsByTagName("*")
+
+       // Create a fake element
+       var div = document.createElement("div");
+       div.appendChild( document.createComment("") );
+
+       // Make sure no comments are found
+       if ( div.getElementsByTagName("*").length > 0 ) {
+               Expr.find.TAG = function(match, context){
+                       var results = context.getElementsByTagName(match[1]);
+
+                       // Filter out possible comments
+                       if ( match[1] === "*" ) {
+                               var tmp = [];
+
+                               for ( var i = 0; results[i]; i++ ) {
+                                       if ( results[i].nodeType === 1 ) {
+                                               tmp.push( results[i] );
+                                       }
+                               }
+
+                               results = tmp;
+                       }
+
+                       return results;
+               };
+       }
+
+       // Check to see if an attribute returns normalized href attributes
+       div.innerHTML = "<a href='#'></a>";
+       if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
+                       div.firstChild.getAttribute("href") !== "#" ) {
+               Expr.attrHandle.href = function(elem){
+                       return elem.getAttribute("href", 2);
+               };
+       }
+})();
+
+if ( document.querySelectorAll ) (function(){
+       var oldSizzle = Sizzle, div = document.createElement("div");
+       div.innerHTML = "<p class='TEST'></p>";
+
+       // Safari can't handle uppercase or unicode characters when
+       // in quirks mode.
+       if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
+               return;
+       }
+       
+       Sizzle = function(query, context, extra, seed){
+               context = context || document;
+
+               // Only use querySelectorAll on non-XML documents
+               // (ID selectors don't work in non-HTML documents)
+               if ( !seed && context.nodeType === 9 && !isXML(context) ) {
+                       try {
+                               return makeArray( context.querySelectorAll(query), extra );
+                       } catch(e){}
+               }
+               
+               return oldSizzle(query, context, extra, seed);
+       };
+
+       Sizzle.find = oldSizzle.find;
+       Sizzle.filter = oldSizzle.filter;
+       Sizzle.selectors = oldSizzle.selectors;
+       Sizzle.matches = oldSizzle.matches;
+})();
+
+if ( document.getElementsByClassName && document.documentElement.getElementsByClassName ) (function(){
+       var div = document.createElement("div");
+       div.innerHTML = "<div class='test e'></div><div class='test'></div>";
+
+       // Opera can't find a second classname (in 9.6)
+       if ( div.getElementsByClassName("e").length === 0 )
+               return;
+
+       // Safari caches class attributes, doesn't catch changes (in 3.2)
+       div.lastChild.className = "e";
+
+       if ( div.getElementsByClassName("e").length === 1 )
+               return;
+
+       Expr.order.splice(1, 0, "CLASS");
+       Expr.find.CLASS = function(match, context, isXML) {
+               if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
+                       return context.getElementsByClassName(match[1]);
+               }
+       };
+})();
+
+function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
+       var sibDir = dir == "previousSibling" && !isXML;
+       for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+               var elem = checkSet[i];
+               if ( elem ) {
+                       if ( sibDir && elem.nodeType === 1 ){
+                               elem.sizcache = doneName;
+                               elem.sizset = i;
+                       }
+                       elem = elem[dir];
+                       var match = false;
+
+                       while ( elem ) {
+                               if ( elem.sizcache === doneName ) {
+                                       match = checkSet[elem.sizset];
+                                       break;
+                               }
+
+                               if ( elem.nodeType === 1 && !isXML ){
+                                       elem.sizcache = doneName;
+                                       elem.sizset = i;
+                               }
+
+                               if ( elem.nodeName === cur ) {
+                                       match = elem;
+                                       break;
+                               }
+
+                               elem = elem[dir];
+                       }
+
+                       checkSet[i] = match;
+               }
+       }
+}
+
+function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
+       var sibDir = dir == "previousSibling" && !isXML;
+       for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+               var elem = checkSet[i];
+               if ( elem ) {
+                       if ( sibDir && elem.nodeType === 1 ) {
+                               elem.sizcache = doneName;
+                               elem.sizset = i;
+                       }
+                       elem = elem[dir];
+                       var match = false;
+
+                       while ( elem ) {
+                               if ( elem.sizcache === doneName ) {
+                                       match = checkSet[elem.sizset];
+                                       break;
+                               }
+
+                               if ( elem.nodeType === 1 ) {
+                                       if ( !isXML ) {
+                                               elem.sizcache = doneName;
+                                               elem.sizset = i;
+                                       }
+                                       if ( typeof cur !== "string" ) {
+                                               if ( elem === cur ) {
+                                                       match = true;
+                                                       break;
+                                               }
+
+                                       } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
+                                               match = elem;
+                                               break;
+                                       }
+                               }
+
+                               elem = elem[dir];
+                       }
+
+                       checkSet[i] = match;
+               }
+       }
+}
+
+var contains = document.compareDocumentPosition ?  function(a, b){
+       return a.compareDocumentPosition(b) & 16;
+} : function(a, b){
+       return a !== b && (a.contains ? a.contains(b) : true);
+};
+
+var isXML = function(elem){
+       return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" ||
+               !!elem.ownerDocument && isXML( elem.ownerDocument );
+};
+
+var posProcess = function(selector, context){
+       var tmpSet = [], later = "", match,
+               root = context.nodeType ? [context] : context;
+
+       // Position selectors must be done after the filter
+       // And so must :not(positional) so we move all PSEUDOs to the end
+       while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
+               later += match[0];
+               selector = selector.replace( Expr.match.PSEUDO, "" );
+       }
+
+       selector = Expr.relative[selector] ? selector + "*" : selector;
+
+       for ( var i = 0, l = root.length; i < l; i++ ) {
+               Sizzle( selector, root[i], tmpSet );
+       }
+
+       return Sizzle.filter( later, tmpSet );
+};
+
+// EXPOSE
+jQuery.find = Sizzle;
+jQuery.filter = Sizzle.filter;
+jQuery.expr = Sizzle.selectors;
+jQuery.expr[":"] = jQuery.expr.filters;
+
+Sizzle.selectors.filters.hidden = function(elem){
+       return elem.offsetWidth === 0 || elem.offsetHeight === 0;
+};
+
+Sizzle.selectors.filters.visible = function(elem){
+       return elem.offsetWidth > 0 || elem.offsetHeight > 0;
+};
+
+Sizzle.selectors.filters.animated = function(elem){
+       return jQuery.grep(jQuery.timers, function(fn){
+               return elem === fn.elem;
+       }).length;
+};
+
+jQuery.multiFilter = function( expr, elems, not ) {
+       if ( not ) {
+               expr = ":not(" + expr + ")";
+       }
+
+       return Sizzle.matches(expr, elems);
+};
+
+jQuery.dir = function( elem, dir ){
+       var matched = [], cur = elem[dir];
+       while ( cur && cur != document ) {
+               if ( cur.nodeType == 1 )
+                       matched.push( cur );
+               cur = cur[dir];
+       }
+       return matched;
+};
+
+jQuery.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;
+};
+
+jQuery.sibling = function(n, elem){
+       var r = [];
+
+       for ( ; n; n = n.nextSibling ) {
+               if ( n.nodeType == 1 && n != elem )
+                       r.push( n );
+       }
+
+       return r;
+};
+
+return;
+
+window.Sizzle = Sizzle;
+
+})();
+/*
+ * A number of helper functions used for managing events.
+ * Many of the ideas behind this code originated from
+ * Dean Edwards' addEvent library.
+ */
+jQuery.event = {
+
+       // Bind an event to an element
+       // Original by Dean Edwards
+       add: function(elem, types, handler, data) {
+               if ( elem.nodeType == 3 || elem.nodeType == 8 )
+                       return;
+
+               // For whatever reason, IE has trouble passing the window object
+               // around, causing it to be cloned in the process
+               if ( elem.setInterval && elem != window )
+                       elem = window;
+
+               // Make sure that the function being executed has a unique ID
+               if ( !handler.guid )
+                       handler.guid = this.guid++;
+
+               // if data is passed, bind to handler
+               if ( data !== undefined ) {
+                       // Create temporary function pointer to original handler
+                       var fn = handler;
+
+                       // Create unique handler function, wrapped around original handler
+                       handler = this.proxy( fn );
+
+                       // Store data in unique handler
+                       handler.data = data;
+               }
+
+               // Init the element's event structure
+               var events = jQuery.data(elem, "events") || jQuery.data(elem, "events", {}),
+                       handle = jQuery.data(elem, "handle") || jQuery.data(elem, "handle", function(){
+                               // Handle the second event of a trigger and when
+                               // an event is called after a page has unloaded
+                               return typeof jQuery !== "undefined" && !jQuery.event.triggered ?
+                                       jQuery.event.handle.apply(arguments.callee.elem, arguments) :
+                                       undefined;
+                       });
+               // Add elem as a property of the handle function
+               // This is to prevent a memory leak with non-native
+               // event in IE.
+               handle.elem = elem;
+
+               // Handle multiple events separated by a space
+               // jQuery(...).bind("mouseover mouseout", fn);
+               jQuery.each(types.split(/\s+/), function(index, type) {
+                       // Namespaced event handlers
+                       var namespaces = type.split(".");
+                       type = namespaces.shift();
+                       handler.type = namespaces.slice().sort().join(".");
+
+                       // Get the current list of functions bound to this event
+                       var handlers = events[type];
+                       
+                       if ( jQuery.event.specialAll[type] )
+                               jQuery.event.specialAll[type].setup.call(elem, data, namespaces);
+
+                       // Init the event handler queue
+                       if (!handlers) {
+                               handlers = events[type] = {};
+
+                               // Check for a special event handler
+                               // Only use addEventListener/attachEvent if the special
+                               // events handler returns false
+                               if ( !jQuery.event.special[type] || jQuery.event.special[type].setup.call(elem, data, namespaces) === false ) {
+                                       // Bind the global event handler to the element
+                                       if (elem.addEventListener)
+                                               elem.addEventListener(type, handle, false);
+                                       else if (elem.attachEvent)
+                                               elem.attachEvent("on" + type, handle);
+                               }
+                       }
+
+                       // Add the function to the element's handler list
+                       handlers[handler.guid] = handler;
+
+                       // Keep track of which events have been used, for global triggering
+                       jQuery.event.global[type] = true;
+               });
+
+               // Nullify elem to prevent memory leaks in IE
+               elem = null;
+       },
+
+       guid: 1,
+       global: {},
+
+       // Detach an event or set of events from an element
+       remove: function(elem, types, handler) {
+               // don't do events on text and comment nodes
+               if ( elem.nodeType == 3 || elem.nodeType == 8 )
+                       return;
+
+               var events = jQuery.data(elem, "events"), ret, index;
+
+               if ( events ) {
+                       // Unbind all events for the element
+                       if ( types === undefined || (typeof types === "string" && types.charAt(0) == ".") )
+                               for ( var type in events )
+                                       this.remove( elem, type + (types || "") );
+                       else {
+                               // types is actually an event object here
+                               if ( types.type ) {
+                                       handler = types.handler;
+                                       types = types.type;
+                               }
+
+                               // Handle multiple events seperated by a space
+                               // jQuery(...).unbind("mouseover mouseout", fn);
+                               jQuery.each(types.split(/\s+/), function(index, type){
+                                       // Namespaced event handlers
+                                       var namespaces = type.split(".");
+                                       type = namespaces.shift();
+                                       var namespace = RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)");
+
+                                       if ( events[type] ) {
+                                               // remove the given handler for the given type
+                                               if ( handler )
+                                                       delete events[type][handler.guid];
+
+                                               // remove all handlers for the given type
+                                               else
+                                                       for ( var handle in events[type] )
+                                                               // Handle the removal of namespaced events
+                                                               if ( namespace.test(events[type][handle].type) )
+                                                                       delete events[type][handle];
+                                                                       
+                                               if ( jQuery.event.specialAll[type] )
+                                                       jQuery.event.specialAll[type].teardown.call(elem, namespaces);
+
+                                               // remove generic event handler if no more handlers exist
+                                               for ( ret in events[type] ) break;
+                                               if ( !ret ) {
+                                                       if ( !jQuery.event.special[type] || jQuery.event.special[type].teardown.call(elem, namespaces) === false ) {
+                                                               if (elem.removeEventListener)
+                                                                       elem.removeEventListener(type, jQuery.data(elem, "handle"), false);
+                                                               else if (elem.detachEvent)
+                                                                       elem.detachEvent("on" + type, jQuery.data(elem, "handle"));
+                                                       }
+                                                       ret = null;
+                                                       delete events[type];
+                                               }
+                                       }
+                               });
+                       }
+
+                       // Remove the expando if it's no longer used
+                       for ( ret in events ) break;
+                       if ( !ret ) {
+                               var handle = jQuery.data( elem, "handle" );
+                               if ( handle ) handle.elem = null;
+                               jQuery.removeData( elem, "events" );
+                               jQuery.removeData( elem, "handle" );
+                       }
+               }
+       },
+
+       // bubbling is internal
+       trigger: function( event, data, elem, bubbling ) {
+               // Event object or event type
+               var type = event.type || event;
+
+               if( !bubbling ){
+                       event = typeof event === "object" ?
+                               // jQuery.Event object
+                               event[expando] ? event :
+                               // Object literal
+                               jQuery.extend( jQuery.Event(type), event ) :
+                               // Just the event type (string)
+                               jQuery.Event(type);
+
+                       if ( type.indexOf("!") >= 0 ) {
+                               event.type = type = type.slice(0, -1);
+                               event.exclusive = true;
+                       }
+
+                       // Handle a global trigger
+                       if ( !elem ) {
+                               // Don't bubble custom events when global (to avoid too much overhead)
+                               event.stopPropagation();
+                               // Only trigger if we've ever bound an event for it
+                               if ( this.global[type] )
+                                       jQuery.each( jQuery.cache, function(){
+                                               if ( this.events && this.events[type] )
+                                                       jQuery.event.trigger( event, data, this.handle.elem );
+                                       });
+                       }
+
+                       // Handle triggering a single element
+
+                       // don't do events on text and comment nodes
+                       if ( !elem || elem.nodeType == 3 || elem.nodeType == 8 )
+                               return undefined;
+                       
+                       // Clean up in case it is reused
+                       event.result = undefined;
+                       event.target = elem;
+                       
+                       // Clone the incoming data, if any
+                       data = jQuery.makeArray(data);
+                       data.unshift( event );
+               }
+
+               event.currentTarget = elem;
+
+               // Trigger the event, it is assumed that "handle" is a function
+               var handle = jQuery.data(elem, "handle");
+               if ( handle )
+                       handle.apply( elem, data );
+
+               // Handle triggering native .onfoo handlers (and on links since we don't call .click() for links)
+               if ( (!elem[type] || (jQuery.nodeName(elem, 'a') && type == "click")) && elem["on"+type] && elem["on"+type].apply( elem, data ) === false )
+                       event.result = false;
+
+               // Trigger the native events (except for clicks on links)
+               if ( !bubbling && elem[type] && !event.isDefaultPrevented() && !(jQuery.nodeName(elem, 'a') && type == "click") ) {
+                       this.triggered = true;
+                       try {
+                               elem[ type ]();
+                       // prevent IE from throwing an error for some hidden elements
+                       } catch (e) {}
+               }
+
+               this.triggered = false;
+
+               if ( !event.isPropagationStopped() ) {
+                       var parent = elem.parentNode || elem.ownerDocument;
+                       if ( parent )
+                               jQuery.event.trigger(event, data, parent, true);
+               }
+       },
+
+       handle: function(event) {
+               // returned undefined or false
+               var all, handlers;
+
+               event = arguments[0] = jQuery.event.fix( event || window.event );
+               event.currentTarget = this;
+               
+               // Namespaced event handlers
+               var namespaces = event.type.split(".");
+               event.type = namespaces.shift();
+
+               // Cache this now, all = true means, any handler
+               all = !namespaces.length && !event.exclusive;
+               
+               var namespace = RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)");
+
+               handlers = ( jQuery.data(this, "events") || {} )[event.type];
+
+               for ( var j in handlers ) {
+                       var handler = handlers[j];
+
+                       // Filter the functions by class
+                       if ( all || namespace.test(handler.type) ) {
+                               // Pass in a reference to the handler function itself
+                               // So that we can later remove it
+                               event.handler = handler;
+                               event.data = handler.data;
+
+                               var ret = handler.apply(this, arguments);
+
+                               if( ret !== undefined ){
+                                       event.result = ret;
+                                       if ( ret === false ) {
+                                               event.preventDefault();
+                                               event.stopPropagation();
+                                       }
+                               }
+
+                               if( event.isImmediatePropagationStopped() )
+                                       break;
+
+                       }
+               }
+       },
+
+       props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),
+
+       fix: function(event) {
+               if ( event[expando] )
+                       return event;
+
+               // store a copy of the original event object
+               // and "clone" to set read-only properties
+               var originalEvent = event;
+               event = jQuery.Event( originalEvent );
+
+               for ( var i = this.props.length, prop; i; ){
+                       prop = this.props[ --i ];
+                       event[ prop ] = originalEvent[ prop ];
+               }
+
+               // Fix target property, if necessary
+               if ( !event.target )
+                       event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either
+
+               // check if target is a textnode (safari)
+               if ( event.target.nodeType == 3 )
+                       event.target = event.target.parentNode;
+
+               // Add relatedTarget, if necessary
+               if ( !event.relatedTarget && event.fromElement )
+                       event.relatedTarget = event.fromElement == event.target ? event.toElement : event.fromElement;
+
+               // Calculate pageX/Y if missing and clientX/Y available
+               if ( event.pageX == null && event.clientX != null ) {
+                       var doc = document.documentElement, body = document.body;
+                       event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc.clientLeft || 0);
+                       event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc.clientTop || 0);
+               }
+
+               // Add which for key events
+               if ( !event.which && ((event.charCode || event.charCode === 0) ? event.charCode : event.keyCode) )
+                       event.which = event.charCode || event.keyCode;
+
+               // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
+               if ( !event.metaKey && event.ctrlKey )
+                       event.metaKey = event.ctrlKey;
+
+               // Add which for click: 1 == left; 2 == middle; 3 == right
+               // Note: button is not normalized, so don't use it
+               if ( !event.which && event.button )
+                       event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));
+
+               return event;
+       },
+
+       proxy: function( fn, proxy ){
+               proxy = proxy || function(){ return fn.apply(this, arguments); };
+               // Set the guid of unique handler to the same of original handler, so it can be removed
+               proxy.guid = fn.guid = fn.guid || proxy.guid || this.guid++;
+               // So proxy can be declared as an argument
+               return proxy;
+       },
+
+       special: {
+               ready: {
+                       // Make sure the ready event is setup
+                       setup: bindReady,
+                       teardown: function() {}
+               }
+       },
+       
+       specialAll: {
+               live: {
+                       setup: function( selector, namespaces ){
+                               jQuery.event.add( this, namespaces[0], liveHandler );
+                       },
+                       teardown:  function( namespaces ){
+                               if ( namespaces.length ) {
+                                       var remove = 0, name = RegExp("(^|\\.)" + namespaces[0] + "(\\.|$)");
+                                       
+                                       jQuery.each( (jQuery.data(this, "events").live || {}), function(){
+                                               if ( name.test(this.type) )
+                                                       remove++;
+                                       });
+                                       
+                                       if ( remove < 1 )
+                                               jQuery.event.remove( this, namespaces[0], liveHandler );
+                               }
+                       }
+               }
+       }
+};
+
+jQuery.Event = function( src ){
+       // Allow instantiation without the 'new' keyword
+       if( !this.preventDefault )
+               return new jQuery.Event(src);
+       
+       // Event object
+       if( src && src.type ){
+               this.originalEvent = src;
+               this.type = src.type;
+       // Event type
+       }else
+               this.type = src;
+
+       // timeStamp is buggy for some events on Firefox(#3843)
+       // So we won't rely on the native value
+       this.timeStamp = now();
+       
+       // Mark it as fixed
+       this[expando] = true;
+};
+
+function returnFalse(){
+       return false;
+}
+function returnTrue(){
+       return true;
+}
+
+// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
+// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
+jQuery.Event.prototype = {
+       preventDefault: function() {
+               this.isDefaultPrevented = returnTrue;
+
+               var e = this.originalEvent;
+               if( !e )
+                       return;
+               // if preventDefault exists run it on the original event
+               if (e.preventDefault)
+                       e.preventDefault();
+               // otherwise set the returnValue property of the original event to false (IE)
+               e.returnValue = false;
+       },
+       stopPropagation: function() {
+               this.isPropagationStopped = returnTrue;
+
+               var e = this.originalEvent;
+               if( !e )
+                       return;
+               // if stopPropagation exists run it on the original event
+               if (e.stopPropagation)
+                       e.stopPropagation();
+               // otherwise set the cancelBubble property of the original event to true (IE)
+               e.cancelBubble = true;
+       },
+       stopImmediatePropagation:function(){
+               this.isImmediatePropagationStopped = returnTrue;
+               this.stopPropagation();
+       },
+       isDefaultPrevented: returnFalse,
+       isPropagationStopped: returnFalse,
+       isImmediatePropagationStopped: returnFalse
+};
+// Checks if an event happened on an element within another element
+// Used in jQuery.event.special.mouseenter and mouseleave handlers
+var withinElement = function(event) {
+       // Check if mouse(over|out) are still within the same parent element
+       var parent = event.relatedTarget;
+       // Traverse up the tree
+       while ( parent && parent != this )
+               try { parent = parent.parentNode; }
+               catch(e) { parent = this; }
+       
+       if( parent != this ){
+               // set the correct event type
+               event.type = event.data;
+               // handle event if we actually just moused on to a non sub-element
+               jQuery.event.handle.apply( this, arguments );
+       }
+};
+       
+jQuery.each({ 
+       mouseover: 'mouseenter', 
+       mouseout: 'mouseleave'
+}, function( orig, fix ){
+       jQuery.event.special[ fix ] = {
+               setup: function(){
+                       jQuery.event.add( this, orig, withinElement, fix );
+               },
+               teardown: function(){
+                       jQuery.event.remove( this, orig, withinElement );
+               }
+       };                         
+});
+
+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 ) {
+               var one = jQuery.event.proxy( fn || data, function(event) {
+                       jQuery(this).unbind(event, one);
+                       return (fn || data).apply( this, arguments );
+               });
+               return this.each(function(){
+                       jQuery.event.add( this, type, one, fn && data);
+               });
+       },
+
+       unbind: function( type, fn ) {
+               return this.each(function(){
+                       jQuery.event.remove( this, type, fn );
+               });
+       },
+
+       trigger: function( type, data ) {
+               return this.each(function(){
+                       jQuery.event.trigger( type, data, this );
+               });
+       },
+
+       triggerHandler: function( type, data ) {
+               if( this[0] ){
+                       var event = jQuery.Event(type);
+                       event.preventDefault();
+                       event.stopPropagation();
+                       jQuery.event.trigger( event, data, this[0] );
+                       return event.result;
+               }               
+       },
+
+       toggle: function( fn ) {
+               // Save reference to arguments for access in closure
+               var args = arguments, i = 1;
+
+               // link all the functions, so any of them can unbind this click handler
+               while( i < args.length )
+                       jQuery.event.proxy( fn, args[i++] );
+
+               return this.click( jQuery.event.proxy( fn, function(event) {
+                       // Figure out which function to execute
+                       this.lastToggle = ( this.lastToggle || 0 ) % i;
+
+                       // Make sure that clicks stop
+                       event.preventDefault();
+
+                       // and execute the function
+                       return args[ this.lastToggle++ ].apply( this, arguments ) || false;
+               }));
+       },
+
+       hover: function(fnOver, fnOut) {
+               return this.mouseenter(fnOver).mouseleave(fnOut);
+       },
+
+       ready: function(fn) {
+               // Attach the listeners
+               bindReady();
+
+               // If the DOM is already ready
+               if ( jQuery.isReady )
+                       // Execute the function immediately
+                       fn.call( document, jQuery );
+
+               // Otherwise, remember the function for later
+               else
+                       // Add the function to the wait list
+                       jQuery.readyList.push( fn );
+
+               return this;
+       },
+       
+       live: function( type, fn ){
+               var proxy = jQuery.event.proxy( fn );
+               proxy.guid += this.selector + type;
+
+               jQuery(document).bind( liveConvert(type, this.selector), this.selector, proxy );
+
+               return this;
+       },
+       
+       die: function( type, fn ){
+               jQuery(document).unbind( liveConvert(type, this.selector), fn ? { guid: fn.guid + this.selector + type } : null );
+               return this;
+       }
+});
+
+function liveHandler( event ){
+       var check = RegExp("(^|\\.)" + event.type + "(\\.|$)"),
+               stop = true,
+               elems = [];
+
+       jQuery.each(jQuery.data(this, "events").live || [], function(i, fn){
+               if ( check.test(fn.type) ) {
+                       var elem = jQuery(event.target).closest(fn.data)[0];
+                       if ( elem )
+                               elems.push({ elem: elem, fn: fn });
+               }
+       });
+
+       elems.sort(function(a,b) {
+               return jQuery.data(a.elem, "closest") - jQuery.data(b.elem, "closest");
+       });
+       
+       jQuery.each(elems, function(){
+               if ( this.fn.call(this.elem, event, this.fn.data) === false )
+                       return (stop = false);
+       });
+
+       return stop;
+}
+
+function liveConvert(type, selector){
+       return ["live", type, selector.replace(/\./g, "`").replace(/ /g, "|")].join(".");
+}
+
+jQuery.extend({
+       isReady: false,
+       readyList: [],
+       // Handle when the DOM is ready
+       ready: function() {
+               // Make sure that the DOM is not already loaded
+               if ( !jQuery.isReady ) {
+                       // Remember that the DOM is ready
+                       jQuery.isReady = true;
+
+                       // If there are functions bound, to execute
+                       if ( jQuery.readyList ) {
+                               // Execute all of them
+                               jQuery.each( jQuery.readyList, function(){
+                                       this.call( document, jQuery );
+                               });
+
+                               // Reset the list of functions
+                               jQuery.readyList = null;
+                       }
+
+                       // Trigger any bound ready events
+                       jQuery(document).triggerHandler("ready");
+               }
+       }
+});
+
+var readyBound = false;
+
+function bindReady(){
+       if ( readyBound ) return;
+       readyBound = true;
+
+       // Mozilla, Opera and webkit nightlies currently support this event
+       if ( document.addEventListener ) {
+               // Use the handy event callback
+               document.addEventListener( "DOMContentLoaded", function(){
+                       document.removeEventListener( "DOMContentLoaded", arguments.callee, false );
+                       jQuery.ready();
+               }, false );
+
+       // If IE event model is used
+       } else if ( document.attachEvent ) {
+               // ensure firing before onload,
+               // maybe late but safe also for iframes
+               document.attachEvent("onreadystatechange", function(){
+                       if ( document.readyState === "complete" ) {
+                               document.detachEvent( "onreadystatechange", arguments.callee );
+                               jQuery.ready();
+                       }
+               });
+
+               // If IE and not an iframe
+               // continually check to see if the document is ready
+               if ( document.documentElement.doScroll && window == window.top ) (function(){
+                       if ( jQuery.isReady ) return;
+
+                       try {
+                               // If IE is used, use the trick by Diego Perini
+                               // http://javascript.nwbox.com/IEContentLoaded/
+                               document.documentElement.doScroll("left");
+                       } catch( error ) {
+                               setTimeout( arguments.callee, 0 );
+                               return;
+                       }
+
+                       // and execute any waiting functions
+                       jQuery.ready();
+               })();
+       }
+
+       // A fallback to window.onload, that will always work
+       jQuery.event.add( window, "load", jQuery.ready );
+}
+
+jQuery.each( ("blur,focus,load,resize,scroll,unload,click,dblclick," +
+       "mousedown,mouseup,mousemove,mouseover,mouseout,mouseenter,mouseleave," +
+       "change,select,submit,keydown,keypress,keyup,error").split(","), function(i, name){
+
+       // Handle event binding
+       jQuery.fn[name] = function(fn){
+               return fn ? this.bind(name, fn) : this.trigger(name);
+       };
+});
+
+// Prevent memory leaks in IE
+// And prevent errors on refresh with events like mouseover in other browsers
+// Window isn't included so as not to unbind existing unload events
+jQuery( window ).bind( 'unload', function(){ 
+       for ( var id in jQuery.cache )
+               // Skip the window
+               if ( id != 1 && jQuery.cache[ id ].handle )
+                       jQuery.event.remove( jQuery.cache[ id ].handle.elem );
+}); 
+(function(){
+
+       jQuery.support = {};
+
+       var root = document.documentElement,
+               script = document.createElement("script"),
+               div = document.createElement("div"),
+               id = "script" + (new Date).getTime();
+
+       div.style.display = "none";
+       div.innerHTML = '   <link/><table></table><a href="/a" style="color:red;float:left;opacity:.5;">a</a><select><option>text</option></select><object><param/></object>';
+
+       var all = div.getElementsByTagName("*"),
+               a = div.getElementsByTagName("a")[0];
+
+       // Can't get basic test support
+       if ( !all || !all.length || !a ) {
+               return;
+       }
+
+       jQuery.support = {
+               // IE strips leading whitespace when .innerHTML is used
+               leadingWhitespace: div.firstChild.nodeType == 3,
+               
+               // Make sure that tbody elements aren't automatically inserted
+               // IE will insert them into empty tables
+               tbody: !div.getElementsByTagName("tbody").length,
+               
+               // Make sure that you can get all elements in an <object> element
+               // IE 7 always returns no results
+               objectAll: !!div.getElementsByTagName("object")[0]
+                       .getElementsByTagName("*").length,
+               
+               // Make sure that link elements get serialized correctly by innerHTML
+               // This requires a wrapper element in IE
+               htmlSerialize: !!div.getElementsByTagName("link").length,
+               
+               // Get the style information from getAttribute
+               // (IE uses .cssText insted)
+               style: /red/.test( a.getAttribute("style") ),
+               
+               // Make sure that URLs aren't manipulated
+               // (IE normalizes it by default)
+               hrefNormalized: a.getAttribute("href") === "/a",
+               
+               // Make sure that element opacity exists
+               // (IE uses filter instead)
+               opacity: a.style.opacity === "0.5",
+               
+               // Verify style float existence
+               // (IE uses styleFloat instead of cssFloat)
+               cssFloat: !!a.style.cssFloat,
+
+               // Will be defined later
+               scriptEval: false,
+               noCloneEvent: true,
+               boxModel: null
+       };
+       
+       script.type = "text/javascript";
+       try {
+               script.appendChild( document.createTextNode( "window." + id + "=1;" ) );
+       } catch(e){}
+
+       root.insertBefore( script, root.firstChild );
+       
+       // Make sure that the execution of code works by injecting a script
+       // tag with appendChild/createTextNode
+       // (IE doesn't support this, fails, and uses .text instead)
+       if ( window[ id ] ) {
+               jQuery.support.scriptEval = true;
+               delete window[ id ];
+       }
+
+       root.removeChild( script );
+
+       if ( div.attachEvent && div.fireEvent ) {
+               div.attachEvent("onclick", function(){
+                       // Cloning a node shouldn't copy over any
+                       // bound event handlers (IE does this)
+                       jQuery.support.noCloneEvent = false;
+                       div.detachEvent("onclick", arguments.callee);
+               });
+               div.cloneNode(true).fireEvent("onclick");
+       }
+
+       // Figure out if the W3C box model works as expected
+       // document.body must exist before we can do this
+       jQuery(function(){
+               var div = document.createElement("div");
+               div.style.width = div.style.paddingLeft = "1px";
+
+               document.body.appendChild( div );
+               jQuery.boxModel = jQuery.support.boxModel = div.offsetWidth === 2;
+               document.body.removeChild( div ).style.display = 'none';
+       });
+})();
+
+var styleFloat = jQuery.support.cssFloat ? "cssFloat" : "styleFloat";
+
+jQuery.props = {
+       "for": "htmlFor",
+       "class": "className",
+       "float": styleFloat,
+       cssFloat: styleFloat,
+       styleFloat: styleFloat,
+       readonly: "readOnly",
+       maxlength: "maxLength",
+       cellspacing: "cellSpacing",
+       rowspan: "rowSpan",
+       tabindex: "tabIndex"
+};
+jQuery.fn.extend({
+       // Keep a copy of the old load
+       _load: jQuery.fn.load,
+
+       load: function( url, params, callback ) {
+               if ( typeof url !== "string" )
+                       return this._load( url );
+
+               var off = url.indexOf(" ");
+               if ( off >= 0 ) {
+                       var selector = url.slice(off, url.length);
+                       url = url.slice(0, off);
+               }
+
+               // Default to a GET request
+               var type = "GET";
+
+               // If the second parameter was provided
+               if ( params )
+                       // If it's a function
+                       if ( jQuery.isFunction( params ) ) {
+                               // We assume that it's the callback
+                               callback = params;
+                               params = null;
+
+                       // Otherwise, build a param string
+                       } else if( typeof params === "object" ) {
+                               params = jQuery.param( params );
+                               type = "POST";
+                       }
+
+               var self = this;
+
+               // Request the remote document
+               jQuery.ajax({
+                       url: url,
+                       type: type,
+                       dataType: "html",
+                       data: params,
+                       complete: function(res, status){
+                               // If successful, inject the HTML into all the matched elements
+                               if ( status == "success" || status == "notmodified" )
+                                       // See if a selector was specified
+                                       self.html( selector ?
+                                               // Create a dummy div to hold the results
+                                               jQuery("<div/>")
+                                                       // inject the contents of the document in, removing the scripts
+                                                       // to avoid any 'Permission Denied' errors in IE
+                                                       .append(res.responseText.replace(/<script(.|\s)*?\/script>/g, ""))
+
+                                                       // Locate the specified elements
+                                                       .find(selector) :
+
+                                               // If not, just inject the full result
+                                               res.responseText );
+
+                               if( callback )
+                                       self.each( callback, [res.responseText, status, res] );
+                       }
+               });
+               return this;
+       },
+
+       serialize: function() {
+               return jQuery.param(this.serializeArray());
+       },
+       serializeArray: function() {
+               return this.map(function(){
+                       return this.elements ? jQuery.makeArray(this.elements) : this;
+               })
+               .filter(function(){
+                       return this.name && !this.disabled &&
+                               (this.checked || /select|textarea/i.test(this.nodeName) ||
+                                       /text|hidden|password|search/i.test(this.type));
+               })
+               .map(function(i, elem){
+                       var val = jQuery(this).val();
+                       return val == null ? null :
+                               jQuery.isArray(val) ?
+                                       jQuery.map( val, function(val, i){
+                                               return {name: elem.name, value: val};
+                                       }) :
+                                       {name: elem.name, value: val};
+               }).get();
+       }
+});
+
+// Attach a bunch of functions for handling common AJAX events
+jQuery.each( "ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","), function(i,o){
+       jQuery.fn[o] = function(f){
+               return this.bind(o, f);
+       };
+});
+
+var jsc = now();
+
+jQuery.extend({
+  
+       get: function( url, data, callback, type ) {
+               // shift arguments if data argument was ommited
+               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: {
+               url: location.href,
+               global: true,
+               type: "GET",
+               contentType: "application/x-www-form-urlencoded",
+               processData: true,
+               async: true,
+               /*
+               timeout: 0,
+               data: null,
+               username: null,
+               password: null,
+               */
+               // Create the request object; Microsoft failed to properly
+               // implement the XMLHttpRequest in IE7, so we use the ActiveXObject when it is available
+               // This function can be overriden by calling jQuery.ajaxSetup
+               xhr:function(){
+                       return window.ActiveXObject ? new ActiveXObject("Microsoft.XMLHTTP") : new XMLHttpRequest();
+               },
+               accepts: {
+                       xml: "application/xml, text/xml",
+                       html: "text/html",
+                       script: "text/javascript, application/javascript",
+                       json: "application/json, text/javascript",
+                       text: "text/plain",
+                       _default: "*/*"
+               }
+       },
+
+       // Last-Modified header cache for next request
+       lastModified: {},
+
+       ajax: function( s ) {
+               // Extend the settings, but re-extend 's' so that it can be
+               // checked again later (in the test suite, specifically)
+               s = jQuery.extend(true, s, jQuery.extend(true, {}, jQuery.ajaxSettings, s));
+
+               var jsonp, jsre = /=\?(&|$)/g, status, data,
+                       type = s.type.toUpperCase();
+
+               // convert data if not already a string
+               if ( s.data && s.processData && typeof s.data !== "string" )
+                       s.data = jQuery.param(s.data);
+
+               // Handle JSONP Parameter Callbacks
+               if ( s.dataType == "jsonp" ) {
+                       if ( type == "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";
+               }
+
+               // Build temporary JSONP function
+               if ( s.dataType == "json" && (s.data && s.data.match(jsre) || s.url.match(jsre)) ) {
+                       jsonp = "jsonp" + jsc++;
+
+                       // Replace the =? sequence both in the query string and the data
+                       if ( s.data )
+                               s.data = (s.data + "").replace(jsre, "=" + jsonp + "$1");
+                       s.url = s.url.replace(jsre, "=" + jsonp + "$1");
+
+                       // We need to make sure
+                       // that a JSONP style response is executed properly
+                       s.dataType = "script";
+
+                       // Handle JSONP-style loading
+                       window[ jsonp ] = function(tmp){
+                               data = tmp;
+                               success();
+                               complete();
+                               // Garbage collect
+                               window[ jsonp ] = undefined;
+                               try{ delete window[ jsonp ]; } catch(e){}
+                               if ( head )
+                                       head.removeChild( script );
+                       };
+               }
+
+               if ( s.dataType == "script" && s.cache == null )
+                       s.cache = false;
+
+               if ( s.cache === false && type == "GET" ) {
+                       var ts = now();
+                       // try replacing _= if it is there
+                       var ret = s.url.replace(/(\?|&)_=.*?(&|$)/, "$1_=" + ts + "$2");
+                       // if nothing was replaced, add timestamp to the end
+                       s.url = ret + ((ret == s.url) ? (s.url.match(/\?/) ? "&" : "?") + "_=" + ts : "");
+               }
+
+               // If data is available, append data to url for get requests
+               if ( s.data && type == "GET" ) {
+                       s.url += (s.url.match(/\?/) ? "&" : "?") + s.data;
+
+                       // IE likes to send both get and post data, prevent this
+                       s.data = null;
+               }
+
+               // Watch for a new set of requests
+               if ( s.global && ! jQuery.active++ )
+                       jQuery.event.trigger( "ajaxStart" );
+
+               // Matches an absolute URL, and saves the domain
+               var parts = /^(\w+:)?\/\/([^\/?#]+)/.exec( s.url );
+
+               // If we're requesting a remote document
+               // and trying to load JSON or Script with a GET
+               if ( s.dataType == "script" && type == "GET" && parts
+                       && ( parts[1] && parts[1] != location.protocol || parts[2] != location.host )){
+
+                       var head = document.getElementsByTagName("head")[0];
+                       var script = document.createElement("script");
+                       script.src = s.url;
+                       if (s.scriptCharset)
+                               script.charset = s.scriptCharset;
+
+                       // Handle Script loading
+                       if ( !jsonp ) {
+                               var done = false;
+
+                               // Attach handlers for all browsers
+                               script.onload = script.onreadystatechange = function(){
+                                       if ( !done && (!this.readyState ||
+                                                       this.readyState == "loaded" || this.readyState == "complete") ) {
+                                               done = true;
+                                               success();
+                                               complete();
+
+                                               // Handle memory leak in IE
+                                               script.onload = script.onreadystatechange = null;
+                                               head.removeChild( script );
+                                       }
+                               };
+                       }
+
+                       head.appendChild(script);
+
+                       // We handle everything using the script element injection
+                       return undefined;
+               }
+
+               var requestDone = false;
+
+               // Create the request object
+               var xhr = s.xhr();
+
+               // Open the socket
+               // Passing null username, generates a login popup on Opera (#2865)
+               if( s.username )
+                       xhr.open(type, s.url, s.async, s.username, s.password);
+               else
+                       xhr.open(type, s.url, s.async);
+
+               // Need an extra try/catch for cross domain requests in Firefox 3
+               try {
+                       // Set the correct header, if data is being sent
+                       if ( s.data )
+                               xhr.setRequestHeader("Content-Type", s.contentType);
+
+                       // Set the If-Modified-Since header, if ifModified mode.
+                       if ( s.ifModified )
+                               xhr.setRequestHeader("If-Modified-Since",
+                                       jQuery.lastModified[s.url] || "Thu, 01 Jan 1970 00:00:00 GMT" );
+
+                       // Set header so the called script knows that it's an XMLHttpRequest
+                       xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+
+                       // Set the Accepts header for the server, depending on the dataType
+                       xhr.setRequestHeader("Accept", s.dataType && s.accepts[ s.dataType ] ?
+                               s.accepts[ s.dataType ] + ", */*" :
+                               s.accepts._default );
+               } catch(e){}
+
+               // Allow custom headers/mimetypes and early abort
+               if ( s.beforeSend && s.beforeSend(xhr, s) === false ) {
+                       // Handle the global AJAX counter
+                       if ( s.global && ! --jQuery.active )
+                               jQuery.event.trigger( "ajaxStop" );
+                       // close opended socket
+                       xhr.abort();
+                       return false;
+               }
+
+               if ( s.global )
+                       jQuery.event.trigger("ajaxSend", [xhr, s]);
+
+               // Wait for a response to come back
+               var onreadystatechange = function(isTimeout){
+                       // The request was aborted, clear the interval and decrement jQuery.active
+                       if (xhr.readyState == 0) {
+                               if (ival) {
+                                       // clear poll interval
+                                       clearInterval(ival);
+                                       ival = null;
+                                       // Handle the global AJAX counter
+                                       if ( s.global && ! --jQuery.active )
+                                               jQuery.event.trigger( "ajaxStop" );
+                               }
+                       // The transfer is complete and the data is available, or the request timed out
+                       } else if ( !requestDone && xhr && (xhr.readyState == 4 || isTimeout == "timeout") ) {
+                               requestDone = true;
+
+                               // clear poll interval
+                               if (ival) {
+                                       clearInterval(ival);
+                                       ival = null;
+                               }
+
+                               status = isTimeout == "timeout" ? "timeout" :
+                                       !jQuery.httpSuccess( xhr ) ? "error" :
+                                       s.ifModified && jQuery.httpNotModified( xhr, s.url ) ? "notmodified" :
+                                       "success";
+
+                               if ( status == "success" ) {
+                                       // Watch for, and catch, XML document parse errors
+                                       try {
+                                               // process the data (runs the xml through httpData regardless of callback)
+                                               data = jQuery.httpData( xhr, s.dataType, s );
+                                       } catch(e) {
+                                               status = "parsererror";
+                                       }
+                               }
+
+                               // Make sure that the request was successful or notmodified
+                               if ( status == "success" ) {
+                                       // Cache Last-Modified header, if ifModified mode.
+                                       var modRes;
+                                       try {
+                                               modRes = xhr.getResponseHeader("Last-Modified");
+                                       } catch(e) {} // swallow exception thrown by FF if header is not available
+
+                                       if ( s.ifModified && modRes )
+                                               jQuery.lastModified[s.url] = modRes;
+
+                                       // JSONP handles its own success callback
+                                       if ( !jsonp )
+                                               success();
+                               } else
+                                       jQuery.handleError(s, xhr, status);
+
+                               // Fire the complete handlers
+                               complete();
+
+                               if ( isTimeout )
+                                       xhr.abort();
+
+                               // Stop memory leaks
+                               if ( s.async )
+                                       xhr = null;
+                       }
+               };
+
+               if ( s.async ) {
+                       // don't attach the handler to the request, just poll it instead
+                       var ival = setInterval(onreadystatechange, 13);
+
+                       // Timeout checker
+                       if ( s.timeout > 0 )
+                               setTimeout(function(){
+                                       // Check to see if the request is still happening
+                                       if ( xhr && !requestDone )
+                                               onreadystatechange( "timeout" );
+                               }, s.timeout);
+               }
+
+               // Send the data
+               try {
+                       xhr.send(s.data);
+               } catch(e) {
+                       jQuery.handleError(s, xhr, null, e);
+               }
+
+               // firefox 1.5 doesn't fire statechange for sync requests
+               if ( !s.async )
+                       onreadystatechange();
+
+               function success(){
+                       // If a local callback was specified, fire it and pass it the data
+                       if ( s.success )
+                               s.success( data, status );
+
+                       // Fire the global callback
+                       if ( s.global )
+                               jQuery.event.trigger( "ajaxSuccess", [xhr, s] );
+               }
+
+               function complete(){
+                       // Process result
+                       if ( s.complete )
+                               s.complete(xhr, status);
+
+                       // The request was completed
+                       if ( s.global )
+                               jQuery.event.trigger( "ajaxComplete", [xhr, s] );
+
+                       // Handle the global AJAX counter
+                       if ( s.global && ! --jQuery.active )
+                               jQuery.event.trigger( "ajaxStop" );
+               }
+
+               // return XMLHttpRequest to allow aborting the request etc.
+               return xhr;
+       },
+
+       handleError: function( s, xhr, status, e ) {
+               // If a local callback was specified, fire it
+               if ( s.error ) s.error( xhr, status, e );
+
+               // Fire the global callback
+               if ( s.global )
+                       jQuery.event.trigger( "ajaxError", [xhr, s, e] );
+       },
+
+       // Counter for holding the number of active queries
+       active: 0,
+
+       // Determines if an XMLHttpRequest was successful or not
+       httpSuccess: function( xhr ) {
+               try {
+                       // IE error sometimes returns 1223 when it should be 204 so treat it as success, see #1450
+                       return !xhr.status && location.protocol == "file:" ||
+                               ( xhr.status >= 200 && xhr.status < 300 ) || xhr.status == 304 || xhr.status == 1223;
+               } catch(e){}
+               return false;
+       },
+
+       // Determines if an XMLHttpRequest returns NotModified
+       httpNotModified: function( xhr, url ) {
+               try {
+                       var xhrRes = xhr.getResponseHeader("Last-Modified");
+
+                       // Firefox always returns 200. check Last-Modified date
+                       return xhr.status == 304 || xhrRes == jQuery.lastModified[url];
+               } catch(e){}
+               return false;
+       },
+
+       httpData: function( xhr, type, s ) {
+               var ct = xhr.getResponseHeader("content-type"),
+                       xml = type == "xml" || !type && ct && ct.indexOf("xml") >= 0,
+                       data = xml ? xhr.responseXML : xhr.responseText;
+
+               if ( xml && data.documentElement.tagName == "parsererror" )
+                       throw "parsererror";
+                       
+               // Allow a pre-filtering function to sanitize the response
+               // s != null is checked to keep backwards compatibility
+               if( s && s.dataFilter )
+                       data = s.dataFilter( data, type );
+
+               // The filter can actually parse the response
+               if( typeof data === "string" ){
+
+                       // If the type is "script", eval it in global context
+                       if ( type == "script" )
+                               jQuery.globalEval( data );
+
+                       // Get the JavaScript object, if JSON is used.
+                       if ( type == "json" )
+                               data = window["eval"]("(" + data + ")");
+               }
+               
+               return data;
+       },
+
+       // Serialize an array of form elements or a set of
+       // key/values into a query string
+       param: function( a ) {
+               var s = [ ];
+
+               function add( key, value ){
+                       s[ s.length ] = encodeURIComponent(key) + '=' + encodeURIComponent(value);
+               };
+
+               // If an array was passed in, assume that it is an array
+               // of form elements
+               if ( jQuery.isArray(a) || a.jquery )
+                       // Serialize the form elements
+                       jQuery.each( a, function(){
+                               add( this.name, this.value );
+                       });
+
+               // Otherwise, assume that it's an object of key/value pairs
+               else
+                       // Serialize the key/values
+                       for ( var j in a )
+                               // If the value is an array then the key names need to be repeated
+                               if ( jQuery.isArray(a[j]) )
+                                       jQuery.each( a[j], function(){
+                                               add( j, this );
+                                       });
+                               else
+                                       add( j, jQuery.isFunction(a[j]) ? a[j]() : a[j] );
+
+               // Return the resulting serialization
+               return s.join("&").replace(/%20/g, "+");
+       }
+
+});
+var elemdisplay = {},
+       timerId,
+       fxAttrs = [
+               // height animations
+               [ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ],
+               // width animations
+               [ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ],
+               // opacity animations
+               [ "opacity" ]
+       ];
+
+function genFx( type, num ){
+       var obj = {};
+       jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice(0,num)), function(){
+               obj[ this ] = type;
+       });
+       return obj;
+}
+
+jQuery.fn.extend({
+       show: function(speed,callback){
+               if ( speed ) {
+                       return this.animate( genFx("show", 3), speed, callback);
+               } else {
+                       for ( var i = 0, l = this.length; i < l; i++ ){
+                               var old = jQuery.data(this[i], "olddisplay");
+                               
+                               this[i].style.display = old || "";
+                               
+                               if ( jQuery.css(this[i], "display") === "none" ) {
+                                       var tagName = this[i].tagName, display;
+                                       
+                                       if ( elemdisplay[ tagName ] ) {
+                                               display = elemdisplay[ tagName ];
+                                       } else {
+                                               var elem = jQuery("<" + tagName + " />").appendTo("body");
+                                               
+                                               display = elem.css("display");
+                                               if ( display === "none" )
+                                                       display = "block";
+                                               
+                                               elem.remove();
+                                               
+                                               elemdisplay[ tagName ] = display;
+                                       }
+                                       
+                                       jQuery.data(this[i], "olddisplay", display);
+                               }
+                       }
+
+                       // Set the display of the elements in a second loop
+                       // to avoid the constant reflow
+                       for ( var i = 0, l = this.length; i < l; i++ ){
+                               this[i].style.display = jQuery.data(this[i], "olddisplay") || "";
+                       }
+                       
+                       return this;
+               }
+       },
+
+       hide: function(speed,callback){
+               if ( speed ) {
+                       return this.animate( genFx("hide", 3), speed, callback);
+               } else {
+                       for ( var i = 0, l = this.length; i < l; i++ ){
+                               var old = jQuery.data(this[i], "olddisplay");
+                               if ( !old && old !== "none" )
+                                       jQuery.data(this[i], "olddisplay", jQuery.css(this[i], "display"));
+                       }
+
+                       // Set the display of the elements in a second loop
+                       // to avoid the constant reflow
+                       for ( var i = 0, l = this.length; i < l; i++ ){
+                               this[i].style.display = "none";
+                       }
+
+                       return this;
+               }
+       },
+
+       // Save the old toggle function
+       _toggle: jQuery.fn.toggle,
+
+       toggle: function( fn, fn2 ){
+               var bool = typeof fn === "boolean";
+
+               return jQuery.isFunction(fn) && jQuery.isFunction(fn2) ?
+                       this._toggle.apply( this, arguments ) :
+                       fn == null || bool ?
+                               this.each(function(){
+                                       var state = bool ? fn : jQuery(this).is(":hidden");
+                                       jQuery(this)[ state ? "show" : "hide" ]();
+                               }) :
+                               this.animate(genFx("toggle", 3), fn, fn2);
+       },
+
+       fadeTo: function(speed,to,callback){
+               return this.animate({opacity: to}, speed, callback);
+       },
+
+       animate: function( prop, speed, easing, callback ) {
+               var optall = jQuery.speed(speed, easing, callback);
+
+               return this[ optall.queue === false ? "each" : "queue" ](function(){
+               
+                       var opt = jQuery.extend({}, optall), p,
+                               hidden = this.nodeType == 1 && jQuery(this).is(":hidden"),
+                               self = this;
+       
+                       for ( p in prop ) {
+                               if ( prop[p] == "hide" && hidden || prop[p] == "show" && !hidden )
+                                       return opt.complete.call(this);
+
+                               if ( ( p == "height" || p == "width" ) && this.style ) {
+                                       // Store display property
+                                       opt.display = jQuery.css(this, "display");
+
+                                       // Make sure that nothing sneaks out
+                                       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";
+
+                                               // We need to compute starting value
+                                               if ( unit != "px" ) {
+                                                       self.style[ name ] = (end || 1) + unit;
+                                                       start = ((end || 1) / e.cur(true)) * start;
+                                                       self.style[ name ] = start + unit;
+                                               }
+
+                                               // If a +=/-= token was provided, we're doing a relative animation
+                                               if ( parts[1] )
+                                                       end = ((parts[1] == "-=" ? -1 : 1) * end) + start;
+
+                                               e.custom( start, end, unit );
+                                       } else
+                                               e.custom( start, val, "" );
+                               }
+                       });
+
+                       // For JS strict compliance
+                       return true;
+               });
+       },
+
+       stop: function(clearQueue, gotoEnd){
+               var timers = jQuery.timers;
+
+               if (clearQueue)
+                       this.queue([]);
+
+               this.each(function(){
+                       // go in reverse order so anything added to the queue during the loop is ignored
+                       for ( var i = timers.length - 1; i >= 0; i-- )
+                               if ( timers[i].elem == this ) {
+                                       if (gotoEnd)
+                                               // force the next step to be the last
+                                               timers[i](true);
+                                       timers.splice(i, 1);
+                               }
+               });
+
+               // start the next in the queue if the last step wasn't forced
+               if (!gotoEnd)
+                       this.dequeue();
+
+               return this;
+       }
+
+});
+
+// Generate shortcuts for custom animations
+jQuery.each({
+       slideDown: genFx("show", 1),
+       slideUp: genFx("hide", 1),
+       slideToggle: genFx("toggle", 1),
+       fadeIn: { opacity: "show" },
+       fadeOut: { opacity: "hide" }
+}, function( name, props ){
+       jQuery.fn[ name ] = function( speed, callback ){
+               return this.animate( props, speed, callback );
+       };
+});
+
+jQuery.extend({
+
+       speed: function(speed, easing, fn) {
+               var opt = typeof speed === "object" ? speed : {
+                       complete: fn || !fn && easing ||
+                               jQuery.isFunction( speed ) && speed,
+                       duration: speed,
+                       easing: fn && easing || easing && !jQuery.isFunction(easing) && easing
+               };
+
+               opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
+                       jQuery.fx.speeds[opt.duration] || jQuery.fx.speeds._default;
+
+               // Queueing
+               opt.old = opt.complete;
+               opt.complete = function(){
+                       if ( opt.queue !== false )
+                               jQuery(this).dequeue();
+                       if ( jQuery.isFunction( opt.old ) )
+                               opt.old.call( 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 = {
+
+       // Simple function for setting a style value
+       update: function(){
+               if ( this.options.step )
+                       this.options.step.call( this.elem, this.now, this );
+
+               (jQuery.fx.step[this.prop] || jQuery.fx.step._default)( this );
+
+               // Set display property to block for height/width animations
+               if ( ( this.prop == "height" || this.prop == "width" ) && this.elem.style )
+                       this.elem.style.display = "block";
+       },
+
+       // Get the current size
+       cur: function(force){
+               if ( this.elem[this.prop] != null && (!this.elem.style || this.elem.style[this.prop] == null) )
+                       return this.elem[ this.prop ];
+
+               var r = parseFloat(jQuery.css(this.elem, this.prop, force));
+               return r && r > -10000 ? r : parseFloat(jQuery.curCSS(this.elem, this.prop)) || 0;
+       },
+
+       // Start an animation from one number to another
+       custom: function(from, to, unit){
+               this.startTime = now();
+               this.start = from;
+               this.end = to;
+               this.unit = unit || this.unit || "px";
+               this.now = this.start;
+               this.pos = this.state = 0;
+
+               var self = this;
+               function t(gotoEnd){
+                       return self.step(gotoEnd);
+               }
+
+               t.elem = this.elem;
+
+               if ( t() && jQuery.timers.push(t) && !timerId ) {
+                       timerId = 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( timerId );
+                                       timerId = undefined;
+                               }
+                       }, 13);
+               }
+       },
+
+       // Simple 'show' function
+       show: function(){
+               // Remember where we started, so that we can go back to it later
+               this.options.orig[this.prop] = jQuery.attr( this.elem.style, this.prop );
+               this.options.show = true;
+
+               // Begin the animation
+               // Make sure that we start at a small width/height to avoid any
+               // flash of content
+               this.custom(this.prop == "width" || this.prop == "height" ? 1 : 0, this.cur());
+
+               // Start by showing the element
+               jQuery(this.elem).show();
+       },
+
+       // Simple 'hide' function
+       hide: function(){
+               // Remember where we started, so that we can go back to it later
+               this.options.orig[this.prop] = jQuery.attr( this.elem.style, this.prop );
+               this.options.hide = true;
+
+               // Begin the animation
+               this.custom(this.cur(), 0);
+       },
+
+       // Each step of an animation
+       step: function(gotoEnd){
+               var t = now();
+
+               if ( gotoEnd || 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 ) {
+                                       // Reset the overflow
+                                       this.elem.style.overflow = this.options.overflow;
+
+                                       // Reset the display
+                                       this.elem.style.display = this.options.display;
+                                       if ( jQuery.css(this.elem, "display") == "none" )
+                                               this.elem.style.display = "block";
+                               }
+
+                               // Hide the element if the "hide" operation was done
+                               if ( this.options.hide )
+                                       jQuery(this.elem).hide();
+
+                               // Reset the properties, if the item has been hidden or shown
+                               if ( this.options.hide || this.options.show )
+                                       for ( var p in this.options.curAnim )
+                                               jQuery.attr(this.elem.style, p, this.options.orig[p]);
+                                       
+                               // Execute the complete function
+                               this.options.complete.call( this.elem );
+                       }
+
+                       return false;
+               } else {
+                       var n = t - this.startTime;
+                       this.state = n / this.options.duration;
+
+                       // Perform the easing function, defaults to swing
+                       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);
+
+                       // Perform the next step of the animation
+                       this.update();
+               }
+
+               return true;
+       }
+
+};
+
+jQuery.extend( jQuery.fx, {
+       speeds:{
+               slow: 600,
+               fast: 200,
+               // Default speed
+               _default: 400
+       },
+       step: {
+
+               opacity: function(fx){
+                       jQuery.attr(fx.elem.style, "opacity", fx.now);
+               },
+
+               _default: function(fx){
+                       if ( fx.elem.style && fx.elem.style[ fx.prop ] != null )
+                               fx.elem.style[ fx.prop ] = fx.now + fx.unit;
+                       else
+                               fx.elem[ fx.prop ] = fx.now;
+               }
+       }
+});
+if ( document.documentElement["getBoundingClientRect"] )
+       jQuery.fn.offset = function() {
+               if ( !this[0] ) return { top: 0, left: 0 };
+               if ( this[0] === this[0].ownerDocument.body ) return jQuery.offset.bodyOffset( this[0] );
+               var box  = this[0].getBoundingClientRect(), doc = this[0].ownerDocument, body = doc.body, docElem = doc.documentElement,
+                       clientTop = docElem.clientTop || body.clientTop || 0, clientLeft = docElem.clientLeft || body.clientLeft || 0,
+                       top  = box.top  + (self.pageYOffset || jQuery.boxModel && docElem.scrollTop  || body.scrollTop ) - clientTop,
+                       left = box.left + (self.pageXOffset || jQuery.boxModel && docElem.scrollLeft || body.scrollLeft) - clientLeft;
+               return { top: top, left: left };
+       };
+else 
+       jQuery.fn.offset = function() {
+               if ( !this[0] ) return { top: 0, left: 0 };
+               if ( this[0] === this[0].ownerDocument.body ) return jQuery.offset.bodyOffset( this[0] );
+               jQuery.offset.initialized || jQuery.offset.initialize();
+
+               var elem = this[0], offsetParent = elem.offsetParent, prevOffsetParent = elem,
+                       doc = elem.ownerDocument, computedStyle, docElem = doc.documentElement,
+                       body = doc.body, defaultView = doc.defaultView,
+                       prevComputedStyle = defaultView.getComputedStyle(elem, null),
+                       top = elem.offsetTop, left = elem.offsetLeft;
+
+               while ( (elem = elem.parentNode) && elem !== body && elem !== docElem ) {
+                       computedStyle = defaultView.getComputedStyle(elem, null);
+                       top -= elem.scrollTop, left -= elem.scrollLeft;
+                       if ( elem === offsetParent ) {
+                               top += elem.offsetTop, left += elem.offsetLeft;
+                               if ( jQuery.offset.doesNotAddBorder && !(jQuery.offset.doesAddBorderForTableAndCells && /^t(able|d|h)$/i.test(elem.tagName)) )
+                                       top  += parseInt( computedStyle.borderTopWidth,  10) || 0,
+                                       left += parseInt( computedStyle.borderLeftWidth, 10) || 0;
+                               prevOffsetParent = offsetParent, offsetParent = elem.offsetParent;
+                       }
+                       if ( jQuery.offset.subtractsBorderForOverflowNotVisible && computedStyle.overflow !== "visible" )
+                               top  += parseInt( computedStyle.borderTopWidth,  10) || 0,
+                               left += parseInt( computedStyle.borderLeftWidth, 10) || 0;
+                       prevComputedStyle = computedStyle;
+               }
+
+               if ( prevComputedStyle.position === "relative" || prevComputedStyle.position === "static" )
+                       top  += body.offsetTop,
+                       left += body.offsetLeft;
+
+               if ( prevComputedStyle.position === "fixed" )
+                       top  += Math.max(docElem.scrollTop, body.scrollTop),
+                       left += Math.max(docElem.scrollLeft, body.scrollLeft);
+
+               return { top: top, left: left };
+       };
+
+jQuery.offset = {
+       initialize: function() {
+               if ( this.initialized ) return;
+               var body = document.body, container = document.createElement('div'), innerDiv, checkDiv, table, td, rules, prop, bodyMarginTop = body.style.marginTop,
+                       html = '<div style="position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;"><div></div></div><table style="position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;" cellpadding="0" cellspacing="0"><tr><td></td></tr></table>';
+
+               rules = { position: 'absolute', top: 0, left: 0, margin: 0, border: 0, width: '1px', height: '1px', visibility: 'hidden' };
+               for ( prop in rules ) container.style[prop] = rules[prop];
+
+               container.innerHTML = html;
+               body.insertBefore(container, body.firstChild);
+               innerDiv = container.firstChild, checkDiv = innerDiv.firstChild, td = innerDiv.nextSibling.firstChild.firstChild;
+
+               this.doesNotAddBorder = (checkDiv.offsetTop !== 5);
+               this.doesAddBorderForTableAndCells = (td.offsetTop === 5);
+
+               innerDiv.style.overflow = 'hidden', innerDiv.style.position = 'relative';
+               this.subtractsBorderForOverflowNotVisible = (checkDiv.offsetTop === -5);
+
+               body.style.marginTop = '1px';
+               this.doesNotIncludeMarginInBodyOffset = (body.offsetTop === 0);
+               body.style.marginTop = bodyMarginTop;
+
+               body.removeChild(container);
+               this.initialized = true;
+       },
+
+       bodyOffset: function(body) {
+               jQuery.offset.initialized || jQuery.offset.initialize();
+               var top = body.offsetTop, left = body.offsetLeft;
+               if ( jQuery.offset.doesNotIncludeMarginInBodyOffset )
+                       top  += parseInt( jQuery.curCSS(body, 'marginTop',  true), 10 ) || 0,
+                       left += parseInt( jQuery.curCSS(body, 'marginLeft', true), 10 ) || 0;
+               return { top: top, left: left };
+       }
+};
+
+
+jQuery.fn.extend({
+       position: function() {
+               var left = 0, top = 0, results;
+
+               if ( this[0] ) {
+                       // Get *real* offsetParent
+                       var offsetParent = this.offsetParent(),
+
+                       // Get correct offsets
+                       offset       = this.offset(),
+                       parentOffset = /^body|html$/i.test(offsetParent[0].tagName) ? { top: 0, left: 0 } : offsetParent.offset();
+
+                       // Subtract element margins
+                       // note: when an element has margin: auto the offsetLeft and marginLeft 
+                       // are the same in Safari causing offset.left to incorrectly be 0
+                       offset.top  -= num( this, 'marginTop'  );
+                       offset.left -= num( this, 'marginLeft' );
+
+                       // Add offsetParent borders
+                       parentOffset.top  += num( offsetParent, 'borderTopWidth'  );
+                       parentOffset.left += num( offsetParent, 'borderLeftWidth' );
+
+                       // Subtract the two offsets
+                       results = {
+                               top:  offset.top  - parentOffset.top,
+                               left: offset.left - parentOffset.left
+                       };
+               }
+
+               return results;
+       },
+
+       offsetParent: function() {
+               var offsetParent = this[0].offsetParent || document.body;
+               while ( offsetParent && (!/^body|html$/i.test(offsetParent.tagName) && jQuery.css(offsetParent, 'position') == 'static') )
+                       offsetParent = offsetParent.offsetParent;
+               return jQuery(offsetParent);
+       }
+});
+
+
+// Create scrollLeft and scrollTop methods
+jQuery.each( ['Left', 'Top'], function(i, name) {
+       var method = 'scroll' + name;
+       
+       jQuery.fn[ method ] = function(val) {
+               if (!this[0]) return null;
+
+               return val !== undefined ?
+
+                       // Set the scroll offset
+                       this.each(function() {
+                               this == window || this == document ?
+                                       window.scrollTo(
+                                               !i ? val : jQuery(window).scrollLeft(),
+                                                i ? val : jQuery(window).scrollTop()
+                                       ) :
+                                       this[ method ] = val;
+                       }) :
+
+                       // Return the scroll offset
+                       this[0] == window || this[0] == document ?
+                               self[ i ? 'pageYOffset' : 'pageXOffset' ] ||
+                                       jQuery.boxModel && document.documentElement[ method ] ||
+                                       document.body[ method ] :
+                               this[0][ method ];
+       };
+});
+// Create innerHeight, innerWidth, outerHeight and outerWidth methods
+jQuery.each([ "Height", "Width" ], function(i, name){
+
+       var tl = i ? "Left"  : "Top",  // top or left
+               br = i ? "Right" : "Bottom", // bottom or right
+               lower = name.toLowerCase();
+
+       // innerHeight and innerWidth
+       jQuery.fn["inner" + name] = function(){
+               return this[0] ?
+                       jQuery.css( this[0], lower, false, "padding" ) :
+                       null;
+       };
+
+       // outerHeight and outerWidth
+       jQuery.fn["outer" + name] = function(margin) {
+               return this[0] ?
+                       jQuery.css( this[0], lower, false, margin ? "margin" : "border" ) :
+                       null;
+       };
+       
+       var type = name.toLowerCase();
+
+       jQuery.fn[ type ] = function( size ) {
+               // Get window width or height
+               return this[0] == window ?
+                       // Everyone else use document.documentElement or document.body depending on Quirks vs Standards mode
+                       document.compatMode == "CSS1Compat" && document.documentElement[ "client" + name ] ||
+                       document.body[ "client" + name ] :
+
+                       // Get document width or height
+                       this[0] == document ?
+                               // Either scroll[Width/Height] or offset[Width/Height], whichever is greater
+                               Math.max(
+                                       document.documentElement["client" + name],
+                                       document.body["scroll" + name], document.documentElement["scroll" + name],
+                                       document.body["offset" + name], document.documentElement["offset" + name]
+                               ) :
+
+                               // Get or set width or height on the element
+                               size === undefined ?
+                                       // Get width or height on the element
+                                       (this.length ? jQuery.css( this[0], type ) : null) :
+
+                                       // Set the width or height on the element (default to pixels if value is unitless)
+                                       this.css( type, typeof size === "string" ? size : size + "px" );
+       };
+
+});
+})();
diff --git a/share/roundup/templates/devel/html/keyword.index.html b/share/roundup/templates/devel/html/keyword.index.html
new file mode 100644 (file)
index 0000000..f78a0b5
--- /dev/null
@@ -0,0 +1,46 @@
+<tal:block metal:use-macro="templates/page/macros/frame">
+<title metal:fill-slot="head_title" >
+  <span tal:omit-tag="true" i18n:translate="" >List of keywords</span>
+  <span tal:condition="request/dispname"
+   tal:replace="python:' - %s '%request.dispname"
+  /> - <span tal:replace="config/TRACKER_NAME" />
+</title>
+<span metal:fill-slot="body_title" tal:omit-tag="true">
+  <span tal:omit-tag="true" i18n:translate="" >List of keywords</span>
+  <span tal:condition="request/dispname"
+   tal:replace="python:' - %s' % request.dispname" />
+</span>
+<tal:block metal:fill-slot="content">
+
+<p tal:condition="python:not (context.is_view_ok()
+ or request.user.hasRole('Anonymous'))" i18n:translate="">
+ You are not allowed to view this page.</p>
+
+<p tal:condition="python:not context.is_view_ok()
+ and request.user.hasRole('Anonymous')" i18n:translate="">
+ Please login with your username and password.</p>
+
+<table width="100%" tal:condition="context/is_view_ok" class="list">
+<tr>
+ <th i18n:translate="">Keyword</th>
+ <th i18n:translate="">Description</th>
+</tr>
+
+<tal:block repeat="keyword context/list">
+<tr tal:attributes="class python:['normal', 'alt'][repeat['keyword'].index%6/3]">
+ <td>
+  <a tal:attributes="href string:keyword${keyword/id}"
+     tal:content="keyword/name">keyword name</a>
+ </td>
+ <td tal:content="python:keyword.description.plain() or
+     default">&nbsp;</td>
+</tr>
+</tal:block>
+
+<tr tal:condition="context/is_edit_ok">
+   <td colspan="2"><a href="keyword?@template=item">New Keyword</a></td>
+</tr>
+</table>
+
+</tal:block>
+</tal:block>
diff --git a/share/roundup/templates/devel/html/keyword.item.html b/share/roundup/templates/devel/html/keyword.item.html
new file mode 100644 (file)
index 0000000..22a5fd0
--- /dev/null
@@ -0,0 +1,78 @@
+<tal:doc metal:use-macro="templates/page/macros/frame"
+define="edit_ok context/is_edit_ok"
+>
+<title metal:fill-slot="head_title">
+<tal:if condition="context/id" i18n:translate=""
+ >Keyword <span tal:replace="context/id" i18n:name="id"
+ />: <span tal:replace="context/name" i18n:name="title"
+ /> - <span tal:replace="config/TRACKER_NAME" i18n:name="tracker"
+/></tal:if>
+<tal:if condition="not:context/id" i18n:translate=""
+ >New Keyword - <span tal:replace="config/TRACKER_NAME" i18n:name="tracker"
+/></tal:if>
+</title>
+<metal:slot fill-slot="more-javascript">
+<script type="text/javascript" src="@@file/help_controls.js"></script>
+</metal:slot>
+<tal:block metal:fill-slot="body_title"
+  define="edit_ok context/is_edit_ok">
+ <span tal:condition="python: not (context.id or edit_ok)"
+  tal:omit-tag="python:1" i18n:translate="">New Keyword</span>
+ <span tal:condition="python: not context.id and edit_ok"
+  tal:omit-tag="python:1" i18n:translate="">New Keyword Editing</span>
+ <span tal:condition="python: context.id and not edit_ok"
+  tal:omit-tag="python:1" i18n:translate="">Keyword<tal:x
+  replace="context/id" i18n:name="id" /></span>
+ <span tal:condition="python: context.id and edit_ok"
+  tal:omit-tag="python:1" i18n:translate="">Keyword<tal:x
+  replace="context/id" i18n:name="id" /> Editing</span>
+</tal:block>
+
+<td class="content" metal:fill-slot="content">
+
+<p tal:condition="python:not (context.is_view_ok()
+ or request.user.hasRole('Anonymous'))" i18n:translate="">
+ You are not allowed to view this page.</p>
+
+<p tal:condition="python:not context.is_view_ok()
+ and request.user.hasRole('Anonymous')" i18n:translate="">
+ Please login with your username and password.</p>
+
+<div tal:condition="context/is_view_ok">
+
+<form method="POST"
+      tal:define="required python:'name description'.split()"
+      enctype="multipart/form-data"
+      tal:attributes="action context/designator;">
+
+<table class="form">
+
+<tr>
+ <th class="required" i18n:translate="">Keyword:</th>
+ <td tal:content="structure
+ python:context.name.field(size=60)">title</td>
+</tr>
+
+<tr>
+ <th class="required" i18n:translate="">Description:</th>
+ <td tal:content="structure python:context.description.field(size=60)">description</td>
+
+</tr>
+
+ <tr tal:condition="context/is_edit_ok">
+  <td>
+   &nbsp;
+   <input type="hidden" name="@template" value="item">
+   <input type="hidden" name="@required" value="name,description"
+          tal:attributes="value python:','.join(required)">
+  </td>
+  <td><input type="submit" value="save" tal:replace="structure context/submit"><!--submit button here-->
+    <input type="reset">
+  </td>
+ </tr>
+
+</table>
+</form>
+</div>
+</td>
+</tal:doc>
diff --git a/share/roundup/templates/devel/html/milestone.index.html b/share/roundup/templates/devel/html/milestone.index.html
new file mode 100644 (file)
index 0000000..0e45817
--- /dev/null
@@ -0,0 +1,144 @@
+<tal:block metal:use-macro="templates/page/macros/frame">
+<title metal:fill-slot="head_title" >
+  <span tal:omit-tag="true" i18n:translate="" >List of milestones</span>
+  <span tal:condition="request/dispname"
+   tal:replace="python:' - %s '%request.dispname"
+  /> - <span tal:replace="config/TRACKER_NAME" />
+</title>
+<span metal:fill-slot="body_title" tal:omit-tag="true">
+  <span tal:omit-tag="true" i18n:translate="" >List of milestones</span>
+  <span tal:condition="request/dispname"
+   tal:replace="python:' - %s' % request.dispname" />
+</span>
+<tal:block metal:fill-slot="content">
+
+<p tal:condition="python:not (context.is_view_ok()
+ or request.user.hasRole('Anonymous'))" i18n:translate="">
+ You are not allowed to view this page.</p>
+
+<p tal:condition="python:not context.is_view_ok()
+ and request.user.hasRole('Anonymous')" i18n:translate="">
+ Please login with your username and password.</p>
+
+<tal:block tal:define="batch request/batch" tal:condition="context/is_view_ok">
+ <table class="list">
+  <tr>
+   <th tal:condition="request/show/id" i18n:translate="">ID</th>
+   <th tal:condition="request/show/title" i18n:translate="">Title</th>
+   <th tal:condition="request/show/status"    i18n:translate="">Status</th>
+<!--   <th tal:condition="request/show/type" i18n:translate="">Type</th>-->
+  </tr>
+ <tal:block tal:repeat="i batch" condition=true>
+  <tr tal:define="group python:[r[1] for r in request.group]"
+      tal:condition="python:group and batch.propchanged(*group)">
+   <th tal:attributes="colspan python:len(request.columns)" class="group">
+    <tal:block tal:repeat="g group">
+     <tal:block tal:content="python:str(i[g]) or '(no %s set)'%g"/>
+    </tal:block>
+   </th>
+  </tr>
+
+  <tr tal:attributes="class python:['even','odd'][repeat['i'].even()]">
+   <td tal:condition="request/show/id" tal:content="i/id">&nbsp;</td>
+   <td tal:condition="request/show/title">
+    <a tal:attributes="href string:milestone${i/id}"
+               tal:content="python:str(i.title.plain(hyperlink=0)) or '[no title]'">title</a>
+   </td>
+   <td tal:condition="request/show/status"
+       tal:content="python:i.status.plain() or default">&nbsp;</td>
+  </tr>
+
+ </tal:block>
+
+ <metal:index define-macro="batch-footer">
+ <tr tal:condition="batch">
+  <th tal:attributes="colspan python:len(request.columns)">
+   <table width="100%">
+    <tr class="navigation">
+     <th>
+      <a tal:define="prev batch/previous" tal:condition="prev"
+         tal:attributes="href python:request.indexargs_url(request.classname,
+         {'@startwith':prev.first, '@pagesize':prev.size})"
+         i18n:translate="">&lt;&lt; previous</a>
+      &nbsp;
+     </th>
+     <th i18n:translate=""><span tal:replace="batch/start" i18n:name="start"
+     />..<span tal:replace="python: batch.start + batch.length -1" i18n:name="end"
+     /> out of <span tal:replace="batch/sequence_length" i18n:name="total"
+     /></th>
+     <th>
+      <a tal:define="next batch/next" tal:condition="next"
+         tal:attributes="href python:request.indexargs_url(request.classname,
+         {'@startwith':next.first, '@pagesize':next.size})"
+         i18n:translate="">next &gt;&gt;</a>
+      &nbsp;
+     </th>
+    </tr>
+   </table>
+  </th>
+ </tr>
+ </metal:index>
+</table>
+<!--
+<a tal:attributes="href python:request.indexargs_url('milestone',
+            {'@action':'export_csv'})" i18n:translate="">Download as CSV</a>
+-->
+<form method="get" class="index-controls"
+    tal:attributes="action request/classname">
+
+ <table class="form" tal:define="n_sort python:2">
+<!--
+<tal:block tal:repeat="n python:range(n_sort)" tal:condition="batch">
+  <tr tal:define="key python:len(request.sort)>n and request.sort[n]">
+   <th>
+    <tal:block tal:condition="not:n" i18n:translate="">Sort on:</tal:block>
+   </th>
+   <td>
+    <select tal:attributes="name python:'@sort%d'%n">
+     <option value="" i18n:translate="">- nothing -</option>
+     <option tal:repeat="col context/properties"
+             tal:attributes="value col/_name;
+                             selected python:key and col._name == key[1]"
+             tal:content="col/_name"
+             i18n:translate="">column</option>
+    </select>
+   </td>
+   <th i18n:translate="">Descending:</th>
+   <td><input type="checkbox" tal:attributes="name python:'@sortdir%d'%n;
+              checked python:key and key[0] == '-'"/>
+   </td>
+  </tr>
+  </tal:block>
+  -->
+  <tal:block tal:repeat="n python:range(n_sort)" tal:condition="batch">
+  <tr tal:define="key python:len(request.group)>n and request.group[n]">
+   <th>
+    <tal:block tal:condition="not:n" i18n:translate="">Group on:</tal:block>
+   </th>
+   <td>
+    <select tal:attributes="name python:'@group%d'%n">
+     <option value="" i18n:translate="">- nothing -</option>
+     <option tal:repeat="col context/properties"
+             tal:attributes="value col/_name;
+                             selected python:key and col._name == key[1]"
+             tal:content="col/_name"
+             i18n:translate="">column</option>
+    </select>
+   </td>
+   <th i18n:translate="">Descending:</th>
+   <td><input type="checkbox" tal:attributes="name python:'@groupdir%d'%n;
+              checked python:key and key[0] == '-'"/>
+   </td>
+  </tr>
+  </tal:block>
+  <tr><td colspan="4">
+              <input type="submit" value="Redisplay" i18n:attributes="value"/>
+              <tal:block tal:replace="structure
+                python:request.indexargs_form(sort=0, group=0)" />
+  </td></tr>
+ </table>
+</form>
+
+</tal:block>
+</tal:block>
+</tal:block>
diff --git a/share/roundup/templates/devel/html/milestone.item.html b/share/roundup/templates/devel/html/milestone.item.html
new file mode 100644 (file)
index 0000000..7501928
--- /dev/null
@@ -0,0 +1,179 @@
+<!-- HEADER -->
+
+<tal:block metal:use-macro="templates/page/macros/frame">
+  <tal:block metal:fill-slot="body_title">
+    <p class="header">Milestone<span tal:replace="context/id" /> Editing</p>
+  </tal:block>
+
+  <tal:block metal:fill-slot="content">
+
+    <tal:block tal:condition="python:context.is_view_ok or context.is_edit_ok">
+
+<!-- BODY -->
+
+<form method="post" id="itemSynopsis" onSubmit="return submit_once()"
+      enctype="multipart/form-data">
+
+  <p><input type="hidden" name=":template" value="item" /></p>
+
+  <table class="form">
+
+    <!-- Title -->
+    <tr>
+      <th class="required">Title</th>
+      <td colspan="5"
+          tal:condition="python: request.user.hasPermission('Process')"
+          tal:content="structure python:context.title.field(size=60)">
+        title
+      </td>
+      <td colspan="5"
+          tal:condition="python: not request.user.hasPermission('Process')"
+          tal:content="structure context/title/plain">
+        title
+      </td>
+    </tr>
+
+    <!-- creation, creator, activity -->
+    <tr tal:condition="context/id">
+      <td colspan="6"
+          tal:content="structure string:Created on
+            <b>${context/creation}</b> by <b>${context/creator}</b>, last
+            changed <b>${context/activity}</b>.">last changed
+      </td>
+    </tr>
+
+    <!-- status -->
+    <tr tal:condition="context/id">
+      <th>
+        <a href="javascript:help_window('status?:template=help&properties=name,d
+escription', 500, 400)">status</a>
+      </th>
+      <td colspan="5"
+          tal:condition="python: request.user.hasPermission('Process')"
+          tal:content="structure context/status/menu">
+        status
+      </td>
+      <td colspan="5"
+          tal:condition="python: not request.user.hasPermission('Process')"
+          tal:content="structure context/status/plain">
+        status
+      </td>
+    </tr>
+
+    <tr>
+      <th>Bugs</th>
+      <td colspan="2"
+          tal:condition="python: request.user.hasPermission('Process')">
+        <table>
+          <tr>
+           <td>Add: <input name=":add:bugs"  size="20" /></td>
+            </tr>
+            <tr tal:repeat="bug context/bugs">
+              <td>
+                <a tal:attributes="href string:bug${bug/id}; title string:${bug/title}"
+                   tal:content="string:bug${bug/id}">&nbsp</a>
+              </td>
+              <td>
+                <input type="checkbox"
+                       name=":remove:bugs"
+                       tal:attributes="value bug/id" />
+                remove
+              </td>
+            </tr>
+        </table>
+      </td>
+      <td colspan="2"
+        tal:condition="python: not request.user.hasPermission('Process')">
+        <span tal:condition="context/bugs"
+              tal:repeat="bug context/bugs">
+          <br/>View:
+          <a tal:attributes="href string:bug${bug/id};
+                             title string:${bug/title}"
+             tal:content="string:bug${bug/id}">
+          &nbsp;
+          </a>
+        </span>
+      </td>
+    </tr>
+
+    <!-- Comment -->
+    <tr>  
+      <th>Add Comment:</th>
+      <td colspan="5" class="form-text">
+        <textarea name=":note" wrap="hard" rows="5" cols="50"></textarea>
+      </td>
+    </tr>
+
+    <!-- Files -->
+    <tr>
+      <th>File</th>
+      <td colspan="5"><input type="file" name=":file" size="40" /></td>
+    </tr>
+
+    <!-- Nosy List -->
+    <tr>
+      <th>
+        CC List:<span tal:replace="structure python:db.user.classhelp('username,realname,address', property='nosy', label='?', width=500)" />
+      </th>
+      <td colspan="5">
+        <span tal:replace="structure context/nosy/field" />
+      </td>
+    </tr>
+  </table>
+
+  <!-- graph
+  <p tal:condition="context/id">
+View the <a tal:attributes="href string:/graph?type=milestone&id=${context/id}&output=cmap">dependency graph</a> for this milestone.
+  </p>-->
+
+  <!-- Submit -->
+  <p tal:content="structure context/submit">
+    submit button will go here
+  </p>
+
+  <tal:block tal:condition="context/id">
+    <!-- Messages -->
+    <table class="messages" tal:condition="context/messages">
+      <tr><th colspan="3" class="header">Messages</th></tr>
+      <tal:block tal:repeat="msg context/messages">
+        <tr>
+          <th tal:content="string:Author: ${msg/author}">author</th>
+          <th tal:content="string:Date: ${msg/date}">date</th>
+        </tr>
+        <tr>
+          <td colspan="2"><pre tal:content="msg/content">content</pre></td>
+        </tr>
+      </tal:block>
+    </table>
+
+    <!-- Files -->
+    <table class="files" tal:condition="context/files">
+      <tr><th colspan="2" class="header">Files</th></tr>
+      <tr><th>File name</th><th>Uploaded</th></tr>
+      <tr tal:repeat="file context/files">
+        <td>
+          <a tal:attributes="href string:file${file/id}/${file/name}"
+             tal:content="file/name">dld link</a>
+        </td>
+        <td>
+          <span tal:content="file/creator">creator's name</span>,
+          <span tal:content="file/creation">creation date</span>
+        </td>
+      </tr>
+    </table>
+
+    <!-- Sort ascending -->
+    <tal:block tal:replace="structure python:context.history(direction='ascending')" />   
+
+  </tal:block>
+</form>
+
+<!-- FOOTER -->
+    </tal:block>
+    <span tal:condition="python:not (context.is_view_ok or context.is_edit_ok)">
+You are not allowed to view this page.
+    </span>
+
+  </tal:block>
+</tal:block>
+
diff --git a/share/roundup/templates/devel/html/msg.index.html b/share/roundup/templates/devel/html/msg.index.html
new file mode 100644 (file)
index 0000000..1ad4b33
--- /dev/null
@@ -0,0 +1,26 @@
+<tal:block metal:use-macro="templates/page/macros/frame">
+<title metal:fill-slot="head_title" i18n:translate=""
+ >List of messages - <span tal:replace="config/TRACKER_NAME"
+ i18n:name="tracker"/></title>
+<span metal:fill-slot="body_title" tal:omit-tag="python:1"
+ i18n:translate="">Message listing</span>
+<td class="content" metal:fill-slot="content">
+<table tal:define="batch request/batch" class="messages">
+ <tr><th colspan=3 class="header" i18n:translate="">Messages</th></tr>
+ <tal:block tal:repeat="msg batch">
+  <tr>
+   <th><a tal:attributes="href string:msg${msg/id}"><tal:x replace="msg/id"/></a></th>
+   <th tal:content="string:Author: ${msg/author}">author</th>
+   <th tal:content="string:Date: ${msg/date}">date</th>
+  </tr>
+  <tr>
+   <td colspan="3"><pre tal:content="msg/content">content</pre></td>
+  </tr>
+ </tal:block>
+
+ <metal:block use-macro="templates/issue.index/macros/batch-footer" />
+
+</table>
+</td>
+
+</tal:block>
diff --git a/share/roundup/templates/devel/html/msg.item.html b/share/roundup/templates/devel/html/msg.item.html
new file mode 100644 (file)
index 0000000..ebe16eb
--- /dev/null
@@ -0,0 +1,123 @@
+<tal:block metal:use-macro="templates/page/macros/frame">
+<title metal:fill-slot="head_title">
+<tal:block condition="context/id" i18n:translate=""
+ >Message <span tal:replace="context/id" i18n:name="id"
+ /> - <span tal:replace="config/TRACKER_NAME" i18n:name="tracker"
+/></tal:block>
+<tal:block condition="not:context/id" i18n:translate=""
+ >New Message - <span tal:replace="config/TRACKER_NAME" i18n:name="tracker"
+/></tal:block>
+</title>
+<tal:block metal:fill-slot="body_title">
+ <span tal:condition="python: not (context.id or context.is_edit_ok())"
+  tal:omit-tag="python:1" i18n:translate="">New Message</span>
+ <span tal:condition="python: not context.id and context.is_edit_ok()"
+  tal:omit-tag="python:1" i18n:translate="">New Message Editing</span>
+ <span tal:condition="python: context.id and not context.is_edit_ok()"
+  tal:omit-tag="python:1" i18n:translate="">Message<tal:x
+  replace="context/id" i18n:name="id" /></span>
+ <span tal:condition="python: context.id and context.is_edit_ok()"
+  tal:omit-tag="python:1" i18n:translate="">Message<tal:x
+  replace="context/id" i18n:name="id" /> Editing</span>
+</tal:block>
+<td class="content" metal:fill-slot="content">
+
+<p tal:condition="python:not (context.is_view_ok()
+ or request.user.hasRole('Anonymous'))" i18n:translate="">
+ You are not allowed to view this page.</p>
+
+<p tal:condition="python:not context.is_view_ok()
+ and request.user.hasRole('Anonymous')" i18n:translate="">
+ Please login with your username and password.</p>
+
+<div tal:condition="context/is_view_ok">
+<table class="form">
+
+<tr>
+ <th i18n:translate="">Author</th>
+ <td tal:content="context/author"></td>
+</tr>
+
+<tr>
+ <th i18n:translate="">Recipients</th>
+ <td tal:content="context/recipients"></td>
+</tr>
+
+<tr>
+ <th i18n:translate="">Date</th>
+ <td tal:content="context/date"></td>
+</tr>
+
+ <tr>
+  <th i18n:translate="">Message-id</th>
+  <td tal:content="context/messageid"></td>
+ </tr>
+
+ <tr>
+  <th i18n:translate="">In-reply-to</th>
+  <td tal:content="context/inreplyto"></td>
+ </tr>
+
+</table>
+
+<!--<p tal:condition="python:utils.sb_is_spam(context)" class="error-message">
+   Message has been classified as spam</p>-->
+
+<table class="messages">
+
+<!-- <tr>
+   <th class="header" i18n:translate="">Content</th>
+   <th class="header"
+   tal:condition="python:request.user.hasPermission('SB: May Classify')">
+     <form method="POST" onSubmit="return submit_once()"
+       enctype="multipart/form-data"
+       tal:attributes="action context/designator">
+      <input type="hidden" name="@action" value="spambayes_classify">
+      <input type="submit" name="trainspam" value="Mark as SPAM" i18n:attributes="value">
+      <input type="submit" name="trainham" value="Mark as HAM (not SPAM)" i18n:attributes="value">
+     </form>
+   </th>
+
+</tr>-->
+
+ <tr>
+  <td class="content" colspan="2"
+      tal:condition="python:context.content.is_view_ok()">
+      <pre
+      tal:content="structure python:utils.localReplace(context.content.hyperlinked())"></pre></td>
+  <td class="content" colspan="2"
+      tal:condition="python:not context.content.is_view_ok()">
+      You are not authorized to see this message.
+<!--            Message has been classified as spam and is therefore not
+      available to unathorized users. If you think this is
+      incorrect, please login and report the message as being
+      misclassified. -->
+  </td> 
+ </tr>
+</table>
+
+<table class="files" tal:condition="context/files">
+ <tr><th colspan="2" class="header" i18n:translate="">Files</th></tr>
+ <tr>
+  <th i18n:translate="">File name</th>
+  <th i18n:translate="">Uploaded</th>
+ </tr>
+ <tr tal:repeat="file context/files">
+  <td>
+   <a tal:attributes="href string:file${file/id}/${file/name}"
+      tal:content="file/name">dld link</a>
+  </td>
+  <td>
+   <span tal:content="file/creator">creator's name</span>,
+   <span tal:content="file/creation">creation date</span>
+  </td>
+ </tr>
+</table>
+
+<tal:block tal:replace="structure context/history" />
+
+</div>
+</td>
+
+</tal:block>
diff --git a/share/roundup/templates/devel/html/page.html b/share/roundup/templates/devel/html/page.html
new file mode 100644 (file)
index 0000000..339c0bd
--- /dev/null
@@ -0,0 +1,407 @@
+<tal:block metal:define-macro="frame">
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html>
+ <head>
+  <title metal:define-slot="head_title">title goes here</title>
+  <link rel="stylesheet" type="text/css" href="@@file/style.css" />
+  <meta http-equiv="Content-Type"
+        tal:attributes="content string:text/html;; charset=${request/client/charset}" />
+  <script tal:replace="structure request/base_javascript"></script>
+  <metal:x define-slot="more-javascript" />
+ </head>
+ <body tal:attributes="class request/classname"
+       tal:define="kw_create python:request.user.hasPermission('Create', 'keyword');
+                   bug_columns string:id,activity,title,creator,status;
+                   bug_columns_showall string:id,activity,title,creator,assignee,status;
+                   bug_status_notclosed string:-1,1,2,4;
+
+                   task_columns string:id,activity,title,creator,status;
+                   task_columns_showall string:id,activity,title,creator,assignee,status;
+                   task_status_notclosed string:-1,1,2,4;
+
+                   milestone_columns string:id,activity,title,creator,status;
+                   milestone_columns_showall string:id,activity,title,creator,assignee,status;
+                   milestone_status_notclosed string:-1,1,2,4;">
+  <div class="header">
+   <h1><a href="/">Roundup Demo Tracker</a></h1>
+   <div id="searchbox">
+    <form name="searchform" method="get" action="bug" id="searchform">
+     <div id="search">
+      <input type="hidden" name="@columns"
+             tal:attributes="value bug_columns_showall"/>
+      <input type="hidden" name="@sort" value="-activity" />
+      <input type="hidden" name="@group" value="priority" />
+      <input type="hidden" name="@filter" value="status"/>
+      <input type="hidden" name="status" tal:attributes="value bug_status_notclosed"/>
+      <input class="input-text" id="search-text" name="@search_text" size="10" />
+      <input type="submit" id="submit" value="search in open bugs" name="submit" class="input-button"/>
+     </div>
+    </form>
+   </div> <!-- searchbox -->
+  </div> <!-- header -->
+  <div class="navigation">
+   <div class="menu">
+    <ul>
+     <li class="current"><a href="/" i18n:translate="">Bugs</a>
+      <ul tal:condition="python:request.user.hasPermission('View', 'bug')">
+       <li tal:condition="python:request.user.hasPermission('Create', 'bug')">
+        <a href="bug?@template=item" i18n:translate="">Create New</a>
+       </li>
+       <li>
+        <a href="#"
+           tal:attributes="href python:request.indexargs_url('bug', {
+                                '@sort': '-activity',
+                                '@group': 'priority',
+                                '@filter': 'status',
+                                '@columns': bug_columns_showall,
+                                '@search_text': '',
+                                'status': bug_status_notclosed,
+                                '@dispname': i18n.gettext('Show All'),
+                                '@startwith': 0,
+                                })"
+                i18n:translate="">Show Open</a>
+       </li>
+       <li><a href="#"
+              tal:attributes="href python:request.indexargs_url('bug', {
+                                '@sort': '-activity',
+                                '@group': 'priority',
+                                '@filter': 'status,assignee',
+                                '@columns': bug_columns,
+                                '@search_text': '',
+                                'status': bug_status_notclosed,
+                                'assignee': '-1',
+                                '@dispname': i18n.gettext('Show Unassigned'),
+                                '@startwith': 0,
+                                })"
+                i18n:translate="">Show Unassigned</a>
+       </li>
+       <li>
+        <a href="bug?@template=search&status=1" i18n:translate="">Search</a>
+       </li>
+       <li>
+        <form method="post" action="#">
+         <input type="submit" class="form-small" value="Show bug:" i18n:attributes="value"/>
+         <input class="form-small" size="4" type="text" name="@number"/>
+         <input type="hidden" name="@type" value="bug"/>
+         <input type="hidden" name="@action" value="show"/>
+        </form>
+       </li>
+      </ul>
+     </li>
+     <li class="current"><a href="/" i18n:translate="">Tasks</a>
+      <ul tal:condition="python:request.user.hasPermission('View', 'task')">
+       <li tal:condition="python:request.user.hasPermission('Create', 'task')">
+        <a href="task?@template=item" i18n:translate="">Create New</a>
+       </li>
+       <li>
+        <a href="#" tal:attributes="href python:request.indexargs_url('task', {
+                                '@sort': '-activity',
+                                '@group': 'priority',
+                                '@filter': 'status',
+                                '@columns': task_columns_showall,
+                                '@search_text': '',
+                                'status': task_status_notclosed,
+                                '@dispname': i18n.gettext('Show All'),
+                                '@startwith': 0,
+                                })"
+                i18n:translate="">Show Open</a>
+       </li>
+       <li>
+        <a href="#" tal:attributes="href python:request.indexargs_url('task', {
+                                '@sort': '-activity',
+                                '@group': 'priority',
+                                '@filter': 'status,assignee',
+                                '@columns': task_columns,
+                                '@search_text': '',
+                                'status': task_status_notclosed,
+                                'assignee': '-1',
+                                '@dispname': i18n.gettext('Show Unassigned'),
+                                '@startwith': 0,
+                                })"
+                i18n:translate="">Show Unassigned</a>
+       </li>
+       <li>
+        <a href="task?@template=search&status=1" i18n:translate="">Search</a>
+       </li>
+       <li>
+        <form method="post" action="#">
+         <input type="submit" class="form-small" value="Show task:" i18n:attributes="value"/>
+         <input class="form-small" size="4" type="text" name="@number"/>
+         <input type="hidden" name="@type" value="task"/>
+         <input type="hidden" name="@action" value="show"/>
+        </form>
+       </li>
+      </ul>
+     </li>
+     <li class="current"><a href="milestone" i18n:translate="">Milestones</a>
+      <ul tal:condition="python:request.user.hasPermission('View', 'milestone')">
+       <li tal:condition="python:request.user.hasPermission('Create', 'milestone')">
+        <a href="milestone?@template=item" i18n:translate="">Create New</a>
+       </li>
+       <li>
+        <a href="#" tal:attributes="href python:request.indexargs_url('milestone', {
+                                '@filter': 'status',
+                                'status': milestone_status_notclosed,
+                                '@dispname': i18n.gettext('Show Open'),
+                                '@startwith': 0,
+                                })"
+                i18n:translate="">Show Open</a>
+       </li>
+      </ul>
+     </li>
+    </ul>
+    <ul class="user">
+     <li tal:condition="python:request.user.username=='anonymous'" class="submenu">
+      <b i18n:translate="">User</b>
+      <form method="post" action="#">
+       <ul>
+        <li>
+         <tal:span i18n:translate="">Login</tal:span><br/>
+         <input size="10" name="__login_name"/><br/>
+         <input size="10" type="password" name="__login_password"/><br/>
+         <input type="hidden" name="@action" value="Login"/>
+         <input type="checkbox" name="remember" id="remember"/>
+         <label for="remember" i18n:translate="">Remember me?</label><br/>
+         <input class="form-small" type="submit" value="Login" i18n:attributes="value"/><br/>
+         <input type="hidden" name="__came_from" tal:attributes="value string:${request/env/PATH_INFO}"/>
+         <span tal:replace="structure request/indexargs_form" />
+        </li>
+        <li>
+         <a href="user?@template=register"
+            tal:condition="python:request.user.hasPermission('Create', 'user')"
+            i18n:translate="">Register</a>
+        </li>
+        <li>
+         <a href="user?@template=forgotten" i18n:translate="">Lost&nbsp;your&nbsp;login?</a>
+        </li>
+       </ul>
+      </form>
+     </li>
+     <li tal:condition="python:request.user.username != 'anonymous'" class="submenu">
+      <p class="label"><b tal:replace="request/user/username">username</b></p>
+      <ul>
+       <li>
+        <a href="#" tal:attributes="href python:request.indexargs_url('bug', {
+                                     '@sort': '-activity',
+                                     '@group': 'priority',
+                                     '@filter': 'status,assignee',
+                                     '@columns': bug_columns,
+                                     '@search_text': '',
+                                     'status': bug_status_notclosed,
+                                     'assignee': request.user.id,
+                                     '@dispname': i18n.gettext('Bugs assigned to you'),
+                                     '@startwith': 0})"
+                    i18n:translate="">Bugs assigned to you</a>
+       </li>
+       <li>
+        <a tal:attributes="href string:user${request/user/id}" i18n:translate="">Your Details</a>
+       </li>
+       <li>
+        <a tal:attributes="href python:request.indexargs_url('', {'@action':'logout'})" 
+           i18n:translate="">Logout</a>
+       </li>
+       <li class="" tal:condition="python:request.user.hasPermission('View', 'query')">
+        <span i18n:translate=""><b>Your Queries</b> (<a class="nomargin" href="query?@template=edit">edit</a>)</span><br/>
+        <ul tal:repeat="qs request/user/queries">
+         <li>
+          <a tal:attributes="href string:${qs/klass}?${qs/url}&@dispname=${qs/name}" tal:content="qs/name">link</a>
+         </li>
+        </ul>
+       </li>
+       <li class="" tal:condition="python:request.user.hasPermission('View', 'user')">
+        <b i18n:translate="">Administration</b>
+        <ul>
+         <li tal:condition="python:request.user.hasPermission('Edit', None)">
+          <a href="home?@template=classlist" i18n:translate="">Class List</a>
+         </li>
+         <li tal:condition="python:request.user.hasPermission('View', 'user')
+                            or request.user.hasPermission('Edit', 'user')">
+          <a href="user?@sort=username"  i18n:translate="">User List</a>
+         </li>
+         <li tal:condition="python:request.user.hasPermission('Create', 'user')">
+          <a href="user?@template=item" i18n:translate="">Add User</a>
+         </li>
+         <li tal:condition="python:request.user.hasPermission('Edit', 'keyword')">
+          <a href="keyword" i18n:translate="">Edit Keywords</a>
+         </li>
+        </ul>
+       </li>
+      </ul>
+     </li>
+    </ul> <!-- user -->
+   </div> <!-- menu -->
+  </div> <!-- navigation -->
+  <div class="content">
+   <h1 id="breadcrumb"><span metal:define-slot="body_title">body title</span></h1>
+   <p tal:condition="options/error_message | nothing" class="error-message"
+      tal:repeat="m options/error_message" tal:content="structure m" />
+   <p tal:condition="options/ok_message | nothing" class="ok-message">
+    <span tal:repeat="m options/ok_message" tal:content="structure string:$m <br/ > " />
+    <a class="form-small" tal:attributes="href request/current_url" i18n:translate="">clear this message</a>
+   </p>
+   <tal:block metal:define-slot="content">Page content goes here</tal:block>
+  </div> <!-- content -->
+  <div class="footer">
+<!-- Created: Wed Jan 14 11:55:38 EST 2009 -->
+<!-- hhmts start -->
+Last modified: Wed Jun  3 00:02:37 EDT 2009
+<!-- hhmts end -->
+  </div> <!-- footer -->
+  <pre tal:condition="request/form/deissue | nothing" tal:content="request"></pre>
+ </body>
+</html>
+</tal:block>
+
+<!--
+The following macros are intended to be used in search pages.
+
+The invoking context must define a "name" variable which names the
+property being searched.
+
+See issue.search.html in the classic template for examples.
+-->
+
+<!-- creates a th and a label: -->
+<th metal:define-macro="th_label"
+    tal:define="required required | python:[]"
+    tal:attributes="class python:(name in required) and 'required' or nothing">
+  <label tal:attributes="for name" tal:content="label" i18n:translate="">text</label>
+       <metal:x define-slot="behind_the_label" />
+</th>
+
+<td metal:define-macro="search_input">
+  <input tal:attributes="value python:request.form.getvalue(name) or nothing;
+                         name name;
+                         id name"/>
+</td>
+
+<td metal:define-macro="search_date">
+  <input tal:attributes="value python:request.form.getvalue(name) or nothing;
+                         name name;
+                         id name"/>
+  <a class="classhelp"
+        tal:attributes="href python:'''javascript:help_window('task?@template=calendar&property=%s&form=itemSynopsis', 300, 200)'''%name">(cal)</a>
+</td>
+
+<td metal:define-macro="search_popup">
+  <!--
+    context needs to specify the popup "columns" as a comma-separated
+    string (eg. "id,title" or "id,name,description") as well as name
+  -->
+  <input tal:attributes="value python:request.form.getvalue(name) or nothing;
+                         name name;
+                         id name"/>
+  <span tal:replace="structure python:db.task.classhelp(columns,
+                                      property=name)" />
+</td>
+
+<td metal:define-macro="search_select">
+  <select tal:attributes="name name; id name"
+          tal:define="value python:request.form.getvalue(name)">
+    <option value="" i18n:translate="">don't care</option>
+    <metal:slot define-slot="extra_options" />
+    <option value="" i18n:translate="" disabled="disabled">------------</option>
+    <option tal:repeat="s python:db[db_klass].list()"
+            tal:attributes="value s/id; selected python:value == s.id"
+            tal:content="python:s[db_content]"></option>
+  </select>
+</td>
+
+<!-- like search_select, but translates the further values.
+Could extend it (METAL 1.1 attribute "extend-macro")
+-->
+<td metal:define-macro="search_select_translated">
+  <select tal:attributes="name name; id name"
+          tal:define="value python:request.form.getvalue(name)">
+    <option value="" i18n:translate="">don't care</option>
+    <metal:slot define-slot="extra_options" />
+    <option value="" i18n:translate="" disabled="disabled">------------</option>
+    <option tal:repeat="s python:db[db_klass].list()"
+            tal:attributes="value s/id; selected python:value == s.id"
+                                               tal:content="python:s[db_content]"
+                                               i18n:translate=""></option>
+  </select>
+</td>
+
+<!-- currently, there is no convenient API to get a list of all roles -->
+<td metal:define-macro="search_select_roles"
+         tal:define="onchange onchange | nothing">
+  <select name="roles" id="roles" tal:attributes="onchange onchange">
+    <option value="" i18n:translate="">don't care</option>
+    <option value="" i18n:translate="" disabled="disabled">------------</option>
+    <option value="User">User</option>
+    <option value="Developer">Developer</option>
+    <option value="Coordinator">Coordinator</option>
+  </select>
+</td>
+
+<td metal:define-macro="search_multiselect">
+  <input tal:attributes="value python:request.form.getvalue(name) or nothing;
+                         name name;
+                         id name"/>
+  <span tal:replace="structure python:db[db_klass].classhelp(db_content,
+                                        property=name, width='600')" />
+</td>
+
+<td metal:define-macro="search_checkboxes">
+ <ul class="search-checkboxes"
+     tal:define="value python:request.form.getvalue(name);
+                 values python:value and value.split(',') or []">
+ <li tal:repeat="s python:db[db_klass].list()">
+  <input type="checkbox" tal:attributes="name name; id string:$name-${s/id};
+    value s/id; checked python:s.id in values" />
+  <label tal:attributes="for string:$name-${s/id}"
+         tal:content="python:s[db_content]" />
+ </li>
+ <li metal:define-slot="no_value_item">
+  <input type="checkbox" value="-1" tal:attributes="name name;
+     id string:$name--1; checked python:value == '-1'" />
+  <label tal:attributes="for string:$name--1" i18n:translate="">no value</label>
+ </li>
+ </ul>
+</td>
+
+<td metal:define-macro="column_input">
+  <input type="checkbox" name="@columns"
+         tal:attributes="value name;
+                         checked python:name in cols"/>
+</td>
+
+<td metal:define-macro="sort_input">
+  <input type="radio" name="@sort"
+         tal:attributes="value name;
+                         checked python:name == sort_on"/>
+</td>
+
+<td metal:define-macro="group_input">
+  <input type="radio" name="@group"
+         tal:attributes="value name;
+                         checked python:name == group_on"/>
+</td>
+
+<!--
+The following macros are intended for user editing.
+
+The invoking context must define a "name" variable which names the
+property being searched; the "edit_ok" variable tells whether the
+current user is allowed to edit.
+
+See user.item.html in the classic template for examples.
+-->
+<script metal:define-macro="user_utils" type="text/javascript" src="@@file/user_utils.js"></script>
+
+<!-- src: value will be re-used for other input fields -->
+<input metal:define-macro="user_src_input"
+    type="text" tal:attributes="onblur python:edit_ok and 'split_name(this)';
+    id name; name name; value value; readonly not:edit_ok"
+    value="heinz.kunz"/>
+<!-- normal: no re-using -->
+<input metal:define-macro="user_normal_input" type="text"
+    tal:attributes="id name; name name; value value; readonly not:edit_ok"
+    value="heinz"/>
+<!-- password: type; no initial value -->
+    <input metal:define-macro="user_pw_input" type="password"
+    tal:attributes="id name; name name; readonly not:edit_ok" value=""/>
+    <input metal:define-macro="user_confirm_input" type="password"
+    tal:attributes="id name; name string:@confirm@$name; readonly not:edit_ok" value=""/>
+
diff --git a/share/roundup/templates/devel/html/patch.index.html b/share/roundup/templates/devel/html/patch.index.html
new file mode 100644 (file)
index 0000000..a9186c4
--- /dev/null
@@ -0,0 +1,66 @@
+<tal:block metal:use-macro="templates/page/macros/frame">
+<title metal:fill-slot="head_title" i18n:translate="">
+  List of patches - <span tal:replace="config/TRACKER_NAME" i18n:name="tracker" />
+</title>
+<span metal:fill-slot="body_title" tal:omit-tag="python:1"
+  i18n:translate="">List of patches</span>
+<td class="content" metal:fill-slot="content">
+
+<table class="otherinfo" tal:define="batch request/batch">
+ <tr><th style="padding-right: 10" i18n:translate="">Download</th>
+     <th style="padding-right: 10" i18n:translate="">Description</th>
+     <th style="padding-right: 10" i18n:translate="">Content Type</th>
+     <th style="padding-right: 10" i18n:translate="">Uploaded By</th>
+     <th style="padding-right: 10" i18n:translate="">Date</th>
+ </tr>
+ <tr tal:repeat="patch batch" tal:attributes="class python:['normal', 'alt'][repeat['patch'].index%6/3]">
+  <td>
+   <a tal:attributes="href string:patch${patch/id}/${patch/name}"
+      tal:content="patch/name">dld link</a>
+  </td>
+  <td tal:content="patch/description">description</td>
+  <td tal:content="patch/type">content type</td>
+  <td tal:content="patch/creator">creator's name</td>
+  <td tal:content="patch/creation">creation date</td>
+ </tr>
+
+ <metal:block use-macro="templates/issue.index/macros/batch-footer" />
+
+</table>
+
+</td>
+
+</tal:block>
+<tal:block metal:use-macro="templates/page/macros/frame">
+<title metal:fill-slot="head_title" i18n:translate="">
+  List of patches - <span tal:replace="config/TRACKER_NAME" i18n:name="tracker" />
+</title>
+<span metal:fill-slot="body_title" tal:omit-tag="python:1"
+  i18n:translate="">List of patches</span>
+<td class="content" metal:fill-slot="content">
+
+<table class="otherinfo" tal:define="batch request/batch">
+ <tr><th style="padding-right: 10" i18n:translate="">Download</th>
+     <th style="padding-right: 10" i18n:translate="">Description</th>
+     <th style="padding-right: 10" i18n:translate="">Content Type</th>
+     <th style="padding-right: 10" i18n:translate="">Uploaded By</th>
+     <th style="padding-right: 10" i18n:translate="">Date</th>
+ </tr>
+ <tr tal:repeat="patch batch" tal:attributes="class python:['normal', 'alt'][repeat['patch'].index%6/3]">
+  <td>
+   <a tal:attributes="href string:patch${patch/id}/${patch/name}"
+      tal:content="patch/name">dld link</a>
+  </td>
+  <td tal:content="patch/description">description</td>
+  <td tal:content="patch/type">content type</td>
+  <td tal:content="patch/creator">creator's name</td>
+  <td tal:content="patch/creation">creation date</td>
+ </tr>
+
+ <metal:block use-macro="templates/issue.index/macros/batch-footer" />
+
+</table>
+
+</td>
+
+</tal:block>
diff --git a/share/roundup/templates/devel/html/patch.item.html b/share/roundup/templates/devel/html/patch.item.html
new file mode 100644 (file)
index 0000000..b616ac1
--- /dev/null
@@ -0,0 +1,172 @@
+<tal:block metal:use-macro="templates/page/macros/frame">
+<title metal:fill-slot="head_title" i18n:translate="">Patch display - <span
+ i18n:name="tracker" tal:replace="config/TRACKER_NAME" /></title>
+<span metal:fill-slot="body_title" tal:omit-tag="python:1"
+ i18n:translate="">Modify patch</span>
+
+<td class="content" metal:fill-slot="content">
+
+<p tal:condition="python:not (context.is_view_ok()
+ or request.user.hasRole('Anonymous'))" i18n:translate="">
+ You are not allowed to view this page.</p>
+
+<p tal:condition="python:not context.is_view_ok()
+ and request.user.hasRole('Anonymous')" i18n:translate="">
+ Please login with your username and password.</p>
+
+<form method="POST" onSubmit="return submit_once()"
+      enctype="multipart/form-data" tal:condition="context/is_view_ok"
+      tal:attributes="action context/designator">
+
+<table class="form">
+ <tr>
+  <th i18n:translate="">Name</th>
+  <td tal:content="structure context/name/field"></td>
+ </tr>
+ <tr>
+  <th i18n:translate="">Description</th>
+  <td tal:content="structure context/description/field"></td>
+ </tr>
+ <tr>
+  <th i18n:translate="">Content Type</th>
+  <td tal:content="structure context/type/field"/>
+  <td style="border: none" tal:condition="python: context.is_edit_ok()">Please note that
+  for security reasons, it's not permitted to set content type to <i>text/html</i>.</td>
+ </tr>
+ <tr>
+  <th i18n:translate="">Repository</th>
+  <td tal:content="structure context/repository/field"></td>
+ </tr>
+ <tr>
+  <th i18n:translate="">Repository revision</th>
+  <td tal:content="structure context/revision/field"></td>
+ </tr>
+ <tr tal:condition="python:context.is_edit_ok()">
+  <td>
+   &nbsp;
+   <input type="hidden" name="@template" value="item">
+   <input type="hidden" name="@required" value="name,type">
+   <input type="hidden" name="@multilink"
+          tal:condition="python:request.form.has_key('@multilink')"
+          tal:attributes="value request/form/@multilink/value">
+  </td>
+  <td tal:content="structure context/submit">submit button here</td>
+ </tr>
+</table>
+</form>
+
+<!--<p tal:condition="python:utils.sb_is_spam(context)" class="error-message">
+   File has been classified as spam.</p>-->
+
+<a tal:condition="python:context.id and context.content.is_view_ok()"
+ tal:attributes="href string:patch${context/id}/${context/name}"
+ i18n:translate="">download</a>
+
+<!--<p tal:condition="python:context.id and not context.content.is_view_ok()">
+   Files classified as spam are not available for download by
+   unathorized users. If you think the file has been misclassified,
+   please login and click on the button for reclassification.
+</p>-->
+
+
+<!--     <form method="POST" onSubmit="return submit_once()"
+       enctype="multipart/form-data"
+       tal:attributes="action context/designator"
+       tal:condition="python:request.user.hasPermission('SB: May Classify')">
+      <input type="hidden" name="@action" value="spambayes_classify">
+      <input type="submit" name="trainspam" value="Mark as SPAM" i18n:attributes="value">
+      <input type="submit" name="trainham" value="Mark as HAM (not SPAM)" i18n:attributes="value">
+     </form>-->
+
+<tal:block tal:condition="context/id" tal:replace="structure context/history" />
+
+</td>
+
+</tal:block>
+<tal:block metal:use-macro="templates/page/macros/frame">
+<title metal:fill-slot="head_title" i18n:translate="">Patch display - <span
+ i18n:name="tracker" tal:replace="config/TRACKER_NAME" /></title>
+<span metal:fill-slot="body_title" tal:omit-tag="python:1"
+ i18n:translate="">Patch display</span>
+
+<td class="content" metal:fill-slot="content">
+
+<p tal:condition="python:not (context.is_view_ok()
+ or request.user.hasRole('Anonymous'))" i18n:translate="">
+ You are not allowed to view this page.</p>
+
+<p tal:condition="python:not context.is_view_ok()
+ and request.user.hasRole('Anonymous')" i18n:translate="">
+ Please login with your username and password.</p>
+
+<form method="POST" onSubmit="return submit_once()"
+      enctype="multipart/form-data" tal:condition="context/is_view_ok"
+      tal:attributes="action context/designator">
+
+<table class="form">
+ <tr>
+  <th i18n:translate="">Name</th>
+  <td tal:content="structure context/name/field"></td>
+ </tr>
+ <tr>
+  <th i18n:translate="">Description</th>
+  <td tal:content="structure context/description/field"></td>
+ </tr>
+ <tr>
+  <th i18n:translate="">Content Type</th>
+  <td tal:content="structure context/type/field"/>
+  <td style="border: none" tal:condition="python: context.is_edit_ok()">Please note that
+  for security reasons, it's not permitted to set content type to <i>text/html</i>.</td>
+ </tr>
+ <tr>
+  <th i18n:translate="">Repository</th>
+  <td tal:content="structure context/repository/field"></td>
+ </tr>
+ <tr>
+  <th i18n:translate="">Repository revision</th>
+  <td tal:content="structure context/revision/field"></td>
+ </tr>
+ <tr tal:condition="python:context.is_edit_ok()">
+  <td>
+   &nbsp;
+   <input type="hidden" name="@template" value="item">
+   <input type="hidden" name="@required" value="name,type">
+   <input type="hidden" name="@multilink"
+          tal:condition="python:request.form.has_key('@multilink')"
+          tal:attributes="value request/form/@multilink/value">
+  </td>
+  <td tal:content="structure context/submit">submit button here</td>
+ </tr>
+</table>
+</form>
+
+<!--<p tal:condition="python:utils.sb_is_spam(context)" class="error-message">
+   File has been classified as spam.</p>-->
+
+<a tal:condition="python:context.id and context.content.is_view_ok()"
+ tal:attributes="href string:patch${context/id}/${context/name}"
+ i18n:translate="">download</a>
+
+<!--<p tal:condition="python:context.id and not context.content.is_view_ok()">
+   Files classified as spam are not available for download by
+   unathorized users. If you think the file has been misclassified,
+   please login and click on the button for reclassification.
+</p>-->
+
+
+<!--     <form method="POST" onSubmit="return submit_once()"
+       enctype="multipart/form-data"
+       tal:attributes="action context/designator"
+       tal:condition="python:request.user.hasPermission('SB: May Classify')">
+      <input type="hidden" name="@action" value="spambayes_classify">
+      <input type="submit" name="trainspam" value="Mark as SPAM" i18n:attributes="value">
+      <input type="submit" name="trainham" value="Mark as HAM (not SPAM)" i18n:attributes="value">
+     </form>-->
+
+<tal:block tal:condition="context/id" tal:replace="structure context/history" />
+
+</td>
+
+</tal:block>
diff --git a/share/roundup/templates/devel/html/query.edit.html b/share/roundup/templates/devel/html/query.edit.html
new file mode 100644 (file)
index 0000000..3afc099
--- /dev/null
@@ -0,0 +1,108 @@
+<tal:block metal:use-macro="templates/page/macros/frame">
+<title metal:fill-slot="head_title" i18n:translate=""
+ >"Your Queries" Editing - <span tal:replace="config/TRACKER_NAME"
+ i18n:name="tracker" /></title>
+<span metal:fill-slot="body_title" tal:omit-tag="python:1"
+ i18n:translate="">"Your Queries" Editing</span>
+
+<td class="content" metal:fill-slot="content">
+
+<span tal:condition="not:context/is_edit_ok"
+ i18n:translate="">You are not allowed to edit queries.</span>
+
+<script language="javascript">
+// This exists solely because I can't figure how to get the & into an
+// attributes TALES expression, and so it keeps getting quoted.
+function retire(qid) {
+    window.location = 'query'+qid+'?@action=retire&@template=edit';
+}
+</script>
+
+<form method="POST" onSubmit="return submit_once()" action="query"
+      enctype="multipart/form-data" tal:condition="context/is_edit_ok">
+
+<table class="list" width="100%"
+       tal:define="uid request/user/id; mine request/user/queries">
+
+<tr><th i18n:translate="">Query</th>
+    <th i18n:translate="">Include in "Your Queries"</th>
+    <th i18n:translate="">Edit</th>
+    <th i18n:translate="">Private to you?</th>
+    <th>&nbsp;</th>
+</tr>
+
+<tr tal:repeat="query mine">
+ <tal:block condition="query/is_retired">
+
+ <td><a tal:attributes="href string:${query/klass}?${query/url}"
+        tal:content="query/name">query</a></td>
+
+ <td metal:define-macro="include">
+  <select tal:condition="python:query.id not in mine"
+          tal:attributes="name string:user${uid}@add@queries">
+    <option value="" i18n:translate="">leave out</option>
+    <option tal:attributes="value query/id" i18n:translate="">include</option>
+  </select>
+  <select tal:condition="python:query.id in mine"
+          tal:attributes="name string:user${uid}@remove@queries">
+    <option value="" i18n:translate="">leave in</option>
+    <option tal:attributes="value query/id" i18n:translate="">remove</option>
+  </select>
+ </td>
+
+ <td colspan="3" i18n:translate="">[query is retired]</td>
+
+ <!-- <td> maybe offer "restore" some day </td> -->
+ </tal:block>
+</tr>
+
+<tr tal:define="queries python:db.query.filter(filterspec={'private_for':uid})"
+     tal:repeat="query queries">
+ <td><a tal:attributes="href string:${query/klass}?${query/url}"
+        tal:content="query/name">query</a></td>
+
+ <td metal:use-macro="template/macros/include" />
+
+ <td><a tal:attributes="href string:query${query/id}" i18n:translate="">edit</a></td>
+
+ <td>
+  <select tal:attributes="name string:query${query/id}@private_for">
+   <option tal:attributes="selected python:query.private_for == uid;
+           value uid" i18n:translate="">yes</option>
+   <option tal:attributes="selected python:query.private_for == None"
+           value="-1" i18n:translate="">no</option>
+  </select>
+ </td>
+
+ <td>
+  <input type="button" value="Delete" i18n:attributes="value"
+  tal:attributes="onClick python:'''retire('%s')'''%query.id">
+  </td>
+</tr>
+
+<tr tal:define="queries python:db.query.filter(filterspec={'private_for':None})"
+     tal:repeat="query queries">
+ <td><a tal:attributes="href string:${query/klass}?${query/url}"
+        tal:content="query/name">query</a></td>
+
+ <td metal:use-macro="template/macros/include" />
+
+ <td colspan="3" tal:condition="query/is_edit_ok">
+  <a tal:attributes="href string:query${query/id}" i18n:translate="">edit</a>
+ </td>
+ <td tal:condition="not:query/is_edit_ok" colspan="3"
+    i18n:translate="">[not yours to edit]</td>
+
+</tr>
+
+<tr><td colspan="5">
+   <input type="hidden" name="@action" value="edit">
+   <input type="hidden" name="@template" value="edit">
+   <input type="submit" value="Save Selection" i18n:attributes="value">
+</td></tr>
+
+</table>
+
+</form>
+</td>
+</tal:block>
diff --git a/share/roundup/templates/devel/html/query.item.html b/share/roundup/templates/devel/html/query.item.html
new file mode 100644 (file)
index 0000000..453e13d
--- /dev/null
@@ -0,0 +1,3 @@
+<!-- query.item -->
+<span tal:replace="structure context/renderQueryForm" />
+
diff --git a/share/roundup/templates/devel/html/search.js b/share/roundup/templates/devel/html/search.js
new file mode 100644 (file)
index 0000000..86d4eef
--- /dev/null
@@ -0,0 +1,31 @@
+// Inspect a form element to construct a 'get' request,
+// register it to the 'submit' event, and deactivate the
+// form's submit action.
+function bind_search()
+{
+  var form = $("form");
+  var action = form.attr("action");
+  form.attr("action",""); // reset
+
+  function display(data)
+  {
+    var list = $("div.list");
+    list.empty();
+    list.append(data);
+  }
+
+  function query()
+  {
+    var inputs = $(":input");
+    var data = {}
+    for (var i = 0; i < inputs.length; i++)
+      data[inputs[i].name] = inputs[i].value;
+    jQuery.get(action, data, display);
+    return false;
+  }
+
+  form.submit(query);
+}
+
+
+$(document).ready(bind_search);
diff --git a/share/roundup/templates/devel/html/style.css b/share/roundup/templates/devel/html/style.css
new file mode 100644 (file)
index 0000000..0372f36
--- /dev/null
@@ -0,0 +1,138 @@
+/* layout*/
+body 
+{
+  font-family: sans-serif, Arial, Helvetica;
+  background-color: white;
+  color: #333;
+  margin:0;
+  padding: 0 3em 0 13em;
+}
+body > .header { margin: 0 0 0 -13em;}
+body > .footer { margin: 0 0 0 -13em; clear:both;}
+body > .navigation 
+{
+  margin-left: -13em;
+  width: 13em;
+  float: left;
+}
+body > .content 
+{
+  width: 100%;
+  margin: 0;
+}
+body > .header > #searchbox { position: absolute; right: 1em; top: 1em;}
+
+/* style */
+
+.footer { padding: 1em;}
+
+:link { color: #bb0000; text-decoration: none;}
+:visited { color: #770000; text-decoration: none;}
+
+.header h1 { margin-left: 1em; }
+
+body
+{
+  font-family: sans-serif, Arial, Helvetica;
+  background-color: #f5f5f5;
+  color: #333;
+}
+
+.menu { padding: 0; margin-right: 1em;}
+.menu ul 
+{
+  padding: 0;
+  margin: 0;
+}
+.menu li
+{
+  margin: 5pt 0;
+}
+.menu > ul > li > *
+{ 
+  display: block;
+  padding: 2pt 2pt 2pt 10pt;
+  border: solid thin #dadada;
+  background-color:#ffffff;
+}
+.menu > ul > li.current > *
+{ 
+  background-color:#dddddd;
+}
+
+.menu ul li:first-child { margin-top:0;}
+.menu ul { list-style-type:none;}
+
+/* sub-menus are indented */ 
+.menu > ul > li > ul,
+.menu > ul > li.current > ul
+{
+  border: none;
+  background-color: inherit;
+  margin: 0;
+}
+.menu ul ul 
+{
+  margin-left: 2em;
+  font-size: smaller;
+  margin: 0 0 0 5pt;
+}
+
+/* sub-menu items draw a separator */
+.menu ul ul > li 
+{  
+  margin: 0;
+  padding: 0;
+  border: none;
+  border-top: solid thin #dadada;
+  background-color: inherit;
+}
+.menu ul ul > li:first-child
+{  
+  border-top: none;
+}
+
+.footer
+{
+  font-size: small;
+  text-align: center;
+  color: lightgrey;
+}
+
+.content
+{ 
+  padding: 1em;
+  border: solid thin #dadada;
+  background-color: #ffffff;
+}
+
+#search { text-align: right; }
+
+p.ok-message 
+{
+  background-color: #eeffee;
+  border: 1px solid #009900;
+  padding: 5px;
+  color: #009900;
+  font-weight: bold;
+}
+p.error-message 
+{
+  background-color: #ffeeee;
+  border: 1px solid #990000;
+  padding: 5px;
+  color: #990000;
+  font-weight: bold;
+}
+
+table
+{ 
+  border-collapse: collapse;
+  border-spacing: 1px;
+  width: 100%;
+  background-color: #fafafa;
+}
+
+tr.odd { background-color:#f5f5f5; }
+
+input, textarea { border-width: 1px; }
diff --git a/share/roundup/templates/devel/html/task.index.html b/share/roundup/templates/devel/html/task.index.html
new file mode 100644 (file)
index 0000000..1080dfe
--- /dev/null
@@ -0,0 +1,179 @@
+<tal:block metal:use-macro="templates/page/macros/frame">
+<title metal:fill-slot="head_title" >
+  <span tal:omit-tag="true" i18n:translate="" >List of tasks</span>
+  <span tal:condition="request/dispname"
+   tal:replace="python:' - %s '%request.dispname"
+  /> - <span tal:replace="config/TRACKER_NAME" />
+</title>
+<span metal:fill-slot="body_title" tal:omit-tag="true">
+  <span tal:omit-tag="true" i18n:translate="" >List of tasks</span>
+  <span tal:condition="request/dispname"
+   tal:replace="python:' - %s' % request.dispname" />
+</span>
+<tal:block metal:fill-slot="content">
+
+<p tal:condition="python:not (context.is_view_ok()
+ or request.user.hasRole('Anonymous'))" i18n:translate="">
+ You are not allowed to view this page.</p>
+
+<p tal:condition="python:not context.is_view_ok()
+ and request.user.hasRole('Anonymous')" i18n:translate="">
+ Please login with your username and password.</p>
+
+<tal:block tal:define="batch request/batch" tal:condition="context/is_view_ok">
+ <table class="list">
+  <tr>
+   <th tal:condition="request/show/severity" i18n:translate="">Severity</th>
+   <th tal:condition="request/show/id" i18n:translate="">ID</th>
+   <th tal:condition="request/show/creation" i18n:translate="">Creation</th>
+   <th tal:condition="request/show/activity" i18n:translate="">Activity</th>
+   <th tal:condition="request/show/actor" i18n:translate="">Actor</th>
+   <th tal:condition="request/show/title" i18n:translate="">Title</th>
+   <th tal:condition="request/show/components"  i18n:translate="">Components</th>
+   <th tal:condition="request/show/versions" i18n:translate="">Versions</th>
+   <th tal:condition="request/show/status"    i18n:translate="">Status</th>
+   <th tal:condition="request/show/resolution" i18n:translate="">Resolution</th>
+   <th tal:condition="request/show/creator" i18n:translate="">Creator</th>
+   <th tal:condition="request/show/assignee" i18n:translate="">Assigned&nbsp;To</th>
+   <th tal:condition="request/show/keywords" i18n:translate="">Keywords</th>
+   <th tal:condition="request/show/dependencies" i18n:translate="">Depends On</th>
+   <th tal:condition="request/show/type" i18n:translate="">Type</th>
+  </tr>
+ <tal:block tal:repeat="i batch" condition="true">
+  <tr tal:define="group python:[r[1] for r in request.group]"
+      tal:condition="python:group and batch.propchanged(*group)">
+   <th tal:attributes="colspan python:len(request.columns)" class="group">
+    <tal:block tal:repeat="g group">
+     <tal:block tal:content="python:str(i[g]) or '(no %s set)'%g"/>
+    </tal:block>
+   </th>
+  </tr>
+
+  <tr tal:attributes="class python:['even','odd'][repeat['i'].even()]">
+   <td tal:condition="request/show/severity"
+       tal:content="python:i.severity.plain() or default">&nbsp;</td>
+   <td tal:condition="request/show/id" tal:content="i/id">&nbsp;</td>
+   <td class="date" tal:condition="request/show/creation"
+       tal:content="i/creation/reldate">&nbsp;</td>
+   <td class="date" tal:condition="request/show/activity"
+       tal:content="i/activity/reldate">&nbsp;</td>
+   <td tal:condition="request/show/actor"
+       tal:content="python:i.actor.plain() or default">&nbsp;</td>
+   <td tal:condition="request/show/title">
+    <a tal:attributes="href string:task${i/id}"
+               tal:content="python:str(i.title.plain(hyperlink=0)) or '[no title]'">title</a>
+   </td>
+   <td tal:condition="request/show/components"
+       tal:content="python:i.components.plain() or default">&nbsp;</td>
+   <td tal:condition="request/show/versions"
+       tal:content="python:i.versions.plain() or default">&nbsp;</td>
+   <td tal:condition="request/show/status"
+       tal:content="python:i.status.plain() or default">&nbsp;</td>
+   <td tal:condition="request/show/resolution"
+       tal:content="python:i.resolution.plain() or default">&nbsp;</td>
+   <td tal:condition="request/show/creator"
+       tal:content="python:i.creator.plain() or default">&nbsp;</td>
+   <td tal:condition="request/show/assignee"
+       tal:content="python:i.assignee.plain() or default">&nbsp;</td>
+   <td tal:condition="request/show/keywords"
+       tal:content="python:i.keywords.plain() or default">&nbsp;</td>
+   <td tal:condition="request/show/dependencies"
+       tal:content="python:i.dependencies.plain() or default">&nbsp;</td>
+   <td tal:condition="request/show/type"
+       tal:content="python:i.type.plain() or default">&nbsp;</td>
+  </tr>
+
+ </tal:block>
+
+ <metal:index define-macro="batch-footer">
+ <tr tal:condition="batch">
+  <th tal:attributes="colspan python:len(request.columns)">
+   <table width="100%">
+    <tr class="navigation">
+     <th>
+      <a tal:define="prev batch/previous" tal:condition="prev"
+         tal:attributes="href python:request.indexargs_url(request.classname,
+         {'@startwith':prev.first, '@pagesize':prev.size})"
+         i18n:translate="">&lt;&lt; previous</a>
+      &nbsp;
+     </th>
+     <th i18n:translate=""><span tal:replace="batch/start" i18n:name="start"
+     />..<span tal:replace="python: batch.start + batch.length -1" i18n:name="end"
+     /> out of <span tal:replace="batch/sequence_length" i18n:name="total"
+     /></th>
+     <th>
+      <a tal:define="next batch/next" tal:condition="next"
+         tal:attributes="href python:request.indexargs_url(request.classname,
+         {'@startwith':next.first, '@pagesize':next.size})"
+         i18n:translate="">next &gt;&gt;</a>
+      &nbsp;
+     </th>
+    </tr>
+   </table>
+  </th>
+ </tr>
+ </metal:index>
+</table>
+<!--
+<a tal:attributes="href python:request.indexargs_url('task',
+            {'@action':'export_csv'})" i18n:translate="">Download as CSV</a>
+-->
+<form method="get" class="index-controls"
+    tal:attributes="action request/classname">
+
+ <table class="form" tal:define="n_sort python:2">
+<!--
+<tal:block tal:repeat="n python:range(n_sort)" tal:condition="batch">
+  <tr tal:define="key python:len(request.sort)>n and request.sort[n]">
+   <th>
+    <tal:block tal:condition="not:n" i18n:translate="">Sort on:</tal:block>
+   </th>
+   <td>
+    <select tal:attributes="name python:'@sort%d'%n">
+     <option value="" i18n:translate="">- nothing -</option>
+     <option tal:repeat="col context/properties"
+             tal:attributes="value col/_name;
+                             selected python:key and col._name == key[1]"
+             tal:content="col/_name"
+             i18n:translate="">column</option>
+    </select>
+   </td>
+   <th i18n:translate="">Descending:</th>
+   <td><input type="checkbox" tal:attributes="name python:'@sortdir%d'%n;
+              checked python:key and key[0] == '-'"/>
+   </td>
+  </tr>
+  </tal:block>
+  -->
+  <tal:block tal:repeat="n python:range(n_sort)" tal:condition="batch">
+  <tr tal:define="key python:len(request.group)>n and request.group[n]">
+   <th>
+    <tal:block tal:condition="not:n" i18n:translate="">Group on:</tal:block>
+   </th>
+   <td>
+    <select tal:attributes="name python:'@group%d'%n">
+     <option value="" i18n:translate="">- nothing -</option>
+     <option tal:repeat="col context/properties"
+             tal:attributes="value col/_name;
+                             selected python:key and col._name == key[1]"
+             tal:content="col/_name"
+             i18n:translate="">column</option>
+    </select>
+   </td>
+   <th i18n:translate="">Descending:</th>
+   <td><input type="checkbox" tal:attributes="name python:'@groupdir%d'%n;
+              checked python:key and key[0] == '-'"/>
+   </td>
+  </tr>
+  </tal:block>
+  <tr><td colspan="4">
+              <input type="submit" value="Redisplay" i18n:attributes="value"/>
+              <tal:block tal:replace="structure
+                python:request.indexargs_form(sort=0, group=0)" />
+  </td></tr>
+ </table>
+</form>
+
+</tal:block>
+</tal:block>
+</tal:block>
diff --git a/share/roundup/templates/devel/html/task.item.html b/share/roundup/templates/devel/html/task.item.html
new file mode 100644 (file)
index 0000000..7c51b2d
--- /dev/null
@@ -0,0 +1,235 @@
+<tal:block metal:use-macro="templates/page/macros/frame">
+<title metal:fill-slot="head_title">
+<tal:block condition="context/id" i18n:translate=""
+ >Task <span tal:replace="context/id" i18n:name="id"
+ />: <span tal:replace="context/title" i18n:name="title"
+ /> - <span tal:replace="config/TRACKER_NAME" i18n:name="tracker"
+/></tal:block>
+<tal:block condition="not:context/id" i18n:translate=""
+ >New Task - <span tal:replace="config/TRACKER_NAME" i18n:name="tracker"
+/></tal:block>
+</title>
+<tal:block metal:fill-slot="body_title">
+ <span tal:condition="python: not (context.id or context.is_edit_ok())"
+  tal:omit-tag="python:1" i18n:translate="">New Task</span>
+ <span tal:condition="python: not context.id and context.is_edit_ok()"
+  tal:omit-tag="python:1" i18n:translate="">New Task Editing</span>
+ <span tal:condition="python: context.id and not context.is_edit_ok()"
+  tal:omit-tag="python:1" i18n:translate="">Task <tal:x
+  replace="context/id" i18n:name="id" /></span>
+ <span tal:condition="python: context.id and context.is_edit_ok()"
+  tal:omit-tag="python:1" i18n:translate="">Task<tal:x
+  replace="context/id" i18n:name="id" /> Editing</span>
+</tal:block>
+
+<td class="content" metal:fill-slot="content">
+
+<p tal:condition="python:not (context.is_view_ok()
+ or request.user.hasRole('Anonymous'))" i18n:translate="">
+ You are not allowed to view this page.</p>
+
+<p tal:condition="python:not context.is_view_ok()
+ and request.user.hasRole('Anonymous')" i18n:translate="">
+ Please login with your username and password.</p>
+
+<div tal:condition="context/is_view_ok">
+
+<form method="POST" name="itemSynopsis"
+      onSubmit="return submit_once()" enctype="multipart/form-data"
+      tal:attributes="action context/designator">
+
+<fieldset><legend>classification</legend>
+<table class="form">
+<tr>
+ <th class="required" i18n:translate="">Title:</th>
+ <td colspan="3" tal:condition="context/title/is_edit_ok"
+     tal:content="structure python:context.title.field(size=60)">title</td>
+ <td colspan="3" tal:condition="not:context/title/is_edit_ok">
+  <span tal:content="structure context/title/plain"/>
+  <input type="hidden" name="title" tal:attributes="value context/title">
+ </td>
+</tr>
+
+<tr>
+ <th class="required" i18n:translate="">
+   <span tal:replace="structure python:db.task_type.classhelp('id,name,description',label='Type')" />:
+ </th>
+ <td tal:content="structure context/type/menu">type</td>
+ <th i18n:translate="">
+   <span tal:replace="structure python:db.component.classhelp('id,name,description',label='Components')" />:
+ </th>
+ <td tal:content="structure context/components/menu">components</td>
+</tr>
+</table>
+</fieldset>
+
+<fieldset><legend>process</legend>
+<table class="form">
+<tr tal:condition="context/id">
+ <th i18n:translate="">
+   <span tal:replace="structure python:db.status.classhelp('id,name,description',label='Status')" />:
+ </th>
+ <td tal:content="structure context/status/menu">status</td>
+ <th i18n:translate="">Resolution:</th>
+ <td tal:content="structure context/resolution/menu">resolution</td>
+</tr>
+
+<tr tal:condition="context/id">
+ <th>
+  <tal:block i18n:translate="">Dependencies</tal:block>:
+  <span tal:condition="context/dependencies/is_edit_ok"
+        tal:replace="structure python:db.task.classhelp('id,title', filter='status=0,1', property='dependencies')" />
+ </th>
+ <td>
+  <span tal:replace="structure python:context.dependencies.field(showid=1,size=20)" />
+  <span tal:condition="context/dependencies" tal:repeat="d python:context.dependencies.sorted('creation')">
+   <br/>View: <a tal:attributes="href string:task${d/id}" tal:content="d/id"></a>
+  </span>
+ </td>
+ <th i18n:translate="">
+  <tal:block i18n:translate="">Superseder</tal:block>:
+  <span tal:condition="context/superseder/is_edit_ok"
+        tal:replace="structure python:db.task.classhelp('id,title', filter='status=0,1', property='superseder')" />
+ </th>
+ <td>
+  <span tal:replace="structure python:context.superseder.field(showid=1, size=20)" />
+ <span tal:condition="context/superseder">
+<!--   <br><span i18n:translate="">View</span>:
+     <a tal:repeat="sup context/superseder"
+        tal:content="python:sup['id'] + ', '*(not repeat['sup'].end)"
+        tal:attributes="href string:task${sup/id}; title sup/title;"></a> -->
+   <br><span i18n:translate="">View</span>:
+     <a tal:content="context/superseder/id"
+        tal:attributes="href string:task${context/superseder/id}; title context/superseder/title;"></a> 
+  </span> 
+ </td>
+ </tr>
+ <tr>
+ <th><tal:block i18n:translate="">Assigned To</tal:block>:</th>
+ <td tal:content="structure context/assignee/menu">assignedto menu</td>
+ <th><tal:block i18n:translate="">Nosy List</tal:block>:
+  <span tal:condition="context/nosy/is_edit_ok"
+        tal:replace="structure python:db.user.classhelp('username,realname,address', property='nosy')" />
+ </th>
+ <td>
+  <span tal:replace="structure context/nosy/field" />
+ </td>
+</tr>
+<tr>
+ <th>
+   <span tal:replace="structure python:db.priority.classhelp('id,name,description',label='Priority')" />:
+ </th>
+ <td tal:content="structure context/priority/menu">priority</td>
+</tr>
+<tr tal:condition="context/is_edit_ok">
+ <th><tal:block i18n:translate="">Comment</tal:block>:</th>
+ <td colspan="3">
+  <textarea tal:content="request/form/@note/value | default"
+            name="@note" wrap="hard" rows="10" cols="72"></textarea>
+ </td>
+</tr>
+
+<tr tal:condition="context/is_edit_ok">
+ <th><tal:block i18n:translate="">File</tal:block>:</th>
+ <td colspan=3>
+   <input type="hidden" name="@link@files" value="file-1">
+   <input type="file" name="file-1@content" size="40">
+ </td>
+</tr>
+<tr tal:condition="context/is_edit_ok">
+ <th><tal:block i18n:translate="">File Description</tal:block>:</th>
+ <td colspan=3><input type="edit" name="file-1@description" size="40"></td>
+</tr>
+</table>
+</fieldset>
+<table class="form">
+<tr tal:condition="context/is_edit_ok">
+ <td>
+  &nbsp;
+  <input type="hidden" name="@template" value="item">
+  <input type="hidden" name="@required" value="title">
+ </td>
+ <td colspan=3>
+  <span tal:replace="structure context/submit">submit button</span>
+  <a tal:condition="context/id" tal:attributes="href context/copy_url"
+   i18n:translate="">Make a copy</a>
+ </td>
+</tr>
+</table>
+</form>
+
+<p tal:condition="context/id" i18n:translate="">
+ Created on <b><tal:x replace="python:context.creation.pretty('%Y-%m-%d %H:%M')" i18n:name="creation" /></b>
+ by <b><tal:x replace="context/creator" i18n:name="creator" /></b>,
+ last changed <b><tal:x replace="python:context.activity.pretty('%Y-%m-%d %H:%M')" i18n:name="activity" /></b>
+ by <b><tal:x replace="context/actor" i18n:name="actor" /></b>.
+</p>
+
+<table class="files" tal:condition="context/files">
+ <tr><th colspan="5" class="header" i18n:translate="">Files</th></tr>
+ <tr>
+  <th i18n:translate="">File name</th>
+  <th i18n:translate="">Uploaded</th>
+  <th i18n:translate="">Description</th>
+  <th i18n:translate="">Edit</th>
+  <th i18n:translate="">Remove</th>
+ </tr>
+ <tr tal:repeat="file python:context.files.sorted('creation')">
+  <td>
+   <a tal:attributes="href file/download_url"
+      tal:content="file/name">dld link</a>
+  </td>
+  <td>
+   <span tal:content="file/creator">creator's name</span>,
+   <span tal:content="python:file.creation.pretty('%Y-%m-%d %H:%M')">creation date</span>
+  </td>
+  <td tal:content="file/description" />
+  <td><a tal:condition="file/is_edit_ok"
+          tal:attributes="href string:file${file/id}">edit</a>
+  </td>
+  <td>
+   <form style="padding:0" tal:condition="file/is_edit_ok"
+         tal:attributes="action string:task${context/id}">
+    <input type="hidden" name="@remove@files" tal:attributes="value file/id">
+    <input type="hidden" name="@action" value="edit">
+    <input type="submit" value="remove" i18n:attributes="value">
+   </form>
+  </td>
+ </tr>
+</table>
+
+<table class="messages" tal:condition="context/messages">
+ <tr><th colspan="4" class="header" i18n:translate="">Messages</th></tr>
+ <tal:block tal:repeat="msg context/messages">
+  <tr>
+   <th><a tal:attributes="href string:msg${msg/id}"
+    i18n:translate="">msg<tal:x replace="msg/id" i18n:name="id" /></a></th>
+   <th i18n:translate="">Author: <tal:x replace="python:msg.author.realname.plain()"
+       i18n:name="author" /> (<tal:x replace="msg/author"/>)</th>
+   <th i18n:translate="">Date: <tal:x replace="python:msg.date.pretty('%Y-%m-%d %H:%M')"
+       i18n:name="date" /></th>
+   <th>
+    <form style="padding:0" tal:condition="msg/is_edit_ok"
+          tal:attributes="action string:task${context/id}">
+     <input type="hidden" name="@remove@messages" tal:attributes="value msg/id">
+     <input type="hidden" name="@action" value="edit">
+     <input type="submit" value="remove" i18n:attributes="value">
+    </form>
+   </th>
+  </tr>
+  <tr>
+   <td colspan="4" class="content">
+    <pre tal:condition="python:msg.content.is_view_ok()"
+         tal:content="structure python:utils.localReplace(msg.content.hyperlinked())">content</pre>
+   </td>
+  </tr>
+ </tal:block>
+</table>
+
+<tal:block tal:condition="context/id" tal:replace="structure context/history" />
+
+</div>
+
+</td>
+
+</tal:block>
diff --git a/share/roundup/templates/devel/html/task.search.html b/share/roundup/templates/devel/html/task.search.html
new file mode 100644 (file)
index 0000000..9679cfb
--- /dev/null
@@ -0,0 +1,311 @@
+<tal:block metal:use-macro="templates/page/macros/frame">
+<title metal:fill-slot="head_title" i18n:translate="">Task searching - <span
+ i18n:name="tracker" tal:replace="config/TRACKER_NAME" /></title>
+<span metal:fill-slot="body_title" tal:omit-tag="python:1"
+ i18n:translate="">Task searching</span>
+<td class="content" metal:fill-slot="content">
+
+<form method="get" name="itemSynopsis"
+      tal:attributes="action request/classname">
+      
+<table class="form" tal:define="
+   cols python:request.columns or 'id activity title status assignedto'.split();
+   sort_on python:request.sort and request.sort[0] or nothing;
+   sort_desc python:sort_on and sort_on[0] == '-';
+   sort_on python:(sort_on and sort_on[1]) or 'activity';
+   group_on python:request.group and request.group[0] or nothing;
+   group_desc python:group_on and group_on[0] == '-';
+   group_on python:(group_on and group_on[1]) or 'priority';
+
+   search_input templates/page/macros/search_input;
+   search_date templates/page/macros/search_date;
+   column_input templates/page/macros/column_input;
+   sort_input templates/page/macros/sort_input;
+   group_input templates/page/macros/group_input;
+   search_select templates/page/macros/search_select;
+   search_select_translated templates/page/macros/search_select_translated;
+   search_multiselect templates/page/macros/search_multiselect;">
+
+<tr>
+ <th class="header">&nbsp;</th>
+ <th class="header" i18n:translate="">Filter on</th>
+ <th class="header" i18n:translate="">Display</th>
+ <th class="header" i18n:translate="">Sort on</th>
+ <th class="header" i18n:translate="">Group on</th>
+</tr>
+
+<tr tal:define="name string:@search_text">
+  <th i18n:translate="">All text*:</th>
+  <td metal:use-macro="search_input"></td>
+  <td>&nbsp;</td>
+  <td>&nbsp;</td>
+  <td>&nbsp;</td>
+</tr>
+
+<tr tal:define="name string:title">
+  <th i18n:translate="">Title:</th>
+  <td metal:use-macro="search_input"></td>
+  <td metal:use-macro="column_input"></td>
+  <td metal:use-macro="sort_input"></td>
+  <td>&nbsp;</td>
+</tr>
+
+<tr tal:define="name string:id">
+  <th i18n:translate="">ID:</th>
+  <td metal:use-macro="search_input"></td>
+  <td metal:use-macro="column_input"></td>
+  <td metal:use-macro="sort_input"></td>
+  <td>&nbsp;</td>
+</tr>
+
+<tr tal:define="name string:creation">
+  <th i18n:translate="">Creation Date:</th>
+  <td metal:use-macro="search_date"></td>
+  <td metal:use-macro="column_input"></td>
+  <td metal:use-macro="sort_input"></td>
+  <td metal:use-macro="group_input"></td>
+</tr>
+
+<tr tal:define="name string:creator;
+                db_klass string:user;
+                db_content string:username;">
+  <th i18n:translate="">Creator:</th>
+  <td metal:use-macro="search_input">
+    <option metal:fill-slot="extra_options" i18n:translate=""
+            tal:attributes="value request/user/id">created by me</option>
+  </td>
+  <td metal:use-macro="column_input"></td>
+  <td metal:use-macro="sort_input"></td>
+  <td metal:use-macro="group_input"></td>
+</tr>
+
+<tr tal:define="name string:activity">
+  <th i18n:translate="">Activity:</th>
+  <td metal:use-macro="search_date"></td>
+  <td metal:use-macro="column_input"></td>
+  <td metal:use-macro="sort_input"></td>
+  <td>&nbsp;</td>
+</tr>
+
+<tr tal:define="name string:actor;
+                db_klass string:user;
+                db_content string:username;">
+  <th i18n:translate="">Last actor:</th>
+  <td metal:use-macro="search_input">
+    <option metal:fill-slot="extra_options" i18n:translate=""
+            tal:attributes="value request/user/id">done by me</option>
+  </td>
+  <td metal:use-macro="column_input"></td>
+  <td metal:use-macro="sort_input"></td>
+  <td>&nbsp;</td>
+</tr>
+
+<tr tal:define="name string:nosy">
+  <th i18n:translate="">Nosy list member:</th>
+  <td metal:use-macro="search_input"></td>
+  <td metal:use-macro="column_input"></td>
+  <td metal:use-macro="sort_input"></td>
+  <td>&nbsp;</td>
+</tr>
+
+<tr tal:define="name string:type;
+                db_klass string:task_type;
+                db_content string:name;">
+  <th i18n:translate="">Type:</th>
+  <td metal:use-macro="search_select_translated">
+    <option metal:fill-slot="extra_options" value="-1" i18n:translate=""
+            tal:attributes="selected python:value == '-1'">not selected</option>
+  </td>
+  <td metal:use-macro="column_input"></td>
+  <td metal:use-macro="sort_input"></td>
+  <td metal:use-macro="group_input"></td>
+</tr>
+
+<tr tal:define="name string:components;
+                db_klass string:component;
+                db_content string:name;">
+  <th i18n:translate="">Components:</th>
+  <td metal:use-macro="search_select_translated">
+    <option metal:fill-slot="extra_options" value="-1" i18n:translate=""
+            tal:attributes="selected python:value == '-1'">not selected</option>
+  </td>
+  <td metal:use-macro="column_input"></td>
+  <td metal:use-macro="sort_input"></td>
+  <td metal:use-macro="group_input"></td>
+</tr>
+
+<tr tal:define="name string:versions;
+                db_klass string:version;
+                db_content string:name;">
+  <th i18n:translate="">Versions:</th>
+  <td metal:use-macro="search_select_translated">
+    <option metal:fill-slot="extra_options" value="-1" i18n:translate=""
+            tal:attributes="selected python:value == '-1'">not selected</option>
+  </td>
+  <td metal:use-macro="column_input"></td>
+  <td metal:use-macro="sort_input"></td>
+  <td metal:use-macro="group_input"></td>
+</tr>
+
+<!--tr tal:define="name string:severity;
+                db_klass string:severity;
+                db_content string:name;">
+  <th i18n:translate="">Severity:</th>
+  <td metal:use-macro="search_select_translated">
+    <option metal:fill-slot="extra_options" value="-1" i18n:translate=""
+            tal:attributes="selected python:value == '-1'">not selected</option>
+  </td>
+  <td metal:use-macro="column_input"></td>
+  <td metal:use-macro="sort_input"></td>
+  <td metal:use-macro="group_input"></td>
+</tr-->
+
+<tr tal:define="name string:dependencies;
+                db_klass string:task;
+                db_content string:id;">
+  <th i18n:translate="">Depends on:</th>
+  <td metal:use-macro="search_input"/>
+  <td metal:use-macro="column_input"></td>
+  <td metal:use-macro="sort_input"></td>
+  <td metal:use-macro="group_input"></td>
+</tr>
+
+<tr tal:define="name string:assignee;
+                db_klass string:user;
+                db_content string:username;">
+  <th i18n:translate="">Assigned to:</th>
+  <td metal:use-macro="search_input">
+    <tal:block metal:fill-slot="extra_options">
+      <option tal:attributes="value request/user/id"
+       i18n:translate="">assigned to me</option>
+      <option value="-1" tal:attributes="selected python:value == '-1'"
+       i18n:translate="">unassigned</option>
+    </tal:block>
+  </td>
+  <td metal:use-macro="column_input"></td>
+  <td metal:use-macro="sort_input"></td>
+  <td metal:use-macro="group_input"></td>
+</tr>
+
+<tr tal:define="name string:keywords;
+                db_klass string:keyword;
+                db_content string:name;">
+  <th i18n:translate="">Keyword:</th>
+  <td metal:use-macro="search_select_translated">
+    <option metal:fill-slot="extra_options" value="-1" i18n:translate=""
+            tal:attributes="selected python:value == '-1'">not set</option>
+  </td>
+  <td metal:use-macro="column_input"></td>
+  <td metal:use-macro="sort_input"></td>
+  <td metal:use-macro="group_input"></td>
+</tr>
+
+<tr tal:define="name string:priority;
+                db_klass string:priority;
+                db_content string:name;">
+  <th i18n:translate="">Priority:</th>
+  <td metal:use-macro="search_select_translated">
+    <option metal:fill-slot="extra_options" value="-1" i18n:translate=""
+            tal:attributes="selected python:value == '-1'">not selected</option>
+  </td>
+  <td metal:use-macro="column_input"></td>
+  <td metal:use-macro="sort_input"></td>
+  <td metal:use-macro="group_input"></td>
+</tr>
+
+<tr tal:define="name string:status;
+                db_klass string:status;
+                db_content string:name;">
+  <th i18n:translate="">Status:</th>
+  <td metal:use-macro="search_select_translated">
+    <tal:block metal:fill-slot="extra_options">
+      <option value="-1,1,2" i18n:translate=""
+              tal:attributes="selected python:value == '-1,1,2'">not closed</option>
+      <option value="-1" i18n:translate=""
+              tal:attributes="selected python:value == '-1'">not selected</option>
+    </tal:block>
+  </td>
+  <td metal:use-macro="column_input"></td>
+  <td metal:use-macro="sort_input"></td>
+  <td metal:use-macro="group_input"></td>
+</tr>
+
+<tr tal:define="name string:resolution;
+                db_klass string:resolution;
+                db_content string:name;">
+  <th i18n:translate="">Resolution:</th>
+  <td metal:use-macro="search_select_translated"/>
+  <td metal:use-macro="column_input"></td>
+  <td metal:use-macro="sort_input"></td>
+  <td metal:use-macro="group_input"></td>
+</tr>
+
+<tr>
+ <th i18n:translate="">No Sort or group:</th>
+ <td>&nbsp;</td>
+ <td>&nbsp;</td>
+ <td><input type="radio" name="@sort" value=""/></td>
+ <td><input type="radio" name="@group" value=""/></td>
+</tr>
+
+<tr>
+<th i18n:translate="">Pagesize:</th>
+<td><input name="@pagesize" size="3" value="50"
+           tal:attributes="value request/form/@pagesize/value | default"/></td>
+</tr>
+
+<tr>
+<th i18n:translate="">Start With:</th>
+<td><input name="@startwith" size="3" value="0"
+           tal:attributes="value request/form/@startwith/value | default"/></td>
+</tr>
+
+<tr>
+<th i18n:translate="">Sort Descending:</th>
+<td><input type="checkbox" name="@sortdir"
+           tal:attributes="checked sort_desc"/>
+</td>
+</tr>
+
+<tr>
+<th i18n:translate="">Group Descending:</th>
+<td><input type="checkbox" name="@groupdir"
+           tal:attributes="checked group_desc"/>
+</td>
+</tr>
+
+<tr tal:condition="python:request.user.hasPermission('Edit', 'query')">
+ <th i18n:translate="">Query name**:</th>
+ <td tal:define="value request/form/@queryname/value | nothing">
+  <input name="@queryname" tal:attributes="value value"/>
+  <input type="hidden" name="@old-queryname" tal:attributes="value value"/>
+ </td>
+</tr>
+
+<tr>
+  <td>
+   &nbsp;
+   <input type="hidden" name="@action" value="search"/>
+  </td>
+  <td><input type="submit" value="Search" i18n:attributes="value"/></td>
+</tr>
+
+<tr><td>&nbsp;</td>
+ <td colspan="4" class="help">
+  <span i18n:translate="" tal:omit-tag="true">
+   *: The "all text" field will look in message bodies and task titles
+  </span><br>
+  <span tal:condition="python:request.user.hasPermission('Edit', 'query')"
+   i18n:translate="" tal:omit-tag="true"
+  >
+   **: If you supply a name, the query will be saved off and available as a
+       link in the sidebar
+  </span>
+ </td>
+</tr>
+</table>
+
+</form>
+</td>
+
+</tal:block>
diff --git a/share/roundup/templates/devel/html/tracker.css b/share/roundup/templates/devel/html/tracker.css
new file mode 100644 (file)
index 0000000..3cce4af
--- /dev/null
@@ -0,0 +1,20 @@
+#search { text-align: right; }
+
+p.ok-message 
+{
+  background-color: #eeffee;
+  border: 1px solid #009900;
+  padding: 5px;
+  color: #009900;
+  font-weight: bold;
+}
+p.error-message 
+{
+  background-color: #ffeeee;
+  border: 1px solid #990000;
+  padding: 5px;
+  color: #990000;
+  font-weight: bold;
+}
+
+tr.odd { background-color:#f5f5f5; }
diff --git a/share/roundup/templates/devel/html/user.forgotten.html b/share/roundup/templates/devel/html/user.forgotten.html
new file mode 100644 (file)
index 0000000..cb8171f
--- /dev/null
@@ -0,0 +1,56 @@
+<tal:block metal:use-macro="templates/page/macros/frame">
+<title metal:fill-slot="head_title" i18n:translate="">Password reset request - <span
+ i18n:name="tracker" tal:replace="config/TRACKER_NAME" /></title>
+<span metal:fill-slot="body_title" tal:omit-tag="python:1"
+ i18n:translate="">Password reset request</span>
+<td class="content" metal:fill-slot="content">
+
+<tal:askforinfo tal:condition="python:options['error_message'] or not request.form.has_key('@action')"> 
+
+<p i18n:translate="">You have two options if you have forgotten your password.
+If you know the email address you registered with, enter it below.</p>
+
+<p i18n:translate="">If your  user was  automatically  created during
+import  from  the old  sourceforge  tracker,  your  e-mail address  is
+&lt;Sourceforge  username&gt;@users.sourceforge.net. The  mail address
+associated with your account can be changed after login.</p>
+
+<form method="POST" onSubmit="return submit_once()"
+      tal:attributes="action context/designator">
+    <table class="form">
+      <tr>
+        <th i18n:translate="">Email Address:</th>
+        <td><input name="address"></td>
+      </tr>
+      <tr>
+        <td>&nbsp;</td>
+        <td>
+          <input type="hidden" name="@action" value="passrst">
+          <input type="hidden" name="@template" value="forgotten">
+          <input type="submit" value="Request password reset"
+           i18n:attributes="value">
+        </td>
+      </tr>
+</table>
+
+<p i18n:translate="">Or, if you know your username, then enter it below.</p>
+
+<p i18n:translate="">If you have previously created or modified issue
+reports in the sourceforge issue tracker, you have an account here with
+the same username as your sourceforge username.</p>
+
+<table class="form">
+ <tr><th i18n:translate="">Username:</th> <td><input name="username"></td> </tr>
+ <tr><td></td><td><input type="submit" value="Request password reset"
+   i18n:attributes="value"></td></tr>
+</table>
+</form>
+
+<p i18n:translate="">A confirmation email will be sent to you -
+please follow the instructions within it to complete the reset process.</p>
+
+</tal:askforinfo>
+
+</td>
+g
+</tal:block>
diff --git a/share/roundup/templates/devel/html/user.help-search.html b/share/roundup/templates/devel/html/user.help-search.html
new file mode 100644 (file)
index 0000000..e32a469
--- /dev/null
@@ -0,0 +1,85 @@
+<html
+  tal:define="form request/form/form/value;
+  field request/form/property/value"
+  >
+  <head>
+    <title>Search input for user helper</title>
+    <script language="Javascript" type="text/javascript"
+        tal:content="structure string:<!--
+        // this is the name of the field in the original form that we're working on
+        form  = parent.opener.document.${form};
+        field  = '${field}';
+        //-->">
+    </script>
+    <script type="text/javascript" src="@@file/help_controls.js"></script>
+    <link rel="stylesheet" type="text/css" href="@@file/style.css" />
+  </head>
+  <body onload="parent.submit.url='...'"
+    tal:define="
+qs request/env/QUERY_STRING;
+qs python:'&'.join([a for a in qs.split('&') if not a.startswith('@template=')])"
+>
+    <pre tal:content="request/env/QUERY_STRING" tal:condition=false />
+    <form method="get" name="itemSynopsis"
+      target="list"
+      tal:attributes="action request/classname"
+      tal:define="
+      property request/form/property/value;
+   cols python:request.columns or 'id username address realname roles'.split();
+   sort_on request/sort | nothing;
+   sort_desc python:sort_on and request.sort[0][0] == '-';
+   sort_on python:sort_on and request.sort[0][1] or 'lastname';
+
+   search_input templates/page/macros/search_input;
+   search_select templates/page/macros/search_select;
+   search_select_roles templates/page/macros/search_select_roles;
+   required python:[];
+   th_label templates/page/macros/th_label;
+   ">
+   <input type="hidden" name="@template" value="help-list">
+   <input type="hidden" name="property" value="" tal:attributes="value property">
+   <input type="hidden" name="form" value="" tal:attributes="value request/form/form/value">
+   <table>
+<tr tal:define="name string:username; label string:Username:">
+  <th metal:use-macro="th_label">Name</th>
+  <td metal:use-macro="search_input"><input type=text></td>
+</tr>
+
+<tr tal:define="name string:phone; label string:Phone number">
+  <th metal:use-macro="th_label">Phone</th>
+  <td metal:use-macro="search_input"><input type=text></td>
+</tr>
+
+<tr tal:define="name string:roles;
+                onchange string:this.form.submit();
+                label string:Roles:"
+                >
+  <th metal:use-macro="th_label">role</th>
+  <td metal:use-macro="search_select_roles">
+    <select>
+      <option value="">jokester</option>
+    </select>
+  </td>
+</tr>
+
+<tr>
+  <td>&nbsp;</td>
+  <td>
+    <input type="hidden" name="@action" value="search">
+    <input type="submit" value="Search" i18n:attributes="value">
+    <input type="reset">
+    <input type="hidden" value="username,realname,phone,organisation,roles" name="properties">
+    <input type="text" name="@pagesize" id="sp-pagesize" value="25" size="2">
+    <label for="sp-pagesize" i18n:translate="">Pagesize</label>
+  </td>
+</tr>
+
+   </table>
+
+</form>
+<pre tal:content="request" tal:condition=false />
+<script type="text/javascript"><!--
+  focus2id('username');
+//--></script>
+  </body>
+</html>
diff --git a/share/roundup/templates/devel/html/user.help.html b/share/roundup/templates/devel/html/user.help.html
new file mode 100644 (file)
index 0000000..328d789
--- /dev/null
@@ -0,0 +1,118 @@
+<tal:block metal:use-macro="templates/help/macros/frame">
+<tal:block tal:define="property request/form/property/value;
+                  qs request/env/QUERY_STRING;
+                  qs python:'&'.join([a for a in qs.split('&') if not a.startswith('@template=')]);
+                  form request/form/form/value;
+                  field request/form/property/value">
+  <tal:block metal:fill-slot="more-javascript">
+     <script language="Javascript" type="text/javascript" tal:content="structure string:<!--
+      // this is the name of the field in the original form that we're working on
+      form  = parent.opener.document.${request/form/form/value};
+      callingform=form
+      field  = '${request/form/property/value}';
+      var listform = null
+      function listPresent() { return document.frm_help.cb_listpresent.checked}
+      function getListForm()
+      {
+        if (listPresent()) { return parent.list.document.forms.dummyform}
+        else { return null}
+      }
+
+      function checkListForm()
+      {
+        // global listform
+        if (listform != null)
+        if (parent.list.document.dummyform) 
+        {
+          listform = parent.list.document.dummyform
+          alert(listform)
+        }
+        var bol= listform != null
+        alert('checkListForm: bol='+bol)
+        return bol
+      }
+      //-->">
+    </script>
+    <script src="@@file/jquery.js" type="text/javascript"></script>
+    <script src="@@file/search.js" type="text/javascript"></script>
+  </tal:block>
+  <tal:block metal:fill-slot="content">
+    <div name="search">
+      <form method="get" name="itemSynopsis" target="list"
+            tal:attributes="action request/classname" 
+            tal:define="property request/form/property/value;
+                        cols python:request.columns or 'id username address realname roles'.split();
+                        sort_on request/sort | nothing;
+                        sort_desc python:sort_on and request.sort[0][0] == '-';
+                        sort_on python:sort_on and request.sort[0][1] or 'lastname';
+                        search_input templates/page/macros/search_input;
+                        search_select templates/page/macros/search_select;
+                        search_select_roles templates/page/macros/search_select_roles;
+                        required python:[];
+                        th_label templates/page/macros/th_label; ">
+       <input type="hidden" name="@template" value="help-list">
+       <input type="hidden" name="property" value="" tal:attributes="value property">
+       <input type="hidden" name="form" value="" tal:attributes="value request/form/form/value">
+       <table>
+        <tr tal:define="name string:username; label string:Username:">
+         <th metal:use-macro="th_label">Name</th> 
+         <td metal:use-macro="search_input"><input type="text"></td>
+        </tr>
+        <tr tal:define="name string:phone; label string:Phone number">
+         <th metal:use-macro="th_label">Phone</th>
+         <td metal:use-macro="search_input"><input type="text"></td>
+        </tr>
+        <tr tal:define="name string:roles; label string:Roles:" >
+         <th metal:use-macro="th_label">role</th>
+         <td metal:use-macro="search_select_roles">
+          <select>
+           <option value="">jokester</option>
+          </select>
+         </td>
+        </tr>
+        <tr>
+         <td>&nbsp;</td>
+         <td>
+          <input type="hidden" name="@action" value="search"/>
+          <input type="submit" value="Search" i18n:attributes="value"/>
+          <input type="reset"/>
+          <input type="hidden" value="username,realname,phone,organisation,roles" name="properties"/>
+          <input type="text" name="@pagesize" id="sp-pagesize" value="25" size="2"/>
+          <label for="sp-pagesize" i18n:translate="">Pagesize</label>
+         </td>
+        </tr>
+       </table>
+      </form>
+      <script type="text/javascript"><!-- focus2id('username'); //--></script>
+    </div>
+    <!-- for search results: help-list -->
+    <div class="list"><p i18n:translate="">Please specify your search parameters!</p></div>
+    <div class="submit" onload="parent.focus();" id="submit">
+    <form name="frm_help"
+          tal:define="batch request/batch;
+                      props python:request.form['properties'].value.split(',')"
+          class="help-submit"
+          id="classhelp-controls">
+    <div style="width:100%;text-align:left;margin-bottom:0.2em">
+     <input type="text" name="text_preview" size="24" class="preview"
+            onchange="f=getListForm();if(f){ reviseList_framed(f, this)};"/>
+    </div>
+    <input type=checkbox name="cb_listpresent" readonly="readonly" style="display:none"/>
+    <input type="button" id="btn_cancel"
+           value=" Cancel " onclick="parent.close();return false;"
+           i18n:attributes="value" />
+    <input type="reset" id="btn_reset"
+           onclick="text_field.value=original_field;f=getListForm();if (f) {reviseList_framed(f, this)};return false"/>
+    <input type="submit" id="btn_apply" class="apply"
+           value=" Apply " onclick="callingform[field].value=text_field.value; parent.close();"
+           i18n:attributes="value" />
+   </form>
+   <script type="text/javascript"><!--
+var text_field = document.frm_help.text_preview;
+original_field=form[field].value;
+text_field.value=original_field;
+//--></script>
+  </div>
+</tal:block>
+</tal:block>
+</tal:block>
diff --git a/share/roundup/templates/devel/html/user.index.html b/share/roundup/templates/devel/html/user.index.html
new file mode 100644 (file)
index 0000000..ed9c3ef
--- /dev/null
@@ -0,0 +1,103 @@
+<tal:block metal:use-macro="templates/page/macros/frame">
+<title metal:fill-slot="head_title" i18n:translate="">User listing - <span
+ i18n:name="tracker" tal:replace="config/TRACKER_NAME" /></title>
+<span metal:fill-slot="body_title" tal:omit-tag="python:1"
+ i18n:translate="">User listing</span>
+<td class="content" metal:fill-slot="content">
+
+<span tal:condition="python:not (context.is_view_ok()
+ or request.user.hasRole('Anonymous'))"
+ i18n:translate="">You are not allowed to view this page.</span>
+
+<span tal:condition="python:not context.is_view_ok()
+ and request.user.hasRole('Anonymous')"
+ i18n:translate="">Please login with your username and password.</span>
+
+<form tal:condition="context/is_view_ok" method="get" name="itemSynopsis"
+      tal:attributes="action request/classname">
+
+<table class="form" tal:define="
+       search_input templates/page/macros/search_input;">
+
+   <tr><th class="header" colspan="5">Search for users</th></tr>
+   <tr>
+       <th class="header">Username</th>
+       <td tal:define="name string:username">
+           <input tal:attributes="value python:request.form.getvalue(name) or nothing;
+                         name name;
+                         id name"/>
+       </td>
+       <th class="header">Realname</th>
+       <td tal:define="name string:realname">
+           <input tal:attributes="value python:request.form.getvalue(name) or nothing;
+                         name name;
+                         id name"/>
+       </td>
+
+       <td><input class="form-small" type="submit" value="Search" i18n:attributes="value"/></td>
+   </tr>
+
+</table>
+   <input type="hidden" name="@action" value="search"/>
+</form>
+
+
+<table width="100%" tal:condition="context/is_view_ok" class="list"
+       tal:define="batch request/batch">
+<tr>
+ <th i18n:translate="">Username</th>
+ <th i18n:translate="">Real name</th>
+ <th i18n:translate="">Organisation</th>
+ <th i18n:translate="">Email address</th>
+ <th i18n:translate="">Phone number</th>
+ <th tal:condition="context/is_edit_ok" i18n:translate="">Retire</th>
+</tr>
+<tal:block repeat="user batch">
+<tr tal:attributes="class python:['normal', 'alt'][repeat['user'].index%6/3]">
+ <td>
+  <a tal:attributes="href string:user${user/id}"
+     tal:content="user/username">username</a>
+ </td>
+ <td tal:content="python:user.realname.plain() or default">&nbsp;</td>
+ <td tal:content="python:user.organisation.plain() or default">&nbsp;</td>
+ <td tal:content="python:user.address.email() or default">&nbsp;</td>
+ <td tal:content="python:user.phone.plain() or default">&nbsp;</td>
+ <td tal:condition="context/is_edit_ok">
+  <a tal:attributes="href string:user${user/id}?@action=retire&@template=index"
+   i18n:translate="">retire</a>
+ </td>
+</tr>
+</tal:block>
+ <tr tal:condition="batch">
+  <th tal:attributes="colspan python:len(request.columns)">
+   <table width="100%">
+    <tr class="navigation">
+     <th>
+      <a tal:define="prev batch/previous" tal:condition="prev"
+         tal:attributes="href python:request.indexargs_url(request.classname,
+         {'@startwith':prev.first, '@pagesize':prev.size})"
+         i18n:translate="">&lt;&lt; previous</a>
+      &nbsp;
+     </th>
+     <th i18n:translate=""><span tal:replace="batch/start" i18n:name="start"
+     />..<span tal:replace="python: batch.start + batch.length -1" i18n:name="end"
+     /> out of <span tal:replace="batch/sequence_length" i18n:name="total"
+     /></th>
+     <th>
+      <a tal:define="next batch/next" tal:condition="next"
+         tal:attributes="href python:request.indexargs_url(request.classname,
+         {'@startwith':next.first, '@pagesize':next.size})"
+         i18n:translate="">next &gt;&gt;</a>
+      &nbsp;
+     </th>
+    </tr>
+   </table>
+  </th>
+ </tr>
+
+</table>
+</td>
+
+
+
+</tal:block>
diff --git a/share/roundup/templates/devel/html/user.item.html b/share/roundup/templates/devel/html/user.item.html
new file mode 100644 (file)
index 0000000..375aac7
--- /dev/null
@@ -0,0 +1,165 @@
+<tal:doc metal:use-macro="templates/page/macros/frame"
+define="edit_ok context/is_edit_ok"
+>
+<title metal:fill-slot="head_title">
+<tal:if condition="context/id" i18n:translate=""
+ >User <span tal:replace="context/id" i18n:name="id"
+ />: <span tal:replace="context/username" i18n:name="title"
+ /> - <span tal:replace="config/TRACKER_NAME" i18n:name="tracker"
+/></tal:if>
+<tal:if condition="not:context/id" i18n:translate=""
+ >New User - <span tal:replace="config/TRACKER_NAME" i18n:name="tracker"
+/></tal:if>
+</title>
+<metal:slot fill-slot="more-javascript">
+<script metal:use-macro="templates/page/macros/user_utils"></script>
+<script type="text/javascript" src="@@file/help_controls.js"></script>
+</metal:slot>
+<tal:block metal:fill-slot="body_title"
+  define="edit_ok context/is_edit_ok">
+ <span tal:condition="python: not (context.id or edit_ok)"
+  tal:omit-tag="python:1" i18n:translate="">New User</span>
+ <span tal:condition="python: not context.id and edit_ok"
+  tal:omit-tag="python:1" i18n:translate="">New User Editing</span>
+ <span tal:condition="python: context.id and not edit_ok"
+  tal:omit-tag="python:1" i18n:translate="">User<tal:x
+  replace="context/id" i18n:name="id" /></span>
+ <span tal:condition="python: context.id and edit_ok"
+  tal:omit-tag="python:1" i18n:translate="">User<tal:x
+  replace="context/id" i18n:name="id" /> Editing</span>
+</tal:block>
+
+<td class="content" metal:fill-slot="content">
+
+<p tal:condition="python:not (context.is_view_ok()
+ or request.user.hasRole('Anonymous'))" i18n:translate="">
+ You are not allowed to view this page.</p>
+
+<p tal:condition="python:not context.is_view_ok()
+ and request.user.hasRole('Anonymous')" i18n:translate="">
+ Please login with your username and password.</p>
+
+<div tal:condition="context/is_view_ok">
+
+<form method="POST"
+      tal:define="required python:'username address'.split()"
+      enctype="multipart/form-data"
+      tal:attributes="action context/designator;
+      onSubmit python:'return checkRequiredFields(\'%s\')'%'\', \''.join(required);
+      ">
+<table class="form" tal:define="
+  th_label templates/page/macros/th_label;
+  src_input templates/page/macros/user_src_input;
+  normal_input templates/page/macros/user_normal_input;
+  pw_input templates/page/macros/user_pw_input;
+  confirm_input templates/page/macros/user_confirm_input;
+  edit_ok context/is_edit_ok;
+  ">
+ <tr tal:define="name string:realname; label string:Name; value context/realname; edit_ok edit_ok">
+  <th metal:use-macro="th_label">Name</th>
+  <td><input name="realname" metal:use-macro="src_input"></td>
+ </tr>
+ <tr tal:define="name string:username; label string:Login Name; value context/username">
+   <th metal:use-macro="th_label">Login Name</th>
+   <td><input metal:use-macro="src_input"></td>
+ </tr>
+ <tal:if condition="edit_ok">
+ <tr tal:define="name string:password; label string:Login Password">
+  <th metal:use-macro="th_label">Login Password</th>
+  <td><input metal:use-macro="pw_input" type="password"></td>
+ </tr>
+ <tr tal:define="name string:password; label string:Confirm Password">
+  <th metal:use-macro="th_label">Confirm Password</th>
+  <td><input metal:use-macro="confirm_input" type="password"></td>
+ </tr>
+ </tal:if>
+ <tal:if condition="python:request.user.hasPermission('Web Roles')">
+ <tr tal:define="name string:roles; label string:Roles;">
+  <th><label for="roles" i18n:translate="">Roles</label></th>
+  <td tal:define="gips context/id">
+    <tal:subif condition=gips define="value context/roles">
+      <input metal:use-macro="normal_input">
+    </tal:subif>
+    <tal:subif condition="not:gips" define="value db/config/NEW_WEB_USER_ROLES">
+      <input metal:use-macro="normal_input">
+    </tal:subif>
+   <tal:block i18n:translate="">(to give the user more than one role,
+    enter a comma,separated,list)</tal:block>
+  </td>
+ </tr>
+ </tal:if>
+
+ <tr tal:define="name string:phone; label string:Phone; value context/phone">
+  <th metal:use-macro="th_label">Phone</th>
+  <td><input name="phone" metal:use-macro="normal_input"></td>
+ </tr>
+
+ <tr tal:define="name string:organisation; label string:Organisation; value context/organisation">
+  <th metal:use-macro="th_label">Organisation</th>
+  <td><input name="organisation" metal:use-macro="normal_input"></td>
+ </tr>
+
+ <tr tal:condition="python:edit_ok or context.timezone"
+     tal:define="name string:timezone; label string:Timezone; value context/timezone">
+  <th metal:use-macro="th_label">Timezone</th>
+  <td><input tal:replace="structure python:
+       utils.tzfield(context.timezone, 'timezone', db.config.DEFAULT_TIMEZONE)"/>
+  </td>
+ </tr>
+
+ <tr tal:define="name string:address; label string:E-mail address; value context/address">
+  <th metal:use-macro="th_label">E-mail address</th>
+  <td tal:define="mailto python:context.address.field(id='address');
+         mklink python:mailto and not edit_ok">
+      <a href="mailto:calvin@the-z.org"
+                 tal:attributes="href string:mailto:$value"
+                 tal:content="value"
+          tal:condition="python:mklink">calvin@the-z.org</a>
+      <tal:if condition=edit_ok>
+      <input metal:use-macro="src_input" value="calvin@the-z.org">
+      </tal:if>
+      &nbsp;
+  </td>
+ </tr>
+
+ <tr>
+  <th><label for="alternate_addresses" i18n:translate="">Alternate E-mail addresses<br>One address per line</label></th>
+  <td>
+    <textarea rows=5 cols=40 tal:replace="structure context/alternate_addresses/multiline">nobody@nowhere.org
+anybody@everywhere.net
+(alternate_addresses)
+    </textarea>
+  </td>
+ </tr>
+
+ <tr tal:condition="edit_ok">
+  <td>
+   &nbsp;
+   <input type="hidden" name="@template" value="item">
+   <input type="hidden" name="@required" value="username,address"
+          tal:attributes="value python:','.join(required)">
+  </td>
+  <td><input type="submit" value="save" tal:replace="structure context/submit"><!--submit button here-->
+    <input type="reset">
+  </td>
+ </tr>
+</table>
+</form>
+
+<tal:block tal:condition="not:context/id" i18n:translate="">
+<table class="form">
+<tr>
+ <td>Note:&nbsp;</td>
+ <th class="required">highlighted</th>
+ <td>&nbsp;fields are required.</td>
+</tr>
+</table>
+</tal:block>
+
+<tal:block tal:condition="context/id" tal:replace="structure context/history" />
+
+</div>
+
+</td>
+
+</tal:doc>
diff --git a/share/roundup/templates/devel/html/user.register.html b/share/roundup/templates/devel/html/user.register.html
new file mode 100644 (file)
index 0000000..0ee95b2
--- /dev/null
@@ -0,0 +1,88 @@
+<tal:block metal:use-macro="templates/page/macros/frame">
+<title metal:fill-slot="head_title"
+ i18n:translate="">Registering with <span i18n:name="tracker"
+ tal:replace="db/config/TRACKER_NAME" /></title>
+<span metal:fill-slot="body_title" tal:omit-tag="python:1"
+ i18n:translate="">Registering with <span i18n:name="tracker"
+ tal:replace="db/config/TRACKER_NAME" /></span>
+<td class="content" metal:fill-slot="content">
+
+<p><span style="color:red; font-size:large;">NOTE</span>: If you have previously created or modified issue
+reports using the sourceforge issue tracker previously used for python
+bugs, your username on sourceforge already exists in this tracker. Use
+the <a href="user?@template=forgotten">Password recovery form</a> to
+get a password for your account.</p>
+
+
+<form method="POST" onSubmit="return submit_once()"
+      enctype="multipart/form-data"
+      tal:attributes="action context/designator">
+
+<input type="hidden" name="opaque" tal:attributes="value python: utils.timestamp()" />
+<table class="form">
+ <tr>
+  <th i18n:translate="">Name</th>
+  <td tal:content="structure context/realname/field">realname</td>
+ </tr>
+ <tr>
+  <th class="required" i18n:translate="">Login Name</th>
+  <td tal:content="structure context/username/field">username</td>
+ </tr>
+ <tr>
+  <th class="required" i18n:translate="">Login Password</th>
+  <td tal:content="structure context/password/field">password</td>
+ </tr>
+ <tr>
+  <th class="required" i18n:translate="">Confirm Password</th>
+  <td tal:content="structure context/password/confirm">password</td>
+ </tr>
+ <tr tal:condition="python:request.user.hasPermission('Web Roles')">
+  <th i18n:translate="">Roles</th>
+  <td tal:condition="exists:item"
+      tal:content="structure context/roles/field">roles</td>
+  <td tal:condition="not:exists:item">
+   <input name="roles" tal:attributes="value db/config/NEW_WEB_USER_ROLES">
+  </td>
+ </tr>
+ <tr>
+  <th i18n:translate="">Phone</th>
+  <td tal:content="structure context/phone/field">phone</td>
+ </tr>
+ <tr>
+  <th i18n:translate="">Organisation</th>
+  <td tal:content="structure context/organisation/field">organisation</td>
+ </tr>
+ <tr>
+  <th class="required" i18n:translate="">E-mail address</th>
+  <td tal:content="structure context/address/field">address</td>
+ </tr>
+ <tr>
+  <th i18n:translate="">Alternate E-mail addresses<br>One address per line</th>
+  <td tal:content="structure context/alternate_addresses/multiline">alternate_addresses</td>
+ </tr>
+
+ <tr>
+  <td>&nbsp;</td>
+  <td>
+   <input type="hidden" name="@template" value="register">
+   <input type="hidden" name="@required" value="username,password,address">
+   <input type="hidden" name="@action" value="register">
+   <input type="submit" name="submit" value="Register" i18n:attributes="value">
+  </td>
+ </tr>
+</table>
+</form>
+
+<tal:block tal:condition="not:context/id" i18n:translate="">
+<table class="form">
+<tr>
+ <td>Note:&nbsp;</td>
+ <th class="required">highlighted</th>
+ <td>&nbsp;fields are required.</td>
+</tr>
+</table>
+</tal:block>
+
+</td>
+
+</tal:block>
diff --git a/share/roundup/templates/devel/html/user.rego_progress.html b/share/roundup/templates/devel/html/user.rego_progress.html
new file mode 100644 (file)
index 0000000..58e2f6c
--- /dev/null
@@ -0,0 +1,15 @@
+<tal:block metal:use-macro="templates/page/macros/frame">
+<title metal:fill-slot="head_title"
+ i18n:translate="">Registration in progress - <span i18n:name="tracker"
+ tal:replace="config/TRACKER_NAME" /></title>
+<span metal:fill-slot="body_title" tal:omit-tag="python:1"
+ i18n:translate="">Registration in progress...</span>
+<td class="content" metal:fill-slot="content">
+
+<p i18n:translate="">You will shortly receive an email
+to confirm your registration. To complete the registration process,
+visit the link indicated in the email.
+</p>
+
+</td>
+</tal:block>
diff --git a/share/roundup/templates/devel/html/user_utils.js b/share/roundup/templates/devel/html/user_utils.js
new file mode 100644 (file)
index 0000000..b992f6d
--- /dev/null
@@ -0,0 +1,111 @@
+// User Editing Utilities
+
+/**
+ * for new users:
+ * Depending on the input field which calls it, takes the value
+ * and dispatches it to certain other input fields:
+ *
+ * address
+ *  +-> username
+ *  |    `-> realname
+ *  `-> organisation
+ */
+function split_name(that) {
+    var raw = that.value
+    var val = trim(raw)
+    if (val == '') {
+        return
+    }
+    var username=''
+    var realname=''
+    var address=''
+    switch (that.name) {
+        case 'address':
+            address=val
+            break
+        case 'username':
+            username=val
+            break
+        case 'realname':
+            realname=val
+            break
+        default:
+            alert('Ooops - unknown name field '+that.name+'!')
+            return
+    }
+    var the_form = that.form;
+
+    function field_empty(name) {
+        return the_form[name].value == ''
+    }
+    
+    // no break statements - on purpose!
+    switch (that.name) {
+        case 'address':
+            var split1 = address.split('@')
+            if (field_empty('username')) {
+                username = split1[0]
+                the_form.username.value = username
+            }
+            if (field_empty('organisation')) {
+                the_form.organisation.value = default_organisation(split1[1])
+            }
+        case 'username':
+            if (field_empty('realname')) {
+                realname = Cap(username.split('.').join(' '))
+                the_form.realname.value = realname
+            }
+        case 'realname':
+            if (field_empty('username')) {
+                username = Cap(realname.replace(' ', '.'))
+                the_form.username.value = username
+            }
+            if (the_form.firstname && the_form.lastname) {
+                var split2 = realname.split(' ')
+                var firstname='', lastname=''
+                firstname = split2[0]
+                lastname = split2.slice(1).join(' ')
+                if (field_empty('firstname')) {
+                    the_form.firstname.value = firstname
+                }
+                if (field_empty('lastname')) {
+                    the_form.lastname.value = lastname
+                }
+            }
+    }
+}
+
+function SubCap(str) {
+    switch (str) {
+        case 'de': case 'do': case 'da':
+        case 'du': case 'von':
+            return str;
+    }
+    if (str.toLowerCase().slice(0,2) == 'mc') {
+        return 'Mc'+str.slice(2,3).toUpperCase()+str.slice(3).toLowerCase()
+    }
+    return str.slice(0,1).toUpperCase()+str.slice(1).toLowerCase()
+}
+
+function Cap(str) {
+    var liz = str.split(' ')
+    for (var i=0; i<liz.length; i++) {
+        liz[i] = SubCap(liz[i])
+    }
+    return liz.join(' ')
+}
+
+/**
+ * Takes a domain name (behind the @ part of an email address)
+ * Customise this to handle the mail domains you're interested in 
+ */
+function default_organisation(orga) {
+    switch (orga.toLowerCase()) {
+        case 'gmx':
+        case 'yahoo':
+            return ''
+        default:
+            return orga
+    }
+}
+
diff --git a/share/roundup/templates/devel/initial_data.py b/share/roundup/templates/devel/initial_data.py
new file mode 100644 (file)
index 0000000..d9695bb
--- /dev/null
@@ -0,0 +1,87 @@
+from roundup.password import Password
+
+#
+# TRACKER INITIAL PRIORITY AND STATUS VALUES
+#
+
+bug_type = db.getclass('bug_type')
+bug_type.create(name='crash', order='1')
+bug_type.create(name='compile error', order='2')
+bug_type.create(name='resource usage', order='3')
+bug_type.create(name='security', order='4')
+bug_type.create(name='behavior', order='5')
+bug_type.create(name='rfe', order='6')
+
+component = db.getclass('component')
+component.create(name="backend", order="1")
+component.create(name="frontend", order="2")
+component.create(name="documentation", order="3")
+component.create(name="specification", order="4")
+
+version = db.getclass('version')
+version.create(name='devel', order='1')
+version.create(name='1.0', order='2')
+version.create(name='1.1', order='3')
+version.create(name='1.2', order='4')
+version.create(name='1.3', order='5')
+version.create(name='1.4', order='6')
+
+
+severity = db.getclass('severity')
+severity.create(name='critical', order='1')
+severity.create(name='urgent', order='2')
+severity.create(name='major', order='3')
+severity.create(name='normal', order='4')
+severity.create(name='minor', order='5')
+
+priority = db.getclass('priority')
+priority.create(name='immediate', order='1')
+priority.create(name='urgent', order='2')
+priority.create(name='high', order='3')
+priority.create(name='normal', order='4')
+priority.create(name='low', order='5')
+
+status = db.getclass('status')
+status.create(name = "new", order = "1")
+status.create(name='open', order='2')
+status.create(name='closed', order='3')
+status.create(name='pending', description='user feedback required', order='4')
+
+resolution = db.getclass('resolution')
+resolution.create(name='accepted', order='1')
+resolution.create(name='duplicate', order='2')
+resolution.create(name='fixed', order='3')
+resolution.create(name='invalid', order='4')
+resolution.create(name='later', order='5')
+resolution.create(name='out of date', order='6')
+resolution.create(name='postponed', order='7')
+resolution.create(name='rejected', order='8')
+resolution.create(name='remind', order='9')
+resolution.create(name='wont fix', order='10')
+resolution.create(name='works for me', order='11')
+
+keyword = db.getclass("keyword")
+keyword.create(name="patch", description="Contains patch")
+
+#
+# create the two default users
+user = db.getclass('user')
+user.create(username="admin", password=adminpw, address=admin_email, roles='Admin')
+user.create(username="anonymous", roles='Anonymous')
+user.create(username="user", roles='User')
+user.create(username="developer", roles='User, Developer')
+user.create(username="coordinator", roles='User, Developer, Coordinator')
+
+bug = db.getclass('bug')
+bug.create(title="the first")
+bug.create(title="the second")
+bug.create(title="the third")
+
+task = db.getclass('task')
+task.create(title="the first")
+task.create(title="the second")
+task.create(title="the third")
+
+milestone = db.getclass('milestone')
+milestone.create(title="release 1", bugs=['1','2'], tasks=['3'])
+
diff --git a/share/roundup/templates/devel/schema.py b/share/roundup/templates/devel/schema.py
new file mode 100644 (file)
index 0000000..a994fb8
--- /dev/null
@@ -0,0 +1,377 @@
+
+#
+# TRACKER SCHEMA
+#
+
+# Class automatically gets these properties:
+#   creation = Date()
+#   activity = Date()
+#   creator = Link('user')
+#   actor = Link('user')
+
+
+# This is the repository class, then you can see/edit repositories in pages like
+# "http://tracker/url/vcs_repo1"
+vcs_repo = Class(db, "vcs_repo",
+name=String(),
+host=String(),
+path=String(),
+webview_url=String())
+vcs_repo.setkey('name')
+
+# Stores revision data, lets you see/edit revisions in pages like
+# "http://tracker/url/vcs_rev1". The vcs_rev.item.html template is currently
+# broken, but this works fine without it.
+vcs_rev = Class(db, "vcs_rev",
+repository=Link('vcs_repo'),
+revision=String())
+
+
+
+# Component
+component = Class(db, 'component',
+                  name=String(),
+                  description=String(),
+                  order=Number(),
+                  assign_to=Link('user'))
+component.setkey('name')
+
+# Version
+version = Class(db, 'version',
+                name=String(),
+                description=String(),
+                order=Number())
+version.setkey('name')
+
+# Severity
+severity = Class(db, 'severity',
+                 name=String(),
+                 description=String(),
+                 order=Number())
+severity.setkey('name')
+
+# Priority
+priority = Class(db, 'priority',
+                 name=String(),
+                 description=String(),
+                 order=Number())
+priority.setkey('name')
+
+# Status
+status = Class(db, "status",
+               name=String(),
+               description=String(),
+               order=Number())
+status.setkey("name")
+
+# Resolution
+resolution = Class(db, "resolution",
+                   name=String(),
+                   description=String(),
+                   order=Number())
+resolution.setkey('name')
+
+# Keyword
+keyword = Class(db, "keyword",
+                name=String(),
+                description=String())
+keyword.setkey("name")
+                
+
+# User-defined saved searches
+query = Class(db, "query",
+              klass=String(),
+              name=String(),
+              url=String(),
+              private_for=Link('user'))
+
+# add any additional database schema configuration here
+
+user = Class(db, "user",
+             username=String(),
+             password=Password(),
+             address=String(),
+             realname=String(),
+             phone=String(),
+             organisation=String(),
+             alternate_addresses=String(),
+             queries=Multilink('query'),
+             roles=String(),     # comma-separated string of Role names
+             timezone=String(),
+             vcs_name=String())
+
+user.setkey("username")
+
+# Permissions for revision creation and repository viewing.
+for role in ('User',):
+    db.security.addPermissionToRole(role, 'Create', 'vcs_rev')
+    db.security.addPermissionToRole(role, 'View', 'vcs_repo')
+
+# FileClass automatically gets this property in addition to the Class ones:
+#   content = String()    [saved to disk in <tracker home>/db/files/]
+#   type = String()       [MIME type of the content, default 'text/plain']
+msg = FileClass(db, "msg",
+                author=Link("user", do_journal='no'),
+                recipients=Multilink("user", do_journal='no'),
+                date=Date(),
+                summary=String(),
+                files=Multilink("file"),
+                messageid=String(),
+                inreplyto=String(),
+                revision=Link("vcs_rev"))
+
+# File
+file = FileClass(db, "file",
+                name=String(),
+                description=String(indexme='yes'))
+
+# Patch
+patch = FileClass(db, "patch",
+                  name=String(),
+                  description=String(indexme='yes'),
+                  repository=String(),
+                  revision=String())
+
+# Bug Type
+bug_type = Class(db, 'bug_type',
+                 name=String(),
+                 description=String(),
+                 order=Number())
+bug_type.setkey('name')
+
+# IssueClass automatically gets these properties in addition to the Class ones:
+#   title = String()
+#   messages = Multilink("msg")
+#   files = Multilink("file")
+#   patches = Multilink("patches")
+#   nosy = Multilink("user")
+#   superseder = Multilink("issue")
+bug = IssueClass(db, "bug",
+                 type=Link('bug_type'),
+                 components=Multilink('component'),
+                 versions=Multilink('version'),
+                 severity=Link('severity'),
+                 priority=Link('priority'),
+                 dependencies=Multilink('bug'),
+                 assignee=Link('user'),
+                 status=Link('status'),
+                 resolution=Link('resolution'),
+                 superseder=Link('bug'),
+                 keywords=Multilink('keyword'))
+
+# Task Type
+task_type = Class(db, 'task_type',
+                 name=String(),
+                 description=String(),
+                 order=Number())
+task_type.setkey('name')
+
+# IssueClass automatically gets these properties in addition to the Class ones:
+#   title = String()
+#   messages = Multilink("msg")
+#   files = Multilink("file")
+#   nosy = Multilink("user")
+#   superseder = Multilink("issue")
+task = IssueClass(db, "task",
+                  type=Link('task_type'),
+                  components=Multilink('component'),
+                  priority=Link('priority'),
+                  dependencies=Multilink('task'),
+                  assignee=Multilink('user'),
+                  status=Link('status'),
+                  resolution=Link('resolution'),
+                  solves=Link('bug'))
+
+milestone = IssueClass(db, "milestone",
+                       bugs=Multilink("bug"),
+                       tasks=Multilink("task"),
+                       status=Link("status"),
+                       release_date=String())
+
+#
+# TRACKER SECURITY SETTINGS
+#
+# See the configuration and customisation document for information
+# about security setup.
+
+db.security.addRole(name='Developer', description='A developer')
+db.security.addRole(name='Coordinator', description='A coordinator')
+
+#
+# REGULAR USERS
+#
+# Give the regular users access to the web and email interface
+for r in 'User', 'Developer', 'Coordinator':
+    db.security.addPermissionToRole(r, 'Web Access')
+    db.security.addPermissionToRole(r, 'Email Access')
+
+##########################
+# User permissions
+##########################
+
+for cl in ('severity', 'component',
+           'version', 'priority', 'status', 'resolution',
+           'bug_type', 'bug', 'task_type', 'task', 'milestone',
+           'keyword', 'file', 'msg'):
+    db.security.addPermissionToRole('User', 'View', cl)
+    db.security.addPermissionToRole('Anonymous', 'View', cl)
+    db.security.addPermissionToRole('User', 'Create', cl)
+    
+
+def may_edit_file(db, userid, itemid):
+    return userid == db.file.get(itemid, "creator")
+
+p = db.security.addPermission(name='Edit', klass='file', check=may_edit_file,
+    description="User is allowed to remove their own files")
+db.security.addPermissionToRole('User', p)
+
+p = db.security.addPermission(name='Create', klass='bug',
+                              properties=('title', 'bug_type',
+                                          'components', 'versions',
+                                          'severity',
+                                          'messages', 'files', 'nosy'),
+                              description='User can report and discuss bugs')
+db.security.addPermissionToRole('User', p)
+
+p = db.security.addPermission(name='Edit', klass='bug',
+                              properties=('title', 'bug_type',
+                                          'components', 'versions',
+                                          'severity',
+                                          'messages', 'files', 'nosy'),
+                              description='User can report and discuss bugs')
+db.security.addPermissionToRole('User', p)
+
+p = db.security.addPermission(name='Create', klass='task',
+                              properties=('title', 'task_type',
+                                          'components',
+                                          'messages', 'files', 'nosy'),
+                              description='Developer can create and discuss tasks')
+db.security.addPermissionToRole('Developer', p)
+
+p = db.security.addPermission(name='Edit', klass='task',
+                              properties=('title', 'task_type',
+                                          'components',
+                                          'messages', 'files', 'nosy'),
+                              description='Developer can create and discuss tasks')
+db.security.addPermissionToRole('Developer', p)
+
+p = db.security.addPermission(name='Create', klass='milestone',
+                              description='Coordinator can create and discuss milestones')
+db.security.addPermissionToRole('Coordinator', p)
+
+p = db.security.addPermission(name='Edit', klass='milestone',
+                              description='Coordinator can create and discuss milestones')
+db.security.addPermissionToRole('Coordinator', p)
+
+
+##########################
+# Developer permissions
+##########################
+for cl in ('bug_type', 'severity', 'component',
+           'version', 'priority', 'status', 'resolution',
+           'bug', 'file', 'msg', 'keyword'):
+    db.security.addPermissionToRole('Developer', 'View', cl)
+
+for cl in ('bug', 'file', 'msg', 'keyword'):
+    db.security.addPermissionToRole('Developer', 'Edit', cl)
+    db.security.addPermissionToRole('Developer', 'Create', cl)
+
+
+##########################
+# Coordinator permissions
+##########################
+for cl in ('bug_type', 'task_type', 'severity', 'component',
+           'version', 'priority', 'status', 'resolution', 'bug', 'task', 'file', 'msg'):
+    db.security.addPermissionToRole('Coordinator', 'View', cl)
+    db.security.addPermissionToRole('Coordinator', 'Edit', cl)
+    db.security.addPermissionToRole('Coordinator', 'Create', cl)
+
+# May users view other user information? Comment these lines out
+# if you don't want them to
+db.security.addPermissionToRole('User', 'View', 'user')
+db.security.addPermissionToRole('Developer', 'View', 'user')
+db.security.addPermissionToRole('Coordinator', 'View', 'user')
+
+# Allow Coordinator to edit any user, including their roles.
+db.security.addPermissionToRole('Coordinator', 'Edit', 'user')
+db.security.addPermissionToRole('Coordinator', 'Web Roles')
+
+# Users should be able to edit their own details -- this permission is
+# limited to only the situation where the Viewed or Edited item is their own.
+def own_record(db, userid, itemid):
+    '''Determine whether the userid matches the item being accessed.'''
+    return userid == itemid
+p = db.security.addPermission(name='View', klass='user', check=own_record,
+    description="User is allowed to view their own user details")
+for r in 'User', 'Developer', 'Coordinator':
+    db.security.addPermissionToRole(r, p)
+p = db.security.addPermission(name='Edit', klass='user', check=own_record,
+    description="User is allowed to edit their own user details",
+    properties=('username', 'password',
+                'address', 'realname',
+                'phone', 'organization',
+                'alternate_addresses',
+                'queries',
+                'timezone')) # Note: 'roles' excluded - users should not be able to edit their own roles. 
+for r in 'User', 'Developer':
+    db.security.addPermissionToRole(r, p)
+
+# Users should be able to edit and view their own queries. They should also
+# be able to view any marked as not private. They should not be able to
+# edit others' queries, even if they're not private
+def view_query(db, userid, itemid):
+    private_for = db.query.get(itemid, 'private_for')
+    if not private_for: return True
+    return userid == private_for
+def edit_query(db, userid, itemid):
+    return userid == db.query.get(itemid, 'creator')
+p = db.security.addPermission(name='View', klass='query', check=view_query,
+    description="User is allowed to view their own and public queries")
+for r in 'User', 'Developer', 'Coordinator':
+    db.security.addPermissionToRole(r, p)
+p = db.security.addPermission(name='Edit', klass='query', check=edit_query,
+    description="User is allowed to edit their queries")
+for r in 'User', 'Developer', 'Coordinator':
+    db.security.addPermissionToRole(r, p)
+p = db.security.addPermission(name='Create', klass='query',
+    description="User is allowed to create queries")
+for r in 'User', 'Developer', 'Coordinator':
+    db.security.addPermissionToRole(r, p)
+
+
+#
+# ANONYMOUS USER PERMISSIONS
+#
+# Let anonymous users access the web interface. Note that almost all
+# trackers will need this Permission. The only situation where it's not
+# required is in a tracker that uses an HTTP Basic Authenticated front-end.
+db.security.addPermissionToRole('Anonymous', 'Web Access')
+
+# Let anonymous users access the email interface (note that this implies
+# that they will be registered automatically, hence they will need the
+# "Create" user Permission below)
+# This is disabled by default to stop spam from auto-registering users on
+# public trackers.
+#db.security.addPermissionToRole('Anonymous', 'Email Access')
+
+# Assign the appropriate permissions to the anonymous user's Anonymous
+# Role. Choices here are:
+# - Allow anonymous users to register
+db.security.addPermissionToRole('Anonymous', 'Create', 'user')
+
+# Allow anonymous users access to view issues (and the related, linked
+# information).
+
+for cl in 'bug', 'task', 'milestone', 'severity', 'status', 'resolution', 'msg', 'file':
+    db.security.addPermissionToRole('Anonymous', 'View', cl)
+
+# [OPTIONAL]
+# Allow anonymous users access to create or edit "issue" items (and the
+# related file and message items)
+#for cl in 'issue', 'file', 'msg':
+#   db.security.addPermissionToRole('Anonymous', 'Create', cl)
+#   db.security.addPermissionToRole('Anonymous', 'Edit', cl)
+
+
+# vim: set filetype=python sts=4 sw=4 et si :
+