[Bps-public-commit] wifty branch, master, created. 94e56c4dee5911b28346972be17d675bee181f7e
Kevin Falcone
falcone at bestpractical.com
Wed Mar 31 15:40:51 EDT 2010
The branch, master has been created
at 94e56c4dee5911b28346972be17d675bee181f7e (commit)
- Log -----------------------------------------------------------------
commit 38eaa832eb93c3c3af24f0117f5e188533a2c362
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Sun Dec 25 01:26:13 2005 +0000
(empty commit message)
commit 655563731fb5704ff52337d72cd0063384bdb884
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Sun Dec 25 01:29:37 2005 +0000
Merge into 'trunk'
r18591 at truegrounds (orig r2266): alexmv | 2005-11-08 16:34:35 -0500
r7059 at zoq-fot-pik: chmrr | 2005-11-08 16:33:37 -0500
* Look ma, no helpers. Temporary replacements dropped in
r18592 at truegrounds (orig r2267): alexmv | 2005-11-08 16:34:53 -0500
r7060 at zoq-fot-pik: chmrr | 2005-11-08 16:33:55 -0500
* Continuations working epsilon better
r18566 at truegrounds (orig r2262): root | 2005-11-08 12:49:06 -0500
* Task edit style updates
r18568 at truegrounds (orig r2263): root | 2005-11-08 13:11:27 -0500
* CSS hackery
r18602 at truegrounds (orig r2272): alexmv | 2005-11-08 18:19:49 -0500
r7070 at zoq-fot-pik: chmrr | 2005-11-08 18:19:08 -0500
* Hackish API for making linksandbuttonsthat pop the stack
* First steps toward preserving J:C arg when needed
r18603 at truegrounds (orig r2273): alexmv | 2005-11-09 14:55:48 -0500
r7081 at zoq-fot-pik: chmrr | 2005-11-09 14:55:22 -0500
* Jifty::Web->(button|link) both take parameters
* Jifty::Action->button takes parameters, but calls Jifty::Web->button
* Started another doc pass
* Linkified
r18655 at truegrounds (orig r2274): alexmv | 2005-11-09 17:22:58 -0500
r7083 at zoq-fot-pik: chmrr | 2005-11-09 17:22:27 -0500
* Some docs, notably a bunch of continuations
* Now guessed configu is the base of the config file, not an alternative to it
r18660 at truegrounds (orig r2276): alexmv | 2005-11-10 15:50:10 -0500
r7085 at zoq-fot-pik: chmrr | 2005-11-09 18:25:39 -0500
* Don't explode if I don't recognize the continuation, silently fail
r18661 at truegrounds (orig r2277): alexmv | 2005-11-10 15:50:23 -0500
r7086 at zoq-fot-pik: chmrr | 2005-11-10 15:49:53 -0500
* Coderefs through continuations
r18662 at truegrounds (orig r2278): alexmv | 2005-11-10 15:50:32 -0500
* started to add users
* Config file loading cleanups
* new_article became new_entry
* Refactored "config" to its own package to clean out Jifty::
* checkpoint of new blogdemo pseudocode
r18706 at truegrounds (orig r2285): alexmv | 2005-11-10 16:51:50 -0500
r7100 at zoq-fot-pik: chmrr | 2005-11-10 16:51:16 -0500
* First pass at a "gosub" call
r18707 at truegrounds (orig r2286): alexmv | 2005-11-10 16:51:56 -0500
* jesse and alex's hit list
* made Jifty::Handle its own subclass
r18718 at truegrounds (orig r2292): alexmv | 2005-11-11 13:15:51 -0500
r7112 at zoq-fot-pik: chmrr | 2005-11-11 13:15:18 -0500
* Validation rototill. XML is now generated by /validator.xml not by
random function calls.
* Tiny style cleanups to render_messages
* checkpoint
* A bit of Jifty script command help cleanup
* makefile deps update
r18914 at truegrounds (orig r2321): alexmv | 2005-11-15 18:25:25 -0500
r7213 at zoq-fot-pik: chmrr | 2005-11-15 18:24:42 -0500
* No defaults from arguments which aren't columns
* Save continuations after validation
* Only redirect if not validating or success
r18933 at truegrounds (orig r2331): alexmv | 2005-11-17 15:16:39 -0500
r7218 at zoq-fot-pik: chmrr | 2005-11-17 15:11:57 -0500
* Automagic password_confirm
r18934 at truegrounds (orig r2332): alexmv | 2005-11-17 15:16:47 -0500
r7219 at zoq-fot-pik: chmrr | 2005-11-17 15:12:23 -0500
* Remove all references to bin/schema
r18935 at truegrounds (orig r2333): alexmv | 2005-11-17 15:16:53 -0500
r7220 at zoq-fot-pik: chmrr | 2005-11-17 15:12:45 -0500
* Fix current_user bug ( was grabbing framework all the time)
* Cleanups and warning avoidance and more correct monikerization
* moved serial and mason out of the base class
* Switched "framework" to "web"
* Added the other new wiki components
* Removed Text::Markdown from our dist, as it's on CPAN now
* Recent changes
* Added menu structure
* Render menus if you are not logged in
* Reverted men stuff
* Reverted the change to nav
r19038 at truegrounds (orig r2356): alexmv | 2005-11-21 15:38:27 -0500
r7288 at zoq-fot-pik: chmrr | 2005-11-21 15:37:14 -0500
* Pull mandatory from column
* Move jifty stuff out of btdt_behavior.js
* autocomplete cleanups
* multiplace frobing
r19050 at truegrounds (orig r2364): alexmv | 2005-11-22 02:27:49 -0500
r7299 at zoq-fot-pik: chmrr | 2005-11-22 02:26:32 -0500
* s/Jifty->web->form->add_action/Jifty->web->new_action/g
* Paging on groups, news, invitations
* 'Accept all' button for invites
r19055 at truegrounds (orig r2367): alexmv | 2005-11-22 16:15:53 -0500
r7308 at zoq-fot-pik: chmrr | 2005-11-22 16:14:59 -0500
* Revert r2327
r19056 at truegrounds (orig r2368): alexmv | 2005-11-22 16:32:30 -0500
r7312 at zoq-fot-pik: chmrr | 2005-11-22 16:30:15 -0500
* Updates only need to validate arguments that get submitted
* Continuations - holy grail
*removed crack
r19055 at truegrounds (orig r2367): alexmv | 2005-11-22 16:15:53 -0500
r7308 at zoq-fot-pik: chmrr | 2005-11-22 16:14:59 -0500
* Revert r2327
r19056 at truegrounds (orig r2368): alexmv | 2005-11-22 16:32:30 -0500
r7312 at zoq-fot-pik: chmrr | 2005-11-22 16:30:15 -0500
* Updates only need to validate arguments that get submitted
r19065 at truegrounds (orig r2373): alexmv | 2005-11-22 18:18:06 -0500
r7319 at zoq-fot-pik: chmrr | 2005-11-22 18:17:02 -0500
* JiftyWiki -> Wifty because I like my tab completion, dammit
r19066 at truegrounds (orig r2374): alexmv | 2005-11-22 18:18:17 -0500
* more wifty rename
More rename
* All sorts of updates to Wifty. It's starting to look like a real app
* ClassLoader now autocreates ::Record, ::Collection and $AppName base classes
* added rico
* cleaning up css a bit
* Added IE7. it almost improves IE
r19347 at truegrounds (orig r2380): alexmv | 2005-11-25 15:37:10 -0500
r7348 at zoq-fot-pik: chmrr | 2005-11-25 15:34:12 -0500
* validate_argument(s?) -> _validate_argument$1 to avoid possible infinite look with arguments named "arguments" or "argument"
r19348 at truegrounds (orig r2381): alexmv | 2005-11-25 15:37:21 -0500
r7349 at zoq-fot-pik: chmrr | 2005-11-25 15:34:50 -0500
* Use *latest* possible match when auto-guessing config
* We really don't want to pull in mandatoryness from tasks
* don't run the quicksearch if the user doesn't want to.
* Don't create a task if the user doesn't put in a summary.
* If the moniker the test looks for isn't there, carp.
* more explicit test info
* Look Ma! full support for SQLite
* Switched to Markdown
r19533 at truegrounds (orig r2400): alexmv | 2005-11-29 02:05:55 -0500
r7379 at zoq-fot-pik: chmrr | 2005-11-29 02:04:44 -0500
* Jifty::Web::Form::Link gutted; most of it now in Jifty::Web::Form::Clickable
* Elements have key bindings, ids, labels, and classes
* Changed everything to use new link syntax; "button" is gone
* Tests for low-level continuations
* Only make new continuations during CALL if we absolutely need to
* Snip J:C-* out of request when it is saved into a continuation, so
we don't infinite loop
* Continuations non-raw API needs tests
* Lots of docs needed
r19534 at truegrounds (orig r2401): alexmv | 2005-11-29 02:06:06 -0500
* Added a BPS logo
r19537 at truegrounds (orig r2402): root | 2005-11-29 04:15:12 -0500
* Fixed to cope with API changes (removed web->submit)
r19538 at truegrounds (orig r2403): root | 2005-11-29 04:21:42 -0500
* "ApplicationName" not "Name"
r19539 at truegrounds (orig r2404): alexmv | 2005-11-29 15:18:17 -0500
r7387 at zoq-fot-pik: chmrr | 2005-11-29 12:52:14 -0500
* Continuations don't need to specify paths for their requests all
the time (eg, counter demo)
r19540 at truegrounds (orig r2405): alexmv | 2005-11-29 15:18:36 -0500
r7388 at zoq-fot-pik: chmrr | 2005-11-29 15:17:26 -0500
* More Jifty continuation tests, using the API this time
* Small contoinuation bugfixes
r19541 at truegrounds (orig r2406): alexmv | 2005-11-29 15:41:10 -0500
r7391 at zoq-fot-pik: chmrr | 2005-11-29 15:40:33 -0500
* Login as continuations
r19549 at truegrounds (orig r2407): alexmv | 2005-11-29 18:57:52 -0500
r7393 at zoq-fot-pik: chmrr | 2005-11-29 18:57:08 -0500
* Use new submit syntax
* No more Rico (JS bugs)
* New form syntax
* Half-broken task review
r19642 at truegrounds (orig r2408): alexmv | 2005-11-30 16:48:44 -0500
r7402 at zoq-fot-pik: chmrr | 2005-11-30 16:47:48 -0500
* Task review using continuations
r19643 at truegrounds (orig r2409): alexmv | 2005-11-30 16:48:52 -0500
r7403 at zoq-fot-pik: chmrr | 2005-11-30 16:48:10 -0500
* Forgot control loop for task review
diff --git a/bin/jifty b/bin/jifty
new file mode 100755
index 0000000..4f653a5
--- /dev/null
+++ b/bin/jifty
@@ -0,0 +1,13 @@
+#!/usr/bin/perl
+use warnings;
+use strict;
+use File::Basename qw(dirname);
+
+BEGIN {
+ my $dir = dirname(__FILE__);
+ push @INC, "$dir/../lib";
+ push @INC, "$dir/../../Jifty/lib";
+}
+
+use Jifty::Script;
+Jifty::Script->dispatch();
diff --git a/etc/config.yml b/etc/config.yml
new file mode 100644
index 0000000..5812a3d
--- /dev/null
+++ b/etc/config.yml
@@ -0,0 +1,15 @@
+framework:
+ LogConfig: etc/btdt.log4perl.conf
+ Database:
+ Driver: Pg
+ Host: localhost
+ User: postgres
+ Version: 0.0.8
+ Password: ''
+ RequireSSL: 0
+# Mailer: IO
+# MailerArgs:
+# - %log/mail.log%
+ SiteConfig: etc/site_config.yml
+application:
+ MaxWurbles: 9
diff --git a/lib/Wifty/Bootstrap.pm b/lib/Wifty/Bootstrap.pm
new file mode 100644
index 0000000..f05c0d4
--- /dev/null
+++ b/lib/Wifty/Bootstrap.pm
@@ -0,0 +1,15 @@
+package Wifty::Bootstrap;
+
+use Wifty::Model::Page;
+sub run {
+ my $self = shift;
+
+ my $index = Wifty::Model::Page->new();
+ $index->create( name => 'home',
+ content=> 'Welcome to your Wifty');
+
+
+}
+
+
+1;
diff --git a/lib/Wifty/Model/Page.pm b/lib/Wifty/Model/Page.pm
new file mode 100644
index 0000000..b6541f8
--- /dev/null
+++ b/lib/Wifty/Model/Page.pm
@@ -0,0 +1,106 @@
+package Wifty::Model::Page::Schema;
+use Jifty::DBI::Schema;
+
+column name =>
+ type is 'text',
+ is mandatory,
+ is distinct;
+
+column content =>
+ type is 'text',
+ label is 'Page content',
+ render_as 'textarea';
+
+column updated =>
+ type is 'timestamp',
+ since '0.0.6';
+
+
+#column revisions => refers_to Wifty::Model::Revision by 'page';
+
+package Wifty::Model::Page;
+use base qw/Wifty::Record/;
+use Text::Markdown;
+use HTML::Scrubber;
+
+sub wiki_content {
+ my $self = shift;
+ my $content = $self->content();
+ my $scrubber = HTML::Scrubber->new();
+
+ $scrubber->default(
+ 0,
+ { '*' => 0,
+ id => 1,
+ class => 1,
+ href => qr{^(?:http:|ftp:|https:|/)}i,
+
+ # Match http, ftp and relative urls
+ face => 1,
+ size => 1,
+ target => 1
+ }
+ );
+
+ $scrubber->deny(qw[*]);
+ $scrubber->allow(
+ qw[A B U P BR I HR BR SMALL EM FONT SPAN DIV UL OL LI DL DT DD]);
+ $scrubber->comment(0);
+ return ( markdown( $scrubber->scrub( $self->content ) ) );
+
+}
+
+
+sub create {
+ my $self = shift;
+ my %args = (@_);
+ my $now = DateTime->now();
+ $args{'updated'} = $now->ymd." ".$now->hms;
+ my ($id) = $self->SUPER::create(%args);
+ if ($self->id) {
+ $self->_add_revision(%args);
+ }
+ return($id);
+}
+
+=head2 _add_revision
+
+Adds a revision for this page. Called by create and set_content
+
+=over
+
+=item content
+
+=back
+
+=cut
+
+sub _add_revision {
+ my $self = shift;
+ my %args = (@_);
+
+ my $rev = Wifty::Model::Revision->new();
+ $rev->create(
+ page => $self->id,
+ content => $args{'content'}
+ );
+
+}
+
+sub set_content {
+ my $self = shift;
+ my $content = shift;
+ my ($val, $msg) = $self->SUPER::set_content($content);
+ $self->_add_revision(content =>$content );
+ return ($val,$msg);
+}
+
+sub _set {
+ my $self = shift;
+ my ($val,$msg) = $self->SUPER::_set(@_);
+ my $now = DateTime->now();
+ $self->SUPER::_set( column => 'updated', value => $now->ymd." ".$now->hms);
+ return ($val,$msg);
+}
+
+1;
diff --git a/lib/Wifty/Model/Revision.pm b/lib/Wifty/Model/Revision.pm
new file mode 100644
index 0000000..646519b
--- /dev/null
+++ b/lib/Wifty/Model/Revision.pm
@@ -0,0 +1,33 @@
+package Wifty::Model::Revision::Schema;
+use Jifty::DBI::Schema;
+
+column page =>
+ refers_to Wifty::Model::Revision;
+
+column content =>
+ type is 'text',
+ render_as 'textarea';
+
+column created =>
+ type is 'timestamp';
+
+package Wifty::Model::Revision;
+use base qw/Wifty::Record/;
+
+use DateTime;
+
+
+sub since { '0.0.5' }
+
+
+sub create {
+ my $self = shift;
+ my %args = (@_);
+
+ my $now = DateTime->now();
+ $args{'created'} = $now->ymd." ".$now->hms;
+ $self->SUPER::create(%args);
+
+}
+
+1;
diff --git a/lib/Wifty/Model/User.pm b/lib/Wifty/Model/User.pm
new file mode 100644
index 0000000..61cd92a
--- /dev/null
+++ b/lib/Wifty/Model/User.pm
@@ -0,0 +1,32 @@
+package Wifty::Model::User::Schema;
+use Jifty::DBI::Schema;
+
+column name =>
+ type is 'text',
+ is mandatory,
+ is distinct;
+
+column email =>
+ type is 'text',
+ is mandatory,
+ is distinct;
+
+column password =>,
+ type is 'text',
+ render_as 'password';
+
+
+
+package Wifty::Model::User;
+use base qw/Wifty::Record/;
+
+sub since {'0.0.7'}
+
+sub create {
+ my $self = shift;
+ my %args = (@_);
+ my ($id) = $self->SUPER::create(%args);
+ return($id);
+}
+
+1;
diff --git a/web/static/css/.base.css.swp b/web/static/css/.base.css.swp
new file mode 100644
index 0000000..79c4cf5
Binary files /dev/null and b/web/static/css/.base.css.swp differ
diff --git a/web/static/css/base.css b/web/static/css/base.css
new file mode 100644
index 0000000..df6ab06
--- /dev/null
+++ b/web/static/css/base.css
@@ -0,0 +1,111 @@
+body {
+ background-color: #dddddd;
+
+
+}
+
+h1 {
+ background: green;
+ color: white;
+ padding: 0.2em;
+ border: 0;
+ margin: 0;
+ margin: -10px 0 0 -10px ;
+ margin-right: -10px;
+
+}
+
+a {
+ color: black;
+ font-style: bold;
+}
+
+#jifty-wait-message {
+ display: none;
+}
+
+div#menu {
+ background: green;
+
+}
+
+div#sidebar {
+ float: left;
+
+
+}
+
+div#content {
+ background: #ffffff;
+ padding: 2em;
+}
+
+
+ul.menu {
+ display: block;
+ border-bottom: 2px solid black;
+ padding-bottom: 4px;
+ width: 100%;
+ margin-left: 0px;
+ margin-right: 0px;
+
+}
+
+ul.menu li {
+
+ background: #770077;
+ color:#ffffff;
+ text-align: center;
+ display: inline;
+ padding: 0.4em 0.4em 4px 0.4em;
+ border-bottom: 2px solid black;
+;
+
+}
+
+ul.menu li a {
+ color: #ffffff;
+
+}
+
+div#salutation {
+ float: right;
+ font-style: italic;
+}
+
+textarea.content {
+ height: 50em;
+ background: #ddd;
+ border: 1px solid black;
+ padding: 5px;
+}
+
+label {
+ position: absolute;
+ top: 3.4em;
+ left: 1em;
+ font-size: 2em;
+}
+
+input[type=submit] {
+ border: 1px solid black;
+ font-size: 2em;
+ margin: 5px;
+
+}
+
+div#syntax {
+ float: right;
+ background: white;
+ border: 1px solid #333;
+ padding: 3px;
+ font-size: 0.8em;
+ width: 25%;
+ position: absolute;
+ top: 10em;
+ right: 2em;
+}
+
+
+
+
diff --git a/web/static/css/main.css b/web/static/css/main.css
new file mode 100644
index 0000000..db0e44c
--- /dev/null
+++ b/web/static/css/main.css
@@ -0,0 +1 @@
+ at import "base.css";
diff --git a/web/static/js/behaviour.js b/web/static/js/behaviour.js
new file mode 100644
index 0000000..bc5504f
--- /dev/null
+++ b/web/static/js/behaviour.js
@@ -0,0 +1,254 @@
+/*
+ Behaviour v1.1 by Ben Nolan, June 2005. Based largely on the work
+ of Simon Willison (see comments by Simon below).
+
+ Description:
+
+ Uses css selectors to apply javascript behaviours to enable
+ unobtrusive javascript in html documents.
+
+ Usage:
+
+ var myrules = {
+ 'b.someclass' : function(element){
+ element.onclick = function(){
+ alert(this.innerHTML);
+ }
+ },
+ '#someid u' : function(element){
+ element.onmouseover = function(){
+ this.innerHTML = "BLAH!";
+ }
+ }
+ };
+
+ Behaviour.register(myrules);
+
+ // Call Behaviour.apply() to re-apply the rules (if you
+ // update the dom, etc).
+
+ License:
+
+ My stuff is BSD licensed. Not sure about Simon's.
+
+ More information:
+
+ http://ripcord.co.nz/behaviour/
+
+*/
+
+var Behaviour = {
+ list : new Array,
+
+ register : function(sheet){
+ Behaviour.list.push(sheet);
+ },
+
+ start : function(){
+ Behaviour.addLoadEvent(function(){
+ Behaviour.apply();
+ });
+ },
+
+ apply : function(){
+ for (h=0;sheet=Behaviour.list[h];h++){
+ for (selector in sheet){
+ list = document.getElementsBySelector(selector);
+
+ if (!list){
+ continue;
+ }
+
+ for (i=0;element=list[i];i++){
+ sheet[selector](element);
+ }
+ }
+ }
+ },
+
+ addLoadEvent : function(func){
+ var oldonload = window.onload;
+
+ if (typeof window.onload != 'function') {
+ window.onload = func;
+ } else {
+ window.onload = function() {
+ oldonload();
+ func();
+ }
+ }
+ }
+}
+
+Behaviour.start();
+
+/*
+ The following code is Copyright (C) Simon Willison 2004.
+
+ document.getElementsBySelector(selector)
+ - returns an array of element objects from the current document
+ matching the CSS selector. Selectors can contain element names,
+ class names and ids and can be nested. For example:
+
+ elements = document.getElementsBySelect('div#main p a.external')
+
+ Will return an array of all 'a' elements with 'external' in their
+ class attribute that are contained inside 'p' elements that are
+ contained inside the 'div' element which has id="main"
+
+ New in version 0.4: Support for CSS2 and CSS3 attribute selectors:
+ See http://www.w3.org/TR/css3-selectors/#attribute-selectors
+
+ Version 0.4 - Simon Willison, March 25th 2003
+ -- Works in Phoenix 0.5, Mozilla 1.3, Opera 7, Internet Explorer 6, Internet Explorer 5 on Windows
+ -- Opera 7 fails
+*/
+
+function getAllChildren(e) {
+ // Returns all children of element. Workaround required for IE5/Windows. Ugh.
+ return e.all ? e.all : e.getElementsByTagName('*');
+}
+
+document.getElementsBySelector = function(selector) {
+ // Attempt to fail gracefully in lesser browsers
+ if (!document.getElementsByTagName) {
+ return new Array();
+ }
+ // Split selector in to tokens
+ var tokens = selector.split(' ');
+ var currentContext = new Array(document);
+ for (var i = 0; i < tokens.length; i++) {
+ token = tokens[i].replace(/^\s+/,'').replace(/\s+$/,'');;
+ if (token.indexOf('#') > -1) {
+ // Token is an ID selector
+ var bits = token.split('#');
+ var tagName = bits[0];
+ var id = bits[1];
+ var element = document.getElementById(id);
+ if (tagName && element.nodeName.toLowerCase() != tagName) {
+ // tag with that ID not found, return false
+ return new Array();
+ }
+ // Set currentContext to contain just this element
+ currentContext = new Array(element);
+ continue; // Skip to next token
+ }
+ if (token.indexOf('.') > -1) {
+ // Token contains a class selector
+ var bits = token.split('.');
+ var tagName = bits[0];
+ var className = bits[1];
+ if (!tagName) {
+ tagName = '*';
+ }
+ // Get elements matching tag, filter them for class selector
+ var found = new Array;
+ var foundCount = 0;
+ for (var h = 0; h < currentContext.length; h++) {
+ var elements;
+ if (tagName == '*') {
+ elements = getAllChildren(currentContext[h]);
+ } else {
+ elements = currentContext[h].getElementsByTagName(tagName);
+ }
+ for (var j = 0; j < elements.length; j++) {
+ found[foundCount++] = elements[j];
+ }
+ }
+ currentContext = new Array;
+ var currentContextIndex = 0;
+ for (var k = 0; k < found.length; k++) {
+ if (found[k].className && found[k].className.match(new RegExp('\\b'+className+'\\b'))) {
+ currentContext[currentContextIndex++] = found[k];
+ }
+ }
+ continue; // Skip to next token
+ }
+ // Code to deal with attribute selectors
+ if (token.match(/^(\w*)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/)) {
+ var tagName = RegExp.$1;
+ var attrName = RegExp.$2;
+ var attrOperator = RegExp.$3;
+ var attrValue = RegExp.$4;
+ if (!tagName) {
+ tagName = '*';
+ }
+ // Grab all of the tagName elements within current context
+ var found = new Array;
+ var foundCount = 0;
+ for (var h = 0; h < currentContext.length; h++) {
+ var elements;
+ if (tagName == '*') {
+ elements = getAllChildren(currentContext[h]);
+ } else {
+ elements = currentContext[h].getElementsByTagName(tagName);
+ }
+ for (var j = 0; j < elements.length; j++) {
+ found[foundCount++] = elements[j];
+ }
+ }
+ currentContext = new Array;
+ var currentContextIndex = 0;
+ var checkFunction; // This function will be used to filter the elements
+ switch (attrOperator) {
+ case '=': // Equality
+ checkFunction = function(e) { return (e.getAttribute(attrName) == attrValue); };
+ break;
+ case '~': // Match one of space seperated words
+ checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('\\b'+attrValue+'\\b'))); };
+ break;
+ case '|': // Match start with value followed by optional hyphen
+ checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('^'+attrValue+'-?'))); };
+ break;
+ case '^': // Match starts with value
+ checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) == 0); };
+ break;
+ case '$': // Match ends with value - fails with "Warning" in Opera 7
+ checkFunction = function(e) { return (e.getAttribute(attrName).lastIndexOf(attrValue) == e.getAttribute(attrName).length - attrValue.length); };
+ break;
+ case '*': // Match ends with value
+ checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) > -1); };
+ break;
+ default :
+ // Just test for existence of attribute
+ checkFunction = function(e) { return e.getAttribute(attrName); };
+ }
+ currentContext = new Array;
+ var currentContextIndex = 0;
+ for (var k = 0; k < found.length; k++) {
+ if (checkFunction(found[k])) {
+ currentContext[currentContextIndex++] = found[k];
+ }
+ }
+ // alert('Attribute Selector: '+tagName+' '+attrName+' '+attrOperator+' '+attrValue);
+ continue; // Skip to next token
+ }
+
+ if (!currentContext[0]){
+ return;
+ }
+
+ // If we get here, token is JUST an element (not a class or ID selector)
+ tagName = token;
+ var found = new Array;
+ var foundCount = 0;
+ for (var h = 0; h < currentContext.length; h++) {
+ var elements = currentContext[h].getElementsByTagName(tagName);
+ for (var j = 0; j < elements.length; j++) {
+ found[foundCount++] = elements[j];
+ }
+ }
+ currentContext = found;
+ }
+ return currentContext;
+}
+
+/* That revolting regular expression explained
+/^(\w+)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/
+ \---/ \---/\-------------/ \-------/
+ | | | |
+ | | | The value
+ | | ~,|,^,$,* or =
+ | Attribute
+ Tag
+*/
diff --git a/web/static/js/bps_util.js b/web/static/js/bps_util.js
new file mode 100644
index 0000000..330c8a5
--- /dev/null
+++ b/web/static/js/bps_util.js
@@ -0,0 +1,89 @@
+// XXX TODO This library should likely be refactored to use behaviour
+
+function focusElementById(id) {
+ var e = document.getElementById(id);
+ if (e) e.focus();
+}
+
+function openCalWindow(field) {
+ var objWindow = window.open('/helpers/calendar.html?field='+field, 'Calendar', 'height=200,width=235,scrollbars=1');
+ objWindow.focus();
+}
+
+function updateParentField(field, value) {
+ if (window.opener) {
+ window.opener.document.getElementById(field).value = value;
+ window.close();
+ }
+}
+
+function createCalendarLink(input) {
+ var e = document.getElementById(input);
+ if (e) {
+ var link = document.createElement('a');
+ link.setAttribute('href', '#');
+ link.setAttribute('onclick', "openCalWindow('"+input+"'); return false;");
+
+ var text = document.createTextNode('Calendar');
+ link.appendChild(text);
+
+ var space = document.createTextNode(' ');
+
+ e.parentNode.insertBefore(link, e.nextSibling);
+ e.parentNode.insertBefore(space, e.nextSibling);
+
+ return true;
+ }
+ return false;
+}
+
+// onload handlers
+
+var onLoadStack = new Array();
+var onLoadLastStack = new Array();
+var onLoadExecuted = 0;
+
+function onLoadHook(commandStr) {
+ if(typeof(commandStr) == "string") {
+ onLoadStack[onLoadStack.length] = commandStr;
+ return true;
+ }
+ return false;
+}
+
+// some things *really* need to be done after everything else
+function onLoadLastHook(commandStr) {
+ if(typeof(commandStr) == "string"){
+ onLoadLastStack[onLoadLastStack.length] = commandStr;
+ return true;
+ }
+ return false;
+}
+
+function doOnLoadHooks() {
+ if(onLoadExecuted) return;
+ for (var x=0; x < onLoadStack.length; x++) {
+ eval(onLoadStack[x]);
+ }
+ for (var x=0; x < onLoadLastStack.length; x++) {
+ eval(onLoadLastStack[x]);
+ }
+ onLoadExecuted = 1;
+}
+
+
+if (typeof window.onload != 'function') {
+ window.onload = doOnLoadHooks;
+} else {
+ var oldonload = window.onload;
+
+ window.onload = function() {
+ oldonload();
+ doOnLoadHooks();
+ }
+}
+
+function jifty_button_click() {
+
+1;
+}
diff --git a/web/static/js/btdt_behaviour.js b/web/static/js/btdt_behaviour.js
new file mode 100644
index 0000000..7c3f3ec
--- /dev/null
+++ b/web/static/js/btdt_behaviour.js
@@ -0,0 +1,36 @@
+/* Uses Behaviour v1.0 (behaviour.js); see
+ http://ripcord.co.nz/behaviour/
+
+ IMPORTANT: if you make DOM changes that mean that an element
+ ought to gain or lose a behaviour, call Behaviour.apply()!
+ (Actually, that *won't* make something lose a behaviour, so if that's necessary
+ you'll need to have an empty "fallback". Ie, if "div#foo a" should have a special
+ onclick and other "a" shouldn't, then there ought to be an explicit "a" style
+ that sets onclick to a trivial function, if DOM changes will ever happen.)
+ (Also, with the current behaviour.js, the order of application of styles is undefined,
+ so you can't really do cascading. I've suggested to the author that he change it;
+ if he doesn't, but we need it, it's an easy change to make the sheets arrays instead
+ of Objects (hashes). For now this can be dealt with by loading multiple sheets (register
+ calls), though.)
+*/
+
+
+
+/* 'textarea.bigbox' : function(elt) {
+ new Form.Element.Observer( elt.id,
+ 1,
+ function( element, value ) {
+ new Ajax.Updater( elt.id+'-observer',
+ '/fragments/parsetext',
+ { parameters: Form.Element.getAction(elt).serialize(),
+ onComplete: function () { Behaviour.apply() } }
+ )
+ }
+ );
+ },
+*/
+
+var myrules = {
+ };
+
+ Behaviour.register(myrules);
diff --git a/web/static/js/combobox.js b/web/static/js/combobox.js
new file mode 100644
index 0000000..90fcddf
--- /dev/null
+++ b/web/static/js/combobox.js
@@ -0,0 +1,233 @@
+function ComboBox_InitWith(n) {
+ if ( typeof( window.addEventListener ) != "undefined" ) {
+ window.addEventListener("load", ComboBox_Init(n), false);
+ } else if ( typeof( window.attachEvent ) != "undefined" ) {
+ window.attachEvent("onload", ComboBox_Init(n));
+ } else {
+ ComboBox_Init(n)();
+ }
+}
+function ComboBox_Init(n) {
+ return function () {
+ if ( ComboBox_UplevelBrowser( n ) ) {
+ ComboBox_Load( n );
+ }
+ }
+}
+function ComboBox_UplevelBrowser( n ) {
+ if( typeof( document.getElementById ) == "undefined" ) return false;
+ var combo = document.getElementById( n + "_Container" );
+ if( combo == null || typeof( combo ) == "undefined" ) return false;
+ if( typeof( combo.style ) == "undefined" ) return false;
+ if( typeof( combo.innerHTML ) == "undefined" ) return false;
+ return true;
+}
+function ComboBox_Load( comboId ) {
+ var combo = document.getElementById( comboId + "_Container" );
+ var button = document.getElementById( comboId + "_Button" );
+ var list = document.getElementById( comboId + "_List" );
+ var text = document.getElementById( comboId );
+
+
+ combo.List = list;
+ combo.Button = button;
+ combo.Text = text;
+
+ button.Container = combo;
+ button.Toggle = ComboBox_ToggleList;
+ button.onclick = button.Toggle;
+ button.onmouseover = function(e) { this.Container.List.DisableBlur(e); };
+ button.onmouseout = function(e) { this.Container.List.EnableBlur(e); };
+ button.innerHTML = "\u25BC";
+ button.onselectstart = function(e){ return false; };
+ button.style.height = ( list.offsetHeight - 4 ) + "px";
+
+ text.Container = combo;
+ text.TypeDown = ComboBox_TextTypeDown;
+ text.KeyAccess = ComboBox_TextKeyAccess;
+ text.onkeyup = function(e) { this.KeyAccess(e); this.TypeDown(e); };
+ text.style.width = ( list.offsetWidth ) + "px";
+
+ list.Container = combo;
+ list.Show = ComboBox_ShowList;
+ list.Hide = ComboBox_HideList;
+ list.EnableBlur = ComboBox_ListEnableBlur;
+ list.DisableBlur = ComboBox_ListDisableBlur;
+ list.Select = ComboBox_ListItemSelect;
+ list.ClearSelection = ComboBox_ListClearSelection;
+ list.KeyAccess = ComboBox_ListKeyAccess;
+ list.FireTextChange = ComboBox_ListFireTextChange;
+ list.onchange = null;
+ list.onclick = function(e){ this.Select(e); this.ClearSelection(); this.FireTextChange(); };
+ list.onkeyup = function(e) { this.KeyAccess(e); };
+ list.EnableBlur(null);
+ list.style.position = "absolute";
+ list.size = ComboBox_GetListSize( list );
+ list.IsShowing = true;
+ list.Hide();
+
+}
+function ComboBox_InitEvent( e ) {
+ if( typeof( e ) == "undefined" && typeof( window.event ) != "undefined" ) e = window.event;
+ if( e == null ) e = new Object();
+ return e;
+}
+function ComboBox_ListClearSelection() {
+ if ( typeof( this.Container.Text.createTextRange ) == "undefined" ) return;
+ var rNew = this.Container.Text.createTextRange();
+ rNew.moveStart('character', this.Container.Text.value.length) ;
+ rNew.select();
+}
+function ComboBox_GetListSize( theList ) {
+ ComboBox_EnsureListSize( theList );
+ return theList.listSize;
+}
+function ComboBox_EnsureListSize( theList ) {
+ if ( typeof( theList.listSize ) == "undefined" ) {
+ if( typeof( theList.getAttribute ) != "undefined" ) {
+ if( theList.getAttribute( "listSize" ) != null && theList.getAttribute( "listSize" ) != "" ) {
+ theList.listSize = theList.getAttribute( "listSize" );
+ return;
+ }
+ }
+ if( theList.options.length > 0 ) {
+ theList.listSize = theList.options.length;
+ return;
+ }
+ theList.listSize = 4;
+ }
+}
+function ComboBox_ListKeyAccess(e) { //Make enter/space and escape do the right thing :)
+ e = ComboBox_InitEvent( e );
+ if( e.keyCode == 13 || e.keyCode == 32 ) {
+ this.Select();
+ return;
+ }
+ if( e.keyCode == 27 ) {
+ this.Hide();
+ this.Container.Text.focus();
+ return;
+ }
+}
+function ComboBox_TextKeyAccess(e) { //Make alt+arrow expand the list
+ e = ComboBox_InitEvent( e );
+ if( e.altKey && (e.keyCode == 38 || e.keyCode == 40) ) {
+ this.Container.List.Show();
+ }
+}
+function ComboBox_TextTypeDown(e) { //Make the textbox do a type-down on the list
+ e = ComboBox_InitEvent( e );
+ var items = this.Container.List.options;
+ if( this.value == "" ) return;
+ var ctrlKeys = Array( 8, 46, 37, 38, 39, 40, 33, 34, 35, 36, 45, 16, 20 );
+ for( var i = 0; i < ctrlKeys.length; i++ ) {
+ if( e.keyCode == ctrlKeys[i] ) return;
+ }
+ for( var i = 0; i < items.length; i++ ) {
+ var item = items[i];
+ if( item.text.toLowerCase().indexOf( this.value.toLowerCase() ) == 0 ) {
+ this.Container.List.selectedIndex = i;
+ if ( typeof( this.Container.Text.createTextRange ) != "undefined" ) {
+ this.Container.List.Select();
+ }
+ break;
+ }
+ }
+}
+function ComboBox_ListFireTextChange() {
+ var textOnChange = this.Container.Text.onchange;
+ if ( textOnChange != null && typeof(textOnChange) == "function" ) {
+ textOnChange();
+ }
+}
+function ComboBox_ListEnableBlur(e) {
+ this.onblur = this.Hide;
+}
+function ComboBox_ListDisableBlur(e) {
+ this.onblur = null;
+}
+function ComboBox_ListItemSelect(e) {
+ if( this.options.length > 0 ) {
+ var text = this.Container.Text;
+ var oldValue = text.value;
+ var newValue = this.options[ this.selectedIndex ].value;
+ text.value = newValue;
+ if ( typeof( text.createTextRange ) != "undefined" ) {
+ if (newValue != oldValue) {
+ var rNew = text.createTextRange();
+ rNew.moveStart('character', oldValue.length) ;
+ rNew.select();
+ }
+ }
+ }
+ this.Hide();
+ this.Container.Text.focus();
+}
+function ComboBox_ToggleList(e) {
+ if( this.Container.List.IsShowing == true ) {
+ this.Container.List.Hide();
+ } else {
+ this.Container.List.Show();
+ }
+}
+function ComboBox_ShowList(e) {
+ if ( !this.IsShowing && !this.disabled ) {
+ this.style.width = ( this.Container.offsetWidth ) + "px";
+ this.style.top = ( this.Container.offsetHeight + ComboBox_RecursiveOffsetTop(this.Container,true) ) + "px";
+ this.style.left = ( ComboBox_RecursiveOffsetLeft(this.Container,true) + 1 ) + "px";
+ ComboBox_SetVisibility(this,true);
+ this.focus();
+ this.IsShowing = true;
+ }
+}
+function ComboBox_HideList(e) {
+ if( this.IsShowing ) {
+ ComboBox_SetVisibility(this,false);
+ this.IsShowing = false;
+ }
+}
+function ComboBox_SetVisibility(theList,isVisible) {
+ var isIE = ( typeof( theList.dataSrc ) != "undefined" ); // dataSrc is an IE-only property which is unlikely to be supported elsewhere
+ var ua = navigator.userAgent.toLowerCase();
+ var isSafari = (ua.indexOf('safari') != - 1);
+ if ( isIE || isSafari) {
+ if ( isVisible ) {
+ theList.style.visibility = "visible";
+ } else {
+ theList.style.visibility = "hidden";
+ }
+ } else {
+ if ( isVisible ) {
+ theList.style.display = "block";
+ } else {
+ theList.style.display = "none";
+ }
+ }
+}
+function ComboBox_RecursiveOffsetTop(thisObject,isFirst) {
+ if(thisObject.offsetParent) {
+ if ( thisObject.style.position == "absolute" && !isFirst && typeof(document.designMode) != "undefined" ) {
+ return 0;
+ }
+ return (thisObject.offsetTop + ComboBox_RecursiveOffsetTop(thisObject.offsetParent,false));
+ } else {
+ return thisObject.offsetTop;
+ }
+}
+function ComboBox_RecursiveOffsetLeft(thisObject,isFirst) {
+ if(thisObject.offsetParent) {
+ if ( thisObject.style.position == "absolute" && !isFirst && typeof(document.designMode) != "undefined" ) {
+ return 0;
+ }
+ return (thisObject.offsetLeft + ComboBox_RecursiveOffsetLeft(thisObject.offsetParent,false));
+ } else {
+ return thisObject.offsetLeft;
+ }
+}
+function ComboBox_SimpleAttach(selectElement,textElement) {
+ textElement.value = selectElement.options[ selectElement.options.selectedIndex ].value;
+ var textOnChange = textElement.onchange;
+ if ( textOnChange != null && typeof( textOnChange ) == "function" ) {
+ textOnChange();
+ }
+}
diff --git a/web/static/js/dom-drag.js b/web/static/js/dom-drag.js
new file mode 100644
index 0000000..9fb7118
--- /dev/null
+++ b/web/static/js/dom-drag.js
@@ -0,0 +1,9 @@
+/**************************************************
+ * dom-drag.js
+ * 09.25.2001
+ * www.youngpup.net
+ **************************************************
+ * 10.28.2001 - fixed minor bug where events
+ * sometimes fired off the handle, not the root.
+ **************************************************/
+var Drag = { obj : null, init : function(o, oRoot, minX, maxX, minY, maxY, bSwapHorzRef, bSwapVertRef, fXMapper, fYMapper){ o.onmousedown = Drag.start; o.hmode = bSwapHorzRef ? false : true ; o.vmode = bSwapVertRef ? false : true ; o.root = oRoot && oRoot != null ? oRoot : o ; if (o.hmode && isNaN(parseInt(o.root.style.left ))) o.root.style.left = "0px"; if (o.vmode && isNaN(parseInt(o.root.style.top ))) o.root.style.top = "0px"; if (!o.hmode && isNaN(parseInt(o.root.style.right ))) o.root.style.right = "0px"; if (!o.vmode && isNaN(parseInt(o.root.style.bottom))) o.root.style.bottom = "0px"; o.minX = typeof minX != 'undefined' ? minX : null; o.minY = typeof minY != 'undefined' ? minY : null; o.maxX = typeof maxX != 'undefined' ? maxX : null; o.maxY = typeof maxY != 'undefined' ? maxY : null; o.xMapper = fXMapper ? fXMapper : null; o.yMapper = fYMapper ? fYMapper : null; o.root.onDragStart = new Function(); o.root.onDragEnd = new Function(); o.root.onDrag = new Function();},
start : function(e){ var o = Drag.obj = this; e = Drag.fixE(e); var y = parseInt(o.vmode ? o.root.style.top : o.root.style.bottom); var x = parseInt(o.hmode ? o.root.style.left : o.root.style.right ); o.root.onDragStart(x, y); o.lastMouseX = e.clientX; o.lastMouseY = e.clientY; if (o.hmode) { if (o.minX != null) o.minMouseX = e.clientX - x + o.minX; if (o.maxX != null) o.maxMouseX = o.minMouseX + o.maxX - o.minX;} else { if (o.minX != null) o.maxMouseX = -o.minX + e.clientX + x; if (o.maxX != null) o.minMouseX = -o.maxX + e.clientX + x;} if (o.vmode) { if (o.minY != null) o.minMouseY = e.clientY - y + o.minY; if (o.maxY != null) o.maxMouseY = o.minMouseY + o.maxY - o.minY;} else { if (o.minY != null) o.maxMouseY = -o.minY + e.clientY + y; if (o.maxY != null) o.minMouseY = -o.maxY + e.clientY + y;} document.onmousemove = Drag.drag; document.onmouseup = Drag.end; return false;}, drag : function(e){ e = Drag.fixE(e); var o = Drag.obj; var ey = e.clientY; var ex = e.clientX; var
y = parseInt(o.vmode ? o.root.style.top : o.root.style.bottom); var x = parseInt(o.hmode ? o.root.style.left : o.root.style.right ); var nx, ny; if (o.minX != null) ex = o.hmode ? Math.max(ex, o.minMouseX) : Math.min(ex, o.maxMouseX); if (o.maxX != null) ex = o.hmode ? Math.min(ex, o.maxMouseX) : Math.max(ex, o.minMouseX); if (o.minY != null) ey = o.vmode ? Math.max(ey, o.minMouseY) : Math.min(ey, o.maxMouseY); if (o.maxY != null) ey = o.vmode ? Math.min(ey, o.maxMouseY) : Math.max(ey, o.minMouseY); nx = x + ((ex - o.lastMouseX) * (o.hmode ? 1 : -1)); ny = y + ((ey - o.lastMouseY) * (o.vmode ? 1 : -1)); if(o.xMapper){ nx = o.xMapper(y) } else if (o.yMapper) { ny = o.yMapper(x); } Drag.obj.root.style[o.hmode ? "left" : "right"] = nx + "px"; Drag.obj.root.style[o.vmode ? "top" : "bottom"] = ny + "px"; Drag.obj.lastMouseX = ex; Drag.obj.lastMouseY = ey; Drag.obj.root.onDrag(nx, ny); return false;}, end : function(){ document.onmousemove = null; document.onmouseup = null; Drag.
obj.root.onDragEnd( parseInt(Drag.obj.root.style[Drag.obj.hmode ? "left" : "right"]), parseInt(Drag.obj.root.style[Drag.obj.vmode ? "top" : "bottom"])); Drag.obj = null;}, fixE : function(e){ if (typeof e == 'undefined') e = window.event; if (typeof e.layerX == 'undefined') e.layerX = e.offsetX; if (typeof e.layerY == 'undefined') e.layerY = e.offsetY; return e;} };
\ No newline at end of file
diff --git a/web/static/js/ie7/README.txt b/web/static/js/ie7/README.txt
new file mode 100644
index 0000000..e546ce4
--- /dev/null
+++ b/web/static/js/ie7/README.txt
@@ -0,0 +1,34 @@
+Installation
+------------
+
+Follow these simple instructions to get IE7 working immediately on your server:
+
+ * download the latest IE7 ZIP file (https://sourceforge.net/project/showfiles.php?group_id=109983&package_id=119707)
+
+ * extract the contents to a directory on your server (keep the folder names used in the ZIP)
+
+ * you will now have an IE7 directory on your server
+
+ * include the IE7 JavaScript library in the page you wish to test
+
+ <!-- compliance patch for microsoft browsers -->
+ <!--[if lt IE 7]><script src="/ie7/ie7-standard-p.js" type="text/javascript"></script><![endif]-->
+
+ * make sure this also points to the same directory
+
+ * open the page in your web browser
+
+ * the page should now be IE7 enabled.
+
+ * if you are using the PNG solution then be aware that it operates on files
+ names "something-trans.png"
+
+ * see this page for more configuration and usage options:
+ http://dean.edwards.name/IE7/usage/
+
+You may extract the contents of the ZIP file to your hard disk if you do not have access to a web server.
+
+
+Enjoy ;-)
+
+Dean Edwards, 23rd May 2005
diff --git a/web/static/js/ie7/blank.gif b/web/static/js/ie7/blank.gif
new file mode 100644
index 0000000..a4fe2e6
Binary files /dev/null and b/web/static/js/ie7/blank.gif differ
diff --git a/web/static/js/ie7/ie7-base64.php b/web/static/js/ie7/ie7-base64.php
new file mode 100644
index 0000000..530392d
--- /dev/null
+++ b/web/static/js/ie7/ie7-base64.php
@@ -0,0 +1,7 @@
+<?php
+$data = split(";", $_SERVER["REDIRECT_QUERY_STRING"]);
+$type = $data[0];
+$data = split(",", $data[1]);
+header("Content-type: ".$type);
+echo base64_decode($data[1]);
+?>
\ No newline at end of file
diff --git a/web/static/js/ie7/ie7-content.htc b/web/static/js/ie7/ie7-content.htc
new file mode 100644
index 0000000..cc480cb
--- /dev/null
+++ b/web/static/js/ie7/ie7-content.htc
@@ -0,0 +1,14 @@
+<html>
+<!--
+ IE7, version 0.9 (alpha) (2005-08-19)
+ Copyright: 2004-2005, Dean Edwards (http://dean.edwards.name/)
+ License: http://creativecommons.org/licenses/LGPL/2.1/
+-->
+<head>
+<object id="dummy" width="0" height="0"></object>
+<base id="base">
+<style type="text/css">html,body,img{margin:0;}img{vertical-align:top}#dummy{display:inline}</style>
+<script type="text/javascript">public_description=new function(){var l=false;this.ie7_anon=true;this.load=function(o,c,u){if(l)return;l=true;base.href=o.document.URL;dummy.style.cssText=c;var _0=o.parentElement;var _1=Boolean(dummy.currentStyle.display=="inline");function r(){o.runtimeStyle.width=(_1)?image.offsetWidth:"100%";o.runtimeStyle.height=body.offsetHeight};image.onreadystatechange=function(){if(this.readyState=="complete")_2()};image.src=u;function _2(){function copy(p){try{body.style[p]=_0.currentStyle[p]}catch(i){}};for(var j in body.currentStyle)copy(j);body.style.width="";body.style.height="";body.style.border="none";body.style.padding="0";body.style.margin="0";body.style.textIndent="";body.style.position="static";while(_0&&_0.currentStyle.backgroundColor=="transparent"){_0=_0.parentElement}if(_0)document.body.style.backgroundColor=_0.currentStyle.backgroundColor;body.runtimeStyle.cssText=c;body.runtimeStyle.margin="0";if(_1)body.runtimeStyle.width="";r()}}};</
script>
+</head>
+<body><span id="body"><img id="image"></span></body>
+</html>
diff --git a/web/static/js/ie7/ie7-core.js b/web/static/js/ie7/ie7-core.js
new file mode 100644
index 0000000..c3dbcef
--- /dev/null
+++ b/web/static/js/ie7/ie7-core.js
@@ -0,0 +1,6 @@
+/*
+ IE7, version 0.9 (alpha) (2005-08-19)
+ Copyright: 2004-2005, Dean Edwards (http://dean.edwards.name/)
+ License: http://creativecommons.org/licenses/LGPL/2.1/
+*/
+eval(function(p,a,c,k,e,d){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)d[e(c)]=k[c]||e(c);k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('x(!1M.1j)z 6(){1l{1M.1j=8;4 1W=8.2m=z 26;8.O=6(){7"1j 2q 0.9 (5G)"};4 36=/36/.B(42.41.40);4 1G=(36)?6(m){1M.1G(1j+"\\n\\n"+m)}:1W;4 2t=5F.2t.1g(/5E (\\d\\.\\d)/)[1];4 2z=K.5D!="5C";x(/5B/.B(42.41.40)||2t<5||!/^5A/.B(K.1J.2A))7;4 1H=K.39=="1H";4 1t,5z;4 1J=K.1J,2s,3X,17=K.17;4 5y="!";4 22={};4 1u=1C;1j.2m=6(n,s){x(!22[n]){x(1u)1k("s="+2o(s));22[n]=z s()}};4 R=/^[\\w\\.]+[^:]*$/;6 1I(h,p){x(R.B(h))h=(p||"")+h;7 h};6 2e(h,p){h=1I(h,p);7 h.1d(0,h.3n("/")+1)};4 s=K.3Z[K.3Z.y-1];1l{1k(s.3z)}1i(i){}4 1R=2e(s.5x);4 1F;1l{4 l=(5w()>=5)?"5v":"5u";1F=z 5t(l+".5s")}1i(i){}4 2w={};6 2y(h,p){1l{h=1I(h,p);x(!2w[h]){1F.5r("5q",h,1C);1F.5p();x(1F.3Y==0||1F.3Y==5o){2w[h]=1
F.5n}}}1i(i){1G("2x [1]: 30 5m 5l "+h)}37{7 2w[h]||""}};4 5k=1I("5j.5i",1R);6 1E(V){x(V!=1L){V.2v=13.16.2v;V.12=13.16.12}7 V};1E.12=6(p,c){x(!p)p={};x(!c)c=p.J;x(c=={}.J)c=z 26("8.2v()");c.Y=z 26("7 8");c.Y.16=z 8.Y;c.Y.16.12(p);c.16=z c.Y;c.Y.16.J=c.16.J=c;c.1r=8;c.12=F.32;c.2u=8.2u;7 c};1E.Y=z 26("7 8");1E.Y.16={J:1E,2v:6(){7 F.32.5h.1r.2k(8,F)},12:6(V){x(8==8.J.16&&8.J.12){7 8.J.Y.16.12(V)}D(4 i 5g V){2K(i){1o"J":1o"O":1o"Y":2X}x(2V V[i]=="6"&&V[i]!=8[i]){V[i].1r=8[i]}8[i]=V[i]}x(V.O!=8.O&&V.O!={}.O){V.O.1r=8.O;8.O=V.O}7 8}};6 13(){};8.13=1E.12({J:13,O:6(){7"[5f "+(8.J.2Z||"5e")+"]"},5d:6(1h){7 8.J==1h||1h.2u(8.J)}});13.2Z="13";13.1r=1L;13.2u=6(1h){1f(1h&&1h.1r!=8)1h=1h.1r;7 3J(1h)};13.Y.1r=1E;2a 8.13;4 3A=13.12({J:6(){8.5c=[];8.1p=[]},1s:1W});x(2t<5.5)1k(2y("Z-5b.3a",1R));4 35=1C;1j.1s=6(){1l{x(35)7;35=1H=1c;2s=K.2s;3X=(2z)?2s:1J;x(1K&&1t)1t.2k();15.2k();1n();1G("1u 5a")}1i(e){1G("2x [2]: "+e.38)}};4 1p=[];6 2C(r){1p.11(r)};6 1n(){H.3P();x(1K&&1t)1t.1n();15.1n();D(4 i=0;
i<1p.y;i++)1p[i]()};6 23(){4 E=0,R=1,L=2;4 G=/\\(/g,S=/\\$\\d/,I=/^\\$\\d+$/,T=/([\'"])\\1\\+(.*)\\+\\1\\1$/,3Q=/\\\\./g,Q=/\'/,3W=/\\25[^\\25]*\\25/g;4 1X=8;8.18=6(e,r){x(!r)r="";4 l=(34(2o(e)).1g(G)||"").y+1;x(S.B(r)){x(I.B(r)){r=3e(r.1d(1))-1}1b{4 i=l;4 q=Q.B(34(r))?\'"\':"\'";1f(i)r=r.2S("$"+i--).2p(q+"+a[o+"+i+"]+"+q);r=z 26("a,o","7"+q+r.19(T,"$1")+q)}}3V(e||"/^$/",r,l)};8.1U=6(s){24.y=0;7 3R(3S(s,8.2r).19(z 1Z(1D,8.33?"2I":"g"),3T),8.2r).19(3W,"")};8.59=6(){1D.y=0};4 24=[];4 1D=[];4 3U=6(){7"("+2o(8[E]).1d(1,-1)+")"};1D.O=6(){7 8.2p("|")};6 3V(){F.O=3U;1D[1D.y]=F}6 3T(){x(!F[0])7"";4 i=1,j=0,p;1f(p=1D[j++]){x(F[i]){4 r=p[R];2K(2V r){1o"6":7 r(F,i);1o"58":7 F[r+i]}4 d=(F[i].57(1X.2r)==-1)?"":"\\25"+F[i]+"\\25";7 d+r}1b i+=p[L]}};6 3S(s,e){7 e?s.19(z 1Z("\\\\"+e+"(.)","g"),6(m,c){24[24.y]=c;7 e}):s};6 3R(s,e){4 i=0;7 e?s.19(z 1Z("\\\\"+e,"g"),6(){7 e+(24[i++]||"")}):s};6 34(s){7 s.19(3Q,"")}};23.16={J:23,33:1C,2r:""};13.12(23.16);4 1V=23.12({33:1c});4 H=6(){4 2q="2.0.2"
;4 C=/\\s*,\\s*/;4 H=6(s,14){1l{4 m=[];4 u=F.32.2Q&&!14;4 b=(14)?(14.J==3G)?14:[14]:[K];4 31=3D(s).2S(C),i;D(i=0;i<31.y;i++){s=2R(31[i]);x(3K&&s.1d(0,3).2p("")==" *#"){s=s.1d(2);14=3H([],b,s[1])}1b 14=b;4 j=0,t,f,a,c="";1f(j<s.y){t=s[j++];f=s[j++];c+=t+f;a="";x(s[j]=="("){1f(s[j++]!=")"&&j<s.y){a+=s[j]}a=a.1d(0,-1);c+="("+a+")"}14=(u&&1P[c])?1P[c]:3F(14,t,f,a);x(u)1P[c]=14}m=m.3t(14)}2a H.30;7 m}1i(e){H.30=e;7[]}};H.O=6(){7"6 H() {\\n [2q "+2q+"]\\n}"};4 1P={};H.2Q=1C;H.3P=6(s){x(s){s=2R(s).2p("");2a 1P[s]}1b 1P={}};4 22={};4 1u=1C;H.2m=6(n,s){x(1u)1k("s="+2o(s));22[n]=z s()};H.Y=6(c){7 c?1k(c):8};4 1B={};4 2n={};4 56={1g:/\\[([\\w-]+(\\|[\\w-]+)?)\\s*(\\W?=)?\\s*([^\\]]*)\\]/};4 55=[];1B[" "]=6(r,f,t,n){4 e,i,j;D(i=0;i<f.y;i++){4 s=2l(f[i],t,n);D(j=0;(e=s[j]);j++){x(1q(e)&&2T(e,n))r.11(e)}}};1B["#"]=6(r,f,i){4 e,j;D(j=0;(e=f[j]);j++)x(e.1a==i)r.11(e)};1B["."]=6(r,f,c){c=z 1Z("(^|\\\\s)"+c+"(\\\\s|$)");4 e,i;D(i=0;(e=f[i]);i++)x(c.B(e.2Z))r.11(e)};1B[":"]=6(r,f,p,a){4 t=2n[
p],e,i;x(t)D(i=0;(e=f[i]);i++)x(t(e,a))r.11(e)};2n["21"]=6(e){4 d=2U(e);x(d.2Y)D(4 i=0;i<d.2Y.y;i++){x(d.2Y[i]==e)7 1c}};2n["2N"]=6(e){};4 1q=6(e){7(e&&e.3B==1&&e.2P!="!")?e:1L};4 3N=6(e){1f(e&&(e=e.54)&&!1q(e))2X;7 e};4 2W=6(e){1f(e&&(e=e.53)&&!1q(e))2X;7 e};4 3L=6(e){7 1q(e.3O)||2W(e.3O)};4 52=6(e){7 1q(e.3M)||3N(e.3M)};4 51=6(e){4 c=[];e=3L(e);1f(e){c.11(e);e=2W(e)}7 c};4 3K=1c;4 2O=6(e){4 d=2U(e);7(2V d.3I=="50")?/\\.4Z$/i.B(d.4Y):3J(d.3I=="4X 4W")};4 2U=6(e){7 e.4V||e.K};4 2l=6(e,t){7(t=="*"&&e.1A)?e.1A:e.2l(t)};4 4U=6(e,t,n){x(t=="*")7 1q(e);x(!2T(e,n))7 1C;x(!2O(e))t=t.4T();7 e.2P==t};4 2T=6(e,n){7!n||(n=="*")||(e.4S==n)};4 4R=6(e){7 e.4Q};6 3H(r,f,1a){4 m,i,j;D(i=0;i<f.y;i++){x(m=f[i].1A.4P(1a)){x(m.1a==1a)r.11(m);1b x(m.y!=1L){D(j=0;j<m.y;j++){x(m[j].1a==1a)r.11(m[j])}}}}7 r};x(![].11)3G.16.11=6(){D(4 i=0;i<F.y;i++){8[8.y]=F[i]}7 8.y};4 N=/\\|/;6 3F(14,t,f,a){x(N.B(f)){f=f.2S(N);a=f[0];f=f[1]}4 r=[];x(1B[t]){1B[t](r,14,f,a)}7 r};4 S=/^[^\\s>+~]/;4 3E=/[\\s#.:>+~()@]
|[^\\s#.:>+~()@]+/g;6 2R(s){x(S.B(s))s=" "+s;7 s.1g(3E)||[]};4 W=/\\s*([\\s>+~(),]|^|$)\\s*/g;4 I=/([\\s>+~,]|[^(]\\+|^)([#.:@])/g;4 3D=6(s){7 s.19(W,"$1").19(I,"$1*$2")};4 1y={O:6(){7"\'"},1g:/^(\'[^\']*\')|("[^"]*")$/,B:6(s){7 8.1g.B(s)},18:6(s){7 8.B(s)?s:8+s+8},3C:6(s){7 8.B(s)?s.1d(1,-1):s}};4 1N=6(t){7 1y.3C(t)};4 E=/([\\/()[\\]?{}|*+-])/g;6 4O(s){7 s.19(E,"\\\\$1")};1u=1c;7 H}();H.2Q=1c;H.2m("Z",6(){1q=6(e){7(e&&e.3B==1&&e.2P!="!"&&!e.3d)?e:1L}});H.Y("1N=F[1]",3k);4 1K=!H.Y("2O(F[1])",1J);4 2h=":21{Z-21:21}:2N{Z-21:2N}"+(1K?"":"*{4N:0}");4 15=z(3A.12({2F:z 1V,1O:"",1w:"",2L:[],1s:6(){8.2M();8.2g()},2g:6(){15.1Y.X=2h+8.1O+8.1w},3y:6(){4 20=K.2l("1e"),s;D(4 i=20.y-1;(s=20[i]);i--){x(!s.2H&&!s.Z){8.2L.11(s.3z)}}},2k:6(){8.3y();8.2g();z 28("1O");8.3u()},3w:6(e,r){8.2F.18(e,r)},1n:6(){4 R=/3v\\d+/g;4 s=2h.1g(/[{,]/g).y;4 20=s+(8.1O.X.1g(/\\{/g)||"").y;4 3x=8.1Y.4M,r;4 2j,c,2i,e,i,j,k,1a;D(i=s;i<20;i++){r=3x[i];x(r&&(2j=r.1e.X.1g(R))){2i=H(r.4L);x(2i.y)D(j=0;j<2j.y;j++){1a=
2j[j];c=15.1p[1a.1d(10)][2];D(k=0;(e=2i[k]);k++){x(e.1v[1a])c(e)}}}}},2C:6(p,t,h,r){t=z 1Z("([{;\\\\s])"+p+"\\\\s*:\\\\s*"+t+"[^;}]*");4 i=8.1p.y;x(r)r=p+":"+r;8.3w(t,6(m,o){7(r?m[o+1]+r:m[o])+";Z-"+m[o].1d(1)+";3v"+i+":1"});8.1p.11(F);7 i},1N:6(s){7 s.X||""},2M:6(){x(1H||!1K)K.2M();1b K.4K("<1e Z=1c></1e>");8.1Y=17[17.y-1];8.1Y.Z=1c;8.1Y.X=2h},3u:6(){D(4 i=0;i<17.y;i++){x(!17[i].Z&&17[i].X){17[i].X=""}}}}));6 28(m){8.1z=m;8.1S();15[m]=8;15.2g()};13.12({J:28,O:6(){7"@1z "+8.1z+"{"+8.X+"}"},1n:1W,1S:6(){8.X="";8.1N();8.3m();8.X=3j(8.X);f={}},1N:6(){4 3r=[].3t(15.2L);4 M=/@1z\\s+([^{]*)\\{([^@]+\\})\\s*\\}/2I;4 A=/\\4J\\b|^$/i,S=/\\4I\\b/i,P=/\\4H\\b/i;6 3q(c,m){2f.v=m;7 c.19(M,2f)};6 2f(4G,m,c){m=2J(m);2K(m){1o"1O":1o"1w":x(m!=2f.v)7"";1o"1A":7 c}7""};6 2J(m){x(A.B(m))7"1A";1b x(S.B(m))7(P.B(m))?"1A":"1O";1b x(P.B(m))7"1w"};4 1X=8;6 2G(s,p,m,l){4 c="";x(!l){m=2J(s.1z);l=0}x(m=="1A"||m==1X.1z){x(l<3){D(4 i=0;i<s.3s.y;i++){c+=2G(s.3s[i],2e(s.2d,p),m,l+1)}}c+=3l(s.2d?3p(s,p):3r.
3h()||"");c=3q(c,1X.1z)}7 c};4 f={};6 3p(s,p){4 u=1I(s.2d,p);x(f[u])7"";f[u]=(s.2H)?"":3o(15.1N(s,p),2e(s.2d,p));7 f[u]};4 U=/(4F\\s*\\(\\s*[\'"]?)([\\w\\.]+[^:\\)]*[\'"]?\\))/2I;6 3o(c,p){7 c.19(U,"$1"+p.1d(0,p.3n("/")+1)+"$2")};D(4 i=0;i<17.y;i++){x(!17[i].2H&&!17[i].Z){8.X+=2G(17[i])}}},3m:6(){8.X=15.2F.1U(8.X)},1n:1W});4 1y=H.Y("1y");4 2b=[];6 3l(c){7 1x.1U(2c.1U(c))};6 2E(m,o){7 1y+(2b.11(m[o])-1)+1y};6 3k(v){7 1y.B(v)?1k(2b[1k(v)]):v};4 1x=z 1V;1x.18(/\\/\\*[^*]*\\*+([^\\/][^*]*\\*+)*\\//);1x.18(/\'[^\']*\'/,2E);1x.18(/"[^"]*"/,2E);1x.18(/\\s+/," ");1x.18(/@(4E|4D)[^;\\n]+[;\\n]|<!\\-\\-|\\-\\->/);4 2c=z 1V;2c.18(/\\\\\'/,"\\\\4C");2c.18(/\\\\"/,"\\\\4B");4 2D=z 1V;2D.18(/\'(\\d+)\'/,3i);6 3j(c){7 2D.1U(c)};6 3i(m,o){7 2b[m[o+1]]};4 2B=[];6 4A(h){2C(h);1Q(1M,"4z",h)};6 1Q(e,t,h){e.4y(t,h);2B.11(F)};6 3g(e,t,h){1l{e.4x(t,h)}1i(i){}};1Q(1M,"4w",6(){4 h;1f(h=2B.3h()){3g(h[0],h[1],h[2])}});6 4v(h,e,c){x(!h.29)h.29={};x(c)h.29[e.2A]=e;1b 2a h.29[e.2A];7 c};1Q(1M,"4u",6(){x(
!15.1w)z 28("1w");15.1w.1n()});4 3f=/^\\d+(4t)?$/i;4 4s=/^\\d+%$/;4 4r=6(e,v){x(3f.B(v))7 3e(v);4 s=e.1e.1m;4 r=e.1T.1m;e.1T.1m=e.1v.1m;e.1e.1m=v||0;v=e.1e.4q;e.1e.1m=s;e.1T.1m=r;7 v};6 4p(t){4 e=K.4o(t||"4n");e.1e.X="3c:4m;4l:0;4k:4j;4i:4h;4g:4f(0 0 0 0);1m:-4e";e.3d=1c;7 e};4 27="Z-";6 4d(e){7 e.1v["Z-3c"]=="4c"};6 4b(e,p){7 e.1v[27+p]||e.1v[p]};6 4a(e,p,v){x(e.1v[27+p]==1L){e.1T[27+p]=e.1v[p]}e.1T[p]=v};6 49(o,c,u){4 t=48(6(){1l{x(!o.1S)7;o.1S(o,c,u);3b(t)}1i(i){3b(t)}},10)};1u=1c;x(2z)1k(2y("Z-47.3a",1R));15.1s();x(1K&&1t)1t.1s();x(1H)1j.1s();1b{1J.46(1I("Z-1S.45",1R));1Q(K,"44",6(){x(K.39=="1H")43(1j.1s,0)})}}1i(e){1G("2x [0]: "+e.38)}37{}};',62,353,'||||var||function|return|this|||||||||||||||||||||||||if|length|new||test||for||arguments||cssQuery||constructor|document||||toString|||||||that||cssText|valueOf|ie7||push|specialize|Common|fr|ie7CSS|prototype|styleSheets|add|replace|id|else|true|slice|style|while|match|klass|catch|IE7|eval|try|left|recalc|case|recalcs|this
Element|ancestor|init|ie7HTML|loaded|currentStyle|print|encoder|Quote|media|all|selectors|false|_0|ICommon|httpRequest|alert|complete|makePath|documentElement|isHTML|null|window|getText|screen|cache|addEventHandler|path|load|runtimeStyle|exec|Parser|DUMMY|self|styleSheet|RegExp|st|link|modules|ParseMaster|_1|x01|Function|_2|StyleSheet|elements|delete|_3|safeString|href|getPath|_4|refresh|HEADER|el|ca|apply|getElementsByTagName|addModule|pseudoClasses|String|join|version|escapeChar|body|appVersion|ancestorOf|inherit|_5|Error|loadFile|quirksMode|uniqueID|_6|addRecalc|decoder|_7|parser|_8|disabled|gi|_9|switch|styles|createStyleSheet|visited|isXML|tagName|caching|_10|split|compareNamespace|getDocument|typeof|nextElementSibling|continue|links|className|error|se|callee|ignoreCase|_11|_12|ie7_debug|finally|description|readyState|js|clearInterval|position|ie7_anon|parseInt|PIXEL|removeEventHandler|pop|_13|decode|getString|_14|parse|lastIndexOf|_15|_16|_17|_18|imports|concat|trash|i
e7_recalc|addFix|ru|getInlineStyles|innerHTML|Fix|nodeType|remove|parseSelector|ST|select|Array|_19|mimeType|Boolean|isMSIE|firstElementChild|lastChild|previousElementSibling|firstChild|clearCache|ES|_20|_21|_22|_23|_24|DE|viewport|status|scripts|search|location|top|setTimeout|onreadystatechange|htc|addBehavior|quirks|setInterval|addTimer|setOverrideStyle|getDefinedStyle|fixed|isFixed|9999|rect|clip|none|border|block|display|padding|absolute|object|createElement|createTempElement|pixelLeft|getPixelValue|PERCENT|px|onbeforeprint|register|onunload|detachEvent|attachEvent|onresize|addResize|x22|x27|import|namespace|url|ma|bprint|bscreen|ball|write|selectorText|rules|margin|regEscape|item|innerText|getTextContent|scopeName|toUpperCase|compareTagName|ownerDocument|Document|XML|URL|xml|unknown|childElements|lastElementChild|nextSibling|previousSibling|attributeSelectors|AttributeSelector|indexOf|number|reset|successfully|ie5|fixes|instanceOf|Object|common|in|caller|gif|blank|BLANK
_GIF|file|loading|responseText|200|send|GET|open|XMLHTTP|ActiveXObject|Microsoft|Msxml2|ScriptEngineMajorVersion|src|ANON|ie7Layout|ms_|ie7_off|CSS1Compat|compatMode|MSIE|navigator|alpha'.split('|'),0,{}))
diff --git a/web/static/js/ie7/ie7-css-strict.js b/web/static/js/ie7/ie7-css-strict.js
new file mode 100644
index 0000000..0c7e330
--- /dev/null
+++ b/web/static/js/ie7/ie7-css-strict.js
@@ -0,0 +1,6 @@
+/*
+ IE7, version 0.9 (alpha) (2005-08-19)
+ Copyright: 2004-2005, Dean Edwards (http://dean.edwards.name/)
+ License: http://creativecommons.org/licenses/LGPL/2.1/
+*/
+IE7.addModule("ie7-css-strict",function(){if(!modules["ie7-css2-selectors"])return;StyleSheet.prototype.specialize({parse:function(){this.inherit();var r=[].concat(this.rules);r.sort(ie7CSS.Rule.compare);this.cssText=r.join("\n")},createRule:function(s,c){var m;if(m=s.match(ie7CSS.PseudoElement.MATCH))return new ie7CSS.PseudoElement(m[1],m[2],c);else if(m=s.match(ie7CSS.DynamicRule.MATCH))return new ie7CSS.DynamicRule(s,m[1],m[2],m[3],c);else return new ie7CSS.Rule(s,c)}});ie7CSS.specialize({apply:function(){this.inherit();this.Rule.MATCH=/([^{}]+)(\{[^{}]*\})/g}});ie7CSS.Rule.compare=function(r1,r2){return r1.specificity-r2.specificity};var N=[],I=/#/g,C=/[.:\[]/g,T=/^\w|[\s>+~]\w/g;ie7CSS.Rule.score=function(s){return(s.match(I)||N).length*10000+(s.match(C)||N).length*100+(s.match(T)||N).length};ie7CSS.Rule.simple=function(){return""};ie7CSS.Rule.prototype.specialize({specificity:0,init:function(){this.specificity=ie7CSS.Rule.score(this.selector)}})});
diff --git a/web/static/js/ie7/ie7-css2-selectors.js b/web/static/js/ie7/ie7-css2-selectors.js
new file mode 100644
index 0000000..bb08da3
--- /dev/null
+++ b/web/static/js/ie7/ie7-css2-selectors.js
@@ -0,0 +1,6 @@
+/*
+ IE7, version 0.9 (alpha) (2005-08-19)
+ Copyright: 2004-2005, Dean Edwards (http://dean.edwards.name/)
+ License: http://creativecommons.org/licenses/LGPL/2.1/
+*/
+IE7.addModule("ie7-css2-selectors",function(){cssQuery.addModule("css-level2",function(){selectors[">"]=function(r,f,t,n){var e,i,j;for(i=0;i<f.length;i++){var s=childElements(f[i]);for(j=0;(e=s[j]);j++)if(compareTagName(e,t,n))r.push(e)}};selectors["+"]=function(r,f,t,n){for(var i=0;i<f.length;i++){var e=nextElementSibling(f[i]);if(e&&compareTagName(e,t,n))r.push(e)}};selectors["@"]=function(r,f,a){var t=attributeSelectors[a].test;var e,i;for(i=0;(e=f[i]);i++)if(t(e))r.push(e)};pseudoClasses["first-child"]=function(e){return!previousElementSibling(e)};pseudoClasses["lang"]=function(e,c){c=new RegExp("^"+c,"i");while(e&&!e.getAttribute("lang"))e=e.parentNode;return e&&c.test(e.getAttribute("lang"))};AttributeSelector.NS_IE=/\\:/g;AttributeSelector.PREFIX="@";AttributeSelector.tests={};AttributeSelector.replace=function(m,a,n,c,v){var k=this.PREFIX+m;if(!attributeSelectors[k]){a=this.create(a,c||"",v||"");attributeSelectors[k]=a;attributeSelectors.push(a)}return attributeSele
ctors[k].id};AttributeSelector.parse=function(s){s=s.replace(this.NS_IE,"|");var m;while(m=s.match(this.match)){var r=this.replace(m[0],m[1],m[2],m[3],m[4]);s=s.replace(this.match,r)}return s};AttributeSelector.create=function(p,t,v){var a={};a.id=this.PREFIX+attributeSelectors.length;a.name=p;t=this.tests[t];t=t?t(this.getAttribute(p),getText(v)):false;a.test=new Function("e","return "+t);return a};AttributeSelector.getAttribute=function(n){switch(n.toLowerCase()){case"id":return"e.id";case"class":return"e.className";case"for":return"e.htmlFor";case"href":if(isMSIE){return"String((e.outerHTML.match(/href=\\x22?([^\\s\\x22]*)\\x22?/)||[])[1]||'')"}}return"e.getAttribute('"+n.replace(N,":")+"')"};AttributeSelector.tests[""]=function(a){return a};AttributeSelector.tests["="]=function(a,v){return a+"=="+Quote.add(v)};AttributeSelector.tests["~="]=function(a,v){return"/(^| )"+regEscape(v)+"( |$)/.test("+a+")"};AttributeSelector.tests["|="]=function(a,v){return"/^"+regEscape(v)+"
(-|$)/.test("+a+")"};var _6=parseSelector;parseSelector=function(s){return _6(AttributeSelector.parse(s))}});var AttributeSelector=cssQuery.valueOf("AttributeSelector");var H=/a(#[\w-]+)?(\.[\w-]+)?:(hover|active)/i;var B1=/\s*\{\s*/,B2=/\s*\}\s*/,C=/\s*\,\s*/;var F=/(.*)(:first-(line|letter))/;StyleSheet.prototype.specialize({parse:function(){this.inherit();var o=ie7CSS.rules.length;var ru=this.cssText.split(B2),r;var se,c,i,j;for(i=0;i<ru.length;i++){r=ru[i].split(B1);se=r[0].split(C);c=r[1];for(j=0;j<se.length;j++){se[j]=c?this.createRule(se[j],c):""}ru[i]=se.join("\n")}this.cssText=ru.join("\n");this.rules=ie7CSS.rules.slice(o)},recalc:function(){var r,i;for(i=0;(r=this.rules[i]);i++)r.recalc()},createRule:function(s,c){if(ie7CSS.UNKNOWN.test(s)){var m;if(m=s.match(PseudoElement.MATCH)){return new PseudoElement(m[1],m[2],c)}else if(m=s.match(DynamicRule.MATCH)){if(!isHTML||!H.test(m)||DynamicRule.COMPLEX.test(m)){return new DynamicRule(s,m[1],m[2],m[3],c)}}else return ne
w Rule(s,c)}return s+" {"+c+"}"}});ie7CSS.specialize({rules:[],pseudoClasses:cssQuery.valueOf("pseudoClasses"),dynamicPseudoClasses:{},cache:cssQuery.valueOf("cache"),Rule:Rule,DynamicRule:DynamicRule,PseudoElement:PseudoElement,DynamicPseudoClass:DynamicPseudoClass,apply:function(){var p=this.pseudoClasses+"|before|after|"+this.dynamicPseudoClasses;p=p.replace(/(link|visited)\|/g,"");this.UNKNOWN=new RegExp("[>+~\[]|([:.])[\\w-()]+\\1|:("+p+")");var c="[^\\s(]+\\s*[+~]|@\\d+|:(";Rule.COMPLEX=new RegExp(c+p+")","g");DynamicRule.COMPLEX=new RegExp(c+this.pseudoClasses+")","g");DynamicRule.MATCH=new RegExp("(.*):("+this.dynamicPseudoClasses+")(.*)");PseudoElement.MATCH=/(.*):(before|after).*/;this.inherit()},recalc:function(){this.screen.recalc();this.inherit()},getText:function(s,p){return httpRequest?(loadFile(s.href,p)||s.cssText):this.inherit(s)},addEventHandler:function(e,t,h){addEventHandler(e,t,h)}});function Rule(s,c){this.id=ie7CSS.rules.length;this.className=Rule.PRE
FIX+this.id;s=(s).match(F)||s||"*";this.selector=s[1]||s;this.selectorText=Rule.simple(this.selector)+"."+this.className+(s[2]||"");this.cssText=c;this.MATCH=new RegExp("\\s"+this.className+"(\\s|$)","g");ie7CSS.rules.push(this);this.init()};Common.specialize({constructor:Rule,toString:function(){return this.selectorText+" {"+this.cssText+"}"},init:DUMMY,add:function(e){e.className+=" "+this.className},remove:function(e){e.className=e.className.replace(this.MATCH,"$1")},recalc:function(){var m=ie7CSS.cache[" *."+this.className]=cssQuery(this.selector);for(i=0;i<m.length;i++)this.add(m[i])}});Rule.PREFIX="ie7_class";Rule.CHILD=/>/g;Rule.simple=function(s){s=AttributeSelector.parse(s);return s.replace(this.COMPLEX,"").replace(this.CHILD," ")};function DynamicRule(s,a,d,t,c){this.attach=a||"*";this.dynamicPseudoClass=ie7CSS.dynamicPseudoClasses[d];this.target=t;this.inherit(s,c)};Rule.specialize({constructor:DynamicRule,recalc:function(){var m=cssQuery(this.attach);for(var i=0;
i<m.length;i++){var t=(this.target)?cssQuery(this.target,m[i]):[m[i]];if(t.length)this.dynamicPseudoClass.apply(m[i],t,this)}}});var A=/^attr/;var U=/^url\s*\(\s*([^)]*)\)$/;var M={before0:"beforeBegin",before1:"afterBegin",after0:"afterEnd",after1:"beforeEnd"};var _5=makePath("ie7-content.htc",path)+"?";HEADER+=".ie7_anon{display:none}";function PseudoElement(s,p,c){this.position=p;var co=c.match(PseudoElement.CONTENT),m,e;if(co){co=co[1];m=co.split(/\s+/);for(var i=0;(e=m[i]);i++){m[i]=A.test(e)?{attr:e.slice(5,-1)}:(e.charAt(0)=="'")?getString(e):decode(e)}co=m}this.content=co;this.inherit(s,decode(c))};Rule.specialize({constructor:PseudoElement,toString:function(){return"."+this.className+"{display:inline}"},init:function(){this.match=cssQuery(this.selector);for(var i=0;i<this.match.length;i++){var r=this.match[i].runtimeStyle;if(!r[this.position])r[this.position]={cssText:""};r[this.position].cssText+=";"+this.cssText;if(this.content!=null)r[this.position].content=this.
content}},recalc:function(){if(this.content==null)return;for(var i=0;i<this.match.length;i++){this.create(this.match[i])}},create:function(t){var g=t.runtimeStyle[this.position];if(g){var c=[].concat(g.content||"");for(var j=0;j<c.length;j++){if(typeof c[j]=="object"){c[j]=t.getAttribute(c[j].attr)}}c=c.join("");var u=c.match(U);var h=PseudoElement[u?"OBJECT":"ANON"].replace(/%1/,this.className);var cs=g.cssText.replace(/'/g,'"');var po=M[this.position+Number(t.canHaveChildren)];if(u){var p=document.createElement(h);t.insertAdjacentElement(po,p);p.data=_5;addTimer(p,cs,Quote.remove(u[1]))}else{h=h.replace(/%2/,cs).replace(/%3/,c);t.insertAdjacentHTML(po,h)}t.runtimeStyle[this.position]=null}}});PseudoElement.CONTENT=/content\s*:\s*([^;]*)(;|$)/;PseudoElement.OBJECT="<object class='ie7_anon %1' ie7_anon width=100% height=0 type=text/x-scriptlet>";PseudoElement.ANON="<ie7:! class='ie7_anon %1' ie7_anon style='%2'>%3</ie7:!>";function DynamicPseudoClass(n,a){this.name=n;this.ap
ply=a;this.instances={};ie7CSS.dynamicPseudoClasses[n]=this};Common.specialize({constructor:DynamicPseudoClass,register:function(i){var c=i[2];i.id=c.id+i[0].uniqueID;if(!this.instances[i.id]){var t=i[1],j;for(j=0;j<t.length;j++)c.add(t[j]);this.instances[i.id]=i}},unregister:function(i){if(this.instances[i.id]){var c=i[2];var t=i[1],j;for(j=0;j<t.length;j++)c.remove(t[j]);delete this.instances[i.id]}}});ie7CSS.pseudoClasses.toString=function(){var t=[],p;for(p in this){if(this[p].length>1)p+="\\([^)]*\\)";t.push(p)}return t.join("|")};ie7CSS.pseudoClasses["link"]=function(e){return e.currentStyle["ie7-link"]=="link"};ie7CSS.pseudoClasses["visited"]=function(e){return e.currentStyle["ie7-link"]=="visited"};var _4=(appVersion<5.5)?"onmouseover":"onmouseenter";var _3=(appVersion<5.5)?"onmouseout":"onmouseleave";ie7CSS.dynamicPseudoClasses.toString=ie7CSS.pseudoClasses.toString;var _0=new DynamicPseudoClass("hover",function(e){var i=arguments;ie7CSS.addEventHandler(e,_4,functio
n(){_0.register(i)});ie7CSS.addEventHandler(e,_3,function(){_0.unregister(i)})});var _1=new DynamicPseudoClass("focus",function(e){var i=arguments;ie7CSS.addEventHandler(e,"onfocus",function(){_1.unregister(i);_1.register(i)});ie7CSS.addEventHandler(e,"onblur",function(){_1.unregister(i)});if(e==document.activeElement){_1.register(i)}});var _2=new DynamicPseudoClass("active",function(e){var i=arguments;ie7CSS.addEventHandler(e,"onmousedown",function(){_2.register(i)})});addEventHandler(document,"onmouseup",function(){var i=_2.instances,j;for(j in i)_2.unregister(i[j]);i=_0.instances;for(j in i)if(!i[j][0].contains(event.srcElement))_0.unregister(i[j])});ICommon(AttributeSelector);AttributeSelector.specialize({getAttribute:function(n){switch(n.toLowerCase()){case"class":return"e.className.replace(/\\b\\s*ie7_class\\d+/g,'')";case"src":return"(e.pngSrc||e.src)"}return this.inherit(n)}});encoder.add(/::/,":");safeString.add(/\\([\da-fA-F]{1,4})/,function(m,o){m=m[o+1];return"\\
u"+"0000".slice(m.length)+m})});
diff --git a/web/static/js/ie7/ie7-css3-selectors.js b/web/static/js/ie7/ie7-css3-selectors.js
new file mode 100644
index 0000000..7337b82
--- /dev/null
+++ b/web/static/js/ie7/ie7-css3-selectors.js
@@ -0,0 +1,6 @@
+/*
+ IE7, version 0.9 (alpha) (2005-08-19)
+ Copyright: 2004-2005, Dean Edwards (http://dean.edwards.name/)
+ License: http://creativecommons.org/licenses/LGPL/2.1/
+*/
+IE7.addModule("ie7-css3-selectors",function(){cssQuery.addModule("css-level3",function(){selectors["~"]=function(r,f,t,n){var e,i;for(i=0;(e=f[i]);i++){while(e=nextElementSibling(e)){if(compareTagName(e,t,n))r.push(e)}}};pseudoClasses["contains"]=function(e,t){t=new RegExp(regEscape(getText(t)));return t.test(getTextContent(e))};pseudoClasses["root"]=function(e){return e==getDocument(e).documentElement};pseudoClasses["empty"]=function(e){var n,i;for(i=0;(n=e.childNodes[i]);i++){if(thisElement(n)||n.nodeType==3)return false}return true};pseudoClasses["last-child"]=function(e){return!nextElementSibling(e)};pseudoClasses["only-child"]=function(e){e=e.parentNode;return firstElementChild(e)==lastElementChild(e)};pseudoClasses["not"]=function(e,s){var n=cssQuery(s,getDocument(e));for(var i=0;i<n.length;i++){if(n[i]==e)return false}return true};pseudoClasses["nth-child"]=function(e,a){return nthChild(e,a,previousElementSibling)};pseudoClasses["nth-last-child"]=function(e,a){return
nthChild(e,a,nextElementSibling)};pseudoClasses["target"]=function(e){return e.id==location.hash.slice(1)};pseudoClasses["checked"]=function(e){return e.checked};pseudoClasses["enabled"]=function(e){return e.disabled===false};pseudoClasses["disabled"]=function(e){return e.disabled};pseudoClasses["indeterminate"]=function(e){return e.indeterminate};AttributeSelector.tests["^="]=function(a,v){return"/^"+regEscape(v)+"/.test("+a+")"};AttributeSelector.tests["$="]=function(a,v){return"/"+regEscape(v)+"$/.test("+a+")"};AttributeSelector.tests["*="]=function(a,v){return"/"+regEscape(v)+"/.test("+a+")"};function nthChild(e,a,t){switch(a){case"n":return true;case"even":a="2n";break;case"odd":a="2n+1"}var ch=childElements(e.parentNode);function _5(i){var i=(t==nextElementSibling)?ch.length-i:i-1;return ch[i]==e};if(!isNaN(a))return _5(a);a=a.split("n");var m=parseInt(a[0]);var s=parseInt(a[1]);if((isNaN(m)||m==1)&&s==0)return true;if(m==0&&!isNaN(s))return _5(s);if(isNaN(s))s=0;var c
=1;while(e=t(e))c++;if(isNaN(m)||m==1)return(t==nextElementSibling)?(c<=s):(s>=c);return(c%m)==s}});var firstElementChild=cssQuery.valueOf("firstElementChild");ie7CSS.pseudoClasses["root"]=function(e){return(e==viewport)||(!isHTML&&e==firstElementChild(body))};var _4=new ie7CSS.DynamicPseudoClass("checked",function(e){if(typeof e.checked!="boolean")return;var i=arguments;ie7CSS.addEventHandler(e,"onpropertychange",function(){if(event.propertyName=="checked"){if(e.checked)_4.register(i);else _4.unregister(i)}});if(e.checked)_4.register(i)});var _3=new ie7CSS.DynamicPseudoClass("enabled",function(e){if(typeof e.disabled!="boolean")return;var i=arguments;ie7CSS.addEventHandler(e,"onpropertychange",function(){if(event.propertyName=="disabled"){if(!e.isDisabled)_3.register(i);else _3.unregister(i)}});if(!e.isDisabled)_3.register(i)});var _2=new ie7CSS.DynamicPseudoClass("disabled",function(e){if(typeof e.disabled!="boolean")return;var i=arguments;ie7CSS.addEventHandler(e,"onprope
rtychange",function(){if(event.propertyName=="disabled"){if(e.isDisabled)_2.register(i);else _2.unregister(i)}});if(e.isDisabled)_2.register(i)});var _1=new ie7CSS.DynamicPseudoClass("indeterminate",function(e){if(typeof e.indeterminate!="boolean")return;var i=arguments;ie7CSS.addEventHandler(e,"onpropertychange",function(){if(event.propertyName=="indeterminate"){if(e.indeterminate)_1.register(i);else _1.unregister(i)}});ie7CSS.addEventHandler(e,"onclick",function(){_1.unregister(i)})});var _0=new ie7CSS.DynamicPseudoClass("target",function(e){var i=arguments;if(!e.tabIndex)e.tabIndex=0;ie7CSS.addEventHandler(document,"onpropertychange",function(){if(event.propertyName=="activeElement"){if(e.id==location.hash.slice(1))_0.register(i);else _0.unregister(i)}});if(e.id==location.hash.slice(1))_0.register(i)});decoder.add(/\|/,"\\:")});
diff --git a/web/static/js/ie7/ie7-dhtml.js b/web/static/js/ie7/ie7-dhtml.js
new file mode 100644
index 0000000..d768063
--- /dev/null
+++ b/web/static/js/ie7/ie7-dhtml.js
@@ -0,0 +1,57 @@
+/*
+ IE7, version 0.9 (alpha) (2005-08-19)
+ Copyright: 2004-2005, Dean Edwards (http://dean.edwards.name/)
+ License: http://creativecommons.org/licenses/LGPL/2.1/
+*/
+IE7.addModule("ie7-dhtml", function() {
+
+/* ---------------------------------------------------------------------
+ This module is still in development and should not be used.
+--------------------------------------------------------------------- */
+
+ie7CSS.specialize("recalc", function() {
+ this.inherit();
+ for (var i = 0; i < this.recalcs.length; i++) {
+ var $recalc = this.recalcs[i];
+ for (var j = 0; i < $recalc[3].length; i++) {
+ _addPropertyChangeHandler($recalc[3][j], _getPropertyName($recalc[2]), $recalc[1]);
+ }
+ }
+});
+
+// constants
+var _PATTERNS = {
+ width: "(width|paddingLeft|paddingRight|borderLeftWidth|borderRightWidth|borderLeftStyle|borderRightStyle)",
+ height: "(height|paddingTop|paddingBottom|borderTopHeight|borderBottomHeight|borderTopStyle|borderBottomStyle)"
+};
+var _PROPERTY_NAMES = {
+ width: "fixedWidth",
+ height: "fixedHeight",
+ right: "width",
+ bottom: "height"
+};
+var _DASH_LETTER = /-(\w)/g;
+var _PROPERTY_NAME = /\w+/;
+
+function _addPropertyChangeHandler($element, $propertyName, $fix) {
+ addEventHandler($element, "onpropertychange", function() {
+ if (_getPattern($propertyName).test(event.propertyName)) {
+ _reset($element, $propertyName);
+ $fix($element);
+ }
+ });
+};
+function _upper($match, $letter) {return $letter.toUpperCase()};
+function _getPropertyName($pattern) {
+ return String(String($pattern).toLowerCase().replace(_DASH_LETTER, _upper).match(_PROPERTY_NAME));
+};
+function _getPattern($propertyName) {
+ return eval("/^style." + (_PATTERNS[$propertyName] || $propertyName) + "$/");
+};
+function _reset($element, $propertyName) {
+ $element.runtimeStyle[$propertyName] = "";
+ $propertyName = _PROPERTY_NAMES[$propertyName]
+ if ($propertyName) $element.runtimeStyle[$propertyName] = "";
+};
+
+});
diff --git a/web/static/js/ie7/ie7-dynamic-attributes.js b/web/static/js/ie7/ie7-dynamic-attributes.js
new file mode 100644
index 0000000..e066911
--- /dev/null
+++ b/web/static/js/ie7/ie7-dynamic-attributes.js
@@ -0,0 +1,6 @@
+/*
+ IE7, version 0.9 (alpha) (2005-08-19)
+ Copyright: 2004-2005, Dean Edwards (http://dean.edwards.name/)
+ License: http://creativecommons.org/licenses/LGPL/2.1/
+*/
+IE7.addModule("ie7-dynamic-attributes",function(){if(!modules["ie7-css2-selectors"])return;var attributeSelectors=cssQuery.valueOf("attributeSelectors");var parseSelector=cssQuery.valueOf("parseSelector");function DynamicAttribute(s,a,d,t,c){this.attach=a||"*";parseSelector(d);this.dynamicAttribute=attributeSelectors["@"+d];this.target=t;this.inherit(s,c)};ie7CSS.Rule.specialize({constructor:DynamicAttribute,recalc:function(){var m=cssQuery(this.attach);for(var i=0;i<m.length;i++){var t=(this.target)?cssQuery(this.target,m[i]):[m[i]];if(t.length)this.apply(m[i],t)}},apply:function(e,t){var self=this;addEventHandler(e,"onpropertychange",function(){if(event.propertyName==self.dynamicAttribute.name)self.test(e,t)});this.test(e,t)},test:function(e,t){var a=this.dynamicAttribute.test(e)?"add":"remove";for(var i=0;(e=t[i]);i++)this[a](e)}});DynamicAttribute.MATCH=/(.*)(\[[^\]]*\])(.*)/;StyleSheet.prototype.specialize({createRule:function(s,c){var m;if(m=s.match(DynamicAttribute.MA
TCH)){return new DynamicAttribute(s,m[1],m[2],m[3],c)}else return this.inherit(s,c)}})});
diff --git a/web/static/js/ie7/ie7-fixed.js b/web/static/js/ie7/ie7-fixed.js
new file mode 100644
index 0000000..10d629f
--- /dev/null
+++ b/web/static/js/ie7/ie7-fixed.js
@@ -0,0 +1,6 @@
+/*
+ IE7, version 0.9 (alpha) (2005-08-19)
+ Copyright: 2004-2005, Dean Edwards (http://dean.edwards.name/)
+ License: http://creativecommons.org/licenses/LGPL/2.1/
+*/
+IE7.addModule("ie7-fixed",function(){ie7CSS.addRecalc("position","fixed",_6,"absolute");ie7CSS.addRecalc("background(-attachment)?","[^};]*fixed",_7);var _10=(quirksMode)?"body":"documentElement";var _8=function(){if(body.currentStyle.backgroundAttachment!="fixed"){if(body.currentStyle.backgroundImage=="none"){body.runtimeStyle.backgroundRepeat="no-repeat";body.runtimeStyle.backgroundImage="url("+BLANK_GIF+")"}body.runtimeStyle.backgroundAttachment="fixed"}_8=DUMMY};var _0=createTempElement("img");function _1(f){return _2.exec(String(f))};var _2=new ParseMaster;_2.add(/Left/,"Top");_2.add(/left/,"top");_2.add(/Width/,"Height");_2.add(/width/,"height");_2.add(/right/,"bottom");_2.add(/X/,"Y");function _3(e){return(e)?isFixed(e)||_3(e.parentElement):false};function setExpression(e,p,ex){setTimeout("document.all."+e.uniqueID+".runtimeStyle.setExpression('"+p+"','"+ex+"')",0)};function _7(e){if(register(_7,e,e.currentStyle.backgroundAttachment=="fixed"&&!e.contains(body))){_8();
backgroundLeft(e);backgroundTop(e);_9(e)}};function _9(e){_0.src=e.currentStyle.backgroundImage.slice(5,-2);var p=(e.canHaveChildren)?e:e.parentElement;p.appendChild(_0);setOffsetLeft(e);setOffsetTop(e);p.removeChild(_0)};function backgroundLeft(e){e.style.backgroundPositionX=e.currentStyle.backgroundPositionX;if(!_3(e)){var ex="(parseInt(runtimeStyle.offsetLeft)+document."+_10+".scrollLeft)||0";setExpression(e,"backgroundPositionX",ex)}};eval(_1(backgroundLeft));function setOffsetLeft(e){var p=_3(e)?"backgroundPositionX":"offsetLeft";e.runtimeStyle[p]=getOffsetLeft(e,e.style.backgroundPositionX)-e.getBoundingClientRect().left-e.clientLeft+2};eval(_1(setOffsetLeft));function getOffsetLeft(e,p){switch(p){case"left":case"top":return 0;case"right":case"bottom":return viewport.clientWidth-_0.offsetWidth;case"center":return(viewport.clientWidth-_0.offsetWidth)/2;default:if(PERCENT.test(p)){return parseInt((viewport.clientWidth-_0.offsetWidth)*parseFloat(p)/100)}_0.style.left=p;re
turn _0.offsetLeft}};eval(_1(getOffsetLeft));function _6(e){if(register(_6,e,isFixed(e))){setOverrideStyle(e,"position","absolute");setOverrideStyle(e,"left",e.currentStyle.left);setOverrideStyle(e,"top",e.currentStyle.top);_8();if(ie7Layout)ie7Layout.fixRight(e);_5(e)}};function _5(e,r){positionTop(e,r);positionLeft(e,r,true);if(!e.runtimeStyle.autoLeft&&e.currentStyle.marginLeft=="auto"&&e.currentStyle.right!="auto"){var l=viewport.clientWidth-getPixelWidth(e,e.currentStyle.right)-getPixelWidth(e,e.runtimeStyle._12)-e.clientWidth;if(e.currentStyle.marginRight=="auto")l=parseInt(l/2);if(_3(e.offsetParent))e.runtimeStyle.pixelLeft+=l;else e.runtimeStyle.shiftLeft=l}clipWidth(e);clipHeight(e)};function clipWidth(e){if(e.currentStyle.width!="auto"){var r=e.getBoundingClientRect();var w=e.offsetWidth-viewport.clientWidth+r.left-2;if(w>=0){w=Math.max(getPixelValue(e,e.currentStyle.width)-w,0);setOverrideStyle(e,"width",w)}}};eval(_1(clipWidth));function positionLeft(e,r){if(!r&&
PERCENT.test(e.currentStyle.width)){e.runtimeStyle.fixWidth=e.currentStyle.width}if(e.runtimeStyle.fixWidth){e.runtimeStyle.width=getPixelWidth(e,e.runtimeStyle.fixWidth)}if(r){if(!e.runtimeStyle.autoLeft)return}else{e.runtimeStyle.shiftLeft=0;e.runtimeStyle._12=e.currentStyle.left;e.runtimeStyle.autoLeft=e.currentStyle.right!="auto"&&e.currentStyle.left=="auto"}e.runtimeStyle.left="";e.runtimeStyle.screenLeft=getScreenLeft(e);e.runtimeStyle.pixelLeft=e.runtimeStyle.screenLeft;if(!r&&!_3(e.offsetParent)){var ex="runtimeStyle.screenLeft+runtimeStyle.shiftLeft+document."+_10+".scrollLeft";setExpression(e,"pixelLeft",ex)}};eval(_1(positionLeft));function getScreenLeft(e){var s=e.offsetLeft,n=1;if(e.runtimeStyle.autoLeft){s=viewport.clientWidth-e.offsetWidth-getPixelWidth(e,e.currentStyle.right)}if(e.currentStyle.marginLeft!="auto"){s-=getPixelWidth(e,e.currentStyle.marginLeft)}while(e=e.offsetParent){if(e.currentStyle.position!="static")n=-1;s+=e.offsetLeft*n}return s};eval(_1(
getScreenLeft));function getPixelWidth(e,v){if(PERCENT.test(v))return parseInt(parseFloat(v)/100*viewport.clientWidth);return getPixelValue(e,v)};eval(_1(getPixelWidth));function _11(){var e=_7.elements;for(var i in e)_9(e[i]);e=_6.elements;for(i in e){_5(e[i],true);_5(e[i],true)}_4=0};var _4;addResize(function(){if(!_4)_4=setTimeout(_11,0)})});
\ No newline at end of file
diff --git a/web/static/js/ie7/ie7-graphics.js b/web/static/js/ie7/ie7-graphics.js
new file mode 100644
index 0000000..7e63c47
--- /dev/null
+++ b/web/static/js/ie7/ie7-graphics.js
@@ -0,0 +1,6 @@
+/*
+ IE7, version 0.9 (alpha) (2005-08-19)
+ Copyright: 2004-2005, Dean Edwards (http://dean.edwards.name/)
+ License: http://creativecommons.org/licenses/LGPL/2.1/
+*/
+IE7.addModule("ie7-graphics",function(){if(appVersion<5.5)return;var A="DXImageTransform.Microsoft.AlphaImageLoader";var F="progid:"+A+"(src='%1',sizingMethod='scale')";var _3=new RegExp((window.IE7_PNG_SUFFIX||"-trans.png")+"$","i");var _0=[];function _2(e){var f=e.filters[A];if(f){f.src=e.src;f.enabled=true}else{e.runtimeStyle.filter=F.replace(/%1/,e.src);_0.push(e)}e.src=BLANK_GIF};function _5(e){e.src=e.pngSrc;e.filters[A].enabled=false};ie7CSS.addFix(/opacity\s*:\s*([\d.]+)/,function(m,o){return"zoom:1;filter:progid:DXImageTransform.Microsoft.Alpha(opacity="+((parseFloat(m[o+1])*100)||1)+")"});var B=/background(-image)?\s*:\s*([^\(};]*)url\(([^\)]+)\)([^;}]*)/;ie7CSS.addFix(B,function(m,o){var u=getString(m[o+3]);return _3.test(u)?"filter:"+F.replace(/scale/,"crop").replace(/%1/,u)+";zoom:1;background"+(m[o+1]||"")+":"+(m[o+2]||"")+"none"+(m[o+4]||""):m[o]});if(ie7HTML){ie7HTML.addRecalc("img,input",function(e){if(e.tagName=="INPUT"&&e.type!="image")return;_4(e);addEven
tHandler(e,"onpropertychange",function(){if(!_1&&event.propertyName=="src"&&e.src.indexOf(BLANK_GIF)==-1)_4(e)})});var B64=/^data:.*;base64/i;var _7=makePath("ie7-base64.php",path);function _4(e){if(_3.test(e.src)){var i=new Image(e.width,e.height);i.onload=function(){e.width=i.width;e.height=i.height;i=null};i.src=e.src;e.pngSrc=e.src;_2(e)}else if(B64.test(e.src)){e.src=_7+"?"+e.src.slice(5)}};var I=/^image/i;var _6=makePath("ie7-object.htc",path);ie7HTML.addRecalc("object",function(e){if(I.test(e.type)){var o=document.createElement("<object type=text/x-scriptlet>");o.style.width=e.currentStyle.width;o.style.height=e.currentStyle.height;o.data=_6;var u=makePath(e.data,getPath(location.href));e.parentNode.replaceChild(o,e);cssQuery.clearCache("object");addTimer(o,"",u);return o}})}var _1=false;addEventHandler(window,"onbeforeprint",function(){_1=true;for(var i=0;i<_0.length;i++)_5(_0[i])});addEventHandler(window,"onafterprint",function(){for(var i=0;i<_0.length;i++)_2(_0[i]
);_1=false})});
diff --git a/web/static/js/ie7/ie7-html4.js b/web/static/js/ie7/ie7-html4.js
new file mode 100644
index 0000000..3fcd891
--- /dev/null
+++ b/web/static/js/ie7/ie7-html4.js
@@ -0,0 +1,6 @@
+/*
+ IE7, version 0.9 (alpha) (2005-08-19)
+ Copyright: 2004-2005, Dean Edwards (http://dean.edwards.name/)
+ License: http://creativecommons.org/licenses/LGPL/2.1/
+*/
+IE7.addModule("ie7-html4",function(){if(!isHTML)return;HEADER+="h1{font-size:2em}h2{font-size:1.5em;}h3{font-size:1.17em;}"+"h4{font-size:1em}h5{font-size:.83em}h6{font-size:.67em}";var _0={};ie7HTML=new(Fix.specialize({init:DUMMY,addFix:function(){this.fixes.push(arguments)},apply:function(){for(var i=0;i<this.fixes.length;i++){var m=cssQuery(this.fixes[i][0]);var f=this.fixes[i][1]||_1;for(var j=0;j<m.length;j++)f(m[j])}},addRecalc:function(){this.recalcs.push(arguments)},recalc:function(){for(var i=0;i<this.recalcs.length;i++){var m=cssQuery(this.recalcs[i][0]);var r=this.recalcs[i][1],e;var k=Math.pow(2,i);for(var j=0;(e=m[j]);j++){var u=e.uniqueID;if((_0[u]&k)==0){e=r(e)||e;_0[u]|=k}}}}}));ie7HTML.addFix("abbr");ie7HTML.addRecalc("label",function(e){if(!e.htmlFor){var f=cssQuery("input,textarea",e)[0];if(f){addEventHandler(e,"onclick",function(){f.click()})}}});ie7HTML.addRecalc("button,input",function(e){if(e.tagName=="BUTTON"){var m=e.outerHTML.match(/ value="([^"]*)"
/i);e.runtimeStyle.value=(m)?m[1]:""}if(e.type=="submit"){addEventHandler(e,"onclick",function(){e.runtimeStyle.clicked=true;setTimeout("document.all."+e.uniqueID+".runtimeStyle.clicked=false",1)})}});var U=/^(submit|reset|button)$/;ie7HTML.addRecalc("form",function(e){addEventHandler(e,"onsubmit",function(){for(var i=0;i<e.length;i++){if(_2(e[i])){e[i].disabled=true;setTimeout("document.all."+e[i].uniqueID+".disabled=false",1)}else if(e[i].tagName=="BUTTON"&&e[i].type=="submit"){setTimeout("document.all."+e[i].uniqueID+".value='"+e[i].value+"'",1);e[i].value=e[i].runtimeStyle.value}}})});function _2(e){return U.test(e.type)&&!e.disabled&&!e.runtimeStyle.clicked};ie7HTML.addRecalc("img",function(e){if(e.alt&&!e.title)e.title=""});var P=(appVersion<5.5)?"HTML:":"";function _1(e){var f=document.createElement("<"+P+e.outerHTML.slice(1));if(e.outerHTML.slice(-2)!="/>"){var en="</"+e.tagName+">",n;while((n=e.nextSibling)&&n.outerHTML!=en){f.appendChild(n)}if(n)n.removeNode()}e.pa
rentNode.replaceChild(f,e)}});
diff --git a/web/static/js/ie7/ie7-ie5.js b/web/static/js/ie7/ie7-ie5.js
new file mode 100644
index 0000000..a07fd98
--- /dev/null
+++ b/web/static/js/ie7/ie7-ie5.js
@@ -0,0 +1,6 @@
+/*
+ IE7, version 0.9 (alpha) (2005-08-19)
+ Copyright: 2004-2005, Dean Edwards (http://dean.edwards.name/)
+ License: http://creativecommons.org/licenses/LGPL/2.1/
+*/
+if(appVersion<5.5){ANON="HTML:!";var ap=function(f,o,a){f.apply(o,a)};if(''.replace(/^/,String)){var _0=String.prototype.replace;var _1=function(e,r){var m,n="",s=this;while(s&&(m=e.exec(s))){n+=s.slice(0,m.index)+ap(r,this,m);s=s.slice(m.lastIndex)}return n+s};String.prototype.replace=function(e,r){this.replace=(typeof r=="function")?_1:_0;return this.replace(e,r)}}if(!Function.apply){var APPLY="apply-"+Number(new Date);ap=function(f,o,a){var r;o[APPLY]=f;switch(a.length){case 0:r=o[APPLY]();break;case 1:r=o[APPLY](a[0]);break;case 2:r=o[APPLY](a[0],a[1]);break;case 3:r=o[APPLY](a[0],a[1],a[2]);break;case 4:r=o[APPLY](a[0],a[1],a[2],a[3]);break;default:var aa=[],i=a.length-1;do aa[i]="a["+i+"]";while(i--);eval("r=o[APPLY]("+aa+")")}delete o[APPLY];return r};ICommon.valueOf.prototype.inherit=function(){return ap(arguments.callee.caller.ancestor,this,arguments)}}if(![].push)Array.prototype.push=function(){for(var i=0;i<arguments.length;i++){this[this.length]=arguments[i]}retu
rn this.length};if(![].pop)Array.prototype.pop=function(){var i=this[this.length-1];this.length--;return i};if(isHTML){HEADER+="address,blockquote,body,dd,div,dt,fieldset,form,"+"frame,frameset,h1,h2,h3,h4,h5,h6,iframe,noframes,object,p,"+"hr,applet,center,dir,menu,pre,dl,li,ol,ul{display:block}"}}
diff --git a/web/static/js/ie7/ie7-layout.js b/web/static/js/ie7/ie7-layout.js
new file mode 100644
index 0000000..d1b64eb
--- /dev/null
+++ b/web/static/js/ie7/ie7-layout.js
@@ -0,0 +1,6 @@
+/*
+ IE7, version 0.9 (alpha) (2005-08-19)
+ Copyright: 2004-2005, Dean Edwards (http://dean.edwards.name/)
+ License: http://creativecommons.org/licenses/LGPL/2.1/
+*/
+IE7.addModule("ie7-layout",function(){ie7Layout=this;HEADER+="*{boxSizing:content-box}";this.hasLayout=(appVersion<5.5)?function(e){return e.clientWidth}:function(e){return e.currentStyle.hasLayout};this.boxSizing=function(e){if(!ie7Layout.hasLayout(e)){e.style.height="0cm";if(e.currentStyle.verticalAlign=="auto")e.runtimeStyle.verticalAlign="top";_1(e)}};function _1(e){if(e!=viewport&&e.currentStyle.position!="absolute"){collapseMarginTop(e);collapseMarginBottom(e)}};var firstElementChild=cssQuery.valueOf("firstElementChild");var lastElementChild=cssQuery.valueOf("lastElementChild");function collapseMarginTop(e){if(!e.runtimeStyle.marginTop){var p=e.parentElement;if(p&&ie7Layout.hasLayout(p)&&e==firstElementChild(p))return;var f=firstElementChild(e);if(f&&f.currentStyle.styleFloat=="none"&&ie7Layout.hasLayout(f)){collapseMarginTop(f);m=_3(e,e.currentStyle.marginTop);c=_3(f,f.currentStyle.marginTop);if(m<0||c<0){e.runtimeStyle.marginTop=m+c}else{e.runtimeStyle.marginTop=Math
.max(c,m)}f.runtimeStyle.marginTop="0px"}}};eval(String(collapseMarginTop).replace(/Top/g,"Bottom").replace(/first/g,"last"));function _3(e,v){return(v=="auto")?0:getPixelValue(e,v)};var U=/^[.\d][\w%]*$/,A=/^(auto|0cm)$/,N="[.\\d]";var applyWidth,applyHeight;function borderBox(e){applyWidth(e);applyHeight(e)};function fixWidth(H){applyWidth=function(e){if(!PERCENT.test(e.currentStyle.width))fixWidth(e);_1(e)};function fixWidth(e,v){if(!e.runtimeStyle.fixedWidth){if(!v)v=e.currentStyle.width;e.runtimeStyle.fixedWidth=(U.test(v))?Math.max(0,getFixedWidth(e,v)):v;setOverrideStyle(e,"width",e.runtimeStyle.fixedWidth)}};function layoutWidth(e){if(!isFixed(e)){var l=e.offsetParent;while(l&&!ie7Layout.hasLayout(l))l=l.offsetParent}return(l||viewport).clientWidth};function getPixelWidth(e,v){if(PERCENT.test(v))return parseInt(parseFloat(v)/100*layoutWidth(e));return getPixelValue(e,v)};var getFixedWidth=function(e,v){var b=e.currentStyle["box-sizing"]=="border-box";var a=0;if(quirk
sMode&&!b)a+=getBorderWidth(e)+getPaddingWidth(e);else if(!quirksMode&&b)a-=getBorderWidth(e)+getPaddingWidth(e);return getPixelWidth(e,v)+a};function getBorderWidth(e){return e.offsetWidth-e.clientWidth};function getPaddingWidth(e){return getPixelWidth(e,e.currentStyle.paddingLeft)+getPixelWidth(e,e.currentStyle.paddingRight)};eval(String(getPaddingWidth).replace(/padding/g,"margin").replace(/Padding/g,"Margin"));HEADER+="*{minWidth:none;maxWidth:none;min-width:none;max-width:none}";function minWidth(e){if(e.currentStyle["min-width"]!=null){e.style.minWidth=e.currentStyle["min-width"]}if(register(minWidth,e,e.currentStyle.minWidth!="none")){ie7Layout.boxSizing(e);fixWidth(e);resizeWidth(e)}};eval(String(minWidth).replace(/min/g,"max"));ie7Layout.minWidth=minWidth;ie7Layout.maxWidth=maxWidth;function resizeWidth(e){var r=e.getBoundingClientRect();var w=r.right-r.left;if(e.currentStyle.minWidth!="none"&&w<=getFixedWidth(e,e.currentStyle.minWidth)){e.runtimeStyle.width=getFixe
dWidth(e,e.currentStyle.minWidth)}else if(e.currentStyle.maxWidth!="none"&&w>=getFixedWidth(e,e.currentStyle.maxWidth)){e.runtimeStyle.width=getFixedWidth(e,e.currentStyle.maxWidth)}else{e.runtimeStyle.width=e.runtimeStyle.fixedWidth}};function fixRight(e){if(register(fixRight,e,/^(fixed|absolute)$/.test(e.currentStyle.position)&&getDefinedStyle(e,"left")!="auto"&&getDefinedStyle(e,"right")!="auto"&&A.test(getDefinedStyle(e,"width")))){resizeRight(e);ie7Layout.boxSizing(e)}};ie7Layout.fixRight=fixRight;function resizeRight(e){var l=getPixelWidth(e,e.runtimeStyle._4||e.currentStyle.left);var w=layoutWidth(e)-getPixelWidth(e,e.currentStyle.right)-l-getMarginWidth(e);if(parseInt(e.runtimeStyle.width)==w)return;e.runtimeStyle.width="";if(isFixed(e)||H||e.offsetWidth<w){if(!quirksMode)w-=getBorderWidth(e)+getPaddingWidth(e);if(w<0)w=0;e.runtimeStyle.fixedWidth=w;setOverrideStyle(e,"width",w)}};var _2=0;addResize(function(){var i,w=(_2<viewport.clientWidth);_2=viewport.clientWidth
;for(i in minWidth.elements){var e=minWidth.elements[i];var f=(parseInt(e.runtimeStyle.width)==getFixedWidth(e,e.currentStyle.minWidth));if(w&&f)e.runtimeStyle.width="";if(w==f)resizeWidth(e)}for(i in maxWidth.elements){var e=maxWidth.elements[i];var f=(parseInt(e.runtimeStyle.width)==getFixedWidth(e,e.currentStyle.maxWidth));if(!w&&f)e.runtimeStyle.width="";if(w!=f)resizeWidth(e)}for(i in fixRight.elements)resizeRight(fixRight.elements[i])});if(window.IE7_BOX_MODEL!==false){ie7CSS.addRecalc("width",N,quirksMode?applyWidth:_1)}ie7CSS.addRecalc("min-width",N,minWidth);ie7CSS.addRecalc("max-width",N,maxWidth);ie7CSS.addRecalc("right",N,fixRight)};ie7CSS.addRecalc("border-spacing",N,function(e){if(e.currentStyle.borderCollapse!="collapse"){e.cellSpacing=getPixelValue(e,e.currentStyle["border-spacing"])}});ie7CSS.addRecalc("box-sizing","content-box",this.boxSizing);ie7CSS.addRecalc("box-sizing","border-box",borderBox);var _0=new ParseMaster;_0.add(/Width/,"Height");_0.add(/width
/,"height");_0.add(/Left/,"Top");_0.add(/left/,"top");_0.add(/Right/,"Bottom");_0.add(/right/,"bottom");eval(_0.exec(String(fixWidth)));fixWidth();fixHeight(true)});
diff --git a/web/static/js/ie7/ie7-load.htc b/web/static/js/ie7/ie7-load.htc
new file mode 100644
index 0000000..a6f1e7f
--- /dev/null
+++ b/web/static/js/ie7/ie7-load.htc
@@ -0,0 +1 @@
+<component lightweight="true"><attach event="ondocumentready" onevent="IE7.init()"/></component>
diff --git a/web/static/js/ie7/ie7-object.htc b/web/static/js/ie7/ie7-object.htc
new file mode 100644
index 0000000..392409e
--- /dev/null
+++ b/web/static/js/ie7/ie7-object.htc
@@ -0,0 +1,12 @@
+<html>
+<!--
+ IE7, version 0.9 (alpha) (2005-08-19)
+ Copyright: 2004-2005, Dean Edwards (http://dean.edwards.name/)
+ License: http://creativecommons.org/licenses/LGPL/2.1/
+-->
+<head>
+<style type="text/css">body{margin:0}</style>
+<script type="text/javascript">public_description=new function(){var l=false;this.ie7_anon=true;this.load=function(o,c,u){if(l)return;l=true;function _0(t,p){t.style[p]=o.currentStyle[p]};var p=o;while(p&&p.currentStyle.backgroundColor=="transparent"){p=p.parentElement}if(p)body.style.backgroundColor=p.currentStyle.backgroundColor;_0(body,"backgroundImage");_0(body,"backgroundRepeat");_0(body,"backgroundPositionX");_0(body,"backgroundPositionY");_0(body,"fontFamily");_0(body,"fontSize");_0(wrapper,"paddingTop");_0(wrapper,"paddingRight");_0(wrapper,"paddingBottom");_0(wrapper,"paddingLeft");image.width=o.clientWidth;image.height=o.clientHeight;var B64=/^data:.*;base64/i,P=/.png$/i;if(B64.test(u))u="ie7-base64.php"+"?"+u.slice(5);if(P.test(u)&&!/MSIE 5.0/.test(navigator.userAgent)){image.src="blank.gif";image.style.filter="progid:DXImageTransform.Microsoft.AlphaImageLoader(src='"+u+"',sizingMethod='scale')"}else{image.src=u}o.style.width=body.scrollWidth;o.style.height=body.s
crollHeight}};</script>
+</head>
+<body id="body"><div id="wrapper"><img id="image"></div></body>
+</html>
diff --git a/web/static/js/ie7/ie7-overflow.js b/web/static/js/ie7/ie7-overflow.js
new file mode 100644
index 0000000..ad2e030
--- /dev/null
+++ b/web/static/js/ie7/ie7-overflow.js
@@ -0,0 +1,6 @@
+/*
+ IE7, version 0.9 (alpha) (2005-08-19)
+ Copyright: 2004-2005, Dean Edwards (http://dean.edwards.name/)
+ License: http://creativecommons.org/licenses/LGPL/2.1/
+*/
+IE7.addModule("ie7-overflow",function(){var S={backgroundColor:"transparent",backgroundImage:"none",backgroundPositionX:null,backgroundPositionY:null,backgroundRepeat:null,borderTopWidth:0,borderRightWidth:0,borderBottomWidth:0,borderLeftStyle:"none",borderTopStyle:"none",borderRightStyle:"none",borderBottomStyle:"none",borderLeftWidth:0,height:null,marginTop:0,marginBottom:0,marginRight:0,marginLeft:0,width:"100%"};function _3(p,s,t){t.style[p]=s.currentStyle[p];if(S[p]!=null){s.runtimeStyle[p]=S[p]}};ie7CSS.addRecalc("overflow","visible",function(e){if(e.parentNode.ie7_wrapper)return;if(ie7Layout&&e.currentStyle["max-height"]!="auto"){ie7Layout.maxHeight(e)}if(e.currentStyle.marginLeft=="auto")e.style.marginLeft=0;if(e.currentStyle.marginRight=="auto")e.style.marginRight=0;var w=document.createElement(ANON);w.ie7_wrapper=true;for(var p in S)_3(p,e,w);w.style.display="block";w.style.position="relative";e.runtimeStyle.position="absolute";e.parentNode.insertBefore(w,e);w.appe
ndChild(e)});cssQuery.addModule("ie7-overflow",function(){function _0(e){return(e&&e.ie7_wrapper)?e.firstChild:e};var _2=previousElementSibling;previousElementSibling=function(e){return _0(_2(e))};var _1=nextElementSibling;nextElementSibling=function(e){return _0(_1(e))};selectors[" "]=function(r,f,t,n){var e,i,j;for(i=0;i<f.length;i++){var s=getElementsByTagName(f[i],t,n);for(j=0;(e=_0(s[j]));j++){if(thisElement(e)&&(!n||compareNamespace(e,n)))r.push(e)}}};selectors[">"]=function(r,f,t,n){var e,i,j;for(i=0;i<f.length;i++){var s=childElements(f[i]);for(j=0;(e=_0(s[j]));j++){if(compareTagName(e,t,n))r.push(e)}}}})});
diff --git a/web/static/js/ie7/ie7-quirks.js b/web/static/js/ie7/ie7-quirks.js
new file mode 100644
index 0000000..a1314dd
--- /dev/null
+++ b/web/static/js/ie7/ie7-quirks.js
@@ -0,0 +1,6 @@
+/*
+ IE7, version 0.9 (alpha) (2005-08-19)
+ Copyright: 2004-2005, Dean Edwards (http://dean.edwards.name/)
+ License: http://creativecommons.org/licenses/LGPL/2.1/
+*/
+IE7.addModule("ie7-quirks",function(){if(quirksMode){var F="xx-small,x-small,small,medium,large,x-large,xx-large".split(",");for(var i=0;i<F.length;i++){F[F[i]]=F[i-1]||"0.67em"}ie7CSS.addFix(new RegExp("(font(-size)?\\s*:\\s*)([\\w\\-\\.]+)"),function(m,o){return m[o+1]+(F[m[o+3]]||m[o+3])});if(appVersion<6){var N=/^\-/,L=/(em|ex)$/i;var EM=/em$/i,EX=/ex$/i;function _2(e){var s=1;_0.style.fontFamily=e.currentStyle.fontFamily;_0.style.lineHeight=e.currentStyle.lineHeight;while(e!=body){var f=e.currentStyle["ie7-font-size"];if(f){if(EM.test(f))s*=parseFloat(f);else if(PERCENT.test(f))s*=(parseFloat(f)/100);else if(EX.test(f))s*=(parseFloat(f)/2);else{_0.style.fontSize=f;return 1}}e=e.parentElement}return s};var _0=createTempElement();getPixelValue=function(e,v){if(PIXEL.test(v||0))return parseInt(v||0);var scale=N.test(v)?-1:1;if(L.test(v))scale*=_2(e);_0.style.width=(scale<0)?v.slice(1):v;body.appendChild(_0);v=scale*_0.offsetWidth;_0.removeNode();return parseInt(v)};HEADER=
HEADER.replace(/(font(-size)?\s*:\s*([^\s;}\/]*))/gi,"ie7-font-size:$3;$1");ie7CSS.addFix(/cursor\s*:\s*pointer/,"cursor:hand");ie7CSS.addFix(/display\s*:\s*list-item/,"display:block")}function getPaddingWidth(e){return getPixelValue(e,e.currentStyle.paddingLeft)+getPixelValue(e,e.currentStyle.paddingRight)};function _1(e){if(appVersion<5.5&&ie7Layout)ie7Layout.boxSizing(e.parentElement);var p=e.parentElement;var m=p.offsetWidth-e.offsetWidth-getPaddingWidth(p);var a=(e.currentStyle["ie7-margin"]&&e.currentStyle.marginRight=="auto")||e.currentStyle["ie7-margin-right"]=="auto";switch(p.currentStyle.textAlign){case"right":m=(a)?parseInt(m/2):0;e.runtimeStyle.marginRight=parseInt(m)+"px";break;case"center":if(a)m=0;default:if(a)m=parseInt(m/2);e.runtimeStyle.marginLeft=parseInt(m)+"px"}};ie7CSS.addRecalc("margin(-left|-right)?","[^};]*auto",function(e){if(register(_1,e,e.parentElement&&e.currentStyle.display=="block"&&e.currentStyle.marginLeft=="auto"&&e.currentStyle.position!=
"absolute")){_1(e)}});addResize(function(){for(var i in _1.elements){e=_1.elements[i];e.runtimeStyle.marginLeft=e.runtimeStyle.marginRight="";_1(e)}})}});
diff --git a/web/static/js/ie7/ie7-recalc.js b/web/static/js/ie7/ie7-recalc.js
new file mode 100644
index 0000000..10cb839
--- /dev/null
+++ b/web/static/js/ie7/ie7-recalc.js
@@ -0,0 +1,6 @@
+/*
+ IE7, version 0.9 (alpha) (2005-08-19)
+ Copyright: 2004-2005, Dean Edwards (http://dean.edwards.name/)
+ License: http://creativecommons.org/licenses/LGPL/2.1/
+*/
+IE7.addModule("ie7-recalc",function(){C=/\sie7_class\d+/g;function _0(e){e.className=e.className.replace(C,"")};function _1(e){e.runtimeStyle.cssText=""};ie7CSS.specialize({elements:{},handlers:[],reset:function(){this.removeEventHandlers();var e=this.elements;for(var i in e)_1(e[i]);this.elements={};if(this.Rule){var e=this.Rule.elements;for(var i in e)_0(e[i]);this.Rule.elements={}}},reload:function(){ie7CSS.rules=[];this.getInlineStyles();this.screen.load();if(this.print)this.print.load();this.refresh();this.trash()},addRecalc:function(p,t,h,r){this.inherit(p,t,function(e){h(e);ie7CSS.elements[e.uniqueID]=e},r)},recalc:function(){this.reset();this.inherit()},addEventHandler:function(e,t,h){e.attachEvent(t,h);this.handlers.push(arguments)},removeEventHandlers:function(){var h;while(h=this.handlers.pop()){removeEventHandler(h[0],h[1],h[2])}},getInlineStyles:function(){var st=document.getElementsByTagName("style"),s;for(var i=st.length-1;(s=st[i]);i--){if(!s.disabled&&!s.ie7
){var c=s.c||s.innerHTML;this.styles.push(c);s.c=c}}},trash:function(){var s,i;for(i=0;i<styleSheets.length;i++){s=styleSheets[i];if(!s.ie7&&!s.c){s.c=s.cssText}}this.inherit()},getText:function(s){return s.c||this.inherit(s)}});addEventHandler(window,"onunload",function(){ie7CSS.removeEventHandlers()});if(ie7CSS.Rule){ie7CSS.Rule.elements={};ie7CSS.Rule.prototype.specialize({add:function(e){this.inherit(e);ie7CSS.Rule.elements[e.uniqueID]=e}});ie7CSS.PseudoElement.hash={};ie7CSS.PseudoElement.prototype.specialize({create:function(t){var k=this.selector+":"+t.uniqueID;if(!ie7CSS.PseudoElement.hash[k]){ie7CSS.PseudoElement.hash[k]=true;this.inherit(t)}}})}if(isHTML&&ie7HTML){ie7HTML.specialize({elements:{},addRecalc:function(s,h){this.inherit(s,function(e){if(!ie7HTML.elements[e.uniqueID]){h(e);ie7HTML.elements[e.uniqueID]=e}})}})}document.recalc=function(reload){if(ie7CSS.screen){if(reload)ie7CSS.reload();recalc()}}});
diff --git a/web/static/js/ie7/ie7-server.css b/web/static/js/ie7/ie7-server.css
new file mode 100644
index 0000000..50ca605
--- /dev/null
+++ b/web/static/js/ie7/ie7-server.css
@@ -0,0 +1,44 @@
+body, td, dd {font: 10pt Verdana, Arial, Helvetica, sans-serif; color: black;}
+body {margin: 8px; background: #333;}
+h1 {margin: 0;}
+h1 a:hover {background-color: transparent;}
+h2 {font-size: 1.75em;}
+h3 {font-size: 1.1em;}
+p.footnote {font-family: "Times New Roman", Times, serif; font-style: italic;}
+a:active {color: #ff0000;}
+a:link {color: #0a6cce;}
+a:visited {color: #0a6cce;}
+code, *.code {font-family: monospace; font-size: 100%; font-style: normal; white-space: nowrap;
+ padding: 0 1px; background: #f2f3f8; border: #d6d9e9 1px solid;}
+code.box {display: block; padding: 10px; margin: 0.5em 0;}
+ul {list-style-type: square;}
+dd {margin: .2em 0 .5em 1em;}
+dl.library dt {display: list-item; margin-left: 3em; list-style-type: square;}
+dl.library dd {font-style: italic; margin-left: 3em;}
+dt {font-weight: bold;}
+dt.pack {color: brown;}
+a img {border-style: none;}
+hr {height: 1px; color: #000; border-style: solid;}
+hr.short {height: 2px; width: 100px;}
+div.document {background: #eef; padding: 20px 20px 5px 20px; width: 600px; border: 1px solid black;}
+hr {border-bottom-width: 0px;}
+div.header hr {color: #0a6cce; background-color: #0a6cce;}
+div.footer hr {color: #898e79; background-color: #898e79; }
+div.header, div.header a:link, div.header a:visited, h3 a:link, h3 a:visited {text-decoration: none;}
+a:hover {color: #fff; background-color: #0a6cce; text-decoration: none;}
+div.footer a:hover {background-color: transparent; text-decoration: none;}
+div.header .menu {text-align: right;}
+div.content {min-height: 100px;}
+div.footer {font-size: x-small; margin-top: 8px;}
+div.footnote {font-family: "times new roman", times; font-style: italic; margin-top: 10px;}
+#license {margin-top: 5px; font-size: xx-small;}
+table {border-top: 1px solid #000; border-left: 1px solid #000;}
+th {background-color: #fff; text-align: left;}
+th, td {border-right: 1px solid #000; border-bottom: 1px solid #000;}
+th.small {width: 100px;}
+th.medium {width: 200px;}
+th.large {width: 270px;}
+th.x-large {width: 408px;}
+table.fixed {table-layout: fixed;}
+span.comment {color: #666;}
+
diff --git a/web/static/js/ie7/ie7-squish.js b/web/static/js/ie7/ie7-squish.js
new file mode 100644
index 0000000..e5a1972
--- /dev/null
+++ b/web/static/js/ie7/ie7-squish.js
@@ -0,0 +1,45 @@
+/*
+ IE7, version 0.9 (alpha) (2005-08-19)
+ Copyright: 2004-2005, Dean Edwards (http://dean.edwards.name/)
+ License: http://creativecommons.org/licenses/LGPL/2.1/
+*/
+IE7.addModule("ie7-squish", function() {
+
+/* ---------------------------------------------------------------------
+
+ Squish some IE bugs!
+
+ Some of these bug fixes may have adverse effects so they are
+ not included in the standard library. Add your own if you want.
+
+ -dean
+
+--------------------------------------------------------------------- */
+
+// @NOTE: ie7Layout.boxSizing is the same as the "Holly Hack"
+
+// "doubled margin" bug
+// http://www.positioniseverything.net/explorer/doubled-margin.html
+ie7CSS.addFix(/float\s*:\s*(left|right)/, "display:inline;$1");
+
+if (ie7Layout) {
+ // "peekaboo" bug
+ // http://www.positioniseverything.net/explorer/peekaboo.html
+ if (appVersion >= 6) ie7CSS.addRecalc("float", "left|right", function($element) {
+ ie7Layout.boxSizing($element.parentElement);
+ // "doubled margin" bug
+ $element.runtimeStyle.display = "inline";
+ });
+
+ // "unscrollable content" bug
+ // http://www.positioniseverything.net/explorer/unscrollable.html
+ ie7CSS.addRecalc("position", "absolute|fixed", function($element) {
+ if ($element.offsetParent && $element.offsetParent.currentStyle.position == "relative")
+ ie7Layout.boxSizing($element.offsetParent);
+ });
+}
+
+//# // get rid of Microsoft's pesky image toolbar
+//# if (!complete) document.write('<meta http-equiv="imagetoolbar" content="no">');
+
+});
diff --git a/web/static/js/ie7/ie7-standard-p.js b/web/static/js/ie7/ie7-standard-p.js
new file mode 100644
index 0000000..6db85f5
--- /dev/null
+++ b/web/static/js/ie7/ie7-standard-p.js
@@ -0,0 +1,6 @@
+/*
+ IE7, version 0.9 (alpha) (2005-08-19)
+ Copyright: 2004-2005, Dean Edwards (http://dean.edwards.name/)
+ License: http://creativecommons.org/licenses/LGPL/2.1/
+*/
+eval(function(p,a,c,k,e,d){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)d[e(c)]=k[c]||e(c);k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('y(!26.1F)11 7(){2C{26.1F=8;6 2s=8.24=11 3b;8.1g=7(){z"1F 4x 0.9 (ad)"};6 5T=/5T/.Z(2y.5h.7C);6 31=(5T)?7(m){26.31(1F+"\\n\\n"+m)}:2s;6 29=ac.29.19(/ab (\\d\\.\\d)/)[1];6 2m=16.aa!="a9";y(/a8/.Z(2y.5h.7C)||29<5||!/^a7/.Z(16.2F.2a))z;6 33=16.5W=="33";6 1s,1K;6 2F=16.2F,1X,1J,1R=16.1R;6 4E="!";6 3Q={};6 2G=1z;1F.24=7(n,s){y(!3Q[n]){y(2G)1k("s="+23(s));3Q[n]=11 s()}};6 R=/^[\\w\\.]+[^:]*$/;7 1Z(h,p){y(R.Z(h))h=(p||"")+h;z h};7 3F(h,p){h=1Z(h,p);z h.1q(0,h.7a("/")+1)};6 s=16.7B[16.7B.K-1];2C{1k(s.7j)}2j(i){}6 2k=3F(s.1l);6 2v;2C{6 l=(a6()>=5)?"a5":"5n";2v=11 a4(l+".a3")}2j(i){}6 4A={};7 3T(h,p){2C{h=1Z(h,p);y(!4A[h]){2v.a2("a1",h,1z);2v.a0();y(2v.7A==0||2v.7A=
=9Z){4A[h]=2v.9Y}}}2j(i){31("4B [1]: 5O 9X 9W "+h)}5U{z 4A[h]||""}};6 4i=1Z("9V.9U",2k);7 2o(1w){y(1w!=1U){1w.1T=1t.1C.1T;1w.1e=1t.1C.1e}z 1w};2o.1e=7(p,c){y(!p)p={};y(!c)c=p.1h;y(c=={}.1h)c=11 3b("8.1T()");c.1i=11 3b("z 8");c.1i.1C=11 8.1i;c.1i.1C.1e(p);c.1C=11 c.1i;c.1i.1C.1h=c.1C.1h=c;c.2E=8;c.1e=1a.5P;c.4z=8.4z;z c};2o.1i=11 3b("z 8");2o.1i.1C={1h:2o,1T:7(){z 1a.5P.9T.2E.2q(8,1a)},1e:7(1w){y(8==8.1h.1C&&8.1h.1e){z 8.1h.1i.1C.1e(1w)}O(6 i 28 1w){34(i){1m"1h":1m"1g":1m"1i":5M}y(3Y 1w[i]=="7"&&1w[i]!=8[i]){1w[i].2E=8[i]}8[i]=1w[i]}y(1w.1g!=8.1g&&1w.1g!={}.1g){1w.1g.2E=8.1g;8.1g=1w.1g}z 8}};7 1t(){};8.1t=2o.1e({1h:1t,1g:7(){z"[9S "+(8.1h.1x||"9R")+"]"},9Q:7(2i){z 8.1h==2i||2i.4z(8.1h)}});1t.1x="1t";1t.2E=1U;1t.4z=7(2i){1D(2i&&2i.2E!=8)2i=2i.2E;z 7q(2i)};1t.1i.2E=2o;3u 8.1t;6 5x=1t.1e({1h:7(){8.3L=[];8.1Q=[]},1S:2s});y(29<5.5)1k(3T("17-9P.5X",2k));6 5S=1z;1F.1S=7(){2C{y(5S)z;5S=33=1o;1X=16.1X;1J=(2m)?1X:2F;y(2l&&1s)1s.2q();V.2q();1u();31("2G 9O")}2j(e){31("4B [2]: "+e.5V)}};6
1Q=[];7 1n(r){1Q.1b(r)};7 1u(){14.5g();y(2l&&1s)1s.1u();V.1u();O(6 i=0;i<1Q.K;i++)1Q[i]()};7 2U(){6 E=0,R=1,L=2;6 G=/\\(/g,S=/\\$\\d/,I=/^\\$\\d+$/,T=/([\'"])\\1\\+(.*)\\+\\1\\1$/,7t=/\\\\./g,Q=/\'/,7z=/\\3S[^\\3S]*\\3S/g;6 3N=8;8.15=7(e,r){y(!r)r="";6 l=(5R(23(e)).19(G)||"").K+1;y(S.Z(r)){y(I.Z(r)){r=25(r.1q(1))-1}1d{6 i=l;6 q=Q.Z(5R(r))?\'"\':"\'";1D(i)r=r.2O("$"+i--).2p(q+"+a[o+"+i+"]+"+q);r=11 3b("a,o","z"+q+r.13(T,"$1")+q)}}7y(e||"/^$/",r,l)};8.2V=7(s){3R.K=0;z 7u(7v(s,8.4y).13(11 1N(30,8.5Q?"5D":"g"),7w),8.4y).13(7z,"")};8.72=7(){30.K=0};6 3R=[];6 30=[];6 7x=7(){z"("+23(8[E]).1q(1,-1)+")"};30.1g=7(){z 8.2p("|")};7 7y(){1a.1g=7x;30[30.K]=1a}7 7w(){y(!1a[0])z"";6 i=1,j=0,p;1D(p=30[j++]){y(1a[i]){6 r=p[R];34(3Y r){1m"7":z r(1a,i);1m"9N":z 1a[r+i]}6 d=(1a[i].6F(3N.4y)==-1)?"":"\\3S"+1a[i]+"\\3S";z d+r}1d i+=p[L]}};7 7v(s,e){z e?s.13(11 1N("\\\\"+e+"(.)","g"),7(m,c){3R[3R.K]=c;z e}):s};7 7u(s,e){6 i=0;z e?s.13(11 1N("\\\\"+e,"g"),7(){z e+(3R[i++]||"")}):s};7 5R(s){z s.13(7
t,"")}};2U.1C={1h:2U,5Q:1z,4y:""};1t.1e(2U.1C);6 3M=2U.1e({5Q:1o});6 14=7(){6 4x="2.0.2";6 C=/\\s*,\\s*/;6 14=7(s,1E){2C{6 m=[];6 u=1a.5P.5I&&!1E;6 b=(1E)?(1E.1h==7n)?1E:[1E]:[16];6 2f=45(s).2O(C),i;O(i=0;i<2f.K;i++){s=5J(2f[i]);y(4P&&s.1q(0,3).2p("")==" *#"){s=s.1q(2);1E=7o([],b,s[1])}1d 1E=b;6 j=0,t,f,a,c="";1D(j<s.K){t=s[j++];f=s[j++];c+=t+f;a="";y(s[j]=="("){1D(s[j++]!=")"&&j<s.K){a+=s[j]}a=a.1q(0,-1);c+="("+a+")"}1E=(u&&2e[c])?2e[c]:7m(1E,t,f,a);y(u)2e[c]=1E}m=m.4J(1E)}3u 14.5O;z m}2j(e){14.5O=e;z[]}};14.1g=7(){z"7 14() {\\n [4x "+4x+"]\\n}"};6 2e={};14.5I=1z;14.5g=7(s){y(s){s=5J(s).2p("");3u 2e[s]}1d 2e={}};6 3Q={};6 2G=1z;14.24=7(n,s){y(2G)1k("s="+23(s));3Q[n]=11 s()};14.1i=7(c){z c?1k(c):8};6 1V={};6 1B={};6 1p={19:/\\[([\\w-]+(\\|[\\w-]+)?)\\s*(\\W?=)?\\s*([^\\]]*)\\]/};6 2R=[];1V[" "]=7(r,f,t,n){6 e,i,j;O(i=0;i<f.K;i++){6 s=4w(f[i],t,n);O(j=0;(e=s[j]);j++){y(2D(e)&&5K(e,n))r.1b(e)}}};1V["#"]=7(r,f,i){6 e,j;O(j=0;(e=f[j]);j++)y(e.1c==i)r.1b(e)};1V["."]=7(r,f,c){c=1
1 1N("(^|\\\\s)"+c+"(\\\\s|$)");6 e,i;O(i=0;(e=f[i]);i++)y(c.Z(e.1x))r.1b(e)};1V[":"]=7(r,f,p,a){6 t=1B[p],e,i;y(t)O(i=0;(e=f[i]);i++)y(t(e,a))r.1b(e)};1B["21"]=7(e){6 d=5L(e);y(d.5N)O(6 i=0;i<d.5N.K;i++){y(d.5N[i]==e)z 1o}};1B["37"]=7(e){};6 2D=7(e){z(e&&e.7k==1&&e.2W!="!")?e:1U};6 4S=7(e){1D(e&&(e=e.9M)&&!2D(e))5M;z e};6 47=7(e){1D(e&&(e=e.6W)&&!2D(e))5M;z e};6 3l=7(e){z 2D(e.7s)||47(e.7s)};6 5t=7(e){z 2D(e.7r)||4S(e.7r)};6 6q=7(e){6 c=[];e=3l(e);1D(e){c.1b(e);e=47(e)}z c};6 4P=1o;6 5H=7(e){6 d=5L(e);z(3Y d.7p=="9L")?/\\.9K$/i.Z(d.9J):7q(d.7p=="9I 9H")};6 5L=7(e){z e.9G||e.16};6 4w=7(e,t){z(t=="*"&&e.1Y)?e.1Y:e.4w(t)};6 4T=7(e,t,n){y(t=="*")z 2D(e);y(!5K(e,n))z 1z;y(!5H(e))t=t.9F();z e.2W==t};6 5K=7(e,n){z!n||(n=="*")||(e.9E==n)};6 9D=7(e){z e.9C};7 7o(r,f,1c){6 m,i,j;O(i=0;i<f.K;i++){y(m=f[i].1Y.9B(1c)){y(m.1c==1c)r.1b(m);1d y(m.K!=1U){O(j=0;j<m.K;j++){y(m[j].1c==1c)r.1b(m[j])}}}}z r};y(![].1b)7n.1C.1b=7(){O(6 i=0;i<1a.K;i++){8[8.K]=1a[i]}z 8.K};6 N=/\\|/;7 7m(1E,t,f,a){y
(N.Z(f)){f=f.2O(N);a=f[0];f=f[1]}6 r=[];y(1V[t]){1V[t](r,1E,f,a)}z r};6 S=/^[^\\s>+~]/;6 7l=/[\\s#.:>+~()@]|[^\\s#.:>+~()@]+/g;7 5J(s){y(S.Z(s))s=" "+s;z s.19(7l)||[]};6 W=/\\s*([\\s>+~(),]|^|$)\\s*/g;6 I=/([\\s>+~,]|[^(]\\+|^)([#.:@])/g;6 45=7(s){z s.13(W,"$1").13(I,"$1*$2")};6 2c={1g:7(){z"\'"},19:/^(\'[^\']*\')|("[^"]*")$/,Z:7(s){z 8.19.Z(s)},15:7(s){z 8.Z(s)?s:8+s+8},3v:7(s){z 8.Z(s)?s.1q(1,-1):s}};6 2w=7(t){z 2c.3v(t)};6 E=/([\\/()[\\]?{}|*+-])/g;7 4O(s){z s.13(E,"\\\\$1")};2G=1o;z 14}();14.5I=1o;14.24("17",7(){2D=7(e){z(e&&e.7k==1&&e.2W!="!"&&!e.2K)?e:1U}});14.1i("2w=1a[1]",42);6 2l=!14.1i("5H(1a[1])",2F);6 2r=":21{17-21:21}:37{17-21:37}"+(2l?"":"*{6Q:0}");6 V=11(5x.1e({5B:11 3M,2P:"",2Y:"",5F:[],1S:7(){8.5G();8.4t()},4t:7(){V.3O.18=2r+8.2P+8.2Y},7i:7(){6 3P=16.4w("1r"),s;O(6 i=3P.K-1;(s=3P[i]);i--){y(!s.3m&&!s.17){8.5F.1b(s.7j)}}},2q:7(){8.7i();8.4t();11 3y("2P");8.7g()},3i:7(e,r){8.5B.15(e,r)},1u:7(){6 R=/7h\\d+/g;6 s=2r.19(/[{,]/g).K;6 3P=s+(8.2P.18.19(/\\{/g)||"").
K;6 2Q=8.3O.2t,r;6 4v,c,4u,e,i,j,k,1c;O(i=s;i<3P;i++){r=2Q[i];y(r&&(4v=r.1r.18.19(R))){4u=14(r.4M);y(4u.K)O(j=0;j<4v.K;j++){1c=4v[j];c=V.1Q[1c.1q(10)][2];O(k=0;(e=4u[k]);k++){y(e.D[1c])c(e)}}}}},1n:7(p,t,h,r){t=11 1N("([{;\\\\s])"+p+"\\\\s*:\\\\s*"+t+"[^;}]*");6 i=8.1Q.K;y(r)r=p+":"+r;8.3i(t,7(m,o){z(r?m[o+1]+r:m[o])+";17-"+m[o].1q(1)+";7h"+i+":1"});8.1Q.1b(1a);z i},2w:7(s){z s.18||""},5G:7(){y(33||!2l)16.5G();1d 16.9A("<1r 17=1o></1r>");8.3O=1R[1R.K-1];8.3O.17=1o;8.3O.18=2r},7g:7(){O(6 i=0;i<1R.K;i++){y(!1R[i].17&&1R[i].18){1R[i].18=""}}}}));7 3y(m){8.2Z=m;8.3q();V[m]=8;V.4t()};1t.1e({1h:3y,1g:7(){z"@2Z "+8.2Z+"{"+8.18+"}"},1u:2s,3q:7(){8.18="";8.2w();8.38();8.18=41(8.18);f={}},2w:7(){6 7e=[].4J(V.5F);6 M=/@2Z\\s+([^{]*)\\{([^@]+\\})\\s*\\}/5D;6 A=/\\9z\\b|^$/i,S=/\\9y\\b/i,P=/\\9x\\b/i;7 7d(c,m){4s.v=m;z c.13(M,4s)};7 4s(9w,m,c){m=5E(m);34(m){1m"2P":1m"2Y":y(m!=4s.v)z"";1m"1Y":z c}z""};7 5E(m){y(A.Z(m))z"1Y";1d y(S.Z(m))z(P.Z(m))?"1Y":"2P";1d y(P.Z(m))z"2Y"};6 3N=8;7 5C(s,
p,m,l){6 c="";y(!l){m=5E(s.2Z);l=0}y(m=="1Y"||m==3N.2Z){y(l<3){O(6 i=0;i<s.7f.K;i++){c+=5C(s.7f[i],3F(s.2u,p),m,l+1)}}c+=79(s.2u?7c(s,p):7e.77()||"");c=7d(c,3N.2Z)}z c};6 f={};7 7c(s,p){6 u=1Z(s.2u,p);y(f[u])z"";f[u]=(s.3m)?"":7b(V.2w(s,p),3F(s.2u,p));z f[u]};6 U=/(43\\s*\\(\\s*[\'"]?)([\\w\\.]+[^:\\)]*[\'"]?\\))/5D;7 7b(c,p){z c.13(U,"$1"+p.1q(0,p.7a("/")+1)+"$2")};O(6 i=0;i<1R.K;i++){y(!1R[i].3m&&!1R[i].17){8.18+=5C(1R[i])}}},38:7(){8.18=V.5B.2V(8.18)},1u:2s});6 2c=14.1i("2c");6 4r=[];7 79(c){z 2n.2V(3r.2V(c))};7 5A(m,o){z 2c+(4r.1b(m[o])-1)+2c};7 42(v){z 2c.Z(v)?1k(4r[1k(v)]):v};6 2n=11 3M;2n.15(/\\/\\*[^*]*\\*+([^\\/][^*]*\\*+)*\\//);2n.15(/\'[^\']*\'/,5A);2n.15(/"[^"]*"/,5A);2n.15(/\\s+/," ");2n.15(/@(9v|9u)[^;\\n]+[;\\n]|<!\\-\\-|\\-\\->/);6 3r=11 3M;3r.15(/\\\\\'/,"\\\\9t");3r.15(/\\\\"/,"\\\\46");6 5z=11 3M;5z.15(/\'(\\d+)\'/,78);7 41(c){z 5z.2V(c)};7 78(m,o){z 4r[m[o+1]]};6 5y=[];7 4U(h){1n(h);1j(26,"9s",h)};7 1j(e,t,h){e.9r(t,h);5y.1b(1a)};7 76(e,t,h){2C{e.9q(t,h)}
2j(i){}};1j(26,"9p",7(){6 h;1D(h=5y.77()){76(h[0],h[1],h[2])}});7 20(h,e,c){y(!h.1O)h.1O={};y(c)h.1O[e.2a]=e;1d 3u h.1O[e.2a];z c};1j(26,"6z",7(){y(!V.2Y)11 3y("2Y");V.2Y.1u()});6 75=/^\\d+(9o)?$/i;6 3d=/^\\d+%$/;6 3c=7(e,v){y(75.Z(v))z 25(v);6 s=e.1r.1f;6 r=e.J.1f;e.J.1f=e.D.1f;e.1r.1f=v||0;v=e.1r.4e;e.1r.1f=s;e.J.1f=r;z v};7 6x(t){6 e=16.3X(t||"2M");e.1r.18="1y:3C;6R:0;4K:9n;3G:1M;9m:9l(0 0 0 0);1f:-9k";e.2K=1o;z e};6 4q="17-";7 3D(e){z e.D["17-1y"]=="2z"};7 4o(e,p){z e.D[4q+p]||e.D[p]};7 2T(e,p,v){y(e.D[4q+p]==1U){e.J[4q+p]=e.D[p]}e.J[p]=v};7 4H(o,c,u){6 t=9j(7(){2C{y(!o.3q)z;o.3q(o,c,u);74(t)}2j(i){74(t)}},10)};1F.24("17-9i",7(){y(!2l)z;2r+="9h{3p-3o:9g}9f{3p-3o:1.9e;}9d{3p-3o:1.9c;}"+"9b{3p-3o:9a}99{3p-3o:.98}97{3p-3o:.96}";6 5w={};1s=11(5x.1e({1S:2s,3i:7(){8.3L.1b(1a)},2q:7(){O(6 i=0;i<8.3L.K;i++){6 m=14(8.3L[i][0]);6 f=8.3L[i][1]||6X;O(6 j=0;j<m.K;j++)f(m[j])}},1n:7(){8.1Q.1b(1a)},1u:7(){O(6 i=0;i<8.1Q.K;i++){6 m=14(8.1Q[i][0]);6 r=8.1Q[i][1],e;6 k=4g.95(2,i);O(6 j=0;
(e=m[j]);j++){6 u=e.2a;y((5w[u]&k)==0){e=r(e)||e;5w[u]|=k}}}}}));1s.3i("94");1s.1n("93",7(e){y(!e.6o){6 f=14("5l,92",e)[0];y(f){1j(e,"73",7(){f.91()})}}});1s.1n("71,5l",7(e){y(e.2W=="70"){6 m=e.3z.19(/ 3n="([^"]*)"/i);e.J.3n=(m)?m[1]:""}y(e.2L=="5v"){1j(e,"73",7(){e.J.5u=1o;32("16.1Y."+e.2a+".J.5u=1z",1)})}});6 U=/^(5v|72|71)$/;1s.1n("90",7(e){1j(e,"8Z",7(){O(6 i=0;i<e.K;i++){y(6Z(e[i])){e[i].3m=1o;32("16.1Y."+e[i].2a+".3m=1z",1)}1d y(e[i].2W=="70"&&e[i].2L=="5v"){32("16.1Y."+e[i].2a+".3n=\'"+e[i].3n+"\'",1);e[i].3n=e[i].J.3n}}})});7 6Z(e){z U.Z(e.2L)&&!e.3m&&!e.J.5u};1s.1n("5d",7(e){y(e.8Y&&!e.6Y)e.6Y=""});6 P=(29<5.5)?"8X:":"";7 6X(e){6 f=16.3X("<"+P+e.3z.1q(1));y(e.3z.1q(-2)!="/>"){6 6V="</"+e.2W+">",n;1D((n=e.6W)&&n.3z!=6V){f.6t(n)}y(n)n.8W()}e.4R.6A(f,e)}});1F.24("17-8V",7(){1K=8;2r+="*{3H:22-2X}";8.3j=(29<5.5)?7(e){z e.1I}:7(e){z e.D.3j};8.3H=7(e){y(!1K.3j(e)){e.1r.2b="6T";y(e.D.6U=="1P")e.J.6U="2y";4k(e)}};7 4k(e){y(e!=1J&&e.D.1y!="3C"){4p(e);8U(e)}};6 3l=14.1i("3l");
6 5t=14.1i("5t");7 4p(e){y(!e.J.3k){6 p=e.59;y(p&&1K.3j(p)&&e==3l(p))z;6 f=3l(e);y(f&&f.D.8T=="1M"&&1K.3j(f)){4p(f);m=5s(e,e.D.3k);c=5s(f,f.D.3k);y(m<0||c<0){e.J.3k=m+c}1d{e.J.3k=4g.3g(c,m)}f.J.3k="8S"}}};1k(23(4p).13(/5c/g,"6N").13(/4N/g,"8R"));7 5s(e,v){z(v=="1P")?0:3c(e,v)};6 U=/^[.\\d][\\w%]*$/,A=/^(1P|6T)$/,N="[.\\\\d]";6 4l,6S;7 6O(e){4l(e);6S(e)};7 2g(H){4l=7(e){y(!3d.Z(e.D.12))2g(e);4k(e)};7 2g(e,v){y(!e.J.3J){y(!v)v=e.D.12;e.J.3J=(U.Z(v))?4g.3g(0,2B(e,v)):v;2T(e,"12",e.J.3J)}};7 5r(e){y(!3D(e)){6 l=e.3B;1D(l&&!1K.3j(l))l=l.3B}z(l||1J).1I};7 1H(e,v){y(3d.Z(v))z 25(4c(v)/3w*5r(e));z 3c(e,v)};6 2B=7(e,v){6 b=e.D["2X-5o"]=="3G-2X";6 a=0;y(2m&&!b)a+=4n(e)+3K(e);1d y(!2m&&b)a-=4n(e)+3K(e);z 1H(e,v)+a};7 4n(e){z e.2S-e.1I};7 3K(e){z 1H(e,e.D.8Q)+1H(e,e.D.8P)};1k(23(3K).13(/6R/g,"6Q").13(/8O/g,"8N"));2r+="*{1A:1M;27:1M;3I-12:1M;3g-12:1M}";7 1A(e){y(e.D["3I-12"]!=1U){e.1r.1A=e.D["3I-12"]}y(20(1A,e,e.D.1A!="1M")){1K.3H(e);2g(e);4m(e)}};1k(23(1A).13(/3I/g,"3g"));1K.1A=1A;1K.27
=27;7 4m(e){6 r=e.54();6 w=r.1W-r.1f;y(e.D.1A!="1M"&&w<=2B(e,e.D.1A)){e.J.12=2B(e,e.D.1A)}1d y(e.D.27!="1M"&&w>=2B(e,e.D.27)){e.J.12=2B(e,e.D.27)}1d{e.J.12=e.J.3J}};7 2x(e){y(20(2x,e,/^(2z|3C)$/.Z(e.D.1y)&&4o(e,"1f")!="1P"&&4o(e,"1W")!="1P"&&A.Z(4o(e,"12")))){5p(e);1K.3H(e)}};1K.2x=2x;7 5p(e){6 l=1H(e,e.J.52||e.D.1f);6 w=5r(e)-1H(e,e.D.1W)-l-8M(e);y(25(e.J.12)==w)z;e.J.12="";y(3D(e)||H||e.2S<w){y(!2m)w-=4n(e)+3K(e);y(w<0)w=0;e.J.3J=w;2T(e,"12",w)}};6 5q=0;4U(7(){6 i,w=(5q<1J.1I);5q=1J.1I;O(i 28 1A.1O){6 e=1A.1O[i];6 f=(25(e.J.12)==2B(e,e.D.1A));y(w&&f)e.J.12="";y(w==f)4m(e)}O(i 28 27.1O){6 e=27.1O[i];6 f=(25(e.J.12)==2B(e,e.D.27));y(!w&&f)e.J.12="";y(w!=f)4m(e)}O(i 28 2x.1O)5p(2x.1O[i])});y(26.8L!==1z){V.1n("12",N,2m?4l:4k)}V.1n("3I-12",N,1A);V.1n("3g-12",N,27);V.1n("1W",N,2x)};V.1n("3G-6P",N,7(e){y(e.D.8K!="8J"){e.8I=3c(e,e.D["3G-6P"])}});V.1n("2X-5o","22-2X",8.3H);V.1n("2X-5o","3G-2X",6O);6 1v=11 2U;1v.15(/6v/,"6u");1v.15(/12/,"2b");1v.15(/6w/,"5c");1v.15(/1f/,"2y");1v.15(
/8H/,"6N");1v.15(/1W/,"56");1k(1v.2V(23(2g)));2g();8G(1o)});1F.24("17-8F",7(){y(29<5.5)z;6 A="6J.5n.8E";6 F="6K:"+A+"(1l=\'%1\',8D=\'6H\')";6 5j=11 1N((26.8C||"-8B.8A")+"$","i");6 3h=[];7 5f(e){6 f=e.6M[A];y(f){f.1l=e.1l;f.6L=1o}1d{e.J.5m=F.13(/%1/,e.1l);3h.1b(e)}e.1l=4i};7 6y(e){e.1l=e.4D;e.6M[A].6L=1z};V.3i(/6I\\s*:\\s*([\\d.]+)/,7(m,o){z"6G:1;5m:6K:6J.5n.8z(6I="+((4c(m[o+1])*3w)||1)+")"});6 B=/5e(-5i)?\\s*:\\s*([^\\(};]*)43\\(([^\\)]+)\\)([^;}]*)/;V.3i(B,7(m,o){6 u=42(m[o+3]);z 5j.Z(u)?"5m:"+F.13(/6H/,"8y").13(/%1/,u)+";6G:1;5e"+(m[o+1]||"")+":"+(m[o+2]||"")+"1M"+(m[o+4]||""):m[o]});y(1s){1s.1n("5d,5l",7(e){y(e.2W=="8x"&&e.2L!="5i")z;5k(e);1j(e,"8w",7(){y(!4j&&60.8v=="1l"&&e.1l.6F(4i)==-1)5k(e)})});6 6D=/^3W:.*;6E/i;6 6C=1Z("17-6E.8u",2k);7 5k(e){y(5j.Z(e.1l)){6 i=11 8t(e.12,e.2b);i.8s=7(){e.12=i.12;e.2b=i.2b;i=1U};i.1l=e.1l;e.4D=e.1l;5f(e)}1d y(6D.Z(e.1l)){e.1l=6C+"?"+e.1l.1q(5)}};6 I=/^5i/i;6 6B=1Z("17-2M.4C",2k);1s.1n("2M",7(e){y(I.Z(e.2L)){6 o=16.3X("<2M 2L=68/x-67>")
;o.1r.12=e.D.12;o.1r.2b=e.D.2b;o.3W=6B;6 u=1Z(e.3W,3F(5h.2u));e.4R.6A(o,e);14.5g("2M");4H(o,"",u);z o}})}6 4j=1z;1j(26,"6z",7(){4j=1o;O(6 i=0;i<3h.K;i++)6y(3h[i])});1j(26,"8r",7(){O(6 i=0;i<3h.K;i++)5f(3h[i]);4j=1z})});1F.24("17-2z",7(){V.1n("1y","2z",4a,"3C");V.1n("5e(-8q)?","[^};]*2z",4b);6 4Z=(2m)?"1X":"2F";6 4h=7(){y(1X.D.5b!="2z"){y(1X.D.5a=="1M"){1X.J.8p="8o-8n";1X.J.5a="43("+4i+")"}1X.J.5b="2z"}4h=2s};6 2h=6x("5d");7 1v(f){z 2A.2V(23(f))};6 2A=11 2U;2A.15(/6w/,"5c");2A.15(/1f/,"2y");2A.15(/6v/,"6u");2A.15(/12/,"2b");2A.15(/1W/,"56");2A.15(/X/,"Y");7 3f(e){z(e)?3D(e)||3f(e.59):1z};7 4f(e,p,3e){32("16.1Y."+e.2a+".J.4f(\'"+p+"\',\'"+3e+"\')",0)};7 4b(e){y(20(4b,e,e.D.5b=="2z"&&!e.61(1X))){4h();58(e);8m(e);4V(e)}};7 4V(e){2h.1l=e.D.5a.1q(5,-2);6 p=(e.6c)?e:e.59;p.6t(2h);57(e);8l(e);p.8k(2h)};7 58(e){e.1r.3E=e.D.3E;y(!3f(e)){6 3e="(25(J.3A)+16."+4Z+".6s)||0";4f(e,"3E",3e)}};1k(1v(58));7 57(e){6 p=3f(e)?"3E":"3A";e.J[p]=55(e,e.1r.3E)-e.54().1f-e.8j+2};1k(1v(57));7 55(e,p){3
4(p){1m"1f":1m"2y":z 0;1m"1W":1m"56":z 1J.1I-2h.2S;1m"8i":z(1J.1I-2h.2S)/2;8h:y(3d.Z(p)){z 25((1J.1I-2h.2S)*4c(p)/3w)}2h.1r.1f=p;z 2h.3A}};1k(1v(55));7 4a(e){y(20(4a,e,3D(e))){2T(e,"1y","3C");2T(e,"1f",e.D.1f);2T(e,"2y",e.D.2y);4h();y(1K)1K.2x(e);49(e)}};7 49(e,r){8g(e,r);4Y(e,r,1o);y(!e.J.4d&&e.D.4X=="1P"&&e.D.1W!="1P"){6 l=1J.1I-1H(e,e.D.1W)-1H(e,e.J.52)-e.1I;y(e.D.8f=="1P")l=25(l/2);y(3f(e.3B))e.J.4e+=l;1d e.J.50=l}53(e);8e(e)};7 53(e){y(e.D.12!="1P"){6 r=e.54();6 w=e.2S-1J.1I+r.1f-2;y(w>=0){w=4g.3g(3c(e,e.D.12)-w,0);2T(e,"12",w)}}};1k(1v(53));7 4Y(e,r){y(!r&&3d.Z(e.D.12)){e.J.2g=e.D.12}y(e.J.2g){e.J.12=1H(e,e.J.2g)}y(r){y(!e.J.4d)z}1d{e.J.50=0;e.J.52=e.D.1f;e.J.4d=e.D.1W!="1P"&&e.D.1f=="1P"}e.J.1f="";e.J.51=4W(e);e.J.4e=e.J.51;y(!r&&!3f(e.3B)){6 3e="J.51+J.50+16."+4Z+".6s";4f(e,"4e",3e)}};1k(1v(4Y));7 4W(e){6 s=e.3A,n=1;y(e.J.4d){s=1J.1I-e.2S-1H(e,e.D.1W)}y(e.D.4X!="1P"){s-=1H(e,e.D.4X)}1D(e=e.3B){y(e.D.1y!="8d")n=-1;s+=e.3A*n}z s};1k(1v(4W));7 1H(e,v){y(3d.Z(v))z 25(4c(
v)/3w*1J.1I);z 3c(e,v)};1k(1v(1H));7 6r(){6 e=4b.1O;O(6 i 28 e)4V(e[i]);e=4a.1O;O(i 28 e){49(e[i],1o);49(e[i],1o)}48=0};6 48;4U(7(){y(!48)48=32(6r,0)})});1F.24("17-8c-1V",7(){14.24("8b-8a",7(){1V[">"]=7(r,f,t,n){6 e,i,j;O(i=0;i<f.K;i++){6 s=6q(f[i]);O(j=0;(e=s[j]);j++)y(4T(e,t,n))r.1b(e)}};1V["+"]=7(r,f,t,n){O(6 i=0;i<f.K;i++){6 e=47(f[i]);y(e&&4T(e,t,n))r.1b(e)}};1V["@"]=7(r,f,a){6 t=2R[a].Z;6 e,i;O(i=0;(e=f[i]);i++)y(t(e))r.1b(e)};1B["4N-89"]=7(e){z!4S(e)};1B["4Q"]=7(e,c){c=11 1N("^"+c,"i");1D(e&&!e.2H("4Q"))e=e.4R;z e&&c.Z(e.2H("4Q"))};1p.6p=/\\\\:/g;1p.3x="@";1p.3a={};1p.13=7(m,a,n,c,v){6 k=8.3x+m;y(!2R[k]){a=8.3Z(a,c||"",v||"");2R[k]=a;2R.1b(a)}z 2R[k].1c};1p.38=7(s){s=s.13(8.6p,"|");6 m;1D(m=s.19(8.19)){6 r=8.13(m[0],m[1],m[2],m[3],m[4]);s=s.13(8.19,r)}z s};1p.3Z=7(p,t,v){6 a={};a.1c=8.3x+2R.K;a.66=p;t=8.3a[t];t=t?t(8.2H(p),2w(v)):1z;a.Z=11 3b("e","z "+t);z a};1p.2H=7(n){34(n.5Z()){1m"1c":z"e.1c";1m"3U":z"e.1x";1m"O":z"e.6o";1m"2u":y(4P){z"23((e.3z.19(/2u=\\\\46?([^\\\
\s\\\\46]*)\\\\46?/)||[])[1]||\'\')"}}z"e.2H(\'"+n.13(N,":")+"\')"};1p.3a[""]=7(a){z a};1p.3a["="]=7(a,v){z a+"=="+2c.15(v)};1p.3a["~="]=7(a,v){z"/(^| )"+4O(v)+"( |$)/.Z("+a+")"};1p.3a["|="]=7(a,v){z"/^"+4O(v)+"(-|$)/.Z("+a+")"};6 6n=45;45=7(s){z 6n(1p.38(s))}});6 1p=14.1i("1p");6 H=/a(#[\\w-]+)?(\\.[\\w-]+)?:(65|62)/i;6 6l=/\\s*\\{\\s*/,6m=/\\s*\\}\\s*/,C=/\\s*\\,\\s*/;6 F=/(.*)(:4N-(88|87))/;3y.1C.1e({38:7(){8.1T();6 o=V.2t.K;6 2Q=8.18.2O(6m),r;6 2f,c,i,j;O(i=0;i<2Q.K;i++){r=2Q[i].2O(6l);2f=r[0].2O(C);c=r[1];O(j=0;j<2f.K;j++){2f[j]=c?8.6k(2f[j],c):""}2Q[i]=2f.2p("\\n")}8.18=2Q.2p("\\n");8.2t=V.2t.1q(o)},1u:7(){6 r,i;O(i=0;(r=8.2t[i]);i++)r.1u()},6k:7(s,c){y(V.6j.Z(s)){6 m;y(m=s.19(1L.39)){z 11 1L(m[1],m[2],c)}1d y(m=s.19(2d.39)){y(!2l||!H.Z(m)||2d.44.Z(m)){z 11 2d(s,m[1],m[2],m[3],c)}}1d z 11 1G(s,c)}z s+" {"+c+"}"}});V.1e({2t:[],1B:14.1i("1B"),36:{},2e:14.1i("2e"),1G:1G,2d:2d,1L:1L,2J:2J,2q:7(){6 p=8.1B+"|6i|6h|"+8.36;p=p.13(/(21|37)\\|/g,"");8.6j=11 1N("[>+~\\[]|([:.])[\
\\\w-()]+\\\\1|:("+p+")");6 c="[^\\\\s(]+\\\\s*[+~]|@\\\\d+|:(";1G.44=11 1N(c+p+")","g");2d.44=11 1N(c+8.1B+")","g");2d.39=11 1N("(.*):("+8.36+")(.*)");1L.39=/(.*):(6i|6h).*/;8.1T()},1u:7(){8.2P.1u();8.1T()},2w:7(s,p){z 2v?(3T(s.2u,p)||s.18):8.1T(s)},1j:7(e,t,h){1j(e,t,h)}});7 1G(s,c){8.1c=V.2t.K;8.1x=1G.3x+8.1c;s=(s).19(F)||s||"*";8.40=s[1]||s;8.4M=1G.6g(8.40)+"."+8.1x+(s[2]||"");8.18=c;8.39=11 1N("\\\\s"+8.1x+"(\\\\s|$)","g");V.2t.1b(8);8.1S()};1t.1e({1h:1G,1g:7(){z 8.4M+" {"+8.18+"}"},1S:2s,15:7(e){e.1x+=" "+8.1x},3v:7(e){e.1x=e.1x.13(8.39,"$1")},1u:7(){6 m=V.2e[" *."+8.1x]=14(8.40);O(i=0;i<m.K;i++)8.15(m[i])}});1G.3x="5Y";1G.6f=/>/g;1G.6g=7(s){s=1p.38(s);z s.13(8.44,"").13(8.6f," ")};7 2d(s,a,d,t,c){8.6e=a||"*";8.6d=V.36[d];8.4L=t;8.1T(s,c)};1G.1e({1h:2d,1u:7(){6 m=14(8.6e);O(6 i=0;i<m.K;i++){6 t=(8.4L)?14(8.4L,m[i]):[m[i]];y(t.K)8.6d.2q(m[i],t,8)}}});6 A=/^4I/;6 U=/^43\\s*\\(\\s*([^)]*)\\)$/;6 M={86:"85",84:"83",82:"81",80:"7Z"};6 6b=1Z("17-22.4C",2k)+"?";2r+=".2K{4K:1M
}";7 1L(s,p,c){8.1y=p;6 2N=c.19(1L.6a),m,e;y(2N){2N=2N[1];m=2N.2O(/\\s+/);O(6 i=0;(e=m[i]);i++){m[i]=A.Z(e)?{4I:e.1q(5,-1)}:(e.7Y(0)=="\'")?42(e):41(e)}2N=m}8.22=2N;8.1T(s,41(c))};1G.1e({1h:1L,1g:7(){z"."+8.1x+"{4K:7X}"},1S:7(){8.19=14(8.40);O(6 i=0;i<8.19.K;i++){6 r=8.19[i].J;y(!r[8.1y])r[8.1y]={18:""};r[8.1y].18+=";"+8.18;y(8.22!=1U)r[8.1y].22=8.22}},1u:7(){y(8.22==1U)z;O(6 i=0;i<8.19.K;i++){8.3Z(8.19[i])}},3Z:7(t){6 g=t.J[8.1y];y(g){6 c=[].4J(g.22||"");O(6 j=0;j<c.K;j++){y(3Y c[j]=="2M"){c[j]=t.2H(c[j].4I)}}c=c.2p("");6 u=c.19(U);6 h=1L[u?"69":"4E"].13(/%1/,8.1x);6 4G=g.18.13(/\'/g,\'"\');6 4F=M[8.1y+7W(t.6c)];y(u){6 p=16.3X(h);t.7V(4F,p);p.3W=6b;4H(p,4G,2c.3v(u[1]))}1d{h=h.13(/%2/,4G).13(/%3/,c);t.7U(4F,h)}t.J[8.1y]=1U}}});1L.6a=/22\\s*:\\s*([^;]*)(;|$)/;1L.69="<2M 3U=\'2K %1\' 2K 12=3w% 2b=0 2L=68/x-67>";1L.4E="<17:! 3U=\'2K %1\' 2K 1r=\'%2\'>%3</17:!>";7 2J(n,a){8.66=n;8.2q=a;8.2I={};V.36[n]=8};1t.1e({1h:2J,20:7(i){6 c=i[2];i.1c=c.1c+i[0].2a;y(!8.2I[i.1c]){6 t=i[1],j;O
(j=0;j<t.K;j++)c.15(t[j]);8.2I[i.1c]=i}},35:7(i){y(8.2I[i.1c]){6 c=i[2];6 t=i[1],j;O(j=0;j<t.K;j++)c.3v(t[j]);3u 8.2I[i.1c]}}});V.1B.1g=7(){6 t=[],p;O(p 28 8){y(8[p].K>1)p+="\\\\([^)]*\\\\)";t.1b(p)}z t.2p("|")};V.1B["21"]=7(e){z e.D["17-21"]=="21"};V.1B["37"]=7(e){z e.D["17-21"]=="37"};6 64=(29<5.5)?"7T":"7S";6 63=(29<5.5)?"7R":"7Q";V.36.1g=V.1B.1g;6 3s=11 2J("65",7(e){6 i=1a;V.1j(e,64,7(){3s.20(i)});V.1j(e,63,7(){3s.35(i)})});6 3t=11 2J("7P",7(e){6 i=1a;V.1j(e,"7O",7(){3t.35(i);3t.20(i)});V.1j(e,"7N",7(){3t.35(i)});y(e==16.7M){3t.20(i)}});6 3V=11 2J("62",7(e){6 i=1a;V.1j(e,"7L",7(){3V.20(i)})});1j(16,"7K",7(){6 i=3V.2I,j;O(j 28 i)3V.35(i[j]);i=3s.2I;O(j 28 i)y(!i[j][0].61(60.7J))3s.35(i[j])});2o(1p);1p.1e({2H:7(n){34(n.5Z()){1m"3U":z"e.1x.13(/\\\\b\\\\s*5Y\\\\d+/g,\'\')";1m"1l":z"(e.4D||e.1l)"}z 8.1T(n)}});2n.15(/::/,":");3r.15(/\\\\([\\7I-7H-F]{1,4})/,7(m,o){m=m[o+1];z"\\\\u"+"7G".1q(m.K)+m})});2G=1o;y(2m)1k(3T("17-7F.5X",2k));V.1S();y(2l&&1s)1s.1S();y(33)1F.1S();1d{2F.7E
(1Z("17-3q.4C",2k));1j(16,"7D",7(){y(16.5W=="33")32(1F.1S,0)})}}2j(e){31("4B [0]: "+e.5V)}5U{}};',62,634,'||||||var|function|this||||||||||||||||||||||||||if|return||||currentStyle||||||runtimeStyle|length||||for|||||||ie7CSS||||test||new|width|replace|cssQuery|add|document|ie7|cssText|match|arguments|push|id|else|specialize|left|toString|constructor|valueOf|addEventHandler|eval|src|case|addRecalc|true|AttributeSelector|slice|style|ie7HTML|Common|recalc|_0|that|className|position|false|minWidth|pseudoClasses|prototype|while|fr|IE7|Rule|getPixelWidth|clientWidth|viewport|ie7Layout|PseudoElement|none|RegExp|elements|auto|recalcs|styleSheets|init|inherit|null|selectors|right|body|all|makePath|register|link|content|String|addModule|parseInt|window|maxWidth|in|appVersion|uniqueID|height|Quote|DynamicRule|cache|se|fixWidth|_1|klass|catch|path|isHTML|quirksMode|encoder|ICommon|join|apply|HEADER|DUMMY|rules|href|httpRequest|getText|fixRight|top|fixed|_2|getFixedWidth|try|thisElement
|ancestor|documentElement|loaded|getAttribute|instances|DynamicPseudoClass|ie7_anon|type|object|co|split|screen|ru|attributeSelectors|offsetWidth|setOverrideStyle|ParseMaster|exec|tagName|box|print|media|_3|alert|setTimeout|complete|switch|unregister|dynamicPseudoClasses|visited|parse|MATCH|tests|Function|getPixelValue|PERCENT|ex|_4|max|_5|addFix|hasLayout|marginTop|firstElementChild|disabled|value|size|font|load|safeString|_6|_7|delete|remove|100|PREFIX|StyleSheet|outerHTML|offsetLeft|offsetParent|absolute|isFixed|backgroundPositionX|getPath|border|boxSizing|min|fixedWidth|getPaddingWidth|fixes|Parser|self|styleSheet|st|modules|_8|x01|loadFile|class|_9|data|createElement|typeof|create|selector|decode|getString|url|COMPLEX|parseSelector|x22|nextElementSibling|_10|_11|_12|_13|parseFloat|autoLeft|pixelLeft|setExpression|Math|_14|BLANK_GIF|_15|_16|applyWidth|resizeWidth|getBorderWidth|getDefinedStyle|collapseMarginTop|_17|_18|_19|refresh|el|ca|getElementsByTagName|version|escap
eChar|ancestorOf|_20|Error|htc|pngSrc|ANON|po|cs|addTimer|attr|concat|display|target|selectorText|first|regEscape|isMSIE|lang|parentNode|previousElementSibling|compareTagName|addResize|_21|getScreenLeft|marginLeft|positionLeft|_22|shiftLeft|screenLeft|_23|clipWidth|getBoundingClientRect|getOffsetLeft|bottom|setOffsetLeft|backgroundLeft|parentElement|backgroundImage|backgroundAttachment|Top|img|background|_24|clearCache|location|image|_25|_26|input|filter|Microsoft|sizing|resizeRight|_27|layoutWidth|_28|lastElementChild|clicked|submit|_29|Fix|_30|decoder|_31|parser|_32|gi|_33|styles|createStyleSheet|isXML|caching|_34|compareNamespace|getDocument|continue|links|error|callee|ignoreCase|_35|_36|ie7_debug|finally|description|readyState|js|ie7_class|toLowerCase|event|contains|active|_37|_38|hover|name|scriptlet|text|OBJECT|CONTENT|_39|canHaveChildren|dynamicPseudoClass|attach|CHILD|simple|after|before|UNKNOWN|createRule|B1|B2|_40|htmlFor|NS_IE|childElements|_41|scrollLeft|appendCh
ild|Height|Width|Left|createTempElement|_42|onbeforeprint|replaceChild|_43|_44|B64|base64|indexOf|zoom|scale|opacity|DXImageTransform|progid|enabled|filters|Bottom|borderBox|spacing|margin|padding|applyHeight|0cm|verticalAlign|en|nextSibling|_45|title|_46|BUTTON|button|reset|onclick|clearInterval|PIXEL|removeEventHandler|pop|_47|_48|lastIndexOf|_49|_50|_51|_52|imports|trash|ie7_recalc|getInlineStyles|innerHTML|nodeType|ST|select|Array|_53|mimeType|Boolean|lastChild|firstChild|ES|_54|_55|_56|_57|_58|DE|status|scripts|search|onreadystatechange|addBehavior|quirks|0000|fA|da|srcElement|onmouseup|onmousedown|activeElement|onblur|onfocus|focus|onmouseleave|onmouseout|onmouseenter|onmouseover|insertAdjacentHTML|insertAdjacentElement|Number|inline|charAt|beforeEnd|after1|afterEnd|after0|afterBegin|before1|beforeBegin|before0|letter|line|child|level2|css|css2|static|clipHeight|marginRight|positionTop|default|center|clientLeft|removeChild|setOffsetTop|backgroundTop|repeat|no|backgroun
dRepeat|attachment|onafterprint|onload|Image|php|propertyName|onpropertychange|INPUT|crop|Alpha|png|trans|IE7_PNG_SUFFIX|sizingMethod|AlphaImageLoader|graphics|fixHeight|Right|cellSpacing|collapse|borderCollapse|IE7_BOX_MODEL|getMarginWidth|Margin|Padding|paddingRight|paddingLeft|last|0px|styleFloat|collapseMarginBottom|layout|removeNode|HTML|alt|onsubmit|form|click|textarea|label|abbr|pow|67em|h6|83em|h5|1em|h4|17em|h3|5em|h2|2em|h1|html4|setInterval|9999|rect|clip|block|px|onunload|detachEvent|attachEvent|onresize|x27|import|namespace|ma|bprint|bscreen|ball|write|item|innerText|getTextContent|scopeName|toUpperCase|ownerDocument|Document|XML|URL|xml|unknown|previousSibling|number|successfully|ie5|instanceOf|Object|common|caller|gif|blank|file|loading|responseText|200|send|GET|open|XMLHTTP|ActiveXObject|Msxml2|ScriptEngineMajorVersion|ms_|ie7_off|CSS1Compat|compatMode|MSIE|navigator|alpha'.split('|'),0,{}))
diff --git a/web/static/js/ie7/ie7-xml-extras.js b/web/static/js/ie7/ie7-xml-extras.js
new file mode 100644
index 0000000..97846f6
--- /dev/null
+++ b/web/static/js/ie7/ie7-xml-extras.js
@@ -0,0 +1,6 @@
+/*
+ IE7, version 0.9 (alpha) (2005-08-19)
+ Copyright: 2004-2005, Dean Edwards (http://dean.edwards.name/)
+ License: http://creativecommons.org/licenses/LGPL/2.1/
+*/
+function XMLHttpRequest(){var l=(ScriptEngineMajorVersion()>=5)?"Msxml2":"Microsoft";return new ActiveXObject(l+".XMLHTTP")};function DOMParser(){};DOMParser.prototype={toString:function(){return"[object DOMParser]"},parseFromString:function(s,c){var x=new ActiveXObject("Microsoft.XMLDOM");x.loadXML(s);return x},parseFromStream:new Function,baseURI:""};function XMLSerializer(){};XMLSerializer.prototype={toString:function(){return"[object XMLSerializer]"},serializeToString:function(r){return r.xml||r.outerHTML},serializeToStream:new Function};
diff --git a/web/static/js/ie7/ie7.gif b/web/static/js/ie7/ie7.gif
new file mode 100644
index 0000000..64a2c2d
Binary files /dev/null and b/web/static/js/ie7/ie7.gif differ
diff --git a/web/static/js/ie7/test-trans.png b/web/static/js/ie7/test-trans.png
new file mode 100644
index 0000000..e187e2c
Binary files /dev/null and b/web/static/js/ie7/test-trans.png differ
diff --git a/web/static/js/ie7/test.html b/web/static/js/ie7/test.html
new file mode 100644
index 0000000..ab78f46
--- /dev/null
+++ b/web/static/js/ie7/test.html
@@ -0,0 +1,100 @@
+<html xmlns:html="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<title>IE7 Test Page</title>
+<meta name="author" content="Dean Edwards"/>
+<!-- compliance patch for microsoft browsers -->
+<!--[if lt IE 7]>
+<script src="ie7-standard-p.js" type="text/javascript"></script>
+<script src="ie7-css3-selectors.js" type="text/javascript"></script>
+<script src="ie7-css-strict.js" type="text/javascript"></script>
+<![endif]-->
+<style type="text/css">
+ body {background-color: #ccc;}
+ img {border: none;}
+ h1 {font-family: monospace;}
+ h2 {background-color: black; color: white; font-style: normal;}
+ h3 {margin: 0.1em 0;}
+</style>
+</head>
+
+<body>
+<div class="document">
+<div class="header">
+<h1>IE7 { css2: auto; }</h1>
+<hr />
+</div>
+
+<div class="content">
+
+<h2>Black & White Test</h2>
+
+<h3>Legend</h3>
+<style type="text/css">
+ div.legend {height: 20px; font-weight: bold; text-indent: 4px;}
+ #fail {background-color: black; color: white;}
+ #pass {background-color: white; color: black;}
+</style>
+<div class="legend" id="pass">PASS</div>
+<div class="legend" id="fail">FAIL</div>
+
+<hr />
+
+<h3>ie7-html4.js</h3>
+<style type="text/css">
+ #ie7-html4 {background-color: black; height: 20px;}
+ #ie7-html4 abbr {display: block; background-color: white; height: 20px;}
+</style>
+<div id="ie7-html4"><abbr> </abbr></div>
+
+<h3>ie7-layout.js</h3>
+<style type="text/css">
+ #ie7-layout {background-color: black; height: 20px; overflow: hidden;}
+ #ie7-layout div.box {position: relative; top: -40px; background-color: white;
+ height: 40px; border-top: 20px black solid;}
+</style>
+<div id="ie7-layout"><div class="box"></div></div>
+
+<h3>ie7-graphics.js</h3>
+<style type="text/css">
+ #ie7-graphics {background-color: white; height: 20px;}
+ #ie7-graphics div.box {height: 20px; background: url(test-trans.png);}
+</style>
+<div id="ie7-graphics"><div class="box"></div></div>
+
+<h3>ie7-fixed.js</h3>
+<style type="text/css">
+ #ie7-fixed {background-color: white; height: 20px;}
+ #ie7-fixed div.box {position: fixed; top: -20px; background-color: black; height: 20px;}
+</style>
+<div id="ie7-fixed"><div class="box"></div></div>
+
+<h3>ie7-css2-selectors.js</h3>
+<style type="text/css">
+ #ie7-css2-selectors {background-color: black; height: 20px;}
+ #ie7-css2-selectors > span {display: block; background-color: white; height: 20px;}
+</style>
+<div id="ie7-css2-selectors"><span> </span></div>
+
+<h3>ie7-css3-selectors.js</h3>
+<style type="text/css">
+ #ie7-css3-selectors {background-color: black; height: 20px;}
+ #ie7-css3-selectors:empty {background-color: white;}
+</style>
+<div id="ie7-css3-selectors"></div>
+
+<h3>ie7-css-strict.js</h3>
+<style type="text/css">
+ #ie7-css-strict {background-color: black; height: 20px;}
+ #ie7-css-strict > span.strict {display: block; background-color: white; height: 20px;}
+ #ie7-css-strict > span {display: block; background-color: black}
+</style>
+<div id="ie7-css-strict"><span class="strict"></span></div>
+</div>
+
+<div class="footer">
+<hr />
+<a href="http://dean.edwards.name/IE7/"><img src="ie7.gif" width="80" height="15" alt="IE7 Enhanced"/></a>
+</div>
+</div>
+</body>
+</html>
diff --git a/web/static/js/jifty.js b/web/static/js/jifty.js
new file mode 100644
index 0000000..e674eb4
--- /dev/null
+++ b/web/static/js/jifty.js
@@ -0,0 +1,333 @@
+/* An empty class so we can create things inside it */
+var Jifty = Class.create();
+
+/* General methods for dealing with forms, actions, and fields */
+/* Actions */
+var Action = Class.create();
+Action.prototype = {
+ // New takes the moniker, a string
+ initialize: function(moniker) {
+ this.moniker = moniker;
+
+ this.register = $('J:A-' + this.moniker); // Simple case -- no ordering information
+ if (! this.register) {
+ // We need to go looking
+ var elements = document.getElementsByTagName('input');
+ for (var i = 0; i < elements.length; i++) {
+ if (Form.Element.getMoniker(elements[i]) == this.moniker) {
+ this.register = elements[i];
+ break;
+ }
+ }
+ }
+
+ this.form = Form.Element.getForm(this.register);
+ this.actionClass = this.register.value;
+ },
+
+ // Returns an Array of all fields in this Action
+ fields: function() {
+ var elements = new Array;
+ var possible = Form.getElements(this.form);
+
+ for (var i = 0; i < possible.length; i++) {
+ if (Form.Element.getMoniker(possible[i]) == this.moniker)
+ elements.push(possible[i]);
+ }
+ return elements;
+ },
+
+ // Serialize and return all fields needed for this action
+ serialize: function() {
+ var fields = this.fields();
+ var serialized = new Array;
+
+ for (var i = 0; i < fields.length; i++) {
+ serialized.push(Form.Element.serialize(fields[i].id));
+ }
+ return serialized.join('&');
+ },
+
+ // Validate the action
+ validate: function() {
+ show_wait_message();
+ var id = this.register.id;
+
+ new Ajax.Request(
+ '/validator.xml', // Right now, the URL is actually completely irrelevant
+ {
+ asynchronous: 1,
+ method: "get",
+ parameters: this.serialize() + "&J:VALIDATE=1",
+ onComplete:
+ function (request) {
+ var response = request.responseXML.documentElement;
+ for (var action = response.firstChild; action != null; action = action.nextSibling) {
+ if ((action.nodeName != 'action') || (action.getAttribute("id") != id))
+ continue;
+ for (var field = action.firstChild; field != null; field = field.nextSibling) {
+ // Possibilities for field.nodeName: it could be #text (whitespace),
+ // or 'blank' (the field was blank, don't mess with the error div), or 'ok'
+ // (clear the error div!) or 'error' (fill in the error div!)
+ if (field.nodeName == 'error') {
+ var err_div = document.getElementById(field.getAttribute("id"));
+ if (err_div != null) {
+ err_div.innerHTML = field.firstChild.data;
+ }
+ } else if (field.nodeName == 'ok') {
+ var err_div = document.getElementById(field.getAttribute("id"));
+ if (err_div != null) {
+ err_div.innerHTML = '';
+ }
+ }
+ }
+ }
+ return true;
+ }
+ }
+ );
+ hide_wait_message();
+ return false;
+ },
+
+ submit: function() {
+ show_wait_message();
+ new Ajax.Request(
+ '/empty',
+ { parameters: this.serialize() }
+ );
+ hide_wait_message();
+ }
+};
+
+
+
+/* Forms */
+// Return an Array of Actions that are in this form
+Form.getActions = function (element) {
+ var elements = new Array;
+ var possible = Form.getElements(element);
+
+ for (var i = 0; i < possible.length; i++) {
+ if (Form.Element.isRegistration(possible[i]))
+ elements.push(new Action(Form.Element.getMoniker(possible[i])));
+ }
+
+ return elements;
+};
+
+/* Fields */
+// Get the moniker for this form element
+// Takes an element or an element id
+Form.Element.getMoniker = function (element) {
+ // if we have an element id, get the element itself
+ if (typeof(element) == "string") {
+ element = $(element);
+ }
+ if (/^J:A:F(:F)*-[^-]+-.+$/.test(element.name)) {
+ var bits = element.name.match(/^J:A:F(?::F)*-[^-]+-(.+)$/);
+ return bits[1];
+ } else if (/^J:A-(\d+-)?.+$/.test(element.name)) {
+ var bits = element.name.match(/^J:A-(?:\d+-)?(.+)$/);
+ return bits[1];
+ } else {
+ return null;
+ }
+};
+
+// Get the Action for this form element
+// Takes an element or an element id
+Form.Element.getAction = function (element) {
+ // if we have an element id, get the element itself
+ if (typeof(element) == "string") {
+ element = $(element);
+ }
+ var moniker = Form.Element.getMoniker(element);
+ return new Action(moniker);
+}
+
+// Returns true if this form element is the registration for its action
+Form.Element.isRegistration = function (element) {
+ return /^J:A-/.test(element.name)
+};
+
+// Validates the action this form element is part of
+Form.Element.validate = function (element) {
+ Form.Element.getAction(element).validate();
+};
+
+// Form elements should AJAX validate if the CSS says so
+Behaviour.register({
+ 'input.ajaxvalidation': function(elt) {
+ elt.onblur = function () {
+ Form.Element.validate(this);
+ }
+ }
+});
+
+// Look up the form that this element is part of -- this is sometimes
+// more complicated than you'd think because the form may not exist
+// anymore, or the element may have been inserted into a new form.
+// Hence, we may need to walk the DOM.
+Form.Element.getForm = function (element) {
+ if (element.form)
+ return element.form;
+
+ for (var elt = element.parentNode; elt != null; elt = elt.parentNode) {
+ if (elt.nodeName == 'FORM') {
+ element.form = elt;
+ return elt;
+ }
+ }
+ return null;
+}
+
+function serialize(thing) {
+ var serialized = new Array;
+ for (n in thing) {
+ if (typeof(thing[n]) == "string" && thing[n].length)
+ serialized.push(encodeURIComponent(n) + '=' +
+ encodeURIComponent(thing[n]));
+ }
+ return serialized.join('&');
+}
+
+var fragments = {};
+var current_args = {};
+function region(name, args, path) {
+ fragments[name] = {name: name, args: args, path: path};
+ current_args[name] = {};
+}
+
+function update_region() {
+ show_wait_message();
+ arguments = arguments[0];
+ var name = arguments['name'];
+
+ var args = {};
+ for (var n in fragments[name].args) {
+ args[n] = fragments[name].args[n];
+ }
+ for (var n in current_args) {
+ if (typeof(current_args[n]) == "string") {
+ args[n] = current_args[n];
+ var parsed = n.match(/J:NV-region-(.*?)\.(.*)/);
+
+ if ((parsed != null) && (parsed.length == 3) && (parsed[1] == name)) {
+ args[parsed[2]] = current_args[n];
+ }
+ }
+ }
+ for (var n in arguments['args']) {
+ args[n] = arguments['args'][n];
+ if (n.indexOf('J:NV-') != 0) {
+ current_args['J:NV-region-'+name+'.'+n] = args[n];
+ args['J:NV-region-'+name+'.'+n] = args[n];
+ }
+ }
+ var path;
+ if (arguments['fragment'] != null) {
+ path = arguments['fragment'];
+ } else {
+ path = fragments[name].path;
+ }
+ args['J:NV-region-'+name] = path;
+ current_args['J:NV-region-'+name] = path;
+
+ for (var i = 0; i < document.forms.length; i++) {
+ var form = document.forms[i];
+ for (var n in args) {
+ if ((typeof(args[n]) == "string") && (/^J:NV-/.test(n))) {
+ if (form[n]) {
+ form[n].value = args[n];
+ } else {
+ var hidden = document.createElement('input');
+ hidden.setAttribute('type', 'hidden');
+ hidden.setAttribute('name', n);
+ hidden.setAttribute('id', n);
+ hidden.setAttribute('value', args[n]);
+ form.appendChild(hidden);
+ }
+ }
+ }
+ }
+
+ args['J-NAME'] = name;
+ args['J-PATH'] = document.URL;
+
+ var query = serialize(args);
+ if (arguments['submit']) {
+ var a = new Action(arguments['submit']);
+ query = query + '&' + a.serialize();
+ }
+
+ new Ajax.Updater('region-' + name,
+ path,
+ { parameters: query,
+ onComplete: function () { Behaviour.apply();
+ hide_wait_message();
+ },
+ evalScripts: true }
+ );
+}
+
+function trace( msg ){
+ if( typeof( jsTrace ) != 'undefined' ){
+ jsTrace.send( msg );
+ }
+}
+
+
+function show_wait_message (){
+ chunk = document.getElementById('jifty-wait-message');
+ if (chunk) { chunk.style.display= 'block';}
+}
+
+function hide_wait_message (){
+
+ chunk = document.getElementById('jifty-wait-message');
+ if (chunk) { chunk.style.display = "none";}
+}
+
+
+
+Jifty.Autocompleter = Class.create();
+Object.extend(Object.extend(Jifty.Autocompleter.prototype, Ajax.Autocompleter.prototype), {
+ initialize: function(element, update, url, options) {
+ this.baseInitialize(element, update, options);
+ this.options.asynchronous = true;
+ this.options.onComplete = this.onComplete.bind(this);
+ this.options.defaultParams = this.options.parameters || null;
+ this.url = url;
+ },
+
+ getUpdatedChoices: function() {
+ entry = encodeURIComponent("J:A-autocomplete")
+ + "=" +encodeURIComponent("Jifty::Action::Autocomplete");
+
+ entry += '&' + encodeURIComponent("J:A:F-argument-autocomplete")
+ + "=" + encodeURIComponent(this.options.paramName);
+
+ entry += '&' + encodeURIComponent("J:A:F-action-autocomplete")
+ + "=" + encodeURIComponent(
+ Form.Element.getMoniker(this.options.paramName)
+ );
+
+ entry += '&'+ encodeURIComponent("J:ACTIONS") + '=' + encodeURIComponent("autocomplete");
+
+
+ this.options.parameters = this.options.callback ?
+ this.options.callback(this.element, entry) : entry;
+
+ if(this.options.defaultParams)
+ this.options.parameters += '&' + this.options.defaultParams;
+
+ var action = Form.Element.getAction(this.options.paramName);
+ this.options.parameters += '&' + action.serialize();
+
+ new Ajax.Request(this.url, this.options);
+ }
+
+
+});
+
diff --git a/web/static/js/jsTrace.js b/web/static/js/jsTrace.js
new file mode 100644
index 0000000..5e8c93e
--- /dev/null
+++ b/web/static/js/jsTrace.js
@@ -0,0 +1,12 @@
+/*------------------------------------------------------------------------------
+Function: jsTrace()
+Author: Aaron Gustafson (aaron at easy-designs dot net)
+Creation Date: 26 October 2005
+Version: 1.0
+Homepage: http://www.easy-designs.net/code/jsTrace/
+License: Creative Commons Attribution-ShareAlike 2.0 License
+ http://creativecommons.org/licenses/by-sa/2.0/
+Note: If you change or improve on this script, please let us know by
+ emailing the author (above) with a link to your demo page.
+------------------------------------------------------------------------------*/
+var jsTrace = { debugging_on: false, window: null, viewport: null, init: function(){ if( !document.getElementsByTagName || !document.getElementById || !document.createElement || !document.createTextNode ) return; jsTrace.createWindow(); jsTrace.debugging_on = true;}, createWindow: function(){ jsTrace.window = document.createElement( 'div' ); jsTrace.window.style.background = '#000'; jsTrace.window.style.font = '80% "Lucida Grande", "Lucida Sans Unicode", Helvetica, Arial, sans-serif'; jsTrace.window.style.padding = '2px'; jsTrace.window.style.position = 'absolute'; jsTrace.window.style.top = '50px'; jsTrace.window.style.left = '700px'; jsTrace.window.style.height = '360px'; jsTrace.window.style.zIndex = '100'; jsTrace.window.style.minHeight = '150px'; jsTrace.window.style.width = '190px'; jsTrace.window.style.minWidth = '150px'; var x = document.createElement('span'); x.style.border = '1px solid #000'; x.style.cursor = 'pointer'; x.style.color = '#000'; x.style.display = 'bl
ock'; x.style.lineHeight = '.5em'; x.style.padding = '0 0 3px'; x.style.position = 'absolute'; x.style.top = '4px'; x.style.right = '4px'; jsTrace.addEvent( x, 'click', function(){ jsTrace.kill();} ); x.setAttribute( 'title', 'Close jsTrace Debugger' ); x.appendChild( document.createTextNode( 'x' ) ); jsTrace.window.appendChild( x ); var sh = document.createElement('div'); sh.style.position = 'absolute'; sh.style.bottom = '3px'; sh.style.right = '3px'; var sg = document.createElement('span'); sg.style.border = '5px solid #ccc'; sg.style.borderLeftColor = sg.style.borderTopColor = '#000'; sg.style.cursor = 'pointer'; sg.style.color = '#ccc'; sg.style.display = 'block'; sg.style.height = '0'; sg.style.width = '0'; sg.style.overflow = 'hidden'; sg.setAttribute( 'title', 'Resize the jsTrace Debugger' ); if( typeof( Drag ) != 'undefined' ){ sg.xFrom = 0; sg.yFrom = 0; Drag.init( sg, null, null, null, null, null, true, true ); sg.onDrag = function( x, y ){ jsTrace.resizeX( x, this
); jsTrace.resizeY( y, this );}; sh.appendChild( sg ); jsTrace.window.appendChild( sh );} var tools = document.createElement( 'div' ); tools.style.fontSize = '.7em'; tools.style.fontVariant = 'small-caps'; tools.style.lineHeight = '10px'; tools.style.position = 'absolute'; tools.style.bottom = '5px'; tools.style.left = '3px'; var dl = document.createElement( 'span' ); dl.style.color = '#ccc'; dl.style.padding = '0 10px 0 0'; dl.style.overflow = 'hidden'; dl.style.cursor = 'pointer'; dl.setAttribute( 'title', 'Add a Delimeter' ); dl.appendChild( document.createTextNode( 'delimit' ) ); jsTrace.addEvent( dl, 'click', function(){ jsTrace.sendDelimeter();} ); tools.appendChild( dl ); var cl = document.createElement( 'span' ); cl.style.color = '#ccc'; cl.style.padding = '0 10px 0 0'; cl.style.overflow = 'hidden'; cl.style.cursor = 'pointer'; cl.setAttribute( 'title', 'Add a Delimeter' ); cl.appendChild( document.createTextNode( 'clear' ) ); jsTrace.addEvent( cl, 'click', function
(){ jsTrace.clearWindow();} ); tools.appendChild( cl ); jsTrace.window.appendChild( tools ); var header = document.createElement( 'h3' ); header.style.background = '#ccc'; header.style.color = '#000'; header.style.cursor = 'pointer'; header.style.fontSize = '1em'; header.style.fontVariant = 'small-caps'; header.style.margin = '0 0 2px'; header.style.padding = '5px 10px'; header.style.lineHeight = '15px'; header.appendChild( document.createTextNode( 'jsTrace Debugger' ) ); jsTrace.window.appendChild( header ); jsTrace.viewport = document.createElement( 'pre' ); jsTrace.viewport.style.border = '1px solid #ccc'; jsTrace.viewport.style.color = '#ebebeb'; jsTrace.viewport.style.fontSize = '1.2em'; jsTrace.viewport.style.margin = '0'; jsTrace.viewport.style.padding = '0 3px'; jsTrace.viewport.style.position = 'absolute'; jsTrace.viewport.style.top = '30px'; jsTrace.viewport.style.left = '2px'; jsTrace.viewport.style.overflow = 'auto'; jsTrace.viewport.style.width = ( parseInt( jsT
race.window.style.width ) - 8 ) + 'px'; jsTrace.viewport.style.height = ( parseInt( jsTrace.window.style.height ) - 45 ) + 'px'; jsTrace.window.appendChild( jsTrace.viewport ); document.getElementsByTagName( 'body' )[0].appendChild( jsTrace.window ); if( typeof( Drag ) != 'undefined' ){ Drag.init( header, jsTrace.window );} }, resizeX: function( x, grip ){ var width = parseInt( jsTrace.window.style.width ); var newWidth = Math.abs( width - ( x - grip.xFrom ) ) + 'px'; if( parseInt( newWidth ) < parseInt( jsTrace.window.style.minWidth ) ) newWidth = jsTrace.window.style.minWidth; jsTrace.window.style.width = newWidth; grip.xFrom = x; jsTrace.viewport.style.width = ( parseInt( jsTrace.window.style.width ) - 8 ) + 'px';}, resizeY: function( y, grip ){ var height = parseInt( jsTrace.window.style.height ); var newHeight = Math.abs( height - ( y - grip.yFrom ) ) + 'px'; if( parseInt( newHeight ) < parseInt( jsTrace.window.style.minHeight ) ) newHeight = jsTrace.window.style.minHei
ght; jsTrace.window.style.height = newHeight; grip.yFrom = y; jsTrace.viewport.style.height = ( parseInt( jsTrace.window.style.height ) - 45 ) + 'px';}, send: function( text ){ text = text + "<br />"; jsTrace.viewport.innerHTML += text;}, sendDelimeter: function(){ jsTrace.send( '<span style="color: #f00">--------------------</span>' );}, clearWindow: function(){ jsTrace.viewport.innerHTML = '';}, kill: function() { jsTrace.window.parentNode.removeChild( jsTrace.window ); jsTrace.debugging_on = false;}, addEvent: function( obj, type, fn ){ if (obj.addEventListener) obj.addEventListener( type, fn, false ); else if (obj.attachEvent) { obj["e"+type+fn] = fn; obj[type+fn] = function() { obj["e"+type+fn]( window.event );}; obj.attachEvent( "on"+type, obj[type+fn] );} }, removeEvent: function ( obj, type, fn ) { if (obj.removeEventListener) obj.removeEventListener( type, fn, false ); else if (obj.detachEvent) { obj.detachEvent( "on"+type, obj[type+fn] ); obj[type+fn] = null; obj["
e"+type+fn] = null;} } }; jsTrace.addEvent( window, 'load', jsTrace.init );
\ No newline at end of file
diff --git a/web/static/js/key_bindings.js b/web/static/js/key_bindings.js
new file mode 100644
index 0000000..c480f0c
--- /dev/null
+++ b/web/static/js/key_bindings.js
@@ -0,0 +1,69 @@
+// Copyright 2004-2005, Best Practical Solutions, LLC
+// This Library is licensed to you under the same terms as Perl 5.x
+
+var bindings = Array;
+
+document.onkeydown = doClick;
+function doClick(e) {
+ var targ;
+ if (!e) var e = window.event;
+ if (e.target) targ = e.target;
+ else if (e.srcElement) targ = e.srcElement;
+ if (targ.nodeType == 3) // defeat Safari bug
+ targ = targ.parentNode;
+
+ // safari or mozilla
+ if ( ( ! e.metaKey && ! e.altKey && ! e.ctrlKey )
+ && (
+ (targ == document.body) ||
+ (targ == document.getElementsByTagName('html')[0])
+ ) ){
+ var code = String.fromCharCode(e.keyCode);
+ var binding = getKeyBinding(code);
+ if (binding) {
+ if (binding["action"] == "goto") {
+ document.location = (binding["data"]);
+ }
+ else if (binding["action"] == "focus") {
+ var elements = document.getElementsByName(binding["data"]);
+ elements[0].focus();
+ }
+ else if (binding["action"] == "click") {
+ var elements = document.getElementsByName(binding["data"]);
+ elements[0].click();
+ }
+
+ }
+
+}
+}
+
+function addKeyBinding(key, action, data, label) {
+ var binding = new Array;
+ binding["action"] = action;
+ binding["data"] = data;
+ binding["label"] = label;
+ bindings[key] = binding;
+}
+
+
+function getKeyBinding(key) {
+ return(bindings[key]);
+}
+
+
+function writeKeyBindingLegend() {
+ var content = '';
+ for (var key in bindings) {
+ if ( bindings[key]['label']) {
+ content = content + '<dt>'+key + '</dt>' +'<dd>'+bindings[key]['label'] +'</dd>';
+ }
+ }
+ if (content) {
+ document.write('<div class="keybindings">');
+ document.write('<dl class="keybindings">');
+ document.write(content);
+ document.write('</dl>');
+ document.write('</div>');
+ }
+}
diff --git a/web/static/js/prototype.js b/web/static/js/prototype.js
new file mode 100644
index 0000000..fbdab91
--- /dev/null
+++ b/web/static/js/prototype.js
@@ -0,0 +1,1038 @@
+/* Prototype JavaScript framework, version 1.3.1
+ * (c) 2005 Sam Stephenson <sam at conio.net>
+ *
+ * THIS FILE IS AUTOMATICALLY GENERATED. When sending patches, please diff
+ * against the source tree, available from the Prototype darcs repository.
+ *
+ * Prototype is freely distributable under the terms of an MIT-style license.
+ *
+ * For details, see the Prototype web site: http://prototype.conio.net/
+ *
+/*--------------------------------------------------------------------------*/
+
+var Prototype = {
+ Version: '1.3.1',
+ emptyFunction: function() {}
+}
+
+var Class = {
+ create: function() {
+ return function() {
+ this.initialize.apply(this, arguments);
+ }
+ }
+}
+
+var Abstract = new Object();
+
+Object.extend = function(destination, source) {
+ for (property in source) {
+ destination[property] = source[property];
+ }
+ return destination;
+}
+
+Object.prototype.extend = function(object) {
+ return Object.extend.apply(this, [this, object]);
+}
+
+Function.prototype.bind = function(object) {
+ var __method = this;
+ return function() {
+ __method.apply(object, arguments);
+ }
+}
+
+Function.prototype.bindAsEventListener = function(object) {
+ var __method = this;
+ return function(event) {
+ __method.call(object, event || window.event);
+ }
+}
+
+Number.prototype.toColorPart = function() {
+ var digits = this.toString(16);
+ if (this < 16) return '0' + digits;
+ return digits;
+}
+
+var Try = {
+ these: function() {
+ var returnValue;
+
+ for (var i = 0; i < arguments.length; i++) {
+ var lambda = arguments[i];
+ try {
+ returnValue = lambda();
+ break;
+ } catch (e) {}
+ }
+
+ return returnValue;
+ }
+}
+
+/*--------------------------------------------------------------------------*/
+
+var PeriodicalExecuter = Class.create();
+PeriodicalExecuter.prototype = {
+ initialize: function(callback, frequency) {
+ this.callback = callback;
+ this.frequency = frequency;
+ this.currentlyExecuting = false;
+
+ this.registerCallback();
+ },
+
+ registerCallback: function() {
+ setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
+ },
+
+ onTimerEvent: function() {
+ if (!this.currentlyExecuting) {
+ try {
+ this.currentlyExecuting = true;
+ this.callback();
+ } finally {
+ this.currentlyExecuting = false;
+ }
+ }
+ }
+}
+
+/*--------------------------------------------------------------------------*/
+
+function $() {
+ var elements = new Array();
+
+ for (var i = 0; i < arguments.length; i++) {
+ var element = arguments[i];
+ if (typeof element == 'string')
+ element = document.getElementById(element);
+
+ if (arguments.length == 1)
+ return element;
+
+ elements.push(element);
+ }
+
+ return elements;
+}
+
+if (!Array.prototype.push) {
+ Array.prototype.push = function() {
+ var startLength = this.length;
+ for (var i = 0; i < arguments.length; i++)
+ this[startLength + i] = arguments[i];
+ return this.length;
+ }
+}
+
+if (!Function.prototype.apply) {
+ // Based on code from http://www.youngpup.net/
+ Function.prototype.apply = function(object, parameters) {
+ var parameterStrings = new Array();
+ if (!object) object = window;
+ if (!parameters) parameters = new Array();
+
+ for (var i = 0; i < parameters.length; i++)
+ parameterStrings[i] = 'parameters[' + i + ']';
+
+ object.__apply__ = this;
+ var result = eval('object.__apply__(' +
+ parameterStrings.join(', ') + ')');
+ object.__apply__ = null;
+
+ return result;
+ }
+}
+
+String.prototype.extend({
+ stripTags: function() {
+ return this.replace(/<\/?[^>]+>/gi, '');
+ },
+
+ escapeHTML: function() {
+ var div = document.createElement('div');
+ var text = document.createTextNode(this);
+ div.appendChild(text);
+ return div.innerHTML;
+ },
+
+ unescapeHTML: function() {
+ var div = document.createElement('div');
+ div.innerHTML = this.stripTags();
+ return div.childNodes[0].nodeValue;
+ }
+});
+
+var Ajax = {
+ getTransport: function() {
+ return Try.these(
+ function() {return new ActiveXObject('Msxml2.XMLHTTP')},
+ function() {return new ActiveXObject('Microsoft.XMLHTTP')},
+ function() {return new XMLHttpRequest()}
+ ) || false;
+ }
+}
+
+Ajax.Base = function() {};
+Ajax.Base.prototype = {
+ setOptions: function(options) {
+ this.options = {
+ method: 'post',
+ asynchronous: true,
+ parameters: ''
+ }.extend(options || {});
+ },
+
+ responseIsSuccess: function() {
+ return this.transport.status == undefined
+ || this.transport.status == 0
+ || (this.transport.status >= 200 && this.transport.status < 300);
+ },
+
+ responseIsFailure: function() {
+ return !this.responseIsSuccess();
+ }
+}
+
+Ajax.Request = Class.create();
+Ajax.Request.Events =
+ ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
+
+Ajax.Request.prototype = (new Ajax.Base()).extend({
+ initialize: function(url, options) {
+ this.transport = Ajax.getTransport();
+ this.setOptions(options);
+ this.request(url);
+ },
+
+ request: function(url) {
+ var parameters = this.options.parameters || '';
+ if (parameters.length > 0) parameters += '&_=';
+
+ try {
+ if (this.options.method == 'get')
+ url += '?' + parameters;
+
+ this.transport.open(this.options.method, url,
+ this.options.asynchronous);
+
+ if (this.options.asynchronous) {
+ this.transport.onreadystatechange = this.onStateChange.bind(this);
+ setTimeout((function() {this.respondToReadyState(1)}).bind(this), 10);
+ }
+
+ this.setRequestHeaders();
+
+ var body = this.options.postBody ? this.options.postBody : parameters;
+ this.transport.send(this.options.method == 'post' ? body : null);
+
+ } catch (e) {
+ }
+ },
+
+ setRequestHeaders: function() {
+ var requestHeaders =
+ ['X-Requested-With', 'XMLHttpRequest',
+ 'X-Prototype-Version', Prototype.Version];
+
+ if (this.options.method == 'post') {
+ requestHeaders.push('Content-type',
+ 'application/x-www-form-urlencoded');
+
+ /* Force "Connection: close" for Mozilla browsers to work around
+ * a bug where XMLHttpReqeuest sends an incorrect Content-length
+ * header. See Mozilla Bugzilla #246651.
+ */
+ if (this.transport.overrideMimeType)
+ requestHeaders.push('Connection', 'close');
+ }
+
+ if (this.options.requestHeaders)
+ requestHeaders.push.apply(requestHeaders, this.options.requestHeaders);
+
+ for (var i = 0; i < requestHeaders.length; i += 2)
+ this.transport.setRequestHeader(requestHeaders[i], requestHeaders[i+1]);
+ },
+
+ onStateChange: function() {
+ var readyState = this.transport.readyState;
+ if (readyState != 1)
+ this.respondToReadyState(this.transport.readyState);
+ },
+
+ respondToReadyState: function(readyState) {
+ var event = Ajax.Request.Events[readyState];
+
+ if (event == 'Complete')
+ (this.options['on' + this.transport.status]
+ || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')]
+ || Prototype.emptyFunction)(this.transport);
+
+ (this.options['on' + event] || Prototype.emptyFunction)(this.transport);
+
+ /* Avoid memory leak in MSIE: clean up the oncomplete event handler */
+ if (event == 'Complete')
+ this.transport.onreadystatechange = Prototype.emptyFunction;
+ }
+});
+
+Ajax.Updater = Class.create();
+Ajax.Updater.ScriptFragment = '(?:<script.*?>)((\n|.)*?)(?:<\/script>)';
+
+Ajax.Updater.prototype.extend(Ajax.Request.prototype).extend({
+ initialize: function(container, url, options) {
+ this.containers = {
+ success: container.success ? $(container.success) : $(container),
+ failure: container.failure ? $(container.failure) :
+ (container.success ? null : $(container))
+ }
+
+ this.transport = Ajax.getTransport();
+ this.setOptions(options);
+
+ var onComplete = this.options.onComplete || Prototype.emptyFunction;
+ this.options.onComplete = (function() {
+ this.updateContent();
+ onComplete(this.transport);
+ }).bind(this);
+
+ this.request(url);
+ },
+
+ updateContent: function() {
+ var receiver = this.responseIsSuccess() ?
+ this.containers.success : this.containers.failure;
+
+ var match = new RegExp(Ajax.Updater.ScriptFragment, 'img');
+ var response = this.transport.responseText.replace(match, '');
+ var scripts = this.transport.responseText.match(match);
+
+ if (receiver) {
+ if (this.options.insertion) {
+ new this.options.insertion(receiver, response);
+ } else {
+ receiver.innerHTML = response;
+ }
+ }
+
+ if (this.responseIsSuccess()) {
+ if (this.onComplete)
+ setTimeout((function() {this.onComplete(
+ this.transport)}).bind(this), 10);
+ }
+
+ if (this.options.evalScripts && scripts) {
+ match = new RegExp(Ajax.Updater.ScriptFragment, 'im');
+ setTimeout((function() {
+ for (var i = 0; i < scripts.length; i++)
+ eval(scripts[i].match(match)[1]);
+ }).bind(this), 10);
+ }
+ }
+});
+
+Ajax.PeriodicalUpdater = Class.create();
+Ajax.PeriodicalUpdater.prototype = (new Ajax.Base()).extend({
+ initialize: function(container, url, options) {
+ this.setOptions(options);
+ this.onComplete = this.options.onComplete;
+
+ this.frequency = (this.options.frequency || 2);
+ this.decay = 1;
+
+ this.updater = {};
+ this.container = container;
+ this.url = url;
+
+ this.start();
+ },
+
+ start: function() {
+ this.options.onComplete = this.updateComplete.bind(this);
+ this.onTimerEvent();
+ },
+
+ stop: function() {
+ this.updater.onComplete = undefined;
+ clearTimeout(this.timer);
+ (this.onComplete || Ajax.emptyFunction).apply(this, arguments);
+ },
+
+ updateComplete: function(request) {
+ if (this.options.decay) {
+ this.decay = (request.responseText == this.lastText ?
+ this.decay * this.options.decay : 1);
+
+ this.lastText = request.responseText;
+ }
+ this.timer = setTimeout(this.onTimerEvent.bind(this),
+ this.decay * this.frequency * 1000);
+ },
+
+ onTimerEvent: function() {
+ this.updater = new Ajax.Updater(this.container, this.url, this.options);
+ }
+});
+
+document.getElementsByClassName = function(className) {
+ var children = document.getElementsByTagName('*') || document.all;
+ var elements = new Array();
+
+ for (var i = 0; i < children.length; i++) {
+ var child = children[i];
+ var classNames = child.className.split(' ');
+ for (var j = 0; j < classNames.length; j++) {
+ if (classNames[j] == className) {
+ elements.push(child);
+ break;
+ }
+ }
+ }
+
+ return elements;
+}
+
+/*--------------------------------------------------------------------------*/
+
+if (!window.Element) {
+ var Element = new Object();
+}
+
+Object.extend(Element, {
+ toggle: function() {
+ for (var i = 0; i < arguments.length; i++) {
+ var element = $(arguments[i]);
+ element.style.display =
+ (element.style.display == 'none' ? '' : 'none');
+ }
+ },
+
+ hide: function() {
+ for (var i = 0; i < arguments.length; i++) {
+ var element = $(arguments[i]);
+ element.style.display = 'none';
+ }
+ },
+
+ show: function() {
+ for (var i = 0; i < arguments.length; i++) {
+ var element = $(arguments[i]);
+ element.style.display = '';
+ }
+ },
+
+ remove: function(element) {
+ element = $(element);
+ element.parentNode.removeChild(element);
+ },
+
+ getHeight: function(element) {
+ element = $(element);
+ return element.offsetHeight;
+ },
+
+ hasClassName: function(element, className) {
+ element = $(element);
+ if (!element)
+ return;
+ var a = element.className.split(' ');
+ for (var i = 0; i < a.length; i++) {
+ if (a[i] == className)
+ return true;
+ }
+ return false;
+ },
+
+ addClassName: function(element, className) {
+ element = $(element);
+ Element.removeClassName(element, className);
+ element.className += ' ' + className;
+ },
+
+ removeClassName: function(element, className) {
+ element = $(element);
+ if (!element)
+ return;
+ var newClassName = '';
+ var a = element.className.split(' ');
+ for (var i = 0; i < a.length; i++) {
+ if (a[i] != className) {
+ if (i > 0)
+ newClassName += ' ';
+ newClassName += a[i];
+ }
+ }
+ element.className = newClassName;
+ },
+
+ // removes whitespace-only text node children
+ cleanWhitespace: function(element) {
+ var element = $(element);
+ for (var i = 0; i < element.childNodes.length; i++) {
+ var node = element.childNodes[i];
+ if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
+ Element.remove(node);
+ }
+ }
+});
+
+var Toggle = new Object();
+Toggle.display = Element.toggle;
+
+/*--------------------------------------------------------------------------*/
+
+Abstract.Insertion = function(adjacency) {
+ this.adjacency = adjacency;
+}
+
+Abstract.Insertion.prototype = {
+ initialize: function(element, content) {
+ this.element = $(element);
+ this.content = content;
+
+ if (this.adjacency && this.element.insertAdjacentHTML) {
+ this.element.insertAdjacentHTML(this.adjacency, this.content);
+ } else {
+ this.range = this.element.ownerDocument.createRange();
+ if (this.initializeRange) this.initializeRange();
+ this.fragment = this.range.createContextualFragment(this.content);
+ this.insertContent();
+ }
+ }
+}
+
+var Insertion = new Object();
+
+Insertion.Before = Class.create();
+Insertion.Before.prototype = (new Abstract.Insertion('beforeBegin')).extend({
+ initializeRange: function() {
+ this.range.setStartBefore(this.element);
+ },
+
+ insertContent: function() {
+ this.element.parentNode.insertBefore(this.fragment, this.element);
+ }
+});
+
+Insertion.Top = Class.create();
+Insertion.Top.prototype = (new Abstract.Insertion('afterBegin')).extend({
+ initializeRange: function() {
+ this.range.selectNodeContents(this.element);
+ this.range.collapse(true);
+ },
+
+ insertContent: function() {
+ this.element.insertBefore(this.fragment, this.element.firstChild);
+ }
+});
+
+Insertion.Bottom = Class.create();
+Insertion.Bottom.prototype = (new Abstract.Insertion('beforeEnd')).extend({
+ initializeRange: function() {
+ this.range.selectNodeContents(this.element);
+ this.range.collapse(this.element);
+ },
+
+ insertContent: function() {
+ this.element.appendChild(this.fragment);
+ }
+});
+
+Insertion.After = Class.create();
+Insertion.After.prototype = (new Abstract.Insertion('afterEnd')).extend({
+ initializeRange: function() {
+ this.range.setStartAfter(this.element);
+ },
+
+ insertContent: function() {
+ this.element.parentNode.insertBefore(this.fragment,
+ this.element.nextSibling);
+ }
+});
+
+var Field = {
+ clear: function() {
+ for (var i = 0; i < arguments.length; i++)
+ $(arguments[i]).value = '';
+ },
+
+ focus: function(element) {
+ $(element).focus();
+ },
+
+ present: function() {
+ for (var i = 0; i < arguments.length; i++)
+ if ($(arguments[i]).value == '') return false;
+ return true;
+ },
+
+ select: function(element) {
+ $(element).select();
+ },
+
+ activate: function(element) {
+ $(element).focus();
+ $(element).select();
+ }
+}
+
+/*--------------------------------------------------------------------------*/
+
+var Form = {
+ serialize: function(form) {
+ var elements = Form.getElements($(form));
+ var queryComponents = new Array();
+
+ for (var i = 0; i < elements.length; i++) {
+ var queryComponent = Form.Element.serialize(elements[i]);
+ if (queryComponent)
+ queryComponents.push(queryComponent);
+ }
+
+ return queryComponents.join('&');
+ },
+
+ getElements: function(form) {
+ var form = $(form);
+ var elements = new Array();
+
+ for (tagName in Form.Element.Serializers) {
+ var tagElements = form.getElementsByTagName(tagName);
+ for (var j = 0; j < tagElements.length; j++)
+ elements.push(tagElements[j]);
+ }
+ return elements;
+ },
+
+ getInputs: function(form, typeName, name) {
+ var form = $(form);
+ var inputs = form.getElementsByTagName('input');
+
+ if (!typeName && !name)
+ return inputs;
+
+ var matchingInputs = new Array();
+ for (var i = 0; i < inputs.length; i++) {
+ var input = inputs[i];
+ if ((typeName && input.type != typeName) ||
+ (name && input.name != name))
+ continue;
+ matchingInputs.push(input);
+ }
+
+ return matchingInputs;
+ },
+
+ disable: function(form) {
+ var elements = Form.getElements(form);
+ for (var i = 0; i < elements.length; i++) {
+ var element = elements[i];
+ element.blur();
+ element.disabled = 'true';
+ }
+ },
+
+ enable: function(form) {
+ var elements = Form.getElements(form);
+ for (var i = 0; i < elements.length; i++) {
+ var element = elements[i];
+ element.disabled = '';
+ }
+ },
+
+ focusFirstElement: function(form) {
+ var form = $(form);
+ var elements = Form.getElements(form);
+ for (var i = 0; i < elements.length; i++) {
+ var element = elements[i];
+ if (element.type != 'hidden' && !element.disabled) {
+ Field.activate(element);
+ break;
+ }
+ }
+ },
+
+ reset: function(form) {
+ $(form).reset();
+ }
+}
+
+Form.Element = {
+ serialize: function(element) {
+ var element = $(element);
+ var method = element.tagName.toLowerCase();
+ var parameter = Form.Element.Serializers[method](element);
+
+ if (parameter)
+ return encodeURIComponent(parameter[0]) + '=' +
+ encodeURIComponent(parameter[1]);
+ },
+
+ getValue: function(element) {
+ var element = $(element);
+ var method = element.tagName.toLowerCase();
+ var parameter = Form.Element.Serializers[method](element);
+
+ if (parameter)
+ return parameter[1];
+ }
+}
+
+Form.Element.Serializers = {
+ input: function(element) {
+ switch (element.type.toLowerCase()) {
+ case 'submit':
+ case 'hidden':
+ case 'password':
+ case 'text':
+ return Form.Element.Serializers.textarea(element);
+ case 'checkbox':
+ case 'radio':
+ return Form.Element.Serializers.inputSelector(element);
+ }
+ return false;
+ },
+
+ inputSelector: function(element) {
+ if (element.checked)
+ return [element.name, element.value];
+ },
+
+ textarea: function(element) {
+ return [element.name, element.value];
+ },
+
+ select: function(element) {
+ var value = '';
+ if (element.type == 'select-one') {
+ var index = element.selectedIndex;
+ if (index >= 0)
+ value = element.options[index].value || element.options[index].text;
+ } else {
+ value = new Array();
+ for (var i = 0; i < element.length; i++) {
+ var opt = element.options[i];
+ if (opt.selected)
+ value.push(opt.value || opt.text);
+ }
+ }
+ return [element.name, value];
+ }
+}
+
+/*--------------------------------------------------------------------------*/
+
+var $F = Form.Element.getValue;
+
+/*--------------------------------------------------------------------------*/
+
+Abstract.TimedObserver = function() {}
+Abstract.TimedObserver.prototype = {
+ initialize: function(element, frequency, callback) {
+ this.frequency = frequency;
+ this.element = $(element);
+ this.callback = callback;
+
+ this.lastValue = this.getValue();
+ this.registerCallback();
+ },
+
+ registerCallback: function() {
+ setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
+ },
+
+ onTimerEvent: function() {
+ var value = this.getValue();
+ if (this.lastValue != value) {
+ this.callback(this.element, value);
+ this.lastValue = value;
+ }
+ }
+}
+
+Form.Element.Observer = Class.create();
+Form.Element.Observer.prototype = (new Abstract.TimedObserver()).extend({
+ getValue: function() {
+ return Form.Element.getValue(this.element);
+ }
+});
+
+Form.Observer = Class.create();
+Form.Observer.prototype = (new Abstract.TimedObserver()).extend({
+ getValue: function() {
+ return Form.serialize(this.element);
+ }
+});
+
+/*--------------------------------------------------------------------------*/
+
+Abstract.EventObserver = function() {}
+Abstract.EventObserver.prototype = {
+ initialize: function(element, callback) {
+ this.element = $(element);
+ this.callback = callback;
+
+ this.lastValue = this.getValue();
+ if (this.element.tagName.toLowerCase() == 'form')
+ this.registerFormCallbacks();
+ else
+ this.registerCallback(this.element);
+ },
+
+ onElementEvent: function() {
+ var value = this.getValue();
+ if (this.lastValue != value) {
+ this.callback(this.element, value);
+ this.lastValue = value;
+ }
+ },
+
+ registerFormCallbacks: function() {
+ var elements = Form.getElements(this.element);
+ for (var i = 0; i < elements.length; i++)
+ this.registerCallback(elements[i]);
+ },
+
+ registerCallback: function(element) {
+ if (element.type) {
+ switch (element.type.toLowerCase()) {
+ case 'checkbox':
+ case 'radio':
+ element.target = this;
+ element.prev_onclick = element.onclick || Prototype.emptyFunction;
+ element.onclick = function() {
+ this.prev_onclick();
+ this.target.onElementEvent();
+ }
+ break;
+ case 'password':
+ case 'text':
+ case 'textarea':
+ case 'select-one':
+ case 'select-multiple':
+ element.target = this;
+ element.prev_onchange = element.onchange || Prototype.emptyFunction;
+ element.onchange = function() {
+ this.prev_onchange();
+ this.target.onElementEvent();
+ }
+ break;
+ }
+ }
+ }
+}
+
+Form.Element.EventObserver = Class.create();
+Form.Element.EventObserver.prototype = (new Abstract.EventObserver()).extend({
+ getValue: function() {
+ return Form.Element.getValue(this.element);
+ }
+});
+
+Form.EventObserver = Class.create();
+Form.EventObserver.prototype = (new Abstract.EventObserver()).extend({
+ getValue: function() {
+ return Form.serialize(this.element);
+ }
+});
+
+
+if (!window.Event) {
+ var Event = new Object();
+}
+
+Object.extend(Event, {
+ KEY_BACKSPACE: 8,
+ KEY_TAB: 9,
+ KEY_RETURN: 13,
+ KEY_ESC: 27,
+ KEY_LEFT: 37,
+ KEY_UP: 38,
+ KEY_RIGHT: 39,
+ KEY_DOWN: 40,
+ KEY_DELETE: 46,
+
+ element: function(event) {
+ return event.target || event.srcElement;
+ },
+
+ isLeftClick: function(event) {
+ return (((event.which) && (event.which == 1)) ||
+ ((event.button) && (event.button == 1)));
+ },
+
+ pointerX: function(event) {
+ return event.pageX || (event.clientX +
+ (document.documentElement.scrollLeft || document.body.scrollLeft));
+ },
+
+ pointerY: function(event) {
+ return event.pageY || (event.clientY +
+ (document.documentElement.scrollTop || document.body.scrollTop));
+ },
+
+ stop: function(event) {
+ if (event.preventDefault) {
+ event.preventDefault();
+ event.stopPropagation();
+ } else {
+ event.returnValue = false;
+ }
+ },
+
+ // find the first node with the given tagName, starting from the
+ // node the event was triggered on; traverses the DOM upwards
+ findElement: function(event, tagName) {
+ var element = Event.element(event);
+ while (element.parentNode && (!element.tagName ||
+ (element.tagName.toUpperCase() != tagName.toUpperCase())))
+ element = element.parentNode;
+ return element;
+ },
+
+ observers: false,
+
+ _observeAndCache: function(element, name, observer, useCapture) {
+ if (!this.observers) this.observers = [];
+ if (element.addEventListener) {
+ this.observers.push([element, name, observer, useCapture]);
+ element.addEventListener(name, observer, useCapture);
+ } else if (element.attachEvent) {
+ this.observers.push([element, name, observer, useCapture]);
+ element.attachEvent('on' + name, observer);
+ }
+ },
+
+ unloadCache: function() {
+ if (!Event.observers) return;
+ for (var i = 0; i < Event.observers.length; i++) {
+ Event.stopObserving.apply(this, Event.observers[i]);
+ Event.observers[i][0] = null;
+ }
+ Event.observers = false;
+ },
+
+ observe: function(element, name, observer, useCapture) {
+ var element = $(element);
+ useCapture = useCapture || false;
+
+ if (name == 'keypress' &&
+ ((navigator.appVersion.indexOf('AppleWebKit') > 0)
+ || element.attachEvent))
+ name = 'keydown';
+
+ this._observeAndCache(element, name, observer, useCapture);
+ },
+
+ stopObserving: function(element, name, observer, useCapture) {
+ var element = $(element);
+ useCapture = useCapture || false;
+
+ if (name == 'keypress' &&
+ ((navigator.appVersion.indexOf('AppleWebKit') > 0)
+ || element.detachEvent))
+ name = 'keydown';
+
+ if (element.removeEventListener) {
+ element.removeEventListener(name, observer, useCapture);
+ } else if (element.detachEvent) {
+ element.detachEvent('on' + name, observer);
+ }
+ }
+});
+
+/* prevent memory leaks in IE */
+Event.observe(window, 'unload', Event.unloadCache, false);
+
+var Position = {
+
+ // set to true if needed, warning: firefox performance problems
+ // NOT neeeded for page scrolling, only if draggable contained in
+ // scrollable elements
+ includeScrollOffsets: false,
+
+ // must be called before calling withinIncludingScrolloffset, every time the
+ // page is scrolled
+ prepare: function() {
+ this.deltaX = window.pageXOffset
+ || document.documentElement.scrollLeft
+ || document.body.scrollLeft
+ || 0;
+ this.deltaY = window.pageYOffset
+ || document.documentElement.scrollTop
+ || document.body.scrollTop
+ || 0;
+ },
+
+ realOffset: function(element) {
+ var valueT = 0, valueL = 0;
+ do {
+ valueT += element.scrollTop || 0;
+ valueL += element.scrollLeft || 0;
+ element = element.parentNode;
+ } while (element);
+ return [valueL, valueT];
+ },
+
+ cumulativeOffset: function(element) {
+ var valueT = 0, valueL = 0;
+ do {
+ valueT += element.offsetTop || 0;
+ valueL += element.offsetLeft || 0;
+ element = element.offsetParent;
+ } while (element);
+ return [valueL, valueT];
+ },
+
+ // caches x/y coordinate pair to use with overlap
+ within: function(element, x, y) {
+ if (this.includeScrollOffsets)
+ return this.withinIncludingScrolloffsets(element, x, y);
+ this.xcomp = x;
+ this.ycomp = y;
+ this.offset = this.cumulativeOffset(element);
+
+ return (y >= this.offset[1] &&
+ y < this.offset[1] + element.offsetHeight &&
+ x >= this.offset[0] &&
+ x < this.offset[0] + element.offsetWidth);
+ },
+
+ withinIncludingScrolloffsets: function(element, x, y) {
+ var offsetcache = this.realOffset(element);
+
+ this.xcomp = x + offsetcache[0] - this.deltaX;
+ this.ycomp = y + offsetcache[1] - this.deltaY;
+ this.offset = this.cumulativeOffset(element);
+
+ return (this.ycomp >= this.offset[1] &&
+ this.ycomp < this.offset[1] + element.offsetHeight &&
+ this.xcomp >= this.offset[0] &&
+ this.xcomp < this.offset[0] + element.offsetWidth);
+ },
+
+ // within must be called directly before
+ overlap: function(mode, element) {
+ if (!mode) return 0;
+ if (mode == 'vertical')
+ return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
+ element.offsetHeight;
+ if (mode == 'horizontal')
+ return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
+ element.offsetWidth;
+ },
+
+ clone: function(source, target) {
+ source = $(source);
+ target = $(target);
+ target.style.position = 'absolute';
+ var offsets = this.cumulativeOffset(source);
+ target.style.top = offsets[1] + 'px';
+ target.style.left = offsets[0] + 'px';
+ target.style.width = source.offsetWidth + 'px';
+ target.style.height = source.offsetHeight + 'px';
+ }
+}
diff --git a/web/static/js/rico.js b/web/static/js/rico.js
new file mode 100644
index 0000000..a1f9113
--- /dev/null
+++ b/web/static/js/rico.js
@@ -0,0 +1,2666 @@
+/**
+ *
+ * Copyright 2005 Sabre Airline Solutions
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
+ * file except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ **/
+
+
+//-------------------- rico.js
+var Rico = {
+ Version: '1.1-beta2'
+}
+
+Rico.ArrayExtensions = new Array();
+
+if (Object.prototype.extend) {
+ // in prototype.js...
+ Rico.ArrayExtensions[ Rico.ArrayExtensions.length ] = Object.prototype.extend;
+}
+
+if (Array.prototype.push) {
+ // in prototype.js...
+ Rico.ArrayExtensions[ Rico.ArrayExtensions.length ] = Array.prototype.push;
+}
+
+if (!Array.prototype.remove) {
+ Array.prototype.remove = function(dx) {
+ if( isNaN(dx) || dx > this.length )
+ return false;
+ for( var i=0,n=0; i<this.length; i++ )
+ if( i != dx )
+ this[n++]=this[i];
+ this.length-=1;
+ };
+ Rico.ArrayExtensions[ Rico.ArrayExtensions.length ] = Array.prototype.remove;
+}
+
+if (!Array.prototype.removeItem) {
+ Array.prototype.removeItem = function(item) {
+ for ( var i = 0 ; i < this.length ; i++ )
+ if ( this[i] == item ) {
+ this.remove(i);
+ break;
+ }
+ };
+ Rico.ArrayExtensions[ Rico.ArrayExtensions.length ] = Array.prototype.removeItem;
+}
+
+if (!Array.prototype.indices) {
+ Array.prototype.indices = function() {
+ var indexArray = new Array();
+ for ( index in this ) {
+ var ignoreThis = false;
+ for ( var i = 0 ; i < Rico.ArrayExtensions.length ; i++ ) {
+ if ( this[index] == Rico.ArrayExtensions[i] ) {
+ ignoreThis = true;
+ break;
+ }
+ }
+ if ( !ignoreThis )
+ indexArray[ indexArray.length ] = index;
+ }
+ return indexArray;
+ }
+ Rico.ArrayExtensions[ Rico.ArrayExtensions.length ] = Array.prototype.indices;
+}
+
+// Create the loadXML method and xml getter for Mozilla
+if ( window.DOMParser &&
+ window.XMLSerializer &&
+ window.Node && Node.prototype && Node.prototype.__defineGetter__ ) {
+
+ if (!Document.prototype.loadXML) {
+ Document.prototype.loadXML = function (s) {
+ var doc2 = (new DOMParser()).parseFromString(s, "text/xml");
+ while (this.hasChildNodes())
+ this.removeChild(this.lastChild);
+
+ for (var i = 0; i < doc2.childNodes.length; i++) {
+ this.appendChild(this.importNode(doc2.childNodes[i], true));
+ }
+ };
+ }
+
+ Document.prototype.__defineGetter__( "xml",
+ function () {
+ return (new XMLSerializer()).serializeToString(this);
+ }
+ );
+}
+
+document.getElementsByTagAndClassName = function(tagName, className) {
+ if ( tagName == null )
+ tagName = '*';
+
+ var children = document.getElementsByTagName(tagName) || document.all;
+ var elements = new Array();
+
+ if ( className == null )
+ return children;
+
+ for (var i = 0; i < children.length; i++) {
+ var child = children[i];
+ var classNames = child.className.split(' ');
+ for (var j = 0; j < classNames.length; j++) {
+ if (classNames[j] == className) {
+ elements.push(child);
+ break;
+ }
+ }
+ }
+
+ return elements;
+}
+
+
+//-------------------- ricoAccordion.js
+
+Rico.Accordion = Class.create();
+
+Rico.Accordion.prototype = {
+
+ initialize: function(container, options) {
+ this.container = $(container);
+ this.lastExpandedTab = null;
+ this.accordionTabs = new Array();
+ this.setOptions(options);
+ this._attachBehaviors();
+
+ this.container.style.borderBottom = '1px solid ' + this.options.borderColor;
+
+ // set the initial visual state...
+ for ( var i=1 ; i < this.accordionTabs.length ; i++ )
+ {
+ this.accordionTabs[i].collapse();
+ this.accordionTabs[i].content.style.display = 'none';
+ }
+ this.lastExpandedTab = this.accordionTabs[0];
+ this.lastExpandedTab.content.style.height = this.options.panelHeight + "px";
+ this.lastExpandedTab.showExpanded();
+ this.lastExpandedTab.titleBar.style.fontWeight = this.options.expandedFontWeight;
+ },
+
+ setOptions: function(options) {
+ this.options = {
+ expandedBg : '#63699c',
+ hoverBg : '#63699c',
+ collapsedBg : '#6b79a5',
+ expandedTextColor : '#ffffff',
+ expandedFontWeight : 'bold',
+ hoverTextColor : '#ffffff',
+ collapsedTextColor : '#ced7ef',
+ collapsedFontWeight : 'normal',
+ hoverTextColor : '#ffffff',
+ borderColor : '#1f669b',
+ panelHeight : 200,
+ onHideTab : null,
+ onShowTab : null
+ }.extend(options || {});
+ },
+
+ showTabByIndex: function( anIndex, animate ) {
+ var doAnimate = arguments.length == 1 ? true : animate;
+ this.showTab( this.accordionTabs[anIndex], doAnimate );
+ },
+
+ showTab: function( accordionTab, animate ) {
+
+ var doAnimate = arguments.length == 1 ? true : animate;
+
+ if ( this.options.onHideTab )
+ this.options.onHideTab(this.lastExpandedTab);
+
+ this.lastExpandedTab.showCollapsed();
+ var accordion = this;
+ var lastExpandedTab = this.lastExpandedTab;
+
+ this.lastExpandedTab.content.style.height = (this.options.panelHeight - 1) + 'px';
+ accordionTab.content.style.display = '';
+
+ accordionTab.titleBar.style.fontWeight = this.options.expandedFontWeight;
+
+ if ( doAnimate ) {
+ new Effect.AccordionSize( this.lastExpandedTab.content,
+ accordionTab.content,
+ 1,
+ this.options.panelHeight,
+ 100, 10,
+ { complete: function() {accordion.showTabDone(lastExpandedTab)} } );
+ this.lastExpandedTab = accordionTab;
+ }
+ else {
+ this.lastExpandedTab.content.style.height = "1px";
+ accordionTab.content.style.height = this.options.panelHeight + "px";
+ this.lastExpandedTab = accordionTab;
+ this.showTabDone(lastExpandedTab);
+ }
+ },
+
+ showTabDone: function(collapsedTab) {
+ collapsedTab.content.style.display = 'none';
+ this.lastExpandedTab.showExpanded();
+ if ( this.options.onShowTab )
+ this.options.onShowTab(this.lastExpandedTab);
+ },
+
+ _attachBehaviors: function() {
+ var panels = this._getDirectChildrenByTag(this.container, 'DIV');
+ for ( var i = 0 ; i < panels.length ; i++ ) {
+
+ var tabChildren = this._getDirectChildrenByTag(panels[i],'DIV');
+ if ( tabChildren.length != 2 )
+ continue; // unexpected
+
+ var tabTitleBar = tabChildren[0];
+ var tabContentBox = tabChildren[1];
+ this.accordionTabs.push( new Rico.Accordion.Tab(this,tabTitleBar,tabContentBox) );
+ }
+ },
+
+ _getDirectChildrenByTag: function(e, tagName) {
+ var kids = new Array();
+ var allKids = e.childNodes;
+ for( var i = 0 ; i < allKids.length ; i++ )
+ if ( allKids[i] && allKids[i].tagName && allKids[i].tagName == tagName )
+ kids.push(allKids[i]);
+ return kids;
+ }
+
+};
+
+Rico.Accordion.Tab = Class.create();
+
+Rico.Accordion.Tab.prototype = {
+
+ initialize: function(accordion, titleBar, content) {
+ this.accordion = accordion;
+ this.titleBar = titleBar;
+ this.content = content;
+ this._attachBehaviors();
+ },
+
+ collapse: function() {
+ this.showCollapsed();
+ this.content.style.height = "1px";
+ },
+
+ showCollapsed: function() {
+ this.expanded = false;
+ this.titleBar.style.backgroundColor = this.accordion.options.collapsedBg;
+ this.titleBar.style.color = this.accordion.options.collapsedTextColor;
+ this.titleBar.style.fontWeight = this.accordion.options.collapsedFontWeight;
+ this.content.style.overflow = "hidden";
+ },
+
+ showExpanded: function() {
+ this.expanded = true;
+ this.titleBar.style.backgroundColor = this.accordion.options.expandedBg;
+ this.titleBar.style.color = this.accordion.options.expandedTextColor;
+ this.content.style.overflow = "visible";
+ },
+
+ titleBarClicked: function(e) {
+ if ( this.accordion.lastExpandedTab == this )
+ return;
+ this.accordion.showTab(this);
+ },
+
+ hover: function(e) {
+ this.titleBar.style.backgroundColor = this.accordion.options.hoverBg;
+ this.titleBar.style.color = this.accordion.options.hoverTextColor;
+ },
+
+ unhover: function(e) {
+ if ( this.expanded ) {
+ this.titleBar.style.backgroundColor = this.accordion.options.expandedBg;
+ this.titleBar.style.color = this.accordion.options.expandedTextColor;
+ }
+ else {
+ this.titleBar.style.backgroundColor = this.accordion.options.collapsedBg;
+ this.titleBar.style.color = this.accordion.options.collapsedTextColor;
+ }
+ },
+
+ _attachBehaviors: function() {
+ this.content.style.border = "1px solid " + this.accordion.options.borderColor;
+ this.content.style.borderTopWidth = "0px";
+ this.content.style.borderBottomWidth = "0px";
+ this.content.style.margin = "0px";
+
+ this.titleBar.onclick = this.titleBarClicked.bindAsEventListener(this);
+ this.titleBar.onmouseover = this.hover.bindAsEventListener(this);
+ this.titleBar.onmouseout = this.unhover.bindAsEventListener(this);
+ }
+
+};
+
+
+//-------------------- ricoAjaxEngine.js
+
+Rico.AjaxEngine = Class.create();
+
+Rico.AjaxEngine.prototype = {
+
+ initialize: function() {
+ this.ajaxElements = new Array();
+ this.ajaxObjects = new Array();
+ this.requestURLS = new Array();
+ },
+
+ registerAjaxElement: function( anId, anElement ) {
+ if ( arguments.length == 1 )
+ anElement = $(anId);
+ this.ajaxElements[anId] = anElement;
+ },
+
+ registerAjaxObject: function( anId, anObject ) {
+ this.ajaxObjects[anId] = anObject;
+ },
+
+ registerRequest: function (requestLogicalName, requestURL) {
+ this.requestURLS[requestLogicalName] = requestURL;
+ },
+
+ sendRequest: function(requestName) {
+ var requestURL = this.requestURLS[requestName];
+ if ( requestURL == null )
+ return;
+
+ var queryString = "";
+ if ( arguments.length > 1 )
+ queryString = this._createQueryString(arguments, 1);
+
+ new Ajax.Request(requestURL, this._requestOptions(queryString));
+ },
+
+ sendRequestWithData: function(requestName, xmlDocument) {
+ var requestURL = this.requestURLS[requestName];
+ if ( requestURL == null )
+ return;
+
+ var queryString = "";
+ if ( arguments.length > 2 )
+ queryString = this._createQueryString(arguments, 2);
+
+ new Ajax.Request(requestURL + "?" + queryString, this._requestOptions(null,xmlDocument));
+ },
+
+ sendRequestAndUpdate: function(requestName,container,options) {
+ var requestURL = this.requestURLS[requestName];
+ if ( requestURL == null )
+ return;
+
+ var queryString = "";
+ if ( arguments.length > 3 )
+ queryString = this._createQueryString(arguments, 3);
+
+ var updaterOptions = this._requestOptions(queryString);
+ updaterOptions.onComplete = null;
+ updaterOptions.extend(options);
+
+ new Ajax.Updater(container, requestURL, updaterOptions);
+ },
+
+ sendRequestWithDataAndUpdate: function(requestName,xmlDocument,container,options) {
+ var requestURL = this.requestURLS[requestName];
+ if ( requestURL == null )
+ return;
+
+ var queryString = "";
+ if ( arguments.length > 4 )
+ queryString = this._createQueryString(arguments, 4);
+
+
+ var updaterOptions = this._requestOptions(queryString,xmlDocument);
+ updaterOptions.onComplete = null;
+ updaterOptions.extend(options);
+
+ new Ajax.Updater(container, requestURL + "?" + queryString, updaterOptions);
+ },
+
+ // Private -- not part of intended engine API --------------------------------------------------------------------
+
+ _requestOptions: function(queryString,xmlDoc) {
+ var self = this;
+
+ var requestHeaders = ['X-Rico-Version', Rico.Version ];
+ var sendMethod = "post"
+ if ( arguments[1] )
+ requestHeaders.push( 'Content-type', 'text/xml' );
+ else
+ sendMethod = "get";
+
+ return { requestHeaders: requestHeaders,
+ parameters: queryString,
+ postBody: arguments[1] ? xmlDoc : null,
+ method: sendMethod,
+ onComplete: self._onRequestComplete.bind(self) };
+ },
+
+ _createQueryString: function( theArgs, offset ) {
+ var queryString = ""
+ for ( var i = offset ; i < theArgs.length ; i++ ) {
+ if ( i != offset )
+ queryString += "&";
+
+ var anArg = theArgs[i];
+
+ if ( anArg.name != undefined && anArg.value != undefined ) {
+ queryString += anArg.name + "=" + escape(anArg.value);
+ }
+ else {
+ var ePos = anArg.indexOf('=');
+ var argName = anArg.substring( 0, ePos );
+ var argValue = anArg.substring( ePos + 1 );
+ queryString += argName + "=" + escape(argValue);
+ }
+ }
+
+ return queryString;
+ },
+
+ _onRequestComplete : function(request) {
+
+ //!!TODO: error handling infrastructure??
+ if (request.status != 200)
+ return;
+
+ var response = request.responseXML.getElementsByTagName("ajax-response");
+ if (response == null || response.length != 1)
+ return;
+ this._processAjaxResponse( response[0].childNodes );
+ },
+
+ _processAjaxResponse: function( xmlResponseElements ) {
+ for ( var i = 0 ; i < xmlResponseElements.length ; i++ ) {
+ var responseElement = xmlResponseElements[i];
+
+ // only process nodes of type element.....
+ if ( responseElement.nodeType != 1 )
+ continue;
+
+ var responseType = responseElement.getAttribute("type");
+ var responseId = responseElement.getAttribute("id");
+
+ if ( responseType == "object" )
+ this._processAjaxObjectUpdate( this.ajaxObjects[ responseId ], responseElement );
+ else if ( responseType == "element" )
+ this._processAjaxElementUpdate( this.ajaxElements[ responseId ], responseElement );
+ else
+ alert('unrecognized AjaxResponse type : ' + responseType );
+ }
+ },
+
+ _processAjaxObjectUpdate: function( ajaxObject, responseElement ) {
+ ajaxObject.ajaxUpdate( responseElement );
+ },
+
+ _processAjaxElementUpdate: function( ajaxElement, responseElement ) {
+ ajaxElement.innerHTML = RicoUtil.getContentAsString(responseElement);
+ }
+
+}
+
+var ajaxEngine = new Rico.AjaxEngine();
+
+
+//-------------------- ricoColor.js
+Rico.Color = Class.create();
+
+Rico.Color.prototype = {
+
+ initialize: function(red, green, blue) {
+ this.rgb = { r: red, g : green, b : blue };
+ },
+
+ setRed: function(r) {
+ this.rgb.r = r;
+ },
+
+ setGreen: function(g) {
+ this.rgb.g = g;
+ },
+
+ setBlue: function(b) {
+ this.rgb.b = b;
+ },
+
+ setHue: function(h) {
+
+ // get an HSB model, and set the new hue...
+ var hsb = this.asHSB();
+ hsb.h = h;
+
+ // convert back to RGB...
+ this.rgb = Rico.Color.HSBtoRGB(hsb.h, hsb.s, hsb.b);
+ },
+
+ setSaturation: function(s) {
+ // get an HSB model, and set the new hue...
+ var hsb = this.asHSB();
+ hsb.s = s;
+
+ // convert back to RGB and set values...
+ this.rgb = Rico.Color.HSBtoRGB(hsb.h, hsb.s, hsb.b);
+ },
+
+ setBrightness: function(b) {
+ // get an HSB model, and set the new hue...
+ var hsb = this.asHSB();
+ hsb.b = b;
+
+ // convert back to RGB and set values...
+ this.rgb = Rico.Color.HSBtoRGB( hsb.h, hsb.s, hsb.b );
+ },
+
+ darken: function(percent) {
+ var hsb = this.asHSB();
+ this.rgb = Rico.Color.HSBtoRGB(hsb.h, hsb.s, Math.max(hsb.b - percent,0));
+ },
+
+ brighten: function(percent) {
+ var hsb = this.asHSB();
+ this.rgb = Rico.Color.HSBtoRGB(hsb.h, hsb.s, Math.min(hsb.b + percent,1));
+ },
+
+ blend: function(other) {
+ this.rgb.r = Math.floor((this.rgb.r + other.rgb.r)/2);
+ this.rgb.g = Math.floor((this.rgb.g + other.rgb.g)/2);
+ this.rgb.b = Math.floor((this.rgb.b + other.rgb.b)/2);
+ },
+
+ isBright: function() {
+ var hsb = this.asHSB();
+ return this.asHSB().b > 0.5;
+ },
+
+ isDark: function() {
+ return ! this.isBright();
+ },
+
+ asRGB: function() {
+ return "rgb(" + this.rgb.r + "," + this.rgb.g + "," + this.rgb.b + ")";
+ },
+
+ asHex: function() {
+ return "#" + this.rgb.r.toColorPart() + this.rgb.g.toColorPart() + this.rgb.b.toColorPart();
+ },
+
+ asHSB: function() {
+ return Rico.Color.RGBtoHSB(this.rgb.r, this.rgb.g, this.rgb.b);
+ },
+
+ toString: function() {
+ return this.asHex();
+ }
+
+};
+
+Rico.Color.createFromHex = function(hexCode) {
+
+ if ( hexCode.indexOf('#') == 0 )
+ hexCode = hexCode.substring(1);
+ var red = hexCode.substring(0,2);
+ var green = hexCode.substring(2,4);
+ var blue = hexCode.substring(4,6);
+ return new Rico.Color( parseInt(red,16), parseInt(green,16), parseInt(blue,16) );
+}
+
+/**
+ * Factory method for creating a color from the background of
+ * an HTML element.
+ */
+Rico.Color.createColorFromBackground = function(elem) {
+
+ var actualColor = RicoUtil.getElementsComputedStyle($(elem), "backgroundColor", "background-color");
+
+ if ( actualColor == "transparent" && elem.parent )
+ return Rico.Color.createColorFromBackground(elem.parent);
+
+ if ( actualColor == null )
+ return new Rico.Color(255,255,255);
+
+ if ( actualColor.indexOf("rgb(") == 0 ) {
+ var colors = actualColor.substring(4, actualColor.length - 1 );
+ var colorArray = colors.split(",");
+ return new Rico.Color( parseInt( colorArray[0] ),
+ parseInt( colorArray[1] ),
+ parseInt( colorArray[2] ) );
+
+ }
+ else if ( actualColor.indexOf("#") == 0 ) {
+ var redPart = parseInt(actualColor.substring(1,3), 16);
+ var greenPart = parseInt(actualColor.substring(3,5), 16);
+ var bluePart = parseInt(actualColor.substring(5), 16);
+ return new Rico.Color( redPart, greenPart, bluePart );
+ }
+ else
+ return new Rico.Color(255,255,255);
+}
+
+Rico.Color.HSBtoRGB = function(hue, saturation, brightness) {
+
+ var red = 0;
+ var green = 0;
+ var blue = 0;
+
+ if (saturation == 0) {
+ red = parseInt(brightness * 255.0 + 0.5);
+ green = red;
+ blue = red;
+ }
+ else {
+ var h = (hue - Math.floor(hue)) * 6.0;
+ var f = h - Math.floor(h);
+ var p = brightness * (1.0 - saturation);
+ var q = brightness * (1.0 - saturation * f);
+ var t = brightness * (1.0 - (saturation * (1.0 - f)));
+
+ switch (parseInt(h)) {
+ case 0:
+ red = (brightness * 255.0 + 0.5);
+ green = (t * 255.0 + 0.5);
+ blue = (p * 255.0 + 0.5);
+ break;
+ case 1:
+ red = (q * 255.0 + 0.5);
+ green = (brightness * 255.0 + 0.5);
+ blue = (p * 255.0 + 0.5);
+ break;
+ case 2:
+ red = (p * 255.0 + 0.5);
+ green = (brightness * 255.0 + 0.5);
+ blue = (t * 255.0 + 0.5);
+ break;
+ case 3:
+ red = (p * 255.0 + 0.5);
+ green = (q * 255.0 + 0.5);
+ blue = (brightness * 255.0 + 0.5);
+ break;
+ case 4:
+ red = (t * 255.0 + 0.5);
+ green = (p * 255.0 + 0.5);
+ blue = (brightness * 255.0 + 0.5);
+ break;
+ case 5:
+ red = (brightness * 255.0 + 0.5);
+ green = (p * 255.0 + 0.5);
+ blue = (q * 255.0 + 0.5);
+ break;
+ }
+ }
+
+ return { r : parseInt(red), g : parseInt(green) , b : parseInt(blue) };
+}
+
+Rico.Color.RGBtoHSB = function(r, g, b) {
+
+ var hue;
+ var saturaton;
+ var brightness;
+
+ var cmax = (r > g) ? r : g;
+ if (b > cmax)
+ cmax = b;
+
+ var cmin = (r < g) ? r : g;
+ if (b < cmin)
+ cmin = b;
+
+ brightness = cmax / 255.0;
+ if (cmax != 0)
+ saturation = (cmax - cmin)/cmax;
+ else
+ saturation = 0;
+
+ if (saturation == 0)
+ hue = 0;
+ else {
+ var redc = (cmax - r)/(cmax - cmin);
+ var greenc = (cmax - g)/(cmax - cmin);
+ var bluec = (cmax - b)/(cmax - cmin);
+
+ if (r == cmax)
+ hue = bluec - greenc;
+ else if (g == cmax)
+ hue = 2.0 + redc - bluec;
+ else
+ hue = 4.0 + greenc - redc;
+
+ hue = hue / 6.0;
+ if (hue < 0)
+ hue = hue + 1.0;
+ }
+
+ return { h : hue, s : saturation, b : brightness };
+}
+
+
+//-------------------- ricoCorner.js
+
+Rico.Corner = {
+
+ round: function(e, options) {
+ var e = $(e);
+ this._setOptions(options);
+
+ var color = this.options.color;
+ if ( this.options.color == "fromElement" )
+ color = this._background(e);
+
+ var bgColor = this.options.bgColor;
+ if ( this.options.bgColor == "fromParent" )
+ bgColor = this._background(e.offsetParent);
+
+ this._roundCornersImpl(e, color, bgColor);
+ },
+
+ _roundCornersImpl: function(e, color, bgColor) {
+ if(this.options.border)
+ this._renderBorder(e,bgColor);
+ if(this._isTopRounded())
+ this._roundTopCorners(e,color,bgColor);
+ if(this._isBottomRounded())
+ this._roundBottomCorners(e,color,bgColor);
+ },
+
+ _renderBorder: function(el,bgColor) {
+ var borderValue = "1px solid " + this._borderColor(bgColor);
+ var borderL = "border-left: " + borderValue;
+ var borderR = "border-right: " + borderValue;
+ var style = "style='" + borderL + ";" + borderR + "'";
+ el.innerHTML = "<div " + style + ">" + el.innerHTML + "</div>"
+ },
+
+ _roundTopCorners: function(el, color, bgColor) {
+ var corner = this._createCorner(bgColor);
+ for(var i=0 ; i < this.options.numSlices ; i++ )
+ corner.appendChild(this._createCornerSlice(color,bgColor,i,"top"));
+ el.style.paddingTop = 0;
+ el.insertBefore(corner,el.firstChild);
+ },
+
+ _roundBottomCorners: function(el, color, bgColor) {
+ var corner = this._createCorner(bgColor);
+ for(var i=(this.options.numSlices-1) ; i >= 0 ; i-- )
+ corner.appendChild(this._createCornerSlice(color,bgColor,i,"bottom"));
+ el.style.paddingBottom = 0;
+ el.appendChild(corner);
+ },
+
+ _createCorner: function(bgColor) {
+ var corner = document.createElement("div");
+ corner.style.backgroundColor = (this._isTransparent() ? "transparent" : bgColor);
+ return corner;
+ },
+
+ _createCornerSlice: function(color,bgColor, n, position) {
+ var slice = document.createElement("span");
+
+ var inStyle = slice.style;
+ inStyle.backgroundColor = color;
+ inStyle.display = "block";
+ inStyle.height = "1px";
+ inStyle.overflow = "hidden";
+ inStyle.fontSize = "1px";
+
+ var borderColor = this._borderColor(color,bgColor);
+ if ( this.options.border && n == 0 ) {
+ inStyle.borderTopStyle = "solid";
+ inStyle.borderTopWidth = "1px";
+ inStyle.borderLeftWidth = "0px";
+ inStyle.borderRightWidth = "0px";
+ inStyle.borderBottomWidth = "0px";
+ inStyle.height = "0px"; // assumes css compliant box model
+ inStyle.borderColor = borderColor;
+ }
+ else if(borderColor) {
+ inStyle.borderColor = borderColor;
+ inStyle.borderStyle = "solid";
+ inStyle.borderWidth = "0px 1px";
+ }
+
+ if ( !this.options.compact && (n == (this.options.numSlices-1)) )
+ inStyle.height = "2px";
+
+ this._setMargin(slice, n, position);
+ this._setBorder(slice, n, position);
+
+ return slice;
+ },
+
+ _setOptions: function(options) {
+ this.options = {
+ corners : "all",
+ color : "fromElement",
+ bgColor : "fromParent",
+ blend : true,
+ border : false,
+ compact : false
+ }.extend(options || {});
+
+ this.options.numSlices = this.options.compact ? 2 : 4;
+ if ( this._isTransparent() )
+ this.options.blend = false;
+ },
+
+ _whichSideTop: function() {
+ if ( this._hasString(this.options.corners, "all", "top") )
+ return "";
+
+ if ( this.options.corners.indexOf("tl") >= 0 && this.options.corners.indexOf("tr") >= 0 )
+ return "";
+
+ if (this.options.corners.indexOf("tl") >= 0)
+ return "left";
+ else if (this.options.corners.indexOf("tr") >= 0)
+ return "right";
+ return "";
+ },
+
+ _whichSideBottom: function() {
+ if ( this._hasString(this.options.corners, "all", "bottom") )
+ return "";
+
+ if ( this.options.corners.indexOf("bl")>=0 && this.options.corners.indexOf("br")>=0 )
+ return "";
+
+ if(this.options.corners.indexOf("bl") >=0)
+ return "left";
+ else if(this.options.corners.indexOf("br")>=0)
+ return "right";
+ return "";
+ },
+
+ _borderColor : function(color,bgColor) {
+ if ( color == "transparent" )
+ return bgColor;
+ else if ( this.options.border )
+ return this.options.border;
+ else if ( this.options.blend )
+ return this._blend( bgColor, color );
+ else
+ return "";
+ },
+
+
+ _setMargin: function(el, n, corners) {
+ var marginSize = this._marginSize(n);
+ var whichSide = corners == "top" ? this._whichSideTop() : this._whichSideBottom();
+
+ if ( whichSide == "left" ) {
+ el.style.marginLeft = marginSize + "px"; el.style.marginRight = "0px";
+ }
+ else if ( whichSide == "right" ) {
+ el.style.marginRight = marginSize + "px"; el.style.marginLeft = "0px";
+ }
+ else {
+ el.style.marginLeft = marginSize + "px"; el.style.marginRight = marginSize + "px";
+ }
+ },
+
+ _setBorder: function(el,n,corners) {
+ var borderSize = this._borderSize(n);
+ var whichSide = corners == "top" ? this._whichSideTop() : this._whichSideBottom();
+
+ if ( whichSide == "left" ) {
+ el.style.borderLeftWidth = borderSize + "px"; el.style.borderRightWidth = "0px";
+ }
+ else if ( whichSide == "right" ) {
+ el.style.borderRightWidth = borderSize + "px"; el.style.borderLeftWidth = "0px";
+ }
+ else {
+ el.style.borderLeftWidth = borderSize + "px"; el.style.borderRightWidth = borderSize + "px";
+ }
+ },
+
+ _marginSize: function(n) {
+ if ( this._isTransparent() )
+ return 0;
+
+ var marginSizes = [ 5, 3, 2, 1 ];
+ var blendedMarginSizes = [ 3, 2, 1, 0 ];
+ var compactMarginSizes = [ 2, 1 ];
+ var smBlendedMarginSizes = [ 1, 0 ];
+
+ if ( this.options.compact && this.options.blend )
+ return smBlendedMarginSizes[n];
+ else if ( this.options.compact )
+ return compactMarginSizes[n];
+ else if ( this.options.blend )
+ return blendedMarginSizes[n];
+ else
+ return marginSizes[n];
+ },
+
+ _borderSize: function(n) {
+ var transparentBorderSizes = [ 5, 3, 2, 1 ];
+ var blendedBorderSizes = [ 2, 1, 1, 1 ];
+ var compactBorderSizes = [ 1, 0 ];
+ var actualBorderSizes = [ 0, 2, 0, 0 ];
+
+ if ( this.options.compact && (this.options.blend || this._isTransparent()) )
+ return 1;
+ else if ( this.options.compact )
+ return compactBorderSizes[n];
+ else if ( this.options.blend )
+ return blendedBorderSizes[n];
+ else if ( this.options.border )
+ return actualBorderSizes[n];
+ else if ( this._isTransparent() )
+ return transparentBorderSizes[n];
+ return 0;
+ },
+
+ _hasString: function(str) { for(var i=1 ; i<arguments.length ; i++) if (str.indexOf(arguments[i]) >= 0) return true; return false; },
+ _blend: function(c1, c2) { var cc1 = Rico.Color.createFromHex(c1); cc1.blend(Rico.Color.createFromHex(c2)); return cc1; },
+ _background: function(el) { try { return Rico.Color.createColorFromBackground(el).asHex(); } catch(err) { return "#ffffff"; } },
+ _isTransparent: function() { return this.options.color == "transparent"; },
+ _isTopRounded: function() { return this._hasString(this.options.corners, "all", "top", "tl", "tr"); },
+ _isBottomRounded: function() { return this._hasString(this.options.corners, "all", "bottom", "bl", "br"); },
+ _hasSingleTextChild: function(el) { return el.childNodes.length == 1 && el.childNodes[0].nodeType == 3; }
+}
+
+
+//-------------------- ricoDragAndDrop.js
+Rico.DragAndDrop = Class.create();
+
+Rico.DragAndDrop.prototype = {
+
+ initialize: function() {
+ this.dropZones = new Array();
+ this.draggables = new Array();
+ this.currentDragObjects = new Array();
+ this.dragElement = null;
+ this.lastSelectedDraggable = null;
+ this.currentDragObjectVisible = false;
+ this.interestedInMotionEvents = false;
+ },
+
+ registerDropZone: function(aDropZone) {
+ this.dropZones[ this.dropZones.length ] = aDropZone;
+ },
+
+ deregisterDropZone: function(aDropZone) {
+ var newDropZones = new Array();
+ var j = 0;
+ for ( var i = 0 ; i < this.dropZones.length ; i++ ) {
+ if ( this.dropZones[i] != aDropZone )
+ newDropZones[j++] = this.dropZones[i];
+ }
+
+ this.dropZones = newDropZones;
+ },
+
+ clearDropZones: function() {
+ this.dropZones = new Array();
+ },
+
+ registerDraggable: function( aDraggable ) {
+ this.draggables[ this.draggables.length ] = aDraggable;
+ this._addMouseDownHandler( aDraggable );
+ },
+
+ clearSelection: function() {
+ for ( var i = 0 ; i < this.currentDragObjects.length ; i++ )
+ this.currentDragObjects[i].deselect();
+ this.currentDragObjects = new Array();
+ this.lastSelectedDraggable = null;
+ },
+
+ hasSelection: function() {
+ return this.currentDragObjects.length > 0;
+ },
+
+ setStartDragFromElement: function( e, mouseDownElement ) {
+ this.origPos = RicoUtil.toDocumentPosition(mouseDownElement);
+ this.startx = e.screenX - this.origPos.x
+ this.starty = e.screenY - this.origPos.y
+ //this.startComponentX = e.layerX ? e.layerX : e.offsetX;
+ //this.startComponentY = e.layerY ? e.layerY : e.offsetY;
+ //this.adjustedForDraggableSize = false;
+
+ this.interestedInMotionEvents = this.hasSelection();
+ this._terminateEvent(e);
+ },
+
+ updateSelection: function( draggable, extendSelection ) {
+ if ( ! extendSelection )
+ this.clearSelection();
+
+ if ( draggable.isSelected() ) {
+ this.currentDragObjects.removeItem(draggable);
+ draggable.deselect();
+ if ( draggable == this.lastSelectedDraggable )
+ this.lastSelectedDraggable = null;
+ }
+ else {
+ this.currentDragObjects[ this.currentDragObjects.length ] = draggable;
+ draggable.select();
+ this.lastSelectedDraggable = draggable;
+ }
+ },
+
+ _mouseDownHandler: function(e) {
+ if ( arguments.length == 0 )
+ e = event;
+
+ // if not button 1 ignore it...
+ var nsEvent = e.which != undefined;
+ if ( (nsEvent && e.which != 1) || (!nsEvent && e.button != 1))
+ return;
+
+ var eventTarget = e.target ? e.target : e.srcElement;
+ var draggableObject = eventTarget.draggable;
+
+ var candidate = eventTarget;
+ while (draggableObject == null && candidate.parentNode) {
+ candidate = candidate.parentNode;
+ draggableObject = candidate.draggable;
+ }
+
+ if ( draggableObject == null )
+ return;
+
+ this.updateSelection( draggableObject, e.ctrlKey );
+
+ // clear the drop zones postion cache...
+ if ( this.hasSelection() )
+ for ( var i = 0 ; i < this.dropZones.length ; i++ )
+ this.dropZones[i].clearPositionCache();
+
+ this.setStartDragFromElement( e, draggableObject.getMouseDownHTMLElement() );
+ },
+
+
+ _mouseMoveHandler: function(e) {
+ var nsEvent = e.which != undefined;
+ if ( !this.interestedInMotionEvents ) {
+ this._terminateEvent(e);
+ return;
+ }
+
+ if ( ! this.hasSelection() )
+ return;
+
+ if ( ! this.currentDragObjectVisible )
+ this._startDrag(e);
+
+ if ( !this.activatedDropZones )
+ this._activateRegisteredDropZones();
+
+ //if ( !this.adjustedForDraggableSize )
+ // this._adjustForDraggableSize(e);
+
+ this._updateDraggableLocation(e);
+ this._updateDropZonesHover(e);
+
+ this._terminateEvent(e);
+ },
+
+ _makeDraggableObjectVisible: function(e)
+ {
+ if ( !this.hasSelection() )
+ return;
+
+ var dragElement;
+ if ( this.currentDragObjects.length > 1 )
+ dragElement = this.currentDragObjects[0].getMultiObjectDragGUI(this.currentDragObjects);
+ else
+ dragElement = this.currentDragObjects[0].getSingleObjectDragGUI();
+
+ // go ahead and absolute position it...
+ if ( RicoUtil.getElementsComputedStyle(dragElement, "position") != "absolute" )
+ dragElement.style.position = "absolute";
+
+ // need to parent him into the document...
+ if ( dragElement.parentNode == null || dragElement.parentNode.nodeType == 11 )
+ document.body.appendChild(dragElement);
+
+ this.dragElement = dragElement;
+ this._updateDraggableLocation(e);
+
+ this.currentDragObjectVisible = true;
+ },
+
+ /**
+ _adjustForDraggableSize: function(e) {
+ var dragElementWidth = this.dragElement.offsetWidth;
+ var dragElementHeight = this.dragElement.offsetHeight;
+ if ( this.startComponentX > dragElementWidth )
+ this.startx -= this.startComponentX - dragElementWidth + 2;
+ if ( e.offsetY ) {
+ if ( this.startComponentY > dragElementHeight )
+ this.starty -= this.startComponentY - dragElementHeight + 2;
+ }
+ this.adjustedForDraggableSize = true;
+ },
+ **/
+
+ _updateDraggableLocation: function(e) {
+ var dragObjectStyle = this.dragElement.style;
+ dragObjectStyle.left = (e.screenX - this.startx) + "px"
+ dragObjectStyle.top = (e.screenY - this.starty) + "px";
+ },
+
+ _updateDropZonesHover: function(e) {
+ var n = this.dropZones.length;
+ for ( var i = 0 ; i < n ; i++ ) {
+ if ( ! this._mousePointInDropZone( e, this.dropZones[i] ) )
+ this.dropZones[i].hideHover();
+ }
+
+ for ( var i = 0 ; i < n ; i++ ) {
+ if ( this._mousePointInDropZone( e, this.dropZones[i] ) ) {
+ if ( this.dropZones[i].canAccept(this.currentDragObjects) )
+ this.dropZones[i].showHover();
+ }
+ }
+ },
+
+ _startDrag: function(e) {
+ for ( var i = 0 ; i < this.currentDragObjects.length ; i++ )
+ this.currentDragObjects[i].startDrag();
+
+ this._makeDraggableObjectVisible(e);
+ },
+
+ _mouseUpHandler: function(e) {
+ if ( ! this.hasSelection() )
+ return;
+
+ var nsEvent = e.which != undefined;
+ if ( (nsEvent && e.which != 1) || (!nsEvent && e.button != 1))
+ return;
+
+ this.interestedInMotionEvents = false;
+
+ if ( this.dragElement == null ) {
+ this._terminateEvent(e);
+ return;
+ }
+
+ if ( this._placeDraggableInDropZone(e) )
+ this._completeDropOperation(e);
+ else {
+ this._terminateEvent(e);
+ new Effect.Position( this.dragElement,
+ this.origPos.x,
+ this.origPos.y,
+ 200,
+ 20,
+ { complete : this._doCancelDragProcessing.bind(this) } );
+ }
+ },
+
+ _completeDropOperation: function(e) {
+ if ( this.dragElement != this.currentDragObjects[0].getMouseDownHTMLElement() ) {
+ if ( this.dragElement.parentNode != null )
+ this.dragElement.parentNode.removeChild(this.dragElement);
+ }
+
+ this._deactivateRegisteredDropZones();
+ this._endDrag();
+ this.clearSelection();
+ this.dragElement = null;
+ this.currentDragObjectVisible = false;
+ this._terminateEvent(e);
+ },
+
+ _doCancelDragProcessing: function() {
+ this._cancelDrag();
+
+ if ( this.dragElement != this.currentDragObjects[0].getMouseDownHTMLElement() ) {
+ if ( this.dragElement.parentNode != null ) {
+ this.dragElement.parentNode.removeChild(this.dragElement);
+ }
+ }
+
+ this._deactivateRegisteredDropZones();
+ this.dragElement = null;
+ this.currentDragObjectVisible = false;
+ },
+
+ _placeDraggableInDropZone: function(e) {
+ var foundDropZone = false;
+ var n = this.dropZones.length;
+ for ( var i = 0 ; i < n ; i++ ) {
+ if ( this._mousePointInDropZone( e, this.dropZones[i] ) ) {
+ if ( this.dropZones[i].canAccept(this.currentDragObjects) ) {
+ this.dropZones[i].hideHover();
+ this.dropZones[i].accept(this.currentDragObjects);
+ foundDropZone = true;
+ break;
+ }
+ }
+ }
+
+ return foundDropZone;
+ },
+
+ _cancelDrag: function() {
+ for ( var i = 0 ; i < this.currentDragObjects.length ; i++ )
+ this.currentDragObjects[i].cancelDrag();
+ },
+
+ _endDrag: function() {
+ for ( var i = 0 ; i < this.currentDragObjects.length ; i++ )
+ this.currentDragObjects[i].endDrag();
+ },
+
+ _mousePointInDropZone: function( e, dropZone ) {
+
+ var absoluteRect = dropZone.getAbsoluteRect();
+
+ return e.clientX > absoluteRect.left &&
+ e.clientX < absoluteRect.right &&
+ e.clientY > absoluteRect.top &&
+ e.clientY < absoluteRect.bottom;
+ },
+
+ _addMouseDownHandler: function( aDraggable )
+ {
+ var htmlElement = aDraggable.getMouseDownHTMLElement();
+ if ( htmlElement != null ) {
+ htmlElement.draggable = aDraggable;
+ this._addMouseDownEvent( htmlElement );
+ }
+ },
+
+ _activateRegisteredDropZones: function() {
+ var n = this.dropZones.length;
+ for ( var i = 0 ; i < n ; i++ ) {
+ var dropZone = this.dropZones[i];
+ if ( dropZone.canAccept(this.currentDragObjects) )
+ dropZone.activate();
+ }
+
+ this.activatedDropZones = true;
+ },
+
+ _deactivateRegisteredDropZones: function() {
+ var n = this.dropZones.length;
+ for ( var i = 0 ; i < n ; i++ )
+ this.dropZones[i].deactivate();
+ this.activatedDropZones = false;
+ },
+
+ _addMouseDownEvent: function( htmlElement ) {
+ if ( typeof document.implementation != "undefined" &&
+ document.implementation.hasFeature("HTML", "1.0") &&
+ document.implementation.hasFeature("Events", "2.0") &&
+ document.implementation.hasFeature("CSS", "2.0") ) {
+ htmlElement.addEventListener("mousedown", this._mouseDownHandler.bindAsEventListener(this), false);
+ }
+ else {
+ htmlElement.attachEvent( "onmousedown", this._mouseDownHandler.bindAsEventListener(this) );
+ }
+ },
+
+ _terminateEvent: function(e) {
+ if ( e.stopPropagation != undefined )
+ e.stopPropagation();
+ else if ( e.cancelBubble != undefined )
+ e.cancelBubble = true;
+
+ if ( e.preventDefault != undefined )
+ e.preventDefault();
+ else
+ e.returnValue = false;
+ },
+
+ initializeEventHandlers: function() {
+ if ( typeof document.implementation != "undefined" &&
+ document.implementation.hasFeature("HTML", "1.0") &&
+ document.implementation.hasFeature("Events", "2.0") &&
+ document.implementation.hasFeature("CSS", "2.0") ) {
+ document.addEventListener("mouseup", this._mouseUpHandler.bindAsEventListener(this), false);
+ document.addEventListener("mousemove", this._mouseMoveHandler.bindAsEventListener(this), false);
+ }
+ else {
+ document.attachEvent( "onmouseup", this._mouseUpHandler.bindAsEventListener(this) );
+ document.attachEvent( "onmousemove", this._mouseMoveHandler.bindAsEventListener(this) );
+ }
+ }
+}
+
+var dndMgr = new Rico.DragAndDrop();
+dndMgr.initializeEventHandlers();
+
+
+//-------------------- ricoDraggable.js
+Rico.Draggable = Class.create();
+
+Rico.Draggable.prototype = {
+
+ initialize: function( type, htmlElement ) {
+ this.type = type;
+ this.htmlElement = $(htmlElement);
+ this.selected = false;
+ },
+
+ /**
+ * Returns the HTML element that should have a mouse down event
+ * added to it in order to initiate a drag operation
+ *
+ **/
+ getMouseDownHTMLElement: function() {
+ return this.htmlElement;
+ },
+
+ select: function() {
+ this.selected = true;
+
+ if ( this.showingSelected )
+ return;
+
+ var htmlElement = this.getMouseDownHTMLElement();
+
+ var color = Rico.Color.createColorFromBackground(htmlElement);
+ color.isBright() ? color.darken(0.033) : color.brighten(0.033);
+
+ this.saveBackground = RicoUtil.getElementsComputedStyle(htmlElement, "backgroundColor", "background-color");
+ htmlElement.style.backgroundColor = color.asHex();
+ this.showingSelected = true;
+ },
+
+ deselect: function() {
+ this.selected = false;
+ if ( !this.showingSelected )
+ return;
+
+ var htmlElement = this.getMouseDownHTMLElement();
+
+ htmlElement.style.backgroundColor = this.saveBackground;
+ this.showingSelected = false;
+ },
+
+ isSelected: function() {
+ return this.selected;
+ },
+
+ startDrag: function() {
+ },
+
+ cancelDrag: function() {
+ },
+
+ endDrag: function() {
+ },
+
+ getSingleObjectDragGUI: function() {
+ return this.htmlElement;
+ },
+
+ getMultiObjectDragGUI: function( draggables ) {
+ return this.htmlElement;
+ },
+
+ getDroppedGUI: function() {
+ return this.htmlElement;
+ },
+
+ toString: function() {
+ return this.type + ":" + this.htmlElement + ":";
+ }
+
+}
+
+
+//-------------------- ricoDropzone.js
+Rico.Dropzone = Class.create();
+
+Rico.Dropzone.prototype = {
+
+ initialize: function( htmlElement ) {
+ this.htmlElement = $(htmlElement);
+ this.absoluteRect = null;
+ },
+
+ getHTMLElement: function() {
+ return this.htmlElement;
+ },
+
+ clearPositionCache: function() {
+ this.absoluteRect = null;
+ },
+
+ getAbsoluteRect: function() {
+ if ( this.absoluteRect == null ) {
+ var htmlElement = this.getHTMLElement();
+ var pos = RicoUtil.toViewportPosition(htmlElement);
+
+ this.absoluteRect = {
+ top: pos.y,
+ left: pos.x,
+ bottom: pos.y + htmlElement.offsetHeight,
+ right: pos.x + htmlElement.offsetWidth
+ };
+ }
+ return this.absoluteRect;
+ },
+
+ activate: function() {
+ var htmlElement = this.getHTMLElement();
+ if (htmlElement == null || this.showingActive)
+ return;
+
+ this.showingActive = true;
+ this.saveBackgroundColor = htmlElement.style.backgroundColor;
+
+ var fallbackColor = "#ffea84";
+ var currentColor = Rico.Color.createColorFromBackground(htmlElement);
+ if ( currentColor == null )
+ htmlElement.style.backgroundColor = fallbackColor;
+ else {
+ currentColor.isBright() ? currentColor.darken(0.2) : currentColor.brighten(0.2);
+ htmlElement.style.backgroundColor = currentColor.asHex();
+ }
+ },
+
+ deactivate: function() {
+ var htmlElement = this.getHTMLElement();
+ if (htmlElement == null || !this.showingActive)
+ return;
+
+ htmlElement.style.backgroundColor = this.saveBackgroundColor;
+ this.showingActive = false;
+ this.saveBackgroundColor = null;
+ },
+
+ showHover: function() {
+ var htmlElement = this.getHTMLElement();
+ if ( htmlElement == null || this.showingHover )
+ return;
+
+ this.saveBorderWidth = htmlElement.style.borderWidth;
+ this.saveBorderStyle = htmlElement.style.borderStyle;
+ this.saveBorderColor = htmlElement.style.borderColor;
+
+ this.showingHover = true;
+ htmlElement.style.borderWidth = "1px";
+ htmlElement.style.borderStyle = "solid";
+ //htmlElement.style.borderColor = "#ff9900";
+ htmlElement.style.borderColor = "#ffff00";
+ },
+
+ hideHover: function() {
+ var htmlElement = this.getHTMLElement();
+ if ( htmlElement == null || !this.showingHover )
+ return;
+
+ htmlElement.style.borderWidth = this.saveBorderWidth;
+ htmlElement.style.borderStyle = this.saveBorderStyle;
+ htmlElement.style.borderColor = this.saveBorderColor;
+ this.showingHover = false;
+ },
+
+ canAccept: function(draggableObjects) {
+ return true;
+ },
+
+ accept: function(draggableObjects) {
+ var htmlElement = this.getHTMLElement();
+ if ( htmlElement == null )
+ return;
+
+ n = draggableObjects.length;
+ for ( var i = 0 ; i < n ; i++ )
+ {
+ var theGUI = draggableObjects[i].getDroppedGUI();
+ if ( RicoUtil.getElementsComputedStyle( theGUI, "position" ) == "absolute" )
+ {
+ theGUI.style.position = "static";
+ theGUI.style.top = "";
+ theGUI.style.top = "";
+ }
+ htmlElement.appendChild(theGUI);
+ }
+ }
+}
+
+
+//-------------------- ricoEffects.js
+
+/**
+ * Use the Effect namespace for effects. If using scriptaculous effects
+ * this will already be defined, otherwise we'll just create an empty
+ * object for it...
+ **/
+if ( window.Effect == undefined )
+ Effect = {};
+
+Effect.SizeAndPosition = Class.create();
+Effect.SizeAndPosition.prototype = {
+
+ initialize: function(element, x, y, w, h, duration, steps, options) {
+ this.element = $(element);
+ this.x = x;
+ this.y = y;
+ this.w = w;
+ this.h = h;
+ this.duration = duration;
+ this.steps = steps;
+ this.options = arguments[7] || {};
+
+ this.sizeAndPosition();
+ },
+
+ sizeAndPosition: function() {
+ if (this.isFinished()) {
+ if(this.options.complete) this.options.complete(this);
+ return;
+ }
+
+ if (this.timer)
+ clearTimeout(this.timer);
+
+ var stepDuration = Math.round(this.duration/this.steps) ;
+
+ // Get original values: x,y = top left corner; w,h = width height
+ var currentX = this.element.offsetLeft;
+ var currentY = this.element.offsetTop;
+ var currentW = this.element.offsetWidth;
+ var currentH = this.element.offsetHeight;
+
+ // If values not set, or zero, we do not modify them, and take original as final as well
+ this.x = (this.x) ? this.x : currentX;
+ this.y = (this.y) ? this.y : currentY;
+ this.w = (this.w) ? this.w : currentW;
+ this.h = (this.h) ? this.h : currentH;
+
+ // how much do we need to modify our values for each step?
+ var difX = this.steps > 0 ? (this.x - currentX)/this.steps : 0;
+ var difY = this.steps > 0 ? (this.y - currentY)/this.steps : 0;
+ var difW = this.steps > 0 ? (this.w - currentW)/this.steps : 0;
+ var difH = this.steps > 0 ? (this.h - currentH)/this.steps : 0;
+
+ this.moveBy(difX, difY);
+ this.resizeBy(difW, difH);
+
+ this.duration -= stepDuration;
+ this.steps--;
+
+ this.timer = setTimeout(this.sizeAndPosition.bind(this), stepDuration);
+ },
+
+ isFinished: function() {
+ return this.steps <= 0;
+ },
+
+ moveBy: function( difX, difY ) {
+ var currentLeft = this.element.offsetLeft;
+ var currentTop = this.element.offsetTop;
+ var intDifX = parseInt(difX);
+ var intDifY = parseInt(difY);
+
+ var style = this.element.style;
+ if ( intDifX != 0 )
+ style.left = (currentLeft + intDifX) + "px";
+ if ( intDifY != 0 )
+ style.top = (currentTop + intDifY) + "px";
+ },
+
+ resizeBy: function( difW, difH ) {
+ var currentWidth = this.element.offsetWidth;
+ var currentHeight = this.element.offsetHeight;
+ var intDifW = parseInt(difW);
+ var intDifH = parseInt(difH);
+
+ var style = this.element.style;
+ if ( intDifW != 0 )
+ style.width = (currentWidth + intDifW) + "px";
+ if ( intDifH != 0 )
+ style.height = (currentHeight + intDifH) + "px";
+ }
+}
+
+Effect.Size = Class.create();
+Effect.Size.prototype = {
+
+ initialize: function(element, w, h, duration, steps, options) {
+ new Effect.SizeAndPosition(element, null, null, w, h, duration, steps, options);
+ }
+}
+
+Effect.Position = Class.create();
+Effect.Position.prototype = {
+
+ initialize: function(element, x, y, duration, steps, options) {
+ new Effect.SizeAndPosition(element, x, y, null, null, duration, steps, options);
+ }
+}
+
+Effect.Round = Class.create();
+Effect.Round.prototype = {
+
+ initialize: function(tagName, className, options) {
+ var elements = document.getElementsByTagAndClassName(tagName,className);
+ for ( var i = 0 ; i < elements.length ; i++ )
+ Rico.Corner.round( elements[i], options );
+ }
+};
+
+Effect.FadeTo = Class.create();
+Effect.FadeTo.prototype = {
+
+ initialize: function( element, opacity, duration, steps, options) {
+ this.element = $(element);
+ this.opacity = opacity;
+ this.duration = duration;
+ this.steps = steps;
+ this.options = arguments[4] || {};
+ this.fadeTo();
+ },
+
+ fadeTo: function() {
+ if (this.isFinished()) {
+ if(this.options.complete) this.options.complete(this);
+ return;
+ }
+
+ if (this.timer)
+ clearTimeout(this.timer);
+
+ var stepDuration = Math.round(this.duration/this.steps) ;
+ var currentOpacity = this.getElementOpacity();
+ var delta = this.steps > 0 ? (this.opacity - currentOpacity)/this.steps : 0;
+
+ this.changeOpacityBy(delta);
+ this.duration -= stepDuration;
+ this.steps--;
+
+ this.timer = setTimeout(this.fadeTo.bind(this), stepDuration);
+ },
+
+ changeOpacityBy: function(v) {
+ var currentOpacity = this.getElementOpacity();
+ var newOpacity = Math.max(0, Math.min(currentOpacity+v, 1));
+ this.element.ricoOpacity = newOpacity;
+
+ this.element.style.filter = "alpha(opacity:"+Math.round(newOpacity*100)+")";
+ this.element.style.opacity = newOpacity; /*//*/;
+ },
+
+ isFinished: function() {
+ return this.steps <= 0;
+ },
+
+ getElementOpacity: function() {
+ if ( this.element.ricoOpacity == undefined ) {
+ var opacity;
+ if ( this.element.currentStyle ) {
+ opacity = this.element.currentStyle.opacity;
+ }
+ else if ( document.defaultView.getComputedStyle != undefined ) {
+ var computedStyle = document.defaultView.getComputedStyle;
+ opacity = computedStyle(this.element, null).getPropertyValue('opacity');
+ }
+
+ this.element.ricoOpacity = opacity != undefined ? opacity : 1.0;
+ }
+
+ return parseFloat(this.element.ricoOpacity);
+ }
+}
+
+Effect.AccordionSize = Class.create();
+
+Effect.AccordionSize.prototype = {
+
+ initialize: function(e1, e2, start, end, duration, steps, options) {
+ this.e1 = $(e1);
+ this.e2 = $(e2);
+ this.start = start;
+ this.end = end;
+ this.duration = duration;
+ this.steps = steps;
+ this.options = arguments[6] || {};
+
+ this.accordionSize();
+ },
+
+ accordionSize: function() {
+
+ if (this.isFinished()) {
+ // just in case there are round errors or such...
+ this.e1.style.height = this.start + "px";
+ this.e2.style.height = this.end + "px";
+
+ if(this.options.complete)
+ this.options.complete(this);
+ return;
+ }
+
+ if (this.timer)
+ clearTimeout(this.timer);
+
+ var stepDuration = Math.round(this.duration/this.steps) ;
+
+ var diff = this.steps > 0 ? (parseInt(this.e1.offsetHeight) - this.start)/this.steps : 0;
+ this.resizeBy(diff);
+
+ this.duration -= stepDuration;
+ this.steps--;
+
+ this.timer = setTimeout(this.accordionSize.bind(this), stepDuration);
+ },
+
+ isFinished: function() {
+ return this.steps <= 0;
+ },
+
+ resizeBy: function(diff) {
+ var h1Height = this.e1.offsetHeight;
+ var h2Height = this.e2.offsetHeight;
+ var intDiff = parseInt(diff);
+ if ( diff != 0 ) {
+ this.e1.style.height = (h1Height - intDiff) + "px";
+ this.e2.style.height = (h2Height + intDiff) + "px";
+ }
+ }
+
+};
+
+
+//-------------------- ricoLiveGrid.js
+
+// Rico.LiveGridMetaData -----------------------------------------------------
+
+Rico.LiveGridMetaData = Class.create();
+
+Rico.LiveGridMetaData.prototype = {
+
+ initialize: function( pageSize, totalRows, columnCount, options ) {
+ this.pageSize = pageSize;
+ this.totalRows = totalRows;
+ this.setOptions(options);
+ this.scrollArrowHeight = 16;
+ this.columnCount = columnCount;
+ },
+
+ setOptions: function(options) {
+ this.options = {
+ largeBufferSize : 7.0, // 7 pages
+ nearLimitFactor : 0.2 // 20% of buffer
+ }.extend(options || {});
+ },
+
+ getPageSize: function() {
+ return this.pageSize;
+ },
+
+ getTotalRows: function() {
+ return this.totalRows;
+ },
+
+ setTotalRows: function(n) {
+ this.totalRows = n;
+ },
+
+ getLargeBufferSize: function() {
+ return parseInt(this.options.largeBufferSize * this.pageSize);
+ },
+
+ getLimitTolerance: function() {
+ return parseInt(this.getLargeBufferSize() * this.options.nearLimitFactor);
+ }
+};
+
+// Rico.LiveGridScroller -----------------------------------------------------
+
+Rico.LiveGridScroller = Class.create();
+
+Rico.LiveGridScroller.prototype = {
+
+ initialize: function(liveGrid, viewPort) {
+ this.isIE = navigator.userAgent.toLowerCase().indexOf("msie") >= 0;
+ this.liveGrid = liveGrid;
+ this.metaData = liveGrid.metaData;
+ this.createScrollBar();
+ this.scrollTimeout = null;
+ this.lastScrollPos = 0;
+ this.viewPort = viewPort;
+ this.rows = new Array();
+ },
+
+ isUnPlugged: function() {
+ return this.scrollerDiv.onscroll == null;
+ },
+
+ plugin: function() {
+ this.scrollerDiv.onscroll = this.handleScroll.bindAsEventListener(this);
+ },
+
+ unplug: function() {
+ this.scrollerDiv.onscroll = null;
+ },
+
+ sizeIEHeaderHack: function() {
+ if ( !this.isIE ) return;
+ var headerTable = $(this.liveGrid.tableId + "_header");
+ if ( headerTable )
+ headerTable.rows[0].cells[0].style.width =
+ (headerTable.rows[0].cells[0].offsetWidth + 1) + "px";
+ },
+
+ createScrollBar: function() {
+ var visibleHeight = this.liveGrid.viewPort.visibleHeight();
+ // create the outer div...
+ this.scrollerDiv = document.createElement("div");
+ var scrollerStyle = this.scrollerDiv.style;
+ scrollerStyle.borderRight = "1px solid #ababab"; // hard coded color!!!
+ scrollerStyle.position = "relative";
+ scrollerStyle.left = this.isIE ? "-6px" : "-3px";
+ scrollerStyle.width = "19px";
+ scrollerStyle.height = visibleHeight + "px";
+ scrollerStyle.overflow = "auto";
+
+ // create the inner div...
+ this.heightDiv = document.createElement("div");
+ this.heightDiv.style.width = "1px";
+
+ this.heightDiv.style.height = parseInt(visibleHeight *
+ this.metaData.getTotalRows()/this.metaData.getPageSize()) + "px" ;
+ this.scrollerDiv.appendChild(this.heightDiv);
+ this.scrollerDiv.onscroll = this.handleScroll.bindAsEventListener(this);
+
+ var table = this.liveGrid.table;
+ table.parentNode.parentNode.insertBefore( this.scrollerDiv, table.parentNode.nextSibling );
+ },
+
+ updateSize: function() {
+ var table = this.liveGrid.table;
+ var visibleHeight = this.viewPort.visibleHeight();
+ this.heightDiv.style.height = parseInt(visibleHeight *
+ this.metaData.getTotalRows()/this.metaData.getPageSize()) + "px";
+ },
+
+ rowToPixel: function(rowOffset) {
+ return (rowOffset / this.metaData.getTotalRows()) * this.heightDiv.offsetHeight
+ },
+
+ moveScroll: function(rowOffset) {
+ this.scrollerDiv.scrollTop = this.rowToPixel(rowOffset);
+ if ( this.metaData.options.onscroll )
+ this.metaData.options.onscroll( this.liveGrid, rowOffset );
+ },
+
+ handleScroll: function() {
+ if ( this.scrollTimeout )
+ clearTimeout( this.scrollTimeout );
+
+ var contentOffset = parseInt(this.scrollerDiv.scrollTop / this.viewPort.rowHeight);
+ this.liveGrid.requestContentRefresh(contentOffset);
+ this.viewPort.scrollTo(this.scrollerDiv.scrollTop);
+
+ if ( this.metaData.options.onscroll )
+ this.metaData.options.onscroll( this.liveGrid, contentOffset );
+
+ this.scrollTimeout = setTimeout( this.scrollIdle.bind(this), 1200 );
+ },
+
+ scrollIdle: function() {
+ if ( this.metaData.options.onscrollidle )
+ this.metaData.options.onscrollidle();
+ }
+};
+
+// Rico.LiveGridBuffer -----------------------------------------------------
+
+Rico.LiveGridBuffer = Class.create();
+
+Rico.LiveGridBuffer.prototype = {
+
+ initialize: function(metaData, viewPort) {
+ this.startPos = 0;
+ this.size = 0;
+ this.metaData = metaData;
+ this.rows = new Array();
+ this.updateInProgress = false;
+ this.viewPort = viewPort;
+ this.maxBufferSize = metaData.getLargeBufferSize() * 2;
+ this.maxFetchSize = metaData.getLargeBufferSize();
+ this.lastOffset = 0;
+ },
+
+ getBlankRow: function() {
+ if (!this.blankRow ) {
+ this.blankRow = new Array();
+ for ( var i=0; i < this.metaData.columnCount ; i++ )
+ this.blankRow[i] = " ";
+ }
+ return this.blankRow;
+ },
+
+ loadRows: function(ajaxResponse) {
+ var rowsElement = ajaxResponse.getElementsByTagName('rows')[0];
+ this.updateUI = rowsElement.getAttribute("update_ui") == "true"
+ var newRows = new Array()
+ var trs = rowsElement.getElementsByTagName("tr");
+ for ( var i=0 ; i < trs.length; i++ ) {
+ var row = newRows[i] = new Array();
+ var cells = trs[i].getElementsByTagName("td");
+ for ( var j=0; j < cells.length ; j++ ) {
+ var cell = cells[j];
+ var convertSpaces = cell.getAttribute("convert_spaces") == "true";
+ var cellContent = RicoUtil.getContentAsString(cell);
+ row[j] = convertSpaces ? this.convertSpaces(cellContent) : cellContent;
+ if (!row[j])
+ row[j] = ' ';
+ }
+ }
+ return newRows;
+ },
+
+ update: function(ajaxResponse, start) {
+ var newRows = this.loadRows(ajaxResponse);
+ if (this.rows.length == 0) { // initial load
+ this.rows = newRows;
+ this.size = this.rows.length;
+ this.startPos = start;
+ return;
+ }
+ if (start > this.startPos) { //appending
+ if (this.startPos + this.rows.length < start) {
+ this.rows = newRows;
+ this.startPos = start;//
+ } else {
+ this.rows = this.rows.concat( newRows.slice(0, newRows.length));
+ if (this.rows.length > this.maxBufferSize) {
+ var fullSize = this.rows.length;
+ this.rows = this.rows.slice(this.rows.length - this.maxBufferSize, this.rows.length)
+ this.startPos = this.startPos + (fullSize - this.rows.length);
+ }
+ }
+ } else { //prepending
+ if (start + newRows.length < this.startPos) {
+ this.rows = newRows;
+ } else {
+ this.rows = newRows.slice(0, this.startPos).concat(this.rows);
+ if (this.rows.length > this.maxBufferSize)
+ this.rows = this.rows.slice(0, this.maxBufferSize)
+ }
+ this.startPos = start;
+ }
+ this.size = this.rows.length;
+ },
+
+ clear: function() {
+ this.rows = new Array();
+ this.startPos = 0;
+ this.size = 0;
+ },
+
+ isOverlapping: function(start, size) {
+ return ((start < this.endPos()) && (this.startPos < start + size)) || (this.endPos() == 0)
+ },
+
+ isInRange: function(position) {
+ return (position >= this.startPos) && (position + this.metaData.getPageSize() <= this.endPos());
+ //&& this.size() != 0;
+ },
+
+ isNearingTopLimit: function(position) {
+ return position - this.startPos < this.metaData.getLimitTolerance();
+ },
+
+ endPos: function() {
+ return this.startPos + this.rows.length;
+ },
+
+ isNearingBottomLimit: function(position) {
+ return this.endPos() - (position + this.metaData.getPageSize()) < this.metaData.getLimitTolerance();
+ },
+
+ isAtTop: function() {
+ return this.startPos == 0;
+ },
+
+ isAtBottom: function() {
+ return this.endPos() == this.metaData.getTotalRows();
+ },
+
+ isNearingLimit: function(position) {
+ return ( !this.isAtTop() && this.isNearingTopLimit(position)) ||
+ ( !this.isAtBottom() && this.isNearingBottomLimit(position) )
+ },
+
+ getFetchSize: function(offset) {
+ var adjustedOffset = this.getFetchOffset(offset);
+ var adjustedSize = 0;
+ if (adjustedOffset >= this.startPos) { //apending
+ var endFetchOffset = this.maxFetchSize + adjustedOffset;
+ if (endFetchOffset > this.metaData.totalRows)
+ endFetchOffset = this.metaData.totalRows;
+ adjustedSize = endFetchOffset - adjustedOffset;
+ } else {//prepending
+ var adjustedSize = this.startPos - adjustedOffset;
+ if (adjustedSize > this.maxFetchSize)
+ adjustedSize = this.maxFetchSize;
+ }
+ return adjustedSize;
+ },
+
+ getFetchOffset: function(offset) {
+ var adjustedOffset = offset;
+ if (offset > this.startPos) //apending
+ adjustedOffset = (offset > this.endPos()) ? offset : this.endPos();
+ else { //prepending
+ if (offset + this.maxFetchSize >= this.startPos) {
+ var adjustedOffset = this.startPos - this.maxFetchSize;
+ if (adjustedOffset < 0)
+ adjustedOffset = 0;
+ }
+ }
+ this.lastOffset = adjustedOffset;
+ return adjustedOffset;
+ },
+
+ getRows: function(start, count) {
+ var begPos = start - this.startPos
+ var endPos = begPos + count
+
+ // er? need more data...
+ if ( endPos > this.size )
+ endPos = this.size
+
+ var results = new Array()
+ var index = 0;
+ for ( var i=begPos ; i < endPos; i++ ) {
+ results[index++] = this.rows[i]
+ }
+ return results
+ },
+
+ convertSpaces: function(s) {
+ return s.split(" ").join(" ");
+ }
+
+};
+
+
+//Rico.GridViewPort --------------------------------------------------
+Rico.GridViewPort = Class.create();
+
+Rico.GridViewPort.prototype = {
+
+ initialize: function(table, rowHeight, visibleRows, buffer, liveGrid) {
+ this.lastDisplayedStartPos = 0;
+ this.div = table.parentNode;
+ this.table = table
+ this.rowHeight = rowHeight;
+ this.div.style.height = this.rowHeight * visibleRows;
+ this.div.style.overflow = "hidden";
+ this.buffer = buffer;
+ this.liveGrid = liveGrid;
+ this.visibleRows = visibleRows + 1;
+ this.lastPixelOffset = 0;
+ this.startPos = 0;
+ },
+
+ populateRow: function(htmlRow, row) {
+ for (var j=0; j < row.length; j++) {
+ htmlRow.cells[j].innerHTML = row[j]
+ }
+ },
+
+ bufferChanged: function() {
+ this.refreshContents( parseInt(this.lastPixelOffset / this.rowHeight));
+ },
+
+ clearRows: function() {
+ if (!this.isBlank) {
+ for (var i=0; i < this.visibleRows; i++)
+ this.populateRow(this.table.rows[i], this.buffer.getBlankRow());
+ this.isBlank = true;
+ }
+ },
+
+ clearContents: function() {
+ this.clearRows();
+ this.scrollTo(0);
+ this.startPos = 0;
+ this.lastStartPos = -1;
+ },
+
+ refreshContents: function(startPos) {
+ if (startPos == this.lastRowPos && !this.isPartialBlank && !this.isBlank) {
+ return;
+ }
+ if ((startPos + this.visibleRows < this.buffer.startPos)
+ || (this.buffer.startPos + this.buffer.size < startPos)
+ || (this.buffer.size == 0)) {
+ this.clearRows();
+ return;
+ }
+ this.isBlank = false;
+ var viewPrecedesBuffer = this.buffer.startPos > startPos
+ var contentStartPos = viewPrecedesBuffer ? this.buffer.startPos: startPos;
+
+ var contentEndPos = (this.buffer.startPos + this.buffer.size < startPos + this.visibleRows)
+ ? this.buffer.startPos + this.buffer.size
+ : startPos + this.visibleRows;
+ var rowSize = contentEndPos - contentStartPos;
+ var rows = this.buffer.getRows(contentStartPos, rowSize );
+ var blankSize = this.visibleRows - rowSize;
+ var blankOffset = viewPrecedesBuffer ? 0: rowSize;
+ var contentOffset = viewPrecedesBuffer ? blankSize: 0;
+
+ for (var i=0; i < rows.length; i++) {//initialize what we have
+ this.populateRow(this.table.rows[i + contentOffset], rows[i]);
+ }
+ for (var i=0; i < blankSize; i++) {// blank out the rest
+ this.populateRow(this.table.rows[i + blankOffset], this.buffer.getBlankRow());
+ }
+ this.isPartialBlank = blankSize > 0;
+ this.lastRowPos = startPos;
+ },
+
+ scrollTo: function(pixelOffset) {
+ if (this.lastPixelOffset == pixelOffset)
+ return;
+
+ this.refreshContents(parseInt(pixelOffset / this.rowHeight))
+ this.div.scrollTop = pixelOffset % this.rowHeight
+
+ this.lastPixelOffset = pixelOffset;
+ },
+
+ visibleHeight: function() {
+ return parseInt(this.div.style.height);
+ }
+
+};
+
+
+Rico.LiveGridRequest = Class.create();
+Rico.LiveGridRequest.prototype = {
+ initialize: function( requestOffset, options ) {
+ this.requestOffset = requestOffset;
+ }
+};
+
+// Rico.LiveGrid -----------------------------------------------------
+
+Rico.LiveGrid = Class.create();
+
+Rico.LiveGrid.prototype = {
+
+ initialize: function( tableId, visibleRows, totalRows, url, options ) {
+ if ( options == null )
+ options = {};
+
+ this.tableId = tableId;
+ this.table = $(tableId);
+ var columnCount = this.table.rows[0].cells.length
+ this.metaData = new Rico.LiveGridMetaData(visibleRows, totalRows, columnCount, options);
+ this.buffer = new Rico.LiveGridBuffer(this.metaData);
+
+ var rowCount = this.table.rows.length;
+ this.viewPort = new Rico.GridViewPort(this.table,
+ this.table.offsetHeight/rowCount,
+ visibleRows,
+ this.buffer, this);
+ this.scroller = new Rico.LiveGridScroller(this,this.viewPort);
+
+ this.additionalParms = options.requestParameters || [];
+
+ options.sortHandler = this.sortHandler.bind(this);
+
+ if ( $(tableId + '_header') )
+ this.sort = new Rico.LiveGridSort(tableId + '_header', options)
+
+ this.processingRequest = null;
+ this.unprocessedRequest = null;
+
+ this.initAjax(url);
+ if ( options.prefetchBuffer || options.prefetchOffset > 0) {
+ var offset = 0;
+ if (options.offset ) {
+ offset = options.offset;
+ this.scroller.moveScroll(offset);
+ this.viewPort.scrollTo(this.scroller.rowToPixel(offset));
+ }
+ if (options.sortCol) {
+ this.sortCol = options.sortCol;
+ this.sortDir = options.sortDir;
+ }
+ this.requestContentRefresh(offset);
+ }
+ },
+
+ resetContents: function() {
+ this.scroller.moveScroll(0);
+ this.buffer.clear();
+ this.viewPort.clearContents();
+ },
+
+ sortHandler: function(column) {
+ this.sortCol = column.name;
+ this.sortDir = column.currentSort;
+
+ this.resetContents();
+ this.requestContentRefresh(0)
+ },
+
+ setRequestParams: function() {
+ this.additionalParms = [];
+ for ( var i=0 ; i < arguments.length ; i++ )
+ this.additionalParms[i] = arguments[i];
+ },
+
+ setTotalRows: function( newTotalRows ) {
+ this.resetContents();
+ this.metaData.setTotalRows(newTotalRows);
+ this.scroller.updateSize();
+ },
+
+ initAjax: function(url) {
+ ajaxEngine.registerRequest( this.tableId + '_request', url );
+ ajaxEngine.registerAjaxObject( this.tableId + '_updater', this );
+ },
+
+ invokeAjax: function() {
+ },
+
+ handleTimedOut: function() {
+ //server did not respond in 4 seconds... assume that there could have been
+ //an error or something, and allow requests to be processed again...
+ this.processingRequest = null;
+ this.processQueuedRequest();
+ },
+
+ fetchBuffer: function(offset) {
+ if ( this.buffer.isInRange(offset) &&
+ !this.buffer.isNearingLimit(offset)) {
+ return;
+ }
+ if (this.processingRequest) {
+ this.unprocessedRequest = new Rico.LiveGridRequest(offset);
+ return;
+ }
+ var bufferStartPos = this.buffer.getFetchOffset(offset);
+ this.processingRequest = new Rico.LiveGridRequest(offset);
+ this.processingRequest.bufferOffset = bufferStartPos;
+ var fetchSize = this.buffer.getFetchSize(offset);
+ var partialLoaded = false;
+ var callParms = [];
+ callParms.push(this.tableId + '_request');
+ callParms.push('id=' + this.tableId);
+ callParms.push('page_size=' + fetchSize);
+ callParms.push('offset=' + bufferStartPos);
+ if ( this.sortCol) {
+ callParms.push('sort_col=' + this.sortCol);
+ callParms.push('sort_dir=' + this.sortDir);
+ }
+
+ for( var i=0 ; i < this.additionalParms.length ; i++ )
+ callParms.push(this.additionalParms[i]);
+ ajaxEngine.sendRequest.apply( ajaxEngine, callParms );
+
+ this.timeoutHandler = setTimeout( this.handleTimedOut.bind(this), 20000 ); //todo: make as option
+ },
+
+ requestContentRefresh: function(contentOffset) {
+ this.fetchBuffer(contentOffset);
+ },
+
+ ajaxUpdate: function(ajaxResponse) {
+ try {
+ clearTimeout( this.timeoutHandler );
+ this.buffer.update(ajaxResponse,this.processingRequest.bufferOffset);
+ this.viewPort.bufferChanged();
+ }
+ catch(err) {}
+ finally {this.processingRequest = null; }
+ this.processQueuedRequest();
+ },
+
+ processQueuedRequest: function() {
+ if (this.unprocessedRequest != null) {
+ this.requestContentRefresh(this.unprocessedRequest.requestOffset);
+ this.unprocessedRequest = null
+ }
+ }
+
+};
+
+
+//-------------------- ricoLiveGridSort.js
+Rico.LiveGridSort = Class.create();
+
+Rico.LiveGridSort.prototype = {
+
+ initialize: function(headerTableId, options) {
+ this.headerTableId = headerTableId;
+ this.headerTable = $(headerTableId);
+ this.setOptions(options);
+ this.applySortBehavior();
+
+ if ( this.options.sortCol ) {
+ this.setSortUI( this.options.sortCol, this.options.sortDir );
+ }
+ },
+
+ setSortUI: function( columnName, sortDirection ) {
+ var cols = this.options.columns;
+ for ( var i = 0 ; i < cols.length ; i++ ) {
+ if ( cols[i].name == columnName ) {
+ this.setColumnSort(i, sortDirection);
+ break;
+ }
+ }
+ },
+
+ setOptions: function(options) {
+ this.options = {
+ sortAscendImg: 'images/sort_asc.gif',
+ sortDescendImg: 'images/sort_desc.gif',
+ imageWidth: 9,
+ imageHeight: 5,
+ ajaxSortURLParms: []
+ }.extend(options);
+
+ // preload the images...
+ new Image().src = this.options.sortAscendImg;
+ new Image().src = this.options.sortDescendImg;
+
+ this.sort = options.sortHandler;
+ if ( !this.options.columns )
+ this.options.columns = this.introspectForColumnInfo();
+ else {
+ // allow client to pass { columns: [ ["a", true], ["b", false] ] }
+ // and convert to an array of Rico.TableColumn objs...
+ this.options.columns = this.convertToTableColumns(this.options.columns);
+ }
+ },
+
+ applySortBehavior: function() {
+ var headerRow = this.headerTable.rows[0];
+ var headerCells = headerRow.cells;
+ for ( var i = 0 ; i < headerCells.length ; i++ ) {
+ this.addSortBehaviorToColumn( i, headerCells[i] );
+ }
+ },
+
+ addSortBehaviorToColumn: function( n, cell ) {
+ if ( this.options.columns[n].isSortable() ) {
+ cell.id = this.headerTableId + '_' + n;
+ cell.style.cursor = 'pointer';
+ cell.onclick = this.headerCellClicked.bindAsEventListener(this);
+ cell.innerHTML = cell.innerHTML + '<span id="' + this.headerTableId + '_img_' + n + '">'
+ + ' </span>';
+ }
+ },
+
+ // event handler....
+ headerCellClicked: function(evt) {
+ var eventTarget = evt.target ? evt.target : evt.srcElement;
+ var cellId = eventTarget.id;
+ var columnNumber = parseInt(cellId.substring( cellId.lastIndexOf('_') + 1 ));
+ var sortedColumnIndex = this.getSortedColumnIndex();
+ if ( sortedColumnIndex != -1 ) {
+ if ( sortedColumnIndex != columnNumber ) {
+ this.removeColumnSort(sortedColumnIndex);
+ this.setColumnSort(columnNumber, Rico.TableColumn.SORT_ASC);
+ }
+ else
+ this.toggleColumnSort(sortedColumnIndex);
+ }
+ else
+ this.setColumnSort(columnNumber, Rico.TableColumn.SORT_ASC);
+
+ if (this.options.sortHandler) {
+ this.options.sortHandler(this.options.columns[columnNumber]);
+ }
+ },
+
+ removeColumnSort: function(n) {
+ this.options.columns[n].setUnsorted();
+ this.setSortImage(n);
+ },
+
+ setColumnSort: function(n, direction) {
+ this.options.columns[n].setSorted(direction);
+ this.setSortImage(n);
+ },
+
+ toggleColumnSort: function(n) {
+ this.options.columns[n].toggleSort();
+ this.setSortImage(n);
+ },
+
+ setSortImage: function(n) {
+ var sortDirection = this.options.columns[n].getSortDirection();
+
+ var sortImageSpan = $( this.headerTableId + '_img_' + n );
+ if ( sortDirection == Rico.TableColumn.UNSORTED )
+ sortImageSpan.innerHTML = ' ';
+ else if ( sortDirection == Rico.TableColumn.SORT_ASC )
+ sortImageSpan.innerHTML = ' <img width="' + this.options.imageWidth + '" ' +
+ 'height="'+ this.options.imageHeight + '" ' +
+ 'src="' + this.options.sortAscendImg + '"/>';
+ else if ( sortDirection == Rico.TableColumn.SORT_DESC )
+ sortImageSpan.innerHTML = ' <img width="' + this.options.imageWidth + '" ' +
+ 'height="'+ this.options.imageHeight + '" ' +
+ 'src="' + this.options.sortDescendImg + '"/>';
+ },
+
+ getSortedColumnIndex: function() {
+ var cols = this.options.columns;
+ for ( var i = 0 ; i < cols.length ; i++ ) {
+ if ( cols[i].isSorted() )
+ return i;
+ }
+
+ return -1;
+ },
+
+ introspectForColumnInfo: function() {
+ var columns = new Array();
+ var headerRow = this.headerTable.rows[0];
+ var headerCells = headerRow.cells;
+ for ( var i = 0 ; i < headerCells.length ; i++ )
+ columns.push( new Rico.TableColumn( this.deriveColumnNameFromCell(headerCells[i],i), true ) );
+ return columns;
+ },
+
+ convertToTableColumns: function(cols) {
+ var columns = new Array();
+ for ( var i = 0 ; i < cols.length ; i++ )
+ columns.push( new Rico.TableColumn( cols[i][0], cols[i][1] ) );
+ },
+
+ deriveColumnNameFromCell: function(cell,columnNumber) {
+ var cellContent = cell.innerText != undefined ? cell.innerText : cell.textContent;
+ return cellContent ? cellContent.toLowerCase().split(' ').join('_') : "col_" + columnNumber;
+ }
+};
+
+Rico.TableColumn = Class.create();
+
+Rico.TableColumn.UNSORTED = 0;
+Rico.TableColumn.SORT_ASC = "ASC";
+Rico.TableColumn.SORT_DESC = "DESC";
+
+Rico.TableColumn.prototype = {
+ initialize: function(name, sortable) {
+ this.name = name;
+ this.sortable = sortable;
+ this.currentSort = Rico.TableColumn.UNSORTED;
+ },
+
+ isSortable: function() {
+ return this.sortable;
+ },
+
+ isSorted: function() {
+ return this.currentSort != Rico.TableColumn.UNSORTED;
+ },
+
+ getSortDirection: function() {
+ return this.currentSort;
+ },
+
+ toggleSort: function() {
+ if ( this.currentSort == Rico.TableColumn.UNSORTED || this.currentSort == Rico.TableColumn.SORT_DESC )
+ this.currentSort = Rico.TableColumn.SORT_ASC;
+ else if ( this.currentSort == Rico.TableColumn.SORT_ASC )
+ this.currentSort = Rico.TableColumn.SORT_DESC;
+ },
+
+ setUnsorted: function(direction) {
+ this.setSorted(Rico.TableColumn.UNSORTED);
+ },
+
+ setSorted: function(direction) {
+ // direction must by one of Rico.TableColumn.UNSORTED, .SORT_ASC, or .SET_DESC...
+ this.currentSort = direction;
+ }
+
+};
+
+
+//-------------------- ricoUtil.js
+
+var RicoUtil = {
+
+ getElementsComputedStyle: function ( htmlElement, cssProperty, mozillaEquivalentCSS) {
+ if ( arguments.length == 2 )
+ mozillaEquivalentCSS = cssProperty;
+
+ var el = $(htmlElement);
+ if ( el.currentStyle )
+ return el.currentStyle[cssProperty];
+ else
+ return document.defaultView.getComputedStyle(el, null).getPropertyValue(mozillaEquivalentCSS);
+ },
+
+ createXmlDocument : function() {
+ if (document.implementation && document.implementation.createDocument) {
+ var doc = document.implementation.createDocument("", "", null);
+
+ if (doc.readyState == null) {
+ doc.readyState = 1;
+ doc.addEventListener("load", function () {
+ doc.readyState = 4;
+ if (typeof doc.onreadystatechange == "function")
+ doc.onreadystatechange();
+ }, false);
+ }
+
+ return doc;
+ }
+
+ if (window.ActiveXObject)
+ return Try.these(
+ function() { return new ActiveXObject('MSXML2.DomDocument') },
+ function() { return new ActiveXObject('Microsoft.DomDocument')},
+ function() { return new ActiveXObject('MSXML.DomDocument') },
+ function() { return new ActiveXObject('MSXML3.DomDocument') }
+ ) || false;
+
+ return null;
+ },
+
+ getContentAsString: function( parentNode ) {
+ return parentNode.xml != undefined ?
+ this._getContentAsStringIE(parentNode) :
+ this._getContentAsStringMozilla(parentNode);
+ },
+
+ _getContentAsStringIE: function(parentNode) {
+ var contentStr = "";
+ for ( var i = 0 ; i < parentNode.childNodes.length ; i++ )
+ contentStr += parentNode.childNodes[i].xml;
+ return contentStr;
+ },
+
+ _getContentAsStringMozilla: function(parentNode) {
+ var xmlSerializer = new XMLSerializer();
+ var contentStr = "";
+ for ( var i = 0 ; i < parentNode.childNodes.length ; i++ )
+ contentStr += xmlSerializer.serializeToString(parentNode.childNodes[i]);
+ return contentStr;
+ },
+
+ toViewportPosition: function(element) {
+ return this._toAbsolute(element,true);
+ },
+
+ toDocumentPosition: function(element) {
+ return this._toAbsolute(element,false);
+ },
+
+ /**
+ * Compute the elements position in terms of the window viewport
+ * so that it can be compared to the position of the mouse (dnd)
+ * This is additions of all the offsetTop,offsetLeft values up the
+ * offsetParent hierarchy, ...taking into account any scrollTop,
+ * scrollLeft values along the way...
+ *
+ * IE has a bug reporting a correct offsetLeft of elements within a
+ * a relatively positioned parent!!!
+ **/
+ _toAbsolute: function(element,accountForDocScroll) {
+
+ if ( navigator.userAgent.toLowerCase().indexOf("msie") == -1 )
+ return this._toAbsoluteMozilla(element,accountForDocScroll);
+
+ var x = 0;
+ var y = 0;
+ var parent = element;
+ while ( parent ) {
+
+ var borderXOffset = 0;
+ var borderYOffset = 0;
+ if ( parent != element ) {
+ var borderXOffset = parseInt(this.getElementsComputedStyle(parent, "borderLeftWidth" ));
+ var borderYOffset = parseInt(this.getElementsComputedStyle(parent, "borderTopWidth" ));
+ borderXOffset = isNaN(borderXOffset) ? 0 : borderXOffset;
+ borderYOffset = isNaN(borderYOffset) ? 0 : borderYOffset;
+ }
+
+ x += parent.offsetLeft - parent.scrollLeft + borderXOffset;
+ y += parent.offsetTop - parent.scrollTop + borderYOffset;
+ parent = parent.offsetParent;
+ }
+
+ if ( accountForDocScroll ) {
+ x -= this.docScrollLeft();
+ y -= this.docScrollTop();
+ }
+
+ return { x:x, y:y };
+ },
+
+ /**
+ * Mozilla did not report all of the parents up the hierarchy via the
+ * offsetParent property that IE did. So for the calculation of the
+ * offsets we use the offsetParent property, but for the calculation of
+ * the scrollTop/scrollLeft adjustments we navigate up via the parentNode
+ * property instead so as to get the scroll offsets...
+ *
+ **/
+ _toAbsoluteMozilla: function(element,accountForDocScroll) {
+ var x = 0;
+ var y = 0;
+ var parent = element;
+ while ( parent ) {
+ x += parent.offsetLeft;
+ y += parent.offsetTop;
+ parent = parent.offsetParent;
+ }
+
+ parent = element;
+ while ( parent &&
+ parent != document.body &&
+ parent != document.documentElement ) {
+ if ( parent.scrollLeft )
+ x -= parent.scrollLeft;
+ if ( parent.scrollTop )
+ y -= parent.scrollTop;
+ parent = parent.parentNode;
+ }
+
+ if ( accountForDocScroll ) {
+ x -= this.docScrollLeft();
+ y -= this.docScrollTop();
+ }
+
+ return { x:x, y:y };
+ },
+
+ docScrollLeft: function() {
+ if ( window.pageXOffset )
+ return window.pageXOffset;
+ else if ( document.documentElement && document.documentElement.scrollLeft )
+ return document.documentElement.scrollLeft;
+ else if ( document.body )
+ return document.body.scrollLeft;
+ else
+ return 0;
+ },
+
+ docScrollTop: function() {
+ if ( window.pageYOffset )
+ return window.pageYOffset;
+ else if ( document.documentElement && document.documentElement.scrollTop )
+ return document.documentElement.scrollTop;
+ else if ( document.body )
+ return document.body.scrollTop;
+ else
+ return 0;
+ }
+
+};
diff --git a/web/static/js/scriptaculous/builder.js b/web/static/js/scriptaculous/builder.js
new file mode 100644
index 0000000..5e00f45
--- /dev/null
+++ b/web/static/js/scriptaculous/builder.js
@@ -0,0 +1,97 @@
+// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+//
+// See scriptaculous.js for full license.
+
+var Builder = {
+ NODEMAP: {
+ AREA: 'map',
+ CAPTION: 'table',
+ COL: 'table',
+ COLGROUP: 'table',
+ LEGEND: 'fieldset',
+ OPTGROUP: 'select',
+ OPTION: 'select',
+ PARAM: 'object',
+ TBODY: 'table',
+ TD: 'table',
+ TFOOT: 'table',
+ TH: 'table',
+ THEAD: 'table',
+ TR: 'table'
+ },
+ // note: For Firefox < 1.5, OPTION and OPTGROUP tags are currently broken,
+ // due to a Firefox bug
+ node: function(elementName) {
+ elementName = elementName.toUpperCase();
+
+ // try innerHTML approach
+ var parentTag = this.NODEMAP[elementName] || 'div';
+ var parentElement = document.createElement(parentTag);
+ parentElement.innerHTML = "<" + elementName + "></" + elementName + ">";
+ var element = parentElement.firstChild || null;
+
+ // see if browser added wrapping tags
+ if(element && (element.tagName != elementName))
+ element = element.getElementsByTagName(elementName)[0];
+
+ // fallback to createElement approach
+ if(!element) element = document.createElement(elementName);
+
+ // abort if nothing could be created
+ if(!element) return;
+
+ // attributes (or text)
+ if(arguments[1])
+ if(this._isStringOrNumber(arguments[1]) ||
+ (arguments[1] instanceof Array)) {
+ this._children(element, arguments[1]);
+ } else {
+ var attrs = this._attributes(arguments[1]);
+ if(attrs.length) {
+ parentElement.innerHTML = "<" +elementName + " " +
+ attrs + "></" + elementName + ">";
+ element = parentElement.firstChild || null;
+ // workaround firefox 1.0.X bug
+ if(!element) {
+ element = document.createElement(elementName);
+ for(attr in arguments[1])
+ element[attr == 'class' ? 'className' : attr] = arguments[1][attr];
+ }
+ if(element.tagName != elementName)
+ element = parentElement.getElementsByTagName(elementName)[0];
+ }
+ }
+
+ // text, or array of children
+ if(arguments[2])
+ this._children(element, arguments[2]);
+
+ return element;
+ },
+ _text: function(text) {
+ return document.createTextNode(text);
+ },
+ _attributes: function(attributes) {
+ var attrs = [];
+ for(attribute in attributes)
+ attrs.push((attribute=='className' ? 'class' : attribute) +
+ '="' + attributes[attribute].toString().escapeHTML() + '"');
+ return attrs.join(" ");
+ },
+ _children: function(element, children) {
+ if(typeof children=='object') { // array can hold nodes and text
+ children.flatten().each( function(e) {
+ if(typeof e=='object')
+ element.appendChild(e)
+ else
+ if(Builder._isStringOrNumber(e))
+ element.appendChild(Builder._text(e));
+ });
+ } else
+ if(Builder._isStringOrNumber(children))
+ element.appendChild(Builder._text(children));
+ },
+ _isStringOrNumber: function(param) {
+ return(typeof param=='string' || typeof param=='number');
+ }
+}
\ No newline at end of file
diff --git a/web/static/js/scriptaculous/controls.js b/web/static/js/scriptaculous/controls.js
new file mode 100644
index 0000000..18bdf20
--- /dev/null
+++ b/web/static/js/scriptaculous/controls.js
@@ -0,0 +1,721 @@
+// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+// (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
+// (c) 2005 Jon Tirsen (http://www.tirsen.com)
+// Contributors:
+// Richard Livsey
+// Rahul Bhargava
+// Rob Wills
+//
+// See scriptaculous.js for full license.
+
+// Autocompleter.Base handles all the autocompletion functionality
+// that's independent of the data source for autocompletion. This
+// includes drawing the autocompletion menu, observing keyboard
+// and mouse events, and similar.
+//
+// Specific autocompleters need to provide, at the very least,
+// a getUpdatedChoices function that will be invoked every time
+// the text inside the monitored textbox changes. This method
+// should get the text for which to provide autocompletion by
+// invoking this.getToken(), NOT by directly accessing
+// this.element.value. This is to allow incremental tokenized
+// autocompletion. Specific auto-completion logic (AJAX, etc)
+// belongs in getUpdatedChoices.
+//
+// Tokenized incremental autocompletion is enabled automatically
+// when an autocompleter is instantiated with the 'tokens' option
+// in the options parameter, e.g.:
+// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
+// will incrementally autocomplete with a comma as the token.
+// Additionally, ',' in the above example can be replaced with
+// a token array, e.g. { tokens: [',', '\n'] } which
+// enables autocompletion on multiple tokens. This is most
+// useful when one of the tokens is \n (a newline), as it
+// allows smart autocompletion after linebreaks.
+
+var Autocompleter = {}
+Autocompleter.Base = function() {};
+Autocompleter.Base.prototype = {
+ baseInitialize: function(element, update, options) {
+ this.element = $(element);
+ this.update = $(update);
+ this.hasFocus = false;
+ this.changed = false;
+ this.active = false;
+ this.index = 0;
+ this.entryCount = 0;
+
+ if (this.setOptions)
+ this.setOptions(options);
+ else
+ this.options = options || {};
+
+ this.options.paramName = this.options.paramName || this.element.name;
+ this.options.tokens = this.options.tokens || [];
+ this.options.frequency = this.options.frequency || 0.4;
+ this.options.minChars = this.options.minChars || 1;
+ this.options.onShow = this.options.onShow ||
+ function(element, update){
+ if(!update.style.position || update.style.position=='absolute') {
+ update.style.position = 'absolute';
+ Position.clone(element, update, {setHeight: false, offsetTop: element.offsetHeight});
+ }
+ Effect.Appear(update,{duration:0.15});
+ };
+ this.options.onHide = this.options.onHide ||
+ function(element, update){ new Effect.Fade(update,{duration:0.15}) };
+
+ if (typeof(this.options.tokens) == 'string')
+ this.options.tokens = new Array(this.options.tokens);
+
+ this.observer = null;
+
+ this.element.setAttribute('autocomplete','off');
+
+ Element.hide(this.update);
+
+ Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this));
+ Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this));
+ },
+
+ show: function() {
+ if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
+ if(!this.iefix && (navigator.appVersion.indexOf('MSIE')>0) && (Element.getStyle(this.update, 'position')=='absolute')) {
+ new Insertion.After(this.update,
+ '<iframe id="' + this.update.id + '_iefix" '+
+ 'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
+ 'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
+ this.iefix = $(this.update.id+'_iefix');
+ }
+ if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
+ },
+
+ fixIEOverlapping: function() {
+ Position.clone(this.update, this.iefix);
+ this.iefix.style.zIndex = 1;
+ this.update.style.zIndex = 2;
+ Element.show(this.iefix);
+ },
+
+ hide: function() {
+ this.stopIndicator();
+ if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
+ if(this.iefix) Element.hide(this.iefix);
+ },
+
+ startIndicator: function() {
+ if(this.options.indicator) Element.show(this.options.indicator);
+ },
+
+ stopIndicator: function() {
+ if(this.options.indicator) Element.hide(this.options.indicator);
+ },
+
+ onKeyPress: function(event) {
+ if(this.active)
+ switch(event.keyCode) {
+ case Event.KEY_TAB:
+ case Event.KEY_RETURN:
+ this.selectEntry();
+ Event.stop(event);
+ case Event.KEY_ESC:
+ this.hide();
+ this.active = false;
+ Event.stop(event);
+ return;
+ case Event.KEY_LEFT:
+ case Event.KEY_RIGHT:
+ return;
+ case Event.KEY_UP:
+ this.markPrevious();
+ this.render();
+ if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
+ return;
+ case Event.KEY_DOWN:
+ this.markNext();
+ this.render();
+ if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
+ return;
+ }
+ else
+ if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN)
+ return;
+
+ this.changed = true;
+ this.hasFocus = true;
+
+ if(this.observer) clearTimeout(this.observer);
+ this.observer =
+ setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
+ },
+
+ onHover: function(event) {
+ var element = Event.findElement(event, 'LI');
+ if(this.index != element.autocompleteIndex)
+ {
+ this.index = element.autocompleteIndex;
+ this.render();
+ }
+ Event.stop(event);
+ },
+
+ onClick: function(event) {
+ var element = Event.findElement(event, 'LI');
+ this.index = element.autocompleteIndex;
+ this.selectEntry();
+ this.hide();
+ },
+
+ onBlur: function(event) {
+ // needed to make click events working
+ setTimeout(this.hide.bind(this), 250);
+ this.hasFocus = false;
+ this.active = false;
+ },
+
+ render: function() {
+ if(this.entryCount > 0) {
+ for (var i = 0; i < this.entryCount; i++)
+ this.index==i ?
+ Element.addClassName(this.getEntry(i),"selected") :
+ Element.removeClassName(this.getEntry(i),"selected");
+
+ if(this.hasFocus) {
+ this.show();
+ this.active = true;
+ }
+ } else {
+ this.active = false;
+ this.hide();
+ }
+ },
+
+ markPrevious: function() {
+ if(this.index > 0) this.index--
+ else this.index = this.entryCount-1;
+ },
+
+ markNext: function() {
+ if(this.index < this.entryCount-1) this.index++
+ else this.index = 0;
+ },
+
+ getEntry: function(index) {
+ return this.update.firstChild.childNodes[index];
+ },
+
+ getCurrentEntry: function() {
+ return this.getEntry(this.index);
+ },
+
+ selectEntry: function() {
+ this.active = false;
+ this.updateElement(this.getCurrentEntry());
+ },
+
+ updateElement: function(selectedElement) {
+ if (this.options.updateElement) {
+ this.options.updateElement(selectedElement);
+ return;
+ }
+
+ var value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
+ var lastTokenPos = this.findLastToken();
+ if (lastTokenPos != -1) {
+ var newValue = this.element.value.substr(0, lastTokenPos + 1);
+ var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/);
+ if (whitespace)
+ newValue += whitespace[0];
+ this.element.value = newValue + value;
+ } else {
+ this.element.value = value;
+ }
+ this.element.focus();
+
+ if (this.options.afterUpdateElement)
+ this.options.afterUpdateElement(this.element, selectedElement);
+ },
+
+ updateChoices: function(choices) {
+ if(!this.changed && this.hasFocus) {
+ this.update.innerHTML = choices;
+ Element.cleanWhitespace(this.update);
+ Element.cleanWhitespace(this.update.firstChild);
+
+ if(this.update.firstChild && this.update.firstChild.childNodes) {
+ this.entryCount =
+ this.update.firstChild.childNodes.length;
+ for (var i = 0; i < this.entryCount; i++) {
+ var entry = this.getEntry(i);
+ entry.autocompleteIndex = i;
+ this.addObservers(entry);
+ }
+ } else {
+ this.entryCount = 0;
+ }
+
+ this.stopIndicator();
+
+ this.index = 0;
+ this.render();
+ }
+ },
+
+ addObservers: function(element) {
+ Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
+ Event.observe(element, "click", this.onClick.bindAsEventListener(this));
+ },
+
+ onObserverEvent: function() {
+ this.changed = false;
+ if(this.getToken().length>=this.options.minChars) {
+ this.startIndicator();
+ this.getUpdatedChoices();
+ } else {
+ this.active = false;
+ this.hide();
+ }
+ },
+
+ getToken: function() {
+ var tokenPos = this.findLastToken();
+ if (tokenPos != -1)
+ var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,'');
+ else
+ var ret = this.element.value;
+
+ return /\n/.test(ret) ? '' : ret;
+ },
+
+ findLastToken: function() {
+ var lastTokenPos = -1;
+
+ for (var i=0; i<this.options.tokens.length; i++) {
+ var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]);
+ if (thisTokenPos > lastTokenPos)
+ lastTokenPos = thisTokenPos;
+ }
+ return lastTokenPos;
+ }
+}
+
+Ajax.Autocompleter = Class.create();
+Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), {
+ initialize: function(element, update, url, options) {
+ this.baseInitialize(element, update, options);
+ this.options.asynchronous = true;
+ this.options.onComplete = this.onComplete.bind(this);
+ this.options.defaultParams = this.options.parameters || null;
+ this.url = url;
+ },
+
+ getUpdatedChoices: function() {
+ entry = encodeURIComponent(this.options.paramName) + '=' +
+ encodeURIComponent(this.getToken());
+
+ this.options.parameters = this.options.callback ?
+ this.options.callback(this.element, entry) : entry;
+
+ if(this.options.defaultParams)
+ this.options.parameters += '&' + this.options.defaultParams;
+
+ new Ajax.Request(this.url, this.options);
+ },
+
+ onComplete: function(request) {
+ this.updateChoices(request.responseText);
+ }
+
+});
+
+// The local array autocompleter. Used when you'd prefer to
+// inject an array of autocompletion options into the page, rather
+// than sending out Ajax queries, which can be quite slow sometimes.
+//
+// The constructor takes four parameters. The first two are, as usual,
+// the id of the monitored textbox, and id of the autocompletion menu.
+// The third is the array you want to autocomplete from, and the fourth
+// is the options block.
+//
+// Extra local autocompletion options:
+// - choices - How many autocompletion choices to offer
+//
+// - partialSearch - If false, the autocompleter will match entered
+// text only at the beginning of strings in the
+// autocomplete array. Defaults to true, which will
+// match text at the beginning of any *word* in the
+// strings in the autocomplete array. If you want to
+// search anywhere in the string, additionally set
+// the option fullSearch to true (default: off).
+//
+// - fullSsearch - Search anywhere in autocomplete array strings.
+//
+// - partialChars - How many characters to enter before triggering
+// a partial match (unlike minChars, which defines
+// how many characters are required to do any match
+// at all). Defaults to 2.
+//
+// - ignoreCase - Whether to ignore case when autocompleting.
+// Defaults to true.
+//
+// It's possible to pass in a custom function as the 'selector'
+// option, if you prefer to write your own autocompletion logic.
+// In that case, the other options above will not apply unless
+// you support them.
+
+Autocompleter.Local = Class.create();
+Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
+ initialize: function(element, update, array, options) {
+ this.baseInitialize(element, update, options);
+ this.options.array = array;
+ },
+
+ getUpdatedChoices: function() {
+ this.updateChoices(this.options.selector(this));
+ },
+
+ setOptions: function(options) {
+ this.options = Object.extend({
+ choices: 10,
+ partialSearch: true,
+ partialChars: 2,
+ ignoreCase: true,
+ fullSearch: false,
+ selector: function(instance) {
+ var ret = []; // Beginning matches
+ var partial = []; // Inside matches
+ var entry = instance.getToken();
+ var count = 0;
+
+ for (var i = 0; i < instance.options.array.length &&
+ ret.length < instance.options.choices ; i++) {
+
+ var elem = instance.options.array[i];
+ var foundPos = instance.options.ignoreCase ?
+ elem.toLowerCase().indexOf(entry.toLowerCase()) :
+ elem.indexOf(entry);
+
+ while (foundPos != -1) {
+ if (foundPos == 0 && elem.length != entry.length) {
+ ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
+ elem.substr(entry.length) + "</li>");
+ break;
+ } else if (entry.length >= instance.options.partialChars &&
+ instance.options.partialSearch && foundPos != -1) {
+ if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
+ partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
+ elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
+ foundPos + entry.length) + "</li>");
+ break;
+ }
+ }
+
+ foundPos = instance.options.ignoreCase ?
+ elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :
+ elem.indexOf(entry, foundPos + 1);
+
+ }
+ }
+ if (partial.length)
+ ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
+ return "<ul>" + ret.join('') + "</ul>";
+ }
+ }, options || {});
+ }
+});
+
+// AJAX in-place editor
+//
+// see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor
+
+// Use this if you notice weird scrolling problems on some browsers,
+// the DOM might be a bit confused when this gets called so do this
+// waits 1 ms (with setTimeout) until it does the activation
+Field.scrollFreeActivate = function(field) {
+ setTimeout(function() {
+ Field.activate(field);
+ }, 1);
+}
+
+Ajax.InPlaceEditor = Class.create();
+Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99";
+Ajax.InPlaceEditor.prototype = {
+ initialize: function(element, url, options) {
+ this.url = url;
+ this.element = $(element);
+
+ this.options = Object.extend({
+ okText: "ok",
+ cancelText: "cancel",
+ savingText: "Saving...",
+ clickToEditText: "Click to edit",
+ okText: "ok",
+ rows: 1,
+ onComplete: function(transport, element) {
+ new Effect.Highlight(element, {startcolor: this.options.highlightcolor});
+ },
+ onFailure: function(transport) {
+ alert("Error communicating with the server: " + transport.responseText.stripTags());
+ },
+ callback: function(form) {
+ return Form.serialize(form);
+ },
+ handleLineBreaks: true,
+ loadingText: 'Loading...',
+ savingClassName: 'inplaceeditor-saving',
+ loadingClassName: 'inplaceeditor-loading',
+ formClassName: 'inplaceeditor-form',
+ highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor,
+ highlightendcolor: "#FFFFFF",
+ externalControl: null,
+ ajaxOptions: {}
+ }, options || {});
+
+ if(!this.options.formId && this.element.id) {
+ this.options.formId = this.element.id + "-inplaceeditor";
+ if ($(this.options.formId)) {
+ // there's already a form with that name, don't specify an id
+ this.options.formId = null;
+ }
+ }
+
+ if (this.options.externalControl) {
+ this.options.externalControl = $(this.options.externalControl);
+ }
+
+ this.originalBackground = Element.getStyle(this.element, 'background-color');
+ if (!this.originalBackground) {
+ this.originalBackground = "transparent";
+ }
+
+ this.element.title = this.options.clickToEditText;
+
+ this.onclickListener = this.enterEditMode.bindAsEventListener(this);
+ this.mouseoverListener = this.enterHover.bindAsEventListener(this);
+ this.mouseoutListener = this.leaveHover.bindAsEventListener(this);
+ Event.observe(this.element, 'click', this.onclickListener);
+ Event.observe(this.element, 'mouseover', this.mouseoverListener);
+ Event.observe(this.element, 'mouseout', this.mouseoutListener);
+ if (this.options.externalControl) {
+ Event.observe(this.options.externalControl, 'click', this.onclickListener);
+ Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener);
+ Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener);
+ }
+ },
+ enterEditMode: function(evt) {
+ if (this.saving) return;
+ if (this.editing) return;
+ this.editing = true;
+ this.onEnterEditMode();
+ if (this.options.externalControl) {
+ Element.hide(this.options.externalControl);
+ }
+ Element.hide(this.element);
+ this.createForm();
+ this.element.parentNode.insertBefore(this.form, this.element);
+ Field.scrollFreeActivate(this.editField);
+ // stop the event to avoid a page refresh in Safari
+ if (evt) {
+ Event.stop(evt);
+ }
+ return false;
+ },
+ createForm: function() {
+ this.form = document.createElement("form");
+ this.form.id = this.options.formId;
+ Element.addClassName(this.form, this.options.formClassName)
+ this.form.onsubmit = this.onSubmit.bind(this);
+
+ this.createEditField();
+
+ if (this.options.textarea) {
+ var br = document.createElement("br");
+ this.form.appendChild(br);
+ }
+
+ okButton = document.createElement("input");
+ okButton.type = "submit";
+ okButton.value = this.options.okText;
+ this.form.appendChild(okButton);
+
+ cancelLink = document.createElement("a");
+ cancelLink.href = "#";
+ cancelLink.appendChild(document.createTextNode(this.options.cancelText));
+ cancelLink.onclick = this.onclickCancel.bind(this);
+ this.form.appendChild(cancelLink);
+ },
+ hasHTMLLineBreaks: function(string) {
+ if (!this.options.handleLineBreaks) return false;
+ return string.match(/<br/i) || string.match(/<p>/i);
+ },
+ convertHTMLLineBreaks: function(string) {
+ return string.replace(/<br>/gi, "\n").replace(/<br\/>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<p>/gi, "");
+ },
+ createEditField: function() {
+ var text;
+ if(this.options.loadTextURL) {
+ text = this.options.loadingText;
+ } else {
+ text = this.getText();
+ }
+
+ if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) {
+ this.options.textarea = false;
+ var textField = document.createElement("input");
+ textField.type = "text";
+ textField.name = "value";
+ textField.value = text;
+ textField.style.backgroundColor = this.options.highlightcolor;
+ var size = this.options.size || this.options.cols || 0;
+ if (size != 0) textField.size = size;
+ this.editField = textField;
+ } else {
+ this.options.textarea = true;
+ var textArea = document.createElement("textarea");
+ textArea.name = "value";
+ textArea.value = this.convertHTMLLineBreaks(text);
+ textArea.rows = this.options.rows;
+ textArea.cols = this.options.cols || 40;
+ this.editField = textArea;
+ }
+
+ if(this.options.loadTextURL) {
+ this.loadExternalText();
+ }
+ this.form.appendChild(this.editField);
+ },
+ getText: function() {
+ return this.element.innerHTML;
+ },
+ loadExternalText: function() {
+ Element.addClassName(this.form, this.options.loadingClassName);
+ this.editField.disabled = true;
+ new Ajax.Request(
+ this.options.loadTextURL,
+ Object.extend({
+ asynchronous: true,
+ onComplete: this.onLoadedExternalText.bind(this)
+ }, this.options.ajaxOptions)
+ );
+ },
+ onLoadedExternalText: function(transport) {
+ Element.removeClassName(this.form, this.options.loadingClassName);
+ this.editField.disabled = false;
+ this.editField.value = transport.responseText.stripTags();
+ },
+ onclickCancel: function() {
+ this.onComplete();
+ this.leaveEditMode();
+ return false;
+ },
+ onFailure: function(transport) {
+ this.options.onFailure(transport);
+ if (this.oldInnerHTML) {
+ this.element.innerHTML = this.oldInnerHTML;
+ this.oldInnerHTML = null;
+ }
+ return false;
+ },
+ onSubmit: function() {
+ // onLoading resets these so we need to save them away for the Ajax call
+ var form = this.form;
+ var value = this.editField.value;
+
+ // do this first, sometimes the ajax call returns before we get a chance to switch on Saving...
+ // which means this will actually switch on Saving... *after* we've left edit mode causing Saving...
+ // to be displayed indefinitely
+ this.onLoading();
+
+ new Ajax.Updater(
+ {
+ success: this.element,
+ // don't update on failure (this could be an option)
+ failure: null
+ },
+ this.url,
+ Object.extend({
+ parameters: this.options.callback(form, value),
+ onComplete: this.onComplete.bind(this),
+ onFailure: this.onFailure.bind(this)
+ }, this.options.ajaxOptions)
+ );
+ // stop the event to avoid a page refresh in Safari
+ if (arguments.length > 1) {
+ Event.stop(arguments[0]);
+ }
+ return false;
+ },
+ onLoading: function() {
+ this.saving = true;
+ this.removeForm();
+ this.leaveHover();
+ this.showSaving();
+ },
+ showSaving: function() {
+ this.oldInnerHTML = this.element.innerHTML;
+ this.element.innerHTML = this.options.savingText;
+ Element.addClassName(this.element, this.options.savingClassName);
+ this.element.style.backgroundColor = this.originalBackground;
+ Element.show(this.element);
+ },
+ removeForm: function() {
+ if(this.form) {
+ if (this.form.parentNode) Element.remove(this.form);
+ this.form = null;
+ }
+ },
+ enterHover: function() {
+ if (this.saving) return;
+ this.element.style.backgroundColor = this.options.highlightcolor;
+ if (this.effect) {
+ this.effect.cancel();
+ }
+ Element.addClassName(this.element, this.options.hoverClassName)
+ },
+ leaveHover: function() {
+ if (this.options.backgroundColor) {
+ this.element.style.backgroundColor = this.oldBackground;
+ }
+ Element.removeClassName(this.element, this.options.hoverClassName)
+ if (this.saving) return;
+ this.effect = new Effect.Highlight(this.element, {
+ startcolor: this.options.highlightcolor,
+ endcolor: this.options.highlightendcolor,
+ restorecolor: this.originalBackground
+ });
+ },
+ leaveEditMode: function() {
+ Element.removeClassName(this.element, this.options.savingClassName);
+ this.removeForm();
+ this.leaveHover();
+ this.element.style.backgroundColor = this.originalBackground;
+ Element.show(this.element);
+ if (this.options.externalControl) {
+ Element.show(this.options.externalControl);
+ }
+ this.editing = false;
+ this.saving = false;
+ this.oldInnerHTML = null;
+ this.onLeaveEditMode();
+ },
+ onComplete: function(transport) {
+ this.leaveEditMode();
+ this.options.onComplete.bind(this)(transport, this.element);
+ },
+ onEnterEditMode: function() {},
+ onLeaveEditMode: function() {},
+ dispose: function() {
+ if (this.oldInnerHTML) {
+ this.element.innerHTML = this.oldInnerHTML;
+ }
+ this.leaveEditMode();
+ Event.stopObserving(this.element, 'click', this.onclickListener);
+ Event.stopObserving(this.element, 'mouseover', this.mouseoverListener);
+ Event.stopObserving(this.element, 'mouseout', this.mouseoutListener);
+ if (this.options.externalControl) {
+ Event.stopObserving(this.options.externalControl, 'click', this.onclickListener);
+ Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener);
+ Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener);
+ }
+ }
+};
diff --git a/web/static/js/scriptaculous/dragdrop.js b/web/static/js/scriptaculous/dragdrop.js
new file mode 100644
index 0000000..7ca95f6
--- /dev/null
+++ b/web/static/js/scriptaculous/dragdrop.js
@@ -0,0 +1,519 @@
+// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+//
+// Element.Class part Copyright (c) 2005 by Rick Olson
+//
+// See scriptaculous.js for full license.
+
+/*--------------------------------------------------------------------------*/
+
+var Droppables = {
+ drops: [],
+
+ remove: function(element) {
+ this.drops = this.drops.reject(function(d) { return d.element==element });
+ },
+
+ add: function(element) {
+ element = $(element);
+ var options = Object.extend({
+ greedy: true,
+ hoverclass: null
+ }, arguments[1] || {});
+
+ // cache containers
+ if(options.containment) {
+ options._containers = [];
+ var containment = options.containment;
+ if((typeof containment == 'object') &&
+ (containment.constructor == Array)) {
+ containment.each( function(c) { options._containers.push($(c)) });
+ } else {
+ options._containers.push($(containment));
+ }
+ }
+
+ Element.makePositioned(element); // fix IE
+ options.element = element;
+
+ this.drops.push(options);
+ },
+
+ isContained: function(element, drop) {
+ var parentNode = element.parentNode;
+ return drop._containers.detect(function(c) { return parentNode == c });
+ },
+
+ isAffected: function(pX, pY, element, drop) {
+ return (
+ (drop.element!=element) &&
+ ((!drop._containers) ||
+ this.isContained(element, drop)) &&
+ ((!drop.accept) ||
+ (Element.Class.has_any(element, drop.accept))) &&
+ Position.within(drop.element, pX, pY) );
+ },
+
+ deactivate: function(drop) {
+ if(drop.hoverclass)
+ Element.Class.remove(drop.element, drop.hoverclass);
+ this.last_active = null;
+ },
+
+ activate: function(drop) {
+ if(this.last_active) this.deactivate(this.last_active);
+ if(drop.hoverclass)
+ Element.Class.add(drop.element, drop.hoverclass);
+ this.last_active = drop;
+ },
+
+ show: function(event, element) {
+ if(!this.drops.length) return;
+ var pX = Event.pointerX(event);
+ var pY = Event.pointerY(event);
+ Position.prepare();
+
+ var i = this.drops.length-1; do {
+ var drop = this.drops[i];
+ if(this.isAffected(pX, pY, element, drop)) {
+ if(drop.onHover)
+ drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
+ if(drop.greedy) {
+ this.activate(drop);
+ return;
+ }
+ }
+ } while (i--);
+
+ if(this.last_active) this.deactivate(this.last_active);
+ },
+
+ fire: function(event, element) {
+ if(!this.last_active) return;
+ Position.prepare();
+
+ if (this.isAffected(Event.pointerX(event), Event.pointerY(event), element, this.last_active))
+ if (this.last_active.onDrop)
+ this.last_active.onDrop(element, this.last_active.element, event);
+ },
+
+ reset: function() {
+ if(this.last_active)
+ this.deactivate(this.last_active);
+ }
+}
+
+var Draggables = {
+ observers: [],
+ addObserver: function(observer) {
+ this.observers.push(observer);
+ },
+ removeObserver: function(element) { // element instead of obsever fixes mem leaks
+ this.observers = this.observers.reject( function(o) { return o.element==element });
+ },
+ notify: function(eventName, draggable) { // 'onStart', 'onEnd'
+ this.observers.invoke(eventName, draggable);
+ }
+}
+
+/*--------------------------------------------------------------------------*/
+
+var Draggable = Class.create();
+Draggable.prototype = {
+ initialize: function(element) {
+ var options = Object.extend({
+ handle: false,
+ starteffect: function(element) {
+ new Effect.Opacity(element, {duration:0.2, from:1.0, to:0.7});
+ },
+ reverteffect: function(element, top_offset, left_offset) {
+ var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
+ new Effect.MoveBy(element, -top_offset, -left_offset, {duration:dur});
+ },
+ endeffect: function(element) {
+ new Effect.Opacity(element, {duration:0.2, from:0.7, to:1.0});
+ },
+ zindex: 1000,
+ revert: false
+ }, arguments[1] || {});
+
+ this.element = $(element);
+ if(options.handle && (typeof options.handle == 'string'))
+ this.handle = Element.Class.childrenWith(this.element, options.handle)[0];
+
+ if(!this.handle) this.handle = $(options.handle);
+ if(!this.handle) this.handle = this.element;
+
+ Element.makePositioned(this.element); // fix IE
+
+ this.offsetX = 0;
+ this.offsetY = 0;
+ this.originalLeft = this.currentLeft();
+ this.originalTop = this.currentTop();
+ this.originalX = this.element.offsetLeft;
+ this.originalY = this.element.offsetTop;
+
+ this.options = options;
+
+ this.active = false;
+ this.dragging = false;
+
+ this.eventMouseDown = this.startDrag.bindAsEventListener(this);
+ this.eventMouseUp = this.endDrag.bindAsEventListener(this);
+ this.eventMouseMove = this.update.bindAsEventListener(this);
+ this.eventKeypress = this.keyPress.bindAsEventListener(this);
+
+ this.registerEvents();
+ },
+ destroy: function() {
+ Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
+ this.unregisterEvents();
+ },
+ registerEvents: function() {
+ Event.observe(document, "mouseup", this.eventMouseUp);
+ Event.observe(document, "mousemove", this.eventMouseMove);
+ Event.observe(document, "keypress", this.eventKeypress);
+ Event.observe(this.handle, "mousedown", this.eventMouseDown);
+ },
+ unregisterEvents: function() {
+ //if(!this.active) return;
+ //Event.stopObserving(document, "mouseup", this.eventMouseUp);
+ //Event.stopObserving(document, "mousemove", this.eventMouseMove);
+ //Event.stopObserving(document, "keypress", this.eventKeypress);
+ },
+ currentLeft: function() {
+ return parseInt(this.element.style.left || '0');
+ },
+ currentTop: function() {
+ return parseInt(this.element.style.top || '0')
+ },
+ startDrag: function(event) {
+ if(Event.isLeftClick(event)) {
+
+ // abort on form elements, fixes a Firefox issue
+ var src = Event.element(event);
+ if(src.tagName && (
+ src.tagName=='INPUT' ||
+ src.tagName=='SELECT' ||
+ src.tagName=='BUTTON' ||
+ src.tagName=='TEXTAREA')) return;
+
+ // this.registerEvents();
+ this.active = true;
+ var pointer = [Event.pointerX(event), Event.pointerY(event)];
+ var offsets = Position.cumulativeOffset(this.element);
+ this.offsetX = (pointer[0] - offsets[0]);
+ this.offsetY = (pointer[1] - offsets[1]);
+ Event.stop(event);
+ }
+ },
+ finishDrag: function(event, success) {
+ // this.unregisterEvents();
+
+ this.active = false;
+ this.dragging = false;
+
+ if(this.options.ghosting) {
+ Position.relativize(this.element);
+ Element.remove(this._clone);
+ this._clone = null;
+ }
+
+ if(success) Droppables.fire(event, this.element);
+ Draggables.notify('onEnd', this);
+
+ var revert = this.options.revert;
+ if(revert && typeof revert == 'function') revert = revert(this.element);
+
+ if(revert && this.options.reverteffect) {
+ this.options.reverteffect(this.element,
+ this.currentTop()-this.originalTop,
+ this.currentLeft()-this.originalLeft);
+ } else {
+ this.originalLeft = this.currentLeft();
+ this.originalTop = this.currentTop();
+ }
+
+ if(this.options.zindex)
+ this.element.style.zIndex = this.originalZ;
+
+ if(this.options.endeffect)
+ this.options.endeffect(this.element);
+
+
+ Droppables.reset();
+ },
+ keyPress: function(event) {
+ if(this.active) {
+ if(event.keyCode==Event.KEY_ESC) {
+ this.finishDrag(event, false);
+ Event.stop(event);
+ }
+ }
+ },
+ endDrag: function(event) {
+ if(this.active && this.dragging) {
+ this.finishDrag(event, true);
+ Event.stop(event);
+ }
+ this.active = false;
+ this.dragging = false;
+ },
+ draw: function(event) {
+ var pointer = [Event.pointerX(event), Event.pointerY(event)];
+ var offsets = Position.cumulativeOffset(this.element);
+ offsets[0] -= this.currentLeft();
+ offsets[1] -= this.currentTop();
+ var style = this.element.style;
+ if((!this.options.constraint) || (this.options.constraint=='horizontal'))
+ style.left = (pointer[0] - offsets[0] - this.offsetX) + "px";
+ if((!this.options.constraint) || (this.options.constraint=='vertical'))
+ style.top = (pointer[1] - offsets[1] - this.offsetY) + "px";
+ if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
+ },
+ update: function(event) {
+ if(this.active) {
+ if(!this.dragging) {
+ var style = this.element.style;
+ this.dragging = true;
+
+ if(Element.getStyle(this.element,'position')=='')
+ style.position = "relative";
+
+ if(this.options.zindex) {
+ this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
+ style.zIndex = this.options.zindex;
+ }
+
+ if(this.options.ghosting) {
+ this._clone = this.element.cloneNode(true);
+ Position.absolutize(this.element);
+ this.element.parentNode.insertBefore(this._clone, this.element);
+ }
+
+ Draggables.notify('onStart', this);
+ if(this.options.starteffect) this.options.starteffect(this.element);
+ }
+
+ Droppables.show(event, this.element);
+ this.draw(event);
+ if(this.options.change) this.options.change(this);
+
+ // fix AppleWebKit rendering
+ if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
+
+ Event.stop(event);
+ }
+ }
+}
+
+/*--------------------------------------------------------------------------*/
+
+var SortableObserver = Class.create();
+SortableObserver.prototype = {
+ initialize: function(element, observer) {
+ this.element = $(element);
+ this.observer = observer;
+ this.lastValue = Sortable.serialize(this.element);
+ },
+ onStart: function() {
+ this.lastValue = Sortable.serialize(this.element);
+ },
+ onEnd: function() {
+ Sortable.unmark();
+ if(this.lastValue != Sortable.serialize(this.element))
+ this.observer(this.element)
+ }
+}
+
+var Sortable = {
+ sortables: new Array(),
+ options: function(element){
+ element = $(element);
+ return this.sortables.detect(function(s) { return s.element == element });
+ },
+ destroy: function(element){
+ element = $(element);
+ this.sortables.findAll(function(s) { return s.element == element }).each(function(s){
+ Draggables.removeObserver(s.element);
+ s.droppables.each(function(d){ Droppables.remove(d) });
+ s.draggables.invoke('destroy');
+ });
+ this.sortables = this.sortables.reject(function(s) { return s.element == element });
+ },
+ create: function(element) {
+ element = $(element);
+ var options = Object.extend({
+ element: element,
+ tag: 'li', // assumes li children, override with tag: 'tagname'
+ dropOnEmpty: false,
+ tree: false, // fixme: unimplemented
+ overlap: 'vertical', // one of 'vertical', 'horizontal'
+ constraint: 'vertical', // one of 'vertical', 'horizontal', false
+ containment: element, // also takes array of elements (or id's); or false
+ handle: false, // or a CSS class
+ only: false,
+ hoverclass: null,
+ ghosting: false,
+ format: null,
+ onChange: Prototype.emptyFunction,
+ onUpdate: Prototype.emptyFunction
+ }, arguments[1] || {});
+
+ // clear any old sortable with same element
+ this.destroy(element);
+
+ // build options for the draggables
+ var options_for_draggable = {
+ revert: true,
+ ghosting: options.ghosting,
+ constraint: options.constraint,
+ handle: options.handle };
+
+ if(options.starteffect)
+ options_for_draggable.starteffect = options.starteffect;
+
+ if(options.reverteffect)
+ options_for_draggable.reverteffect = options.reverteffect;
+ else
+ if(options.ghosting) options_for_draggable.reverteffect = function(element) {
+ element.style.top = 0;
+ element.style.left = 0;
+ };
+
+ if(options.endeffect)
+ options_for_draggable.endeffect = options.endeffect;
+
+ if(options.zindex)
+ options_for_draggable.zindex = options.zindex;
+
+ // build options for the droppables
+ var options_for_droppable = {
+ overlap: options.overlap,
+ containment: options.containment,
+ hoverclass: options.hoverclass,
+ onHover: Sortable.onHover,
+ greedy: !options.dropOnEmpty
+ }
+
+ // fix for gecko engine
+ Element.cleanWhitespace(element);
+
+ options.draggables = [];
+ options.droppables = [];
+
+ // make it so
+
+ // drop on empty handling
+ if(options.dropOnEmpty) {
+ Droppables.add(element,
+ {containment: options.containment, onHover: Sortable.onEmptyHover, greedy: false});
+ options.droppables.push(element);
+ }
+
+ (this.findElements(element, options) || []).each( function(e) {
+ // handles are per-draggable
+ var handle = options.handle ?
+ Element.Class.childrenWith(e, options.handle)[0] : e;
+ options.draggables.push(
+ new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
+ Droppables.add(e, options_for_droppable);
+ options.droppables.push(e);
+ });
+
+ // keep reference
+ this.sortables.push(options);
+
+ // for onupdate
+ Draggables.addObserver(new SortableObserver(element, options.onUpdate));
+
+ },
+
+ // return all suitable-for-sortable elements in a guaranteed order
+ findElements: function(element, options) {
+ if(!element.hasChildNodes()) return null;
+ var elements = [];
+ $A(element.childNodes).each( function(e) {
+ if(e.tagName && e.tagName==options.tag.toUpperCase() &&
+ (!options.only || (Element.Class.has(e, options.only))))
+ elements.push(e);
+ if(options.tree) {
+ var grandchildren = this.findElements(e, options);
+ if(grandchildren) elements.push(grandchildren);
+ }
+ });
+
+ return (elements.length>0 ? elements.flatten() : null);
+ },
+
+ onHover: function(element, dropon, overlap) {
+ if(overlap>0.5) {
+ Sortable.mark(dropon, 'before');
+ if(dropon.previousSibling != element) {
+ var oldParentNode = element.parentNode;
+ element.style.visibility = "hidden"; // fix gecko rendering
+ dropon.parentNode.insertBefore(element, dropon);
+ if(dropon.parentNode!=oldParentNode)
+ Sortable.options(oldParentNode).onChange(element);
+ Sortable.options(dropon.parentNode).onChange(element);
+ }
+ } else {
+ Sortable.mark(dropon, 'after');
+ var nextElement = dropon.nextSibling || null;
+ if(nextElement != element) {
+ var oldParentNode = element.parentNode;
+ element.style.visibility = "hidden"; // fix gecko rendering
+ dropon.parentNode.insertBefore(element, nextElement);
+ if(dropon.parentNode!=oldParentNode)
+ Sortable.options(oldParentNode).onChange(element);
+ Sortable.options(dropon.parentNode).onChange(element);
+ }
+ }
+ },
+
+ onEmptyHover: function(element, dropon) {
+ if(element.parentNode!=dropon) {
+ var oldParentNode = element.parentNode;
+ dropon.appendChild(element);
+ Sortable.options(oldParentNode).onChange(element);
+ Sortable.options(dropon).onChange(element);
+ }
+ },
+
+ unmark: function() {
+ if(Sortable._marker) Element.hide(Sortable._marker);
+ },
+
+ mark: function(dropon, position) {
+ // mark on ghosting only
+ var sortable = Sortable.options(dropon.parentNode);
+ if(sortable && !sortable.ghosting) return;
+
+ if(!Sortable._marker) {
+ Sortable._marker = $('dropmarker') || document.createElement('DIV');
+ Element.hide(Sortable._marker);
+ Element.Class.add(Sortable._marker, 'dropmarker');
+ Sortable._marker.style.position = 'absolute';
+ document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
+ }
+ var offsets = Position.cumulativeOffset(dropon);
+ Sortable._marker.style.top = offsets[1] + 'px';
+ if(position=='after') Sortable._marker.style.top = (offsets[1]+dropon.clientHeight) + 'px';
+ Sortable._marker.style.left = offsets[0] + 'px';
+ Element.show(Sortable._marker);
+ },
+
+ serialize: function(element) {
+ element = $(element);
+ var sortableOptions = this.options(element);
+ var options = Object.extend({
+ tag: sortableOptions.tag,
+ only: sortableOptions.only,
+ name: element.id,
+ format: sortableOptions.format || /^[^_]*_(.*)$/
+ }, arguments[1] || {});
+ return $(this.findElements(element, options) || []).collect( function(item) {
+ return (encodeURIComponent(options.name) + "[]=" +
+ encodeURIComponent(item.id.match(options.format) ? item.id.match(options.format)[1] : ''));
+ }).join("&");
+ }
+}
\ No newline at end of file
diff --git a/web/static/js/scriptaculous/effects.js b/web/static/js/scriptaculous/effects.js
new file mode 100644
index 0000000..3f92992
--- /dev/null
+++ b/web/static/js/scriptaculous/effects.js
@@ -0,0 +1,992 @@
+// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+// Contributors:
+// Justin Palmer (http://encytemedia.com/)
+// Mark Pilgrim (http://diveintomark.org/)
+// Martin Bialasinki
+//
+// See scriptaculous.js for full license.
+
+/* ------------- element ext -------------- */
+
+// converts rgb() and #xxx to #xxxxxx format,
+// returns self (or first argument) if not convertable
+String.prototype.parseColor = function() {
+ color = "#";
+ if(this.slice(0,4) == "rgb(") {
+ var cols = this.slice(4,this.length-1).split(',');
+ var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);
+ } else {
+ if(this.slice(0,1) == '#') {
+ if(this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();
+ if(this.length==7) color = this.toLowerCase();
+ }
+ }
+ return(color.length==7 ? color : (arguments[0] || this));
+}
+
+Element.collectTextNodesIgnoreClass = function(element, ignoreclass) {
+ var children = $(element).childNodes;
+ var text = "";
+ var classtest = new RegExp("^([^ ]+ )*" + ignoreclass+ "( [^ ]+)*$","i");
+
+ for (var i = 0; i < children.length; i++) {
+ if(children[i].nodeType==3) {
+ text+=children[i].nodeValue;
+ } else {
+ if((!children[i].className.match(classtest)) && children[i].hasChildNodes())
+ text += Element.collectTextNodesIgnoreClass(children[i], ignoreclass);
+ }
+ }
+
+ return text;
+}
+
+Element.setContentZoom = function(element, percent) {
+ element = $(element);
+ element.style.fontSize = (percent/100) + "em";
+ if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
+}
+
+Element.getOpacity = function(element){
+ var opacity;
+ if (opacity = Element.getStyle(element, "opacity"))
+ return parseFloat(opacity);
+ if (opacity = (Element.getStyle(element, "filter") || '').match(/alpha\(opacity=(.*)\)/))
+ if(opacity[1]) return parseFloat(opacity[1]) / 100;
+ return 1.0;
+}
+
+Element.setOpacity = function(element, value){
+ element= $(element);
+ var els = element.style;
+ if (value == 1){
+ els.opacity = '0.999999';
+ if(/MSIE/.test(navigator.userAgent))
+ els.filter = Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'');
+ } else {
+ if(value < 0.00001) value = 0;
+ els.opacity = value;
+ if(/MSIE/.test(navigator.userAgent))
+ els.filter = Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'') +
+ "alpha(opacity="+value*100+")";
+ }
+}
+
+Element.getInlineOpacity = function(element){
+ element= $(element);
+ var op;
+ op = element.style.opacity;
+ if (typeof op != "undefined" && op != "") return op;
+ return "";
+}
+
+Element.setInlineOpacity = function(element, value){
+ element= $(element);
+ var els = element.style;
+ els.opacity = value;
+}
+
+/*--------------------------------------------------------------------------*/
+
+Element.Class = {
+ // Element.toggleClass(element, className) toggles the class being on/off
+ // Element.toggleClass(element, className1, className2) toggles between both classes,
+ // defaulting to className1 if neither exist
+ toggle: function(element, className) {
+ if(Element.Class.has(element, className)) {
+ Element.Class.remove(element, className);
+ if(arguments.length == 3) Element.Class.add(element, arguments[2]);
+ } else {
+ Element.Class.add(element, className);
+ if(arguments.length == 3) Element.Class.remove(element, arguments[2]);
+ }
+ },
+
+ // gets space-delimited classnames of an element as an array
+ get: function(element) {
+ return $(element).className.split(' ');
+ },
+
+ // functions adapted from original functions by Gavin Kistner
+ remove: function(element) {
+ element = $(element);
+ var removeClasses = arguments;
+ $R(1,arguments.length-1).each( function(index) {
+ element.className =
+ element.className.split(' ').reject(
+ function(klass) { return (klass == removeClasses[index]) } ).join(' ');
+ });
+ },
+
+ add: function(element) {
+ element = $(element);
+ for(var i = 1; i < arguments.length; i++) {
+ Element.Class.remove(element, arguments[i]);
+ element.className += (element.className.length > 0 ? ' ' : '') + arguments[i];
+ }
+ },
+
+ // returns true if all given classes exist in said element
+ has: function(element) {
+ element = $(element);
+ if(!element || !element.className) return false;
+ var regEx;
+ for(var i = 1; i < arguments.length; i++) {
+ if((typeof arguments[i] == 'object') &&
+ (arguments[i].constructor == Array)) {
+ for(var j = 0; j < arguments[i].length; j++) {
+ regEx = new RegExp("(^|\\s)" + arguments[i][j] + "(\\s|$)");
+ if(!regEx.test(element.className)) return false;
+ }
+ } else {
+ regEx = new RegExp("(^|\\s)" + arguments[i] + "(\\s|$)");
+ if(!regEx.test(element.className)) return false;
+ }
+ }
+ return true;
+ },
+
+ // expects arrays of strings and/or strings as optional paramters
+ // Element.Class.has_any(element, ['classA','classB','classC'], 'classD')
+ has_any: function(element) {
+ element = $(element);
+ if(!element || !element.className) return false;
+ var regEx;
+ for(var i = 1; i < arguments.length; i++) {
+ if((typeof arguments[i] == 'object') &&
+ (arguments[i].constructor == Array)) {
+ for(var j = 0; j < arguments[i].length; j++) {
+ regEx = new RegExp("(^|\\s)" + arguments[i][j] + "(\\s|$)");
+ if(regEx.test(element.className)) return true;
+ }
+ } else {
+ regEx = new RegExp("(^|\\s)" + arguments[i] + "(\\s|$)");
+ if(regEx.test(element.className)) return true;
+ }
+ }
+ return false;
+ },
+
+ childrenWith: function(element, className) {
+ var children = $(element).getElementsByTagName('*');
+ var elements = new Array();
+
+ for (var i = 0; i < children.length; i++)
+ if (Element.Class.has(children[i], className))
+ elements.push(children[i]);
+
+ return elements;
+ }
+}
+
+/*--------------------------------------------------------------------------*/
+
+var Effect = {
+ tagifyText: function(element) {
+ var tagifyStyle = "position:relative";
+ if(/MSIE/.test(navigator.userAgent)) tagifyStyle += ";zoom:1";
+ element = $(element);
+ $A(element.childNodes).each( function(child) {
+ if(child.nodeType==3) {
+ child.nodeValue.toArray().each( function(character) {
+ element.insertBefore(
+ Builder.node('span',{style: tagifyStyle},
+ character == " " ? String.fromCharCode(160) : character),
+ child);
+ });
+ Element.remove(child);
+ }
+ });
+ },
+ multiple: function(element, effect) {
+ var elements;
+ if(((typeof element == 'object') ||
+ (typeof element == 'function')) &&
+ (element.length))
+ elements = element;
+ else
+ elements = $(element).childNodes;
+
+ var options = Object.extend({
+ speed: 0.1,
+ delay: 0.0
+ }, arguments[2] || {});
+ var speed = options.speed;
+ var delay = options.delay;
+
+ $A(elements).each( function(element, index) {
+ new effect(element, Object.extend(options, { delay: delay + index * speed }));
+ });
+ }
+};
+
+var Effect2 = Effect; // deprecated
+
+/* ------------- transitions ------------- */
+
+Effect.Transitions = {}
+
+Effect.Transitions.linear = function(pos) {
+ return pos;
+}
+Effect.Transitions.sinoidal = function(pos) {
+ return (-Math.cos(pos*Math.PI)/2) + 0.5;
+}
+Effect.Transitions.reverse = function(pos) {
+ return 1-pos;
+}
+Effect.Transitions.flicker = function(pos) {
+ return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
+}
+Effect.Transitions.wobble = function(pos) {
+ return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
+}
+Effect.Transitions.pulse = function(pos) {
+ return (Math.floor(pos*10) % 2 == 0 ?
+ (pos*10-Math.floor(pos*10)) : 1-(pos*10-Math.floor(pos*10)));
+}
+Effect.Transitions.none = function(pos) {
+ return 0;
+}
+Effect.Transitions.full = function(pos) {
+ return 1;
+}
+
+/* ------------- core effects ------------- */
+
+Effect.Queue = {
+ effects: [],
+ _each: function(iterator) {
+ this.effects._each(iterator);
+ },
+ interval: null,
+ add: function(effect) {
+ var timestamp = new Date().getTime();
+
+ switch(effect.options.queue) {
+ case 'front':
+ // move unstarted effects after this effect
+ this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
+ e.startOn += effect.finishOn;
+ e.finishOn += effect.finishOn;
+ });
+ break;
+ case 'end':
+ // start effect after last queued effect has finished
+ timestamp = this.effects.pluck('finishOn').max() || timestamp;
+ break;
+ }
+
+ effect.startOn += timestamp;
+ effect.finishOn += timestamp;
+ this.effects.push(effect);
+ if(!this.interval)
+ this.interval = setInterval(this.loop.bind(this), 40);
+ },
+ remove: function(effect) {
+ this.effects = this.effects.reject(function(e) { return e==effect });
+ if(this.effects.length == 0) {
+ clearInterval(this.interval);
+ this.interval = null;
+ }
+ },
+ loop: function() {
+ var timePos = new Date().getTime();
+ this.effects.invoke('loop', timePos);
+ }
+}
+Object.extend(Effect.Queue, Enumerable);
+
+Effect.Base = function() {};
+Effect.Base.prototype = {
+ position: null,
+ setOptions: function(options) {
+ this.options = Object.extend({
+ transition: Effect.Transitions.sinoidal,
+ duration: 1.0, // seconds
+ fps: 25.0, // max. 25fps due to Effect.Queue implementation
+ sync: false, // true for combining
+ from: 0.0,
+ to: 1.0,
+ delay: 0.0,
+ queue: 'parallel'
+ }, options || {});
+ },
+ start: function(options) {
+ this.setOptions(options || {});
+ this.currentFrame = 0;
+ this.state = 'idle';
+ this.startOn = this.options.delay*1000;
+ this.finishOn = this.startOn + (this.options.duration*1000);
+ this.event('beforeStart');
+ if(!this.options.sync) Effect.Queue.add(this);
+ },
+ loop: function(timePos) {
+ if(timePos >= this.startOn) {
+ if(timePos >= this.finishOn) {
+ this.render(1.0);
+ this.cancel();
+ this.event('beforeFinish');
+ if(this.finish) this.finish();
+ this.event('afterFinish');
+ return;
+ }
+ var pos = (timePos - this.startOn) / (this.finishOn - this.startOn);
+ var frame = Math.round(pos * this.options.fps * this.options.duration);
+ if(frame > this.currentFrame) {
+ this.render(pos);
+ this.currentFrame = frame;
+ }
+ }
+ },
+ render: function(pos) {
+ if(this.state == 'idle') {
+ this.state = 'running';
+ this.event('beforeSetup');
+ if(this.setup) this.setup();
+ this.event('afterSetup');
+ }
+ if(this.options.transition) pos = this.options.transition(pos);
+ pos *= (this.options.to-this.options.from);
+ pos += this.options.from;
+ this.position = pos;
+ this.event('beforeUpdate');
+ if(this.update) this.update(pos);
+ this.event('afterUpdate');
+ },
+ cancel: function() {
+ if(!this.options.sync) Effect.Queue.remove(this);
+ this.state = 'finished';
+ },
+ event: function(eventName) {
+ if(this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
+ if(this.options[eventName]) this.options[eventName](this);
+ }
+}
+
+Effect.Parallel = Class.create();
+Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), {
+ initialize: function(effects) {
+ this.effects = effects || [];
+ this.start(arguments[1]);
+ },
+ update: function(position) {
+ this.effects.invoke('render', position);
+ },
+ finish: function(position) {
+ this.effects.each( function(effect) {
+ effect.render(1.0);
+ effect.cancel();
+ effect.event('beforeFinish');
+ if(effect.finish) effect.finish(position);
+ effect.event('afterFinish');
+ });
+ }
+});
+
+Effect.Opacity = Class.create();
+Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), {
+ initialize: function(element) {
+ this.element = $(element);
+ // make this work on IE on elements without 'layout'
+ if(/MSIE/.test(navigator.userAgent) && (!this.element.hasLayout))
+ this.element.style.zoom = 1;
+ var options = Object.extend({
+ from: Element.getOpacity(this.element) || 0.0,
+ to: 1.0
+ }, arguments[1] || {});
+ this.start(options);
+ },
+ update: function(position) {
+ Element.setOpacity(this.element, position);
+ }
+});
+
+Effect.MoveBy = Class.create();
+Object.extend(Object.extend(Effect.MoveBy.prototype, Effect.Base.prototype), {
+ initialize: function(element, toTop, toLeft) {
+ this.element = $(element);
+ this.toTop = toTop;
+ this.toLeft = toLeft;
+ this.start(arguments[3]);
+ },
+ setup: function() {
+ // Bug in Opera: Opera returns the "real" position of a static element or
+ // relative element that does not have top/left explicitly set.
+ // ==> Always set top and left for position relative elements in your stylesheets
+ // (to 0 if you do not need them)
+
+ Element.makePositioned(this.element);
+ this.originalTop = parseFloat(Element.getStyle(this.element,'top') || '0');
+ this.originalLeft = parseFloat(Element.getStyle(this.element,'left') || '0');
+ },
+ update: function(position) {
+ var topd = this.toTop * position + this.originalTop;
+ var leftd = this.toLeft * position + this.originalLeft;
+ this.setPosition(topd, leftd);
+ },
+ setPosition: function(topd, leftd) {
+ this.element.style.top = topd + "px";
+ this.element.style.left = leftd + "px";
+ }
+});
+
+Effect.Scale = Class.create();
+Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), {
+ initialize: function(element, percent) {
+ this.element = $(element)
+ var options = Object.extend({
+ scaleX: true,
+ scaleY: true,
+ scaleContent: true,
+ scaleFromCenter: false,
+ scaleMode: 'box', // 'box' or 'contents' or {} with provided values
+ scaleFrom: 100.0,
+ scaleTo: percent
+ }, arguments[2] || {});
+ this.start(options);
+ },
+ setup: function() {
+ var effect = this;
+
+ this.restoreAfterFinish = this.options.restoreAfterFinish || false;
+ this.elementPositioning = Element.getStyle(this.element,'position');
+
+ effect.originalStyle = {};
+ ['top','left','width','height','fontSize'].each( function(k) {
+ effect.originalStyle[k] = effect.element.style[k];
+ });
+
+ this.originalTop = this.element.offsetTop;
+ this.originalLeft = this.element.offsetLeft;
+
+ var fontSize = Element.getStyle(this.element,'font-size') || "100%";
+ ['em','px','%'].each( function(fontSizeType) {
+ if(fontSize.indexOf(fontSizeType)>0) {
+ effect.fontSize = parseFloat(fontSize);
+ effect.fontSizeType = fontSizeType;
+ }
+ });
+
+ this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
+
+ this.dims = null;
+ if(this.options.scaleMode=='box')
+ this.dims = [this.element.clientHeight, this.element.clientWidth];
+ if(this.options.scaleMode=='content')
+ this.dims = [this.element.scrollHeight, this.element.scrollWidth];
+ if(!this.dims)
+ this.dims = [this.options.scaleMode.originalHeight,
+ this.options.scaleMode.originalWidth];
+ },
+ update: function(position) {
+ var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
+ if(this.options.scaleContent && this.fontSize)
+ this.element.style.fontSize = this.fontSize*currentScale + this.fontSizeType;
+ this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
+ },
+ finish: function(position) {
+ if (this.restoreAfterFinish) {
+ var effect = this;
+ ['top','left','width','height','fontSize'].each( function(k) {
+ effect.element.style[k] = effect.originalStyle[k];
+ });
+ }
+ },
+ setDimensions: function(height, width) {
+ var els = this.element.style;
+ if(this.options.scaleX) els.width = width + 'px';
+ if(this.options.scaleY) els.height = height + 'px';
+ if(this.options.scaleFromCenter) {
+ var topd = (height - this.dims[0])/2;
+ var leftd = (width - this.dims[1])/2;
+ if(this.elementPositioning == 'absolute') {
+ if(this.options.scaleY) els.top = this.originalTop-topd + "px";
+ if(this.options.scaleX) els.left = this.originalLeft-leftd + "px";
+ } else {
+ if(this.options.scaleY) els.top = -topd + "px";
+ if(this.options.scaleX) els.left = -leftd + "px";
+ }
+ }
+ }
+});
+
+Effect.Highlight = Class.create();
+Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), {
+ initialize: function(element) {
+ this.element = $(element);
+ var options = Object.extend({
+ startcolor: "#ffff99"
+ }, arguments[1] || {});
+ this.start(options);
+ },
+ setup: function() {
+ // Prevent executing on elements not in the layout flow
+ if(this.element.style.display=='none') { this.cancel(); return; }
+ // Disable background image during the effect
+ this.oldBgImage = this.element.style.backgroundImage;
+ this.element.style.backgroundImage = "none";
+ if(!this.options.endcolor)
+ this.options.endcolor = Element.getStyle(this.element, 'background-color').parseColor('#ffffff');
+ if (typeof this.options.restorecolor == "undefined")
+ this.options.restorecolor = this.element.style.backgroundColor;
+ // init color calculations
+ this.colors_base = [
+ parseInt(this.options.startcolor.slice(1,3),16),
+ parseInt(this.options.startcolor.slice(3,5),16),
+ parseInt(this.options.startcolor.slice(5),16) ];
+ this.colors_delta = [
+ parseInt(this.options.endcolor.slice(1,3),16)-this.colors_base[0],
+ parseInt(this.options.endcolor.slice(3,5),16)-this.colors_base[1],
+ parseInt(this.options.endcolor.slice(5),16)-this.colors_base[2]];
+ },
+ update: function(position) {
+ var effect = this; var colors = $R(0,2).map( function(i){
+ return Math.round(effect.colors_base[i]+(effect.colors_delta[i]*position))
+ });
+ this.element.style.backgroundColor = "#" +
+ colors[0].toColorPart() + colors[1].toColorPart() + colors[2].toColorPart();
+ },
+ finish: function() {
+ this.element.style.backgroundColor = this.options.restorecolor;
+ this.element.style.backgroundImage = this.oldBgImage;
+ }
+});
+
+Effect.ScrollTo = Class.create();
+Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), {
+ initialize: function(element) {
+ this.element = $(element);
+ this.start(arguments[1] || {});
+ },
+ setup: function() {
+ Position.prepare();
+ var offsets = Position.cumulativeOffset(this.element);
+ var max = window.innerHeight ?
+ window.height - window.innerHeight :
+ document.body.scrollHeight -
+ (document.documentElement.clientHeight ?
+ document.documentElement.clientHeight : document.body.clientHeight);
+ this.scrollStart = Position.deltaY;
+ this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart;
+ },
+ update: function(position) {
+ Position.prepare();
+ window.scrollTo(Position.deltaX,
+ this.scrollStart + (position*this.delta));
+ }
+});
+
+/* ------------- combination effects ------------- */
+
+Effect.Fade = function(element) {
+ var oldOpacity = Element.getInlineOpacity(element);
+ var options = Object.extend({
+ from: Element.getOpacity(element) || 1.0,
+ to: 0.0,
+ afterFinishInternal: function(effect)
+ { if (effect.options.to == 0) {
+ Element.hide(effect.element);
+ Element.setInlineOpacity(effect.element, oldOpacity);
+ }
+ }
+ }, arguments[1] || {});
+ return new Effect.Opacity(element,options);
+}
+
+Effect.Appear = function(element) {
+ var options = Object.extend({
+ from: (Element.getStyle(element, "display") == "none" ? 0.0 : Element.getOpacity(element) || 0.0),
+ to: 1.0,
+ beforeSetup: function(effect)
+ { Element.setOpacity(effect.element, effect.options.from);
+ Element.show(effect.element); }
+ }, arguments[1] || {});
+ return new Effect.Opacity(element,options);
+}
+
+Effect.Puff = function(element) {
+ element = $(element);
+ var oldOpacity = Element.getInlineOpacity(element);
+ var oldPosition = element.style.position;
+ return new Effect.Parallel(
+ [ new Effect.Scale(element, 200,
+ { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }),
+ new Effect.Opacity(element, { sync: true, to: 0.0 } ) ],
+ Object.extend({ duration: 1.0,
+ beforeSetupInternal: function(effect)
+ { effect.effects[0].element.style.position = 'absolute'; },
+ afterFinishInternal: function(effect)
+ { Element.hide(effect.effects[0].element);
+ effect.effects[0].element.style.position = oldPosition;
+ Element.setInlineOpacity(effect.effects[0].element, oldOpacity); }
+ }, arguments[1] || {})
+ );
+}
+
+Effect.BlindUp = function(element) {
+ element = $(element);
+ Element.makeClipping(element);
+ return new Effect.Scale(element, 0,
+ Object.extend({ scaleContent: false,
+ scaleX: false,
+ restoreAfterFinish: true,
+ afterFinishInternal: function(effect)
+ {
+ Element.hide(effect.element);
+ Element.undoClipping(effect.element);
+ }
+ }, arguments[1] || {})
+ );
+}
+
+Effect.BlindDown = function(element) {
+ element = $(element);
+ var oldHeight = element.style.height;
+ var elementDimensions = Element.getDimensions(element);
+ return new Effect.Scale(element, 100,
+ Object.extend({ scaleContent: false,
+ scaleX: false,
+ scaleFrom: 0,
+ scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
+ restoreAfterFinish: true,
+ afterSetup: function(effect) {
+ Element.makeClipping(effect.element);
+ effect.element.style.height = "0px";
+ Element.show(effect.element);
+ },
+ afterFinishInternal: function(effect) {
+ Element.undoClipping(effect.element);
+ effect.element.style.height = oldHeight;
+ }
+ }, arguments[1] || {})
+ );
+}
+
+Effect.SwitchOff = function(element) {
+ element = $(element);
+ var oldOpacity = Element.getInlineOpacity(element);
+ return new Effect.Appear(element, {
+ duration: 0.4,
+ from: 0,
+ transition: Effect.Transitions.flicker,
+ afterFinishInternal: function(effect) {
+ new Effect.Scale(effect.element, 1, {
+ duration: 0.3, scaleFromCenter: true,
+ scaleX: false, scaleContent: false, restoreAfterFinish: true,
+ beforeSetup: function(effect) {
+ Element.makePositioned(effect.element);
+ Element.makeClipping(effect.element);
+ },
+ afterFinishInternal: function(effect) {
+ Element.hide(effect.element);
+ Element.undoClipping(effect.element);
+ Element.undoPositioned(effect.element);
+ Element.setInlineOpacity(effect.element, oldOpacity);
+ }
+ })
+ }
+ });
+}
+
+Effect.DropOut = function(element) {
+ element = $(element);
+ var oldTop = element.style.top;
+ var oldLeft = element.style.left;
+ var oldOpacity = Element.getInlineOpacity(element);
+ return new Effect.Parallel(
+ [ new Effect.MoveBy(element, 100, 0, { sync: true }),
+ new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
+ Object.extend(
+ { duration: 0.5,
+ beforeSetup: function(effect) {
+ Element.makePositioned(effect.effects[0].element); },
+ afterFinishInternal: function(effect) {
+ Element.hide(effect.effects[0].element);
+ Element.undoPositioned(effect.effects[0].element);
+ effect.effects[0].element.style.left = oldLeft;
+ effect.effects[0].element.style.top = oldTop;
+ Element.setInlineOpacity(effect.effects[0].element, oldOpacity); }
+ }, arguments[1] || {}));
+}
+
+Effect.Shake = function(element) {
+ element = $(element);
+ var oldTop = element.style.top;
+ var oldLeft = element.style.left;
+ return new Effect.MoveBy(element, 0, 20,
+ { duration: 0.05, afterFinishInternal: function(effect) {
+ new Effect.MoveBy(effect.element, 0, -40,
+ { duration: 0.1, afterFinishInternal: function(effect) {
+ new Effect.MoveBy(effect.element, 0, 40,
+ { duration: 0.1, afterFinishInternal: function(effect) {
+ new Effect.MoveBy(effect.element, 0, -40,
+ { duration: 0.1, afterFinishInternal: function(effect) {
+ new Effect.MoveBy(effect.element, 0, 40,
+ { duration: 0.1, afterFinishInternal: function(effect) {
+ new Effect.MoveBy(effect.element, 0, -20,
+ { duration: 0.05, afterFinishInternal: function(effect) {
+ Element.undoPositioned(effect.element);
+ effect.element.style.left = oldLeft;
+ effect.element.style.top = oldTop;
+ }}) }}) }}) }}) }}) }});
+}
+
+Effect.SlideDown = function(element) {
+ element = $(element);
+ Element.cleanWhitespace(element);
+ // SlideDown need to have the content of the element wrapped in a container element with fixed height!
+ var oldInnerBottom = element.firstChild.style.bottom;
+ var elementDimensions = Element.getDimensions(element);
+ return new Effect.Scale(element, 100,
+ Object.extend({ scaleContent: false,
+ scaleX: false,
+ scaleFrom: 0,
+ scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
+ restoreAfterFinish: true,
+ afterSetup: function(effect) {
+ Element.makePositioned(effect.element.firstChild);
+ if (window.opera) effect.element.firstChild.style.top = "";
+ Element.makeClipping(effect.element);
+ element.style.height = '0';
+ Element.show(element);
+ },
+ afterUpdateInternal: function(effect) {
+ effect.element.firstChild.style.bottom =
+ (effect.dims[0] - effect.element.clientHeight) + 'px'; },
+ afterFinishInternal: function(effect) {
+ Element.undoClipping(effect.element);
+ Element.undoPositioned(effect.element.firstChild);
+ effect.element.firstChild.style.bottom = oldInnerBottom; }
+ }, arguments[1] || {})
+ );
+}
+
+Effect.SlideUp = function(element) {
+ element = $(element);
+ Element.cleanWhitespace(element);
+ var oldInnerBottom = element.firstChild.style.bottom;
+ return new Effect.Scale(element, 0,
+ Object.extend({ scaleContent: false,
+ scaleX: false,
+ scaleMode: 'box',
+ scaleFrom: 100,
+ restoreAfterFinish: true,
+ beforeStartInternal: function(effect) {
+ Element.makePositioned(effect.element.firstChild);
+ if (window.opera) effect.element.firstChild.style.top = "";
+ Element.makeClipping(effect.element);
+ Element.show(element);
+ },
+ afterUpdateInternal: function(effect) {
+ effect.element.firstChild.style.bottom =
+ (effect.dims[0] - effect.element.clientHeight) + 'px'; },
+ afterFinishInternal: function(effect) {
+ Element.hide(effect.element);
+ Element.undoClipping(effect.element);
+ Element.undoPositioned(effect.element.firstChild);
+ effect.element.firstChild.style.bottom = oldInnerBottom; }
+ }, arguments[1] || {})
+ );
+}
+
+Effect.Squish = function(element) {
+ // Bug in opera makes the TD containing this element expand for a instance after finish
+ return new Effect.Scale(element, window.opera ? 1 : 0,
+ { restoreAfterFinish: true,
+ beforeSetup: function(effect) {
+ Element.makeClipping(effect.element); },
+ afterFinishInternal: function(effect) {
+ Element.hide(effect.element);
+ Element.undoClipping(effect.element); }
+ });
+}
+
+Effect.Grow = function(element) {
+ element = $(element);
+ var options = arguments[1] || {};
+
+ var elementDimensions = Element.getDimensions(element);
+ var originalWidth = elementDimensions.width;
+ var originalHeight = elementDimensions.height;
+ var oldTop = element.style.top;
+ var oldLeft = element.style.left;
+ var oldHeight = element.style.height;
+ var oldWidth = element.style.width;
+ var oldOpacity = Element.getInlineOpacity(element);
+
+ var direction = options.direction || 'center';
+ var moveTransition = options.moveTransition || Effect.Transitions.sinoidal;
+ var scaleTransition = options.scaleTransition || Effect.Transitions.sinoidal;
+ var opacityTransition = options.opacityTransition || Effect.Transitions.full;
+
+ var initialMoveX, initialMoveY;
+ var moveX, moveY;
+
+ switch (direction) {
+ case 'top-left':
+ initialMoveX = initialMoveY = moveX = moveY = 0;
+ break;
+ case 'top-right':
+ initialMoveX = originalWidth;
+ initialMoveY = moveY = 0;
+ moveX = -originalWidth;
+ break;
+ case 'bottom-left':
+ initialMoveX = moveX = 0;
+ initialMoveY = originalHeight;
+ moveY = -originalHeight;
+ break;
+ case 'bottom-right':
+ initialMoveX = originalWidth;
+ initialMoveY = originalHeight;
+ moveX = -originalWidth;
+ moveY = -originalHeight;
+ break;
+ case 'center':
+ initialMoveX = originalWidth / 2;
+ initialMoveY = originalHeight / 2;
+ moveX = -originalWidth / 2;
+ moveY = -originalHeight / 2;
+ break;
+ }
+
+ return new Effect.MoveBy(element, initialMoveY, initialMoveX, {
+ duration: 0.01,
+ beforeSetup: function(effect) {
+ Element.hide(effect.element);
+ Element.makeClipping(effect.element);
+ Element.makePositioned(effect.element);
+ },
+ afterFinishInternal: function(effect) {
+ new Effect.Parallel(
+ [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: opacityTransition }),
+ new Effect.MoveBy(effect.element, moveY, moveX, { sync: true, transition: moveTransition }),
+ new Effect.Scale(effect.element, 100, {
+ scaleMode: { originalHeight: originalHeight, originalWidth: originalWidth },
+ sync: true, scaleFrom: window.opera ? 1 : 0, transition: scaleTransition, restoreAfterFinish: true})
+ ], Object.extend({
+ beforeSetup: function(effect) {
+ effect.effects[0].element.style.height = 0;
+ Element.show(effect.effects[0].element);
+ },
+ afterFinishInternal: function(effect) {
+ var el = effect.effects[0].element;
+ var els = el.style;
+ Element.undoClipping(el);
+ Element.undoPositioned(el);
+ els.top = oldTop;
+ els.left = oldLeft;
+ els.height = oldHeight;
+ els.width = originalWidth + 'px';
+ Element.setInlineOpacity(el, oldOpacity);
+ }
+ }, options)
+ )
+ }
+ });
+}
+
+Effect.Shrink = function(element) {
+ element = $(element);
+ var options = arguments[1] || {};
+
+ var originalWidth = element.clientWidth;
+ var originalHeight = element.clientHeight;
+ var oldTop = element.style.top;
+ var oldLeft = element.style.left;
+ var oldHeight = element.style.height;
+ var oldWidth = element.style.width;
+ var oldOpacity = Element.getInlineOpacity(element);
+
+ var direction = options.direction || 'center';
+ var moveTransition = options.moveTransition || Effect.Transitions.sinoidal;
+ var scaleTransition = options.scaleTransition || Effect.Transitions.sinoidal;
+ var opacityTransition = options.opacityTransition || Effect.Transitions.none;
+
+ var moveX, moveY;
+
+ switch (direction) {
+ case 'top-left':
+ moveX = moveY = 0;
+ break;
+ case 'top-right':
+ moveX = originalWidth;
+ moveY = 0;
+ break;
+ case 'bottom-left':
+ moveX = 0;
+ moveY = originalHeight;
+ break;
+ case 'bottom-right':
+ moveX = originalWidth;
+ moveY = originalHeight;
+ break;
+ case 'center':
+ moveX = originalWidth / 2;
+ moveY = originalHeight / 2;
+ break;
+ }
+
+ return new Effect.Parallel(
+ [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: opacityTransition }),
+ new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: scaleTransition, restoreAfterFinish: true}),
+ new Effect.MoveBy(element, moveY, moveX, { sync: true, transition: moveTransition })
+ ], Object.extend({
+ beforeStartInternal: function(effect) {
+ Element.makePositioned(effect.effects[0].element);
+ Element.makeClipping(effect.effects[0].element);
+ },
+ afterFinishInternal: function(effect) {
+ var el = effect.effects[0].element;
+ var els = el.style;
+ Element.hide(el);
+ Element.undoClipping(el);
+ Element.undoPositioned(el);
+ els.top = oldTop;
+ els.left = oldLeft;
+ els.height = oldHeight;
+ els.width = oldWidth;
+ Element.setInlineOpacity(el, oldOpacity);
+ }
+ }, options)
+ );
+}
+
+Effect.Pulsate = function(element) {
+ element = $(element);
+ var options = arguments[1] || {};
+ var oldOpacity = Element.getInlineOpacity(element);
+ var transition = options.transition || Effect.Transitions.sinoidal;
+ var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos)) };
+ reverser.bind(transition);
+ return new Effect.Opacity(element,
+ Object.extend(Object.extend({ duration: 3.0, from: 0,
+ afterFinishInternal: function(effect) { Element.setInlineOpacity(effect.element, oldOpacity); }
+ }, options), {transition: reverser}));
+}
+
+Effect.Fold = function(element) {
+ element = $(element);
+ var originalTop = element.style.top;
+ var originalLeft = element.style.left;
+ var originalWidth = element.style.width;
+ var originalHeight = element.style.height;
+ Element.makeClipping(element);
+ return new Effect.Scale(element, 5, Object.extend({
+ scaleContent: false,
+ scaleX: false,
+ afterFinishInternal: function(effect) {
+ new Effect.Scale(element, 1, {
+ scaleContent: false,
+ scaleY: false,
+ afterFinishInternal: function(effect) {
+ Element.hide(effect.element);
+ Element.undoClipping(effect.element);
+ effect.element.style.top = originalTop;
+ effect.element.style.left = originalLeft;
+ effect.element.style.width = originalWidth;
+ effect.element.style.height = originalHeight;
+ } });
+ }}, arguments[1] || {}));
+}
diff --git a/web/static/js/scriptaculous/scriptaculous.js b/web/static/js/scriptaculous/scriptaculous.js
new file mode 100644
index 0000000..940d886
--- /dev/null
+++ b/web/static/js/scriptaculous/scriptaculous.js
@@ -0,0 +1,48 @@
+// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+var Scriptaculous = {
+ Version: '1.5_rc4',
+ require: function(libraryName) {
+ // inserting via DOM fails in Safari 2.0, so brute force approach
+ document.write('<script type="text/javascript" src="'+libraryName+'"></script>');
+ },
+ load: function() {
+ if((typeof Prototype=='undefined') ||
+ parseFloat(Prototype.Version.split(".")[0] + "." +
+ Prototype.Version.split(".")[1]) < 1.4)
+ throw("script.aculo.us requires the Prototype JavaScript framework >= 1.4.0");
+ var scriptTags = document.getElementsByTagName("script");
+ for(var i=0;i<scriptTags.length;i++) {
+ if(scriptTags[i].src && scriptTags[i].src.match(/scriptaculous\.js(\?.*)?$/)) {
+ var path = scriptTags[i].src.replace(/scriptaculous\.js(\?.*)?$/,'');
+ this.require(path + 'builder.js');
+ this.require(path + 'effects.js');
+ this.require(path + 'dragdrop.js');
+ this.require(path + 'controls.js');
+ this.require(path + 'slider.js');
+ break;
+ }
+ }
+ }
+}
+
+Scriptaculous.load();
\ No newline at end of file
diff --git a/web/static/js/scriptaculous/slider.js b/web/static/js/scriptaculous/slider.js
new file mode 100644
index 0000000..736ed41
--- /dev/null
+++ b/web/static/js/scriptaculous/slider.js
@@ -0,0 +1,226 @@
+// Copyright (c) 2005 Marty Haught
+//
+// See scriptaculous.js for full license.
+
+if(!Control) var Control = {};
+Control.Slider = Class.create();
+
+// options:
+// axis: 'vertical', or 'horizontal' (default)
+//
+// callbacks:
+// onChange(value)
+// onSlide(value)
+Control.Slider.prototype = {
+ initialize: function(handle, track, options) {
+ var slider = this;
+
+ if(handle instanceof Array) {
+ this.handles = handle.collect( function(e) { return $(e) });
+ } else {
+ this.handles = [$(handle)];
+ }
+
+ this.track = $(track);
+ this.options = options || {};
+
+ this.axis = this.options.axis || 'horizontal';
+ this.increment = this.options.increment || 1;
+ this.step = parseInt(this.options.step || '1');
+ this.range = this.options.range || $R(0,1);
+
+ this.value = 0; // assure backwards compat
+ this.values = this.handles.map( function() { return 0 });
+ this.spans = this.options.spans ? this.options.spans.map(function(s){ return $(s) }) : false;
+ this.restricted = this.options.restricted || false;
+
+ this.maximum = this.options.maximum || this.range.end;
+ this.minimum = this.options.minimum || this.range.start;
+
+ // Will be used to align the handle onto the track, if necessary
+ this.alignX = parseInt(this.options.alignX || '0');
+ this.alignY = parseInt(this.options.alignY || '0');
+
+ this.trackLength = this.maximumOffset() - this.minimumOffset();
+
+ this.active = false;
+ this.dragging = false;
+ this.disabled = false;
+
+ if(this.options.disabled) this.setDisabled();
+
+ // Allowed values array
+ this.allowedValues = this.options.values ? this.options.values.sortBy(Prototype.K) : false;
+ if(this.allowedValues) {
+ this.minimum = this.allowedValues.min();
+ this.maximum = this.allowedValues.max();
+ }
+
+ this.eventMouseDown = this.startDrag.bindAsEventListener(this);
+ this.eventMouseUp = this.endDrag.bindAsEventListener(this);
+ this.eventMouseMove = this.update.bindAsEventListener(this);
+
+ // Initialize handles
+ this.handles.each( function(h,i) {
+ slider.setValue(parseInt(slider.options.sliderValue || slider.range.start), i);
+ Element.makePositioned(h); // fix IE
+ Event.observe(h, "mousedown", slider.eventMouseDown);
+ });
+
+ Event.observe(document, "mouseup", this.eventMouseUp);
+ Event.observe(document, "mousemove", this.eventMouseMove);
+ },
+ dispose: function() {
+ var slider = this;
+ Event.stopObserving(document, "mouseup", this.eventMouseUp);
+ Event.stopObserving(document, "mousemove", this.eventMouseMove);
+ this.handles.each( function(h) {
+ Event.stopObserving(h, "mousedown", slider.eventMouseDown);
+ });
+ },
+ setDisabled: function(){
+ this.disabled = true;
+ },
+ setEnabled: function(){
+ this.disabled = false;
+ },
+ getNearestValue: function(value){
+ if(this.allowedValues){
+ if(value >= this.allowedValues.max()) return(this.allowedValues.max());
+ if(value <= this.allowedValues.min()) return(this.allowedValues.min());
+
+ var offset = Math.abs(this.allowedValues[0] - value);
+ var newValue = this.allowedValues[0];
+ this.allowedValues.each( function(v) {
+ var currentOffset = Math.abs(v - value);
+ if(currentOffset <= offset){
+ newValue = v;
+ offset = currentOffset;
+ }
+ });
+ return newValue;
+ }
+ if(value > this.range.end) return this.range.end;
+ if(value < this.range.start) return this.range.start;
+ return value;
+ },
+ setValue: function(sliderValue, handleIdx){
+ if(!this.active) {
+ this.activeHandle = this.handles[handleIdx];
+ this.activeHandleIdx = handleIdx;
+ }
+ handleIdx = handleIdx || this.activeHandleIdx || 0;
+ if(this.restricted) {
+ if((handleIdx>0) && (sliderValue<this.values[handleIdx-1]))
+ sliderValue = this.values[handleIdx-1];
+ if((handleIdx < (this.handles.length-1)) && (sliderValue>this.values[handleIdx+1]))
+ sliderValue = this.values[handleIdx+1];
+ }
+ sliderValue = this.getNearestValue(sliderValue);
+ this.values[handleIdx] = sliderValue;
+ this.value = this.values[0]; // assure backwards compat
+
+ this.handles[handleIdx].style[ this.isVertical() ? 'top' : 'left'] =
+ this.translateToPx(sliderValue);
+
+ this.drawSpans();
+ this.updateFinished();
+ },
+ setValueBy: function(delta, handleIdx) {
+ this.setValue(this.values[handleIdx || this.activeHandleIdx || 0] + delta,
+ handleIdx || this.activeHandleIdx || 0);
+ },
+ translateToPx: function(value) {
+ return Math.round((this.trackLength / (this.range.end - this.range.start)) * (value - this.range.start)) + "px";
+ },
+ translateToValue: function(offset) {
+ return ((offset/this.trackLength) * (this.range.end - this.range.start)) + this.range.start;
+ },
+ getRange: function(range) {
+ var v = this.values.sortBy(Prototype.K);
+ range = range || 0;
+ return $R(v[range],v[range+1]);
+ },
+ minimumOffset: function(){
+ return(this.isVertical() ? this.alignY : this.alignX);
+ },
+ maximumOffset: function(){
+ return(this.isVertical() ?
+ this.track.offsetHeight - this.alignY : this.track.offsetWidth - this.alignX);
+ },
+ isVertical: function(){
+ return (this.axis == 'vertical');
+ },
+ drawSpans: function() {
+ var slider = this;
+ if(this.spans)
+ $R(0, this.spans.length-1).each(function(r) { slider.setSpan(r, slider.getRange(r)) });
+ },
+ setSpan: function(span, range) {
+ if(this.isVertical()) {
+ this.spans[span].style.top = this.translateToPx(range.start);
+ this.spans[span].style.height = this.translateToPx(range.end - range.start);
+ } else {
+ this.spans[span].style.left = this.translateToPx(range.start);
+ this.spans[span].style.width = this.translateToPx(range.end - range.start);
+ }
+ },
+ startDrag: function(event) {
+ if(Event.isLeftClick(event)) {
+ if(!this.disabled){
+ this.active = true;
+
+ // find the handle (prevents issues with Safari)
+ var handle = Event.element(event);
+ while((this.handles.indexOf(handle) == -1) && handle.parentNode)
+ handle = handle.parentNode;
+
+ this.activeHandle = handle;
+ this.activeHandleIdx = this.handles.indexOf(this.activeHandle);
+
+ var pointer = [Event.pointerX(event), Event.pointerY(event)];
+ var offsets = Position.cumulativeOffset(this.activeHandle);
+ this.offsetX = (pointer[0] - offsets[0]);
+ this.offsetY = (pointer[1] - offsets[1]);
+
+ }
+ Event.stop(event);
+ }
+ },
+ update: function(event) {
+ if(this.active) {
+ if(!this.dragging) {
+ this.dragging = true;
+ if(this.activeHandle.style.position=="") style.position = "relative";
+ }
+ this.draw(event);
+ // fix AppleWebKit rendering
+ if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
+ Event.stop(event);
+ }
+ },
+ draw: function(event) {
+ var pointer = [Event.pointerX(event), Event.pointerY(event)];
+ var offsets = Position.cumulativeOffset(this.track);
+ pointer[0] -= this.offsetX + offsets[0];
+ pointer[1] -= this.offsetY + offsets[1];
+ this.setValue(this.translateToValue( this.isVertical() ? pointer[1] : pointer[0] ));
+ if(this.options.onSlide) this.options.onSlide(this.values.length>1 ? this.values : this.value, this);
+ },
+ endDrag: function(event) {
+ if(this.active && this.dragging) {
+ this.finishDrag(event, true);
+ Event.stop(event);
+ }
+ this.active = false;
+ this.dragging = false;
+ },
+ finishDrag: function(event, success) {
+ this.active = false;
+ this.dragging = false;
+ this.updateFinished();
+ },
+ updateFinished: function() {
+ if(this.options.onChange) this.options.onChange(this.values.length>1 ? this.values : this.value, this);
+ }
+}
diff --git a/web/static/js/scriptaculous/unittest.js b/web/static/js/scriptaculous/unittest.js
new file mode 100644
index 0000000..20941ad
--- /dev/null
+++ b/web/static/js/scriptaculous/unittest.js
@@ -0,0 +1,363 @@
+// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+// (c) 2005 Jon Tirsen (http://www.tirsen.com)
+// (c) 2005 Michael Schuerig (http://www.schuerig.de/michael/)
+//
+// See scriptaculous.js for full license.
+
+// experimental, Firefox-only
+Event.simulateMouse = function(element, eventName) {
+ var options = Object.extend({
+ pointerX: 0,
+ pointerY: 0,
+ buttons: 0
+ }, arguments[2] || {});
+ var oEvent = document.createEvent("MouseEvents");
+ oEvent.initMouseEvent(eventName, true, true, document.defaultView,
+ options.buttons, options.pointerX, options.pointerY, options.pointerX, options.pointerY,
+ false, false, false, false, 0, $(element));
+
+ if(this.mark) Element.remove(this.mark);
+ this.mark = document.createElement('div');
+ this.mark.appendChild(document.createTextNode(" "));
+ document.body.appendChild(this.mark);
+ this.mark.style.position = 'absolute';
+ this.mark.style.top = options.pointerY + "px";
+ this.mark.style.left = options.pointerX + "px";
+ this.mark.style.width = "5px";
+ this.mark.style.height = "5px;";
+ this.mark.style.borderTop = "1px solid red;"
+ this.mark.style.borderLeft = "1px solid red;"
+
+ if(this.step)
+ alert('['+new Date().getTime().toString()+'] '+eventName+'/'+Test.Unit.inspect(options));
+
+ $(element).dispatchEvent(oEvent);
+};
+
+// Note: Due to a fix in Firefox 1.0.5/6 that probably fixed "too much", this doesn't work in 1.0.6 or DP2.
+// You need to downgrade to 1.0.4 for now to get this working
+// See https://bugzilla.mozilla.org/show_bug.cgi?id=289940 for the fix that fixed too much
+Event.simulateKey = function(element, eventName) {
+ var options = Object.extend({
+ ctrlKey: false,
+ altKey: false,
+ shiftKey: false,
+ metaKey: false,
+ keyCode: 0,
+ charCode: 0
+ }, arguments[2] || {});
+
+ var oEvent = document.createEvent("KeyEvents");
+ oEvent.initKeyEvent(eventName, true, true, window,
+ options.ctrlKey, options.altKey, options.shiftKey, options.metaKey,
+ options.keyCode, options.charCode );
+ $(element).dispatchEvent(oEvent);
+};
+
+Event.simulateKeys = function(element, command) {
+ for(var i=0; i<command.length; i++) {
+ Event.simulateKey(element,'keypress',{charCode:command.charCodeAt(i)});
+ }
+};
+
+var Test = {}
+Test.Unit = {};
+
+// security exception workaround
+Test.Unit.inspect = function(obj) {
+ var info = [];
+
+ if(typeof obj=="string" ||
+ typeof obj=="number") {
+ return obj;
+ } else {
+ for(property in obj)
+ if(typeof obj[property]!="function")
+ info.push(property + ' => ' +
+ (typeof obj[property] == "string" ?
+ '"' + obj[property] + '"' :
+ obj[property]));
+ }
+
+ return ("'" + obj + "' #" + typeof obj +
+ ": {" + info.join(", ") + "}");
+}
+
+Test.Unit.Logger = Class.create();
+Test.Unit.Logger.prototype = {
+ initialize: function(log) {
+ this.log = $(log);
+ if (this.log) {
+ this._createLogTable();
+ }
+ },
+ start: function(testName) {
+ if (!this.log) return;
+ this.testName = testName;
+ this.lastLogLine = document.createElement('tr');
+ this.statusCell = document.createElement('td');
+ this.nameCell = document.createElement('td');
+ this.nameCell.appendChild(document.createTextNode(testName));
+ this.messageCell = document.createElement('td');
+ this.lastLogLine.appendChild(this.statusCell);
+ this.lastLogLine.appendChild(this.nameCell);
+ this.lastLogLine.appendChild(this.messageCell);
+ this.loglines.appendChild(this.lastLogLine);
+ },
+ finish: function(status, summary) {
+ if (!this.log) return;
+ this.lastLogLine.className = status;
+ this.statusCell.innerHTML = status;
+ this.messageCell.innerHTML = this._toHTML(summary);
+ },
+ message: function(message) {
+ if (!this.log) return;
+ this.messageCell.innerHTML = this._toHTML(message);
+ },
+ summary: function(summary) {
+ if (!this.log) return;
+ this.logsummary.innerHTML = this._toHTML(summary);
+ },
+ _createLogTable: function() {
+ this.log.innerHTML =
+ '<div id="logsummary"></div>' +
+ '<table id="logtable">' +
+ '<thead><tr><th>Status</th><th>Test</th><th>Message</th></tr></thead>' +
+ '<tbody id="loglines"></tbody>' +
+ '</table>';
+ this.logsummary = $('logsummary')
+ this.loglines = $('loglines');
+ },
+ _toHTML: function(txt) {
+ return txt.escapeHTML().replace(/\n/g,"<br/>");
+ }
+}
+
+Test.Unit.Runner = Class.create();
+Test.Unit.Runner.prototype = {
+ initialize: function(testcases) {
+ this.options = Object.extend({
+ testLog: 'testlog'
+ }, arguments[1] || {});
+ this.options.resultsURL = this.parseResultsURLQueryParameter();
+ if (this.options.testLog) {
+ this.options.testLog = $(this.options.testLog) || null;
+ }
+ if(this.options.tests) {
+ this.tests = [];
+ for(var i = 0; i < this.options.tests.length; i++) {
+ if(/^test/.test(this.options.tests[i])) {
+ this.tests.push(new Test.Unit.Testcase(this.options.tests[i], testcases[this.options.tests[i]], testcases["setup"], testcases["teardown"]));
+ }
+ }
+ } else {
+ if (this.options.test) {
+ this.tests = [new Test.Unit.Testcase(this.options.test, testcases[this.options.test], testcases["setup"], testcases["teardown"])];
+ } else {
+ this.tests = [];
+ for(var testcase in testcases) {
+ if(/^test/.test(testcase)) {
+ this.tests.push(new Test.Unit.Testcase(testcase, testcases[testcase], testcases["setup"], testcases["teardown"]));
+ }
+ }
+ }
+ }
+ this.currentTest = 0;
+ this.logger = new Test.Unit.Logger(this.options.testLog);
+ setTimeout(this.runTests.bind(this), 1000);
+ },
+ parseResultsURLQueryParameter: function() {
+ return window.location.search.parseQuery()["resultsURL"];
+ },
+ // Returns:
+ // "ERROR" if there was an error,
+ // "FAILURE" if there was a failure, or
+ // "SUCCESS" if there was neither
+ getResult: function() {
+ var hasFailure = false;
+ for(var i=0;i<this.tests.length;i++) {
+ if (this.tests[i].errors > 0) {
+ return "ERROR";
+ }
+ if (this.tests[i].failures > 0) {
+ hasFailure = true;
+ }
+ }
+ if (hasFailure) {
+ return "FAILURE";
+ } else {
+ return "SUCCESS";
+ }
+ },
+ postResults: function() {
+ if (this.options.resultsURL) {
+ new Ajax.Request(this.options.resultsURL,
+ { method: 'get', parameters: 'result=' + this.getResult(), asynchronous: false });
+ }
+ },
+ runTests: function() {
+ var test = this.tests[this.currentTest];
+ if (!test) {
+ // finished!
+ this.postResults();
+ this.logger.summary(this.summary());
+ return;
+ }
+ if(!test.isWaiting) {
+ this.logger.start(test.name);
+ }
+ test.run();
+ if(test.isWaiting) {
+ this.logger.message("Waiting for " + test.timeToWait + "ms");
+ setTimeout(this.runTests.bind(this), test.timeToWait || 1000);
+ } else {
+ this.logger.finish(test.status(), test.summary());
+ this.currentTest++;
+ // tail recursive, hopefully the browser will skip the stackframe
+ this.runTests();
+ }
+ },
+ summary: function() {
+ var assertions = 0;
+ var failures = 0;
+ var errors = 0;
+ var messages = [];
+ for(var i=0;i<this.tests.length;i++) {
+ assertions += this.tests[i].assertions;
+ failures += this.tests[i].failures;
+ errors += this.tests[i].errors;
+ }
+ return (
+ this.tests.length + " tests, " +
+ assertions + " assertions, " +
+ failures + " failures, " +
+ errors + " errors");
+ }
+}
+
+Test.Unit.Assertions = Class.create();
+Test.Unit.Assertions.prototype = {
+ initialize: function() {
+ this.assertions = 0;
+ this.failures = 0;
+ this.errors = 0;
+ this.messages = [];
+ },
+ summary: function() {
+ return (
+ this.assertions + " assertions, " +
+ this.failures + " failures, " +
+ this.errors + " errors" + "\n" +
+ this.messages.join("\n"));
+ },
+ pass: function() {
+ this.assertions++;
+ },
+ fail: function(message) {
+ this.failures++;
+ this.messages.push("Failure: " + message);
+ },
+ error: function(error) {
+ this.errors++;
+ this.messages.push(error.name + ": "+ error.message + "(" + Test.Unit.inspect(error) +")");
+ },
+ status: function() {
+ if (this.failures > 0) return 'failed';
+ if (this.errors > 0) return 'error';
+ return 'passed';
+ },
+ assert: function(expression) {
+ var message = arguments[1] || 'assert: got "' + Test.Unit.inspect(expression) + '"';
+ try { expression ? this.pass() :
+ this.fail(message); }
+ catch(e) { this.error(e); }
+ },
+ assertEqual: function(expected, actual) {
+ var message = arguments[2] || "assertEqual";
+ try { (expected == actual) ? this.pass() :
+ this.fail(message + ': expected "' + Test.Unit.inspect(expected) +
+ '", actual "' + Test.Unit.inspect(actual) + '"'); }
+ catch(e) { this.error(e); }
+ },
+ assertNotEqual: function(expected, actual) {
+ var message = arguments[2] || "assertNotEqual";
+ try { (expected != actual) ? this.pass() :
+ this.fail(message + ': got "' + Test.Unit.inspect(actual) + '"'); }
+ catch(e) { this.error(e); }
+ },
+ assertNull: function(obj) {
+ var message = arguments[1] || 'assertNull'
+ try { (obj==null) ? this.pass() :
+ this.fail(message + ': got "' + Test.Unit.inspect(obj) + '"'); }
+ catch(e) { this.error(e); }
+ },
+ assertHidden: function(element) {
+ var message = arguments[1] || 'assertHidden';
+ this.assertEqual("none", element.style.display, message);
+ },
+ assertNotNull: function(object) {
+ var message = arguments[1] || 'assertNotNull';
+ this.assert(object != null, message);
+ },
+ assertInstanceOf: function(expected, actual) {
+ var message = arguments[2] || 'assertInstanceOf';
+ try {
+ (actual instanceof expected) ? this.pass() :
+ this.fail(message + ": object was not an instance of the expected type"); }
+ catch(e) { this.error(e); }
+ },
+ assertNotInstanceOf: function(expected, actual) {
+ var message = arguments[2] || 'assertNotInstanceOf';
+ try {
+ !(actual instanceof expected) ? this.pass() :
+ this.fail(message + ": object was an instance of the not expected type"); }
+ catch(e) { this.error(e); }
+ },
+ _isVisible: function(element) {
+ element = $(element);
+ if(!element.parentNode) return true;
+ this.assertNotNull(element);
+ if(element.style && Element.getStyle(element, 'display') == 'none')
+ return false;
+
+ return this._isVisible(element.parentNode);
+ },
+ assertNotVisible: function(element) {
+ this.assert(!this._isVisible(element), Test.Unit.inspect(element) + " was not hidden and didn't have a hidden parent either. " + ("" || arguments[1]));
+ },
+ assertVisible: function(element) {
+ this.assert(this._isVisible(element), Test.Unit.inspect(element) + " was not visible. " + ("" || arguments[1]));
+ }
+}
+
+Test.Unit.Testcase = Class.create();
+Object.extend(Object.extend(Test.Unit.Testcase.prototype, Test.Unit.Assertions.prototype), {
+ initialize: function(name, test, setup, teardown) {
+ Test.Unit.Assertions.prototype.initialize.bind(this)();
+ this.name = name;
+ this.test = test || function() {};
+ this.setup = setup || function() {};
+ this.teardown = teardown || function() {};
+ this.isWaiting = false;
+ this.timeToWait = 1000;
+ },
+ wait: function(time, nextPart) {
+ this.isWaiting = true;
+ this.test = nextPart;
+ this.timeToWait = time;
+ },
+ run: function() {
+ try {
+ try {
+ if (!this.isWaiting) this.setup.bind(this)();
+ this.isWaiting = false;
+ this.test.bind(this)();
+ } finally {
+ if(!this.isWaiting) {
+ this.teardown.bind(this)();
+ }
+ }
+ }
+ catch(e) { this.error(e); }
+ }
+});
\ No newline at end of file
diff --git a/web/templates/_elements/header b/web/templates/_elements/header
new file mode 100644
index 0000000..e8942d3
--- /dev/null
+++ b/web/templates/_elements/header
@@ -0,0 +1,25 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
+ <meta name="robots" content="all" />
+
+ <title><% $title %></title>
+
+ <link rel="stylesheet" type="text/css" href="/css/main.css" media="all" />
+
+ <script type="text/javascript" src="/js/prototype.js"></script>
+ <script type="text/javascript" src="/js/rico.js"></script>
+ <script type="text/javascript" src="/js/behaviour.js"></script>
+ <script type="text/javascript" src="/js/jifty.js"></script>
+ <script type="text/javascript" src="/js/btdt_behaviour.js"></script>
+ <script type="text/javascript" src="/js/bps_util.js"></script>
+ <script type="text/javascript" src="/js/combobox.js"></script>
+ <script type="text/javascript" src="/js/key_bindings.js"></script>
+</head>
+<%args>
+$title => ""
+</%args>
+<%init>
+$r->content_type('text/html; charset=utf-8');
+</%init>
diff --git a/web/templates/_elements/markup b/web/templates/_elements/markup
new file mode 100644
index 0000000..a281a2a
--- /dev/null
+++ b/web/templates/_elements/markup
@@ -0,0 +1,143 @@
+<div id="syntax">
+<h2>Wiki Syntax</h2>
+
+<h3>Phrase Emphasis</h3>
+
+<pre><code>*italic* **bold**
+_italic_ __bold__
+</code></pre>
+
+<h3>Links</h3>
+
+<p>Inline:</p>
+
+<pre><code>Show me a [wiki page](WikiPage)</code></pre>
+
+<pre><code>An [example](http://url.com/ "Title")
+</code></pre>
+
+<p>Reference-style labels (titles are optional):</p>
+
+<pre><code>An [example][id]. Then, anywhere
+else in the doc, define the link:
+
+ [id]: http://example.com/ "Title"
+</code></pre>
+
+<h3>Images</h3>
+
+<p>Inline (titles are optional):</p>
+
+<pre><code>![alt text](/path/img.jpg "Title")
+</code></pre>
+
+<p>Reference-style:</p>
+
+<pre><code>![alt text][id]
+
+[id]: /url/to/img.jpg "Title"
+</code></pre>
+
+<h3>Headers</h3>
+
+<p>Setext-style:</p>
+
+<pre><code>Header 1
+========
+
+Header 2
+--------
+</code></pre>
+
+<p>atx-style (closing #'s are optional):</p>
+
+<pre><code># Header 1 #
+
+## Header 2 ##
+
+###### Header 6
+</code></pre>
+
+<h3>Lists</h3>
+
+<p>Ordered, without paragraphs:</p>
+
+<pre><code>1. Foo
+2. Bar
+
+</code></pre>
+
+<p>Unordered, with paragraphs:</p>
+
+<pre><code>* A list item.
+
+ With multiple paragraphs.
+
+* Bar
+</code></pre>
+
+<p>You can nest them:</p>
+
+<pre><code>* Abacus
+ * answer
+* Bubbles
+ 1. bunk
+ 2. bupkis
+ * BELITTLER
+ 3. burper
+* Cunning
+</code></pre>
+
+<h3>Blockquotes</h3>
+
+<pre><code>> Email-style angle brackets
+> are used for blockquotes.
+
+> > And, they can be nested.
+
+> #### Headers in blockquotes
+>
+> * You can quote a list.
+> * Etc.
+</code></pre>
+
+<h3>Code Spans</h3>
+
+<pre><code>`<code>` spans are delimited
+by backticks.
+
+You can include literal backticks
+like `` `this` ``.
+</code></pre>
+
+<h3>Preformatted Code Blocks</h3>
+
+<p>Indent every line of a code block
+by at least 4 spaces or 1 tab.</p>
+
+<pre><code>This is a normal paragraph.
+
+ This is a preformatted
+ code block.
+</code></pre>
+
+<h3>Horizontal Rules</h3>
+
+<p>Three or more dashes or asterisks:</p>
+
+<pre><code>---
+
+* * *
+
+- - - -
+</code></pre>
+
+<h3>Manual Line Breaks</h3>
+
+<p>End a line with two or more spaces:</p>
+
+<pre><code>Roses are red,
+Violets are blue.
+</code></pre>
+<address>(Thanks to <a href="http://daringfireball.net/projects/markdown/dingus">Daring Fireball</a>)</address>
+</div>
diff --git a/web/templates/_elements/nav b/web/templates/_elements/nav
new file mode 100644
index 0000000..c773de9
--- /dev/null
+++ b/web/templates/_elements/nav
@@ -0,0 +1,7 @@
+<%init>
+my $top = Jifty->web->navigation;
+$top->child(Home => url => "/", sort_order => 1);
+$top->child(Recent => url => "/recent", label => "Recent Changes", sort_order => 2);
+
+return();
+</%init>
diff --git a/web/templates/_elements/sidebar b/web/templates/_elements/sidebar
new file mode 100644
index 0000000..2869f05
--- /dev/null
+++ b/web/templates/_elements/sidebar
@@ -0,0 +1,26 @@
+<div id="salutation">
+% if (Jifty->web->current_user->id) {
+Hiya, <span class="user"><%Jifty->web->current_user->name%></span>.
+% } else {
+You're not currently signed in.
+% }
+</div>
+<ul class="menu">
+% $m->comp(".menu", item => $_) for (sort { $a->sort_order <=> $b->sort_order} Jifty->web->navigation->children);
+</ul>
+<%def .menu>
+<%args>
+$item
+</%args>
+ <li><%
+ Jifty->web->link(
+ url => $item->url,
+ label => $item->label,
+ class => $item->active ? "active" : ""
+ ) %></li>
+% if (my @kids = $item->children) {
+<ul class="menu submenu">
+% $m->comp(".menu", item => $_) for @kids;
+</ul>
+% }
+</%def>
diff --git a/web/templates/_elements/wrapper b/web/templates/_elements/wrapper
new file mode 100644
index 0000000..0ec63a4
--- /dev/null
+++ b/web/templates/_elements/wrapper
@@ -0,0 +1,27 @@
+<& header, title => $title &>
+<body>
+ <div id="headers">
+ <%Jifty->web->link( url => "/", label => Jifty->config->framework('ApplicationName'))%>
+ <h1 class="title"><% $title %></h1>
+ </div>
+ <& sidebar &>
+ <div id="content">
+ <a name="content"></a>
+ <% Jifty->web->render_messages %>
+ <% $m->content |n%>
+ <div id="keybindings">
+ <script><!--
+ writeKeyBindingLegend();
+ --></script>
+ </div>
+ </div>
+ <div id="jifty-wait-message">Loading...</div>
+</body>
+</html>
+<%args>
+$title => ""
+</%args>
+<%init>
+$m->comp('nav');
+
+</%init>
diff --git a/web/templates/autohandler b/web/templates/autohandler
new file mode 100644
index 0000000..0a4476b
--- /dev/null
+++ b/web/templates/autohandler
@@ -0,0 +1,25 @@
+<%init>
+Jifty->web->handle_request();
+
+if ($m->base_comp->path =~ m|/_elements/|) {
+ # Requesting an internal component by hand -- naughty
+ $m->redirect("/errors/requested_private_component");
+#} elsif (not Jifty->web->current_user->id and $m->request_comp->path !~ m{^/(?:welcome|dhandler|css|js|images|validator\.xml)} ) {
+# # Not logged in, trying to access a protected page
+# $m->notes->{'login-nextpage'} = $m->{top_path};
+# Jifty->web->redirect('/welcome/');
+}
+</%init>
+<%$m->call_next()%>
+<%def .setup_actions>
+<%init>
+Jifty->web->allow_actions(qr/.*/);
+# this method turns around and calls the setup_actions method
+# it's called by Jifty::Web->setup_page_actions.
+my $delegate = $m->fetch_comp($m->next_comp->path);
+if ($delegate and $delegate->method_exists('setup_actions')) {
+ $delegate->call_method('setup_actions');
+}
+
+</%init>
+</%def>
diff --git a/web/templates/create/dhandler b/web/templates/create/dhandler
new file mode 100644
index 0000000..fcbbe89
--- /dev/null
+++ b/web/templates/create/dhandler
@@ -0,0 +1,13 @@
+<%init>
+my $page = $m->dhandler_arg;
+my $action = Jifty->web->new_action( class => 'CreatePage');
+</%init>
+<&|/_elements/wrapper, title => 'New page: '. $page&>
+<% Jifty->web->form->start %>
+<% Jifty->web->form->next_page( url => '/view/'.$page) %>
+<% $action->form_field('name', render_as => 'hidden', default_value => $page) %>
+<% $action->form_field('content')%>
+<% Jifty->web->form->submit( label => 'Save')%>
+<% Jifty->web->form->end %>
+<& /_elements/markup &>
+</&>
diff --git a/web/templates/dhandler b/web/templates/dhandler
new file mode 100644
index 0000000..8a2fe56
--- /dev/null
+++ b/web/templates/dhandler
@@ -0,0 +1,47 @@
+<&| /_elements/wrapper, title => "Something's not quite right" &>
+
+<div id="overview">
+
+<p>You got to a page that we don't think exists. Anyway, the software has logged this error. Sorry about this.</p>
+
+<p><%Jifty->web->link( url => "/", label => 'Go back home...')%></p>
+
+</div>
+</&>
+%# XXX TODO ACTUALLY LOG THIS.
+<%doc>
+Used as a poor man's 404 handler
+</%doc>
+<%init>
+
+# This code loads up any static file and displays it if it would 404 from dynamic content. Failing that, actually 404
+my $file = $m->dhandler_arg;
+my $type = "application/octet-stream";
+if ( $file =~ /\.(gif|png|jpe?g)$/i ) {
+ $type = "image/$1";
+ $type =~ s/jpg/jpeg/gi;
+} elsif ($file =~ /\.css$/i ) {
+ $type ='text/css';
+} elsif ($file =~ /\.js$/i) {
+ $type = 'application/x-javascript';
+}
+my $image = Jifty::Util->absolute_path( Jifty->config->framework('Web')->{'StaticRoot'}
+ || "static" )
+ . "/"
+ . $file;
+
+if ( ( -f $image && -r $image ) ) {
+ $r->header_out( 'Cache-Control' => 'max-age=3600, must-revalidate' );
+ $r->content_type($type);
+ open( FILE, "<$image" ) || die;
+ {
+ local $/ = \16384;
+ $m->out($_) while (<FILE>);
+ close(FILE);
+ }
+ $m->abort;
+}
+
+Jifty->log->error("404: user tried to get to ".$m->dhandler_arg);
+$r->header_out( Status => '404');
+</%init>
diff --git a/web/templates/edit/dhandler b/web/templates/edit/dhandler
new file mode 100644
index 0000000..4a5240d
--- /dev/null
+++ b/web/templates/edit/dhandler
@@ -0,0 +1,16 @@
+<%init>
+my $name = $m->dhandler_arg();
+my $page = Wifty::Model::Page->new();
+$page->load_by_cols( name => $name );
+my $viewer = Jifty->web->new_action( class => 'UpdatePage', record => $page );
+my $top = Jifty->web->navigation;
+$top->child(Show => url => '/view/'.$page->name,, label => 'Show Page', sort_order => 5);
+</%init>
+<&|/_elements/wrapper, title => 'Edit: '.$page->name &>
+<% Jifty->web->form->start %>
+<% Jifty->web->form->next_page( url => '/view/'.$page->name) %>
+<% $viewer->form_field('content') %>
+<% Jifty->web->form->submit( label => 'Save') %>
+<% Jifty->web->form->end%>
+<& /_elements/markup &>
+</&>
diff --git a/web/templates/favicon.ico b/web/templates/favicon.ico
new file mode 100644
index 0000000..e69de29
diff --git a/web/templates/index.html b/web/templates/index.html
new file mode 100644
index 0000000..e5cb700
--- /dev/null
+++ b/web/templates/index.html
@@ -0,0 +1 @@
+% Jifty->web->redirect( '/view/HomePage');
diff --git a/web/templates/pages b/web/templates/pages
new file mode 100644
index 0000000..35f05bc
--- /dev/null
+++ b/web/templates/pages
@@ -0,0 +1,12 @@
+<%init>
+my $pages = Wifty::Model::PageCollection->new();
+$pages->unlimit();
+
+</%init>
+<&|/_elements/wrapper, title => 'These are the pages on your wiki!' &>
+<ul id="pagelist">
+% while (my $page = $pages->next) {
+<li><% Jifty->web->link( label => $page->name, url => '/view/'.$page->name)%></li>
+% }
+</ul>
+</&>
diff --git a/web/templates/recent b/web/templates/recent
new file mode 100644
index 0000000..433fb35
--- /dev/null
+++ b/web/templates/recent
@@ -0,0 +1,14 @@
+<%init>
+my $then = DateTime->from_epoch(epoch => (time - (86400*7)));
+my $pages = Wifty::Model::PageCollection->new();
+$pages->limit( column => 'updated', operator => '>', value => $then->ymd );
+$pages->order_by( column => 'updated', order => 'desc');
+</%init>
+<&|/_elements/wrapper, title => 'Updated this week' &>
+<dl id="recentudates">
+% while (my $page = $pages->next) {
+<dt><% Jifty->web->link( label => $page->name, url => '/view/'.$page->name)%></dt>
+<dd><%$page->updated%></dd>
+% }
+</dl>
+</&>
diff --git a/web/templates/view/dhandler b/web/templates/view/dhandler
new file mode 100644
index 0000000..63c0f18
--- /dev/null
+++ b/web/templates/view/dhandler
@@ -0,0 +1,14 @@
+<%init>
+my $name = $m->dhandler_arg();
+my $page = Wifty::Model::Page->new();
+$page->load_by_cols( name => $name);
+unless ($page->id) {
+ Jifty->web->redirect( '/create/'.$name);
+ # XXX TODO: should this use goto or gosub or whatever we're calling it this week?
+}
+my $top = Jifty->web->navigation;
+$top->child(Edit => url => '/edit/'.$page->name , sort_order => 5);
+</%init>
+<&|/_elements/wrapper, title => $page->name &>
+<% $page->wiki_content |n %>
+</&>
commit ba981322c1fc3a42078619f1d60c2ce53099f93d
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Sun Dec 25 01:30:12 2005 +0000
The 'page' of a revision is a *Page* not a *Revision*
diff --git a/lib/Wifty/Model/Revision.pm b/lib/Wifty/Model/Revision.pm
index 646519b..bd5344e 100644
--- a/lib/Wifty/Model/Revision.pm
+++ b/lib/Wifty/Model/Revision.pm
@@ -2,7 +2,7 @@ package Wifty::Model::Revision::Schema;
use Jifty::DBI::Schema;
column page =>
- refers_to Wifty::Model::Revision;
+ refers_to Wifty::Model::Page;
column content =>
type is 'text',
commit ac379948b2d1e1f77cc4af2a566b3aa0d13be041
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Sun Dec 25 01:30:41 2005 +0000
Switched to asking for autopackaged dependencies
diff --git a/bin/jifty b/bin/jifty
index 4f653a5..0ba9b77 100755
--- a/bin/jifty
+++ b/bin/jifty
@@ -6,6 +6,7 @@ use File::Basename qw(dirname);
BEGIN {
my $dir = dirname(__FILE__);
push @INC, "$dir/../lib";
+ push @INC, "$dir/../../Jifty/deps";
push @INC, "$dir/../../Jifty/lib";
}
commit e71375c9a2572dc8998cbd327bab80770fcdf1fa
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Sun Dec 25 01:30:54 2005 +0000
Turn off admin mode on Wifty and Hiveminder
diff --git a/etc/config.yml b/etc/config.yml
index 5812a3d..ba02778 100644
--- a/etc/config.yml
+++ b/etc/config.yml
@@ -1,4 +1,5 @@
framework:
+ AdminMode: 0
LogConfig: etc/btdt.log4perl.conf
Database:
Driver: Pg
commit 5703cdda805a3234798ba1e72a300147581990f8
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Sun Dec 25 01:31:13 2005 +0000
Use less code (especially the stuff that we can pull from the default app
diff --git a/lib/Wifty/Bootstrap.pm b/lib/Wifty/Bootstrap.pm
index f05c0d4..5026527 100644
--- a/lib/Wifty/Bootstrap.pm
+++ b/lib/Wifty/Bootstrap.pm
@@ -1,15 +1,16 @@
package Wifty::Bootstrap;
use Wifty::Model::Page;
+
sub run {
my $self = shift;
my $index = Wifty::Model::Page->new();
- $index->create( name => 'home',
- content=> 'Welcome to your Wifty');
-
+ $index->create(
+ name => 'HomePage',
+ content => 'Welcome to your Wifty'
+ );
}
-
1;
diff --git a/lib/Wifty/Model/Page.pm b/lib/Wifty/Model/Page.pm
index b6541f8..6af8ae8 100644
--- a/lib/Wifty/Model/Page.pm
+++ b/lib/Wifty/Model/Page.pm
@@ -50,17 +50,16 @@ sub wiki_content {
}
-
sub create {
my $self = shift;
my %args = (@_);
- my $now = DateTime->now();
- $args{'updated'} = $now->ymd." ".$now->hms;
+ my $now = DateTime->now();
+ $args{'updated'} = $now->ymd . " " . $now->hms;
my ($id) = $self->SUPER::create(%args);
- if ($self->id) {
+ if ( $self->id ) {
$self->_add_revision(%args);
}
- return($id);
+ return ($id);
}
=head2 _add_revision
@@ -88,19 +87,22 @@ sub _add_revision {
}
sub set_content {
- my $self = shift;
+ my $self = shift;
my $content = shift;
- my ($val, $msg) = $self->SUPER::set_content($content);
- $self->_add_revision(content =>$content );
- return ($val,$msg);
+ my ( $val, $msg ) = $self->SUPER::set_content($content);
+ $self->_add_revision( content => $content );
+ return ( $val, $msg );
}
sub _set {
my $self = shift;
- my ($val,$msg) = $self->SUPER::_set(@_);
+ my ( $val, $msg ) = $self->SUPER::_set(@_);
my $now = DateTime->now();
- $self->SUPER::_set( column => 'updated', value => $now->ymd." ".$now->hms);
- return ($val,$msg);
+ $self->SUPER::_set(
+ column => 'updated',
+ value => $now->ymd . " " . $now->hms
+ );
+ return ( $val, $msg );
}
1;
diff --git a/web/static/css/base.css b/web/static/css/app-base.css
similarity index 100%
rename from web/static/css/base.css
rename to web/static/css/app-base.css
diff --git a/web/static/css/main.css b/web/static/css/main.css
deleted file mode 100644
index db0e44c..0000000
--- a/web/static/css/main.css
+++ /dev/null
@@ -1 +0,0 @@
- at import "base.css";
diff --git a/web/static/js/behaviour.js b/web/static/js/behaviour.js
deleted file mode 100644
index bc5504f..0000000
--- a/web/static/js/behaviour.js
+++ /dev/null
@@ -1,254 +0,0 @@
-/*
- Behaviour v1.1 by Ben Nolan, June 2005. Based largely on the work
- of Simon Willison (see comments by Simon below).
-
- Description:
-
- Uses css selectors to apply javascript behaviours to enable
- unobtrusive javascript in html documents.
-
- Usage:
-
- var myrules = {
- 'b.someclass' : function(element){
- element.onclick = function(){
- alert(this.innerHTML);
- }
- },
- '#someid u' : function(element){
- element.onmouseover = function(){
- this.innerHTML = "BLAH!";
- }
- }
- };
-
- Behaviour.register(myrules);
-
- // Call Behaviour.apply() to re-apply the rules (if you
- // update the dom, etc).
-
- License:
-
- My stuff is BSD licensed. Not sure about Simon's.
-
- More information:
-
- http://ripcord.co.nz/behaviour/
-
-*/
-
-var Behaviour = {
- list : new Array,
-
- register : function(sheet){
- Behaviour.list.push(sheet);
- },
-
- start : function(){
- Behaviour.addLoadEvent(function(){
- Behaviour.apply();
- });
- },
-
- apply : function(){
- for (h=0;sheet=Behaviour.list[h];h++){
- for (selector in sheet){
- list = document.getElementsBySelector(selector);
-
- if (!list){
- continue;
- }
-
- for (i=0;element=list[i];i++){
- sheet[selector](element);
- }
- }
- }
- },
-
- addLoadEvent : function(func){
- var oldonload = window.onload;
-
- if (typeof window.onload != 'function') {
- window.onload = func;
- } else {
- window.onload = function() {
- oldonload();
- func();
- }
- }
- }
-}
-
-Behaviour.start();
-
-/*
- The following code is Copyright (C) Simon Willison 2004.
-
- document.getElementsBySelector(selector)
- - returns an array of element objects from the current document
- matching the CSS selector. Selectors can contain element names,
- class names and ids and can be nested. For example:
-
- elements = document.getElementsBySelect('div#main p a.external')
-
- Will return an array of all 'a' elements with 'external' in their
- class attribute that are contained inside 'p' elements that are
- contained inside the 'div' element which has id="main"
-
- New in version 0.4: Support for CSS2 and CSS3 attribute selectors:
- See http://www.w3.org/TR/css3-selectors/#attribute-selectors
-
- Version 0.4 - Simon Willison, March 25th 2003
- -- Works in Phoenix 0.5, Mozilla 1.3, Opera 7, Internet Explorer 6, Internet Explorer 5 on Windows
- -- Opera 7 fails
-*/
-
-function getAllChildren(e) {
- // Returns all children of element. Workaround required for IE5/Windows. Ugh.
- return e.all ? e.all : e.getElementsByTagName('*');
-}
-
-document.getElementsBySelector = function(selector) {
- // Attempt to fail gracefully in lesser browsers
- if (!document.getElementsByTagName) {
- return new Array();
- }
- // Split selector in to tokens
- var tokens = selector.split(' ');
- var currentContext = new Array(document);
- for (var i = 0; i < tokens.length; i++) {
- token = tokens[i].replace(/^\s+/,'').replace(/\s+$/,'');;
- if (token.indexOf('#') > -1) {
- // Token is an ID selector
- var bits = token.split('#');
- var tagName = bits[0];
- var id = bits[1];
- var element = document.getElementById(id);
- if (tagName && element.nodeName.toLowerCase() != tagName) {
- // tag with that ID not found, return false
- return new Array();
- }
- // Set currentContext to contain just this element
- currentContext = new Array(element);
- continue; // Skip to next token
- }
- if (token.indexOf('.') > -1) {
- // Token contains a class selector
- var bits = token.split('.');
- var tagName = bits[0];
- var className = bits[1];
- if (!tagName) {
- tagName = '*';
- }
- // Get elements matching tag, filter them for class selector
- var found = new Array;
- var foundCount = 0;
- for (var h = 0; h < currentContext.length; h++) {
- var elements;
- if (tagName == '*') {
- elements = getAllChildren(currentContext[h]);
- } else {
- elements = currentContext[h].getElementsByTagName(tagName);
- }
- for (var j = 0; j < elements.length; j++) {
- found[foundCount++] = elements[j];
- }
- }
- currentContext = new Array;
- var currentContextIndex = 0;
- for (var k = 0; k < found.length; k++) {
- if (found[k].className && found[k].className.match(new RegExp('\\b'+className+'\\b'))) {
- currentContext[currentContextIndex++] = found[k];
- }
- }
- continue; // Skip to next token
- }
- // Code to deal with attribute selectors
- if (token.match(/^(\w*)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/)) {
- var tagName = RegExp.$1;
- var attrName = RegExp.$2;
- var attrOperator = RegExp.$3;
- var attrValue = RegExp.$4;
- if (!tagName) {
- tagName = '*';
- }
- // Grab all of the tagName elements within current context
- var found = new Array;
- var foundCount = 0;
- for (var h = 0; h < currentContext.length; h++) {
- var elements;
- if (tagName == '*') {
- elements = getAllChildren(currentContext[h]);
- } else {
- elements = currentContext[h].getElementsByTagName(tagName);
- }
- for (var j = 0; j < elements.length; j++) {
- found[foundCount++] = elements[j];
- }
- }
- currentContext = new Array;
- var currentContextIndex = 0;
- var checkFunction; // This function will be used to filter the elements
- switch (attrOperator) {
- case '=': // Equality
- checkFunction = function(e) { return (e.getAttribute(attrName) == attrValue); };
- break;
- case '~': // Match one of space seperated words
- checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('\\b'+attrValue+'\\b'))); };
- break;
- case '|': // Match start with value followed by optional hyphen
- checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('^'+attrValue+'-?'))); };
- break;
- case '^': // Match starts with value
- checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) == 0); };
- break;
- case '$': // Match ends with value - fails with "Warning" in Opera 7
- checkFunction = function(e) { return (e.getAttribute(attrName).lastIndexOf(attrValue) == e.getAttribute(attrName).length - attrValue.length); };
- break;
- case '*': // Match ends with value
- checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) > -1); };
- break;
- default :
- // Just test for existence of attribute
- checkFunction = function(e) { return e.getAttribute(attrName); };
- }
- currentContext = new Array;
- var currentContextIndex = 0;
- for (var k = 0; k < found.length; k++) {
- if (checkFunction(found[k])) {
- currentContext[currentContextIndex++] = found[k];
- }
- }
- // alert('Attribute Selector: '+tagName+' '+attrName+' '+attrOperator+' '+attrValue);
- continue; // Skip to next token
- }
-
- if (!currentContext[0]){
- return;
- }
-
- // If we get here, token is JUST an element (not a class or ID selector)
- tagName = token;
- var found = new Array;
- var foundCount = 0;
- for (var h = 0; h < currentContext.length; h++) {
- var elements = currentContext[h].getElementsByTagName(tagName);
- for (var j = 0; j < elements.length; j++) {
- found[foundCount++] = elements[j];
- }
- }
- currentContext = found;
- }
- return currentContext;
-}
-
-/* That revolting regular expression explained
-/^(\w+)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/
- \---/ \---/\-------------/ \-------/
- | | | |
- | | | The value
- | | ~,|,^,$,* or =
- | Attribute
- Tag
-*/
diff --git a/web/static/js/bps_util.js b/web/static/js/bps_util.js
deleted file mode 100644
index 330c8a5..0000000
--- a/web/static/js/bps_util.js
+++ /dev/null
@@ -1,89 +0,0 @@
-// XXX TODO This library should likely be refactored to use behaviour
-
-function focusElementById(id) {
- var e = document.getElementById(id);
- if (e) e.focus();
-}
-
-function openCalWindow(field) {
- var objWindow = window.open('/helpers/calendar.html?field='+field, 'Calendar', 'height=200,width=235,scrollbars=1');
- objWindow.focus();
-}
-
-function updateParentField(field, value) {
- if (window.opener) {
- window.opener.document.getElementById(field).value = value;
- window.close();
- }
-}
-
-function createCalendarLink(input) {
- var e = document.getElementById(input);
- if (e) {
- var link = document.createElement('a');
- link.setAttribute('href', '#');
- link.setAttribute('onclick', "openCalWindow('"+input+"'); return false;");
-
- var text = document.createTextNode('Calendar');
- link.appendChild(text);
-
- var space = document.createTextNode(' ');
-
- e.parentNode.insertBefore(link, e.nextSibling);
- e.parentNode.insertBefore(space, e.nextSibling);
-
- return true;
- }
- return false;
-}
-
-// onload handlers
-
-var onLoadStack = new Array();
-var onLoadLastStack = new Array();
-var onLoadExecuted = 0;
-
-function onLoadHook(commandStr) {
- if(typeof(commandStr) == "string") {
- onLoadStack[onLoadStack.length] = commandStr;
- return true;
- }
- return false;
-}
-
-// some things *really* need to be done after everything else
-function onLoadLastHook(commandStr) {
- if(typeof(commandStr) == "string"){
- onLoadLastStack[onLoadLastStack.length] = commandStr;
- return true;
- }
- return false;
-}
-
-function doOnLoadHooks() {
- if(onLoadExecuted) return;
- for (var x=0; x < onLoadStack.length; x++) {
- eval(onLoadStack[x]);
- }
- for (var x=0; x < onLoadLastStack.length; x++) {
- eval(onLoadLastStack[x]);
- }
- onLoadExecuted = 1;
-}
-
-
-if (typeof window.onload != 'function') {
- window.onload = doOnLoadHooks;
-} else {
- var oldonload = window.onload;
-
- window.onload = function() {
- oldonload();
- doOnLoadHooks();
- }
-}
-
-function jifty_button_click() {
-
-1;
-}
diff --git a/web/static/js/btdt_behaviour.js b/web/static/js/btdt_behaviour.js
deleted file mode 100644
index 7c3f3ec..0000000
--- a/web/static/js/btdt_behaviour.js
+++ /dev/null
@@ -1,36 +0,0 @@
-/* Uses Behaviour v1.0 (behaviour.js); see
- http://ripcord.co.nz/behaviour/
-
- IMPORTANT: if you make DOM changes that mean that an element
- ought to gain or lose a behaviour, call Behaviour.apply()!
- (Actually, that *won't* make something lose a behaviour, so if that's necessary
- you'll need to have an empty "fallback". Ie, if "div#foo a" should have a special
- onclick and other "a" shouldn't, then there ought to be an explicit "a" style
- that sets onclick to a trivial function, if DOM changes will ever happen.)
- (Also, with the current behaviour.js, the order of application of styles is undefined,
- so you can't really do cascading. I've suggested to the author that he change it;
- if he doesn't, but we need it, it's an easy change to make the sheets arrays instead
- of Objects (hashes). For now this can be dealt with by loading multiple sheets (register
- calls), though.)
-*/
-
-
-
-/* 'textarea.bigbox' : function(elt) {
- new Form.Element.Observer( elt.id,
- 1,
- function( element, value ) {
- new Ajax.Updater( elt.id+'-observer',
- '/fragments/parsetext',
- { parameters: Form.Element.getAction(elt).serialize(),
- onComplete: function () { Behaviour.apply() } }
- )
- }
- );
- },
-*/
-
-var myrules = {
- };
-
- Behaviour.register(myrules);
diff --git a/web/static/js/combobox.js b/web/static/js/combobox.js
deleted file mode 100644
index 90fcddf..0000000
--- a/web/static/js/combobox.js
+++ /dev/null
@@ -1,233 +0,0 @@
-function ComboBox_InitWith(n) {
- if ( typeof( window.addEventListener ) != "undefined" ) {
- window.addEventListener("load", ComboBox_Init(n), false);
- } else if ( typeof( window.attachEvent ) != "undefined" ) {
- window.attachEvent("onload", ComboBox_Init(n));
- } else {
- ComboBox_Init(n)();
- }
-}
-function ComboBox_Init(n) {
- return function () {
- if ( ComboBox_UplevelBrowser( n ) ) {
- ComboBox_Load( n );
- }
- }
-}
-function ComboBox_UplevelBrowser( n ) {
- if( typeof( document.getElementById ) == "undefined" ) return false;
- var combo = document.getElementById( n + "_Container" );
- if( combo == null || typeof( combo ) == "undefined" ) return false;
- if( typeof( combo.style ) == "undefined" ) return false;
- if( typeof( combo.innerHTML ) == "undefined" ) return false;
- return true;
-}
-function ComboBox_Load( comboId ) {
- var combo = document.getElementById( comboId + "_Container" );
- var button = document.getElementById( comboId + "_Button" );
- var list = document.getElementById( comboId + "_List" );
- var text = document.getElementById( comboId );
-
-
- combo.List = list;
- combo.Button = button;
- combo.Text = text;
-
- button.Container = combo;
- button.Toggle = ComboBox_ToggleList;
- button.onclick = button.Toggle;
- button.onmouseover = function(e) { this.Container.List.DisableBlur(e); };
- button.onmouseout = function(e) { this.Container.List.EnableBlur(e); };
- button.innerHTML = "\u25BC";
- button.onselectstart = function(e){ return false; };
- button.style.height = ( list.offsetHeight - 4 ) + "px";
-
- text.Container = combo;
- text.TypeDown = ComboBox_TextTypeDown;
- text.KeyAccess = ComboBox_TextKeyAccess;
- text.onkeyup = function(e) { this.KeyAccess(e); this.TypeDown(e); };
- text.style.width = ( list.offsetWidth ) + "px";
-
- list.Container = combo;
- list.Show = ComboBox_ShowList;
- list.Hide = ComboBox_HideList;
- list.EnableBlur = ComboBox_ListEnableBlur;
- list.DisableBlur = ComboBox_ListDisableBlur;
- list.Select = ComboBox_ListItemSelect;
- list.ClearSelection = ComboBox_ListClearSelection;
- list.KeyAccess = ComboBox_ListKeyAccess;
- list.FireTextChange = ComboBox_ListFireTextChange;
- list.onchange = null;
- list.onclick = function(e){ this.Select(e); this.ClearSelection(); this.FireTextChange(); };
- list.onkeyup = function(e) { this.KeyAccess(e); };
- list.EnableBlur(null);
- list.style.position = "absolute";
- list.size = ComboBox_GetListSize( list );
- list.IsShowing = true;
- list.Hide();
-
-}
-function ComboBox_InitEvent( e ) {
- if( typeof( e ) == "undefined" && typeof( window.event ) != "undefined" ) e = window.event;
- if( e == null ) e = new Object();
- return e;
-}
-function ComboBox_ListClearSelection() {
- if ( typeof( this.Container.Text.createTextRange ) == "undefined" ) return;
- var rNew = this.Container.Text.createTextRange();
- rNew.moveStart('character', this.Container.Text.value.length) ;
- rNew.select();
-}
-function ComboBox_GetListSize( theList ) {
- ComboBox_EnsureListSize( theList );
- return theList.listSize;
-}
-function ComboBox_EnsureListSize( theList ) {
- if ( typeof( theList.listSize ) == "undefined" ) {
- if( typeof( theList.getAttribute ) != "undefined" ) {
- if( theList.getAttribute( "listSize" ) != null && theList.getAttribute( "listSize" ) != "" ) {
- theList.listSize = theList.getAttribute( "listSize" );
- return;
- }
- }
- if( theList.options.length > 0 ) {
- theList.listSize = theList.options.length;
- return;
- }
- theList.listSize = 4;
- }
-}
-function ComboBox_ListKeyAccess(e) { //Make enter/space and escape do the right thing :)
- e = ComboBox_InitEvent( e );
- if( e.keyCode == 13 || e.keyCode == 32 ) {
- this.Select();
- return;
- }
- if( e.keyCode == 27 ) {
- this.Hide();
- this.Container.Text.focus();
- return;
- }
-}
-function ComboBox_TextKeyAccess(e) { //Make alt+arrow expand the list
- e = ComboBox_InitEvent( e );
- if( e.altKey && (e.keyCode == 38 || e.keyCode == 40) ) {
- this.Container.List.Show();
- }
-}
-function ComboBox_TextTypeDown(e) { //Make the textbox do a type-down on the list
- e = ComboBox_InitEvent( e );
- var items = this.Container.List.options;
- if( this.value == "" ) return;
- var ctrlKeys = Array( 8, 46, 37, 38, 39, 40, 33, 34, 35, 36, 45, 16, 20 );
- for( var i = 0; i < ctrlKeys.length; i++ ) {
- if( e.keyCode == ctrlKeys[i] ) return;
- }
- for( var i = 0; i < items.length; i++ ) {
- var item = items[i];
- if( item.text.toLowerCase().indexOf( this.value.toLowerCase() ) == 0 ) {
- this.Container.List.selectedIndex = i;
- if ( typeof( this.Container.Text.createTextRange ) != "undefined" ) {
- this.Container.List.Select();
- }
- break;
- }
- }
-}
-function ComboBox_ListFireTextChange() {
- var textOnChange = this.Container.Text.onchange;
- if ( textOnChange != null && typeof(textOnChange) == "function" ) {
- textOnChange();
- }
-}
-function ComboBox_ListEnableBlur(e) {
- this.onblur = this.Hide;
-}
-function ComboBox_ListDisableBlur(e) {
- this.onblur = null;
-}
-function ComboBox_ListItemSelect(e) {
- if( this.options.length > 0 ) {
- var text = this.Container.Text;
- var oldValue = text.value;
- var newValue = this.options[ this.selectedIndex ].value;
- text.value = newValue;
- if ( typeof( text.createTextRange ) != "undefined" ) {
- if (newValue != oldValue) {
- var rNew = text.createTextRange();
- rNew.moveStart('character', oldValue.length) ;
- rNew.select();
- }
- }
- }
- this.Hide();
- this.Container.Text.focus();
-}
-function ComboBox_ToggleList(e) {
- if( this.Container.List.IsShowing == true ) {
- this.Container.List.Hide();
- } else {
- this.Container.List.Show();
- }
-}
-function ComboBox_ShowList(e) {
- if ( !this.IsShowing && !this.disabled ) {
- this.style.width = ( this.Container.offsetWidth ) + "px";
- this.style.top = ( this.Container.offsetHeight + ComboBox_RecursiveOffsetTop(this.Container,true) ) + "px";
- this.style.left = ( ComboBox_RecursiveOffsetLeft(this.Container,true) + 1 ) + "px";
- ComboBox_SetVisibility(this,true);
- this.focus();
- this.IsShowing = true;
- }
-}
-function ComboBox_HideList(e) {
- if( this.IsShowing ) {
- ComboBox_SetVisibility(this,false);
- this.IsShowing = false;
- }
-}
-function ComboBox_SetVisibility(theList,isVisible) {
- var isIE = ( typeof( theList.dataSrc ) != "undefined" ); // dataSrc is an IE-only property which is unlikely to be supported elsewhere
- var ua = navigator.userAgent.toLowerCase();
- var isSafari = (ua.indexOf('safari') != - 1);
- if ( isIE || isSafari) {
- if ( isVisible ) {
- theList.style.visibility = "visible";
- } else {
- theList.style.visibility = "hidden";
- }
- } else {
- if ( isVisible ) {
- theList.style.display = "block";
- } else {
- theList.style.display = "none";
- }
- }
-}
-function ComboBox_RecursiveOffsetTop(thisObject,isFirst) {
- if(thisObject.offsetParent) {
- if ( thisObject.style.position == "absolute" && !isFirst && typeof(document.designMode) != "undefined" ) {
- return 0;
- }
- return (thisObject.offsetTop + ComboBox_RecursiveOffsetTop(thisObject.offsetParent,false));
- } else {
- return thisObject.offsetTop;
- }
-}
-function ComboBox_RecursiveOffsetLeft(thisObject,isFirst) {
- if(thisObject.offsetParent) {
- if ( thisObject.style.position == "absolute" && !isFirst && typeof(document.designMode) != "undefined" ) {
- return 0;
- }
- return (thisObject.offsetLeft + ComboBox_RecursiveOffsetLeft(thisObject.offsetParent,false));
- } else {
- return thisObject.offsetLeft;
- }
-}
-function ComboBox_SimpleAttach(selectElement,textElement) {
- textElement.value = selectElement.options[ selectElement.options.selectedIndex ].value;
- var textOnChange = textElement.onchange;
- if ( textOnChange != null && typeof( textOnChange ) == "function" ) {
- textOnChange();
- }
-}
diff --git a/web/static/js/dom-drag.js b/web/static/js/dom-drag.js
deleted file mode 100644
index 9fb7118..0000000
--- a/web/static/js/dom-drag.js
+++ /dev/null
@@ -1,9 +0,0 @@
-/**************************************************
- * dom-drag.js
- * 09.25.2001
- * www.youngpup.net
- **************************************************
- * 10.28.2001 - fixed minor bug where events
- * sometimes fired off the handle, not the root.
- **************************************************/
-var Drag = { obj : null, init : function(o, oRoot, minX, maxX, minY, maxY, bSwapHorzRef, bSwapVertRef, fXMapper, fYMapper){ o.onmousedown = Drag.start; o.hmode = bSwapHorzRef ? false : true ; o.vmode = bSwapVertRef ? false : true ; o.root = oRoot && oRoot != null ? oRoot : o ; if (o.hmode && isNaN(parseInt(o.root.style.left ))) o.root.style.left = "0px"; if (o.vmode && isNaN(parseInt(o.root.style.top ))) o.root.style.top = "0px"; if (!o.hmode && isNaN(parseInt(o.root.style.right ))) o.root.style.right = "0px"; if (!o.vmode && isNaN(parseInt(o.root.style.bottom))) o.root.style.bottom = "0px"; o.minX = typeof minX != 'undefined' ? minX : null; o.minY = typeof minY != 'undefined' ? minY : null; o.maxX = typeof maxX != 'undefined' ? maxX : null; o.maxY = typeof maxY != 'undefined' ? maxY : null; o.xMapper = fXMapper ? fXMapper : null; o.yMapper = fYMapper ? fYMapper : null; o.root.onDragStart = new Function(); o.root.onDragEnd = new Function(); o.root.onDrag = new Function();},
start : function(e){ var o = Drag.obj = this; e = Drag.fixE(e); var y = parseInt(o.vmode ? o.root.style.top : o.root.style.bottom); var x = parseInt(o.hmode ? o.root.style.left : o.root.style.right ); o.root.onDragStart(x, y); o.lastMouseX = e.clientX; o.lastMouseY = e.clientY; if (o.hmode) { if (o.minX != null) o.minMouseX = e.clientX - x + o.minX; if (o.maxX != null) o.maxMouseX = o.minMouseX + o.maxX - o.minX;} else { if (o.minX != null) o.maxMouseX = -o.minX + e.clientX + x; if (o.maxX != null) o.minMouseX = -o.maxX + e.clientX + x;} if (o.vmode) { if (o.minY != null) o.minMouseY = e.clientY - y + o.minY; if (o.maxY != null) o.maxMouseY = o.minMouseY + o.maxY - o.minY;} else { if (o.minY != null) o.maxMouseY = -o.minY + e.clientY + y; if (o.maxY != null) o.minMouseY = -o.maxY + e.clientY + y;} document.onmousemove = Drag.drag; document.onmouseup = Drag.end; return false;}, drag : function(e){ e = Drag.fixE(e); var o = Drag.obj; var ey = e.clientY; var ex = e.clientX; var
y = parseInt(o.vmode ? o.root.style.top : o.root.style.bottom); var x = parseInt(o.hmode ? o.root.style.left : o.root.style.right ); var nx, ny; if (o.minX != null) ex = o.hmode ? Math.max(ex, o.minMouseX) : Math.min(ex, o.maxMouseX); if (o.maxX != null) ex = o.hmode ? Math.min(ex, o.maxMouseX) : Math.max(ex, o.minMouseX); if (o.minY != null) ey = o.vmode ? Math.max(ey, o.minMouseY) : Math.min(ey, o.maxMouseY); if (o.maxY != null) ey = o.vmode ? Math.min(ey, o.maxMouseY) : Math.max(ey, o.minMouseY); nx = x + ((ex - o.lastMouseX) * (o.hmode ? 1 : -1)); ny = y + ((ey - o.lastMouseY) * (o.vmode ? 1 : -1)); if(o.xMapper){ nx = o.xMapper(y) } else if (o.yMapper) { ny = o.yMapper(x); } Drag.obj.root.style[o.hmode ? "left" : "right"] = nx + "px"; Drag.obj.root.style[o.vmode ? "top" : "bottom"] = ny + "px"; Drag.obj.lastMouseX = ex; Drag.obj.lastMouseY = ey; Drag.obj.root.onDrag(nx, ny); return false;}, end : function(){ document.onmousemove = null; document.onmouseup = null; Drag.
obj.root.onDragEnd( parseInt(Drag.obj.root.style[Drag.obj.hmode ? "left" : "right"]), parseInt(Drag.obj.root.style[Drag.obj.vmode ? "top" : "bottom"])); Drag.obj = null;}, fixE : function(e){ if (typeof e == 'undefined') e = window.event; if (typeof e.layerX == 'undefined') e.layerX = e.offsetX; if (typeof e.layerY == 'undefined') e.layerY = e.offsetY; return e;} };
\ No newline at end of file
diff --git a/web/static/js/ie7/README.txt b/web/static/js/ie7/README.txt
deleted file mode 100644
index e546ce4..0000000
--- a/web/static/js/ie7/README.txt
+++ /dev/null
@@ -1,34 +0,0 @@
-Installation
-------------
-
-Follow these simple instructions to get IE7 working immediately on your server:
-
- * download the latest IE7 ZIP file (https://sourceforge.net/project/showfiles.php?group_id=109983&package_id=119707)
-
- * extract the contents to a directory on your server (keep the folder names used in the ZIP)
-
- * you will now have an IE7 directory on your server
-
- * include the IE7 JavaScript library in the page you wish to test
-
- <!-- compliance patch for microsoft browsers -->
- <!--[if lt IE 7]><script src="/ie7/ie7-standard-p.js" type="text/javascript"></script><![endif]-->
-
- * make sure this also points to the same directory
-
- * open the page in your web browser
-
- * the page should now be IE7 enabled.
-
- * if you are using the PNG solution then be aware that it operates on files
- names "something-trans.png"
-
- * see this page for more configuration and usage options:
- http://dean.edwards.name/IE7/usage/
-
-You may extract the contents of the ZIP file to your hard disk if you do not have access to a web server.
-
-
-Enjoy ;-)
-
-Dean Edwards, 23rd May 2005
diff --git a/web/static/js/ie7/blank.gif b/web/static/js/ie7/blank.gif
deleted file mode 100644
index a4fe2e6..0000000
Binary files a/web/static/js/ie7/blank.gif and /dev/null differ
diff --git a/web/static/js/ie7/ie7-base64.php b/web/static/js/ie7/ie7-base64.php
deleted file mode 100644
index 530392d..0000000
--- a/web/static/js/ie7/ie7-base64.php
+++ /dev/null
@@ -1,7 +0,0 @@
-<?php
-$data = split(";", $_SERVER["REDIRECT_QUERY_STRING"]);
-$type = $data[0];
-$data = split(",", $data[1]);
-header("Content-type: ".$type);
-echo base64_decode($data[1]);
-?>
\ No newline at end of file
diff --git a/web/static/js/ie7/ie7-content.htc b/web/static/js/ie7/ie7-content.htc
deleted file mode 100644
index cc480cb..0000000
--- a/web/static/js/ie7/ie7-content.htc
+++ /dev/null
@@ -1,14 +0,0 @@
-<html>
-<!--
- IE7, version 0.9 (alpha) (2005-08-19)
- Copyright: 2004-2005, Dean Edwards (http://dean.edwards.name/)
- License: http://creativecommons.org/licenses/LGPL/2.1/
--->
-<head>
-<object id="dummy" width="0" height="0"></object>
-<base id="base">
-<style type="text/css">html,body,img{margin:0;}img{vertical-align:top}#dummy{display:inline}</style>
-<script type="text/javascript">public_description=new function(){var l=false;this.ie7_anon=true;this.load=function(o,c,u){if(l)return;l=true;base.href=o.document.URL;dummy.style.cssText=c;var _0=o.parentElement;var _1=Boolean(dummy.currentStyle.display=="inline");function r(){o.runtimeStyle.width=(_1)?image.offsetWidth:"100%";o.runtimeStyle.height=body.offsetHeight};image.onreadystatechange=function(){if(this.readyState=="complete")_2()};image.src=u;function _2(){function copy(p){try{body.style[p]=_0.currentStyle[p]}catch(i){}};for(var j in body.currentStyle)copy(j);body.style.width="";body.style.height="";body.style.border="none";body.style.padding="0";body.style.margin="0";body.style.textIndent="";body.style.position="static";while(_0&&_0.currentStyle.backgroundColor=="transparent"){_0=_0.parentElement}if(_0)document.body.style.backgroundColor=_0.currentStyle.backgroundColor;body.runtimeStyle.cssText=c;body.runtimeStyle.margin="0";if(_1)body.runtimeStyle.width="";r()}}};</
script>
-</head>
-<body><span id="body"><img id="image"></span></body>
-</html>
diff --git a/web/static/js/ie7/ie7-core.js b/web/static/js/ie7/ie7-core.js
deleted file mode 100644
index c3dbcef..0000000
--- a/web/static/js/ie7/ie7-core.js
+++ /dev/null
@@ -1,6 +0,0 @@
-/*
- IE7, version 0.9 (alpha) (2005-08-19)
- Copyright: 2004-2005, Dean Edwards (http://dean.edwards.name/)
- License: http://creativecommons.org/licenses/LGPL/2.1/
-*/
-eval(function(p,a,c,k,e,d){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)d[e(c)]=k[c]||e(c);k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('x(!1M.1j)z 6(){1l{1M.1j=8;4 1W=8.2m=z 26;8.O=6(){7"1j 2q 0.9 (5G)"};4 36=/36/.B(42.41.40);4 1G=(36)?6(m){1M.1G(1j+"\\n\\n"+m)}:1W;4 2t=5F.2t.1g(/5E (\\d\\.\\d)/)[1];4 2z=K.5D!="5C";x(/5B/.B(42.41.40)||2t<5||!/^5A/.B(K.1J.2A))7;4 1H=K.39=="1H";4 1t,5z;4 1J=K.1J,2s,3X,17=K.17;4 5y="!";4 22={};4 1u=1C;1j.2m=6(n,s){x(!22[n]){x(1u)1k("s="+2o(s));22[n]=z s()}};4 R=/^[\\w\\.]+[^:]*$/;6 1I(h,p){x(R.B(h))h=(p||"")+h;7 h};6 2e(h,p){h=1I(h,p);7 h.1d(0,h.3n("/")+1)};4 s=K.3Z[K.3Z.y-1];1l{1k(s.3z)}1i(i){}4 1R=2e(s.5x);4 1F;1l{4 l=(5w()>=5)?"5v":"5u";1F=z 5t(l+".5s")}1i(i){}4 2w={};6 2y(h,p){1l{h=1I(h,p);x(!2w[h]){1F.5r("5q",h,1C);1F.5p();x(1F.3Y==0||1F.3Y==5o){2w[h]=1
F.5n}}}1i(i){1G("2x [1]: 30 5m 5l "+h)}37{7 2w[h]||""}};4 5k=1I("5j.5i",1R);6 1E(V){x(V!=1L){V.2v=13.16.2v;V.12=13.16.12}7 V};1E.12=6(p,c){x(!p)p={};x(!c)c=p.J;x(c=={}.J)c=z 26("8.2v()");c.Y=z 26("7 8");c.Y.16=z 8.Y;c.Y.16.12(p);c.16=z c.Y;c.Y.16.J=c.16.J=c;c.1r=8;c.12=F.32;c.2u=8.2u;7 c};1E.Y=z 26("7 8");1E.Y.16={J:1E,2v:6(){7 F.32.5h.1r.2k(8,F)},12:6(V){x(8==8.J.16&&8.J.12){7 8.J.Y.16.12(V)}D(4 i 5g V){2K(i){1o"J":1o"O":1o"Y":2X}x(2V V[i]=="6"&&V[i]!=8[i]){V[i].1r=8[i]}8[i]=V[i]}x(V.O!=8.O&&V.O!={}.O){V.O.1r=8.O;8.O=V.O}7 8}};6 13(){};8.13=1E.12({J:13,O:6(){7"[5f "+(8.J.2Z||"5e")+"]"},5d:6(1h){7 8.J==1h||1h.2u(8.J)}});13.2Z="13";13.1r=1L;13.2u=6(1h){1f(1h&&1h.1r!=8)1h=1h.1r;7 3J(1h)};13.Y.1r=1E;2a 8.13;4 3A=13.12({J:6(){8.5c=[];8.1p=[]},1s:1W});x(2t<5.5)1k(2y("Z-5b.3a",1R));4 35=1C;1j.1s=6(){1l{x(35)7;35=1H=1c;2s=K.2s;3X=(2z)?2s:1J;x(1K&&1t)1t.2k();15.2k();1n();1G("1u 5a")}1i(e){1G("2x [2]: "+e.38)}};4 1p=[];6 2C(r){1p.11(r)};6 1n(){H.3P();x(1K&&1t)1t.1n();15.1n();D(4 i=0;
i<1p.y;i++)1p[i]()};6 23(){4 E=0,R=1,L=2;4 G=/\\(/g,S=/\\$\\d/,I=/^\\$\\d+$/,T=/([\'"])\\1\\+(.*)\\+\\1\\1$/,3Q=/\\\\./g,Q=/\'/,3W=/\\25[^\\25]*\\25/g;4 1X=8;8.18=6(e,r){x(!r)r="";4 l=(34(2o(e)).1g(G)||"").y+1;x(S.B(r)){x(I.B(r)){r=3e(r.1d(1))-1}1b{4 i=l;4 q=Q.B(34(r))?\'"\':"\'";1f(i)r=r.2S("$"+i--).2p(q+"+a[o+"+i+"]+"+q);r=z 26("a,o","7"+q+r.19(T,"$1")+q)}}3V(e||"/^$/",r,l)};8.1U=6(s){24.y=0;7 3R(3S(s,8.2r).19(z 1Z(1D,8.33?"2I":"g"),3T),8.2r).19(3W,"")};8.59=6(){1D.y=0};4 24=[];4 1D=[];4 3U=6(){7"("+2o(8[E]).1d(1,-1)+")"};1D.O=6(){7 8.2p("|")};6 3V(){F.O=3U;1D[1D.y]=F}6 3T(){x(!F[0])7"";4 i=1,j=0,p;1f(p=1D[j++]){x(F[i]){4 r=p[R];2K(2V r){1o"6":7 r(F,i);1o"58":7 F[r+i]}4 d=(F[i].57(1X.2r)==-1)?"":"\\25"+F[i]+"\\25";7 d+r}1b i+=p[L]}};6 3S(s,e){7 e?s.19(z 1Z("\\\\"+e+"(.)","g"),6(m,c){24[24.y]=c;7 e}):s};6 3R(s,e){4 i=0;7 e?s.19(z 1Z("\\\\"+e,"g"),6(){7 e+(24[i++]||"")}):s};6 34(s){7 s.19(3Q,"")}};23.16={J:23,33:1C,2r:""};13.12(23.16);4 1V=23.12({33:1c});4 H=6(){4 2q="2.0.2"
;4 C=/\\s*,\\s*/;4 H=6(s,14){1l{4 m=[];4 u=F.32.2Q&&!14;4 b=(14)?(14.J==3G)?14:[14]:[K];4 31=3D(s).2S(C),i;D(i=0;i<31.y;i++){s=2R(31[i]);x(3K&&s.1d(0,3).2p("")==" *#"){s=s.1d(2);14=3H([],b,s[1])}1b 14=b;4 j=0,t,f,a,c="";1f(j<s.y){t=s[j++];f=s[j++];c+=t+f;a="";x(s[j]=="("){1f(s[j++]!=")"&&j<s.y){a+=s[j]}a=a.1d(0,-1);c+="("+a+")"}14=(u&&1P[c])?1P[c]:3F(14,t,f,a);x(u)1P[c]=14}m=m.3t(14)}2a H.30;7 m}1i(e){H.30=e;7[]}};H.O=6(){7"6 H() {\\n [2q "+2q+"]\\n}"};4 1P={};H.2Q=1C;H.3P=6(s){x(s){s=2R(s).2p("");2a 1P[s]}1b 1P={}};4 22={};4 1u=1C;H.2m=6(n,s){x(1u)1k("s="+2o(s));22[n]=z s()};H.Y=6(c){7 c?1k(c):8};4 1B={};4 2n={};4 56={1g:/\\[([\\w-]+(\\|[\\w-]+)?)\\s*(\\W?=)?\\s*([^\\]]*)\\]/};4 55=[];1B[" "]=6(r,f,t,n){4 e,i,j;D(i=0;i<f.y;i++){4 s=2l(f[i],t,n);D(j=0;(e=s[j]);j++){x(1q(e)&&2T(e,n))r.11(e)}}};1B["#"]=6(r,f,i){4 e,j;D(j=0;(e=f[j]);j++)x(e.1a==i)r.11(e)};1B["."]=6(r,f,c){c=z 1Z("(^|\\\\s)"+c+"(\\\\s|$)");4 e,i;D(i=0;(e=f[i]);i++)x(c.B(e.2Z))r.11(e)};1B[":"]=6(r,f,p,a){4 t=2n[
p],e,i;x(t)D(i=0;(e=f[i]);i++)x(t(e,a))r.11(e)};2n["21"]=6(e){4 d=2U(e);x(d.2Y)D(4 i=0;i<d.2Y.y;i++){x(d.2Y[i]==e)7 1c}};2n["2N"]=6(e){};4 1q=6(e){7(e&&e.3B==1&&e.2P!="!")?e:1L};4 3N=6(e){1f(e&&(e=e.54)&&!1q(e))2X;7 e};4 2W=6(e){1f(e&&(e=e.53)&&!1q(e))2X;7 e};4 3L=6(e){7 1q(e.3O)||2W(e.3O)};4 52=6(e){7 1q(e.3M)||3N(e.3M)};4 51=6(e){4 c=[];e=3L(e);1f(e){c.11(e);e=2W(e)}7 c};4 3K=1c;4 2O=6(e){4 d=2U(e);7(2V d.3I=="50")?/\\.4Z$/i.B(d.4Y):3J(d.3I=="4X 4W")};4 2U=6(e){7 e.4V||e.K};4 2l=6(e,t){7(t=="*"&&e.1A)?e.1A:e.2l(t)};4 4U=6(e,t,n){x(t=="*")7 1q(e);x(!2T(e,n))7 1C;x(!2O(e))t=t.4T();7 e.2P==t};4 2T=6(e,n){7!n||(n=="*")||(e.4S==n)};4 4R=6(e){7 e.4Q};6 3H(r,f,1a){4 m,i,j;D(i=0;i<f.y;i++){x(m=f[i].1A.4P(1a)){x(m.1a==1a)r.11(m);1b x(m.y!=1L){D(j=0;j<m.y;j++){x(m[j].1a==1a)r.11(m[j])}}}}7 r};x(![].11)3G.16.11=6(){D(4 i=0;i<F.y;i++){8[8.y]=F[i]}7 8.y};4 N=/\\|/;6 3F(14,t,f,a){x(N.B(f)){f=f.2S(N);a=f[0];f=f[1]}4 r=[];x(1B[t]){1B[t](r,14,f,a)}7 r};4 S=/^[^\\s>+~]/;4 3E=/[\\s#.:>+~()@]
|[^\\s#.:>+~()@]+/g;6 2R(s){x(S.B(s))s=" "+s;7 s.1g(3E)||[]};4 W=/\\s*([\\s>+~(),]|^|$)\\s*/g;4 I=/([\\s>+~,]|[^(]\\+|^)([#.:@])/g;4 3D=6(s){7 s.19(W,"$1").19(I,"$1*$2")};4 1y={O:6(){7"\'"},1g:/^(\'[^\']*\')|("[^"]*")$/,B:6(s){7 8.1g.B(s)},18:6(s){7 8.B(s)?s:8+s+8},3C:6(s){7 8.B(s)?s.1d(1,-1):s}};4 1N=6(t){7 1y.3C(t)};4 E=/([\\/()[\\]?{}|*+-])/g;6 4O(s){7 s.19(E,"\\\\$1")};1u=1c;7 H}();H.2Q=1c;H.2m("Z",6(){1q=6(e){7(e&&e.3B==1&&e.2P!="!"&&!e.3d)?e:1L}});H.Y("1N=F[1]",3k);4 1K=!H.Y("2O(F[1])",1J);4 2h=":21{Z-21:21}:2N{Z-21:2N}"+(1K?"":"*{4N:0}");4 15=z(3A.12({2F:z 1V,1O:"",1w:"",2L:[],1s:6(){8.2M();8.2g()},2g:6(){15.1Y.X=2h+8.1O+8.1w},3y:6(){4 20=K.2l("1e"),s;D(4 i=20.y-1;(s=20[i]);i--){x(!s.2H&&!s.Z){8.2L.11(s.3z)}}},2k:6(){8.3y();8.2g();z 28("1O");8.3u()},3w:6(e,r){8.2F.18(e,r)},1n:6(){4 R=/3v\\d+/g;4 s=2h.1g(/[{,]/g).y;4 20=s+(8.1O.X.1g(/\\{/g)||"").y;4 3x=8.1Y.4M,r;4 2j,c,2i,e,i,j,k,1a;D(i=s;i<20;i++){r=3x[i];x(r&&(2j=r.1e.X.1g(R))){2i=H(r.4L);x(2i.y)D(j=0;j<2j.y;j++){1a=
2j[j];c=15.1p[1a.1d(10)][2];D(k=0;(e=2i[k]);k++){x(e.1v[1a])c(e)}}}}},2C:6(p,t,h,r){t=z 1Z("([{;\\\\s])"+p+"\\\\s*:\\\\s*"+t+"[^;}]*");4 i=8.1p.y;x(r)r=p+":"+r;8.3w(t,6(m,o){7(r?m[o+1]+r:m[o])+";Z-"+m[o].1d(1)+";3v"+i+":1"});8.1p.11(F);7 i},1N:6(s){7 s.X||""},2M:6(){x(1H||!1K)K.2M();1b K.4K("<1e Z=1c></1e>");8.1Y=17[17.y-1];8.1Y.Z=1c;8.1Y.X=2h},3u:6(){D(4 i=0;i<17.y;i++){x(!17[i].Z&&17[i].X){17[i].X=""}}}}));6 28(m){8.1z=m;8.1S();15[m]=8;15.2g()};13.12({J:28,O:6(){7"@1z "+8.1z+"{"+8.X+"}"},1n:1W,1S:6(){8.X="";8.1N();8.3m();8.X=3j(8.X);f={}},1N:6(){4 3r=[].3t(15.2L);4 M=/@1z\\s+([^{]*)\\{([^@]+\\})\\s*\\}/2I;4 A=/\\4J\\b|^$/i,S=/\\4I\\b/i,P=/\\4H\\b/i;6 3q(c,m){2f.v=m;7 c.19(M,2f)};6 2f(4G,m,c){m=2J(m);2K(m){1o"1O":1o"1w":x(m!=2f.v)7"";1o"1A":7 c}7""};6 2J(m){x(A.B(m))7"1A";1b x(S.B(m))7(P.B(m))?"1A":"1O";1b x(P.B(m))7"1w"};4 1X=8;6 2G(s,p,m,l){4 c="";x(!l){m=2J(s.1z);l=0}x(m=="1A"||m==1X.1z){x(l<3){D(4 i=0;i<s.3s.y;i++){c+=2G(s.3s[i],2e(s.2d,p),m,l+1)}}c+=3l(s.2d?3p(s,p):3r.
3h()||"");c=3q(c,1X.1z)}7 c};4 f={};6 3p(s,p){4 u=1I(s.2d,p);x(f[u])7"";f[u]=(s.2H)?"":3o(15.1N(s,p),2e(s.2d,p));7 f[u]};4 U=/(4F\\s*\\(\\s*[\'"]?)([\\w\\.]+[^:\\)]*[\'"]?\\))/2I;6 3o(c,p){7 c.19(U,"$1"+p.1d(0,p.3n("/")+1)+"$2")};D(4 i=0;i<17.y;i++){x(!17[i].2H&&!17[i].Z){8.X+=2G(17[i])}}},3m:6(){8.X=15.2F.1U(8.X)},1n:1W});4 1y=H.Y("1y");4 2b=[];6 3l(c){7 1x.1U(2c.1U(c))};6 2E(m,o){7 1y+(2b.11(m[o])-1)+1y};6 3k(v){7 1y.B(v)?1k(2b[1k(v)]):v};4 1x=z 1V;1x.18(/\\/\\*[^*]*\\*+([^\\/][^*]*\\*+)*\\//);1x.18(/\'[^\']*\'/,2E);1x.18(/"[^"]*"/,2E);1x.18(/\\s+/," ");1x.18(/@(4E|4D)[^;\\n]+[;\\n]|<!\\-\\-|\\-\\->/);4 2c=z 1V;2c.18(/\\\\\'/,"\\\\4C");2c.18(/\\\\"/,"\\\\4B");4 2D=z 1V;2D.18(/\'(\\d+)\'/,3i);6 3j(c){7 2D.1U(c)};6 3i(m,o){7 2b[m[o+1]]};4 2B=[];6 4A(h){2C(h);1Q(1M,"4z",h)};6 1Q(e,t,h){e.4y(t,h);2B.11(F)};6 3g(e,t,h){1l{e.4x(t,h)}1i(i){}};1Q(1M,"4w",6(){4 h;1f(h=2B.3h()){3g(h[0],h[1],h[2])}});6 4v(h,e,c){x(!h.29)h.29={};x(c)h.29[e.2A]=e;1b 2a h.29[e.2A];7 c};1Q(1M,"4u",6(){x(
!15.1w)z 28("1w");15.1w.1n()});4 3f=/^\\d+(4t)?$/i;4 4s=/^\\d+%$/;4 4r=6(e,v){x(3f.B(v))7 3e(v);4 s=e.1e.1m;4 r=e.1T.1m;e.1T.1m=e.1v.1m;e.1e.1m=v||0;v=e.1e.4q;e.1e.1m=s;e.1T.1m=r;7 v};6 4p(t){4 e=K.4o(t||"4n");e.1e.X="3c:4m;4l:0;4k:4j;4i:4h;4g:4f(0 0 0 0);1m:-4e";e.3d=1c;7 e};4 27="Z-";6 4d(e){7 e.1v["Z-3c"]=="4c"};6 4b(e,p){7 e.1v[27+p]||e.1v[p]};6 4a(e,p,v){x(e.1v[27+p]==1L){e.1T[27+p]=e.1v[p]}e.1T[p]=v};6 49(o,c,u){4 t=48(6(){1l{x(!o.1S)7;o.1S(o,c,u);3b(t)}1i(i){3b(t)}},10)};1u=1c;x(2z)1k(2y("Z-47.3a",1R));15.1s();x(1K&&1t)1t.1s();x(1H)1j.1s();1b{1J.46(1I("Z-1S.45",1R));1Q(K,"44",6(){x(K.39=="1H")43(1j.1s,0)})}}1i(e){1G("2x [0]: "+e.38)}37{}};',62,353,'||||var||function|return|this|||||||||||||||||||||||||if|length|new||test||for||arguments||cssQuery||constructor|document||||toString|||||||that||cssText|valueOf|ie7||push|specialize|Common|fr|ie7CSS|prototype|styleSheets|add|replace|id|else|true|slice|style|while|match|klass|catch|IE7|eval|try|left|recalc|case|recalcs|this
Element|ancestor|init|ie7HTML|loaded|currentStyle|print|encoder|Quote|media|all|selectors|false|_0|ICommon|httpRequest|alert|complete|makePath|documentElement|isHTML|null|window|getText|screen|cache|addEventHandler|path|load|runtimeStyle|exec|Parser|DUMMY|self|styleSheet|RegExp|st|link|modules|ParseMaster|_1|x01|Function|_2|StyleSheet|elements|delete|_3|safeString|href|getPath|_4|refresh|HEADER|el|ca|apply|getElementsByTagName|addModule|pseudoClasses|String|join|version|escapeChar|body|appVersion|ancestorOf|inherit|_5|Error|loadFile|quirksMode|uniqueID|_6|addRecalc|decoder|_7|parser|_8|disabled|gi|_9|switch|styles|createStyleSheet|visited|isXML|tagName|caching|_10|split|compareNamespace|getDocument|typeof|nextElementSibling|continue|links|className|error|se|callee|ignoreCase|_11|_12|ie7_debug|finally|description|readyState|js|clearInterval|position|ie7_anon|parseInt|PIXEL|removeEventHandler|pop|_13|decode|getString|_14|parse|lastIndexOf|_15|_16|_17|_18|imports|concat|trash|i
e7_recalc|addFix|ru|getInlineStyles|innerHTML|Fix|nodeType|remove|parseSelector|ST|select|Array|_19|mimeType|Boolean|isMSIE|firstElementChild|lastChild|previousElementSibling|firstChild|clearCache|ES|_20|_21|_22|_23|_24|DE|viewport|status|scripts|search|location|top|setTimeout|onreadystatechange|htc|addBehavior|quirks|setInterval|addTimer|setOverrideStyle|getDefinedStyle|fixed|isFixed|9999|rect|clip|none|border|block|display|padding|absolute|object|createElement|createTempElement|pixelLeft|getPixelValue|PERCENT|px|onbeforeprint|register|onunload|detachEvent|attachEvent|onresize|addResize|x22|x27|import|namespace|url|ma|bprint|bscreen|ball|write|selectorText|rules|margin|regEscape|item|innerText|getTextContent|scopeName|toUpperCase|compareTagName|ownerDocument|Document|XML|URL|xml|unknown|childElements|lastElementChild|nextSibling|previousSibling|attributeSelectors|AttributeSelector|indexOf|number|reset|successfully|ie5|fixes|instanceOf|Object|common|in|caller|gif|blank|BLANK
_GIF|file|loading|responseText|200|send|GET|open|XMLHTTP|ActiveXObject|Microsoft|Msxml2|ScriptEngineMajorVersion|src|ANON|ie7Layout|ms_|ie7_off|CSS1Compat|compatMode|MSIE|navigator|alpha'.split('|'),0,{}))
diff --git a/web/static/js/ie7/ie7-css-strict.js b/web/static/js/ie7/ie7-css-strict.js
deleted file mode 100644
index 0c7e330..0000000
--- a/web/static/js/ie7/ie7-css-strict.js
+++ /dev/null
@@ -1,6 +0,0 @@
-/*
- IE7, version 0.9 (alpha) (2005-08-19)
- Copyright: 2004-2005, Dean Edwards (http://dean.edwards.name/)
- License: http://creativecommons.org/licenses/LGPL/2.1/
-*/
-IE7.addModule("ie7-css-strict",function(){if(!modules["ie7-css2-selectors"])return;StyleSheet.prototype.specialize({parse:function(){this.inherit();var r=[].concat(this.rules);r.sort(ie7CSS.Rule.compare);this.cssText=r.join("\n")},createRule:function(s,c){var m;if(m=s.match(ie7CSS.PseudoElement.MATCH))return new ie7CSS.PseudoElement(m[1],m[2],c);else if(m=s.match(ie7CSS.DynamicRule.MATCH))return new ie7CSS.DynamicRule(s,m[1],m[2],m[3],c);else return new ie7CSS.Rule(s,c)}});ie7CSS.specialize({apply:function(){this.inherit();this.Rule.MATCH=/([^{}]+)(\{[^{}]*\})/g}});ie7CSS.Rule.compare=function(r1,r2){return r1.specificity-r2.specificity};var N=[],I=/#/g,C=/[.:\[]/g,T=/^\w|[\s>+~]\w/g;ie7CSS.Rule.score=function(s){return(s.match(I)||N).length*10000+(s.match(C)||N).length*100+(s.match(T)||N).length};ie7CSS.Rule.simple=function(){return""};ie7CSS.Rule.prototype.specialize({specificity:0,init:function(){this.specificity=ie7CSS.Rule.score(this.selector)}})});
diff --git a/web/static/js/ie7/ie7-css2-selectors.js b/web/static/js/ie7/ie7-css2-selectors.js
deleted file mode 100644
index bb08da3..0000000
--- a/web/static/js/ie7/ie7-css2-selectors.js
+++ /dev/null
@@ -1,6 +0,0 @@
-/*
- IE7, version 0.9 (alpha) (2005-08-19)
- Copyright: 2004-2005, Dean Edwards (http://dean.edwards.name/)
- License: http://creativecommons.org/licenses/LGPL/2.1/
-*/
-IE7.addModule("ie7-css2-selectors",function(){cssQuery.addModule("css-level2",function(){selectors[">"]=function(r,f,t,n){var e,i,j;for(i=0;i<f.length;i++){var s=childElements(f[i]);for(j=0;(e=s[j]);j++)if(compareTagName(e,t,n))r.push(e)}};selectors["+"]=function(r,f,t,n){for(var i=0;i<f.length;i++){var e=nextElementSibling(f[i]);if(e&&compareTagName(e,t,n))r.push(e)}};selectors["@"]=function(r,f,a){var t=attributeSelectors[a].test;var e,i;for(i=0;(e=f[i]);i++)if(t(e))r.push(e)};pseudoClasses["first-child"]=function(e){return!previousElementSibling(e)};pseudoClasses["lang"]=function(e,c){c=new RegExp("^"+c,"i");while(e&&!e.getAttribute("lang"))e=e.parentNode;return e&&c.test(e.getAttribute("lang"))};AttributeSelector.NS_IE=/\\:/g;AttributeSelector.PREFIX="@";AttributeSelector.tests={};AttributeSelector.replace=function(m,a,n,c,v){var k=this.PREFIX+m;if(!attributeSelectors[k]){a=this.create(a,c||"",v||"");attributeSelectors[k]=a;attributeSelectors.push(a)}return attributeSele
ctors[k].id};AttributeSelector.parse=function(s){s=s.replace(this.NS_IE,"|");var m;while(m=s.match(this.match)){var r=this.replace(m[0],m[1],m[2],m[3],m[4]);s=s.replace(this.match,r)}return s};AttributeSelector.create=function(p,t,v){var a={};a.id=this.PREFIX+attributeSelectors.length;a.name=p;t=this.tests[t];t=t?t(this.getAttribute(p),getText(v)):false;a.test=new Function("e","return "+t);return a};AttributeSelector.getAttribute=function(n){switch(n.toLowerCase()){case"id":return"e.id";case"class":return"e.className";case"for":return"e.htmlFor";case"href":if(isMSIE){return"String((e.outerHTML.match(/href=\\x22?([^\\s\\x22]*)\\x22?/)||[])[1]||'')"}}return"e.getAttribute('"+n.replace(N,":")+"')"};AttributeSelector.tests[""]=function(a){return a};AttributeSelector.tests["="]=function(a,v){return a+"=="+Quote.add(v)};AttributeSelector.tests["~="]=function(a,v){return"/(^| )"+regEscape(v)+"( |$)/.test("+a+")"};AttributeSelector.tests["|="]=function(a,v){return"/^"+regEscape(v)+"
(-|$)/.test("+a+")"};var _6=parseSelector;parseSelector=function(s){return _6(AttributeSelector.parse(s))}});var AttributeSelector=cssQuery.valueOf("AttributeSelector");var H=/a(#[\w-]+)?(\.[\w-]+)?:(hover|active)/i;var B1=/\s*\{\s*/,B2=/\s*\}\s*/,C=/\s*\,\s*/;var F=/(.*)(:first-(line|letter))/;StyleSheet.prototype.specialize({parse:function(){this.inherit();var o=ie7CSS.rules.length;var ru=this.cssText.split(B2),r;var se,c,i,j;for(i=0;i<ru.length;i++){r=ru[i].split(B1);se=r[0].split(C);c=r[1];for(j=0;j<se.length;j++){se[j]=c?this.createRule(se[j],c):""}ru[i]=se.join("\n")}this.cssText=ru.join("\n");this.rules=ie7CSS.rules.slice(o)},recalc:function(){var r,i;for(i=0;(r=this.rules[i]);i++)r.recalc()},createRule:function(s,c){if(ie7CSS.UNKNOWN.test(s)){var m;if(m=s.match(PseudoElement.MATCH)){return new PseudoElement(m[1],m[2],c)}else if(m=s.match(DynamicRule.MATCH)){if(!isHTML||!H.test(m)||DynamicRule.COMPLEX.test(m)){return new DynamicRule(s,m[1],m[2],m[3],c)}}else return ne
w Rule(s,c)}return s+" {"+c+"}"}});ie7CSS.specialize({rules:[],pseudoClasses:cssQuery.valueOf("pseudoClasses"),dynamicPseudoClasses:{},cache:cssQuery.valueOf("cache"),Rule:Rule,DynamicRule:DynamicRule,PseudoElement:PseudoElement,DynamicPseudoClass:DynamicPseudoClass,apply:function(){var p=this.pseudoClasses+"|before|after|"+this.dynamicPseudoClasses;p=p.replace(/(link|visited)\|/g,"");this.UNKNOWN=new RegExp("[>+~\[]|([:.])[\\w-()]+\\1|:("+p+")");var c="[^\\s(]+\\s*[+~]|@\\d+|:(";Rule.COMPLEX=new RegExp(c+p+")","g");DynamicRule.COMPLEX=new RegExp(c+this.pseudoClasses+")","g");DynamicRule.MATCH=new RegExp("(.*):("+this.dynamicPseudoClasses+")(.*)");PseudoElement.MATCH=/(.*):(before|after).*/;this.inherit()},recalc:function(){this.screen.recalc();this.inherit()},getText:function(s,p){return httpRequest?(loadFile(s.href,p)||s.cssText):this.inherit(s)},addEventHandler:function(e,t,h){addEventHandler(e,t,h)}});function Rule(s,c){this.id=ie7CSS.rules.length;this.className=Rule.PRE
FIX+this.id;s=(s).match(F)||s||"*";this.selector=s[1]||s;this.selectorText=Rule.simple(this.selector)+"."+this.className+(s[2]||"");this.cssText=c;this.MATCH=new RegExp("\\s"+this.className+"(\\s|$)","g");ie7CSS.rules.push(this);this.init()};Common.specialize({constructor:Rule,toString:function(){return this.selectorText+" {"+this.cssText+"}"},init:DUMMY,add:function(e){e.className+=" "+this.className},remove:function(e){e.className=e.className.replace(this.MATCH,"$1")},recalc:function(){var m=ie7CSS.cache[" *."+this.className]=cssQuery(this.selector);for(i=0;i<m.length;i++)this.add(m[i])}});Rule.PREFIX="ie7_class";Rule.CHILD=/>/g;Rule.simple=function(s){s=AttributeSelector.parse(s);return s.replace(this.COMPLEX,"").replace(this.CHILD," ")};function DynamicRule(s,a,d,t,c){this.attach=a||"*";this.dynamicPseudoClass=ie7CSS.dynamicPseudoClasses[d];this.target=t;this.inherit(s,c)};Rule.specialize({constructor:DynamicRule,recalc:function(){var m=cssQuery(this.attach);for(var i=0;
i<m.length;i++){var t=(this.target)?cssQuery(this.target,m[i]):[m[i]];if(t.length)this.dynamicPseudoClass.apply(m[i],t,this)}}});var A=/^attr/;var U=/^url\s*\(\s*([^)]*)\)$/;var M={before0:"beforeBegin",before1:"afterBegin",after0:"afterEnd",after1:"beforeEnd"};var _5=makePath("ie7-content.htc",path)+"?";HEADER+=".ie7_anon{display:none}";function PseudoElement(s,p,c){this.position=p;var co=c.match(PseudoElement.CONTENT),m,e;if(co){co=co[1];m=co.split(/\s+/);for(var i=0;(e=m[i]);i++){m[i]=A.test(e)?{attr:e.slice(5,-1)}:(e.charAt(0)=="'")?getString(e):decode(e)}co=m}this.content=co;this.inherit(s,decode(c))};Rule.specialize({constructor:PseudoElement,toString:function(){return"."+this.className+"{display:inline}"},init:function(){this.match=cssQuery(this.selector);for(var i=0;i<this.match.length;i++){var r=this.match[i].runtimeStyle;if(!r[this.position])r[this.position]={cssText:""};r[this.position].cssText+=";"+this.cssText;if(this.content!=null)r[this.position].content=this.
content}},recalc:function(){if(this.content==null)return;for(var i=0;i<this.match.length;i++){this.create(this.match[i])}},create:function(t){var g=t.runtimeStyle[this.position];if(g){var c=[].concat(g.content||"");for(var j=0;j<c.length;j++){if(typeof c[j]=="object"){c[j]=t.getAttribute(c[j].attr)}}c=c.join("");var u=c.match(U);var h=PseudoElement[u?"OBJECT":"ANON"].replace(/%1/,this.className);var cs=g.cssText.replace(/'/g,'"');var po=M[this.position+Number(t.canHaveChildren)];if(u){var p=document.createElement(h);t.insertAdjacentElement(po,p);p.data=_5;addTimer(p,cs,Quote.remove(u[1]))}else{h=h.replace(/%2/,cs).replace(/%3/,c);t.insertAdjacentHTML(po,h)}t.runtimeStyle[this.position]=null}}});PseudoElement.CONTENT=/content\s*:\s*([^;]*)(;|$)/;PseudoElement.OBJECT="<object class='ie7_anon %1' ie7_anon width=100% height=0 type=text/x-scriptlet>";PseudoElement.ANON="<ie7:! class='ie7_anon %1' ie7_anon style='%2'>%3</ie7:!>";function DynamicPseudoClass(n,a){this.name=n;this.ap
ply=a;this.instances={};ie7CSS.dynamicPseudoClasses[n]=this};Common.specialize({constructor:DynamicPseudoClass,register:function(i){var c=i[2];i.id=c.id+i[0].uniqueID;if(!this.instances[i.id]){var t=i[1],j;for(j=0;j<t.length;j++)c.add(t[j]);this.instances[i.id]=i}},unregister:function(i){if(this.instances[i.id]){var c=i[2];var t=i[1],j;for(j=0;j<t.length;j++)c.remove(t[j]);delete this.instances[i.id]}}});ie7CSS.pseudoClasses.toString=function(){var t=[],p;for(p in this){if(this[p].length>1)p+="\\([^)]*\\)";t.push(p)}return t.join("|")};ie7CSS.pseudoClasses["link"]=function(e){return e.currentStyle["ie7-link"]=="link"};ie7CSS.pseudoClasses["visited"]=function(e){return e.currentStyle["ie7-link"]=="visited"};var _4=(appVersion<5.5)?"onmouseover":"onmouseenter";var _3=(appVersion<5.5)?"onmouseout":"onmouseleave";ie7CSS.dynamicPseudoClasses.toString=ie7CSS.pseudoClasses.toString;var _0=new DynamicPseudoClass("hover",function(e){var i=arguments;ie7CSS.addEventHandler(e,_4,functio
n(){_0.register(i)});ie7CSS.addEventHandler(e,_3,function(){_0.unregister(i)})});var _1=new DynamicPseudoClass("focus",function(e){var i=arguments;ie7CSS.addEventHandler(e,"onfocus",function(){_1.unregister(i);_1.register(i)});ie7CSS.addEventHandler(e,"onblur",function(){_1.unregister(i)});if(e==document.activeElement){_1.register(i)}});var _2=new DynamicPseudoClass("active",function(e){var i=arguments;ie7CSS.addEventHandler(e,"onmousedown",function(){_2.register(i)})});addEventHandler(document,"onmouseup",function(){var i=_2.instances,j;for(j in i)_2.unregister(i[j]);i=_0.instances;for(j in i)if(!i[j][0].contains(event.srcElement))_0.unregister(i[j])});ICommon(AttributeSelector);AttributeSelector.specialize({getAttribute:function(n){switch(n.toLowerCase()){case"class":return"e.className.replace(/\\b\\s*ie7_class\\d+/g,'')";case"src":return"(e.pngSrc||e.src)"}return this.inherit(n)}});encoder.add(/::/,":");safeString.add(/\\([\da-fA-F]{1,4})/,function(m,o){m=m[o+1];return"\\
u"+"0000".slice(m.length)+m})});
diff --git a/web/static/js/ie7/ie7-css3-selectors.js b/web/static/js/ie7/ie7-css3-selectors.js
deleted file mode 100644
index 7337b82..0000000
--- a/web/static/js/ie7/ie7-css3-selectors.js
+++ /dev/null
@@ -1,6 +0,0 @@
-/*
- IE7, version 0.9 (alpha) (2005-08-19)
- Copyright: 2004-2005, Dean Edwards (http://dean.edwards.name/)
- License: http://creativecommons.org/licenses/LGPL/2.1/
-*/
-IE7.addModule("ie7-css3-selectors",function(){cssQuery.addModule("css-level3",function(){selectors["~"]=function(r,f,t,n){var e,i;for(i=0;(e=f[i]);i++){while(e=nextElementSibling(e)){if(compareTagName(e,t,n))r.push(e)}}};pseudoClasses["contains"]=function(e,t){t=new RegExp(regEscape(getText(t)));return t.test(getTextContent(e))};pseudoClasses["root"]=function(e){return e==getDocument(e).documentElement};pseudoClasses["empty"]=function(e){var n,i;for(i=0;(n=e.childNodes[i]);i++){if(thisElement(n)||n.nodeType==3)return false}return true};pseudoClasses["last-child"]=function(e){return!nextElementSibling(e)};pseudoClasses["only-child"]=function(e){e=e.parentNode;return firstElementChild(e)==lastElementChild(e)};pseudoClasses["not"]=function(e,s){var n=cssQuery(s,getDocument(e));for(var i=0;i<n.length;i++){if(n[i]==e)return false}return true};pseudoClasses["nth-child"]=function(e,a){return nthChild(e,a,previousElementSibling)};pseudoClasses["nth-last-child"]=function(e,a){return
nthChild(e,a,nextElementSibling)};pseudoClasses["target"]=function(e){return e.id==location.hash.slice(1)};pseudoClasses["checked"]=function(e){return e.checked};pseudoClasses["enabled"]=function(e){return e.disabled===false};pseudoClasses["disabled"]=function(e){return e.disabled};pseudoClasses["indeterminate"]=function(e){return e.indeterminate};AttributeSelector.tests["^="]=function(a,v){return"/^"+regEscape(v)+"/.test("+a+")"};AttributeSelector.tests["$="]=function(a,v){return"/"+regEscape(v)+"$/.test("+a+")"};AttributeSelector.tests["*="]=function(a,v){return"/"+regEscape(v)+"/.test("+a+")"};function nthChild(e,a,t){switch(a){case"n":return true;case"even":a="2n";break;case"odd":a="2n+1"}var ch=childElements(e.parentNode);function _5(i){var i=(t==nextElementSibling)?ch.length-i:i-1;return ch[i]==e};if(!isNaN(a))return _5(a);a=a.split("n");var m=parseInt(a[0]);var s=parseInt(a[1]);if((isNaN(m)||m==1)&&s==0)return true;if(m==0&&!isNaN(s))return _5(s);if(isNaN(s))s=0;var c
=1;while(e=t(e))c++;if(isNaN(m)||m==1)return(t==nextElementSibling)?(c<=s):(s>=c);return(c%m)==s}});var firstElementChild=cssQuery.valueOf("firstElementChild");ie7CSS.pseudoClasses["root"]=function(e){return(e==viewport)||(!isHTML&&e==firstElementChild(body))};var _4=new ie7CSS.DynamicPseudoClass("checked",function(e){if(typeof e.checked!="boolean")return;var i=arguments;ie7CSS.addEventHandler(e,"onpropertychange",function(){if(event.propertyName=="checked"){if(e.checked)_4.register(i);else _4.unregister(i)}});if(e.checked)_4.register(i)});var _3=new ie7CSS.DynamicPseudoClass("enabled",function(e){if(typeof e.disabled!="boolean")return;var i=arguments;ie7CSS.addEventHandler(e,"onpropertychange",function(){if(event.propertyName=="disabled"){if(!e.isDisabled)_3.register(i);else _3.unregister(i)}});if(!e.isDisabled)_3.register(i)});var _2=new ie7CSS.DynamicPseudoClass("disabled",function(e){if(typeof e.disabled!="boolean")return;var i=arguments;ie7CSS.addEventHandler(e,"onprope
rtychange",function(){if(event.propertyName=="disabled"){if(e.isDisabled)_2.register(i);else _2.unregister(i)}});if(e.isDisabled)_2.register(i)});var _1=new ie7CSS.DynamicPseudoClass("indeterminate",function(e){if(typeof e.indeterminate!="boolean")return;var i=arguments;ie7CSS.addEventHandler(e,"onpropertychange",function(){if(event.propertyName=="indeterminate"){if(e.indeterminate)_1.register(i);else _1.unregister(i)}});ie7CSS.addEventHandler(e,"onclick",function(){_1.unregister(i)})});var _0=new ie7CSS.DynamicPseudoClass("target",function(e){var i=arguments;if(!e.tabIndex)e.tabIndex=0;ie7CSS.addEventHandler(document,"onpropertychange",function(){if(event.propertyName=="activeElement"){if(e.id==location.hash.slice(1))_0.register(i);else _0.unregister(i)}});if(e.id==location.hash.slice(1))_0.register(i)});decoder.add(/\|/,"\\:")});
diff --git a/web/static/js/ie7/ie7-dhtml.js b/web/static/js/ie7/ie7-dhtml.js
deleted file mode 100644
index d768063..0000000
--- a/web/static/js/ie7/ie7-dhtml.js
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- IE7, version 0.9 (alpha) (2005-08-19)
- Copyright: 2004-2005, Dean Edwards (http://dean.edwards.name/)
- License: http://creativecommons.org/licenses/LGPL/2.1/
-*/
-IE7.addModule("ie7-dhtml", function() {
-
-/* ---------------------------------------------------------------------
- This module is still in development and should not be used.
---------------------------------------------------------------------- */
-
-ie7CSS.specialize("recalc", function() {
- this.inherit();
- for (var i = 0; i < this.recalcs.length; i++) {
- var $recalc = this.recalcs[i];
- for (var j = 0; i < $recalc[3].length; i++) {
- _addPropertyChangeHandler($recalc[3][j], _getPropertyName($recalc[2]), $recalc[1]);
- }
- }
-});
-
-// constants
-var _PATTERNS = {
- width: "(width|paddingLeft|paddingRight|borderLeftWidth|borderRightWidth|borderLeftStyle|borderRightStyle)",
- height: "(height|paddingTop|paddingBottom|borderTopHeight|borderBottomHeight|borderTopStyle|borderBottomStyle)"
-};
-var _PROPERTY_NAMES = {
- width: "fixedWidth",
- height: "fixedHeight",
- right: "width",
- bottom: "height"
-};
-var _DASH_LETTER = /-(\w)/g;
-var _PROPERTY_NAME = /\w+/;
-
-function _addPropertyChangeHandler($element, $propertyName, $fix) {
- addEventHandler($element, "onpropertychange", function() {
- if (_getPattern($propertyName).test(event.propertyName)) {
- _reset($element, $propertyName);
- $fix($element);
- }
- });
-};
-function _upper($match, $letter) {return $letter.toUpperCase()};
-function _getPropertyName($pattern) {
- return String(String($pattern).toLowerCase().replace(_DASH_LETTER, _upper).match(_PROPERTY_NAME));
-};
-function _getPattern($propertyName) {
- return eval("/^style." + (_PATTERNS[$propertyName] || $propertyName) + "$/");
-};
-function _reset($element, $propertyName) {
- $element.runtimeStyle[$propertyName] = "";
- $propertyName = _PROPERTY_NAMES[$propertyName]
- if ($propertyName) $element.runtimeStyle[$propertyName] = "";
-};
-
-});
diff --git a/web/static/js/ie7/ie7-dynamic-attributes.js b/web/static/js/ie7/ie7-dynamic-attributes.js
deleted file mode 100644
index e066911..0000000
--- a/web/static/js/ie7/ie7-dynamic-attributes.js
+++ /dev/null
@@ -1,6 +0,0 @@
-/*
- IE7, version 0.9 (alpha) (2005-08-19)
- Copyright: 2004-2005, Dean Edwards (http://dean.edwards.name/)
- License: http://creativecommons.org/licenses/LGPL/2.1/
-*/
-IE7.addModule("ie7-dynamic-attributes",function(){if(!modules["ie7-css2-selectors"])return;var attributeSelectors=cssQuery.valueOf("attributeSelectors");var parseSelector=cssQuery.valueOf("parseSelector");function DynamicAttribute(s,a,d,t,c){this.attach=a||"*";parseSelector(d);this.dynamicAttribute=attributeSelectors["@"+d];this.target=t;this.inherit(s,c)};ie7CSS.Rule.specialize({constructor:DynamicAttribute,recalc:function(){var m=cssQuery(this.attach);for(var i=0;i<m.length;i++){var t=(this.target)?cssQuery(this.target,m[i]):[m[i]];if(t.length)this.apply(m[i],t)}},apply:function(e,t){var self=this;addEventHandler(e,"onpropertychange",function(){if(event.propertyName==self.dynamicAttribute.name)self.test(e,t)});this.test(e,t)},test:function(e,t){var a=this.dynamicAttribute.test(e)?"add":"remove";for(var i=0;(e=t[i]);i++)this[a](e)}});DynamicAttribute.MATCH=/(.*)(\[[^\]]*\])(.*)/;StyleSheet.prototype.specialize({createRule:function(s,c){var m;if(m=s.match(DynamicAttribute.MA
TCH)){return new DynamicAttribute(s,m[1],m[2],m[3],c)}else return this.inherit(s,c)}})});
diff --git a/web/static/js/ie7/ie7-fixed.js b/web/static/js/ie7/ie7-fixed.js
deleted file mode 100644
index 10d629f..0000000
--- a/web/static/js/ie7/ie7-fixed.js
+++ /dev/null
@@ -1,6 +0,0 @@
-/*
- IE7, version 0.9 (alpha) (2005-08-19)
- Copyright: 2004-2005, Dean Edwards (http://dean.edwards.name/)
- License: http://creativecommons.org/licenses/LGPL/2.1/
-*/
-IE7.addModule("ie7-fixed",function(){ie7CSS.addRecalc("position","fixed",_6,"absolute");ie7CSS.addRecalc("background(-attachment)?","[^};]*fixed",_7);var _10=(quirksMode)?"body":"documentElement";var _8=function(){if(body.currentStyle.backgroundAttachment!="fixed"){if(body.currentStyle.backgroundImage=="none"){body.runtimeStyle.backgroundRepeat="no-repeat";body.runtimeStyle.backgroundImage="url("+BLANK_GIF+")"}body.runtimeStyle.backgroundAttachment="fixed"}_8=DUMMY};var _0=createTempElement("img");function _1(f){return _2.exec(String(f))};var _2=new ParseMaster;_2.add(/Left/,"Top");_2.add(/left/,"top");_2.add(/Width/,"Height");_2.add(/width/,"height");_2.add(/right/,"bottom");_2.add(/X/,"Y");function _3(e){return(e)?isFixed(e)||_3(e.parentElement):false};function setExpression(e,p,ex){setTimeout("document.all."+e.uniqueID+".runtimeStyle.setExpression('"+p+"','"+ex+"')",0)};function _7(e){if(register(_7,e,e.currentStyle.backgroundAttachment=="fixed"&&!e.contains(body))){_8();
backgroundLeft(e);backgroundTop(e);_9(e)}};function _9(e){_0.src=e.currentStyle.backgroundImage.slice(5,-2);var p=(e.canHaveChildren)?e:e.parentElement;p.appendChild(_0);setOffsetLeft(e);setOffsetTop(e);p.removeChild(_0)};function backgroundLeft(e){e.style.backgroundPositionX=e.currentStyle.backgroundPositionX;if(!_3(e)){var ex="(parseInt(runtimeStyle.offsetLeft)+document."+_10+".scrollLeft)||0";setExpression(e,"backgroundPositionX",ex)}};eval(_1(backgroundLeft));function setOffsetLeft(e){var p=_3(e)?"backgroundPositionX":"offsetLeft";e.runtimeStyle[p]=getOffsetLeft(e,e.style.backgroundPositionX)-e.getBoundingClientRect().left-e.clientLeft+2};eval(_1(setOffsetLeft));function getOffsetLeft(e,p){switch(p){case"left":case"top":return 0;case"right":case"bottom":return viewport.clientWidth-_0.offsetWidth;case"center":return(viewport.clientWidth-_0.offsetWidth)/2;default:if(PERCENT.test(p)){return parseInt((viewport.clientWidth-_0.offsetWidth)*parseFloat(p)/100)}_0.style.left=p;re
turn _0.offsetLeft}};eval(_1(getOffsetLeft));function _6(e){if(register(_6,e,isFixed(e))){setOverrideStyle(e,"position","absolute");setOverrideStyle(e,"left",e.currentStyle.left);setOverrideStyle(e,"top",e.currentStyle.top);_8();if(ie7Layout)ie7Layout.fixRight(e);_5(e)}};function _5(e,r){positionTop(e,r);positionLeft(e,r,true);if(!e.runtimeStyle.autoLeft&&e.currentStyle.marginLeft=="auto"&&e.currentStyle.right!="auto"){var l=viewport.clientWidth-getPixelWidth(e,e.currentStyle.right)-getPixelWidth(e,e.runtimeStyle._12)-e.clientWidth;if(e.currentStyle.marginRight=="auto")l=parseInt(l/2);if(_3(e.offsetParent))e.runtimeStyle.pixelLeft+=l;else e.runtimeStyle.shiftLeft=l}clipWidth(e);clipHeight(e)};function clipWidth(e){if(e.currentStyle.width!="auto"){var r=e.getBoundingClientRect();var w=e.offsetWidth-viewport.clientWidth+r.left-2;if(w>=0){w=Math.max(getPixelValue(e,e.currentStyle.width)-w,0);setOverrideStyle(e,"width",w)}}};eval(_1(clipWidth));function positionLeft(e,r){if(!r&&
PERCENT.test(e.currentStyle.width)){e.runtimeStyle.fixWidth=e.currentStyle.width}if(e.runtimeStyle.fixWidth){e.runtimeStyle.width=getPixelWidth(e,e.runtimeStyle.fixWidth)}if(r){if(!e.runtimeStyle.autoLeft)return}else{e.runtimeStyle.shiftLeft=0;e.runtimeStyle._12=e.currentStyle.left;e.runtimeStyle.autoLeft=e.currentStyle.right!="auto"&&e.currentStyle.left=="auto"}e.runtimeStyle.left="";e.runtimeStyle.screenLeft=getScreenLeft(e);e.runtimeStyle.pixelLeft=e.runtimeStyle.screenLeft;if(!r&&!_3(e.offsetParent)){var ex="runtimeStyle.screenLeft+runtimeStyle.shiftLeft+document."+_10+".scrollLeft";setExpression(e,"pixelLeft",ex)}};eval(_1(positionLeft));function getScreenLeft(e){var s=e.offsetLeft,n=1;if(e.runtimeStyle.autoLeft){s=viewport.clientWidth-e.offsetWidth-getPixelWidth(e,e.currentStyle.right)}if(e.currentStyle.marginLeft!="auto"){s-=getPixelWidth(e,e.currentStyle.marginLeft)}while(e=e.offsetParent){if(e.currentStyle.position!="static")n=-1;s+=e.offsetLeft*n}return s};eval(_1(
getScreenLeft));function getPixelWidth(e,v){if(PERCENT.test(v))return parseInt(parseFloat(v)/100*viewport.clientWidth);return getPixelValue(e,v)};eval(_1(getPixelWidth));function _11(){var e=_7.elements;for(var i in e)_9(e[i]);e=_6.elements;for(i in e){_5(e[i],true);_5(e[i],true)}_4=0};var _4;addResize(function(){if(!_4)_4=setTimeout(_11,0)})});
\ No newline at end of file
diff --git a/web/static/js/ie7/ie7-graphics.js b/web/static/js/ie7/ie7-graphics.js
deleted file mode 100644
index 7e63c47..0000000
--- a/web/static/js/ie7/ie7-graphics.js
+++ /dev/null
@@ -1,6 +0,0 @@
-/*
- IE7, version 0.9 (alpha) (2005-08-19)
- Copyright: 2004-2005, Dean Edwards (http://dean.edwards.name/)
- License: http://creativecommons.org/licenses/LGPL/2.1/
-*/
-IE7.addModule("ie7-graphics",function(){if(appVersion<5.5)return;var A="DXImageTransform.Microsoft.AlphaImageLoader";var F="progid:"+A+"(src='%1',sizingMethod='scale')";var _3=new RegExp((window.IE7_PNG_SUFFIX||"-trans.png")+"$","i");var _0=[];function _2(e){var f=e.filters[A];if(f){f.src=e.src;f.enabled=true}else{e.runtimeStyle.filter=F.replace(/%1/,e.src);_0.push(e)}e.src=BLANK_GIF};function _5(e){e.src=e.pngSrc;e.filters[A].enabled=false};ie7CSS.addFix(/opacity\s*:\s*([\d.]+)/,function(m,o){return"zoom:1;filter:progid:DXImageTransform.Microsoft.Alpha(opacity="+((parseFloat(m[o+1])*100)||1)+")"});var B=/background(-image)?\s*:\s*([^\(};]*)url\(([^\)]+)\)([^;}]*)/;ie7CSS.addFix(B,function(m,o){var u=getString(m[o+3]);return _3.test(u)?"filter:"+F.replace(/scale/,"crop").replace(/%1/,u)+";zoom:1;background"+(m[o+1]||"")+":"+(m[o+2]||"")+"none"+(m[o+4]||""):m[o]});if(ie7HTML){ie7HTML.addRecalc("img,input",function(e){if(e.tagName=="INPUT"&&e.type!="image")return;_4(e);addEven
tHandler(e,"onpropertychange",function(){if(!_1&&event.propertyName=="src"&&e.src.indexOf(BLANK_GIF)==-1)_4(e)})});var B64=/^data:.*;base64/i;var _7=makePath("ie7-base64.php",path);function _4(e){if(_3.test(e.src)){var i=new Image(e.width,e.height);i.onload=function(){e.width=i.width;e.height=i.height;i=null};i.src=e.src;e.pngSrc=e.src;_2(e)}else if(B64.test(e.src)){e.src=_7+"?"+e.src.slice(5)}};var I=/^image/i;var _6=makePath("ie7-object.htc",path);ie7HTML.addRecalc("object",function(e){if(I.test(e.type)){var o=document.createElement("<object type=text/x-scriptlet>");o.style.width=e.currentStyle.width;o.style.height=e.currentStyle.height;o.data=_6;var u=makePath(e.data,getPath(location.href));e.parentNode.replaceChild(o,e);cssQuery.clearCache("object");addTimer(o,"",u);return o}})}var _1=false;addEventHandler(window,"onbeforeprint",function(){_1=true;for(var i=0;i<_0.length;i++)_5(_0[i])});addEventHandler(window,"onafterprint",function(){for(var i=0;i<_0.length;i++)_2(_0[i]
);_1=false})});
diff --git a/web/static/js/ie7/ie7-html4.js b/web/static/js/ie7/ie7-html4.js
deleted file mode 100644
index 3fcd891..0000000
--- a/web/static/js/ie7/ie7-html4.js
+++ /dev/null
@@ -1,6 +0,0 @@
-/*
- IE7, version 0.9 (alpha) (2005-08-19)
- Copyright: 2004-2005, Dean Edwards (http://dean.edwards.name/)
- License: http://creativecommons.org/licenses/LGPL/2.1/
-*/
-IE7.addModule("ie7-html4",function(){if(!isHTML)return;HEADER+="h1{font-size:2em}h2{font-size:1.5em;}h3{font-size:1.17em;}"+"h4{font-size:1em}h5{font-size:.83em}h6{font-size:.67em}";var _0={};ie7HTML=new(Fix.specialize({init:DUMMY,addFix:function(){this.fixes.push(arguments)},apply:function(){for(var i=0;i<this.fixes.length;i++){var m=cssQuery(this.fixes[i][0]);var f=this.fixes[i][1]||_1;for(var j=0;j<m.length;j++)f(m[j])}},addRecalc:function(){this.recalcs.push(arguments)},recalc:function(){for(var i=0;i<this.recalcs.length;i++){var m=cssQuery(this.recalcs[i][0]);var r=this.recalcs[i][1],e;var k=Math.pow(2,i);for(var j=0;(e=m[j]);j++){var u=e.uniqueID;if((_0[u]&k)==0){e=r(e)||e;_0[u]|=k}}}}}));ie7HTML.addFix("abbr");ie7HTML.addRecalc("label",function(e){if(!e.htmlFor){var f=cssQuery("input,textarea",e)[0];if(f){addEventHandler(e,"onclick",function(){f.click()})}}});ie7HTML.addRecalc("button,input",function(e){if(e.tagName=="BUTTON"){var m=e.outerHTML.match(/ value="([^"]*)"
/i);e.runtimeStyle.value=(m)?m[1]:""}if(e.type=="submit"){addEventHandler(e,"onclick",function(){e.runtimeStyle.clicked=true;setTimeout("document.all."+e.uniqueID+".runtimeStyle.clicked=false",1)})}});var U=/^(submit|reset|button)$/;ie7HTML.addRecalc("form",function(e){addEventHandler(e,"onsubmit",function(){for(var i=0;i<e.length;i++){if(_2(e[i])){e[i].disabled=true;setTimeout("document.all."+e[i].uniqueID+".disabled=false",1)}else if(e[i].tagName=="BUTTON"&&e[i].type=="submit"){setTimeout("document.all."+e[i].uniqueID+".value='"+e[i].value+"'",1);e[i].value=e[i].runtimeStyle.value}}})});function _2(e){return U.test(e.type)&&!e.disabled&&!e.runtimeStyle.clicked};ie7HTML.addRecalc("img",function(e){if(e.alt&&!e.title)e.title=""});var P=(appVersion<5.5)?"HTML:":"";function _1(e){var f=document.createElement("<"+P+e.outerHTML.slice(1));if(e.outerHTML.slice(-2)!="/>"){var en="</"+e.tagName+">",n;while((n=e.nextSibling)&&n.outerHTML!=en){f.appendChild(n)}if(n)n.removeNode()}e.pa
rentNode.replaceChild(f,e)}});
diff --git a/web/static/js/ie7/ie7-ie5.js b/web/static/js/ie7/ie7-ie5.js
deleted file mode 100644
index a07fd98..0000000
--- a/web/static/js/ie7/ie7-ie5.js
+++ /dev/null
@@ -1,6 +0,0 @@
-/*
- IE7, version 0.9 (alpha) (2005-08-19)
- Copyright: 2004-2005, Dean Edwards (http://dean.edwards.name/)
- License: http://creativecommons.org/licenses/LGPL/2.1/
-*/
-if(appVersion<5.5){ANON="HTML:!";var ap=function(f,o,a){f.apply(o,a)};if(''.replace(/^/,String)){var _0=String.prototype.replace;var _1=function(e,r){var m,n="",s=this;while(s&&(m=e.exec(s))){n+=s.slice(0,m.index)+ap(r,this,m);s=s.slice(m.lastIndex)}return n+s};String.prototype.replace=function(e,r){this.replace=(typeof r=="function")?_1:_0;return this.replace(e,r)}}if(!Function.apply){var APPLY="apply-"+Number(new Date);ap=function(f,o,a){var r;o[APPLY]=f;switch(a.length){case 0:r=o[APPLY]();break;case 1:r=o[APPLY](a[0]);break;case 2:r=o[APPLY](a[0],a[1]);break;case 3:r=o[APPLY](a[0],a[1],a[2]);break;case 4:r=o[APPLY](a[0],a[1],a[2],a[3]);break;default:var aa=[],i=a.length-1;do aa[i]="a["+i+"]";while(i--);eval("r=o[APPLY]("+aa+")")}delete o[APPLY];return r};ICommon.valueOf.prototype.inherit=function(){return ap(arguments.callee.caller.ancestor,this,arguments)}}if(![].push)Array.prototype.push=function(){for(var i=0;i<arguments.length;i++){this[this.length]=arguments[i]}retu
rn this.length};if(![].pop)Array.prototype.pop=function(){var i=this[this.length-1];this.length--;return i};if(isHTML){HEADER+="address,blockquote,body,dd,div,dt,fieldset,form,"+"frame,frameset,h1,h2,h3,h4,h5,h6,iframe,noframes,object,p,"+"hr,applet,center,dir,menu,pre,dl,li,ol,ul{display:block}"}}
diff --git a/web/static/js/ie7/ie7-layout.js b/web/static/js/ie7/ie7-layout.js
deleted file mode 100644
index d1b64eb..0000000
--- a/web/static/js/ie7/ie7-layout.js
+++ /dev/null
@@ -1,6 +0,0 @@
-/*
- IE7, version 0.9 (alpha) (2005-08-19)
- Copyright: 2004-2005, Dean Edwards (http://dean.edwards.name/)
- License: http://creativecommons.org/licenses/LGPL/2.1/
-*/
-IE7.addModule("ie7-layout",function(){ie7Layout=this;HEADER+="*{boxSizing:content-box}";this.hasLayout=(appVersion<5.5)?function(e){return e.clientWidth}:function(e){return e.currentStyle.hasLayout};this.boxSizing=function(e){if(!ie7Layout.hasLayout(e)){e.style.height="0cm";if(e.currentStyle.verticalAlign=="auto")e.runtimeStyle.verticalAlign="top";_1(e)}};function _1(e){if(e!=viewport&&e.currentStyle.position!="absolute"){collapseMarginTop(e);collapseMarginBottom(e)}};var firstElementChild=cssQuery.valueOf("firstElementChild");var lastElementChild=cssQuery.valueOf("lastElementChild");function collapseMarginTop(e){if(!e.runtimeStyle.marginTop){var p=e.parentElement;if(p&&ie7Layout.hasLayout(p)&&e==firstElementChild(p))return;var f=firstElementChild(e);if(f&&f.currentStyle.styleFloat=="none"&&ie7Layout.hasLayout(f)){collapseMarginTop(f);m=_3(e,e.currentStyle.marginTop);c=_3(f,f.currentStyle.marginTop);if(m<0||c<0){e.runtimeStyle.marginTop=m+c}else{e.runtimeStyle.marginTop=Math
.max(c,m)}f.runtimeStyle.marginTop="0px"}}};eval(String(collapseMarginTop).replace(/Top/g,"Bottom").replace(/first/g,"last"));function _3(e,v){return(v=="auto")?0:getPixelValue(e,v)};var U=/^[.\d][\w%]*$/,A=/^(auto|0cm)$/,N="[.\\d]";var applyWidth,applyHeight;function borderBox(e){applyWidth(e);applyHeight(e)};function fixWidth(H){applyWidth=function(e){if(!PERCENT.test(e.currentStyle.width))fixWidth(e);_1(e)};function fixWidth(e,v){if(!e.runtimeStyle.fixedWidth){if(!v)v=e.currentStyle.width;e.runtimeStyle.fixedWidth=(U.test(v))?Math.max(0,getFixedWidth(e,v)):v;setOverrideStyle(e,"width",e.runtimeStyle.fixedWidth)}};function layoutWidth(e){if(!isFixed(e)){var l=e.offsetParent;while(l&&!ie7Layout.hasLayout(l))l=l.offsetParent}return(l||viewport).clientWidth};function getPixelWidth(e,v){if(PERCENT.test(v))return parseInt(parseFloat(v)/100*layoutWidth(e));return getPixelValue(e,v)};var getFixedWidth=function(e,v){var b=e.currentStyle["box-sizing"]=="border-box";var a=0;if(quirk
sMode&&!b)a+=getBorderWidth(e)+getPaddingWidth(e);else if(!quirksMode&&b)a-=getBorderWidth(e)+getPaddingWidth(e);return getPixelWidth(e,v)+a};function getBorderWidth(e){return e.offsetWidth-e.clientWidth};function getPaddingWidth(e){return getPixelWidth(e,e.currentStyle.paddingLeft)+getPixelWidth(e,e.currentStyle.paddingRight)};eval(String(getPaddingWidth).replace(/padding/g,"margin").replace(/Padding/g,"Margin"));HEADER+="*{minWidth:none;maxWidth:none;min-width:none;max-width:none}";function minWidth(e){if(e.currentStyle["min-width"]!=null){e.style.minWidth=e.currentStyle["min-width"]}if(register(minWidth,e,e.currentStyle.minWidth!="none")){ie7Layout.boxSizing(e);fixWidth(e);resizeWidth(e)}};eval(String(minWidth).replace(/min/g,"max"));ie7Layout.minWidth=minWidth;ie7Layout.maxWidth=maxWidth;function resizeWidth(e){var r=e.getBoundingClientRect();var w=r.right-r.left;if(e.currentStyle.minWidth!="none"&&w<=getFixedWidth(e,e.currentStyle.minWidth)){e.runtimeStyle.width=getFixe
dWidth(e,e.currentStyle.minWidth)}else if(e.currentStyle.maxWidth!="none"&&w>=getFixedWidth(e,e.currentStyle.maxWidth)){e.runtimeStyle.width=getFixedWidth(e,e.currentStyle.maxWidth)}else{e.runtimeStyle.width=e.runtimeStyle.fixedWidth}};function fixRight(e){if(register(fixRight,e,/^(fixed|absolute)$/.test(e.currentStyle.position)&&getDefinedStyle(e,"left")!="auto"&&getDefinedStyle(e,"right")!="auto"&&A.test(getDefinedStyle(e,"width")))){resizeRight(e);ie7Layout.boxSizing(e)}};ie7Layout.fixRight=fixRight;function resizeRight(e){var l=getPixelWidth(e,e.runtimeStyle._4||e.currentStyle.left);var w=layoutWidth(e)-getPixelWidth(e,e.currentStyle.right)-l-getMarginWidth(e);if(parseInt(e.runtimeStyle.width)==w)return;e.runtimeStyle.width="";if(isFixed(e)||H||e.offsetWidth<w){if(!quirksMode)w-=getBorderWidth(e)+getPaddingWidth(e);if(w<0)w=0;e.runtimeStyle.fixedWidth=w;setOverrideStyle(e,"width",w)}};var _2=0;addResize(function(){var i,w=(_2<viewport.clientWidth);_2=viewport.clientWidth
;for(i in minWidth.elements){var e=minWidth.elements[i];var f=(parseInt(e.runtimeStyle.width)==getFixedWidth(e,e.currentStyle.minWidth));if(w&&f)e.runtimeStyle.width="";if(w==f)resizeWidth(e)}for(i in maxWidth.elements){var e=maxWidth.elements[i];var f=(parseInt(e.runtimeStyle.width)==getFixedWidth(e,e.currentStyle.maxWidth));if(!w&&f)e.runtimeStyle.width="";if(w!=f)resizeWidth(e)}for(i in fixRight.elements)resizeRight(fixRight.elements[i])});if(window.IE7_BOX_MODEL!==false){ie7CSS.addRecalc("width",N,quirksMode?applyWidth:_1)}ie7CSS.addRecalc("min-width",N,minWidth);ie7CSS.addRecalc("max-width",N,maxWidth);ie7CSS.addRecalc("right",N,fixRight)};ie7CSS.addRecalc("border-spacing",N,function(e){if(e.currentStyle.borderCollapse!="collapse"){e.cellSpacing=getPixelValue(e,e.currentStyle["border-spacing"])}});ie7CSS.addRecalc("box-sizing","content-box",this.boxSizing);ie7CSS.addRecalc("box-sizing","border-box",borderBox);var _0=new ParseMaster;_0.add(/Width/,"Height");_0.add(/width
/,"height");_0.add(/Left/,"Top");_0.add(/left/,"top");_0.add(/Right/,"Bottom");_0.add(/right/,"bottom");eval(_0.exec(String(fixWidth)));fixWidth();fixHeight(true)});
diff --git a/web/static/js/ie7/ie7-load.htc b/web/static/js/ie7/ie7-load.htc
deleted file mode 100644
index a6f1e7f..0000000
--- a/web/static/js/ie7/ie7-load.htc
+++ /dev/null
@@ -1 +0,0 @@
-<component lightweight="true"><attach event="ondocumentready" onevent="IE7.init()"/></component>
diff --git a/web/static/js/ie7/ie7-object.htc b/web/static/js/ie7/ie7-object.htc
deleted file mode 100644
index 392409e..0000000
--- a/web/static/js/ie7/ie7-object.htc
+++ /dev/null
@@ -1,12 +0,0 @@
-<html>
-<!--
- IE7, version 0.9 (alpha) (2005-08-19)
- Copyright: 2004-2005, Dean Edwards (http://dean.edwards.name/)
- License: http://creativecommons.org/licenses/LGPL/2.1/
--->
-<head>
-<style type="text/css">body{margin:0}</style>
-<script type="text/javascript">public_description=new function(){var l=false;this.ie7_anon=true;this.load=function(o,c,u){if(l)return;l=true;function _0(t,p){t.style[p]=o.currentStyle[p]};var p=o;while(p&&p.currentStyle.backgroundColor=="transparent"){p=p.parentElement}if(p)body.style.backgroundColor=p.currentStyle.backgroundColor;_0(body,"backgroundImage");_0(body,"backgroundRepeat");_0(body,"backgroundPositionX");_0(body,"backgroundPositionY");_0(body,"fontFamily");_0(body,"fontSize");_0(wrapper,"paddingTop");_0(wrapper,"paddingRight");_0(wrapper,"paddingBottom");_0(wrapper,"paddingLeft");image.width=o.clientWidth;image.height=o.clientHeight;var B64=/^data:.*;base64/i,P=/.png$/i;if(B64.test(u))u="ie7-base64.php"+"?"+u.slice(5);if(P.test(u)&&!/MSIE 5.0/.test(navigator.userAgent)){image.src="blank.gif";image.style.filter="progid:DXImageTransform.Microsoft.AlphaImageLoader(src='"+u+"',sizingMethod='scale')"}else{image.src=u}o.style.width=body.scrollWidth;o.style.height=body.s
crollHeight}};</script>
-</head>
-<body id="body"><div id="wrapper"><img id="image"></div></body>
-</html>
diff --git a/web/static/js/ie7/ie7-overflow.js b/web/static/js/ie7/ie7-overflow.js
deleted file mode 100644
index ad2e030..0000000
--- a/web/static/js/ie7/ie7-overflow.js
+++ /dev/null
@@ -1,6 +0,0 @@
-/*
- IE7, version 0.9 (alpha) (2005-08-19)
- Copyright: 2004-2005, Dean Edwards (http://dean.edwards.name/)
- License: http://creativecommons.org/licenses/LGPL/2.1/
-*/
-IE7.addModule("ie7-overflow",function(){var S={backgroundColor:"transparent",backgroundImage:"none",backgroundPositionX:null,backgroundPositionY:null,backgroundRepeat:null,borderTopWidth:0,borderRightWidth:0,borderBottomWidth:0,borderLeftStyle:"none",borderTopStyle:"none",borderRightStyle:"none",borderBottomStyle:"none",borderLeftWidth:0,height:null,marginTop:0,marginBottom:0,marginRight:0,marginLeft:0,width:"100%"};function _3(p,s,t){t.style[p]=s.currentStyle[p];if(S[p]!=null){s.runtimeStyle[p]=S[p]}};ie7CSS.addRecalc("overflow","visible",function(e){if(e.parentNode.ie7_wrapper)return;if(ie7Layout&&e.currentStyle["max-height"]!="auto"){ie7Layout.maxHeight(e)}if(e.currentStyle.marginLeft=="auto")e.style.marginLeft=0;if(e.currentStyle.marginRight=="auto")e.style.marginRight=0;var w=document.createElement(ANON);w.ie7_wrapper=true;for(var p in S)_3(p,e,w);w.style.display="block";w.style.position="relative";e.runtimeStyle.position="absolute";e.parentNode.insertBefore(w,e);w.appe
ndChild(e)});cssQuery.addModule("ie7-overflow",function(){function _0(e){return(e&&e.ie7_wrapper)?e.firstChild:e};var _2=previousElementSibling;previousElementSibling=function(e){return _0(_2(e))};var _1=nextElementSibling;nextElementSibling=function(e){return _0(_1(e))};selectors[" "]=function(r,f,t,n){var e,i,j;for(i=0;i<f.length;i++){var s=getElementsByTagName(f[i],t,n);for(j=0;(e=_0(s[j]));j++){if(thisElement(e)&&(!n||compareNamespace(e,n)))r.push(e)}}};selectors[">"]=function(r,f,t,n){var e,i,j;for(i=0;i<f.length;i++){var s=childElements(f[i]);for(j=0;(e=_0(s[j]));j++){if(compareTagName(e,t,n))r.push(e)}}}})});
diff --git a/web/static/js/ie7/ie7-quirks.js b/web/static/js/ie7/ie7-quirks.js
deleted file mode 100644
index a1314dd..0000000
--- a/web/static/js/ie7/ie7-quirks.js
+++ /dev/null
@@ -1,6 +0,0 @@
-/*
- IE7, version 0.9 (alpha) (2005-08-19)
- Copyright: 2004-2005, Dean Edwards (http://dean.edwards.name/)
- License: http://creativecommons.org/licenses/LGPL/2.1/
-*/
-IE7.addModule("ie7-quirks",function(){if(quirksMode){var F="xx-small,x-small,small,medium,large,x-large,xx-large".split(",");for(var i=0;i<F.length;i++){F[F[i]]=F[i-1]||"0.67em"}ie7CSS.addFix(new RegExp("(font(-size)?\\s*:\\s*)([\\w\\-\\.]+)"),function(m,o){return m[o+1]+(F[m[o+3]]||m[o+3])});if(appVersion<6){var N=/^\-/,L=/(em|ex)$/i;var EM=/em$/i,EX=/ex$/i;function _2(e){var s=1;_0.style.fontFamily=e.currentStyle.fontFamily;_0.style.lineHeight=e.currentStyle.lineHeight;while(e!=body){var f=e.currentStyle["ie7-font-size"];if(f){if(EM.test(f))s*=parseFloat(f);else if(PERCENT.test(f))s*=(parseFloat(f)/100);else if(EX.test(f))s*=(parseFloat(f)/2);else{_0.style.fontSize=f;return 1}}e=e.parentElement}return s};var _0=createTempElement();getPixelValue=function(e,v){if(PIXEL.test(v||0))return parseInt(v||0);var scale=N.test(v)?-1:1;if(L.test(v))scale*=_2(e);_0.style.width=(scale<0)?v.slice(1):v;body.appendChild(_0);v=scale*_0.offsetWidth;_0.removeNode();return parseInt(v)};HEADER=
HEADER.replace(/(font(-size)?\s*:\s*([^\s;}\/]*))/gi,"ie7-font-size:$3;$1");ie7CSS.addFix(/cursor\s*:\s*pointer/,"cursor:hand");ie7CSS.addFix(/display\s*:\s*list-item/,"display:block")}function getPaddingWidth(e){return getPixelValue(e,e.currentStyle.paddingLeft)+getPixelValue(e,e.currentStyle.paddingRight)};function _1(e){if(appVersion<5.5&&ie7Layout)ie7Layout.boxSizing(e.parentElement);var p=e.parentElement;var m=p.offsetWidth-e.offsetWidth-getPaddingWidth(p);var a=(e.currentStyle["ie7-margin"]&&e.currentStyle.marginRight=="auto")||e.currentStyle["ie7-margin-right"]=="auto";switch(p.currentStyle.textAlign){case"right":m=(a)?parseInt(m/2):0;e.runtimeStyle.marginRight=parseInt(m)+"px";break;case"center":if(a)m=0;default:if(a)m=parseInt(m/2);e.runtimeStyle.marginLeft=parseInt(m)+"px"}};ie7CSS.addRecalc("margin(-left|-right)?","[^};]*auto",function(e){if(register(_1,e,e.parentElement&&e.currentStyle.display=="block"&&e.currentStyle.marginLeft=="auto"&&e.currentStyle.position!=
"absolute")){_1(e)}});addResize(function(){for(var i in _1.elements){e=_1.elements[i];e.runtimeStyle.marginLeft=e.runtimeStyle.marginRight="";_1(e)}})}});
diff --git a/web/static/js/ie7/ie7-recalc.js b/web/static/js/ie7/ie7-recalc.js
deleted file mode 100644
index 10cb839..0000000
--- a/web/static/js/ie7/ie7-recalc.js
+++ /dev/null
@@ -1,6 +0,0 @@
-/*
- IE7, version 0.9 (alpha) (2005-08-19)
- Copyright: 2004-2005, Dean Edwards (http://dean.edwards.name/)
- License: http://creativecommons.org/licenses/LGPL/2.1/
-*/
-IE7.addModule("ie7-recalc",function(){C=/\sie7_class\d+/g;function _0(e){e.className=e.className.replace(C,"")};function _1(e){e.runtimeStyle.cssText=""};ie7CSS.specialize({elements:{},handlers:[],reset:function(){this.removeEventHandlers();var e=this.elements;for(var i in e)_1(e[i]);this.elements={};if(this.Rule){var e=this.Rule.elements;for(var i in e)_0(e[i]);this.Rule.elements={}}},reload:function(){ie7CSS.rules=[];this.getInlineStyles();this.screen.load();if(this.print)this.print.load();this.refresh();this.trash()},addRecalc:function(p,t,h,r){this.inherit(p,t,function(e){h(e);ie7CSS.elements[e.uniqueID]=e},r)},recalc:function(){this.reset();this.inherit()},addEventHandler:function(e,t,h){e.attachEvent(t,h);this.handlers.push(arguments)},removeEventHandlers:function(){var h;while(h=this.handlers.pop()){removeEventHandler(h[0],h[1],h[2])}},getInlineStyles:function(){var st=document.getElementsByTagName("style"),s;for(var i=st.length-1;(s=st[i]);i--){if(!s.disabled&&!s.ie7
){var c=s.c||s.innerHTML;this.styles.push(c);s.c=c}}},trash:function(){var s,i;for(i=0;i<styleSheets.length;i++){s=styleSheets[i];if(!s.ie7&&!s.c){s.c=s.cssText}}this.inherit()},getText:function(s){return s.c||this.inherit(s)}});addEventHandler(window,"onunload",function(){ie7CSS.removeEventHandlers()});if(ie7CSS.Rule){ie7CSS.Rule.elements={};ie7CSS.Rule.prototype.specialize({add:function(e){this.inherit(e);ie7CSS.Rule.elements[e.uniqueID]=e}});ie7CSS.PseudoElement.hash={};ie7CSS.PseudoElement.prototype.specialize({create:function(t){var k=this.selector+":"+t.uniqueID;if(!ie7CSS.PseudoElement.hash[k]){ie7CSS.PseudoElement.hash[k]=true;this.inherit(t)}}})}if(isHTML&&ie7HTML){ie7HTML.specialize({elements:{},addRecalc:function(s,h){this.inherit(s,function(e){if(!ie7HTML.elements[e.uniqueID]){h(e);ie7HTML.elements[e.uniqueID]=e}})}})}document.recalc=function(reload){if(ie7CSS.screen){if(reload)ie7CSS.reload();recalc()}}});
diff --git a/web/static/js/ie7/ie7-server.css b/web/static/js/ie7/ie7-server.css
deleted file mode 100644
index 50ca605..0000000
--- a/web/static/js/ie7/ie7-server.css
+++ /dev/null
@@ -1,44 +0,0 @@
-body, td, dd {font: 10pt Verdana, Arial, Helvetica, sans-serif; color: black;}
-body {margin: 8px; background: #333;}
-h1 {margin: 0;}
-h1 a:hover {background-color: transparent;}
-h2 {font-size: 1.75em;}
-h3 {font-size: 1.1em;}
-p.footnote {font-family: "Times New Roman", Times, serif; font-style: italic;}
-a:active {color: #ff0000;}
-a:link {color: #0a6cce;}
-a:visited {color: #0a6cce;}
-code, *.code {font-family: monospace; font-size: 100%; font-style: normal; white-space: nowrap;
- padding: 0 1px; background: #f2f3f8; border: #d6d9e9 1px solid;}
-code.box {display: block; padding: 10px; margin: 0.5em 0;}
-ul {list-style-type: square;}
-dd {margin: .2em 0 .5em 1em;}
-dl.library dt {display: list-item; margin-left: 3em; list-style-type: square;}
-dl.library dd {font-style: italic; margin-left: 3em;}
-dt {font-weight: bold;}
-dt.pack {color: brown;}
-a img {border-style: none;}
-hr {height: 1px; color: #000; border-style: solid;}
-hr.short {height: 2px; width: 100px;}
-div.document {background: #eef; padding: 20px 20px 5px 20px; width: 600px; border: 1px solid black;}
-hr {border-bottom-width: 0px;}
-div.header hr {color: #0a6cce; background-color: #0a6cce;}
-div.footer hr {color: #898e79; background-color: #898e79; }
-div.header, div.header a:link, div.header a:visited, h3 a:link, h3 a:visited {text-decoration: none;}
-a:hover {color: #fff; background-color: #0a6cce; text-decoration: none;}
-div.footer a:hover {background-color: transparent; text-decoration: none;}
-div.header .menu {text-align: right;}
-div.content {min-height: 100px;}
-div.footer {font-size: x-small; margin-top: 8px;}
-div.footnote {font-family: "times new roman", times; font-style: italic; margin-top: 10px;}
-#license {margin-top: 5px; font-size: xx-small;}
-table {border-top: 1px solid #000; border-left: 1px solid #000;}
-th {background-color: #fff; text-align: left;}
-th, td {border-right: 1px solid #000; border-bottom: 1px solid #000;}
-th.small {width: 100px;}
-th.medium {width: 200px;}
-th.large {width: 270px;}
-th.x-large {width: 408px;}
-table.fixed {table-layout: fixed;}
-span.comment {color: #666;}
-
diff --git a/web/static/js/ie7/ie7-squish.js b/web/static/js/ie7/ie7-squish.js
deleted file mode 100644
index e5a1972..0000000
--- a/web/static/js/ie7/ie7-squish.js
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- IE7, version 0.9 (alpha) (2005-08-19)
- Copyright: 2004-2005, Dean Edwards (http://dean.edwards.name/)
- License: http://creativecommons.org/licenses/LGPL/2.1/
-*/
-IE7.addModule("ie7-squish", function() {
-
-/* ---------------------------------------------------------------------
-
- Squish some IE bugs!
-
- Some of these bug fixes may have adverse effects so they are
- not included in the standard library. Add your own if you want.
-
- -dean
-
---------------------------------------------------------------------- */
-
-// @NOTE: ie7Layout.boxSizing is the same as the "Holly Hack"
-
-// "doubled margin" bug
-// http://www.positioniseverything.net/explorer/doubled-margin.html
-ie7CSS.addFix(/float\s*:\s*(left|right)/, "display:inline;$1");
-
-if (ie7Layout) {
- // "peekaboo" bug
- // http://www.positioniseverything.net/explorer/peekaboo.html
- if (appVersion >= 6) ie7CSS.addRecalc("float", "left|right", function($element) {
- ie7Layout.boxSizing($element.parentElement);
- // "doubled margin" bug
- $element.runtimeStyle.display = "inline";
- });
-
- // "unscrollable content" bug
- // http://www.positioniseverything.net/explorer/unscrollable.html
- ie7CSS.addRecalc("position", "absolute|fixed", function($element) {
- if ($element.offsetParent && $element.offsetParent.currentStyle.position == "relative")
- ie7Layout.boxSizing($element.offsetParent);
- });
-}
-
-//# // get rid of Microsoft's pesky image toolbar
-//# if (!complete) document.write('<meta http-equiv="imagetoolbar" content="no">');
-
-});
diff --git a/web/static/js/ie7/ie7-standard-p.js b/web/static/js/ie7/ie7-standard-p.js
deleted file mode 100644
index 6db85f5..0000000
--- a/web/static/js/ie7/ie7-standard-p.js
+++ /dev/null
@@ -1,6 +0,0 @@
-/*
- IE7, version 0.9 (alpha) (2005-08-19)
- Copyright: 2004-2005, Dean Edwards (http://dean.edwards.name/)
- License: http://creativecommons.org/licenses/LGPL/2.1/
-*/
-eval(function(p,a,c,k,e,d){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)d[e(c)]=k[c]||e(c);k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('y(!26.1F)11 7(){2C{26.1F=8;6 2s=8.24=11 3b;8.1g=7(){z"1F 4x 0.9 (ad)"};6 5T=/5T/.Z(2y.5h.7C);6 31=(5T)?7(m){26.31(1F+"\\n\\n"+m)}:2s;6 29=ac.29.19(/ab (\\d\\.\\d)/)[1];6 2m=16.aa!="a9";y(/a8/.Z(2y.5h.7C)||29<5||!/^a7/.Z(16.2F.2a))z;6 33=16.5W=="33";6 1s,1K;6 2F=16.2F,1X,1J,1R=16.1R;6 4E="!";6 3Q={};6 2G=1z;1F.24=7(n,s){y(!3Q[n]){y(2G)1k("s="+23(s));3Q[n]=11 s()}};6 R=/^[\\w\\.]+[^:]*$/;7 1Z(h,p){y(R.Z(h))h=(p||"")+h;z h};7 3F(h,p){h=1Z(h,p);z h.1q(0,h.7a("/")+1)};6 s=16.7B[16.7B.K-1];2C{1k(s.7j)}2j(i){}6 2k=3F(s.1l);6 2v;2C{6 l=(a6()>=5)?"a5":"5n";2v=11 a4(l+".a3")}2j(i){}6 4A={};7 3T(h,p){2C{h=1Z(h,p);y(!4A[h]){2v.a2("a1",h,1z);2v.a0();y(2v.7A==0||2v.7A=
=9Z){4A[h]=2v.9Y}}}2j(i){31("4B [1]: 5O 9X 9W "+h)}5U{z 4A[h]||""}};6 4i=1Z("9V.9U",2k);7 2o(1w){y(1w!=1U){1w.1T=1t.1C.1T;1w.1e=1t.1C.1e}z 1w};2o.1e=7(p,c){y(!p)p={};y(!c)c=p.1h;y(c=={}.1h)c=11 3b("8.1T()");c.1i=11 3b("z 8");c.1i.1C=11 8.1i;c.1i.1C.1e(p);c.1C=11 c.1i;c.1i.1C.1h=c.1C.1h=c;c.2E=8;c.1e=1a.5P;c.4z=8.4z;z c};2o.1i=11 3b("z 8");2o.1i.1C={1h:2o,1T:7(){z 1a.5P.9T.2E.2q(8,1a)},1e:7(1w){y(8==8.1h.1C&&8.1h.1e){z 8.1h.1i.1C.1e(1w)}O(6 i 28 1w){34(i){1m"1h":1m"1g":1m"1i":5M}y(3Y 1w[i]=="7"&&1w[i]!=8[i]){1w[i].2E=8[i]}8[i]=1w[i]}y(1w.1g!=8.1g&&1w.1g!={}.1g){1w.1g.2E=8.1g;8.1g=1w.1g}z 8}};7 1t(){};8.1t=2o.1e({1h:1t,1g:7(){z"[9S "+(8.1h.1x||"9R")+"]"},9Q:7(2i){z 8.1h==2i||2i.4z(8.1h)}});1t.1x="1t";1t.2E=1U;1t.4z=7(2i){1D(2i&&2i.2E!=8)2i=2i.2E;z 7q(2i)};1t.1i.2E=2o;3u 8.1t;6 5x=1t.1e({1h:7(){8.3L=[];8.1Q=[]},1S:2s});y(29<5.5)1k(3T("17-9P.5X",2k));6 5S=1z;1F.1S=7(){2C{y(5S)z;5S=33=1o;1X=16.1X;1J=(2m)?1X:2F;y(2l&&1s)1s.2q();V.2q();1u();31("2G 9O")}2j(e){31("4B [2]: "+e.5V)}};6
1Q=[];7 1n(r){1Q.1b(r)};7 1u(){14.5g();y(2l&&1s)1s.1u();V.1u();O(6 i=0;i<1Q.K;i++)1Q[i]()};7 2U(){6 E=0,R=1,L=2;6 G=/\\(/g,S=/\\$\\d/,I=/^\\$\\d+$/,T=/([\'"])\\1\\+(.*)\\+\\1\\1$/,7t=/\\\\./g,Q=/\'/,7z=/\\3S[^\\3S]*\\3S/g;6 3N=8;8.15=7(e,r){y(!r)r="";6 l=(5R(23(e)).19(G)||"").K+1;y(S.Z(r)){y(I.Z(r)){r=25(r.1q(1))-1}1d{6 i=l;6 q=Q.Z(5R(r))?\'"\':"\'";1D(i)r=r.2O("$"+i--).2p(q+"+a[o+"+i+"]+"+q);r=11 3b("a,o","z"+q+r.13(T,"$1")+q)}}7y(e||"/^$/",r,l)};8.2V=7(s){3R.K=0;z 7u(7v(s,8.4y).13(11 1N(30,8.5Q?"5D":"g"),7w),8.4y).13(7z,"")};8.72=7(){30.K=0};6 3R=[];6 30=[];6 7x=7(){z"("+23(8[E]).1q(1,-1)+")"};30.1g=7(){z 8.2p("|")};7 7y(){1a.1g=7x;30[30.K]=1a}7 7w(){y(!1a[0])z"";6 i=1,j=0,p;1D(p=30[j++]){y(1a[i]){6 r=p[R];34(3Y r){1m"7":z r(1a,i);1m"9N":z 1a[r+i]}6 d=(1a[i].6F(3N.4y)==-1)?"":"\\3S"+1a[i]+"\\3S";z d+r}1d i+=p[L]}};7 7v(s,e){z e?s.13(11 1N("\\\\"+e+"(.)","g"),7(m,c){3R[3R.K]=c;z e}):s};7 7u(s,e){6 i=0;z e?s.13(11 1N("\\\\"+e,"g"),7(){z e+(3R[i++]||"")}):s};7 5R(s){z s.13(7
t,"")}};2U.1C={1h:2U,5Q:1z,4y:""};1t.1e(2U.1C);6 3M=2U.1e({5Q:1o});6 14=7(){6 4x="2.0.2";6 C=/\\s*,\\s*/;6 14=7(s,1E){2C{6 m=[];6 u=1a.5P.5I&&!1E;6 b=(1E)?(1E.1h==7n)?1E:[1E]:[16];6 2f=45(s).2O(C),i;O(i=0;i<2f.K;i++){s=5J(2f[i]);y(4P&&s.1q(0,3).2p("")==" *#"){s=s.1q(2);1E=7o([],b,s[1])}1d 1E=b;6 j=0,t,f,a,c="";1D(j<s.K){t=s[j++];f=s[j++];c+=t+f;a="";y(s[j]=="("){1D(s[j++]!=")"&&j<s.K){a+=s[j]}a=a.1q(0,-1);c+="("+a+")"}1E=(u&&2e[c])?2e[c]:7m(1E,t,f,a);y(u)2e[c]=1E}m=m.4J(1E)}3u 14.5O;z m}2j(e){14.5O=e;z[]}};14.1g=7(){z"7 14() {\\n [4x "+4x+"]\\n}"};6 2e={};14.5I=1z;14.5g=7(s){y(s){s=5J(s).2p("");3u 2e[s]}1d 2e={}};6 3Q={};6 2G=1z;14.24=7(n,s){y(2G)1k("s="+23(s));3Q[n]=11 s()};14.1i=7(c){z c?1k(c):8};6 1V={};6 1B={};6 1p={19:/\\[([\\w-]+(\\|[\\w-]+)?)\\s*(\\W?=)?\\s*([^\\]]*)\\]/};6 2R=[];1V[" "]=7(r,f,t,n){6 e,i,j;O(i=0;i<f.K;i++){6 s=4w(f[i],t,n);O(j=0;(e=s[j]);j++){y(2D(e)&&5K(e,n))r.1b(e)}}};1V["#"]=7(r,f,i){6 e,j;O(j=0;(e=f[j]);j++)y(e.1c==i)r.1b(e)};1V["."]=7(r,f,c){c=1
1 1N("(^|\\\\s)"+c+"(\\\\s|$)");6 e,i;O(i=0;(e=f[i]);i++)y(c.Z(e.1x))r.1b(e)};1V[":"]=7(r,f,p,a){6 t=1B[p],e,i;y(t)O(i=0;(e=f[i]);i++)y(t(e,a))r.1b(e)};1B["21"]=7(e){6 d=5L(e);y(d.5N)O(6 i=0;i<d.5N.K;i++){y(d.5N[i]==e)z 1o}};1B["37"]=7(e){};6 2D=7(e){z(e&&e.7k==1&&e.2W!="!")?e:1U};6 4S=7(e){1D(e&&(e=e.9M)&&!2D(e))5M;z e};6 47=7(e){1D(e&&(e=e.6W)&&!2D(e))5M;z e};6 3l=7(e){z 2D(e.7s)||47(e.7s)};6 5t=7(e){z 2D(e.7r)||4S(e.7r)};6 6q=7(e){6 c=[];e=3l(e);1D(e){c.1b(e);e=47(e)}z c};6 4P=1o;6 5H=7(e){6 d=5L(e);z(3Y d.7p=="9L")?/\\.9K$/i.Z(d.9J):7q(d.7p=="9I 9H")};6 5L=7(e){z e.9G||e.16};6 4w=7(e,t){z(t=="*"&&e.1Y)?e.1Y:e.4w(t)};6 4T=7(e,t,n){y(t=="*")z 2D(e);y(!5K(e,n))z 1z;y(!5H(e))t=t.9F();z e.2W==t};6 5K=7(e,n){z!n||(n=="*")||(e.9E==n)};6 9D=7(e){z e.9C};7 7o(r,f,1c){6 m,i,j;O(i=0;i<f.K;i++){y(m=f[i].1Y.9B(1c)){y(m.1c==1c)r.1b(m);1d y(m.K!=1U){O(j=0;j<m.K;j++){y(m[j].1c==1c)r.1b(m[j])}}}}z r};y(![].1b)7n.1C.1b=7(){O(6 i=0;i<1a.K;i++){8[8.K]=1a[i]}z 8.K};6 N=/\\|/;7 7m(1E,t,f,a){y
(N.Z(f)){f=f.2O(N);a=f[0];f=f[1]}6 r=[];y(1V[t]){1V[t](r,1E,f,a)}z r};6 S=/^[^\\s>+~]/;6 7l=/[\\s#.:>+~()@]|[^\\s#.:>+~()@]+/g;7 5J(s){y(S.Z(s))s=" "+s;z s.19(7l)||[]};6 W=/\\s*([\\s>+~(),]|^|$)\\s*/g;6 I=/([\\s>+~,]|[^(]\\+|^)([#.:@])/g;6 45=7(s){z s.13(W,"$1").13(I,"$1*$2")};6 2c={1g:7(){z"\'"},19:/^(\'[^\']*\')|("[^"]*")$/,Z:7(s){z 8.19.Z(s)},15:7(s){z 8.Z(s)?s:8+s+8},3v:7(s){z 8.Z(s)?s.1q(1,-1):s}};6 2w=7(t){z 2c.3v(t)};6 E=/([\\/()[\\]?{}|*+-])/g;7 4O(s){z s.13(E,"\\\\$1")};2G=1o;z 14}();14.5I=1o;14.24("17",7(){2D=7(e){z(e&&e.7k==1&&e.2W!="!"&&!e.2K)?e:1U}});14.1i("2w=1a[1]",42);6 2l=!14.1i("5H(1a[1])",2F);6 2r=":21{17-21:21}:37{17-21:37}"+(2l?"":"*{6Q:0}");6 V=11(5x.1e({5B:11 3M,2P:"",2Y:"",5F:[],1S:7(){8.5G();8.4t()},4t:7(){V.3O.18=2r+8.2P+8.2Y},7i:7(){6 3P=16.4w("1r"),s;O(6 i=3P.K-1;(s=3P[i]);i--){y(!s.3m&&!s.17){8.5F.1b(s.7j)}}},2q:7(){8.7i();8.4t();11 3y("2P");8.7g()},3i:7(e,r){8.5B.15(e,r)},1u:7(){6 R=/7h\\d+/g;6 s=2r.19(/[{,]/g).K;6 3P=s+(8.2P.18.19(/\\{/g)||"").
K;6 2Q=8.3O.2t,r;6 4v,c,4u,e,i,j,k,1c;O(i=s;i<3P;i++){r=2Q[i];y(r&&(4v=r.1r.18.19(R))){4u=14(r.4M);y(4u.K)O(j=0;j<4v.K;j++){1c=4v[j];c=V.1Q[1c.1q(10)][2];O(k=0;(e=4u[k]);k++){y(e.D[1c])c(e)}}}}},1n:7(p,t,h,r){t=11 1N("([{;\\\\s])"+p+"\\\\s*:\\\\s*"+t+"[^;}]*");6 i=8.1Q.K;y(r)r=p+":"+r;8.3i(t,7(m,o){z(r?m[o+1]+r:m[o])+";17-"+m[o].1q(1)+";7h"+i+":1"});8.1Q.1b(1a);z i},2w:7(s){z s.18||""},5G:7(){y(33||!2l)16.5G();1d 16.9A("<1r 17=1o></1r>");8.3O=1R[1R.K-1];8.3O.17=1o;8.3O.18=2r},7g:7(){O(6 i=0;i<1R.K;i++){y(!1R[i].17&&1R[i].18){1R[i].18=""}}}}));7 3y(m){8.2Z=m;8.3q();V[m]=8;V.4t()};1t.1e({1h:3y,1g:7(){z"@2Z "+8.2Z+"{"+8.18+"}"},1u:2s,3q:7(){8.18="";8.2w();8.38();8.18=41(8.18);f={}},2w:7(){6 7e=[].4J(V.5F);6 M=/@2Z\\s+([^{]*)\\{([^@]+\\})\\s*\\}/5D;6 A=/\\9z\\b|^$/i,S=/\\9y\\b/i,P=/\\9x\\b/i;7 7d(c,m){4s.v=m;z c.13(M,4s)};7 4s(9w,m,c){m=5E(m);34(m){1m"2P":1m"2Y":y(m!=4s.v)z"";1m"1Y":z c}z""};7 5E(m){y(A.Z(m))z"1Y";1d y(S.Z(m))z(P.Z(m))?"1Y":"2P";1d y(P.Z(m))z"2Y"};6 3N=8;7 5C(s,
p,m,l){6 c="";y(!l){m=5E(s.2Z);l=0}y(m=="1Y"||m==3N.2Z){y(l<3){O(6 i=0;i<s.7f.K;i++){c+=5C(s.7f[i],3F(s.2u,p),m,l+1)}}c+=79(s.2u?7c(s,p):7e.77()||"");c=7d(c,3N.2Z)}z c};6 f={};7 7c(s,p){6 u=1Z(s.2u,p);y(f[u])z"";f[u]=(s.3m)?"":7b(V.2w(s,p),3F(s.2u,p));z f[u]};6 U=/(43\\s*\\(\\s*[\'"]?)([\\w\\.]+[^:\\)]*[\'"]?\\))/5D;7 7b(c,p){z c.13(U,"$1"+p.1q(0,p.7a("/")+1)+"$2")};O(6 i=0;i<1R.K;i++){y(!1R[i].3m&&!1R[i].17){8.18+=5C(1R[i])}}},38:7(){8.18=V.5B.2V(8.18)},1u:2s});6 2c=14.1i("2c");6 4r=[];7 79(c){z 2n.2V(3r.2V(c))};7 5A(m,o){z 2c+(4r.1b(m[o])-1)+2c};7 42(v){z 2c.Z(v)?1k(4r[1k(v)]):v};6 2n=11 3M;2n.15(/\\/\\*[^*]*\\*+([^\\/][^*]*\\*+)*\\//);2n.15(/\'[^\']*\'/,5A);2n.15(/"[^"]*"/,5A);2n.15(/\\s+/," ");2n.15(/@(9v|9u)[^;\\n]+[;\\n]|<!\\-\\-|\\-\\->/);6 3r=11 3M;3r.15(/\\\\\'/,"\\\\9t");3r.15(/\\\\"/,"\\\\46");6 5z=11 3M;5z.15(/\'(\\d+)\'/,78);7 41(c){z 5z.2V(c)};7 78(m,o){z 4r[m[o+1]]};6 5y=[];7 4U(h){1n(h);1j(26,"9s",h)};7 1j(e,t,h){e.9r(t,h);5y.1b(1a)};7 76(e,t,h){2C{e.9q(t,h)}
2j(i){}};1j(26,"9p",7(){6 h;1D(h=5y.77()){76(h[0],h[1],h[2])}});7 20(h,e,c){y(!h.1O)h.1O={};y(c)h.1O[e.2a]=e;1d 3u h.1O[e.2a];z c};1j(26,"6z",7(){y(!V.2Y)11 3y("2Y");V.2Y.1u()});6 75=/^\\d+(9o)?$/i;6 3d=/^\\d+%$/;6 3c=7(e,v){y(75.Z(v))z 25(v);6 s=e.1r.1f;6 r=e.J.1f;e.J.1f=e.D.1f;e.1r.1f=v||0;v=e.1r.4e;e.1r.1f=s;e.J.1f=r;z v};7 6x(t){6 e=16.3X(t||"2M");e.1r.18="1y:3C;6R:0;4K:9n;3G:1M;9m:9l(0 0 0 0);1f:-9k";e.2K=1o;z e};6 4q="17-";7 3D(e){z e.D["17-1y"]=="2z"};7 4o(e,p){z e.D[4q+p]||e.D[p]};7 2T(e,p,v){y(e.D[4q+p]==1U){e.J[4q+p]=e.D[p]}e.J[p]=v};7 4H(o,c,u){6 t=9j(7(){2C{y(!o.3q)z;o.3q(o,c,u);74(t)}2j(i){74(t)}},10)};1F.24("17-9i",7(){y(!2l)z;2r+="9h{3p-3o:9g}9f{3p-3o:1.9e;}9d{3p-3o:1.9c;}"+"9b{3p-3o:9a}99{3p-3o:.98}97{3p-3o:.96}";6 5w={};1s=11(5x.1e({1S:2s,3i:7(){8.3L.1b(1a)},2q:7(){O(6 i=0;i<8.3L.K;i++){6 m=14(8.3L[i][0]);6 f=8.3L[i][1]||6X;O(6 j=0;j<m.K;j++)f(m[j])}},1n:7(){8.1Q.1b(1a)},1u:7(){O(6 i=0;i<8.1Q.K;i++){6 m=14(8.1Q[i][0]);6 r=8.1Q[i][1],e;6 k=4g.95(2,i);O(6 j=0;
(e=m[j]);j++){6 u=e.2a;y((5w[u]&k)==0){e=r(e)||e;5w[u]|=k}}}}}));1s.3i("94");1s.1n("93",7(e){y(!e.6o){6 f=14("5l,92",e)[0];y(f){1j(e,"73",7(){f.91()})}}});1s.1n("71,5l",7(e){y(e.2W=="70"){6 m=e.3z.19(/ 3n="([^"]*)"/i);e.J.3n=(m)?m[1]:""}y(e.2L=="5v"){1j(e,"73",7(){e.J.5u=1o;32("16.1Y."+e.2a+".J.5u=1z",1)})}});6 U=/^(5v|72|71)$/;1s.1n("90",7(e){1j(e,"8Z",7(){O(6 i=0;i<e.K;i++){y(6Z(e[i])){e[i].3m=1o;32("16.1Y."+e[i].2a+".3m=1z",1)}1d y(e[i].2W=="70"&&e[i].2L=="5v"){32("16.1Y."+e[i].2a+".3n=\'"+e[i].3n+"\'",1);e[i].3n=e[i].J.3n}}})});7 6Z(e){z U.Z(e.2L)&&!e.3m&&!e.J.5u};1s.1n("5d",7(e){y(e.8Y&&!e.6Y)e.6Y=""});6 P=(29<5.5)?"8X:":"";7 6X(e){6 f=16.3X("<"+P+e.3z.1q(1));y(e.3z.1q(-2)!="/>"){6 6V="</"+e.2W+">",n;1D((n=e.6W)&&n.3z!=6V){f.6t(n)}y(n)n.8W()}e.4R.6A(f,e)}});1F.24("17-8V",7(){1K=8;2r+="*{3H:22-2X}";8.3j=(29<5.5)?7(e){z e.1I}:7(e){z e.D.3j};8.3H=7(e){y(!1K.3j(e)){e.1r.2b="6T";y(e.D.6U=="1P")e.J.6U="2y";4k(e)}};7 4k(e){y(e!=1J&&e.D.1y!="3C"){4p(e);8U(e)}};6 3l=14.1i("3l");
6 5t=14.1i("5t");7 4p(e){y(!e.J.3k){6 p=e.59;y(p&&1K.3j(p)&&e==3l(p))z;6 f=3l(e);y(f&&f.D.8T=="1M"&&1K.3j(f)){4p(f);m=5s(e,e.D.3k);c=5s(f,f.D.3k);y(m<0||c<0){e.J.3k=m+c}1d{e.J.3k=4g.3g(c,m)}f.J.3k="8S"}}};1k(23(4p).13(/5c/g,"6N").13(/4N/g,"8R"));7 5s(e,v){z(v=="1P")?0:3c(e,v)};6 U=/^[.\\d][\\w%]*$/,A=/^(1P|6T)$/,N="[.\\\\d]";6 4l,6S;7 6O(e){4l(e);6S(e)};7 2g(H){4l=7(e){y(!3d.Z(e.D.12))2g(e);4k(e)};7 2g(e,v){y(!e.J.3J){y(!v)v=e.D.12;e.J.3J=(U.Z(v))?4g.3g(0,2B(e,v)):v;2T(e,"12",e.J.3J)}};7 5r(e){y(!3D(e)){6 l=e.3B;1D(l&&!1K.3j(l))l=l.3B}z(l||1J).1I};7 1H(e,v){y(3d.Z(v))z 25(4c(v)/3w*5r(e));z 3c(e,v)};6 2B=7(e,v){6 b=e.D["2X-5o"]=="3G-2X";6 a=0;y(2m&&!b)a+=4n(e)+3K(e);1d y(!2m&&b)a-=4n(e)+3K(e);z 1H(e,v)+a};7 4n(e){z e.2S-e.1I};7 3K(e){z 1H(e,e.D.8Q)+1H(e,e.D.8P)};1k(23(3K).13(/6R/g,"6Q").13(/8O/g,"8N"));2r+="*{1A:1M;27:1M;3I-12:1M;3g-12:1M}";7 1A(e){y(e.D["3I-12"]!=1U){e.1r.1A=e.D["3I-12"]}y(20(1A,e,e.D.1A!="1M")){1K.3H(e);2g(e);4m(e)}};1k(23(1A).13(/3I/g,"3g"));1K.1A=1A;1K.27
=27;7 4m(e){6 r=e.54();6 w=r.1W-r.1f;y(e.D.1A!="1M"&&w<=2B(e,e.D.1A)){e.J.12=2B(e,e.D.1A)}1d y(e.D.27!="1M"&&w>=2B(e,e.D.27)){e.J.12=2B(e,e.D.27)}1d{e.J.12=e.J.3J}};7 2x(e){y(20(2x,e,/^(2z|3C)$/.Z(e.D.1y)&&4o(e,"1f")!="1P"&&4o(e,"1W")!="1P"&&A.Z(4o(e,"12")))){5p(e);1K.3H(e)}};1K.2x=2x;7 5p(e){6 l=1H(e,e.J.52||e.D.1f);6 w=5r(e)-1H(e,e.D.1W)-l-8M(e);y(25(e.J.12)==w)z;e.J.12="";y(3D(e)||H||e.2S<w){y(!2m)w-=4n(e)+3K(e);y(w<0)w=0;e.J.3J=w;2T(e,"12",w)}};6 5q=0;4U(7(){6 i,w=(5q<1J.1I);5q=1J.1I;O(i 28 1A.1O){6 e=1A.1O[i];6 f=(25(e.J.12)==2B(e,e.D.1A));y(w&&f)e.J.12="";y(w==f)4m(e)}O(i 28 27.1O){6 e=27.1O[i];6 f=(25(e.J.12)==2B(e,e.D.27));y(!w&&f)e.J.12="";y(w!=f)4m(e)}O(i 28 2x.1O)5p(2x.1O[i])});y(26.8L!==1z){V.1n("12",N,2m?4l:4k)}V.1n("3I-12",N,1A);V.1n("3g-12",N,27);V.1n("1W",N,2x)};V.1n("3G-6P",N,7(e){y(e.D.8K!="8J"){e.8I=3c(e,e.D["3G-6P"])}});V.1n("2X-5o","22-2X",8.3H);V.1n("2X-5o","3G-2X",6O);6 1v=11 2U;1v.15(/6v/,"6u");1v.15(/12/,"2b");1v.15(/6w/,"5c");1v.15(/1f/,"2y");1v.15(
/8H/,"6N");1v.15(/1W/,"56");1k(1v.2V(23(2g)));2g();8G(1o)});1F.24("17-8F",7(){y(29<5.5)z;6 A="6J.5n.8E";6 F="6K:"+A+"(1l=\'%1\',8D=\'6H\')";6 5j=11 1N((26.8C||"-8B.8A")+"$","i");6 3h=[];7 5f(e){6 f=e.6M[A];y(f){f.1l=e.1l;f.6L=1o}1d{e.J.5m=F.13(/%1/,e.1l);3h.1b(e)}e.1l=4i};7 6y(e){e.1l=e.4D;e.6M[A].6L=1z};V.3i(/6I\\s*:\\s*([\\d.]+)/,7(m,o){z"6G:1;5m:6K:6J.5n.8z(6I="+((4c(m[o+1])*3w)||1)+")"});6 B=/5e(-5i)?\\s*:\\s*([^\\(};]*)43\\(([^\\)]+)\\)([^;}]*)/;V.3i(B,7(m,o){6 u=42(m[o+3]);z 5j.Z(u)?"5m:"+F.13(/6H/,"8y").13(/%1/,u)+";6G:1;5e"+(m[o+1]||"")+":"+(m[o+2]||"")+"1M"+(m[o+4]||""):m[o]});y(1s){1s.1n("5d,5l",7(e){y(e.2W=="8x"&&e.2L!="5i")z;5k(e);1j(e,"8w",7(){y(!4j&&60.8v=="1l"&&e.1l.6F(4i)==-1)5k(e)})});6 6D=/^3W:.*;6E/i;6 6C=1Z("17-6E.8u",2k);7 5k(e){y(5j.Z(e.1l)){6 i=11 8t(e.12,e.2b);i.8s=7(){e.12=i.12;e.2b=i.2b;i=1U};i.1l=e.1l;e.4D=e.1l;5f(e)}1d y(6D.Z(e.1l)){e.1l=6C+"?"+e.1l.1q(5)}};6 I=/^5i/i;6 6B=1Z("17-2M.4C",2k);1s.1n("2M",7(e){y(I.Z(e.2L)){6 o=16.3X("<2M 2L=68/x-67>")
;o.1r.12=e.D.12;o.1r.2b=e.D.2b;o.3W=6B;6 u=1Z(e.3W,3F(5h.2u));e.4R.6A(o,e);14.5g("2M");4H(o,"",u);z o}})}6 4j=1z;1j(26,"6z",7(){4j=1o;O(6 i=0;i<3h.K;i++)6y(3h[i])});1j(26,"8r",7(){O(6 i=0;i<3h.K;i++)5f(3h[i]);4j=1z})});1F.24("17-2z",7(){V.1n("1y","2z",4a,"3C");V.1n("5e(-8q)?","[^};]*2z",4b);6 4Z=(2m)?"1X":"2F";6 4h=7(){y(1X.D.5b!="2z"){y(1X.D.5a=="1M"){1X.J.8p="8o-8n";1X.J.5a="43("+4i+")"}1X.J.5b="2z"}4h=2s};6 2h=6x("5d");7 1v(f){z 2A.2V(23(f))};6 2A=11 2U;2A.15(/6w/,"5c");2A.15(/1f/,"2y");2A.15(/6v/,"6u");2A.15(/12/,"2b");2A.15(/1W/,"56");2A.15(/X/,"Y");7 3f(e){z(e)?3D(e)||3f(e.59):1z};7 4f(e,p,3e){32("16.1Y."+e.2a+".J.4f(\'"+p+"\',\'"+3e+"\')",0)};7 4b(e){y(20(4b,e,e.D.5b=="2z"&&!e.61(1X))){4h();58(e);8m(e);4V(e)}};7 4V(e){2h.1l=e.D.5a.1q(5,-2);6 p=(e.6c)?e:e.59;p.6t(2h);57(e);8l(e);p.8k(2h)};7 58(e){e.1r.3E=e.D.3E;y(!3f(e)){6 3e="(25(J.3A)+16."+4Z+".6s)||0";4f(e,"3E",3e)}};1k(1v(58));7 57(e){6 p=3f(e)?"3E":"3A";e.J[p]=55(e,e.1r.3E)-e.54().1f-e.8j+2};1k(1v(57));7 55(e,p){3
4(p){1m"1f":1m"2y":z 0;1m"1W":1m"56":z 1J.1I-2h.2S;1m"8i":z(1J.1I-2h.2S)/2;8h:y(3d.Z(p)){z 25((1J.1I-2h.2S)*4c(p)/3w)}2h.1r.1f=p;z 2h.3A}};1k(1v(55));7 4a(e){y(20(4a,e,3D(e))){2T(e,"1y","3C");2T(e,"1f",e.D.1f);2T(e,"2y",e.D.2y);4h();y(1K)1K.2x(e);49(e)}};7 49(e,r){8g(e,r);4Y(e,r,1o);y(!e.J.4d&&e.D.4X=="1P"&&e.D.1W!="1P"){6 l=1J.1I-1H(e,e.D.1W)-1H(e,e.J.52)-e.1I;y(e.D.8f=="1P")l=25(l/2);y(3f(e.3B))e.J.4e+=l;1d e.J.50=l}53(e);8e(e)};7 53(e){y(e.D.12!="1P"){6 r=e.54();6 w=e.2S-1J.1I+r.1f-2;y(w>=0){w=4g.3g(3c(e,e.D.12)-w,0);2T(e,"12",w)}}};1k(1v(53));7 4Y(e,r){y(!r&&3d.Z(e.D.12)){e.J.2g=e.D.12}y(e.J.2g){e.J.12=1H(e,e.J.2g)}y(r){y(!e.J.4d)z}1d{e.J.50=0;e.J.52=e.D.1f;e.J.4d=e.D.1W!="1P"&&e.D.1f=="1P"}e.J.1f="";e.J.51=4W(e);e.J.4e=e.J.51;y(!r&&!3f(e.3B)){6 3e="J.51+J.50+16."+4Z+".6s";4f(e,"4e",3e)}};1k(1v(4Y));7 4W(e){6 s=e.3A,n=1;y(e.J.4d){s=1J.1I-e.2S-1H(e,e.D.1W)}y(e.D.4X!="1P"){s-=1H(e,e.D.4X)}1D(e=e.3B){y(e.D.1y!="8d")n=-1;s+=e.3A*n}z s};1k(1v(4W));7 1H(e,v){y(3d.Z(v))z 25(4c(
v)/3w*1J.1I);z 3c(e,v)};1k(1v(1H));7 6r(){6 e=4b.1O;O(6 i 28 e)4V(e[i]);e=4a.1O;O(i 28 e){49(e[i],1o);49(e[i],1o)}48=0};6 48;4U(7(){y(!48)48=32(6r,0)})});1F.24("17-8c-1V",7(){14.24("8b-8a",7(){1V[">"]=7(r,f,t,n){6 e,i,j;O(i=0;i<f.K;i++){6 s=6q(f[i]);O(j=0;(e=s[j]);j++)y(4T(e,t,n))r.1b(e)}};1V["+"]=7(r,f,t,n){O(6 i=0;i<f.K;i++){6 e=47(f[i]);y(e&&4T(e,t,n))r.1b(e)}};1V["@"]=7(r,f,a){6 t=2R[a].Z;6 e,i;O(i=0;(e=f[i]);i++)y(t(e))r.1b(e)};1B["4N-89"]=7(e){z!4S(e)};1B["4Q"]=7(e,c){c=11 1N("^"+c,"i");1D(e&&!e.2H("4Q"))e=e.4R;z e&&c.Z(e.2H("4Q"))};1p.6p=/\\\\:/g;1p.3x="@";1p.3a={};1p.13=7(m,a,n,c,v){6 k=8.3x+m;y(!2R[k]){a=8.3Z(a,c||"",v||"");2R[k]=a;2R.1b(a)}z 2R[k].1c};1p.38=7(s){s=s.13(8.6p,"|");6 m;1D(m=s.19(8.19)){6 r=8.13(m[0],m[1],m[2],m[3],m[4]);s=s.13(8.19,r)}z s};1p.3Z=7(p,t,v){6 a={};a.1c=8.3x+2R.K;a.66=p;t=8.3a[t];t=t?t(8.2H(p),2w(v)):1z;a.Z=11 3b("e","z "+t);z a};1p.2H=7(n){34(n.5Z()){1m"1c":z"e.1c";1m"3U":z"e.1x";1m"O":z"e.6o";1m"2u":y(4P){z"23((e.3z.19(/2u=\\\\46?([^\\\
\s\\\\46]*)\\\\46?/)||[])[1]||\'\')"}}z"e.2H(\'"+n.13(N,":")+"\')"};1p.3a[""]=7(a){z a};1p.3a["="]=7(a,v){z a+"=="+2c.15(v)};1p.3a["~="]=7(a,v){z"/(^| )"+4O(v)+"( |$)/.Z("+a+")"};1p.3a["|="]=7(a,v){z"/^"+4O(v)+"(-|$)/.Z("+a+")"};6 6n=45;45=7(s){z 6n(1p.38(s))}});6 1p=14.1i("1p");6 H=/a(#[\\w-]+)?(\\.[\\w-]+)?:(65|62)/i;6 6l=/\\s*\\{\\s*/,6m=/\\s*\\}\\s*/,C=/\\s*\\,\\s*/;6 F=/(.*)(:4N-(88|87))/;3y.1C.1e({38:7(){8.1T();6 o=V.2t.K;6 2Q=8.18.2O(6m),r;6 2f,c,i,j;O(i=0;i<2Q.K;i++){r=2Q[i].2O(6l);2f=r[0].2O(C);c=r[1];O(j=0;j<2f.K;j++){2f[j]=c?8.6k(2f[j],c):""}2Q[i]=2f.2p("\\n")}8.18=2Q.2p("\\n");8.2t=V.2t.1q(o)},1u:7(){6 r,i;O(i=0;(r=8.2t[i]);i++)r.1u()},6k:7(s,c){y(V.6j.Z(s)){6 m;y(m=s.19(1L.39)){z 11 1L(m[1],m[2],c)}1d y(m=s.19(2d.39)){y(!2l||!H.Z(m)||2d.44.Z(m)){z 11 2d(s,m[1],m[2],m[3],c)}}1d z 11 1G(s,c)}z s+" {"+c+"}"}});V.1e({2t:[],1B:14.1i("1B"),36:{},2e:14.1i("2e"),1G:1G,2d:2d,1L:1L,2J:2J,2q:7(){6 p=8.1B+"|6i|6h|"+8.36;p=p.13(/(21|37)\\|/g,"");8.6j=11 1N("[>+~\\[]|([:.])[\
\\\w-()]+\\\\1|:("+p+")");6 c="[^\\\\s(]+\\\\s*[+~]|@\\\\d+|:(";1G.44=11 1N(c+p+")","g");2d.44=11 1N(c+8.1B+")","g");2d.39=11 1N("(.*):("+8.36+")(.*)");1L.39=/(.*):(6i|6h).*/;8.1T()},1u:7(){8.2P.1u();8.1T()},2w:7(s,p){z 2v?(3T(s.2u,p)||s.18):8.1T(s)},1j:7(e,t,h){1j(e,t,h)}});7 1G(s,c){8.1c=V.2t.K;8.1x=1G.3x+8.1c;s=(s).19(F)||s||"*";8.40=s[1]||s;8.4M=1G.6g(8.40)+"."+8.1x+(s[2]||"");8.18=c;8.39=11 1N("\\\\s"+8.1x+"(\\\\s|$)","g");V.2t.1b(8);8.1S()};1t.1e({1h:1G,1g:7(){z 8.4M+" {"+8.18+"}"},1S:2s,15:7(e){e.1x+=" "+8.1x},3v:7(e){e.1x=e.1x.13(8.39,"$1")},1u:7(){6 m=V.2e[" *."+8.1x]=14(8.40);O(i=0;i<m.K;i++)8.15(m[i])}});1G.3x="5Y";1G.6f=/>/g;1G.6g=7(s){s=1p.38(s);z s.13(8.44,"").13(8.6f," ")};7 2d(s,a,d,t,c){8.6e=a||"*";8.6d=V.36[d];8.4L=t;8.1T(s,c)};1G.1e({1h:2d,1u:7(){6 m=14(8.6e);O(6 i=0;i<m.K;i++){6 t=(8.4L)?14(8.4L,m[i]):[m[i]];y(t.K)8.6d.2q(m[i],t,8)}}});6 A=/^4I/;6 U=/^43\\s*\\(\\s*([^)]*)\\)$/;6 M={86:"85",84:"83",82:"81",80:"7Z"};6 6b=1Z("17-22.4C",2k)+"?";2r+=".2K{4K:1M
}";7 1L(s,p,c){8.1y=p;6 2N=c.19(1L.6a),m,e;y(2N){2N=2N[1];m=2N.2O(/\\s+/);O(6 i=0;(e=m[i]);i++){m[i]=A.Z(e)?{4I:e.1q(5,-1)}:(e.7Y(0)=="\'")?42(e):41(e)}2N=m}8.22=2N;8.1T(s,41(c))};1G.1e({1h:1L,1g:7(){z"."+8.1x+"{4K:7X}"},1S:7(){8.19=14(8.40);O(6 i=0;i<8.19.K;i++){6 r=8.19[i].J;y(!r[8.1y])r[8.1y]={18:""};r[8.1y].18+=";"+8.18;y(8.22!=1U)r[8.1y].22=8.22}},1u:7(){y(8.22==1U)z;O(6 i=0;i<8.19.K;i++){8.3Z(8.19[i])}},3Z:7(t){6 g=t.J[8.1y];y(g){6 c=[].4J(g.22||"");O(6 j=0;j<c.K;j++){y(3Y c[j]=="2M"){c[j]=t.2H(c[j].4I)}}c=c.2p("");6 u=c.19(U);6 h=1L[u?"69":"4E"].13(/%1/,8.1x);6 4G=g.18.13(/\'/g,\'"\');6 4F=M[8.1y+7W(t.6c)];y(u){6 p=16.3X(h);t.7V(4F,p);p.3W=6b;4H(p,4G,2c.3v(u[1]))}1d{h=h.13(/%2/,4G).13(/%3/,c);t.7U(4F,h)}t.J[8.1y]=1U}}});1L.6a=/22\\s*:\\s*([^;]*)(;|$)/;1L.69="<2M 3U=\'2K %1\' 2K 12=3w% 2b=0 2L=68/x-67>";1L.4E="<17:! 3U=\'2K %1\' 2K 1r=\'%2\'>%3</17:!>";7 2J(n,a){8.66=n;8.2q=a;8.2I={};V.36[n]=8};1t.1e({1h:2J,20:7(i){6 c=i[2];i.1c=c.1c+i[0].2a;y(!8.2I[i.1c]){6 t=i[1],j;O
(j=0;j<t.K;j++)c.15(t[j]);8.2I[i.1c]=i}},35:7(i){y(8.2I[i.1c]){6 c=i[2];6 t=i[1],j;O(j=0;j<t.K;j++)c.3v(t[j]);3u 8.2I[i.1c]}}});V.1B.1g=7(){6 t=[],p;O(p 28 8){y(8[p].K>1)p+="\\\\([^)]*\\\\)";t.1b(p)}z t.2p("|")};V.1B["21"]=7(e){z e.D["17-21"]=="21"};V.1B["37"]=7(e){z e.D["17-21"]=="37"};6 64=(29<5.5)?"7T":"7S";6 63=(29<5.5)?"7R":"7Q";V.36.1g=V.1B.1g;6 3s=11 2J("65",7(e){6 i=1a;V.1j(e,64,7(){3s.20(i)});V.1j(e,63,7(){3s.35(i)})});6 3t=11 2J("7P",7(e){6 i=1a;V.1j(e,"7O",7(){3t.35(i);3t.20(i)});V.1j(e,"7N",7(){3t.35(i)});y(e==16.7M){3t.20(i)}});6 3V=11 2J("62",7(e){6 i=1a;V.1j(e,"7L",7(){3V.20(i)})});1j(16,"7K",7(){6 i=3V.2I,j;O(j 28 i)3V.35(i[j]);i=3s.2I;O(j 28 i)y(!i[j][0].61(60.7J))3s.35(i[j])});2o(1p);1p.1e({2H:7(n){34(n.5Z()){1m"3U":z"e.1x.13(/\\\\b\\\\s*5Y\\\\d+/g,\'\')";1m"1l":z"(e.4D||e.1l)"}z 8.1T(n)}});2n.15(/::/,":");3r.15(/\\\\([\\7I-7H-F]{1,4})/,7(m,o){m=m[o+1];z"\\\\u"+"7G".1q(m.K)+m})});2G=1o;y(2m)1k(3T("17-7F.5X",2k));V.1S();y(2l&&1s)1s.1S();y(33)1F.1S();1d{2F.7E
(1Z("17-3q.4C",2k));1j(16,"7D",7(){y(16.5W=="33")32(1F.1S,0)})}}2j(e){31("4B [0]: "+e.5V)}5U{}};',62,634,'||||||var|function|this||||||||||||||||||||||||||if|return||||currentStyle||||||runtimeStyle|length||||for|||||||ie7CSS||||test||new|width|replace|cssQuery|add|document|ie7|cssText|match|arguments|push|id|else|specialize|left|toString|constructor|valueOf|addEventHandler|eval|src|case|addRecalc|true|AttributeSelector|slice|style|ie7HTML|Common|recalc|_0|that|className|position|false|minWidth|pseudoClasses|prototype|while|fr|IE7|Rule|getPixelWidth|clientWidth|viewport|ie7Layout|PseudoElement|none|RegExp|elements|auto|recalcs|styleSheets|init|inherit|null|selectors|right|body|all|makePath|register|link|content|String|addModule|parseInt|window|maxWidth|in|appVersion|uniqueID|height|Quote|DynamicRule|cache|se|fixWidth|_1|klass|catch|path|isHTML|quirksMode|encoder|ICommon|join|apply|HEADER|DUMMY|rules|href|httpRequest|getText|fixRight|top|fixed|_2|getFixedWidth|try|thisElement
|ancestor|documentElement|loaded|getAttribute|instances|DynamicPseudoClass|ie7_anon|type|object|co|split|screen|ru|attributeSelectors|offsetWidth|setOverrideStyle|ParseMaster|exec|tagName|box|print|media|_3|alert|setTimeout|complete|switch|unregister|dynamicPseudoClasses|visited|parse|MATCH|tests|Function|getPixelValue|PERCENT|ex|_4|max|_5|addFix|hasLayout|marginTop|firstElementChild|disabled|value|size|font|load|safeString|_6|_7|delete|remove|100|PREFIX|StyleSheet|outerHTML|offsetLeft|offsetParent|absolute|isFixed|backgroundPositionX|getPath|border|boxSizing|min|fixedWidth|getPaddingWidth|fixes|Parser|self|styleSheet|st|modules|_8|x01|loadFile|class|_9|data|createElement|typeof|create|selector|decode|getString|url|COMPLEX|parseSelector|x22|nextElementSibling|_10|_11|_12|_13|parseFloat|autoLeft|pixelLeft|setExpression|Math|_14|BLANK_GIF|_15|_16|applyWidth|resizeWidth|getBorderWidth|getDefinedStyle|collapseMarginTop|_17|_18|_19|refresh|el|ca|getElementsByTagName|version|escap
eChar|ancestorOf|_20|Error|htc|pngSrc|ANON|po|cs|addTimer|attr|concat|display|target|selectorText|first|regEscape|isMSIE|lang|parentNode|previousElementSibling|compareTagName|addResize|_21|getScreenLeft|marginLeft|positionLeft|_22|shiftLeft|screenLeft|_23|clipWidth|getBoundingClientRect|getOffsetLeft|bottom|setOffsetLeft|backgroundLeft|parentElement|backgroundImage|backgroundAttachment|Top|img|background|_24|clearCache|location|image|_25|_26|input|filter|Microsoft|sizing|resizeRight|_27|layoutWidth|_28|lastElementChild|clicked|submit|_29|Fix|_30|decoder|_31|parser|_32|gi|_33|styles|createStyleSheet|isXML|caching|_34|compareNamespace|getDocument|continue|links|error|callee|ignoreCase|_35|_36|ie7_debug|finally|description|readyState|js|ie7_class|toLowerCase|event|contains|active|_37|_38|hover|name|scriptlet|text|OBJECT|CONTENT|_39|canHaveChildren|dynamicPseudoClass|attach|CHILD|simple|after|before|UNKNOWN|createRule|B1|B2|_40|htmlFor|NS_IE|childElements|_41|scrollLeft|appendCh
ild|Height|Width|Left|createTempElement|_42|onbeforeprint|replaceChild|_43|_44|B64|base64|indexOf|zoom|scale|opacity|DXImageTransform|progid|enabled|filters|Bottom|borderBox|spacing|margin|padding|applyHeight|0cm|verticalAlign|en|nextSibling|_45|title|_46|BUTTON|button|reset|onclick|clearInterval|PIXEL|removeEventHandler|pop|_47|_48|lastIndexOf|_49|_50|_51|_52|imports|trash|ie7_recalc|getInlineStyles|innerHTML|nodeType|ST|select|Array|_53|mimeType|Boolean|lastChild|firstChild|ES|_54|_55|_56|_57|_58|DE|status|scripts|search|onreadystatechange|addBehavior|quirks|0000|fA|da|srcElement|onmouseup|onmousedown|activeElement|onblur|onfocus|focus|onmouseleave|onmouseout|onmouseenter|onmouseover|insertAdjacentHTML|insertAdjacentElement|Number|inline|charAt|beforeEnd|after1|afterEnd|after0|afterBegin|before1|beforeBegin|before0|letter|line|child|level2|css|css2|static|clipHeight|marginRight|positionTop|default|center|clientLeft|removeChild|setOffsetTop|backgroundTop|repeat|no|backgroun
dRepeat|attachment|onafterprint|onload|Image|php|propertyName|onpropertychange|INPUT|crop|Alpha|png|trans|IE7_PNG_SUFFIX|sizingMethod|AlphaImageLoader|graphics|fixHeight|Right|cellSpacing|collapse|borderCollapse|IE7_BOX_MODEL|getMarginWidth|Margin|Padding|paddingRight|paddingLeft|last|0px|styleFloat|collapseMarginBottom|layout|removeNode|HTML|alt|onsubmit|form|click|textarea|label|abbr|pow|67em|h6|83em|h5|1em|h4|17em|h3|5em|h2|2em|h1|html4|setInterval|9999|rect|clip|block|px|onunload|detachEvent|attachEvent|onresize|x27|import|namespace|ma|bprint|bscreen|ball|write|item|innerText|getTextContent|scopeName|toUpperCase|ownerDocument|Document|XML|URL|xml|unknown|previousSibling|number|successfully|ie5|instanceOf|Object|common|caller|gif|blank|file|loading|responseText|200|send|GET|open|XMLHTTP|ActiveXObject|Msxml2|ScriptEngineMajorVersion|ms_|ie7_off|CSS1Compat|compatMode|MSIE|navigator|alpha'.split('|'),0,{}))
diff --git a/web/static/js/ie7/ie7-xml-extras.js b/web/static/js/ie7/ie7-xml-extras.js
deleted file mode 100644
index 97846f6..0000000
--- a/web/static/js/ie7/ie7-xml-extras.js
+++ /dev/null
@@ -1,6 +0,0 @@
-/*
- IE7, version 0.9 (alpha) (2005-08-19)
- Copyright: 2004-2005, Dean Edwards (http://dean.edwards.name/)
- License: http://creativecommons.org/licenses/LGPL/2.1/
-*/
-function XMLHttpRequest(){var l=(ScriptEngineMajorVersion()>=5)?"Msxml2":"Microsoft";return new ActiveXObject(l+".XMLHTTP")};function DOMParser(){};DOMParser.prototype={toString:function(){return"[object DOMParser]"},parseFromString:function(s,c){var x=new ActiveXObject("Microsoft.XMLDOM");x.loadXML(s);return x},parseFromStream:new Function,baseURI:""};function XMLSerializer(){};XMLSerializer.prototype={toString:function(){return"[object XMLSerializer]"},serializeToString:function(r){return r.xml||r.outerHTML},serializeToStream:new Function};
diff --git a/web/static/js/ie7/ie7.gif b/web/static/js/ie7/ie7.gif
deleted file mode 100644
index 64a2c2d..0000000
Binary files a/web/static/js/ie7/ie7.gif and /dev/null differ
diff --git a/web/static/js/ie7/test-trans.png b/web/static/js/ie7/test-trans.png
deleted file mode 100644
index e187e2c..0000000
Binary files a/web/static/js/ie7/test-trans.png and /dev/null differ
diff --git a/web/static/js/ie7/test.html b/web/static/js/ie7/test.html
deleted file mode 100644
index ab78f46..0000000
--- a/web/static/js/ie7/test.html
+++ /dev/null
@@ -1,100 +0,0 @@
-<html xmlns:html="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-<head>
-<title>IE7 Test Page</title>
-<meta name="author" content="Dean Edwards"/>
-<!-- compliance patch for microsoft browsers -->
-<!--[if lt IE 7]>
-<script src="ie7-standard-p.js" type="text/javascript"></script>
-<script src="ie7-css3-selectors.js" type="text/javascript"></script>
-<script src="ie7-css-strict.js" type="text/javascript"></script>
-<![endif]-->
-<style type="text/css">
- body {background-color: #ccc;}
- img {border: none;}
- h1 {font-family: monospace;}
- h2 {background-color: black; color: white; font-style: normal;}
- h3 {margin: 0.1em 0;}
-</style>
-</head>
-
-<body>
-<div class="document">
-<div class="header">
-<h1>IE7 { css2: auto; }</h1>
-<hr />
-</div>
-
-<div class="content">
-
-<h2>Black & White Test</h2>
-
-<h3>Legend</h3>
-<style type="text/css">
- div.legend {height: 20px; font-weight: bold; text-indent: 4px;}
- #fail {background-color: black; color: white;}
- #pass {background-color: white; color: black;}
-</style>
-<div class="legend" id="pass">PASS</div>
-<div class="legend" id="fail">FAIL</div>
-
-<hr />
-
-<h3>ie7-html4.js</h3>
-<style type="text/css">
- #ie7-html4 {background-color: black; height: 20px;}
- #ie7-html4 abbr {display: block; background-color: white; height: 20px;}
-</style>
-<div id="ie7-html4"><abbr> </abbr></div>
-
-<h3>ie7-layout.js</h3>
-<style type="text/css">
- #ie7-layout {background-color: black; height: 20px; overflow: hidden;}
- #ie7-layout div.box {position: relative; top: -40px; background-color: white;
- height: 40px; border-top: 20px black solid;}
-</style>
-<div id="ie7-layout"><div class="box"></div></div>
-
-<h3>ie7-graphics.js</h3>
-<style type="text/css">
- #ie7-graphics {background-color: white; height: 20px;}
- #ie7-graphics div.box {height: 20px; background: url(test-trans.png);}
-</style>
-<div id="ie7-graphics"><div class="box"></div></div>
-
-<h3>ie7-fixed.js</h3>
-<style type="text/css">
- #ie7-fixed {background-color: white; height: 20px;}
- #ie7-fixed div.box {position: fixed; top: -20px; background-color: black; height: 20px;}
-</style>
-<div id="ie7-fixed"><div class="box"></div></div>
-
-<h3>ie7-css2-selectors.js</h3>
-<style type="text/css">
- #ie7-css2-selectors {background-color: black; height: 20px;}
- #ie7-css2-selectors > span {display: block; background-color: white; height: 20px;}
-</style>
-<div id="ie7-css2-selectors"><span> </span></div>
-
-<h3>ie7-css3-selectors.js</h3>
-<style type="text/css">
- #ie7-css3-selectors {background-color: black; height: 20px;}
- #ie7-css3-selectors:empty {background-color: white;}
-</style>
-<div id="ie7-css3-selectors"></div>
-
-<h3>ie7-css-strict.js</h3>
-<style type="text/css">
- #ie7-css-strict {background-color: black; height: 20px;}
- #ie7-css-strict > span.strict {display: block; background-color: white; height: 20px;}
- #ie7-css-strict > span {display: block; background-color: black}
-</style>
-<div id="ie7-css-strict"><span class="strict"></span></div>
-</div>
-
-<div class="footer">
-<hr />
-<a href="http://dean.edwards.name/IE7/"><img src="ie7.gif" width="80" height="15" alt="IE7 Enhanced"/></a>
-</div>
-</div>
-</body>
-</html>
diff --git a/web/static/js/jifty.js b/web/static/js/jifty.js
deleted file mode 100644
index e674eb4..0000000
--- a/web/static/js/jifty.js
+++ /dev/null
@@ -1,333 +0,0 @@
-/* An empty class so we can create things inside it */
-var Jifty = Class.create();
-
-/* General methods for dealing with forms, actions, and fields */
-/* Actions */
-var Action = Class.create();
-Action.prototype = {
- // New takes the moniker, a string
- initialize: function(moniker) {
- this.moniker = moniker;
-
- this.register = $('J:A-' + this.moniker); // Simple case -- no ordering information
- if (! this.register) {
- // We need to go looking
- var elements = document.getElementsByTagName('input');
- for (var i = 0; i < elements.length; i++) {
- if (Form.Element.getMoniker(elements[i]) == this.moniker) {
- this.register = elements[i];
- break;
- }
- }
- }
-
- this.form = Form.Element.getForm(this.register);
- this.actionClass = this.register.value;
- },
-
- // Returns an Array of all fields in this Action
- fields: function() {
- var elements = new Array;
- var possible = Form.getElements(this.form);
-
- for (var i = 0; i < possible.length; i++) {
- if (Form.Element.getMoniker(possible[i]) == this.moniker)
- elements.push(possible[i]);
- }
- return elements;
- },
-
- // Serialize and return all fields needed for this action
- serialize: function() {
- var fields = this.fields();
- var serialized = new Array;
-
- for (var i = 0; i < fields.length; i++) {
- serialized.push(Form.Element.serialize(fields[i].id));
- }
- return serialized.join('&');
- },
-
- // Validate the action
- validate: function() {
- show_wait_message();
- var id = this.register.id;
-
- new Ajax.Request(
- '/validator.xml', // Right now, the URL is actually completely irrelevant
- {
- asynchronous: 1,
- method: "get",
- parameters: this.serialize() + "&J:VALIDATE=1",
- onComplete:
- function (request) {
- var response = request.responseXML.documentElement;
- for (var action = response.firstChild; action != null; action = action.nextSibling) {
- if ((action.nodeName != 'action') || (action.getAttribute("id") != id))
- continue;
- for (var field = action.firstChild; field != null; field = field.nextSibling) {
- // Possibilities for field.nodeName: it could be #text (whitespace),
- // or 'blank' (the field was blank, don't mess with the error div), or 'ok'
- // (clear the error div!) or 'error' (fill in the error div!)
- if (field.nodeName == 'error') {
- var err_div = document.getElementById(field.getAttribute("id"));
- if (err_div != null) {
- err_div.innerHTML = field.firstChild.data;
- }
- } else if (field.nodeName == 'ok') {
- var err_div = document.getElementById(field.getAttribute("id"));
- if (err_div != null) {
- err_div.innerHTML = '';
- }
- }
- }
- }
- return true;
- }
- }
- );
- hide_wait_message();
- return false;
- },
-
- submit: function() {
- show_wait_message();
- new Ajax.Request(
- '/empty',
- { parameters: this.serialize() }
- );
- hide_wait_message();
- }
-};
-
-
-
-/* Forms */
-// Return an Array of Actions that are in this form
-Form.getActions = function (element) {
- var elements = new Array;
- var possible = Form.getElements(element);
-
- for (var i = 0; i < possible.length; i++) {
- if (Form.Element.isRegistration(possible[i]))
- elements.push(new Action(Form.Element.getMoniker(possible[i])));
- }
-
- return elements;
-};
-
-/* Fields */
-// Get the moniker for this form element
-// Takes an element or an element id
-Form.Element.getMoniker = function (element) {
- // if we have an element id, get the element itself
- if (typeof(element) == "string") {
- element = $(element);
- }
- if (/^J:A:F(:F)*-[^-]+-.+$/.test(element.name)) {
- var bits = element.name.match(/^J:A:F(?::F)*-[^-]+-(.+)$/);
- return bits[1];
- } else if (/^J:A-(\d+-)?.+$/.test(element.name)) {
- var bits = element.name.match(/^J:A-(?:\d+-)?(.+)$/);
- return bits[1];
- } else {
- return null;
- }
-};
-
-// Get the Action for this form element
-// Takes an element or an element id
-Form.Element.getAction = function (element) {
- // if we have an element id, get the element itself
- if (typeof(element) == "string") {
- element = $(element);
- }
- var moniker = Form.Element.getMoniker(element);
- return new Action(moniker);
-}
-
-// Returns true if this form element is the registration for its action
-Form.Element.isRegistration = function (element) {
- return /^J:A-/.test(element.name)
-};
-
-// Validates the action this form element is part of
-Form.Element.validate = function (element) {
- Form.Element.getAction(element).validate();
-};
-
-// Form elements should AJAX validate if the CSS says so
-Behaviour.register({
- 'input.ajaxvalidation': function(elt) {
- elt.onblur = function () {
- Form.Element.validate(this);
- }
- }
-});
-
-// Look up the form that this element is part of -- this is sometimes
-// more complicated than you'd think because the form may not exist
-// anymore, or the element may have been inserted into a new form.
-// Hence, we may need to walk the DOM.
-Form.Element.getForm = function (element) {
- if (element.form)
- return element.form;
-
- for (var elt = element.parentNode; elt != null; elt = elt.parentNode) {
- if (elt.nodeName == 'FORM') {
- element.form = elt;
- return elt;
- }
- }
- return null;
-}
-
-function serialize(thing) {
- var serialized = new Array;
- for (n in thing) {
- if (typeof(thing[n]) == "string" && thing[n].length)
- serialized.push(encodeURIComponent(n) + '=' +
- encodeURIComponent(thing[n]));
- }
- return serialized.join('&');
-}
-
-var fragments = {};
-var current_args = {};
-function region(name, args, path) {
- fragments[name] = {name: name, args: args, path: path};
- current_args[name] = {};
-}
-
-function update_region() {
- show_wait_message();
- arguments = arguments[0];
- var name = arguments['name'];
-
- var args = {};
- for (var n in fragments[name].args) {
- args[n] = fragments[name].args[n];
- }
- for (var n in current_args) {
- if (typeof(current_args[n]) == "string") {
- args[n] = current_args[n];
- var parsed = n.match(/J:NV-region-(.*?)\.(.*)/);
-
- if ((parsed != null) && (parsed.length == 3) && (parsed[1] == name)) {
- args[parsed[2]] = current_args[n];
- }
- }
- }
- for (var n in arguments['args']) {
- args[n] = arguments['args'][n];
- if (n.indexOf('J:NV-') != 0) {
- current_args['J:NV-region-'+name+'.'+n] = args[n];
- args['J:NV-region-'+name+'.'+n] = args[n];
- }
- }
- var path;
- if (arguments['fragment'] != null) {
- path = arguments['fragment'];
- } else {
- path = fragments[name].path;
- }
- args['J:NV-region-'+name] = path;
- current_args['J:NV-region-'+name] = path;
-
- for (var i = 0; i < document.forms.length; i++) {
- var form = document.forms[i];
- for (var n in args) {
- if ((typeof(args[n]) == "string") && (/^J:NV-/.test(n))) {
- if (form[n]) {
- form[n].value = args[n];
- } else {
- var hidden = document.createElement('input');
- hidden.setAttribute('type', 'hidden');
- hidden.setAttribute('name', n);
- hidden.setAttribute('id', n);
- hidden.setAttribute('value', args[n]);
- form.appendChild(hidden);
- }
- }
- }
- }
-
- args['J-NAME'] = name;
- args['J-PATH'] = document.URL;
-
- var query = serialize(args);
- if (arguments['submit']) {
- var a = new Action(arguments['submit']);
- query = query + '&' + a.serialize();
- }
-
- new Ajax.Updater('region-' + name,
- path,
- { parameters: query,
- onComplete: function () { Behaviour.apply();
- hide_wait_message();
- },
- evalScripts: true }
- );
-}
-
-function trace( msg ){
- if( typeof( jsTrace ) != 'undefined' ){
- jsTrace.send( msg );
- }
-}
-
-
-function show_wait_message (){
- chunk = document.getElementById('jifty-wait-message');
- if (chunk) { chunk.style.display= 'block';}
-}
-
-function hide_wait_message (){
-
- chunk = document.getElementById('jifty-wait-message');
- if (chunk) { chunk.style.display = "none";}
-}
-
-
-
-Jifty.Autocompleter = Class.create();
-Object.extend(Object.extend(Jifty.Autocompleter.prototype, Ajax.Autocompleter.prototype), {
- initialize: function(element, update, url, options) {
- this.baseInitialize(element, update, options);
- this.options.asynchronous = true;
- this.options.onComplete = this.onComplete.bind(this);
- this.options.defaultParams = this.options.parameters || null;
- this.url = url;
- },
-
- getUpdatedChoices: function() {
- entry = encodeURIComponent("J:A-autocomplete")
- + "=" +encodeURIComponent("Jifty::Action::Autocomplete");
-
- entry += '&' + encodeURIComponent("J:A:F-argument-autocomplete")
- + "=" + encodeURIComponent(this.options.paramName);
-
- entry += '&' + encodeURIComponent("J:A:F-action-autocomplete")
- + "=" + encodeURIComponent(
- Form.Element.getMoniker(this.options.paramName)
- );
-
- entry += '&'+ encodeURIComponent("J:ACTIONS") + '=' + encodeURIComponent("autocomplete");
-
-
- this.options.parameters = this.options.callback ?
- this.options.callback(this.element, entry) : entry;
-
- if(this.options.defaultParams)
- this.options.parameters += '&' + this.options.defaultParams;
-
- var action = Form.Element.getAction(this.options.paramName);
- this.options.parameters += '&' + action.serialize();
-
- new Ajax.Request(this.url, this.options);
- }
-
-
-});
-
diff --git a/web/static/js/jsTrace.js b/web/static/js/jsTrace.js
deleted file mode 100644
index 5e8c93e..0000000
--- a/web/static/js/jsTrace.js
+++ /dev/null
@@ -1,12 +0,0 @@
-/*------------------------------------------------------------------------------
-Function: jsTrace()
-Author: Aaron Gustafson (aaron at easy-designs dot net)
-Creation Date: 26 October 2005
-Version: 1.0
-Homepage: http://www.easy-designs.net/code/jsTrace/
-License: Creative Commons Attribution-ShareAlike 2.0 License
- http://creativecommons.org/licenses/by-sa/2.0/
-Note: If you change or improve on this script, please let us know by
- emailing the author (above) with a link to your demo page.
-------------------------------------------------------------------------------*/
-var jsTrace = { debugging_on: false, window: null, viewport: null, init: function(){ if( !document.getElementsByTagName || !document.getElementById || !document.createElement || !document.createTextNode ) return; jsTrace.createWindow(); jsTrace.debugging_on = true;}, createWindow: function(){ jsTrace.window = document.createElement( 'div' ); jsTrace.window.style.background = '#000'; jsTrace.window.style.font = '80% "Lucida Grande", "Lucida Sans Unicode", Helvetica, Arial, sans-serif'; jsTrace.window.style.padding = '2px'; jsTrace.window.style.position = 'absolute'; jsTrace.window.style.top = '50px'; jsTrace.window.style.left = '700px'; jsTrace.window.style.height = '360px'; jsTrace.window.style.zIndex = '100'; jsTrace.window.style.minHeight = '150px'; jsTrace.window.style.width = '190px'; jsTrace.window.style.minWidth = '150px'; var x = document.createElement('span'); x.style.border = '1px solid #000'; x.style.cursor = 'pointer'; x.style.color = '#000'; x.style.display = 'bl
ock'; x.style.lineHeight = '.5em'; x.style.padding = '0 0 3px'; x.style.position = 'absolute'; x.style.top = '4px'; x.style.right = '4px'; jsTrace.addEvent( x, 'click', function(){ jsTrace.kill();} ); x.setAttribute( 'title', 'Close jsTrace Debugger' ); x.appendChild( document.createTextNode( 'x' ) ); jsTrace.window.appendChild( x ); var sh = document.createElement('div'); sh.style.position = 'absolute'; sh.style.bottom = '3px'; sh.style.right = '3px'; var sg = document.createElement('span'); sg.style.border = '5px solid #ccc'; sg.style.borderLeftColor = sg.style.borderTopColor = '#000'; sg.style.cursor = 'pointer'; sg.style.color = '#ccc'; sg.style.display = 'block'; sg.style.height = '0'; sg.style.width = '0'; sg.style.overflow = 'hidden'; sg.setAttribute( 'title', 'Resize the jsTrace Debugger' ); if( typeof( Drag ) != 'undefined' ){ sg.xFrom = 0; sg.yFrom = 0; Drag.init( sg, null, null, null, null, null, true, true ); sg.onDrag = function( x, y ){ jsTrace.resizeX( x, this
); jsTrace.resizeY( y, this );}; sh.appendChild( sg ); jsTrace.window.appendChild( sh );} var tools = document.createElement( 'div' ); tools.style.fontSize = '.7em'; tools.style.fontVariant = 'small-caps'; tools.style.lineHeight = '10px'; tools.style.position = 'absolute'; tools.style.bottom = '5px'; tools.style.left = '3px'; var dl = document.createElement( 'span' ); dl.style.color = '#ccc'; dl.style.padding = '0 10px 0 0'; dl.style.overflow = 'hidden'; dl.style.cursor = 'pointer'; dl.setAttribute( 'title', 'Add a Delimeter' ); dl.appendChild( document.createTextNode( 'delimit' ) ); jsTrace.addEvent( dl, 'click', function(){ jsTrace.sendDelimeter();} ); tools.appendChild( dl ); var cl = document.createElement( 'span' ); cl.style.color = '#ccc'; cl.style.padding = '0 10px 0 0'; cl.style.overflow = 'hidden'; cl.style.cursor = 'pointer'; cl.setAttribute( 'title', 'Add a Delimeter' ); cl.appendChild( document.createTextNode( 'clear' ) ); jsTrace.addEvent( cl, 'click', function
(){ jsTrace.clearWindow();} ); tools.appendChild( cl ); jsTrace.window.appendChild( tools ); var header = document.createElement( 'h3' ); header.style.background = '#ccc'; header.style.color = '#000'; header.style.cursor = 'pointer'; header.style.fontSize = '1em'; header.style.fontVariant = 'small-caps'; header.style.margin = '0 0 2px'; header.style.padding = '5px 10px'; header.style.lineHeight = '15px'; header.appendChild( document.createTextNode( 'jsTrace Debugger' ) ); jsTrace.window.appendChild( header ); jsTrace.viewport = document.createElement( 'pre' ); jsTrace.viewport.style.border = '1px solid #ccc'; jsTrace.viewport.style.color = '#ebebeb'; jsTrace.viewport.style.fontSize = '1.2em'; jsTrace.viewport.style.margin = '0'; jsTrace.viewport.style.padding = '0 3px'; jsTrace.viewport.style.position = 'absolute'; jsTrace.viewport.style.top = '30px'; jsTrace.viewport.style.left = '2px'; jsTrace.viewport.style.overflow = 'auto'; jsTrace.viewport.style.width = ( parseInt( jsT
race.window.style.width ) - 8 ) + 'px'; jsTrace.viewport.style.height = ( parseInt( jsTrace.window.style.height ) - 45 ) + 'px'; jsTrace.window.appendChild( jsTrace.viewport ); document.getElementsByTagName( 'body' )[0].appendChild( jsTrace.window ); if( typeof( Drag ) != 'undefined' ){ Drag.init( header, jsTrace.window );} }, resizeX: function( x, grip ){ var width = parseInt( jsTrace.window.style.width ); var newWidth = Math.abs( width - ( x - grip.xFrom ) ) + 'px'; if( parseInt( newWidth ) < parseInt( jsTrace.window.style.minWidth ) ) newWidth = jsTrace.window.style.minWidth; jsTrace.window.style.width = newWidth; grip.xFrom = x; jsTrace.viewport.style.width = ( parseInt( jsTrace.window.style.width ) - 8 ) + 'px';}, resizeY: function( y, grip ){ var height = parseInt( jsTrace.window.style.height ); var newHeight = Math.abs( height - ( y - grip.yFrom ) ) + 'px'; if( parseInt( newHeight ) < parseInt( jsTrace.window.style.minHeight ) ) newHeight = jsTrace.window.style.minHei
ght; jsTrace.window.style.height = newHeight; grip.yFrom = y; jsTrace.viewport.style.height = ( parseInt( jsTrace.window.style.height ) - 45 ) + 'px';}, send: function( text ){ text = text + "<br />"; jsTrace.viewport.innerHTML += text;}, sendDelimeter: function(){ jsTrace.send( '<span style="color: #f00">--------------------</span>' );}, clearWindow: function(){ jsTrace.viewport.innerHTML = '';}, kill: function() { jsTrace.window.parentNode.removeChild( jsTrace.window ); jsTrace.debugging_on = false;}, addEvent: function( obj, type, fn ){ if (obj.addEventListener) obj.addEventListener( type, fn, false ); else if (obj.attachEvent) { obj["e"+type+fn] = fn; obj[type+fn] = function() { obj["e"+type+fn]( window.event );}; obj.attachEvent( "on"+type, obj[type+fn] );} }, removeEvent: function ( obj, type, fn ) { if (obj.removeEventListener) obj.removeEventListener( type, fn, false ); else if (obj.detachEvent) { obj.detachEvent( "on"+type, obj[type+fn] ); obj[type+fn] = null; obj["
e"+type+fn] = null;} } }; jsTrace.addEvent( window, 'load', jsTrace.init );
\ No newline at end of file
diff --git a/web/static/js/key_bindings.js b/web/static/js/key_bindings.js
deleted file mode 100644
index c480f0c..0000000
--- a/web/static/js/key_bindings.js
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright 2004-2005, Best Practical Solutions, LLC
-// This Library is licensed to you under the same terms as Perl 5.x
-
-var bindings = Array;
-
-document.onkeydown = doClick;
-function doClick(e) {
- var targ;
- if (!e) var e = window.event;
- if (e.target) targ = e.target;
- else if (e.srcElement) targ = e.srcElement;
- if (targ.nodeType == 3) // defeat Safari bug
- targ = targ.parentNode;
-
- // safari or mozilla
- if ( ( ! e.metaKey && ! e.altKey && ! e.ctrlKey )
- && (
- (targ == document.body) ||
- (targ == document.getElementsByTagName('html')[0])
- ) ){
- var code = String.fromCharCode(e.keyCode);
- var binding = getKeyBinding(code);
- if (binding) {
- if (binding["action"] == "goto") {
- document.location = (binding["data"]);
- }
- else if (binding["action"] == "focus") {
- var elements = document.getElementsByName(binding["data"]);
- elements[0].focus();
- }
- else if (binding["action"] == "click") {
- var elements = document.getElementsByName(binding["data"]);
- elements[0].click();
- }
-
- }
-
-}
-}
-
-function addKeyBinding(key, action, data, label) {
- var binding = new Array;
- binding["action"] = action;
- binding["data"] = data;
- binding["label"] = label;
- bindings[key] = binding;
-}
-
-
-function getKeyBinding(key) {
- return(bindings[key]);
-}
-
-
-function writeKeyBindingLegend() {
- var content = '';
- for (var key in bindings) {
- if ( bindings[key]['label']) {
- content = content + '<dt>'+key + '</dt>' +'<dd>'+bindings[key]['label'] +'</dd>';
- }
- }
- if (content) {
- document.write('<div class="keybindings">');
- document.write('<dl class="keybindings">');
- document.write(content);
- document.write('</dl>');
- document.write('</div>');
- }
-}
diff --git a/web/static/js/prototype.js b/web/static/js/prototype.js
deleted file mode 100644
index fbdab91..0000000
--- a/web/static/js/prototype.js
+++ /dev/null
@@ -1,1038 +0,0 @@
-/* Prototype JavaScript framework, version 1.3.1
- * (c) 2005 Sam Stephenson <sam at conio.net>
- *
- * THIS FILE IS AUTOMATICALLY GENERATED. When sending patches, please diff
- * against the source tree, available from the Prototype darcs repository.
- *
- * Prototype is freely distributable under the terms of an MIT-style license.
- *
- * For details, see the Prototype web site: http://prototype.conio.net/
- *
-/*--------------------------------------------------------------------------*/
-
-var Prototype = {
- Version: '1.3.1',
- emptyFunction: function() {}
-}
-
-var Class = {
- create: function() {
- return function() {
- this.initialize.apply(this, arguments);
- }
- }
-}
-
-var Abstract = new Object();
-
-Object.extend = function(destination, source) {
- for (property in source) {
- destination[property] = source[property];
- }
- return destination;
-}
-
-Object.prototype.extend = function(object) {
- return Object.extend.apply(this, [this, object]);
-}
-
-Function.prototype.bind = function(object) {
- var __method = this;
- return function() {
- __method.apply(object, arguments);
- }
-}
-
-Function.prototype.bindAsEventListener = function(object) {
- var __method = this;
- return function(event) {
- __method.call(object, event || window.event);
- }
-}
-
-Number.prototype.toColorPart = function() {
- var digits = this.toString(16);
- if (this < 16) return '0' + digits;
- return digits;
-}
-
-var Try = {
- these: function() {
- var returnValue;
-
- for (var i = 0; i < arguments.length; i++) {
- var lambda = arguments[i];
- try {
- returnValue = lambda();
- break;
- } catch (e) {}
- }
-
- return returnValue;
- }
-}
-
-/*--------------------------------------------------------------------------*/
-
-var PeriodicalExecuter = Class.create();
-PeriodicalExecuter.prototype = {
- initialize: function(callback, frequency) {
- this.callback = callback;
- this.frequency = frequency;
- this.currentlyExecuting = false;
-
- this.registerCallback();
- },
-
- registerCallback: function() {
- setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
- },
-
- onTimerEvent: function() {
- if (!this.currentlyExecuting) {
- try {
- this.currentlyExecuting = true;
- this.callback();
- } finally {
- this.currentlyExecuting = false;
- }
- }
- }
-}
-
-/*--------------------------------------------------------------------------*/
-
-function $() {
- var elements = new Array();
-
- for (var i = 0; i < arguments.length; i++) {
- var element = arguments[i];
- if (typeof element == 'string')
- element = document.getElementById(element);
-
- if (arguments.length == 1)
- return element;
-
- elements.push(element);
- }
-
- return elements;
-}
-
-if (!Array.prototype.push) {
- Array.prototype.push = function() {
- var startLength = this.length;
- for (var i = 0; i < arguments.length; i++)
- this[startLength + i] = arguments[i];
- return this.length;
- }
-}
-
-if (!Function.prototype.apply) {
- // Based on code from http://www.youngpup.net/
- Function.prototype.apply = function(object, parameters) {
- var parameterStrings = new Array();
- if (!object) object = window;
- if (!parameters) parameters = new Array();
-
- for (var i = 0; i < parameters.length; i++)
- parameterStrings[i] = 'parameters[' + i + ']';
-
- object.__apply__ = this;
- var result = eval('object.__apply__(' +
- parameterStrings.join(', ') + ')');
- object.__apply__ = null;
-
- return result;
- }
-}
-
-String.prototype.extend({
- stripTags: function() {
- return this.replace(/<\/?[^>]+>/gi, '');
- },
-
- escapeHTML: function() {
- var div = document.createElement('div');
- var text = document.createTextNode(this);
- div.appendChild(text);
- return div.innerHTML;
- },
-
- unescapeHTML: function() {
- var div = document.createElement('div');
- div.innerHTML = this.stripTags();
- return div.childNodes[0].nodeValue;
- }
-});
-
-var Ajax = {
- getTransport: function() {
- return Try.these(
- function() {return new ActiveXObject('Msxml2.XMLHTTP')},
- function() {return new ActiveXObject('Microsoft.XMLHTTP')},
- function() {return new XMLHttpRequest()}
- ) || false;
- }
-}
-
-Ajax.Base = function() {};
-Ajax.Base.prototype = {
- setOptions: function(options) {
- this.options = {
- method: 'post',
- asynchronous: true,
- parameters: ''
- }.extend(options || {});
- },
-
- responseIsSuccess: function() {
- return this.transport.status == undefined
- || this.transport.status == 0
- || (this.transport.status >= 200 && this.transport.status < 300);
- },
-
- responseIsFailure: function() {
- return !this.responseIsSuccess();
- }
-}
-
-Ajax.Request = Class.create();
-Ajax.Request.Events =
- ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
-
-Ajax.Request.prototype = (new Ajax.Base()).extend({
- initialize: function(url, options) {
- this.transport = Ajax.getTransport();
- this.setOptions(options);
- this.request(url);
- },
-
- request: function(url) {
- var parameters = this.options.parameters || '';
- if (parameters.length > 0) parameters += '&_=';
-
- try {
- if (this.options.method == 'get')
- url += '?' + parameters;
-
- this.transport.open(this.options.method, url,
- this.options.asynchronous);
-
- if (this.options.asynchronous) {
- this.transport.onreadystatechange = this.onStateChange.bind(this);
- setTimeout((function() {this.respondToReadyState(1)}).bind(this), 10);
- }
-
- this.setRequestHeaders();
-
- var body = this.options.postBody ? this.options.postBody : parameters;
- this.transport.send(this.options.method == 'post' ? body : null);
-
- } catch (e) {
- }
- },
-
- setRequestHeaders: function() {
- var requestHeaders =
- ['X-Requested-With', 'XMLHttpRequest',
- 'X-Prototype-Version', Prototype.Version];
-
- if (this.options.method == 'post') {
- requestHeaders.push('Content-type',
- 'application/x-www-form-urlencoded');
-
- /* Force "Connection: close" for Mozilla browsers to work around
- * a bug where XMLHttpReqeuest sends an incorrect Content-length
- * header. See Mozilla Bugzilla #246651.
- */
- if (this.transport.overrideMimeType)
- requestHeaders.push('Connection', 'close');
- }
-
- if (this.options.requestHeaders)
- requestHeaders.push.apply(requestHeaders, this.options.requestHeaders);
-
- for (var i = 0; i < requestHeaders.length; i += 2)
- this.transport.setRequestHeader(requestHeaders[i], requestHeaders[i+1]);
- },
-
- onStateChange: function() {
- var readyState = this.transport.readyState;
- if (readyState != 1)
- this.respondToReadyState(this.transport.readyState);
- },
-
- respondToReadyState: function(readyState) {
- var event = Ajax.Request.Events[readyState];
-
- if (event == 'Complete')
- (this.options['on' + this.transport.status]
- || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')]
- || Prototype.emptyFunction)(this.transport);
-
- (this.options['on' + event] || Prototype.emptyFunction)(this.transport);
-
- /* Avoid memory leak in MSIE: clean up the oncomplete event handler */
- if (event == 'Complete')
- this.transport.onreadystatechange = Prototype.emptyFunction;
- }
-});
-
-Ajax.Updater = Class.create();
-Ajax.Updater.ScriptFragment = '(?:<script.*?>)((\n|.)*?)(?:<\/script>)';
-
-Ajax.Updater.prototype.extend(Ajax.Request.prototype).extend({
- initialize: function(container, url, options) {
- this.containers = {
- success: container.success ? $(container.success) : $(container),
- failure: container.failure ? $(container.failure) :
- (container.success ? null : $(container))
- }
-
- this.transport = Ajax.getTransport();
- this.setOptions(options);
-
- var onComplete = this.options.onComplete || Prototype.emptyFunction;
- this.options.onComplete = (function() {
- this.updateContent();
- onComplete(this.transport);
- }).bind(this);
-
- this.request(url);
- },
-
- updateContent: function() {
- var receiver = this.responseIsSuccess() ?
- this.containers.success : this.containers.failure;
-
- var match = new RegExp(Ajax.Updater.ScriptFragment, 'img');
- var response = this.transport.responseText.replace(match, '');
- var scripts = this.transport.responseText.match(match);
-
- if (receiver) {
- if (this.options.insertion) {
- new this.options.insertion(receiver, response);
- } else {
- receiver.innerHTML = response;
- }
- }
-
- if (this.responseIsSuccess()) {
- if (this.onComplete)
- setTimeout((function() {this.onComplete(
- this.transport)}).bind(this), 10);
- }
-
- if (this.options.evalScripts && scripts) {
- match = new RegExp(Ajax.Updater.ScriptFragment, 'im');
- setTimeout((function() {
- for (var i = 0; i < scripts.length; i++)
- eval(scripts[i].match(match)[1]);
- }).bind(this), 10);
- }
- }
-});
-
-Ajax.PeriodicalUpdater = Class.create();
-Ajax.PeriodicalUpdater.prototype = (new Ajax.Base()).extend({
- initialize: function(container, url, options) {
- this.setOptions(options);
- this.onComplete = this.options.onComplete;
-
- this.frequency = (this.options.frequency || 2);
- this.decay = 1;
-
- this.updater = {};
- this.container = container;
- this.url = url;
-
- this.start();
- },
-
- start: function() {
- this.options.onComplete = this.updateComplete.bind(this);
- this.onTimerEvent();
- },
-
- stop: function() {
- this.updater.onComplete = undefined;
- clearTimeout(this.timer);
- (this.onComplete || Ajax.emptyFunction).apply(this, arguments);
- },
-
- updateComplete: function(request) {
- if (this.options.decay) {
- this.decay = (request.responseText == this.lastText ?
- this.decay * this.options.decay : 1);
-
- this.lastText = request.responseText;
- }
- this.timer = setTimeout(this.onTimerEvent.bind(this),
- this.decay * this.frequency * 1000);
- },
-
- onTimerEvent: function() {
- this.updater = new Ajax.Updater(this.container, this.url, this.options);
- }
-});
-
-document.getElementsByClassName = function(className) {
- var children = document.getElementsByTagName('*') || document.all;
- var elements = new Array();
-
- for (var i = 0; i < children.length; i++) {
- var child = children[i];
- var classNames = child.className.split(' ');
- for (var j = 0; j < classNames.length; j++) {
- if (classNames[j] == className) {
- elements.push(child);
- break;
- }
- }
- }
-
- return elements;
-}
-
-/*--------------------------------------------------------------------------*/
-
-if (!window.Element) {
- var Element = new Object();
-}
-
-Object.extend(Element, {
- toggle: function() {
- for (var i = 0; i < arguments.length; i++) {
- var element = $(arguments[i]);
- element.style.display =
- (element.style.display == 'none' ? '' : 'none');
- }
- },
-
- hide: function() {
- for (var i = 0; i < arguments.length; i++) {
- var element = $(arguments[i]);
- element.style.display = 'none';
- }
- },
-
- show: function() {
- for (var i = 0; i < arguments.length; i++) {
- var element = $(arguments[i]);
- element.style.display = '';
- }
- },
-
- remove: function(element) {
- element = $(element);
- element.parentNode.removeChild(element);
- },
-
- getHeight: function(element) {
- element = $(element);
- return element.offsetHeight;
- },
-
- hasClassName: function(element, className) {
- element = $(element);
- if (!element)
- return;
- var a = element.className.split(' ');
- for (var i = 0; i < a.length; i++) {
- if (a[i] == className)
- return true;
- }
- return false;
- },
-
- addClassName: function(element, className) {
- element = $(element);
- Element.removeClassName(element, className);
- element.className += ' ' + className;
- },
-
- removeClassName: function(element, className) {
- element = $(element);
- if (!element)
- return;
- var newClassName = '';
- var a = element.className.split(' ');
- for (var i = 0; i < a.length; i++) {
- if (a[i] != className) {
- if (i > 0)
- newClassName += ' ';
- newClassName += a[i];
- }
- }
- element.className = newClassName;
- },
-
- // removes whitespace-only text node children
- cleanWhitespace: function(element) {
- var element = $(element);
- for (var i = 0; i < element.childNodes.length; i++) {
- var node = element.childNodes[i];
- if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
- Element.remove(node);
- }
- }
-});
-
-var Toggle = new Object();
-Toggle.display = Element.toggle;
-
-/*--------------------------------------------------------------------------*/
-
-Abstract.Insertion = function(adjacency) {
- this.adjacency = adjacency;
-}
-
-Abstract.Insertion.prototype = {
- initialize: function(element, content) {
- this.element = $(element);
- this.content = content;
-
- if (this.adjacency && this.element.insertAdjacentHTML) {
- this.element.insertAdjacentHTML(this.adjacency, this.content);
- } else {
- this.range = this.element.ownerDocument.createRange();
- if (this.initializeRange) this.initializeRange();
- this.fragment = this.range.createContextualFragment(this.content);
- this.insertContent();
- }
- }
-}
-
-var Insertion = new Object();
-
-Insertion.Before = Class.create();
-Insertion.Before.prototype = (new Abstract.Insertion('beforeBegin')).extend({
- initializeRange: function() {
- this.range.setStartBefore(this.element);
- },
-
- insertContent: function() {
- this.element.parentNode.insertBefore(this.fragment, this.element);
- }
-});
-
-Insertion.Top = Class.create();
-Insertion.Top.prototype = (new Abstract.Insertion('afterBegin')).extend({
- initializeRange: function() {
- this.range.selectNodeContents(this.element);
- this.range.collapse(true);
- },
-
- insertContent: function() {
- this.element.insertBefore(this.fragment, this.element.firstChild);
- }
-});
-
-Insertion.Bottom = Class.create();
-Insertion.Bottom.prototype = (new Abstract.Insertion('beforeEnd')).extend({
- initializeRange: function() {
- this.range.selectNodeContents(this.element);
- this.range.collapse(this.element);
- },
-
- insertContent: function() {
- this.element.appendChild(this.fragment);
- }
-});
-
-Insertion.After = Class.create();
-Insertion.After.prototype = (new Abstract.Insertion('afterEnd')).extend({
- initializeRange: function() {
- this.range.setStartAfter(this.element);
- },
-
- insertContent: function() {
- this.element.parentNode.insertBefore(this.fragment,
- this.element.nextSibling);
- }
-});
-
-var Field = {
- clear: function() {
- for (var i = 0; i < arguments.length; i++)
- $(arguments[i]).value = '';
- },
-
- focus: function(element) {
- $(element).focus();
- },
-
- present: function() {
- for (var i = 0; i < arguments.length; i++)
- if ($(arguments[i]).value == '') return false;
- return true;
- },
-
- select: function(element) {
- $(element).select();
- },
-
- activate: function(element) {
- $(element).focus();
- $(element).select();
- }
-}
-
-/*--------------------------------------------------------------------------*/
-
-var Form = {
- serialize: function(form) {
- var elements = Form.getElements($(form));
- var queryComponents = new Array();
-
- for (var i = 0; i < elements.length; i++) {
- var queryComponent = Form.Element.serialize(elements[i]);
- if (queryComponent)
- queryComponents.push(queryComponent);
- }
-
- return queryComponents.join('&');
- },
-
- getElements: function(form) {
- var form = $(form);
- var elements = new Array();
-
- for (tagName in Form.Element.Serializers) {
- var tagElements = form.getElementsByTagName(tagName);
- for (var j = 0; j < tagElements.length; j++)
- elements.push(tagElements[j]);
- }
- return elements;
- },
-
- getInputs: function(form, typeName, name) {
- var form = $(form);
- var inputs = form.getElementsByTagName('input');
-
- if (!typeName && !name)
- return inputs;
-
- var matchingInputs = new Array();
- for (var i = 0; i < inputs.length; i++) {
- var input = inputs[i];
- if ((typeName && input.type != typeName) ||
- (name && input.name != name))
- continue;
- matchingInputs.push(input);
- }
-
- return matchingInputs;
- },
-
- disable: function(form) {
- var elements = Form.getElements(form);
- for (var i = 0; i < elements.length; i++) {
- var element = elements[i];
- element.blur();
- element.disabled = 'true';
- }
- },
-
- enable: function(form) {
- var elements = Form.getElements(form);
- for (var i = 0; i < elements.length; i++) {
- var element = elements[i];
- element.disabled = '';
- }
- },
-
- focusFirstElement: function(form) {
- var form = $(form);
- var elements = Form.getElements(form);
- for (var i = 0; i < elements.length; i++) {
- var element = elements[i];
- if (element.type != 'hidden' && !element.disabled) {
- Field.activate(element);
- break;
- }
- }
- },
-
- reset: function(form) {
- $(form).reset();
- }
-}
-
-Form.Element = {
- serialize: function(element) {
- var element = $(element);
- var method = element.tagName.toLowerCase();
- var parameter = Form.Element.Serializers[method](element);
-
- if (parameter)
- return encodeURIComponent(parameter[0]) + '=' +
- encodeURIComponent(parameter[1]);
- },
-
- getValue: function(element) {
- var element = $(element);
- var method = element.tagName.toLowerCase();
- var parameter = Form.Element.Serializers[method](element);
-
- if (parameter)
- return parameter[1];
- }
-}
-
-Form.Element.Serializers = {
- input: function(element) {
- switch (element.type.toLowerCase()) {
- case 'submit':
- case 'hidden':
- case 'password':
- case 'text':
- return Form.Element.Serializers.textarea(element);
- case 'checkbox':
- case 'radio':
- return Form.Element.Serializers.inputSelector(element);
- }
- return false;
- },
-
- inputSelector: function(element) {
- if (element.checked)
- return [element.name, element.value];
- },
-
- textarea: function(element) {
- return [element.name, element.value];
- },
-
- select: function(element) {
- var value = '';
- if (element.type == 'select-one') {
- var index = element.selectedIndex;
- if (index >= 0)
- value = element.options[index].value || element.options[index].text;
- } else {
- value = new Array();
- for (var i = 0; i < element.length; i++) {
- var opt = element.options[i];
- if (opt.selected)
- value.push(opt.value || opt.text);
- }
- }
- return [element.name, value];
- }
-}
-
-/*--------------------------------------------------------------------------*/
-
-var $F = Form.Element.getValue;
-
-/*--------------------------------------------------------------------------*/
-
-Abstract.TimedObserver = function() {}
-Abstract.TimedObserver.prototype = {
- initialize: function(element, frequency, callback) {
- this.frequency = frequency;
- this.element = $(element);
- this.callback = callback;
-
- this.lastValue = this.getValue();
- this.registerCallback();
- },
-
- registerCallback: function() {
- setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
- },
-
- onTimerEvent: function() {
- var value = this.getValue();
- if (this.lastValue != value) {
- this.callback(this.element, value);
- this.lastValue = value;
- }
- }
-}
-
-Form.Element.Observer = Class.create();
-Form.Element.Observer.prototype = (new Abstract.TimedObserver()).extend({
- getValue: function() {
- return Form.Element.getValue(this.element);
- }
-});
-
-Form.Observer = Class.create();
-Form.Observer.prototype = (new Abstract.TimedObserver()).extend({
- getValue: function() {
- return Form.serialize(this.element);
- }
-});
-
-/*--------------------------------------------------------------------------*/
-
-Abstract.EventObserver = function() {}
-Abstract.EventObserver.prototype = {
- initialize: function(element, callback) {
- this.element = $(element);
- this.callback = callback;
-
- this.lastValue = this.getValue();
- if (this.element.tagName.toLowerCase() == 'form')
- this.registerFormCallbacks();
- else
- this.registerCallback(this.element);
- },
-
- onElementEvent: function() {
- var value = this.getValue();
- if (this.lastValue != value) {
- this.callback(this.element, value);
- this.lastValue = value;
- }
- },
-
- registerFormCallbacks: function() {
- var elements = Form.getElements(this.element);
- for (var i = 0; i < elements.length; i++)
- this.registerCallback(elements[i]);
- },
-
- registerCallback: function(element) {
- if (element.type) {
- switch (element.type.toLowerCase()) {
- case 'checkbox':
- case 'radio':
- element.target = this;
- element.prev_onclick = element.onclick || Prototype.emptyFunction;
- element.onclick = function() {
- this.prev_onclick();
- this.target.onElementEvent();
- }
- break;
- case 'password':
- case 'text':
- case 'textarea':
- case 'select-one':
- case 'select-multiple':
- element.target = this;
- element.prev_onchange = element.onchange || Prototype.emptyFunction;
- element.onchange = function() {
- this.prev_onchange();
- this.target.onElementEvent();
- }
- break;
- }
- }
- }
-}
-
-Form.Element.EventObserver = Class.create();
-Form.Element.EventObserver.prototype = (new Abstract.EventObserver()).extend({
- getValue: function() {
- return Form.Element.getValue(this.element);
- }
-});
-
-Form.EventObserver = Class.create();
-Form.EventObserver.prototype = (new Abstract.EventObserver()).extend({
- getValue: function() {
- return Form.serialize(this.element);
- }
-});
-
-
-if (!window.Event) {
- var Event = new Object();
-}
-
-Object.extend(Event, {
- KEY_BACKSPACE: 8,
- KEY_TAB: 9,
- KEY_RETURN: 13,
- KEY_ESC: 27,
- KEY_LEFT: 37,
- KEY_UP: 38,
- KEY_RIGHT: 39,
- KEY_DOWN: 40,
- KEY_DELETE: 46,
-
- element: function(event) {
- return event.target || event.srcElement;
- },
-
- isLeftClick: function(event) {
- return (((event.which) && (event.which == 1)) ||
- ((event.button) && (event.button == 1)));
- },
-
- pointerX: function(event) {
- return event.pageX || (event.clientX +
- (document.documentElement.scrollLeft || document.body.scrollLeft));
- },
-
- pointerY: function(event) {
- return event.pageY || (event.clientY +
- (document.documentElement.scrollTop || document.body.scrollTop));
- },
-
- stop: function(event) {
- if (event.preventDefault) {
- event.preventDefault();
- event.stopPropagation();
- } else {
- event.returnValue = false;
- }
- },
-
- // find the first node with the given tagName, starting from the
- // node the event was triggered on; traverses the DOM upwards
- findElement: function(event, tagName) {
- var element = Event.element(event);
- while (element.parentNode && (!element.tagName ||
- (element.tagName.toUpperCase() != tagName.toUpperCase())))
- element = element.parentNode;
- return element;
- },
-
- observers: false,
-
- _observeAndCache: function(element, name, observer, useCapture) {
- if (!this.observers) this.observers = [];
- if (element.addEventListener) {
- this.observers.push([element, name, observer, useCapture]);
- element.addEventListener(name, observer, useCapture);
- } else if (element.attachEvent) {
- this.observers.push([element, name, observer, useCapture]);
- element.attachEvent('on' + name, observer);
- }
- },
-
- unloadCache: function() {
- if (!Event.observers) return;
- for (var i = 0; i < Event.observers.length; i++) {
- Event.stopObserving.apply(this, Event.observers[i]);
- Event.observers[i][0] = null;
- }
- Event.observers = false;
- },
-
- observe: function(element, name, observer, useCapture) {
- var element = $(element);
- useCapture = useCapture || false;
-
- if (name == 'keypress' &&
- ((navigator.appVersion.indexOf('AppleWebKit') > 0)
- || element.attachEvent))
- name = 'keydown';
-
- this._observeAndCache(element, name, observer, useCapture);
- },
-
- stopObserving: function(element, name, observer, useCapture) {
- var element = $(element);
- useCapture = useCapture || false;
-
- if (name == 'keypress' &&
- ((navigator.appVersion.indexOf('AppleWebKit') > 0)
- || element.detachEvent))
- name = 'keydown';
-
- if (element.removeEventListener) {
- element.removeEventListener(name, observer, useCapture);
- } else if (element.detachEvent) {
- element.detachEvent('on' + name, observer);
- }
- }
-});
-
-/* prevent memory leaks in IE */
-Event.observe(window, 'unload', Event.unloadCache, false);
-
-var Position = {
-
- // set to true if needed, warning: firefox performance problems
- // NOT neeeded for page scrolling, only if draggable contained in
- // scrollable elements
- includeScrollOffsets: false,
-
- // must be called before calling withinIncludingScrolloffset, every time the
- // page is scrolled
- prepare: function() {
- this.deltaX = window.pageXOffset
- || document.documentElement.scrollLeft
- || document.body.scrollLeft
- || 0;
- this.deltaY = window.pageYOffset
- || document.documentElement.scrollTop
- || document.body.scrollTop
- || 0;
- },
-
- realOffset: function(element) {
- var valueT = 0, valueL = 0;
- do {
- valueT += element.scrollTop || 0;
- valueL += element.scrollLeft || 0;
- element = element.parentNode;
- } while (element);
- return [valueL, valueT];
- },
-
- cumulativeOffset: function(element) {
- var valueT = 0, valueL = 0;
- do {
- valueT += element.offsetTop || 0;
- valueL += element.offsetLeft || 0;
- element = element.offsetParent;
- } while (element);
- return [valueL, valueT];
- },
-
- // caches x/y coordinate pair to use with overlap
- within: function(element, x, y) {
- if (this.includeScrollOffsets)
- return this.withinIncludingScrolloffsets(element, x, y);
- this.xcomp = x;
- this.ycomp = y;
- this.offset = this.cumulativeOffset(element);
-
- return (y >= this.offset[1] &&
- y < this.offset[1] + element.offsetHeight &&
- x >= this.offset[0] &&
- x < this.offset[0] + element.offsetWidth);
- },
-
- withinIncludingScrolloffsets: function(element, x, y) {
- var offsetcache = this.realOffset(element);
-
- this.xcomp = x + offsetcache[0] - this.deltaX;
- this.ycomp = y + offsetcache[1] - this.deltaY;
- this.offset = this.cumulativeOffset(element);
-
- return (this.ycomp >= this.offset[1] &&
- this.ycomp < this.offset[1] + element.offsetHeight &&
- this.xcomp >= this.offset[0] &&
- this.xcomp < this.offset[0] + element.offsetWidth);
- },
-
- // within must be called directly before
- overlap: function(mode, element) {
- if (!mode) return 0;
- if (mode == 'vertical')
- return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
- element.offsetHeight;
- if (mode == 'horizontal')
- return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
- element.offsetWidth;
- },
-
- clone: function(source, target) {
- source = $(source);
- target = $(target);
- target.style.position = 'absolute';
- var offsets = this.cumulativeOffset(source);
- target.style.top = offsets[1] + 'px';
- target.style.left = offsets[0] + 'px';
- target.style.width = source.offsetWidth + 'px';
- target.style.height = source.offsetHeight + 'px';
- }
-}
diff --git a/web/static/js/rico.js b/web/static/js/rico.js
deleted file mode 100644
index a1f9113..0000000
--- a/web/static/js/rico.js
+++ /dev/null
@@ -1,2666 +0,0 @@
-/**
- *
- * Copyright 2005 Sabre Airline Solutions
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
- * file except in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
- * either express or implied. See the License for the specific language governing permissions
- * and limitations under the License.
- **/
-
-
-//-------------------- rico.js
-var Rico = {
- Version: '1.1-beta2'
-}
-
-Rico.ArrayExtensions = new Array();
-
-if (Object.prototype.extend) {
- // in prototype.js...
- Rico.ArrayExtensions[ Rico.ArrayExtensions.length ] = Object.prototype.extend;
-}
-
-if (Array.prototype.push) {
- // in prototype.js...
- Rico.ArrayExtensions[ Rico.ArrayExtensions.length ] = Array.prototype.push;
-}
-
-if (!Array.prototype.remove) {
- Array.prototype.remove = function(dx) {
- if( isNaN(dx) || dx > this.length )
- return false;
- for( var i=0,n=0; i<this.length; i++ )
- if( i != dx )
- this[n++]=this[i];
- this.length-=1;
- };
- Rico.ArrayExtensions[ Rico.ArrayExtensions.length ] = Array.prototype.remove;
-}
-
-if (!Array.prototype.removeItem) {
- Array.prototype.removeItem = function(item) {
- for ( var i = 0 ; i < this.length ; i++ )
- if ( this[i] == item ) {
- this.remove(i);
- break;
- }
- };
- Rico.ArrayExtensions[ Rico.ArrayExtensions.length ] = Array.prototype.removeItem;
-}
-
-if (!Array.prototype.indices) {
- Array.prototype.indices = function() {
- var indexArray = new Array();
- for ( index in this ) {
- var ignoreThis = false;
- for ( var i = 0 ; i < Rico.ArrayExtensions.length ; i++ ) {
- if ( this[index] == Rico.ArrayExtensions[i] ) {
- ignoreThis = true;
- break;
- }
- }
- if ( !ignoreThis )
- indexArray[ indexArray.length ] = index;
- }
- return indexArray;
- }
- Rico.ArrayExtensions[ Rico.ArrayExtensions.length ] = Array.prototype.indices;
-}
-
-// Create the loadXML method and xml getter for Mozilla
-if ( window.DOMParser &&
- window.XMLSerializer &&
- window.Node && Node.prototype && Node.prototype.__defineGetter__ ) {
-
- if (!Document.prototype.loadXML) {
- Document.prototype.loadXML = function (s) {
- var doc2 = (new DOMParser()).parseFromString(s, "text/xml");
- while (this.hasChildNodes())
- this.removeChild(this.lastChild);
-
- for (var i = 0; i < doc2.childNodes.length; i++) {
- this.appendChild(this.importNode(doc2.childNodes[i], true));
- }
- };
- }
-
- Document.prototype.__defineGetter__( "xml",
- function () {
- return (new XMLSerializer()).serializeToString(this);
- }
- );
-}
-
-document.getElementsByTagAndClassName = function(tagName, className) {
- if ( tagName == null )
- tagName = '*';
-
- var children = document.getElementsByTagName(tagName) || document.all;
- var elements = new Array();
-
- if ( className == null )
- return children;
-
- for (var i = 0; i < children.length; i++) {
- var child = children[i];
- var classNames = child.className.split(' ');
- for (var j = 0; j < classNames.length; j++) {
- if (classNames[j] == className) {
- elements.push(child);
- break;
- }
- }
- }
-
- return elements;
-}
-
-
-//-------------------- ricoAccordion.js
-
-Rico.Accordion = Class.create();
-
-Rico.Accordion.prototype = {
-
- initialize: function(container, options) {
- this.container = $(container);
- this.lastExpandedTab = null;
- this.accordionTabs = new Array();
- this.setOptions(options);
- this._attachBehaviors();
-
- this.container.style.borderBottom = '1px solid ' + this.options.borderColor;
-
- // set the initial visual state...
- for ( var i=1 ; i < this.accordionTabs.length ; i++ )
- {
- this.accordionTabs[i].collapse();
- this.accordionTabs[i].content.style.display = 'none';
- }
- this.lastExpandedTab = this.accordionTabs[0];
- this.lastExpandedTab.content.style.height = this.options.panelHeight + "px";
- this.lastExpandedTab.showExpanded();
- this.lastExpandedTab.titleBar.style.fontWeight = this.options.expandedFontWeight;
- },
-
- setOptions: function(options) {
- this.options = {
- expandedBg : '#63699c',
- hoverBg : '#63699c',
- collapsedBg : '#6b79a5',
- expandedTextColor : '#ffffff',
- expandedFontWeight : 'bold',
- hoverTextColor : '#ffffff',
- collapsedTextColor : '#ced7ef',
- collapsedFontWeight : 'normal',
- hoverTextColor : '#ffffff',
- borderColor : '#1f669b',
- panelHeight : 200,
- onHideTab : null,
- onShowTab : null
- }.extend(options || {});
- },
-
- showTabByIndex: function( anIndex, animate ) {
- var doAnimate = arguments.length == 1 ? true : animate;
- this.showTab( this.accordionTabs[anIndex], doAnimate );
- },
-
- showTab: function( accordionTab, animate ) {
-
- var doAnimate = arguments.length == 1 ? true : animate;
-
- if ( this.options.onHideTab )
- this.options.onHideTab(this.lastExpandedTab);
-
- this.lastExpandedTab.showCollapsed();
- var accordion = this;
- var lastExpandedTab = this.lastExpandedTab;
-
- this.lastExpandedTab.content.style.height = (this.options.panelHeight - 1) + 'px';
- accordionTab.content.style.display = '';
-
- accordionTab.titleBar.style.fontWeight = this.options.expandedFontWeight;
-
- if ( doAnimate ) {
- new Effect.AccordionSize( this.lastExpandedTab.content,
- accordionTab.content,
- 1,
- this.options.panelHeight,
- 100, 10,
- { complete: function() {accordion.showTabDone(lastExpandedTab)} } );
- this.lastExpandedTab = accordionTab;
- }
- else {
- this.lastExpandedTab.content.style.height = "1px";
- accordionTab.content.style.height = this.options.panelHeight + "px";
- this.lastExpandedTab = accordionTab;
- this.showTabDone(lastExpandedTab);
- }
- },
-
- showTabDone: function(collapsedTab) {
- collapsedTab.content.style.display = 'none';
- this.lastExpandedTab.showExpanded();
- if ( this.options.onShowTab )
- this.options.onShowTab(this.lastExpandedTab);
- },
-
- _attachBehaviors: function() {
- var panels = this._getDirectChildrenByTag(this.container, 'DIV');
- for ( var i = 0 ; i < panels.length ; i++ ) {
-
- var tabChildren = this._getDirectChildrenByTag(panels[i],'DIV');
- if ( tabChildren.length != 2 )
- continue; // unexpected
-
- var tabTitleBar = tabChildren[0];
- var tabContentBox = tabChildren[1];
- this.accordionTabs.push( new Rico.Accordion.Tab(this,tabTitleBar,tabContentBox) );
- }
- },
-
- _getDirectChildrenByTag: function(e, tagName) {
- var kids = new Array();
- var allKids = e.childNodes;
- for( var i = 0 ; i < allKids.length ; i++ )
- if ( allKids[i] && allKids[i].tagName && allKids[i].tagName == tagName )
- kids.push(allKids[i]);
- return kids;
- }
-
-};
-
-Rico.Accordion.Tab = Class.create();
-
-Rico.Accordion.Tab.prototype = {
-
- initialize: function(accordion, titleBar, content) {
- this.accordion = accordion;
- this.titleBar = titleBar;
- this.content = content;
- this._attachBehaviors();
- },
-
- collapse: function() {
- this.showCollapsed();
- this.content.style.height = "1px";
- },
-
- showCollapsed: function() {
- this.expanded = false;
- this.titleBar.style.backgroundColor = this.accordion.options.collapsedBg;
- this.titleBar.style.color = this.accordion.options.collapsedTextColor;
- this.titleBar.style.fontWeight = this.accordion.options.collapsedFontWeight;
- this.content.style.overflow = "hidden";
- },
-
- showExpanded: function() {
- this.expanded = true;
- this.titleBar.style.backgroundColor = this.accordion.options.expandedBg;
- this.titleBar.style.color = this.accordion.options.expandedTextColor;
- this.content.style.overflow = "visible";
- },
-
- titleBarClicked: function(e) {
- if ( this.accordion.lastExpandedTab == this )
- return;
- this.accordion.showTab(this);
- },
-
- hover: function(e) {
- this.titleBar.style.backgroundColor = this.accordion.options.hoverBg;
- this.titleBar.style.color = this.accordion.options.hoverTextColor;
- },
-
- unhover: function(e) {
- if ( this.expanded ) {
- this.titleBar.style.backgroundColor = this.accordion.options.expandedBg;
- this.titleBar.style.color = this.accordion.options.expandedTextColor;
- }
- else {
- this.titleBar.style.backgroundColor = this.accordion.options.collapsedBg;
- this.titleBar.style.color = this.accordion.options.collapsedTextColor;
- }
- },
-
- _attachBehaviors: function() {
- this.content.style.border = "1px solid " + this.accordion.options.borderColor;
- this.content.style.borderTopWidth = "0px";
- this.content.style.borderBottomWidth = "0px";
- this.content.style.margin = "0px";
-
- this.titleBar.onclick = this.titleBarClicked.bindAsEventListener(this);
- this.titleBar.onmouseover = this.hover.bindAsEventListener(this);
- this.titleBar.onmouseout = this.unhover.bindAsEventListener(this);
- }
-
-};
-
-
-//-------------------- ricoAjaxEngine.js
-
-Rico.AjaxEngine = Class.create();
-
-Rico.AjaxEngine.prototype = {
-
- initialize: function() {
- this.ajaxElements = new Array();
- this.ajaxObjects = new Array();
- this.requestURLS = new Array();
- },
-
- registerAjaxElement: function( anId, anElement ) {
- if ( arguments.length == 1 )
- anElement = $(anId);
- this.ajaxElements[anId] = anElement;
- },
-
- registerAjaxObject: function( anId, anObject ) {
- this.ajaxObjects[anId] = anObject;
- },
-
- registerRequest: function (requestLogicalName, requestURL) {
- this.requestURLS[requestLogicalName] = requestURL;
- },
-
- sendRequest: function(requestName) {
- var requestURL = this.requestURLS[requestName];
- if ( requestURL == null )
- return;
-
- var queryString = "";
- if ( arguments.length > 1 )
- queryString = this._createQueryString(arguments, 1);
-
- new Ajax.Request(requestURL, this._requestOptions(queryString));
- },
-
- sendRequestWithData: function(requestName, xmlDocument) {
- var requestURL = this.requestURLS[requestName];
- if ( requestURL == null )
- return;
-
- var queryString = "";
- if ( arguments.length > 2 )
- queryString = this._createQueryString(arguments, 2);
-
- new Ajax.Request(requestURL + "?" + queryString, this._requestOptions(null,xmlDocument));
- },
-
- sendRequestAndUpdate: function(requestName,container,options) {
- var requestURL = this.requestURLS[requestName];
- if ( requestURL == null )
- return;
-
- var queryString = "";
- if ( arguments.length > 3 )
- queryString = this._createQueryString(arguments, 3);
-
- var updaterOptions = this._requestOptions(queryString);
- updaterOptions.onComplete = null;
- updaterOptions.extend(options);
-
- new Ajax.Updater(container, requestURL, updaterOptions);
- },
-
- sendRequestWithDataAndUpdate: function(requestName,xmlDocument,container,options) {
- var requestURL = this.requestURLS[requestName];
- if ( requestURL == null )
- return;
-
- var queryString = "";
- if ( arguments.length > 4 )
- queryString = this._createQueryString(arguments, 4);
-
-
- var updaterOptions = this._requestOptions(queryString,xmlDocument);
- updaterOptions.onComplete = null;
- updaterOptions.extend(options);
-
- new Ajax.Updater(container, requestURL + "?" + queryString, updaterOptions);
- },
-
- // Private -- not part of intended engine API --------------------------------------------------------------------
-
- _requestOptions: function(queryString,xmlDoc) {
- var self = this;
-
- var requestHeaders = ['X-Rico-Version', Rico.Version ];
- var sendMethod = "post"
- if ( arguments[1] )
- requestHeaders.push( 'Content-type', 'text/xml' );
- else
- sendMethod = "get";
-
- return { requestHeaders: requestHeaders,
- parameters: queryString,
- postBody: arguments[1] ? xmlDoc : null,
- method: sendMethod,
- onComplete: self._onRequestComplete.bind(self) };
- },
-
- _createQueryString: function( theArgs, offset ) {
- var queryString = ""
- for ( var i = offset ; i < theArgs.length ; i++ ) {
- if ( i != offset )
- queryString += "&";
-
- var anArg = theArgs[i];
-
- if ( anArg.name != undefined && anArg.value != undefined ) {
- queryString += anArg.name + "=" + escape(anArg.value);
- }
- else {
- var ePos = anArg.indexOf('=');
- var argName = anArg.substring( 0, ePos );
- var argValue = anArg.substring( ePos + 1 );
- queryString += argName + "=" + escape(argValue);
- }
- }
-
- return queryString;
- },
-
- _onRequestComplete : function(request) {
-
- //!!TODO: error handling infrastructure??
- if (request.status != 200)
- return;
-
- var response = request.responseXML.getElementsByTagName("ajax-response");
- if (response == null || response.length != 1)
- return;
- this._processAjaxResponse( response[0].childNodes );
- },
-
- _processAjaxResponse: function( xmlResponseElements ) {
- for ( var i = 0 ; i < xmlResponseElements.length ; i++ ) {
- var responseElement = xmlResponseElements[i];
-
- // only process nodes of type element.....
- if ( responseElement.nodeType != 1 )
- continue;
-
- var responseType = responseElement.getAttribute("type");
- var responseId = responseElement.getAttribute("id");
-
- if ( responseType == "object" )
- this._processAjaxObjectUpdate( this.ajaxObjects[ responseId ], responseElement );
- else if ( responseType == "element" )
- this._processAjaxElementUpdate( this.ajaxElements[ responseId ], responseElement );
- else
- alert('unrecognized AjaxResponse type : ' + responseType );
- }
- },
-
- _processAjaxObjectUpdate: function( ajaxObject, responseElement ) {
- ajaxObject.ajaxUpdate( responseElement );
- },
-
- _processAjaxElementUpdate: function( ajaxElement, responseElement ) {
- ajaxElement.innerHTML = RicoUtil.getContentAsString(responseElement);
- }
-
-}
-
-var ajaxEngine = new Rico.AjaxEngine();
-
-
-//-------------------- ricoColor.js
-Rico.Color = Class.create();
-
-Rico.Color.prototype = {
-
- initialize: function(red, green, blue) {
- this.rgb = { r: red, g : green, b : blue };
- },
-
- setRed: function(r) {
- this.rgb.r = r;
- },
-
- setGreen: function(g) {
- this.rgb.g = g;
- },
-
- setBlue: function(b) {
- this.rgb.b = b;
- },
-
- setHue: function(h) {
-
- // get an HSB model, and set the new hue...
- var hsb = this.asHSB();
- hsb.h = h;
-
- // convert back to RGB...
- this.rgb = Rico.Color.HSBtoRGB(hsb.h, hsb.s, hsb.b);
- },
-
- setSaturation: function(s) {
- // get an HSB model, and set the new hue...
- var hsb = this.asHSB();
- hsb.s = s;
-
- // convert back to RGB and set values...
- this.rgb = Rico.Color.HSBtoRGB(hsb.h, hsb.s, hsb.b);
- },
-
- setBrightness: function(b) {
- // get an HSB model, and set the new hue...
- var hsb = this.asHSB();
- hsb.b = b;
-
- // convert back to RGB and set values...
- this.rgb = Rico.Color.HSBtoRGB( hsb.h, hsb.s, hsb.b );
- },
-
- darken: function(percent) {
- var hsb = this.asHSB();
- this.rgb = Rico.Color.HSBtoRGB(hsb.h, hsb.s, Math.max(hsb.b - percent,0));
- },
-
- brighten: function(percent) {
- var hsb = this.asHSB();
- this.rgb = Rico.Color.HSBtoRGB(hsb.h, hsb.s, Math.min(hsb.b + percent,1));
- },
-
- blend: function(other) {
- this.rgb.r = Math.floor((this.rgb.r + other.rgb.r)/2);
- this.rgb.g = Math.floor((this.rgb.g + other.rgb.g)/2);
- this.rgb.b = Math.floor((this.rgb.b + other.rgb.b)/2);
- },
-
- isBright: function() {
- var hsb = this.asHSB();
- return this.asHSB().b > 0.5;
- },
-
- isDark: function() {
- return ! this.isBright();
- },
-
- asRGB: function() {
- return "rgb(" + this.rgb.r + "," + this.rgb.g + "," + this.rgb.b + ")";
- },
-
- asHex: function() {
- return "#" + this.rgb.r.toColorPart() + this.rgb.g.toColorPart() + this.rgb.b.toColorPart();
- },
-
- asHSB: function() {
- return Rico.Color.RGBtoHSB(this.rgb.r, this.rgb.g, this.rgb.b);
- },
-
- toString: function() {
- return this.asHex();
- }
-
-};
-
-Rico.Color.createFromHex = function(hexCode) {
-
- if ( hexCode.indexOf('#') == 0 )
- hexCode = hexCode.substring(1);
- var red = hexCode.substring(0,2);
- var green = hexCode.substring(2,4);
- var blue = hexCode.substring(4,6);
- return new Rico.Color( parseInt(red,16), parseInt(green,16), parseInt(blue,16) );
-}
-
-/**
- * Factory method for creating a color from the background of
- * an HTML element.
- */
-Rico.Color.createColorFromBackground = function(elem) {
-
- var actualColor = RicoUtil.getElementsComputedStyle($(elem), "backgroundColor", "background-color");
-
- if ( actualColor == "transparent" && elem.parent )
- return Rico.Color.createColorFromBackground(elem.parent);
-
- if ( actualColor == null )
- return new Rico.Color(255,255,255);
-
- if ( actualColor.indexOf("rgb(") == 0 ) {
- var colors = actualColor.substring(4, actualColor.length - 1 );
- var colorArray = colors.split(",");
- return new Rico.Color( parseInt( colorArray[0] ),
- parseInt( colorArray[1] ),
- parseInt( colorArray[2] ) );
-
- }
- else if ( actualColor.indexOf("#") == 0 ) {
- var redPart = parseInt(actualColor.substring(1,3), 16);
- var greenPart = parseInt(actualColor.substring(3,5), 16);
- var bluePart = parseInt(actualColor.substring(5), 16);
- return new Rico.Color( redPart, greenPart, bluePart );
- }
- else
- return new Rico.Color(255,255,255);
-}
-
-Rico.Color.HSBtoRGB = function(hue, saturation, brightness) {
-
- var red = 0;
- var green = 0;
- var blue = 0;
-
- if (saturation == 0) {
- red = parseInt(brightness * 255.0 + 0.5);
- green = red;
- blue = red;
- }
- else {
- var h = (hue - Math.floor(hue)) * 6.0;
- var f = h - Math.floor(h);
- var p = brightness * (1.0 - saturation);
- var q = brightness * (1.0 - saturation * f);
- var t = brightness * (1.0 - (saturation * (1.0 - f)));
-
- switch (parseInt(h)) {
- case 0:
- red = (brightness * 255.0 + 0.5);
- green = (t * 255.0 + 0.5);
- blue = (p * 255.0 + 0.5);
- break;
- case 1:
- red = (q * 255.0 + 0.5);
- green = (brightness * 255.0 + 0.5);
- blue = (p * 255.0 + 0.5);
- break;
- case 2:
- red = (p * 255.0 + 0.5);
- green = (brightness * 255.0 + 0.5);
- blue = (t * 255.0 + 0.5);
- break;
- case 3:
- red = (p * 255.0 + 0.5);
- green = (q * 255.0 + 0.5);
- blue = (brightness * 255.0 + 0.5);
- break;
- case 4:
- red = (t * 255.0 + 0.5);
- green = (p * 255.0 + 0.5);
- blue = (brightness * 255.0 + 0.5);
- break;
- case 5:
- red = (brightness * 255.0 + 0.5);
- green = (p * 255.0 + 0.5);
- blue = (q * 255.0 + 0.5);
- break;
- }
- }
-
- return { r : parseInt(red), g : parseInt(green) , b : parseInt(blue) };
-}
-
-Rico.Color.RGBtoHSB = function(r, g, b) {
-
- var hue;
- var saturaton;
- var brightness;
-
- var cmax = (r > g) ? r : g;
- if (b > cmax)
- cmax = b;
-
- var cmin = (r < g) ? r : g;
- if (b < cmin)
- cmin = b;
-
- brightness = cmax / 255.0;
- if (cmax != 0)
- saturation = (cmax - cmin)/cmax;
- else
- saturation = 0;
-
- if (saturation == 0)
- hue = 0;
- else {
- var redc = (cmax - r)/(cmax - cmin);
- var greenc = (cmax - g)/(cmax - cmin);
- var bluec = (cmax - b)/(cmax - cmin);
-
- if (r == cmax)
- hue = bluec - greenc;
- else if (g == cmax)
- hue = 2.0 + redc - bluec;
- else
- hue = 4.0 + greenc - redc;
-
- hue = hue / 6.0;
- if (hue < 0)
- hue = hue + 1.0;
- }
-
- return { h : hue, s : saturation, b : brightness };
-}
-
-
-//-------------------- ricoCorner.js
-
-Rico.Corner = {
-
- round: function(e, options) {
- var e = $(e);
- this._setOptions(options);
-
- var color = this.options.color;
- if ( this.options.color == "fromElement" )
- color = this._background(e);
-
- var bgColor = this.options.bgColor;
- if ( this.options.bgColor == "fromParent" )
- bgColor = this._background(e.offsetParent);
-
- this._roundCornersImpl(e, color, bgColor);
- },
-
- _roundCornersImpl: function(e, color, bgColor) {
- if(this.options.border)
- this._renderBorder(e,bgColor);
- if(this._isTopRounded())
- this._roundTopCorners(e,color,bgColor);
- if(this._isBottomRounded())
- this._roundBottomCorners(e,color,bgColor);
- },
-
- _renderBorder: function(el,bgColor) {
- var borderValue = "1px solid " + this._borderColor(bgColor);
- var borderL = "border-left: " + borderValue;
- var borderR = "border-right: " + borderValue;
- var style = "style='" + borderL + ";" + borderR + "'";
- el.innerHTML = "<div " + style + ">" + el.innerHTML + "</div>"
- },
-
- _roundTopCorners: function(el, color, bgColor) {
- var corner = this._createCorner(bgColor);
- for(var i=0 ; i < this.options.numSlices ; i++ )
- corner.appendChild(this._createCornerSlice(color,bgColor,i,"top"));
- el.style.paddingTop = 0;
- el.insertBefore(corner,el.firstChild);
- },
-
- _roundBottomCorners: function(el, color, bgColor) {
- var corner = this._createCorner(bgColor);
- for(var i=(this.options.numSlices-1) ; i >= 0 ; i-- )
- corner.appendChild(this._createCornerSlice(color,bgColor,i,"bottom"));
- el.style.paddingBottom = 0;
- el.appendChild(corner);
- },
-
- _createCorner: function(bgColor) {
- var corner = document.createElement("div");
- corner.style.backgroundColor = (this._isTransparent() ? "transparent" : bgColor);
- return corner;
- },
-
- _createCornerSlice: function(color,bgColor, n, position) {
- var slice = document.createElement("span");
-
- var inStyle = slice.style;
- inStyle.backgroundColor = color;
- inStyle.display = "block";
- inStyle.height = "1px";
- inStyle.overflow = "hidden";
- inStyle.fontSize = "1px";
-
- var borderColor = this._borderColor(color,bgColor);
- if ( this.options.border && n == 0 ) {
- inStyle.borderTopStyle = "solid";
- inStyle.borderTopWidth = "1px";
- inStyle.borderLeftWidth = "0px";
- inStyle.borderRightWidth = "0px";
- inStyle.borderBottomWidth = "0px";
- inStyle.height = "0px"; // assumes css compliant box model
- inStyle.borderColor = borderColor;
- }
- else if(borderColor) {
- inStyle.borderColor = borderColor;
- inStyle.borderStyle = "solid";
- inStyle.borderWidth = "0px 1px";
- }
-
- if ( !this.options.compact && (n == (this.options.numSlices-1)) )
- inStyle.height = "2px";
-
- this._setMargin(slice, n, position);
- this._setBorder(slice, n, position);
-
- return slice;
- },
-
- _setOptions: function(options) {
- this.options = {
- corners : "all",
- color : "fromElement",
- bgColor : "fromParent",
- blend : true,
- border : false,
- compact : false
- }.extend(options || {});
-
- this.options.numSlices = this.options.compact ? 2 : 4;
- if ( this._isTransparent() )
- this.options.blend = false;
- },
-
- _whichSideTop: function() {
- if ( this._hasString(this.options.corners, "all", "top") )
- return "";
-
- if ( this.options.corners.indexOf("tl") >= 0 && this.options.corners.indexOf("tr") >= 0 )
- return "";
-
- if (this.options.corners.indexOf("tl") >= 0)
- return "left";
- else if (this.options.corners.indexOf("tr") >= 0)
- return "right";
- return "";
- },
-
- _whichSideBottom: function() {
- if ( this._hasString(this.options.corners, "all", "bottom") )
- return "";
-
- if ( this.options.corners.indexOf("bl")>=0 && this.options.corners.indexOf("br")>=0 )
- return "";
-
- if(this.options.corners.indexOf("bl") >=0)
- return "left";
- else if(this.options.corners.indexOf("br")>=0)
- return "right";
- return "";
- },
-
- _borderColor : function(color,bgColor) {
- if ( color == "transparent" )
- return bgColor;
- else if ( this.options.border )
- return this.options.border;
- else if ( this.options.blend )
- return this._blend( bgColor, color );
- else
- return "";
- },
-
-
- _setMargin: function(el, n, corners) {
- var marginSize = this._marginSize(n);
- var whichSide = corners == "top" ? this._whichSideTop() : this._whichSideBottom();
-
- if ( whichSide == "left" ) {
- el.style.marginLeft = marginSize + "px"; el.style.marginRight = "0px";
- }
- else if ( whichSide == "right" ) {
- el.style.marginRight = marginSize + "px"; el.style.marginLeft = "0px";
- }
- else {
- el.style.marginLeft = marginSize + "px"; el.style.marginRight = marginSize + "px";
- }
- },
-
- _setBorder: function(el,n,corners) {
- var borderSize = this._borderSize(n);
- var whichSide = corners == "top" ? this._whichSideTop() : this._whichSideBottom();
-
- if ( whichSide == "left" ) {
- el.style.borderLeftWidth = borderSize + "px"; el.style.borderRightWidth = "0px";
- }
- else if ( whichSide == "right" ) {
- el.style.borderRightWidth = borderSize + "px"; el.style.borderLeftWidth = "0px";
- }
- else {
- el.style.borderLeftWidth = borderSize + "px"; el.style.borderRightWidth = borderSize + "px";
- }
- },
-
- _marginSize: function(n) {
- if ( this._isTransparent() )
- return 0;
-
- var marginSizes = [ 5, 3, 2, 1 ];
- var blendedMarginSizes = [ 3, 2, 1, 0 ];
- var compactMarginSizes = [ 2, 1 ];
- var smBlendedMarginSizes = [ 1, 0 ];
-
- if ( this.options.compact && this.options.blend )
- return smBlendedMarginSizes[n];
- else if ( this.options.compact )
- return compactMarginSizes[n];
- else if ( this.options.blend )
- return blendedMarginSizes[n];
- else
- return marginSizes[n];
- },
-
- _borderSize: function(n) {
- var transparentBorderSizes = [ 5, 3, 2, 1 ];
- var blendedBorderSizes = [ 2, 1, 1, 1 ];
- var compactBorderSizes = [ 1, 0 ];
- var actualBorderSizes = [ 0, 2, 0, 0 ];
-
- if ( this.options.compact && (this.options.blend || this._isTransparent()) )
- return 1;
- else if ( this.options.compact )
- return compactBorderSizes[n];
- else if ( this.options.blend )
- return blendedBorderSizes[n];
- else if ( this.options.border )
- return actualBorderSizes[n];
- else if ( this._isTransparent() )
- return transparentBorderSizes[n];
- return 0;
- },
-
- _hasString: function(str) { for(var i=1 ; i<arguments.length ; i++) if (str.indexOf(arguments[i]) >= 0) return true; return false; },
- _blend: function(c1, c2) { var cc1 = Rico.Color.createFromHex(c1); cc1.blend(Rico.Color.createFromHex(c2)); return cc1; },
- _background: function(el) { try { return Rico.Color.createColorFromBackground(el).asHex(); } catch(err) { return "#ffffff"; } },
- _isTransparent: function() { return this.options.color == "transparent"; },
- _isTopRounded: function() { return this._hasString(this.options.corners, "all", "top", "tl", "tr"); },
- _isBottomRounded: function() { return this._hasString(this.options.corners, "all", "bottom", "bl", "br"); },
- _hasSingleTextChild: function(el) { return el.childNodes.length == 1 && el.childNodes[0].nodeType == 3; }
-}
-
-
-//-------------------- ricoDragAndDrop.js
-Rico.DragAndDrop = Class.create();
-
-Rico.DragAndDrop.prototype = {
-
- initialize: function() {
- this.dropZones = new Array();
- this.draggables = new Array();
- this.currentDragObjects = new Array();
- this.dragElement = null;
- this.lastSelectedDraggable = null;
- this.currentDragObjectVisible = false;
- this.interestedInMotionEvents = false;
- },
-
- registerDropZone: function(aDropZone) {
- this.dropZones[ this.dropZones.length ] = aDropZone;
- },
-
- deregisterDropZone: function(aDropZone) {
- var newDropZones = new Array();
- var j = 0;
- for ( var i = 0 ; i < this.dropZones.length ; i++ ) {
- if ( this.dropZones[i] != aDropZone )
- newDropZones[j++] = this.dropZones[i];
- }
-
- this.dropZones = newDropZones;
- },
-
- clearDropZones: function() {
- this.dropZones = new Array();
- },
-
- registerDraggable: function( aDraggable ) {
- this.draggables[ this.draggables.length ] = aDraggable;
- this._addMouseDownHandler( aDraggable );
- },
-
- clearSelection: function() {
- for ( var i = 0 ; i < this.currentDragObjects.length ; i++ )
- this.currentDragObjects[i].deselect();
- this.currentDragObjects = new Array();
- this.lastSelectedDraggable = null;
- },
-
- hasSelection: function() {
- return this.currentDragObjects.length > 0;
- },
-
- setStartDragFromElement: function( e, mouseDownElement ) {
- this.origPos = RicoUtil.toDocumentPosition(mouseDownElement);
- this.startx = e.screenX - this.origPos.x
- this.starty = e.screenY - this.origPos.y
- //this.startComponentX = e.layerX ? e.layerX : e.offsetX;
- //this.startComponentY = e.layerY ? e.layerY : e.offsetY;
- //this.adjustedForDraggableSize = false;
-
- this.interestedInMotionEvents = this.hasSelection();
- this._terminateEvent(e);
- },
-
- updateSelection: function( draggable, extendSelection ) {
- if ( ! extendSelection )
- this.clearSelection();
-
- if ( draggable.isSelected() ) {
- this.currentDragObjects.removeItem(draggable);
- draggable.deselect();
- if ( draggable == this.lastSelectedDraggable )
- this.lastSelectedDraggable = null;
- }
- else {
- this.currentDragObjects[ this.currentDragObjects.length ] = draggable;
- draggable.select();
- this.lastSelectedDraggable = draggable;
- }
- },
-
- _mouseDownHandler: function(e) {
- if ( arguments.length == 0 )
- e = event;
-
- // if not button 1 ignore it...
- var nsEvent = e.which != undefined;
- if ( (nsEvent && e.which != 1) || (!nsEvent && e.button != 1))
- return;
-
- var eventTarget = e.target ? e.target : e.srcElement;
- var draggableObject = eventTarget.draggable;
-
- var candidate = eventTarget;
- while (draggableObject == null && candidate.parentNode) {
- candidate = candidate.parentNode;
- draggableObject = candidate.draggable;
- }
-
- if ( draggableObject == null )
- return;
-
- this.updateSelection( draggableObject, e.ctrlKey );
-
- // clear the drop zones postion cache...
- if ( this.hasSelection() )
- for ( var i = 0 ; i < this.dropZones.length ; i++ )
- this.dropZones[i].clearPositionCache();
-
- this.setStartDragFromElement( e, draggableObject.getMouseDownHTMLElement() );
- },
-
-
- _mouseMoveHandler: function(e) {
- var nsEvent = e.which != undefined;
- if ( !this.interestedInMotionEvents ) {
- this._terminateEvent(e);
- return;
- }
-
- if ( ! this.hasSelection() )
- return;
-
- if ( ! this.currentDragObjectVisible )
- this._startDrag(e);
-
- if ( !this.activatedDropZones )
- this._activateRegisteredDropZones();
-
- //if ( !this.adjustedForDraggableSize )
- // this._adjustForDraggableSize(e);
-
- this._updateDraggableLocation(e);
- this._updateDropZonesHover(e);
-
- this._terminateEvent(e);
- },
-
- _makeDraggableObjectVisible: function(e)
- {
- if ( !this.hasSelection() )
- return;
-
- var dragElement;
- if ( this.currentDragObjects.length > 1 )
- dragElement = this.currentDragObjects[0].getMultiObjectDragGUI(this.currentDragObjects);
- else
- dragElement = this.currentDragObjects[0].getSingleObjectDragGUI();
-
- // go ahead and absolute position it...
- if ( RicoUtil.getElementsComputedStyle(dragElement, "position") != "absolute" )
- dragElement.style.position = "absolute";
-
- // need to parent him into the document...
- if ( dragElement.parentNode == null || dragElement.parentNode.nodeType == 11 )
- document.body.appendChild(dragElement);
-
- this.dragElement = dragElement;
- this._updateDraggableLocation(e);
-
- this.currentDragObjectVisible = true;
- },
-
- /**
- _adjustForDraggableSize: function(e) {
- var dragElementWidth = this.dragElement.offsetWidth;
- var dragElementHeight = this.dragElement.offsetHeight;
- if ( this.startComponentX > dragElementWidth )
- this.startx -= this.startComponentX - dragElementWidth + 2;
- if ( e.offsetY ) {
- if ( this.startComponentY > dragElementHeight )
- this.starty -= this.startComponentY - dragElementHeight + 2;
- }
- this.adjustedForDraggableSize = true;
- },
- **/
-
- _updateDraggableLocation: function(e) {
- var dragObjectStyle = this.dragElement.style;
- dragObjectStyle.left = (e.screenX - this.startx) + "px"
- dragObjectStyle.top = (e.screenY - this.starty) + "px";
- },
-
- _updateDropZonesHover: function(e) {
- var n = this.dropZones.length;
- for ( var i = 0 ; i < n ; i++ ) {
- if ( ! this._mousePointInDropZone( e, this.dropZones[i] ) )
- this.dropZones[i].hideHover();
- }
-
- for ( var i = 0 ; i < n ; i++ ) {
- if ( this._mousePointInDropZone( e, this.dropZones[i] ) ) {
- if ( this.dropZones[i].canAccept(this.currentDragObjects) )
- this.dropZones[i].showHover();
- }
- }
- },
-
- _startDrag: function(e) {
- for ( var i = 0 ; i < this.currentDragObjects.length ; i++ )
- this.currentDragObjects[i].startDrag();
-
- this._makeDraggableObjectVisible(e);
- },
-
- _mouseUpHandler: function(e) {
- if ( ! this.hasSelection() )
- return;
-
- var nsEvent = e.which != undefined;
- if ( (nsEvent && e.which != 1) || (!nsEvent && e.button != 1))
- return;
-
- this.interestedInMotionEvents = false;
-
- if ( this.dragElement == null ) {
- this._terminateEvent(e);
- return;
- }
-
- if ( this._placeDraggableInDropZone(e) )
- this._completeDropOperation(e);
- else {
- this._terminateEvent(e);
- new Effect.Position( this.dragElement,
- this.origPos.x,
- this.origPos.y,
- 200,
- 20,
- { complete : this._doCancelDragProcessing.bind(this) } );
- }
- },
-
- _completeDropOperation: function(e) {
- if ( this.dragElement != this.currentDragObjects[0].getMouseDownHTMLElement() ) {
- if ( this.dragElement.parentNode != null )
- this.dragElement.parentNode.removeChild(this.dragElement);
- }
-
- this._deactivateRegisteredDropZones();
- this._endDrag();
- this.clearSelection();
- this.dragElement = null;
- this.currentDragObjectVisible = false;
- this._terminateEvent(e);
- },
-
- _doCancelDragProcessing: function() {
- this._cancelDrag();
-
- if ( this.dragElement != this.currentDragObjects[0].getMouseDownHTMLElement() ) {
- if ( this.dragElement.parentNode != null ) {
- this.dragElement.parentNode.removeChild(this.dragElement);
- }
- }
-
- this._deactivateRegisteredDropZones();
- this.dragElement = null;
- this.currentDragObjectVisible = false;
- },
-
- _placeDraggableInDropZone: function(e) {
- var foundDropZone = false;
- var n = this.dropZones.length;
- for ( var i = 0 ; i < n ; i++ ) {
- if ( this._mousePointInDropZone( e, this.dropZones[i] ) ) {
- if ( this.dropZones[i].canAccept(this.currentDragObjects) ) {
- this.dropZones[i].hideHover();
- this.dropZones[i].accept(this.currentDragObjects);
- foundDropZone = true;
- break;
- }
- }
- }
-
- return foundDropZone;
- },
-
- _cancelDrag: function() {
- for ( var i = 0 ; i < this.currentDragObjects.length ; i++ )
- this.currentDragObjects[i].cancelDrag();
- },
-
- _endDrag: function() {
- for ( var i = 0 ; i < this.currentDragObjects.length ; i++ )
- this.currentDragObjects[i].endDrag();
- },
-
- _mousePointInDropZone: function( e, dropZone ) {
-
- var absoluteRect = dropZone.getAbsoluteRect();
-
- return e.clientX > absoluteRect.left &&
- e.clientX < absoluteRect.right &&
- e.clientY > absoluteRect.top &&
- e.clientY < absoluteRect.bottom;
- },
-
- _addMouseDownHandler: function( aDraggable )
- {
- var htmlElement = aDraggable.getMouseDownHTMLElement();
- if ( htmlElement != null ) {
- htmlElement.draggable = aDraggable;
- this._addMouseDownEvent( htmlElement );
- }
- },
-
- _activateRegisteredDropZones: function() {
- var n = this.dropZones.length;
- for ( var i = 0 ; i < n ; i++ ) {
- var dropZone = this.dropZones[i];
- if ( dropZone.canAccept(this.currentDragObjects) )
- dropZone.activate();
- }
-
- this.activatedDropZones = true;
- },
-
- _deactivateRegisteredDropZones: function() {
- var n = this.dropZones.length;
- for ( var i = 0 ; i < n ; i++ )
- this.dropZones[i].deactivate();
- this.activatedDropZones = false;
- },
-
- _addMouseDownEvent: function( htmlElement ) {
- if ( typeof document.implementation != "undefined" &&
- document.implementation.hasFeature("HTML", "1.0") &&
- document.implementation.hasFeature("Events", "2.0") &&
- document.implementation.hasFeature("CSS", "2.0") ) {
- htmlElement.addEventListener("mousedown", this._mouseDownHandler.bindAsEventListener(this), false);
- }
- else {
- htmlElement.attachEvent( "onmousedown", this._mouseDownHandler.bindAsEventListener(this) );
- }
- },
-
- _terminateEvent: function(e) {
- if ( e.stopPropagation != undefined )
- e.stopPropagation();
- else if ( e.cancelBubble != undefined )
- e.cancelBubble = true;
-
- if ( e.preventDefault != undefined )
- e.preventDefault();
- else
- e.returnValue = false;
- },
-
- initializeEventHandlers: function() {
- if ( typeof document.implementation != "undefined" &&
- document.implementation.hasFeature("HTML", "1.0") &&
- document.implementation.hasFeature("Events", "2.0") &&
- document.implementation.hasFeature("CSS", "2.0") ) {
- document.addEventListener("mouseup", this._mouseUpHandler.bindAsEventListener(this), false);
- document.addEventListener("mousemove", this._mouseMoveHandler.bindAsEventListener(this), false);
- }
- else {
- document.attachEvent( "onmouseup", this._mouseUpHandler.bindAsEventListener(this) );
- document.attachEvent( "onmousemove", this._mouseMoveHandler.bindAsEventListener(this) );
- }
- }
-}
-
-var dndMgr = new Rico.DragAndDrop();
-dndMgr.initializeEventHandlers();
-
-
-//-------------------- ricoDraggable.js
-Rico.Draggable = Class.create();
-
-Rico.Draggable.prototype = {
-
- initialize: function( type, htmlElement ) {
- this.type = type;
- this.htmlElement = $(htmlElement);
- this.selected = false;
- },
-
- /**
- * Returns the HTML element that should have a mouse down event
- * added to it in order to initiate a drag operation
- *
- **/
- getMouseDownHTMLElement: function() {
- return this.htmlElement;
- },
-
- select: function() {
- this.selected = true;
-
- if ( this.showingSelected )
- return;
-
- var htmlElement = this.getMouseDownHTMLElement();
-
- var color = Rico.Color.createColorFromBackground(htmlElement);
- color.isBright() ? color.darken(0.033) : color.brighten(0.033);
-
- this.saveBackground = RicoUtil.getElementsComputedStyle(htmlElement, "backgroundColor", "background-color");
- htmlElement.style.backgroundColor = color.asHex();
- this.showingSelected = true;
- },
-
- deselect: function() {
- this.selected = false;
- if ( !this.showingSelected )
- return;
-
- var htmlElement = this.getMouseDownHTMLElement();
-
- htmlElement.style.backgroundColor = this.saveBackground;
- this.showingSelected = false;
- },
-
- isSelected: function() {
- return this.selected;
- },
-
- startDrag: function() {
- },
-
- cancelDrag: function() {
- },
-
- endDrag: function() {
- },
-
- getSingleObjectDragGUI: function() {
- return this.htmlElement;
- },
-
- getMultiObjectDragGUI: function( draggables ) {
- return this.htmlElement;
- },
-
- getDroppedGUI: function() {
- return this.htmlElement;
- },
-
- toString: function() {
- return this.type + ":" + this.htmlElement + ":";
- }
-
-}
-
-
-//-------------------- ricoDropzone.js
-Rico.Dropzone = Class.create();
-
-Rico.Dropzone.prototype = {
-
- initialize: function( htmlElement ) {
- this.htmlElement = $(htmlElement);
- this.absoluteRect = null;
- },
-
- getHTMLElement: function() {
- return this.htmlElement;
- },
-
- clearPositionCache: function() {
- this.absoluteRect = null;
- },
-
- getAbsoluteRect: function() {
- if ( this.absoluteRect == null ) {
- var htmlElement = this.getHTMLElement();
- var pos = RicoUtil.toViewportPosition(htmlElement);
-
- this.absoluteRect = {
- top: pos.y,
- left: pos.x,
- bottom: pos.y + htmlElement.offsetHeight,
- right: pos.x + htmlElement.offsetWidth
- };
- }
- return this.absoluteRect;
- },
-
- activate: function() {
- var htmlElement = this.getHTMLElement();
- if (htmlElement == null || this.showingActive)
- return;
-
- this.showingActive = true;
- this.saveBackgroundColor = htmlElement.style.backgroundColor;
-
- var fallbackColor = "#ffea84";
- var currentColor = Rico.Color.createColorFromBackground(htmlElement);
- if ( currentColor == null )
- htmlElement.style.backgroundColor = fallbackColor;
- else {
- currentColor.isBright() ? currentColor.darken(0.2) : currentColor.brighten(0.2);
- htmlElement.style.backgroundColor = currentColor.asHex();
- }
- },
-
- deactivate: function() {
- var htmlElement = this.getHTMLElement();
- if (htmlElement == null || !this.showingActive)
- return;
-
- htmlElement.style.backgroundColor = this.saveBackgroundColor;
- this.showingActive = false;
- this.saveBackgroundColor = null;
- },
-
- showHover: function() {
- var htmlElement = this.getHTMLElement();
- if ( htmlElement == null || this.showingHover )
- return;
-
- this.saveBorderWidth = htmlElement.style.borderWidth;
- this.saveBorderStyle = htmlElement.style.borderStyle;
- this.saveBorderColor = htmlElement.style.borderColor;
-
- this.showingHover = true;
- htmlElement.style.borderWidth = "1px";
- htmlElement.style.borderStyle = "solid";
- //htmlElement.style.borderColor = "#ff9900";
- htmlElement.style.borderColor = "#ffff00";
- },
-
- hideHover: function() {
- var htmlElement = this.getHTMLElement();
- if ( htmlElement == null || !this.showingHover )
- return;
-
- htmlElement.style.borderWidth = this.saveBorderWidth;
- htmlElement.style.borderStyle = this.saveBorderStyle;
- htmlElement.style.borderColor = this.saveBorderColor;
- this.showingHover = false;
- },
-
- canAccept: function(draggableObjects) {
- return true;
- },
-
- accept: function(draggableObjects) {
- var htmlElement = this.getHTMLElement();
- if ( htmlElement == null )
- return;
-
- n = draggableObjects.length;
- for ( var i = 0 ; i < n ; i++ )
- {
- var theGUI = draggableObjects[i].getDroppedGUI();
- if ( RicoUtil.getElementsComputedStyle( theGUI, "position" ) == "absolute" )
- {
- theGUI.style.position = "static";
- theGUI.style.top = "";
- theGUI.style.top = "";
- }
- htmlElement.appendChild(theGUI);
- }
- }
-}
-
-
-//-------------------- ricoEffects.js
-
-/**
- * Use the Effect namespace for effects. If using scriptaculous effects
- * this will already be defined, otherwise we'll just create an empty
- * object for it...
- **/
-if ( window.Effect == undefined )
- Effect = {};
-
-Effect.SizeAndPosition = Class.create();
-Effect.SizeAndPosition.prototype = {
-
- initialize: function(element, x, y, w, h, duration, steps, options) {
- this.element = $(element);
- this.x = x;
- this.y = y;
- this.w = w;
- this.h = h;
- this.duration = duration;
- this.steps = steps;
- this.options = arguments[7] || {};
-
- this.sizeAndPosition();
- },
-
- sizeAndPosition: function() {
- if (this.isFinished()) {
- if(this.options.complete) this.options.complete(this);
- return;
- }
-
- if (this.timer)
- clearTimeout(this.timer);
-
- var stepDuration = Math.round(this.duration/this.steps) ;
-
- // Get original values: x,y = top left corner; w,h = width height
- var currentX = this.element.offsetLeft;
- var currentY = this.element.offsetTop;
- var currentW = this.element.offsetWidth;
- var currentH = this.element.offsetHeight;
-
- // If values not set, or zero, we do not modify them, and take original as final as well
- this.x = (this.x) ? this.x : currentX;
- this.y = (this.y) ? this.y : currentY;
- this.w = (this.w) ? this.w : currentW;
- this.h = (this.h) ? this.h : currentH;
-
- // how much do we need to modify our values for each step?
- var difX = this.steps > 0 ? (this.x - currentX)/this.steps : 0;
- var difY = this.steps > 0 ? (this.y - currentY)/this.steps : 0;
- var difW = this.steps > 0 ? (this.w - currentW)/this.steps : 0;
- var difH = this.steps > 0 ? (this.h - currentH)/this.steps : 0;
-
- this.moveBy(difX, difY);
- this.resizeBy(difW, difH);
-
- this.duration -= stepDuration;
- this.steps--;
-
- this.timer = setTimeout(this.sizeAndPosition.bind(this), stepDuration);
- },
-
- isFinished: function() {
- return this.steps <= 0;
- },
-
- moveBy: function( difX, difY ) {
- var currentLeft = this.element.offsetLeft;
- var currentTop = this.element.offsetTop;
- var intDifX = parseInt(difX);
- var intDifY = parseInt(difY);
-
- var style = this.element.style;
- if ( intDifX != 0 )
- style.left = (currentLeft + intDifX) + "px";
- if ( intDifY != 0 )
- style.top = (currentTop + intDifY) + "px";
- },
-
- resizeBy: function( difW, difH ) {
- var currentWidth = this.element.offsetWidth;
- var currentHeight = this.element.offsetHeight;
- var intDifW = parseInt(difW);
- var intDifH = parseInt(difH);
-
- var style = this.element.style;
- if ( intDifW != 0 )
- style.width = (currentWidth + intDifW) + "px";
- if ( intDifH != 0 )
- style.height = (currentHeight + intDifH) + "px";
- }
-}
-
-Effect.Size = Class.create();
-Effect.Size.prototype = {
-
- initialize: function(element, w, h, duration, steps, options) {
- new Effect.SizeAndPosition(element, null, null, w, h, duration, steps, options);
- }
-}
-
-Effect.Position = Class.create();
-Effect.Position.prototype = {
-
- initialize: function(element, x, y, duration, steps, options) {
- new Effect.SizeAndPosition(element, x, y, null, null, duration, steps, options);
- }
-}
-
-Effect.Round = Class.create();
-Effect.Round.prototype = {
-
- initialize: function(tagName, className, options) {
- var elements = document.getElementsByTagAndClassName(tagName,className);
- for ( var i = 0 ; i < elements.length ; i++ )
- Rico.Corner.round( elements[i], options );
- }
-};
-
-Effect.FadeTo = Class.create();
-Effect.FadeTo.prototype = {
-
- initialize: function( element, opacity, duration, steps, options) {
- this.element = $(element);
- this.opacity = opacity;
- this.duration = duration;
- this.steps = steps;
- this.options = arguments[4] || {};
- this.fadeTo();
- },
-
- fadeTo: function() {
- if (this.isFinished()) {
- if(this.options.complete) this.options.complete(this);
- return;
- }
-
- if (this.timer)
- clearTimeout(this.timer);
-
- var stepDuration = Math.round(this.duration/this.steps) ;
- var currentOpacity = this.getElementOpacity();
- var delta = this.steps > 0 ? (this.opacity - currentOpacity)/this.steps : 0;
-
- this.changeOpacityBy(delta);
- this.duration -= stepDuration;
- this.steps--;
-
- this.timer = setTimeout(this.fadeTo.bind(this), stepDuration);
- },
-
- changeOpacityBy: function(v) {
- var currentOpacity = this.getElementOpacity();
- var newOpacity = Math.max(0, Math.min(currentOpacity+v, 1));
- this.element.ricoOpacity = newOpacity;
-
- this.element.style.filter = "alpha(opacity:"+Math.round(newOpacity*100)+")";
- this.element.style.opacity = newOpacity; /*//*/;
- },
-
- isFinished: function() {
- return this.steps <= 0;
- },
-
- getElementOpacity: function() {
- if ( this.element.ricoOpacity == undefined ) {
- var opacity;
- if ( this.element.currentStyle ) {
- opacity = this.element.currentStyle.opacity;
- }
- else if ( document.defaultView.getComputedStyle != undefined ) {
- var computedStyle = document.defaultView.getComputedStyle;
- opacity = computedStyle(this.element, null).getPropertyValue('opacity');
- }
-
- this.element.ricoOpacity = opacity != undefined ? opacity : 1.0;
- }
-
- return parseFloat(this.element.ricoOpacity);
- }
-}
-
-Effect.AccordionSize = Class.create();
-
-Effect.AccordionSize.prototype = {
-
- initialize: function(e1, e2, start, end, duration, steps, options) {
- this.e1 = $(e1);
- this.e2 = $(e2);
- this.start = start;
- this.end = end;
- this.duration = duration;
- this.steps = steps;
- this.options = arguments[6] || {};
-
- this.accordionSize();
- },
-
- accordionSize: function() {
-
- if (this.isFinished()) {
- // just in case there are round errors or such...
- this.e1.style.height = this.start + "px";
- this.e2.style.height = this.end + "px";
-
- if(this.options.complete)
- this.options.complete(this);
- return;
- }
-
- if (this.timer)
- clearTimeout(this.timer);
-
- var stepDuration = Math.round(this.duration/this.steps) ;
-
- var diff = this.steps > 0 ? (parseInt(this.e1.offsetHeight) - this.start)/this.steps : 0;
- this.resizeBy(diff);
-
- this.duration -= stepDuration;
- this.steps--;
-
- this.timer = setTimeout(this.accordionSize.bind(this), stepDuration);
- },
-
- isFinished: function() {
- return this.steps <= 0;
- },
-
- resizeBy: function(diff) {
- var h1Height = this.e1.offsetHeight;
- var h2Height = this.e2.offsetHeight;
- var intDiff = parseInt(diff);
- if ( diff != 0 ) {
- this.e1.style.height = (h1Height - intDiff) + "px";
- this.e2.style.height = (h2Height + intDiff) + "px";
- }
- }
-
-};
-
-
-//-------------------- ricoLiveGrid.js
-
-// Rico.LiveGridMetaData -----------------------------------------------------
-
-Rico.LiveGridMetaData = Class.create();
-
-Rico.LiveGridMetaData.prototype = {
-
- initialize: function( pageSize, totalRows, columnCount, options ) {
- this.pageSize = pageSize;
- this.totalRows = totalRows;
- this.setOptions(options);
- this.scrollArrowHeight = 16;
- this.columnCount = columnCount;
- },
-
- setOptions: function(options) {
- this.options = {
- largeBufferSize : 7.0, // 7 pages
- nearLimitFactor : 0.2 // 20% of buffer
- }.extend(options || {});
- },
-
- getPageSize: function() {
- return this.pageSize;
- },
-
- getTotalRows: function() {
- return this.totalRows;
- },
-
- setTotalRows: function(n) {
- this.totalRows = n;
- },
-
- getLargeBufferSize: function() {
- return parseInt(this.options.largeBufferSize * this.pageSize);
- },
-
- getLimitTolerance: function() {
- return parseInt(this.getLargeBufferSize() * this.options.nearLimitFactor);
- }
-};
-
-// Rico.LiveGridScroller -----------------------------------------------------
-
-Rico.LiveGridScroller = Class.create();
-
-Rico.LiveGridScroller.prototype = {
-
- initialize: function(liveGrid, viewPort) {
- this.isIE = navigator.userAgent.toLowerCase().indexOf("msie") >= 0;
- this.liveGrid = liveGrid;
- this.metaData = liveGrid.metaData;
- this.createScrollBar();
- this.scrollTimeout = null;
- this.lastScrollPos = 0;
- this.viewPort = viewPort;
- this.rows = new Array();
- },
-
- isUnPlugged: function() {
- return this.scrollerDiv.onscroll == null;
- },
-
- plugin: function() {
- this.scrollerDiv.onscroll = this.handleScroll.bindAsEventListener(this);
- },
-
- unplug: function() {
- this.scrollerDiv.onscroll = null;
- },
-
- sizeIEHeaderHack: function() {
- if ( !this.isIE ) return;
- var headerTable = $(this.liveGrid.tableId + "_header");
- if ( headerTable )
- headerTable.rows[0].cells[0].style.width =
- (headerTable.rows[0].cells[0].offsetWidth + 1) + "px";
- },
-
- createScrollBar: function() {
- var visibleHeight = this.liveGrid.viewPort.visibleHeight();
- // create the outer div...
- this.scrollerDiv = document.createElement("div");
- var scrollerStyle = this.scrollerDiv.style;
- scrollerStyle.borderRight = "1px solid #ababab"; // hard coded color!!!
- scrollerStyle.position = "relative";
- scrollerStyle.left = this.isIE ? "-6px" : "-3px";
- scrollerStyle.width = "19px";
- scrollerStyle.height = visibleHeight + "px";
- scrollerStyle.overflow = "auto";
-
- // create the inner div...
- this.heightDiv = document.createElement("div");
- this.heightDiv.style.width = "1px";
-
- this.heightDiv.style.height = parseInt(visibleHeight *
- this.metaData.getTotalRows()/this.metaData.getPageSize()) + "px" ;
- this.scrollerDiv.appendChild(this.heightDiv);
- this.scrollerDiv.onscroll = this.handleScroll.bindAsEventListener(this);
-
- var table = this.liveGrid.table;
- table.parentNode.parentNode.insertBefore( this.scrollerDiv, table.parentNode.nextSibling );
- },
-
- updateSize: function() {
- var table = this.liveGrid.table;
- var visibleHeight = this.viewPort.visibleHeight();
- this.heightDiv.style.height = parseInt(visibleHeight *
- this.metaData.getTotalRows()/this.metaData.getPageSize()) + "px";
- },
-
- rowToPixel: function(rowOffset) {
- return (rowOffset / this.metaData.getTotalRows()) * this.heightDiv.offsetHeight
- },
-
- moveScroll: function(rowOffset) {
- this.scrollerDiv.scrollTop = this.rowToPixel(rowOffset);
- if ( this.metaData.options.onscroll )
- this.metaData.options.onscroll( this.liveGrid, rowOffset );
- },
-
- handleScroll: function() {
- if ( this.scrollTimeout )
- clearTimeout( this.scrollTimeout );
-
- var contentOffset = parseInt(this.scrollerDiv.scrollTop / this.viewPort.rowHeight);
- this.liveGrid.requestContentRefresh(contentOffset);
- this.viewPort.scrollTo(this.scrollerDiv.scrollTop);
-
- if ( this.metaData.options.onscroll )
- this.metaData.options.onscroll( this.liveGrid, contentOffset );
-
- this.scrollTimeout = setTimeout( this.scrollIdle.bind(this), 1200 );
- },
-
- scrollIdle: function() {
- if ( this.metaData.options.onscrollidle )
- this.metaData.options.onscrollidle();
- }
-};
-
-// Rico.LiveGridBuffer -----------------------------------------------------
-
-Rico.LiveGridBuffer = Class.create();
-
-Rico.LiveGridBuffer.prototype = {
-
- initialize: function(metaData, viewPort) {
- this.startPos = 0;
- this.size = 0;
- this.metaData = metaData;
- this.rows = new Array();
- this.updateInProgress = false;
- this.viewPort = viewPort;
- this.maxBufferSize = metaData.getLargeBufferSize() * 2;
- this.maxFetchSize = metaData.getLargeBufferSize();
- this.lastOffset = 0;
- },
-
- getBlankRow: function() {
- if (!this.blankRow ) {
- this.blankRow = new Array();
- for ( var i=0; i < this.metaData.columnCount ; i++ )
- this.blankRow[i] = " ";
- }
- return this.blankRow;
- },
-
- loadRows: function(ajaxResponse) {
- var rowsElement = ajaxResponse.getElementsByTagName('rows')[0];
- this.updateUI = rowsElement.getAttribute("update_ui") == "true"
- var newRows = new Array()
- var trs = rowsElement.getElementsByTagName("tr");
- for ( var i=0 ; i < trs.length; i++ ) {
- var row = newRows[i] = new Array();
- var cells = trs[i].getElementsByTagName("td");
- for ( var j=0; j < cells.length ; j++ ) {
- var cell = cells[j];
- var convertSpaces = cell.getAttribute("convert_spaces") == "true";
- var cellContent = RicoUtil.getContentAsString(cell);
- row[j] = convertSpaces ? this.convertSpaces(cellContent) : cellContent;
- if (!row[j])
- row[j] = ' ';
- }
- }
- return newRows;
- },
-
- update: function(ajaxResponse, start) {
- var newRows = this.loadRows(ajaxResponse);
- if (this.rows.length == 0) { // initial load
- this.rows = newRows;
- this.size = this.rows.length;
- this.startPos = start;
- return;
- }
- if (start > this.startPos) { //appending
- if (this.startPos + this.rows.length < start) {
- this.rows = newRows;
- this.startPos = start;//
- } else {
- this.rows = this.rows.concat( newRows.slice(0, newRows.length));
- if (this.rows.length > this.maxBufferSize) {
- var fullSize = this.rows.length;
- this.rows = this.rows.slice(this.rows.length - this.maxBufferSize, this.rows.length)
- this.startPos = this.startPos + (fullSize - this.rows.length);
- }
- }
- } else { //prepending
- if (start + newRows.length < this.startPos) {
- this.rows = newRows;
- } else {
- this.rows = newRows.slice(0, this.startPos).concat(this.rows);
- if (this.rows.length > this.maxBufferSize)
- this.rows = this.rows.slice(0, this.maxBufferSize)
- }
- this.startPos = start;
- }
- this.size = this.rows.length;
- },
-
- clear: function() {
- this.rows = new Array();
- this.startPos = 0;
- this.size = 0;
- },
-
- isOverlapping: function(start, size) {
- return ((start < this.endPos()) && (this.startPos < start + size)) || (this.endPos() == 0)
- },
-
- isInRange: function(position) {
- return (position >= this.startPos) && (position + this.metaData.getPageSize() <= this.endPos());
- //&& this.size() != 0;
- },
-
- isNearingTopLimit: function(position) {
- return position - this.startPos < this.metaData.getLimitTolerance();
- },
-
- endPos: function() {
- return this.startPos + this.rows.length;
- },
-
- isNearingBottomLimit: function(position) {
- return this.endPos() - (position + this.metaData.getPageSize()) < this.metaData.getLimitTolerance();
- },
-
- isAtTop: function() {
- return this.startPos == 0;
- },
-
- isAtBottom: function() {
- return this.endPos() == this.metaData.getTotalRows();
- },
-
- isNearingLimit: function(position) {
- return ( !this.isAtTop() && this.isNearingTopLimit(position)) ||
- ( !this.isAtBottom() && this.isNearingBottomLimit(position) )
- },
-
- getFetchSize: function(offset) {
- var adjustedOffset = this.getFetchOffset(offset);
- var adjustedSize = 0;
- if (adjustedOffset >= this.startPos) { //apending
- var endFetchOffset = this.maxFetchSize + adjustedOffset;
- if (endFetchOffset > this.metaData.totalRows)
- endFetchOffset = this.metaData.totalRows;
- adjustedSize = endFetchOffset - adjustedOffset;
- } else {//prepending
- var adjustedSize = this.startPos - adjustedOffset;
- if (adjustedSize > this.maxFetchSize)
- adjustedSize = this.maxFetchSize;
- }
- return adjustedSize;
- },
-
- getFetchOffset: function(offset) {
- var adjustedOffset = offset;
- if (offset > this.startPos) //apending
- adjustedOffset = (offset > this.endPos()) ? offset : this.endPos();
- else { //prepending
- if (offset + this.maxFetchSize >= this.startPos) {
- var adjustedOffset = this.startPos - this.maxFetchSize;
- if (adjustedOffset < 0)
- adjustedOffset = 0;
- }
- }
- this.lastOffset = adjustedOffset;
- return adjustedOffset;
- },
-
- getRows: function(start, count) {
- var begPos = start - this.startPos
- var endPos = begPos + count
-
- // er? need more data...
- if ( endPos > this.size )
- endPos = this.size
-
- var results = new Array()
- var index = 0;
- for ( var i=begPos ; i < endPos; i++ ) {
- results[index++] = this.rows[i]
- }
- return results
- },
-
- convertSpaces: function(s) {
- return s.split(" ").join(" ");
- }
-
-};
-
-
-//Rico.GridViewPort --------------------------------------------------
-Rico.GridViewPort = Class.create();
-
-Rico.GridViewPort.prototype = {
-
- initialize: function(table, rowHeight, visibleRows, buffer, liveGrid) {
- this.lastDisplayedStartPos = 0;
- this.div = table.parentNode;
- this.table = table
- this.rowHeight = rowHeight;
- this.div.style.height = this.rowHeight * visibleRows;
- this.div.style.overflow = "hidden";
- this.buffer = buffer;
- this.liveGrid = liveGrid;
- this.visibleRows = visibleRows + 1;
- this.lastPixelOffset = 0;
- this.startPos = 0;
- },
-
- populateRow: function(htmlRow, row) {
- for (var j=0; j < row.length; j++) {
- htmlRow.cells[j].innerHTML = row[j]
- }
- },
-
- bufferChanged: function() {
- this.refreshContents( parseInt(this.lastPixelOffset / this.rowHeight));
- },
-
- clearRows: function() {
- if (!this.isBlank) {
- for (var i=0; i < this.visibleRows; i++)
- this.populateRow(this.table.rows[i], this.buffer.getBlankRow());
- this.isBlank = true;
- }
- },
-
- clearContents: function() {
- this.clearRows();
- this.scrollTo(0);
- this.startPos = 0;
- this.lastStartPos = -1;
- },
-
- refreshContents: function(startPos) {
- if (startPos == this.lastRowPos && !this.isPartialBlank && !this.isBlank) {
- return;
- }
- if ((startPos + this.visibleRows < this.buffer.startPos)
- || (this.buffer.startPos + this.buffer.size < startPos)
- || (this.buffer.size == 0)) {
- this.clearRows();
- return;
- }
- this.isBlank = false;
- var viewPrecedesBuffer = this.buffer.startPos > startPos
- var contentStartPos = viewPrecedesBuffer ? this.buffer.startPos: startPos;
-
- var contentEndPos = (this.buffer.startPos + this.buffer.size < startPos + this.visibleRows)
- ? this.buffer.startPos + this.buffer.size
- : startPos + this.visibleRows;
- var rowSize = contentEndPos - contentStartPos;
- var rows = this.buffer.getRows(contentStartPos, rowSize );
- var blankSize = this.visibleRows - rowSize;
- var blankOffset = viewPrecedesBuffer ? 0: rowSize;
- var contentOffset = viewPrecedesBuffer ? blankSize: 0;
-
- for (var i=0; i < rows.length; i++) {//initialize what we have
- this.populateRow(this.table.rows[i + contentOffset], rows[i]);
- }
- for (var i=0; i < blankSize; i++) {// blank out the rest
- this.populateRow(this.table.rows[i + blankOffset], this.buffer.getBlankRow());
- }
- this.isPartialBlank = blankSize > 0;
- this.lastRowPos = startPos;
- },
-
- scrollTo: function(pixelOffset) {
- if (this.lastPixelOffset == pixelOffset)
- return;
-
- this.refreshContents(parseInt(pixelOffset / this.rowHeight))
- this.div.scrollTop = pixelOffset % this.rowHeight
-
- this.lastPixelOffset = pixelOffset;
- },
-
- visibleHeight: function() {
- return parseInt(this.div.style.height);
- }
-
-};
-
-
-Rico.LiveGridRequest = Class.create();
-Rico.LiveGridRequest.prototype = {
- initialize: function( requestOffset, options ) {
- this.requestOffset = requestOffset;
- }
-};
-
-// Rico.LiveGrid -----------------------------------------------------
-
-Rico.LiveGrid = Class.create();
-
-Rico.LiveGrid.prototype = {
-
- initialize: function( tableId, visibleRows, totalRows, url, options ) {
- if ( options == null )
- options = {};
-
- this.tableId = tableId;
- this.table = $(tableId);
- var columnCount = this.table.rows[0].cells.length
- this.metaData = new Rico.LiveGridMetaData(visibleRows, totalRows, columnCount, options);
- this.buffer = new Rico.LiveGridBuffer(this.metaData);
-
- var rowCount = this.table.rows.length;
- this.viewPort = new Rico.GridViewPort(this.table,
- this.table.offsetHeight/rowCount,
- visibleRows,
- this.buffer, this);
- this.scroller = new Rico.LiveGridScroller(this,this.viewPort);
-
- this.additionalParms = options.requestParameters || [];
-
- options.sortHandler = this.sortHandler.bind(this);
-
- if ( $(tableId + '_header') )
- this.sort = new Rico.LiveGridSort(tableId + '_header', options)
-
- this.processingRequest = null;
- this.unprocessedRequest = null;
-
- this.initAjax(url);
- if ( options.prefetchBuffer || options.prefetchOffset > 0) {
- var offset = 0;
- if (options.offset ) {
- offset = options.offset;
- this.scroller.moveScroll(offset);
- this.viewPort.scrollTo(this.scroller.rowToPixel(offset));
- }
- if (options.sortCol) {
- this.sortCol = options.sortCol;
- this.sortDir = options.sortDir;
- }
- this.requestContentRefresh(offset);
- }
- },
-
- resetContents: function() {
- this.scroller.moveScroll(0);
- this.buffer.clear();
- this.viewPort.clearContents();
- },
-
- sortHandler: function(column) {
- this.sortCol = column.name;
- this.sortDir = column.currentSort;
-
- this.resetContents();
- this.requestContentRefresh(0)
- },
-
- setRequestParams: function() {
- this.additionalParms = [];
- for ( var i=0 ; i < arguments.length ; i++ )
- this.additionalParms[i] = arguments[i];
- },
-
- setTotalRows: function( newTotalRows ) {
- this.resetContents();
- this.metaData.setTotalRows(newTotalRows);
- this.scroller.updateSize();
- },
-
- initAjax: function(url) {
- ajaxEngine.registerRequest( this.tableId + '_request', url );
- ajaxEngine.registerAjaxObject( this.tableId + '_updater', this );
- },
-
- invokeAjax: function() {
- },
-
- handleTimedOut: function() {
- //server did not respond in 4 seconds... assume that there could have been
- //an error or something, and allow requests to be processed again...
- this.processingRequest = null;
- this.processQueuedRequest();
- },
-
- fetchBuffer: function(offset) {
- if ( this.buffer.isInRange(offset) &&
- !this.buffer.isNearingLimit(offset)) {
- return;
- }
- if (this.processingRequest) {
- this.unprocessedRequest = new Rico.LiveGridRequest(offset);
- return;
- }
- var bufferStartPos = this.buffer.getFetchOffset(offset);
- this.processingRequest = new Rico.LiveGridRequest(offset);
- this.processingRequest.bufferOffset = bufferStartPos;
- var fetchSize = this.buffer.getFetchSize(offset);
- var partialLoaded = false;
- var callParms = [];
- callParms.push(this.tableId + '_request');
- callParms.push('id=' + this.tableId);
- callParms.push('page_size=' + fetchSize);
- callParms.push('offset=' + bufferStartPos);
- if ( this.sortCol) {
- callParms.push('sort_col=' + this.sortCol);
- callParms.push('sort_dir=' + this.sortDir);
- }
-
- for( var i=0 ; i < this.additionalParms.length ; i++ )
- callParms.push(this.additionalParms[i]);
- ajaxEngine.sendRequest.apply( ajaxEngine, callParms );
-
- this.timeoutHandler = setTimeout( this.handleTimedOut.bind(this), 20000 ); //todo: make as option
- },
-
- requestContentRefresh: function(contentOffset) {
- this.fetchBuffer(contentOffset);
- },
-
- ajaxUpdate: function(ajaxResponse) {
- try {
- clearTimeout( this.timeoutHandler );
- this.buffer.update(ajaxResponse,this.processingRequest.bufferOffset);
- this.viewPort.bufferChanged();
- }
- catch(err) {}
- finally {this.processingRequest = null; }
- this.processQueuedRequest();
- },
-
- processQueuedRequest: function() {
- if (this.unprocessedRequest != null) {
- this.requestContentRefresh(this.unprocessedRequest.requestOffset);
- this.unprocessedRequest = null
- }
- }
-
-};
-
-
-//-------------------- ricoLiveGridSort.js
-Rico.LiveGridSort = Class.create();
-
-Rico.LiveGridSort.prototype = {
-
- initialize: function(headerTableId, options) {
- this.headerTableId = headerTableId;
- this.headerTable = $(headerTableId);
- this.setOptions(options);
- this.applySortBehavior();
-
- if ( this.options.sortCol ) {
- this.setSortUI( this.options.sortCol, this.options.sortDir );
- }
- },
-
- setSortUI: function( columnName, sortDirection ) {
- var cols = this.options.columns;
- for ( var i = 0 ; i < cols.length ; i++ ) {
- if ( cols[i].name == columnName ) {
- this.setColumnSort(i, sortDirection);
- break;
- }
- }
- },
-
- setOptions: function(options) {
- this.options = {
- sortAscendImg: 'images/sort_asc.gif',
- sortDescendImg: 'images/sort_desc.gif',
- imageWidth: 9,
- imageHeight: 5,
- ajaxSortURLParms: []
- }.extend(options);
-
- // preload the images...
- new Image().src = this.options.sortAscendImg;
- new Image().src = this.options.sortDescendImg;
-
- this.sort = options.sortHandler;
- if ( !this.options.columns )
- this.options.columns = this.introspectForColumnInfo();
- else {
- // allow client to pass { columns: [ ["a", true], ["b", false] ] }
- // and convert to an array of Rico.TableColumn objs...
- this.options.columns = this.convertToTableColumns(this.options.columns);
- }
- },
-
- applySortBehavior: function() {
- var headerRow = this.headerTable.rows[0];
- var headerCells = headerRow.cells;
- for ( var i = 0 ; i < headerCells.length ; i++ ) {
- this.addSortBehaviorToColumn( i, headerCells[i] );
- }
- },
-
- addSortBehaviorToColumn: function( n, cell ) {
- if ( this.options.columns[n].isSortable() ) {
- cell.id = this.headerTableId + '_' + n;
- cell.style.cursor = 'pointer';
- cell.onclick = this.headerCellClicked.bindAsEventListener(this);
- cell.innerHTML = cell.innerHTML + '<span id="' + this.headerTableId + '_img_' + n + '">'
- + ' </span>';
- }
- },
-
- // event handler....
- headerCellClicked: function(evt) {
- var eventTarget = evt.target ? evt.target : evt.srcElement;
- var cellId = eventTarget.id;
- var columnNumber = parseInt(cellId.substring( cellId.lastIndexOf('_') + 1 ));
- var sortedColumnIndex = this.getSortedColumnIndex();
- if ( sortedColumnIndex != -1 ) {
- if ( sortedColumnIndex != columnNumber ) {
- this.removeColumnSort(sortedColumnIndex);
- this.setColumnSort(columnNumber, Rico.TableColumn.SORT_ASC);
- }
- else
- this.toggleColumnSort(sortedColumnIndex);
- }
- else
- this.setColumnSort(columnNumber, Rico.TableColumn.SORT_ASC);
-
- if (this.options.sortHandler) {
- this.options.sortHandler(this.options.columns[columnNumber]);
- }
- },
-
- removeColumnSort: function(n) {
- this.options.columns[n].setUnsorted();
- this.setSortImage(n);
- },
-
- setColumnSort: function(n, direction) {
- this.options.columns[n].setSorted(direction);
- this.setSortImage(n);
- },
-
- toggleColumnSort: function(n) {
- this.options.columns[n].toggleSort();
- this.setSortImage(n);
- },
-
- setSortImage: function(n) {
- var sortDirection = this.options.columns[n].getSortDirection();
-
- var sortImageSpan = $( this.headerTableId + '_img_' + n );
- if ( sortDirection == Rico.TableColumn.UNSORTED )
- sortImageSpan.innerHTML = ' ';
- else if ( sortDirection == Rico.TableColumn.SORT_ASC )
- sortImageSpan.innerHTML = ' <img width="' + this.options.imageWidth + '" ' +
- 'height="'+ this.options.imageHeight + '" ' +
- 'src="' + this.options.sortAscendImg + '"/>';
- else if ( sortDirection == Rico.TableColumn.SORT_DESC )
- sortImageSpan.innerHTML = ' <img width="' + this.options.imageWidth + '" ' +
- 'height="'+ this.options.imageHeight + '" ' +
- 'src="' + this.options.sortDescendImg + '"/>';
- },
-
- getSortedColumnIndex: function() {
- var cols = this.options.columns;
- for ( var i = 0 ; i < cols.length ; i++ ) {
- if ( cols[i].isSorted() )
- return i;
- }
-
- return -1;
- },
-
- introspectForColumnInfo: function() {
- var columns = new Array();
- var headerRow = this.headerTable.rows[0];
- var headerCells = headerRow.cells;
- for ( var i = 0 ; i < headerCells.length ; i++ )
- columns.push( new Rico.TableColumn( this.deriveColumnNameFromCell(headerCells[i],i), true ) );
- return columns;
- },
-
- convertToTableColumns: function(cols) {
- var columns = new Array();
- for ( var i = 0 ; i < cols.length ; i++ )
- columns.push( new Rico.TableColumn( cols[i][0], cols[i][1] ) );
- },
-
- deriveColumnNameFromCell: function(cell,columnNumber) {
- var cellContent = cell.innerText != undefined ? cell.innerText : cell.textContent;
- return cellContent ? cellContent.toLowerCase().split(' ').join('_') : "col_" + columnNumber;
- }
-};
-
-Rico.TableColumn = Class.create();
-
-Rico.TableColumn.UNSORTED = 0;
-Rico.TableColumn.SORT_ASC = "ASC";
-Rico.TableColumn.SORT_DESC = "DESC";
-
-Rico.TableColumn.prototype = {
- initialize: function(name, sortable) {
- this.name = name;
- this.sortable = sortable;
- this.currentSort = Rico.TableColumn.UNSORTED;
- },
-
- isSortable: function() {
- return this.sortable;
- },
-
- isSorted: function() {
- return this.currentSort != Rico.TableColumn.UNSORTED;
- },
-
- getSortDirection: function() {
- return this.currentSort;
- },
-
- toggleSort: function() {
- if ( this.currentSort == Rico.TableColumn.UNSORTED || this.currentSort == Rico.TableColumn.SORT_DESC )
- this.currentSort = Rico.TableColumn.SORT_ASC;
- else if ( this.currentSort == Rico.TableColumn.SORT_ASC )
- this.currentSort = Rico.TableColumn.SORT_DESC;
- },
-
- setUnsorted: function(direction) {
- this.setSorted(Rico.TableColumn.UNSORTED);
- },
-
- setSorted: function(direction) {
- // direction must by one of Rico.TableColumn.UNSORTED, .SORT_ASC, or .SET_DESC...
- this.currentSort = direction;
- }
-
-};
-
-
-//-------------------- ricoUtil.js
-
-var RicoUtil = {
-
- getElementsComputedStyle: function ( htmlElement, cssProperty, mozillaEquivalentCSS) {
- if ( arguments.length == 2 )
- mozillaEquivalentCSS = cssProperty;
-
- var el = $(htmlElement);
- if ( el.currentStyle )
- return el.currentStyle[cssProperty];
- else
- return document.defaultView.getComputedStyle(el, null).getPropertyValue(mozillaEquivalentCSS);
- },
-
- createXmlDocument : function() {
- if (document.implementation && document.implementation.createDocument) {
- var doc = document.implementation.createDocument("", "", null);
-
- if (doc.readyState == null) {
- doc.readyState = 1;
- doc.addEventListener("load", function () {
- doc.readyState = 4;
- if (typeof doc.onreadystatechange == "function")
- doc.onreadystatechange();
- }, false);
- }
-
- return doc;
- }
-
- if (window.ActiveXObject)
- return Try.these(
- function() { return new ActiveXObject('MSXML2.DomDocument') },
- function() { return new ActiveXObject('Microsoft.DomDocument')},
- function() { return new ActiveXObject('MSXML.DomDocument') },
- function() { return new ActiveXObject('MSXML3.DomDocument') }
- ) || false;
-
- return null;
- },
-
- getContentAsString: function( parentNode ) {
- return parentNode.xml != undefined ?
- this._getContentAsStringIE(parentNode) :
- this._getContentAsStringMozilla(parentNode);
- },
-
- _getContentAsStringIE: function(parentNode) {
- var contentStr = "";
- for ( var i = 0 ; i < parentNode.childNodes.length ; i++ )
- contentStr += parentNode.childNodes[i].xml;
- return contentStr;
- },
-
- _getContentAsStringMozilla: function(parentNode) {
- var xmlSerializer = new XMLSerializer();
- var contentStr = "";
- for ( var i = 0 ; i < parentNode.childNodes.length ; i++ )
- contentStr += xmlSerializer.serializeToString(parentNode.childNodes[i]);
- return contentStr;
- },
-
- toViewportPosition: function(element) {
- return this._toAbsolute(element,true);
- },
-
- toDocumentPosition: function(element) {
- return this._toAbsolute(element,false);
- },
-
- /**
- * Compute the elements position in terms of the window viewport
- * so that it can be compared to the position of the mouse (dnd)
- * This is additions of all the offsetTop,offsetLeft values up the
- * offsetParent hierarchy, ...taking into account any scrollTop,
- * scrollLeft values along the way...
- *
- * IE has a bug reporting a correct offsetLeft of elements within a
- * a relatively positioned parent!!!
- **/
- _toAbsolute: function(element,accountForDocScroll) {
-
- if ( navigator.userAgent.toLowerCase().indexOf("msie") == -1 )
- return this._toAbsoluteMozilla(element,accountForDocScroll);
-
- var x = 0;
- var y = 0;
- var parent = element;
- while ( parent ) {
-
- var borderXOffset = 0;
- var borderYOffset = 0;
- if ( parent != element ) {
- var borderXOffset = parseInt(this.getElementsComputedStyle(parent, "borderLeftWidth" ));
- var borderYOffset = parseInt(this.getElementsComputedStyle(parent, "borderTopWidth" ));
- borderXOffset = isNaN(borderXOffset) ? 0 : borderXOffset;
- borderYOffset = isNaN(borderYOffset) ? 0 : borderYOffset;
- }
-
- x += parent.offsetLeft - parent.scrollLeft + borderXOffset;
- y += parent.offsetTop - parent.scrollTop + borderYOffset;
- parent = parent.offsetParent;
- }
-
- if ( accountForDocScroll ) {
- x -= this.docScrollLeft();
- y -= this.docScrollTop();
- }
-
- return { x:x, y:y };
- },
-
- /**
- * Mozilla did not report all of the parents up the hierarchy via the
- * offsetParent property that IE did. So for the calculation of the
- * offsets we use the offsetParent property, but for the calculation of
- * the scrollTop/scrollLeft adjustments we navigate up via the parentNode
- * property instead so as to get the scroll offsets...
- *
- **/
- _toAbsoluteMozilla: function(element,accountForDocScroll) {
- var x = 0;
- var y = 0;
- var parent = element;
- while ( parent ) {
- x += parent.offsetLeft;
- y += parent.offsetTop;
- parent = parent.offsetParent;
- }
-
- parent = element;
- while ( parent &&
- parent != document.body &&
- parent != document.documentElement ) {
- if ( parent.scrollLeft )
- x -= parent.scrollLeft;
- if ( parent.scrollTop )
- y -= parent.scrollTop;
- parent = parent.parentNode;
- }
-
- if ( accountForDocScroll ) {
- x -= this.docScrollLeft();
- y -= this.docScrollTop();
- }
-
- return { x:x, y:y };
- },
-
- docScrollLeft: function() {
- if ( window.pageXOffset )
- return window.pageXOffset;
- else if ( document.documentElement && document.documentElement.scrollLeft )
- return document.documentElement.scrollLeft;
- else if ( document.body )
- return document.body.scrollLeft;
- else
- return 0;
- },
-
- docScrollTop: function() {
- if ( window.pageYOffset )
- return window.pageYOffset;
- else if ( document.documentElement && document.documentElement.scrollTop )
- return document.documentElement.scrollTop;
- else if ( document.body )
- return document.body.scrollTop;
- else
- return 0;
- }
-
-};
diff --git a/web/static/js/scriptaculous/builder.js b/web/static/js/scriptaculous/builder.js
deleted file mode 100644
index 5e00f45..0000000
--- a/web/static/js/scriptaculous/builder.js
+++ /dev/null
@@ -1,97 +0,0 @@
-// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
-//
-// See scriptaculous.js for full license.
-
-var Builder = {
- NODEMAP: {
- AREA: 'map',
- CAPTION: 'table',
- COL: 'table',
- COLGROUP: 'table',
- LEGEND: 'fieldset',
- OPTGROUP: 'select',
- OPTION: 'select',
- PARAM: 'object',
- TBODY: 'table',
- TD: 'table',
- TFOOT: 'table',
- TH: 'table',
- THEAD: 'table',
- TR: 'table'
- },
- // note: For Firefox < 1.5, OPTION and OPTGROUP tags are currently broken,
- // due to a Firefox bug
- node: function(elementName) {
- elementName = elementName.toUpperCase();
-
- // try innerHTML approach
- var parentTag = this.NODEMAP[elementName] || 'div';
- var parentElement = document.createElement(parentTag);
- parentElement.innerHTML = "<" + elementName + "></" + elementName + ">";
- var element = parentElement.firstChild || null;
-
- // see if browser added wrapping tags
- if(element && (element.tagName != elementName))
- element = element.getElementsByTagName(elementName)[0];
-
- // fallback to createElement approach
- if(!element) element = document.createElement(elementName);
-
- // abort if nothing could be created
- if(!element) return;
-
- // attributes (or text)
- if(arguments[1])
- if(this._isStringOrNumber(arguments[1]) ||
- (arguments[1] instanceof Array)) {
- this._children(element, arguments[1]);
- } else {
- var attrs = this._attributes(arguments[1]);
- if(attrs.length) {
- parentElement.innerHTML = "<" +elementName + " " +
- attrs + "></" + elementName + ">";
- element = parentElement.firstChild || null;
- // workaround firefox 1.0.X bug
- if(!element) {
- element = document.createElement(elementName);
- for(attr in arguments[1])
- element[attr == 'class' ? 'className' : attr] = arguments[1][attr];
- }
- if(element.tagName != elementName)
- element = parentElement.getElementsByTagName(elementName)[0];
- }
- }
-
- // text, or array of children
- if(arguments[2])
- this._children(element, arguments[2]);
-
- return element;
- },
- _text: function(text) {
- return document.createTextNode(text);
- },
- _attributes: function(attributes) {
- var attrs = [];
- for(attribute in attributes)
- attrs.push((attribute=='className' ? 'class' : attribute) +
- '="' + attributes[attribute].toString().escapeHTML() + '"');
- return attrs.join(" ");
- },
- _children: function(element, children) {
- if(typeof children=='object') { // array can hold nodes and text
- children.flatten().each( function(e) {
- if(typeof e=='object')
- element.appendChild(e)
- else
- if(Builder._isStringOrNumber(e))
- element.appendChild(Builder._text(e));
- });
- } else
- if(Builder._isStringOrNumber(children))
- element.appendChild(Builder._text(children));
- },
- _isStringOrNumber: function(param) {
- return(typeof param=='string' || typeof param=='number');
- }
-}
\ No newline at end of file
diff --git a/web/static/js/scriptaculous/controls.js b/web/static/js/scriptaculous/controls.js
deleted file mode 100644
index 18bdf20..0000000
--- a/web/static/js/scriptaculous/controls.js
+++ /dev/null
@@ -1,721 +0,0 @@
-// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
-// (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
-// (c) 2005 Jon Tirsen (http://www.tirsen.com)
-// Contributors:
-// Richard Livsey
-// Rahul Bhargava
-// Rob Wills
-//
-// See scriptaculous.js for full license.
-
-// Autocompleter.Base handles all the autocompletion functionality
-// that's independent of the data source for autocompletion. This
-// includes drawing the autocompletion menu, observing keyboard
-// and mouse events, and similar.
-//
-// Specific autocompleters need to provide, at the very least,
-// a getUpdatedChoices function that will be invoked every time
-// the text inside the monitored textbox changes. This method
-// should get the text for which to provide autocompletion by
-// invoking this.getToken(), NOT by directly accessing
-// this.element.value. This is to allow incremental tokenized
-// autocompletion. Specific auto-completion logic (AJAX, etc)
-// belongs in getUpdatedChoices.
-//
-// Tokenized incremental autocompletion is enabled automatically
-// when an autocompleter is instantiated with the 'tokens' option
-// in the options parameter, e.g.:
-// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
-// will incrementally autocomplete with a comma as the token.
-// Additionally, ',' in the above example can be replaced with
-// a token array, e.g. { tokens: [',', '\n'] } which
-// enables autocompletion on multiple tokens. This is most
-// useful when one of the tokens is \n (a newline), as it
-// allows smart autocompletion after linebreaks.
-
-var Autocompleter = {}
-Autocompleter.Base = function() {};
-Autocompleter.Base.prototype = {
- baseInitialize: function(element, update, options) {
- this.element = $(element);
- this.update = $(update);
- this.hasFocus = false;
- this.changed = false;
- this.active = false;
- this.index = 0;
- this.entryCount = 0;
-
- if (this.setOptions)
- this.setOptions(options);
- else
- this.options = options || {};
-
- this.options.paramName = this.options.paramName || this.element.name;
- this.options.tokens = this.options.tokens || [];
- this.options.frequency = this.options.frequency || 0.4;
- this.options.minChars = this.options.minChars || 1;
- this.options.onShow = this.options.onShow ||
- function(element, update){
- if(!update.style.position || update.style.position=='absolute') {
- update.style.position = 'absolute';
- Position.clone(element, update, {setHeight: false, offsetTop: element.offsetHeight});
- }
- Effect.Appear(update,{duration:0.15});
- };
- this.options.onHide = this.options.onHide ||
- function(element, update){ new Effect.Fade(update,{duration:0.15}) };
-
- if (typeof(this.options.tokens) == 'string')
- this.options.tokens = new Array(this.options.tokens);
-
- this.observer = null;
-
- this.element.setAttribute('autocomplete','off');
-
- Element.hide(this.update);
-
- Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this));
- Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this));
- },
-
- show: function() {
- if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
- if(!this.iefix && (navigator.appVersion.indexOf('MSIE')>0) && (Element.getStyle(this.update, 'position')=='absolute')) {
- new Insertion.After(this.update,
- '<iframe id="' + this.update.id + '_iefix" '+
- 'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
- 'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
- this.iefix = $(this.update.id+'_iefix');
- }
- if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
- },
-
- fixIEOverlapping: function() {
- Position.clone(this.update, this.iefix);
- this.iefix.style.zIndex = 1;
- this.update.style.zIndex = 2;
- Element.show(this.iefix);
- },
-
- hide: function() {
- this.stopIndicator();
- if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
- if(this.iefix) Element.hide(this.iefix);
- },
-
- startIndicator: function() {
- if(this.options.indicator) Element.show(this.options.indicator);
- },
-
- stopIndicator: function() {
- if(this.options.indicator) Element.hide(this.options.indicator);
- },
-
- onKeyPress: function(event) {
- if(this.active)
- switch(event.keyCode) {
- case Event.KEY_TAB:
- case Event.KEY_RETURN:
- this.selectEntry();
- Event.stop(event);
- case Event.KEY_ESC:
- this.hide();
- this.active = false;
- Event.stop(event);
- return;
- case Event.KEY_LEFT:
- case Event.KEY_RIGHT:
- return;
- case Event.KEY_UP:
- this.markPrevious();
- this.render();
- if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
- return;
- case Event.KEY_DOWN:
- this.markNext();
- this.render();
- if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
- return;
- }
- else
- if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN)
- return;
-
- this.changed = true;
- this.hasFocus = true;
-
- if(this.observer) clearTimeout(this.observer);
- this.observer =
- setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
- },
-
- onHover: function(event) {
- var element = Event.findElement(event, 'LI');
- if(this.index != element.autocompleteIndex)
- {
- this.index = element.autocompleteIndex;
- this.render();
- }
- Event.stop(event);
- },
-
- onClick: function(event) {
- var element = Event.findElement(event, 'LI');
- this.index = element.autocompleteIndex;
- this.selectEntry();
- this.hide();
- },
-
- onBlur: function(event) {
- // needed to make click events working
- setTimeout(this.hide.bind(this), 250);
- this.hasFocus = false;
- this.active = false;
- },
-
- render: function() {
- if(this.entryCount > 0) {
- for (var i = 0; i < this.entryCount; i++)
- this.index==i ?
- Element.addClassName(this.getEntry(i),"selected") :
- Element.removeClassName(this.getEntry(i),"selected");
-
- if(this.hasFocus) {
- this.show();
- this.active = true;
- }
- } else {
- this.active = false;
- this.hide();
- }
- },
-
- markPrevious: function() {
- if(this.index > 0) this.index--
- else this.index = this.entryCount-1;
- },
-
- markNext: function() {
- if(this.index < this.entryCount-1) this.index++
- else this.index = 0;
- },
-
- getEntry: function(index) {
- return this.update.firstChild.childNodes[index];
- },
-
- getCurrentEntry: function() {
- return this.getEntry(this.index);
- },
-
- selectEntry: function() {
- this.active = false;
- this.updateElement(this.getCurrentEntry());
- },
-
- updateElement: function(selectedElement) {
- if (this.options.updateElement) {
- this.options.updateElement(selectedElement);
- return;
- }
-
- var value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
- var lastTokenPos = this.findLastToken();
- if (lastTokenPos != -1) {
- var newValue = this.element.value.substr(0, lastTokenPos + 1);
- var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/);
- if (whitespace)
- newValue += whitespace[0];
- this.element.value = newValue + value;
- } else {
- this.element.value = value;
- }
- this.element.focus();
-
- if (this.options.afterUpdateElement)
- this.options.afterUpdateElement(this.element, selectedElement);
- },
-
- updateChoices: function(choices) {
- if(!this.changed && this.hasFocus) {
- this.update.innerHTML = choices;
- Element.cleanWhitespace(this.update);
- Element.cleanWhitespace(this.update.firstChild);
-
- if(this.update.firstChild && this.update.firstChild.childNodes) {
- this.entryCount =
- this.update.firstChild.childNodes.length;
- for (var i = 0; i < this.entryCount; i++) {
- var entry = this.getEntry(i);
- entry.autocompleteIndex = i;
- this.addObservers(entry);
- }
- } else {
- this.entryCount = 0;
- }
-
- this.stopIndicator();
-
- this.index = 0;
- this.render();
- }
- },
-
- addObservers: function(element) {
- Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
- Event.observe(element, "click", this.onClick.bindAsEventListener(this));
- },
-
- onObserverEvent: function() {
- this.changed = false;
- if(this.getToken().length>=this.options.minChars) {
- this.startIndicator();
- this.getUpdatedChoices();
- } else {
- this.active = false;
- this.hide();
- }
- },
-
- getToken: function() {
- var tokenPos = this.findLastToken();
- if (tokenPos != -1)
- var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,'');
- else
- var ret = this.element.value;
-
- return /\n/.test(ret) ? '' : ret;
- },
-
- findLastToken: function() {
- var lastTokenPos = -1;
-
- for (var i=0; i<this.options.tokens.length; i++) {
- var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]);
- if (thisTokenPos > lastTokenPos)
- lastTokenPos = thisTokenPos;
- }
- return lastTokenPos;
- }
-}
-
-Ajax.Autocompleter = Class.create();
-Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), {
- initialize: function(element, update, url, options) {
- this.baseInitialize(element, update, options);
- this.options.asynchronous = true;
- this.options.onComplete = this.onComplete.bind(this);
- this.options.defaultParams = this.options.parameters || null;
- this.url = url;
- },
-
- getUpdatedChoices: function() {
- entry = encodeURIComponent(this.options.paramName) + '=' +
- encodeURIComponent(this.getToken());
-
- this.options.parameters = this.options.callback ?
- this.options.callback(this.element, entry) : entry;
-
- if(this.options.defaultParams)
- this.options.parameters += '&' + this.options.defaultParams;
-
- new Ajax.Request(this.url, this.options);
- },
-
- onComplete: function(request) {
- this.updateChoices(request.responseText);
- }
-
-});
-
-// The local array autocompleter. Used when you'd prefer to
-// inject an array of autocompletion options into the page, rather
-// than sending out Ajax queries, which can be quite slow sometimes.
-//
-// The constructor takes four parameters. The first two are, as usual,
-// the id of the monitored textbox, and id of the autocompletion menu.
-// The third is the array you want to autocomplete from, and the fourth
-// is the options block.
-//
-// Extra local autocompletion options:
-// - choices - How many autocompletion choices to offer
-//
-// - partialSearch - If false, the autocompleter will match entered
-// text only at the beginning of strings in the
-// autocomplete array. Defaults to true, which will
-// match text at the beginning of any *word* in the
-// strings in the autocomplete array. If you want to
-// search anywhere in the string, additionally set
-// the option fullSearch to true (default: off).
-//
-// - fullSsearch - Search anywhere in autocomplete array strings.
-//
-// - partialChars - How many characters to enter before triggering
-// a partial match (unlike minChars, which defines
-// how many characters are required to do any match
-// at all). Defaults to 2.
-//
-// - ignoreCase - Whether to ignore case when autocompleting.
-// Defaults to true.
-//
-// It's possible to pass in a custom function as the 'selector'
-// option, if you prefer to write your own autocompletion logic.
-// In that case, the other options above will not apply unless
-// you support them.
-
-Autocompleter.Local = Class.create();
-Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
- initialize: function(element, update, array, options) {
- this.baseInitialize(element, update, options);
- this.options.array = array;
- },
-
- getUpdatedChoices: function() {
- this.updateChoices(this.options.selector(this));
- },
-
- setOptions: function(options) {
- this.options = Object.extend({
- choices: 10,
- partialSearch: true,
- partialChars: 2,
- ignoreCase: true,
- fullSearch: false,
- selector: function(instance) {
- var ret = []; // Beginning matches
- var partial = []; // Inside matches
- var entry = instance.getToken();
- var count = 0;
-
- for (var i = 0; i < instance.options.array.length &&
- ret.length < instance.options.choices ; i++) {
-
- var elem = instance.options.array[i];
- var foundPos = instance.options.ignoreCase ?
- elem.toLowerCase().indexOf(entry.toLowerCase()) :
- elem.indexOf(entry);
-
- while (foundPos != -1) {
- if (foundPos == 0 && elem.length != entry.length) {
- ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
- elem.substr(entry.length) + "</li>");
- break;
- } else if (entry.length >= instance.options.partialChars &&
- instance.options.partialSearch && foundPos != -1) {
- if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
- partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
- elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
- foundPos + entry.length) + "</li>");
- break;
- }
- }
-
- foundPos = instance.options.ignoreCase ?
- elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :
- elem.indexOf(entry, foundPos + 1);
-
- }
- }
- if (partial.length)
- ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
- return "<ul>" + ret.join('') + "</ul>";
- }
- }, options || {});
- }
-});
-
-// AJAX in-place editor
-//
-// see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor
-
-// Use this if you notice weird scrolling problems on some browsers,
-// the DOM might be a bit confused when this gets called so do this
-// waits 1 ms (with setTimeout) until it does the activation
-Field.scrollFreeActivate = function(field) {
- setTimeout(function() {
- Field.activate(field);
- }, 1);
-}
-
-Ajax.InPlaceEditor = Class.create();
-Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99";
-Ajax.InPlaceEditor.prototype = {
- initialize: function(element, url, options) {
- this.url = url;
- this.element = $(element);
-
- this.options = Object.extend({
- okText: "ok",
- cancelText: "cancel",
- savingText: "Saving...",
- clickToEditText: "Click to edit",
- okText: "ok",
- rows: 1,
- onComplete: function(transport, element) {
- new Effect.Highlight(element, {startcolor: this.options.highlightcolor});
- },
- onFailure: function(transport) {
- alert("Error communicating with the server: " + transport.responseText.stripTags());
- },
- callback: function(form) {
- return Form.serialize(form);
- },
- handleLineBreaks: true,
- loadingText: 'Loading...',
- savingClassName: 'inplaceeditor-saving',
- loadingClassName: 'inplaceeditor-loading',
- formClassName: 'inplaceeditor-form',
- highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor,
- highlightendcolor: "#FFFFFF",
- externalControl: null,
- ajaxOptions: {}
- }, options || {});
-
- if(!this.options.formId && this.element.id) {
- this.options.formId = this.element.id + "-inplaceeditor";
- if ($(this.options.formId)) {
- // there's already a form with that name, don't specify an id
- this.options.formId = null;
- }
- }
-
- if (this.options.externalControl) {
- this.options.externalControl = $(this.options.externalControl);
- }
-
- this.originalBackground = Element.getStyle(this.element, 'background-color');
- if (!this.originalBackground) {
- this.originalBackground = "transparent";
- }
-
- this.element.title = this.options.clickToEditText;
-
- this.onclickListener = this.enterEditMode.bindAsEventListener(this);
- this.mouseoverListener = this.enterHover.bindAsEventListener(this);
- this.mouseoutListener = this.leaveHover.bindAsEventListener(this);
- Event.observe(this.element, 'click', this.onclickListener);
- Event.observe(this.element, 'mouseover', this.mouseoverListener);
- Event.observe(this.element, 'mouseout', this.mouseoutListener);
- if (this.options.externalControl) {
- Event.observe(this.options.externalControl, 'click', this.onclickListener);
- Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener);
- Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener);
- }
- },
- enterEditMode: function(evt) {
- if (this.saving) return;
- if (this.editing) return;
- this.editing = true;
- this.onEnterEditMode();
- if (this.options.externalControl) {
- Element.hide(this.options.externalControl);
- }
- Element.hide(this.element);
- this.createForm();
- this.element.parentNode.insertBefore(this.form, this.element);
- Field.scrollFreeActivate(this.editField);
- // stop the event to avoid a page refresh in Safari
- if (evt) {
- Event.stop(evt);
- }
- return false;
- },
- createForm: function() {
- this.form = document.createElement("form");
- this.form.id = this.options.formId;
- Element.addClassName(this.form, this.options.formClassName)
- this.form.onsubmit = this.onSubmit.bind(this);
-
- this.createEditField();
-
- if (this.options.textarea) {
- var br = document.createElement("br");
- this.form.appendChild(br);
- }
-
- okButton = document.createElement("input");
- okButton.type = "submit";
- okButton.value = this.options.okText;
- this.form.appendChild(okButton);
-
- cancelLink = document.createElement("a");
- cancelLink.href = "#";
- cancelLink.appendChild(document.createTextNode(this.options.cancelText));
- cancelLink.onclick = this.onclickCancel.bind(this);
- this.form.appendChild(cancelLink);
- },
- hasHTMLLineBreaks: function(string) {
- if (!this.options.handleLineBreaks) return false;
- return string.match(/<br/i) || string.match(/<p>/i);
- },
- convertHTMLLineBreaks: function(string) {
- return string.replace(/<br>/gi, "\n").replace(/<br\/>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<p>/gi, "");
- },
- createEditField: function() {
- var text;
- if(this.options.loadTextURL) {
- text = this.options.loadingText;
- } else {
- text = this.getText();
- }
-
- if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) {
- this.options.textarea = false;
- var textField = document.createElement("input");
- textField.type = "text";
- textField.name = "value";
- textField.value = text;
- textField.style.backgroundColor = this.options.highlightcolor;
- var size = this.options.size || this.options.cols || 0;
- if (size != 0) textField.size = size;
- this.editField = textField;
- } else {
- this.options.textarea = true;
- var textArea = document.createElement("textarea");
- textArea.name = "value";
- textArea.value = this.convertHTMLLineBreaks(text);
- textArea.rows = this.options.rows;
- textArea.cols = this.options.cols || 40;
- this.editField = textArea;
- }
-
- if(this.options.loadTextURL) {
- this.loadExternalText();
- }
- this.form.appendChild(this.editField);
- },
- getText: function() {
- return this.element.innerHTML;
- },
- loadExternalText: function() {
- Element.addClassName(this.form, this.options.loadingClassName);
- this.editField.disabled = true;
- new Ajax.Request(
- this.options.loadTextURL,
- Object.extend({
- asynchronous: true,
- onComplete: this.onLoadedExternalText.bind(this)
- }, this.options.ajaxOptions)
- );
- },
- onLoadedExternalText: function(transport) {
- Element.removeClassName(this.form, this.options.loadingClassName);
- this.editField.disabled = false;
- this.editField.value = transport.responseText.stripTags();
- },
- onclickCancel: function() {
- this.onComplete();
- this.leaveEditMode();
- return false;
- },
- onFailure: function(transport) {
- this.options.onFailure(transport);
- if (this.oldInnerHTML) {
- this.element.innerHTML = this.oldInnerHTML;
- this.oldInnerHTML = null;
- }
- return false;
- },
- onSubmit: function() {
- // onLoading resets these so we need to save them away for the Ajax call
- var form = this.form;
- var value = this.editField.value;
-
- // do this first, sometimes the ajax call returns before we get a chance to switch on Saving...
- // which means this will actually switch on Saving... *after* we've left edit mode causing Saving...
- // to be displayed indefinitely
- this.onLoading();
-
- new Ajax.Updater(
- {
- success: this.element,
- // don't update on failure (this could be an option)
- failure: null
- },
- this.url,
- Object.extend({
- parameters: this.options.callback(form, value),
- onComplete: this.onComplete.bind(this),
- onFailure: this.onFailure.bind(this)
- }, this.options.ajaxOptions)
- );
- // stop the event to avoid a page refresh in Safari
- if (arguments.length > 1) {
- Event.stop(arguments[0]);
- }
- return false;
- },
- onLoading: function() {
- this.saving = true;
- this.removeForm();
- this.leaveHover();
- this.showSaving();
- },
- showSaving: function() {
- this.oldInnerHTML = this.element.innerHTML;
- this.element.innerHTML = this.options.savingText;
- Element.addClassName(this.element, this.options.savingClassName);
- this.element.style.backgroundColor = this.originalBackground;
- Element.show(this.element);
- },
- removeForm: function() {
- if(this.form) {
- if (this.form.parentNode) Element.remove(this.form);
- this.form = null;
- }
- },
- enterHover: function() {
- if (this.saving) return;
- this.element.style.backgroundColor = this.options.highlightcolor;
- if (this.effect) {
- this.effect.cancel();
- }
- Element.addClassName(this.element, this.options.hoverClassName)
- },
- leaveHover: function() {
- if (this.options.backgroundColor) {
- this.element.style.backgroundColor = this.oldBackground;
- }
- Element.removeClassName(this.element, this.options.hoverClassName)
- if (this.saving) return;
- this.effect = new Effect.Highlight(this.element, {
- startcolor: this.options.highlightcolor,
- endcolor: this.options.highlightendcolor,
- restorecolor: this.originalBackground
- });
- },
- leaveEditMode: function() {
- Element.removeClassName(this.element, this.options.savingClassName);
- this.removeForm();
- this.leaveHover();
- this.element.style.backgroundColor = this.originalBackground;
- Element.show(this.element);
- if (this.options.externalControl) {
- Element.show(this.options.externalControl);
- }
- this.editing = false;
- this.saving = false;
- this.oldInnerHTML = null;
- this.onLeaveEditMode();
- },
- onComplete: function(transport) {
- this.leaveEditMode();
- this.options.onComplete.bind(this)(transport, this.element);
- },
- onEnterEditMode: function() {},
- onLeaveEditMode: function() {},
- dispose: function() {
- if (this.oldInnerHTML) {
- this.element.innerHTML = this.oldInnerHTML;
- }
- this.leaveEditMode();
- Event.stopObserving(this.element, 'click', this.onclickListener);
- Event.stopObserving(this.element, 'mouseover', this.mouseoverListener);
- Event.stopObserving(this.element, 'mouseout', this.mouseoutListener);
- if (this.options.externalControl) {
- Event.stopObserving(this.options.externalControl, 'click', this.onclickListener);
- Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener);
- Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener);
- }
- }
-};
diff --git a/web/static/js/scriptaculous/dragdrop.js b/web/static/js/scriptaculous/dragdrop.js
deleted file mode 100644
index 7ca95f6..0000000
--- a/web/static/js/scriptaculous/dragdrop.js
+++ /dev/null
@@ -1,519 +0,0 @@
-// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
-//
-// Element.Class part Copyright (c) 2005 by Rick Olson
-//
-// See scriptaculous.js for full license.
-
-/*--------------------------------------------------------------------------*/
-
-var Droppables = {
- drops: [],
-
- remove: function(element) {
- this.drops = this.drops.reject(function(d) { return d.element==element });
- },
-
- add: function(element) {
- element = $(element);
- var options = Object.extend({
- greedy: true,
- hoverclass: null
- }, arguments[1] || {});
-
- // cache containers
- if(options.containment) {
- options._containers = [];
- var containment = options.containment;
- if((typeof containment == 'object') &&
- (containment.constructor == Array)) {
- containment.each( function(c) { options._containers.push($(c)) });
- } else {
- options._containers.push($(containment));
- }
- }
-
- Element.makePositioned(element); // fix IE
- options.element = element;
-
- this.drops.push(options);
- },
-
- isContained: function(element, drop) {
- var parentNode = element.parentNode;
- return drop._containers.detect(function(c) { return parentNode == c });
- },
-
- isAffected: function(pX, pY, element, drop) {
- return (
- (drop.element!=element) &&
- ((!drop._containers) ||
- this.isContained(element, drop)) &&
- ((!drop.accept) ||
- (Element.Class.has_any(element, drop.accept))) &&
- Position.within(drop.element, pX, pY) );
- },
-
- deactivate: function(drop) {
- if(drop.hoverclass)
- Element.Class.remove(drop.element, drop.hoverclass);
- this.last_active = null;
- },
-
- activate: function(drop) {
- if(this.last_active) this.deactivate(this.last_active);
- if(drop.hoverclass)
- Element.Class.add(drop.element, drop.hoverclass);
- this.last_active = drop;
- },
-
- show: function(event, element) {
- if(!this.drops.length) return;
- var pX = Event.pointerX(event);
- var pY = Event.pointerY(event);
- Position.prepare();
-
- var i = this.drops.length-1; do {
- var drop = this.drops[i];
- if(this.isAffected(pX, pY, element, drop)) {
- if(drop.onHover)
- drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
- if(drop.greedy) {
- this.activate(drop);
- return;
- }
- }
- } while (i--);
-
- if(this.last_active) this.deactivate(this.last_active);
- },
-
- fire: function(event, element) {
- if(!this.last_active) return;
- Position.prepare();
-
- if (this.isAffected(Event.pointerX(event), Event.pointerY(event), element, this.last_active))
- if (this.last_active.onDrop)
- this.last_active.onDrop(element, this.last_active.element, event);
- },
-
- reset: function() {
- if(this.last_active)
- this.deactivate(this.last_active);
- }
-}
-
-var Draggables = {
- observers: [],
- addObserver: function(observer) {
- this.observers.push(observer);
- },
- removeObserver: function(element) { // element instead of obsever fixes mem leaks
- this.observers = this.observers.reject( function(o) { return o.element==element });
- },
- notify: function(eventName, draggable) { // 'onStart', 'onEnd'
- this.observers.invoke(eventName, draggable);
- }
-}
-
-/*--------------------------------------------------------------------------*/
-
-var Draggable = Class.create();
-Draggable.prototype = {
- initialize: function(element) {
- var options = Object.extend({
- handle: false,
- starteffect: function(element) {
- new Effect.Opacity(element, {duration:0.2, from:1.0, to:0.7});
- },
- reverteffect: function(element, top_offset, left_offset) {
- var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
- new Effect.MoveBy(element, -top_offset, -left_offset, {duration:dur});
- },
- endeffect: function(element) {
- new Effect.Opacity(element, {duration:0.2, from:0.7, to:1.0});
- },
- zindex: 1000,
- revert: false
- }, arguments[1] || {});
-
- this.element = $(element);
- if(options.handle && (typeof options.handle == 'string'))
- this.handle = Element.Class.childrenWith(this.element, options.handle)[0];
-
- if(!this.handle) this.handle = $(options.handle);
- if(!this.handle) this.handle = this.element;
-
- Element.makePositioned(this.element); // fix IE
-
- this.offsetX = 0;
- this.offsetY = 0;
- this.originalLeft = this.currentLeft();
- this.originalTop = this.currentTop();
- this.originalX = this.element.offsetLeft;
- this.originalY = this.element.offsetTop;
-
- this.options = options;
-
- this.active = false;
- this.dragging = false;
-
- this.eventMouseDown = this.startDrag.bindAsEventListener(this);
- this.eventMouseUp = this.endDrag.bindAsEventListener(this);
- this.eventMouseMove = this.update.bindAsEventListener(this);
- this.eventKeypress = this.keyPress.bindAsEventListener(this);
-
- this.registerEvents();
- },
- destroy: function() {
- Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
- this.unregisterEvents();
- },
- registerEvents: function() {
- Event.observe(document, "mouseup", this.eventMouseUp);
- Event.observe(document, "mousemove", this.eventMouseMove);
- Event.observe(document, "keypress", this.eventKeypress);
- Event.observe(this.handle, "mousedown", this.eventMouseDown);
- },
- unregisterEvents: function() {
- //if(!this.active) return;
- //Event.stopObserving(document, "mouseup", this.eventMouseUp);
- //Event.stopObserving(document, "mousemove", this.eventMouseMove);
- //Event.stopObserving(document, "keypress", this.eventKeypress);
- },
- currentLeft: function() {
- return parseInt(this.element.style.left || '0');
- },
- currentTop: function() {
- return parseInt(this.element.style.top || '0')
- },
- startDrag: function(event) {
- if(Event.isLeftClick(event)) {
-
- // abort on form elements, fixes a Firefox issue
- var src = Event.element(event);
- if(src.tagName && (
- src.tagName=='INPUT' ||
- src.tagName=='SELECT' ||
- src.tagName=='BUTTON' ||
- src.tagName=='TEXTAREA')) return;
-
- // this.registerEvents();
- this.active = true;
- var pointer = [Event.pointerX(event), Event.pointerY(event)];
- var offsets = Position.cumulativeOffset(this.element);
- this.offsetX = (pointer[0] - offsets[0]);
- this.offsetY = (pointer[1] - offsets[1]);
- Event.stop(event);
- }
- },
- finishDrag: function(event, success) {
- // this.unregisterEvents();
-
- this.active = false;
- this.dragging = false;
-
- if(this.options.ghosting) {
- Position.relativize(this.element);
- Element.remove(this._clone);
- this._clone = null;
- }
-
- if(success) Droppables.fire(event, this.element);
- Draggables.notify('onEnd', this);
-
- var revert = this.options.revert;
- if(revert && typeof revert == 'function') revert = revert(this.element);
-
- if(revert && this.options.reverteffect) {
- this.options.reverteffect(this.element,
- this.currentTop()-this.originalTop,
- this.currentLeft()-this.originalLeft);
- } else {
- this.originalLeft = this.currentLeft();
- this.originalTop = this.currentTop();
- }
-
- if(this.options.zindex)
- this.element.style.zIndex = this.originalZ;
-
- if(this.options.endeffect)
- this.options.endeffect(this.element);
-
-
- Droppables.reset();
- },
- keyPress: function(event) {
- if(this.active) {
- if(event.keyCode==Event.KEY_ESC) {
- this.finishDrag(event, false);
- Event.stop(event);
- }
- }
- },
- endDrag: function(event) {
- if(this.active && this.dragging) {
- this.finishDrag(event, true);
- Event.stop(event);
- }
- this.active = false;
- this.dragging = false;
- },
- draw: function(event) {
- var pointer = [Event.pointerX(event), Event.pointerY(event)];
- var offsets = Position.cumulativeOffset(this.element);
- offsets[0] -= this.currentLeft();
- offsets[1] -= this.currentTop();
- var style = this.element.style;
- if((!this.options.constraint) || (this.options.constraint=='horizontal'))
- style.left = (pointer[0] - offsets[0] - this.offsetX) + "px";
- if((!this.options.constraint) || (this.options.constraint=='vertical'))
- style.top = (pointer[1] - offsets[1] - this.offsetY) + "px";
- if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
- },
- update: function(event) {
- if(this.active) {
- if(!this.dragging) {
- var style = this.element.style;
- this.dragging = true;
-
- if(Element.getStyle(this.element,'position')=='')
- style.position = "relative";
-
- if(this.options.zindex) {
- this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
- style.zIndex = this.options.zindex;
- }
-
- if(this.options.ghosting) {
- this._clone = this.element.cloneNode(true);
- Position.absolutize(this.element);
- this.element.parentNode.insertBefore(this._clone, this.element);
- }
-
- Draggables.notify('onStart', this);
- if(this.options.starteffect) this.options.starteffect(this.element);
- }
-
- Droppables.show(event, this.element);
- this.draw(event);
- if(this.options.change) this.options.change(this);
-
- // fix AppleWebKit rendering
- if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
-
- Event.stop(event);
- }
- }
-}
-
-/*--------------------------------------------------------------------------*/
-
-var SortableObserver = Class.create();
-SortableObserver.prototype = {
- initialize: function(element, observer) {
- this.element = $(element);
- this.observer = observer;
- this.lastValue = Sortable.serialize(this.element);
- },
- onStart: function() {
- this.lastValue = Sortable.serialize(this.element);
- },
- onEnd: function() {
- Sortable.unmark();
- if(this.lastValue != Sortable.serialize(this.element))
- this.observer(this.element)
- }
-}
-
-var Sortable = {
- sortables: new Array(),
- options: function(element){
- element = $(element);
- return this.sortables.detect(function(s) { return s.element == element });
- },
- destroy: function(element){
- element = $(element);
- this.sortables.findAll(function(s) { return s.element == element }).each(function(s){
- Draggables.removeObserver(s.element);
- s.droppables.each(function(d){ Droppables.remove(d) });
- s.draggables.invoke('destroy');
- });
- this.sortables = this.sortables.reject(function(s) { return s.element == element });
- },
- create: function(element) {
- element = $(element);
- var options = Object.extend({
- element: element,
- tag: 'li', // assumes li children, override with tag: 'tagname'
- dropOnEmpty: false,
- tree: false, // fixme: unimplemented
- overlap: 'vertical', // one of 'vertical', 'horizontal'
- constraint: 'vertical', // one of 'vertical', 'horizontal', false
- containment: element, // also takes array of elements (or id's); or false
- handle: false, // or a CSS class
- only: false,
- hoverclass: null,
- ghosting: false,
- format: null,
- onChange: Prototype.emptyFunction,
- onUpdate: Prototype.emptyFunction
- }, arguments[1] || {});
-
- // clear any old sortable with same element
- this.destroy(element);
-
- // build options for the draggables
- var options_for_draggable = {
- revert: true,
- ghosting: options.ghosting,
- constraint: options.constraint,
- handle: options.handle };
-
- if(options.starteffect)
- options_for_draggable.starteffect = options.starteffect;
-
- if(options.reverteffect)
- options_for_draggable.reverteffect = options.reverteffect;
- else
- if(options.ghosting) options_for_draggable.reverteffect = function(element) {
- element.style.top = 0;
- element.style.left = 0;
- };
-
- if(options.endeffect)
- options_for_draggable.endeffect = options.endeffect;
-
- if(options.zindex)
- options_for_draggable.zindex = options.zindex;
-
- // build options for the droppables
- var options_for_droppable = {
- overlap: options.overlap,
- containment: options.containment,
- hoverclass: options.hoverclass,
- onHover: Sortable.onHover,
- greedy: !options.dropOnEmpty
- }
-
- // fix for gecko engine
- Element.cleanWhitespace(element);
-
- options.draggables = [];
- options.droppables = [];
-
- // make it so
-
- // drop on empty handling
- if(options.dropOnEmpty) {
- Droppables.add(element,
- {containment: options.containment, onHover: Sortable.onEmptyHover, greedy: false});
- options.droppables.push(element);
- }
-
- (this.findElements(element, options) || []).each( function(e) {
- // handles are per-draggable
- var handle = options.handle ?
- Element.Class.childrenWith(e, options.handle)[0] : e;
- options.draggables.push(
- new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
- Droppables.add(e, options_for_droppable);
- options.droppables.push(e);
- });
-
- // keep reference
- this.sortables.push(options);
-
- // for onupdate
- Draggables.addObserver(new SortableObserver(element, options.onUpdate));
-
- },
-
- // return all suitable-for-sortable elements in a guaranteed order
- findElements: function(element, options) {
- if(!element.hasChildNodes()) return null;
- var elements = [];
- $A(element.childNodes).each( function(e) {
- if(e.tagName && e.tagName==options.tag.toUpperCase() &&
- (!options.only || (Element.Class.has(e, options.only))))
- elements.push(e);
- if(options.tree) {
- var grandchildren = this.findElements(e, options);
- if(grandchildren) elements.push(grandchildren);
- }
- });
-
- return (elements.length>0 ? elements.flatten() : null);
- },
-
- onHover: function(element, dropon, overlap) {
- if(overlap>0.5) {
- Sortable.mark(dropon, 'before');
- if(dropon.previousSibling != element) {
- var oldParentNode = element.parentNode;
- element.style.visibility = "hidden"; // fix gecko rendering
- dropon.parentNode.insertBefore(element, dropon);
- if(dropon.parentNode!=oldParentNode)
- Sortable.options(oldParentNode).onChange(element);
- Sortable.options(dropon.parentNode).onChange(element);
- }
- } else {
- Sortable.mark(dropon, 'after');
- var nextElement = dropon.nextSibling || null;
- if(nextElement != element) {
- var oldParentNode = element.parentNode;
- element.style.visibility = "hidden"; // fix gecko rendering
- dropon.parentNode.insertBefore(element, nextElement);
- if(dropon.parentNode!=oldParentNode)
- Sortable.options(oldParentNode).onChange(element);
- Sortable.options(dropon.parentNode).onChange(element);
- }
- }
- },
-
- onEmptyHover: function(element, dropon) {
- if(element.parentNode!=dropon) {
- var oldParentNode = element.parentNode;
- dropon.appendChild(element);
- Sortable.options(oldParentNode).onChange(element);
- Sortable.options(dropon).onChange(element);
- }
- },
-
- unmark: function() {
- if(Sortable._marker) Element.hide(Sortable._marker);
- },
-
- mark: function(dropon, position) {
- // mark on ghosting only
- var sortable = Sortable.options(dropon.parentNode);
- if(sortable && !sortable.ghosting) return;
-
- if(!Sortable._marker) {
- Sortable._marker = $('dropmarker') || document.createElement('DIV');
- Element.hide(Sortable._marker);
- Element.Class.add(Sortable._marker, 'dropmarker');
- Sortable._marker.style.position = 'absolute';
- document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
- }
- var offsets = Position.cumulativeOffset(dropon);
- Sortable._marker.style.top = offsets[1] + 'px';
- if(position=='after') Sortable._marker.style.top = (offsets[1]+dropon.clientHeight) + 'px';
- Sortable._marker.style.left = offsets[0] + 'px';
- Element.show(Sortable._marker);
- },
-
- serialize: function(element) {
- element = $(element);
- var sortableOptions = this.options(element);
- var options = Object.extend({
- tag: sortableOptions.tag,
- only: sortableOptions.only,
- name: element.id,
- format: sortableOptions.format || /^[^_]*_(.*)$/
- }, arguments[1] || {});
- return $(this.findElements(element, options) || []).collect( function(item) {
- return (encodeURIComponent(options.name) + "[]=" +
- encodeURIComponent(item.id.match(options.format) ? item.id.match(options.format)[1] : ''));
- }).join("&");
- }
-}
\ No newline at end of file
diff --git a/web/static/js/scriptaculous/effects.js b/web/static/js/scriptaculous/effects.js
deleted file mode 100644
index 3f92992..0000000
--- a/web/static/js/scriptaculous/effects.js
+++ /dev/null
@@ -1,992 +0,0 @@
-// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
-// Contributors:
-// Justin Palmer (http://encytemedia.com/)
-// Mark Pilgrim (http://diveintomark.org/)
-// Martin Bialasinki
-//
-// See scriptaculous.js for full license.
-
-/* ------------- element ext -------------- */
-
-// converts rgb() and #xxx to #xxxxxx format,
-// returns self (or first argument) if not convertable
-String.prototype.parseColor = function() {
- color = "#";
- if(this.slice(0,4) == "rgb(") {
- var cols = this.slice(4,this.length-1).split(',');
- var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);
- } else {
- if(this.slice(0,1) == '#') {
- if(this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();
- if(this.length==7) color = this.toLowerCase();
- }
- }
- return(color.length==7 ? color : (arguments[0] || this));
-}
-
-Element.collectTextNodesIgnoreClass = function(element, ignoreclass) {
- var children = $(element).childNodes;
- var text = "";
- var classtest = new RegExp("^([^ ]+ )*" + ignoreclass+ "( [^ ]+)*$","i");
-
- for (var i = 0; i < children.length; i++) {
- if(children[i].nodeType==3) {
- text+=children[i].nodeValue;
- } else {
- if((!children[i].className.match(classtest)) && children[i].hasChildNodes())
- text += Element.collectTextNodesIgnoreClass(children[i], ignoreclass);
- }
- }
-
- return text;
-}
-
-Element.setContentZoom = function(element, percent) {
- element = $(element);
- element.style.fontSize = (percent/100) + "em";
- if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
-}
-
-Element.getOpacity = function(element){
- var opacity;
- if (opacity = Element.getStyle(element, "opacity"))
- return parseFloat(opacity);
- if (opacity = (Element.getStyle(element, "filter") || '').match(/alpha\(opacity=(.*)\)/))
- if(opacity[1]) return parseFloat(opacity[1]) / 100;
- return 1.0;
-}
-
-Element.setOpacity = function(element, value){
- element= $(element);
- var els = element.style;
- if (value == 1){
- els.opacity = '0.999999';
- if(/MSIE/.test(navigator.userAgent))
- els.filter = Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'');
- } else {
- if(value < 0.00001) value = 0;
- els.opacity = value;
- if(/MSIE/.test(navigator.userAgent))
- els.filter = Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'') +
- "alpha(opacity="+value*100+")";
- }
-}
-
-Element.getInlineOpacity = function(element){
- element= $(element);
- var op;
- op = element.style.opacity;
- if (typeof op != "undefined" && op != "") return op;
- return "";
-}
-
-Element.setInlineOpacity = function(element, value){
- element= $(element);
- var els = element.style;
- els.opacity = value;
-}
-
-/*--------------------------------------------------------------------------*/
-
-Element.Class = {
- // Element.toggleClass(element, className) toggles the class being on/off
- // Element.toggleClass(element, className1, className2) toggles between both classes,
- // defaulting to className1 if neither exist
- toggle: function(element, className) {
- if(Element.Class.has(element, className)) {
- Element.Class.remove(element, className);
- if(arguments.length == 3) Element.Class.add(element, arguments[2]);
- } else {
- Element.Class.add(element, className);
- if(arguments.length == 3) Element.Class.remove(element, arguments[2]);
- }
- },
-
- // gets space-delimited classnames of an element as an array
- get: function(element) {
- return $(element).className.split(' ');
- },
-
- // functions adapted from original functions by Gavin Kistner
- remove: function(element) {
- element = $(element);
- var removeClasses = arguments;
- $R(1,arguments.length-1).each( function(index) {
- element.className =
- element.className.split(' ').reject(
- function(klass) { return (klass == removeClasses[index]) } ).join(' ');
- });
- },
-
- add: function(element) {
- element = $(element);
- for(var i = 1; i < arguments.length; i++) {
- Element.Class.remove(element, arguments[i]);
- element.className += (element.className.length > 0 ? ' ' : '') + arguments[i];
- }
- },
-
- // returns true if all given classes exist in said element
- has: function(element) {
- element = $(element);
- if(!element || !element.className) return false;
- var regEx;
- for(var i = 1; i < arguments.length; i++) {
- if((typeof arguments[i] == 'object') &&
- (arguments[i].constructor == Array)) {
- for(var j = 0; j < arguments[i].length; j++) {
- regEx = new RegExp("(^|\\s)" + arguments[i][j] + "(\\s|$)");
- if(!regEx.test(element.className)) return false;
- }
- } else {
- regEx = new RegExp("(^|\\s)" + arguments[i] + "(\\s|$)");
- if(!regEx.test(element.className)) return false;
- }
- }
- return true;
- },
-
- // expects arrays of strings and/or strings as optional paramters
- // Element.Class.has_any(element, ['classA','classB','classC'], 'classD')
- has_any: function(element) {
- element = $(element);
- if(!element || !element.className) return false;
- var regEx;
- for(var i = 1; i < arguments.length; i++) {
- if((typeof arguments[i] == 'object') &&
- (arguments[i].constructor == Array)) {
- for(var j = 0; j < arguments[i].length; j++) {
- regEx = new RegExp("(^|\\s)" + arguments[i][j] + "(\\s|$)");
- if(regEx.test(element.className)) return true;
- }
- } else {
- regEx = new RegExp("(^|\\s)" + arguments[i] + "(\\s|$)");
- if(regEx.test(element.className)) return true;
- }
- }
- return false;
- },
-
- childrenWith: function(element, className) {
- var children = $(element).getElementsByTagName('*');
- var elements = new Array();
-
- for (var i = 0; i < children.length; i++)
- if (Element.Class.has(children[i], className))
- elements.push(children[i]);
-
- return elements;
- }
-}
-
-/*--------------------------------------------------------------------------*/
-
-var Effect = {
- tagifyText: function(element) {
- var tagifyStyle = "position:relative";
- if(/MSIE/.test(navigator.userAgent)) tagifyStyle += ";zoom:1";
- element = $(element);
- $A(element.childNodes).each( function(child) {
- if(child.nodeType==3) {
- child.nodeValue.toArray().each( function(character) {
- element.insertBefore(
- Builder.node('span',{style: tagifyStyle},
- character == " " ? String.fromCharCode(160) : character),
- child);
- });
- Element.remove(child);
- }
- });
- },
- multiple: function(element, effect) {
- var elements;
- if(((typeof element == 'object') ||
- (typeof element == 'function')) &&
- (element.length))
- elements = element;
- else
- elements = $(element).childNodes;
-
- var options = Object.extend({
- speed: 0.1,
- delay: 0.0
- }, arguments[2] || {});
- var speed = options.speed;
- var delay = options.delay;
-
- $A(elements).each( function(element, index) {
- new effect(element, Object.extend(options, { delay: delay + index * speed }));
- });
- }
-};
-
-var Effect2 = Effect; // deprecated
-
-/* ------------- transitions ------------- */
-
-Effect.Transitions = {}
-
-Effect.Transitions.linear = function(pos) {
- return pos;
-}
-Effect.Transitions.sinoidal = function(pos) {
- return (-Math.cos(pos*Math.PI)/2) + 0.5;
-}
-Effect.Transitions.reverse = function(pos) {
- return 1-pos;
-}
-Effect.Transitions.flicker = function(pos) {
- return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
-}
-Effect.Transitions.wobble = function(pos) {
- return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
-}
-Effect.Transitions.pulse = function(pos) {
- return (Math.floor(pos*10) % 2 == 0 ?
- (pos*10-Math.floor(pos*10)) : 1-(pos*10-Math.floor(pos*10)));
-}
-Effect.Transitions.none = function(pos) {
- return 0;
-}
-Effect.Transitions.full = function(pos) {
- return 1;
-}
-
-/* ------------- core effects ------------- */
-
-Effect.Queue = {
- effects: [],
- _each: function(iterator) {
- this.effects._each(iterator);
- },
- interval: null,
- add: function(effect) {
- var timestamp = new Date().getTime();
-
- switch(effect.options.queue) {
- case 'front':
- // move unstarted effects after this effect
- this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
- e.startOn += effect.finishOn;
- e.finishOn += effect.finishOn;
- });
- break;
- case 'end':
- // start effect after last queued effect has finished
- timestamp = this.effects.pluck('finishOn').max() || timestamp;
- break;
- }
-
- effect.startOn += timestamp;
- effect.finishOn += timestamp;
- this.effects.push(effect);
- if(!this.interval)
- this.interval = setInterval(this.loop.bind(this), 40);
- },
- remove: function(effect) {
- this.effects = this.effects.reject(function(e) { return e==effect });
- if(this.effects.length == 0) {
- clearInterval(this.interval);
- this.interval = null;
- }
- },
- loop: function() {
- var timePos = new Date().getTime();
- this.effects.invoke('loop', timePos);
- }
-}
-Object.extend(Effect.Queue, Enumerable);
-
-Effect.Base = function() {};
-Effect.Base.prototype = {
- position: null,
- setOptions: function(options) {
- this.options = Object.extend({
- transition: Effect.Transitions.sinoidal,
- duration: 1.0, // seconds
- fps: 25.0, // max. 25fps due to Effect.Queue implementation
- sync: false, // true for combining
- from: 0.0,
- to: 1.0,
- delay: 0.0,
- queue: 'parallel'
- }, options || {});
- },
- start: function(options) {
- this.setOptions(options || {});
- this.currentFrame = 0;
- this.state = 'idle';
- this.startOn = this.options.delay*1000;
- this.finishOn = this.startOn + (this.options.duration*1000);
- this.event('beforeStart');
- if(!this.options.sync) Effect.Queue.add(this);
- },
- loop: function(timePos) {
- if(timePos >= this.startOn) {
- if(timePos >= this.finishOn) {
- this.render(1.0);
- this.cancel();
- this.event('beforeFinish');
- if(this.finish) this.finish();
- this.event('afterFinish');
- return;
- }
- var pos = (timePos - this.startOn) / (this.finishOn - this.startOn);
- var frame = Math.round(pos * this.options.fps * this.options.duration);
- if(frame > this.currentFrame) {
- this.render(pos);
- this.currentFrame = frame;
- }
- }
- },
- render: function(pos) {
- if(this.state == 'idle') {
- this.state = 'running';
- this.event('beforeSetup');
- if(this.setup) this.setup();
- this.event('afterSetup');
- }
- if(this.options.transition) pos = this.options.transition(pos);
- pos *= (this.options.to-this.options.from);
- pos += this.options.from;
- this.position = pos;
- this.event('beforeUpdate');
- if(this.update) this.update(pos);
- this.event('afterUpdate');
- },
- cancel: function() {
- if(!this.options.sync) Effect.Queue.remove(this);
- this.state = 'finished';
- },
- event: function(eventName) {
- if(this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
- if(this.options[eventName]) this.options[eventName](this);
- }
-}
-
-Effect.Parallel = Class.create();
-Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), {
- initialize: function(effects) {
- this.effects = effects || [];
- this.start(arguments[1]);
- },
- update: function(position) {
- this.effects.invoke('render', position);
- },
- finish: function(position) {
- this.effects.each( function(effect) {
- effect.render(1.0);
- effect.cancel();
- effect.event('beforeFinish');
- if(effect.finish) effect.finish(position);
- effect.event('afterFinish');
- });
- }
-});
-
-Effect.Opacity = Class.create();
-Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), {
- initialize: function(element) {
- this.element = $(element);
- // make this work on IE on elements without 'layout'
- if(/MSIE/.test(navigator.userAgent) && (!this.element.hasLayout))
- this.element.style.zoom = 1;
- var options = Object.extend({
- from: Element.getOpacity(this.element) || 0.0,
- to: 1.0
- }, arguments[1] || {});
- this.start(options);
- },
- update: function(position) {
- Element.setOpacity(this.element, position);
- }
-});
-
-Effect.MoveBy = Class.create();
-Object.extend(Object.extend(Effect.MoveBy.prototype, Effect.Base.prototype), {
- initialize: function(element, toTop, toLeft) {
- this.element = $(element);
- this.toTop = toTop;
- this.toLeft = toLeft;
- this.start(arguments[3]);
- },
- setup: function() {
- // Bug in Opera: Opera returns the "real" position of a static element or
- // relative element that does not have top/left explicitly set.
- // ==> Always set top and left for position relative elements in your stylesheets
- // (to 0 if you do not need them)
-
- Element.makePositioned(this.element);
- this.originalTop = parseFloat(Element.getStyle(this.element,'top') || '0');
- this.originalLeft = parseFloat(Element.getStyle(this.element,'left') || '0');
- },
- update: function(position) {
- var topd = this.toTop * position + this.originalTop;
- var leftd = this.toLeft * position + this.originalLeft;
- this.setPosition(topd, leftd);
- },
- setPosition: function(topd, leftd) {
- this.element.style.top = topd + "px";
- this.element.style.left = leftd + "px";
- }
-});
-
-Effect.Scale = Class.create();
-Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), {
- initialize: function(element, percent) {
- this.element = $(element)
- var options = Object.extend({
- scaleX: true,
- scaleY: true,
- scaleContent: true,
- scaleFromCenter: false,
- scaleMode: 'box', // 'box' or 'contents' or {} with provided values
- scaleFrom: 100.0,
- scaleTo: percent
- }, arguments[2] || {});
- this.start(options);
- },
- setup: function() {
- var effect = this;
-
- this.restoreAfterFinish = this.options.restoreAfterFinish || false;
- this.elementPositioning = Element.getStyle(this.element,'position');
-
- effect.originalStyle = {};
- ['top','left','width','height','fontSize'].each( function(k) {
- effect.originalStyle[k] = effect.element.style[k];
- });
-
- this.originalTop = this.element.offsetTop;
- this.originalLeft = this.element.offsetLeft;
-
- var fontSize = Element.getStyle(this.element,'font-size') || "100%";
- ['em','px','%'].each( function(fontSizeType) {
- if(fontSize.indexOf(fontSizeType)>0) {
- effect.fontSize = parseFloat(fontSize);
- effect.fontSizeType = fontSizeType;
- }
- });
-
- this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
-
- this.dims = null;
- if(this.options.scaleMode=='box')
- this.dims = [this.element.clientHeight, this.element.clientWidth];
- if(this.options.scaleMode=='content')
- this.dims = [this.element.scrollHeight, this.element.scrollWidth];
- if(!this.dims)
- this.dims = [this.options.scaleMode.originalHeight,
- this.options.scaleMode.originalWidth];
- },
- update: function(position) {
- var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
- if(this.options.scaleContent && this.fontSize)
- this.element.style.fontSize = this.fontSize*currentScale + this.fontSizeType;
- this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
- },
- finish: function(position) {
- if (this.restoreAfterFinish) {
- var effect = this;
- ['top','left','width','height','fontSize'].each( function(k) {
- effect.element.style[k] = effect.originalStyle[k];
- });
- }
- },
- setDimensions: function(height, width) {
- var els = this.element.style;
- if(this.options.scaleX) els.width = width + 'px';
- if(this.options.scaleY) els.height = height + 'px';
- if(this.options.scaleFromCenter) {
- var topd = (height - this.dims[0])/2;
- var leftd = (width - this.dims[1])/2;
- if(this.elementPositioning == 'absolute') {
- if(this.options.scaleY) els.top = this.originalTop-topd + "px";
- if(this.options.scaleX) els.left = this.originalLeft-leftd + "px";
- } else {
- if(this.options.scaleY) els.top = -topd + "px";
- if(this.options.scaleX) els.left = -leftd + "px";
- }
- }
- }
-});
-
-Effect.Highlight = Class.create();
-Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), {
- initialize: function(element) {
- this.element = $(element);
- var options = Object.extend({
- startcolor: "#ffff99"
- }, arguments[1] || {});
- this.start(options);
- },
- setup: function() {
- // Prevent executing on elements not in the layout flow
- if(this.element.style.display=='none') { this.cancel(); return; }
- // Disable background image during the effect
- this.oldBgImage = this.element.style.backgroundImage;
- this.element.style.backgroundImage = "none";
- if(!this.options.endcolor)
- this.options.endcolor = Element.getStyle(this.element, 'background-color').parseColor('#ffffff');
- if (typeof this.options.restorecolor == "undefined")
- this.options.restorecolor = this.element.style.backgroundColor;
- // init color calculations
- this.colors_base = [
- parseInt(this.options.startcolor.slice(1,3),16),
- parseInt(this.options.startcolor.slice(3,5),16),
- parseInt(this.options.startcolor.slice(5),16) ];
- this.colors_delta = [
- parseInt(this.options.endcolor.slice(1,3),16)-this.colors_base[0],
- parseInt(this.options.endcolor.slice(3,5),16)-this.colors_base[1],
- parseInt(this.options.endcolor.slice(5),16)-this.colors_base[2]];
- },
- update: function(position) {
- var effect = this; var colors = $R(0,2).map( function(i){
- return Math.round(effect.colors_base[i]+(effect.colors_delta[i]*position))
- });
- this.element.style.backgroundColor = "#" +
- colors[0].toColorPart() + colors[1].toColorPart() + colors[2].toColorPart();
- },
- finish: function() {
- this.element.style.backgroundColor = this.options.restorecolor;
- this.element.style.backgroundImage = this.oldBgImage;
- }
-});
-
-Effect.ScrollTo = Class.create();
-Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), {
- initialize: function(element) {
- this.element = $(element);
- this.start(arguments[1] || {});
- },
- setup: function() {
- Position.prepare();
- var offsets = Position.cumulativeOffset(this.element);
- var max = window.innerHeight ?
- window.height - window.innerHeight :
- document.body.scrollHeight -
- (document.documentElement.clientHeight ?
- document.documentElement.clientHeight : document.body.clientHeight);
- this.scrollStart = Position.deltaY;
- this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart;
- },
- update: function(position) {
- Position.prepare();
- window.scrollTo(Position.deltaX,
- this.scrollStart + (position*this.delta));
- }
-});
-
-/* ------------- combination effects ------------- */
-
-Effect.Fade = function(element) {
- var oldOpacity = Element.getInlineOpacity(element);
- var options = Object.extend({
- from: Element.getOpacity(element) || 1.0,
- to: 0.0,
- afterFinishInternal: function(effect)
- { if (effect.options.to == 0) {
- Element.hide(effect.element);
- Element.setInlineOpacity(effect.element, oldOpacity);
- }
- }
- }, arguments[1] || {});
- return new Effect.Opacity(element,options);
-}
-
-Effect.Appear = function(element) {
- var options = Object.extend({
- from: (Element.getStyle(element, "display") == "none" ? 0.0 : Element.getOpacity(element) || 0.0),
- to: 1.0,
- beforeSetup: function(effect)
- { Element.setOpacity(effect.element, effect.options.from);
- Element.show(effect.element); }
- }, arguments[1] || {});
- return new Effect.Opacity(element,options);
-}
-
-Effect.Puff = function(element) {
- element = $(element);
- var oldOpacity = Element.getInlineOpacity(element);
- var oldPosition = element.style.position;
- return new Effect.Parallel(
- [ new Effect.Scale(element, 200,
- { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }),
- new Effect.Opacity(element, { sync: true, to: 0.0 } ) ],
- Object.extend({ duration: 1.0,
- beforeSetupInternal: function(effect)
- { effect.effects[0].element.style.position = 'absolute'; },
- afterFinishInternal: function(effect)
- { Element.hide(effect.effects[0].element);
- effect.effects[0].element.style.position = oldPosition;
- Element.setInlineOpacity(effect.effects[0].element, oldOpacity); }
- }, arguments[1] || {})
- );
-}
-
-Effect.BlindUp = function(element) {
- element = $(element);
- Element.makeClipping(element);
- return new Effect.Scale(element, 0,
- Object.extend({ scaleContent: false,
- scaleX: false,
- restoreAfterFinish: true,
- afterFinishInternal: function(effect)
- {
- Element.hide(effect.element);
- Element.undoClipping(effect.element);
- }
- }, arguments[1] || {})
- );
-}
-
-Effect.BlindDown = function(element) {
- element = $(element);
- var oldHeight = element.style.height;
- var elementDimensions = Element.getDimensions(element);
- return new Effect.Scale(element, 100,
- Object.extend({ scaleContent: false,
- scaleX: false,
- scaleFrom: 0,
- scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
- restoreAfterFinish: true,
- afterSetup: function(effect) {
- Element.makeClipping(effect.element);
- effect.element.style.height = "0px";
- Element.show(effect.element);
- },
- afterFinishInternal: function(effect) {
- Element.undoClipping(effect.element);
- effect.element.style.height = oldHeight;
- }
- }, arguments[1] || {})
- );
-}
-
-Effect.SwitchOff = function(element) {
- element = $(element);
- var oldOpacity = Element.getInlineOpacity(element);
- return new Effect.Appear(element, {
- duration: 0.4,
- from: 0,
- transition: Effect.Transitions.flicker,
- afterFinishInternal: function(effect) {
- new Effect.Scale(effect.element, 1, {
- duration: 0.3, scaleFromCenter: true,
- scaleX: false, scaleContent: false, restoreAfterFinish: true,
- beforeSetup: function(effect) {
- Element.makePositioned(effect.element);
- Element.makeClipping(effect.element);
- },
- afterFinishInternal: function(effect) {
- Element.hide(effect.element);
- Element.undoClipping(effect.element);
- Element.undoPositioned(effect.element);
- Element.setInlineOpacity(effect.element, oldOpacity);
- }
- })
- }
- });
-}
-
-Effect.DropOut = function(element) {
- element = $(element);
- var oldTop = element.style.top;
- var oldLeft = element.style.left;
- var oldOpacity = Element.getInlineOpacity(element);
- return new Effect.Parallel(
- [ new Effect.MoveBy(element, 100, 0, { sync: true }),
- new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
- Object.extend(
- { duration: 0.5,
- beforeSetup: function(effect) {
- Element.makePositioned(effect.effects[0].element); },
- afterFinishInternal: function(effect) {
- Element.hide(effect.effects[0].element);
- Element.undoPositioned(effect.effects[0].element);
- effect.effects[0].element.style.left = oldLeft;
- effect.effects[0].element.style.top = oldTop;
- Element.setInlineOpacity(effect.effects[0].element, oldOpacity); }
- }, arguments[1] || {}));
-}
-
-Effect.Shake = function(element) {
- element = $(element);
- var oldTop = element.style.top;
- var oldLeft = element.style.left;
- return new Effect.MoveBy(element, 0, 20,
- { duration: 0.05, afterFinishInternal: function(effect) {
- new Effect.MoveBy(effect.element, 0, -40,
- { duration: 0.1, afterFinishInternal: function(effect) {
- new Effect.MoveBy(effect.element, 0, 40,
- { duration: 0.1, afterFinishInternal: function(effect) {
- new Effect.MoveBy(effect.element, 0, -40,
- { duration: 0.1, afterFinishInternal: function(effect) {
- new Effect.MoveBy(effect.element, 0, 40,
- { duration: 0.1, afterFinishInternal: function(effect) {
- new Effect.MoveBy(effect.element, 0, -20,
- { duration: 0.05, afterFinishInternal: function(effect) {
- Element.undoPositioned(effect.element);
- effect.element.style.left = oldLeft;
- effect.element.style.top = oldTop;
- }}) }}) }}) }}) }}) }});
-}
-
-Effect.SlideDown = function(element) {
- element = $(element);
- Element.cleanWhitespace(element);
- // SlideDown need to have the content of the element wrapped in a container element with fixed height!
- var oldInnerBottom = element.firstChild.style.bottom;
- var elementDimensions = Element.getDimensions(element);
- return new Effect.Scale(element, 100,
- Object.extend({ scaleContent: false,
- scaleX: false,
- scaleFrom: 0,
- scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
- restoreAfterFinish: true,
- afterSetup: function(effect) {
- Element.makePositioned(effect.element.firstChild);
- if (window.opera) effect.element.firstChild.style.top = "";
- Element.makeClipping(effect.element);
- element.style.height = '0';
- Element.show(element);
- },
- afterUpdateInternal: function(effect) {
- effect.element.firstChild.style.bottom =
- (effect.dims[0] - effect.element.clientHeight) + 'px'; },
- afterFinishInternal: function(effect) {
- Element.undoClipping(effect.element);
- Element.undoPositioned(effect.element.firstChild);
- effect.element.firstChild.style.bottom = oldInnerBottom; }
- }, arguments[1] || {})
- );
-}
-
-Effect.SlideUp = function(element) {
- element = $(element);
- Element.cleanWhitespace(element);
- var oldInnerBottom = element.firstChild.style.bottom;
- return new Effect.Scale(element, 0,
- Object.extend({ scaleContent: false,
- scaleX: false,
- scaleMode: 'box',
- scaleFrom: 100,
- restoreAfterFinish: true,
- beforeStartInternal: function(effect) {
- Element.makePositioned(effect.element.firstChild);
- if (window.opera) effect.element.firstChild.style.top = "";
- Element.makeClipping(effect.element);
- Element.show(element);
- },
- afterUpdateInternal: function(effect) {
- effect.element.firstChild.style.bottom =
- (effect.dims[0] - effect.element.clientHeight) + 'px'; },
- afterFinishInternal: function(effect) {
- Element.hide(effect.element);
- Element.undoClipping(effect.element);
- Element.undoPositioned(effect.element.firstChild);
- effect.element.firstChild.style.bottom = oldInnerBottom; }
- }, arguments[1] || {})
- );
-}
-
-Effect.Squish = function(element) {
- // Bug in opera makes the TD containing this element expand for a instance after finish
- return new Effect.Scale(element, window.opera ? 1 : 0,
- { restoreAfterFinish: true,
- beforeSetup: function(effect) {
- Element.makeClipping(effect.element); },
- afterFinishInternal: function(effect) {
- Element.hide(effect.element);
- Element.undoClipping(effect.element); }
- });
-}
-
-Effect.Grow = function(element) {
- element = $(element);
- var options = arguments[1] || {};
-
- var elementDimensions = Element.getDimensions(element);
- var originalWidth = elementDimensions.width;
- var originalHeight = elementDimensions.height;
- var oldTop = element.style.top;
- var oldLeft = element.style.left;
- var oldHeight = element.style.height;
- var oldWidth = element.style.width;
- var oldOpacity = Element.getInlineOpacity(element);
-
- var direction = options.direction || 'center';
- var moveTransition = options.moveTransition || Effect.Transitions.sinoidal;
- var scaleTransition = options.scaleTransition || Effect.Transitions.sinoidal;
- var opacityTransition = options.opacityTransition || Effect.Transitions.full;
-
- var initialMoveX, initialMoveY;
- var moveX, moveY;
-
- switch (direction) {
- case 'top-left':
- initialMoveX = initialMoveY = moveX = moveY = 0;
- break;
- case 'top-right':
- initialMoveX = originalWidth;
- initialMoveY = moveY = 0;
- moveX = -originalWidth;
- break;
- case 'bottom-left':
- initialMoveX = moveX = 0;
- initialMoveY = originalHeight;
- moveY = -originalHeight;
- break;
- case 'bottom-right':
- initialMoveX = originalWidth;
- initialMoveY = originalHeight;
- moveX = -originalWidth;
- moveY = -originalHeight;
- break;
- case 'center':
- initialMoveX = originalWidth / 2;
- initialMoveY = originalHeight / 2;
- moveX = -originalWidth / 2;
- moveY = -originalHeight / 2;
- break;
- }
-
- return new Effect.MoveBy(element, initialMoveY, initialMoveX, {
- duration: 0.01,
- beforeSetup: function(effect) {
- Element.hide(effect.element);
- Element.makeClipping(effect.element);
- Element.makePositioned(effect.element);
- },
- afterFinishInternal: function(effect) {
- new Effect.Parallel(
- [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: opacityTransition }),
- new Effect.MoveBy(effect.element, moveY, moveX, { sync: true, transition: moveTransition }),
- new Effect.Scale(effect.element, 100, {
- scaleMode: { originalHeight: originalHeight, originalWidth: originalWidth },
- sync: true, scaleFrom: window.opera ? 1 : 0, transition: scaleTransition, restoreAfterFinish: true})
- ], Object.extend({
- beforeSetup: function(effect) {
- effect.effects[0].element.style.height = 0;
- Element.show(effect.effects[0].element);
- },
- afterFinishInternal: function(effect) {
- var el = effect.effects[0].element;
- var els = el.style;
- Element.undoClipping(el);
- Element.undoPositioned(el);
- els.top = oldTop;
- els.left = oldLeft;
- els.height = oldHeight;
- els.width = originalWidth + 'px';
- Element.setInlineOpacity(el, oldOpacity);
- }
- }, options)
- )
- }
- });
-}
-
-Effect.Shrink = function(element) {
- element = $(element);
- var options = arguments[1] || {};
-
- var originalWidth = element.clientWidth;
- var originalHeight = element.clientHeight;
- var oldTop = element.style.top;
- var oldLeft = element.style.left;
- var oldHeight = element.style.height;
- var oldWidth = element.style.width;
- var oldOpacity = Element.getInlineOpacity(element);
-
- var direction = options.direction || 'center';
- var moveTransition = options.moveTransition || Effect.Transitions.sinoidal;
- var scaleTransition = options.scaleTransition || Effect.Transitions.sinoidal;
- var opacityTransition = options.opacityTransition || Effect.Transitions.none;
-
- var moveX, moveY;
-
- switch (direction) {
- case 'top-left':
- moveX = moveY = 0;
- break;
- case 'top-right':
- moveX = originalWidth;
- moveY = 0;
- break;
- case 'bottom-left':
- moveX = 0;
- moveY = originalHeight;
- break;
- case 'bottom-right':
- moveX = originalWidth;
- moveY = originalHeight;
- break;
- case 'center':
- moveX = originalWidth / 2;
- moveY = originalHeight / 2;
- break;
- }
-
- return new Effect.Parallel(
- [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: opacityTransition }),
- new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: scaleTransition, restoreAfterFinish: true}),
- new Effect.MoveBy(element, moveY, moveX, { sync: true, transition: moveTransition })
- ], Object.extend({
- beforeStartInternal: function(effect) {
- Element.makePositioned(effect.effects[0].element);
- Element.makeClipping(effect.effects[0].element);
- },
- afterFinishInternal: function(effect) {
- var el = effect.effects[0].element;
- var els = el.style;
- Element.hide(el);
- Element.undoClipping(el);
- Element.undoPositioned(el);
- els.top = oldTop;
- els.left = oldLeft;
- els.height = oldHeight;
- els.width = oldWidth;
- Element.setInlineOpacity(el, oldOpacity);
- }
- }, options)
- );
-}
-
-Effect.Pulsate = function(element) {
- element = $(element);
- var options = arguments[1] || {};
- var oldOpacity = Element.getInlineOpacity(element);
- var transition = options.transition || Effect.Transitions.sinoidal;
- var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos)) };
- reverser.bind(transition);
- return new Effect.Opacity(element,
- Object.extend(Object.extend({ duration: 3.0, from: 0,
- afterFinishInternal: function(effect) { Element.setInlineOpacity(effect.element, oldOpacity); }
- }, options), {transition: reverser}));
-}
-
-Effect.Fold = function(element) {
- element = $(element);
- var originalTop = element.style.top;
- var originalLeft = element.style.left;
- var originalWidth = element.style.width;
- var originalHeight = element.style.height;
- Element.makeClipping(element);
- return new Effect.Scale(element, 5, Object.extend({
- scaleContent: false,
- scaleX: false,
- afterFinishInternal: function(effect) {
- new Effect.Scale(element, 1, {
- scaleContent: false,
- scaleY: false,
- afterFinishInternal: function(effect) {
- Element.hide(effect.element);
- Element.undoClipping(effect.element);
- effect.element.style.top = originalTop;
- effect.element.style.left = originalLeft;
- effect.element.style.width = originalWidth;
- effect.element.style.height = originalHeight;
- } });
- }}, arguments[1] || {}));
-}
diff --git a/web/static/js/scriptaculous/scriptaculous.js b/web/static/js/scriptaculous/scriptaculous.js
deleted file mode 100644
index 940d886..0000000
--- a/web/static/js/scriptaculous/scriptaculous.js
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
-//
-// Permission is hereby granted, free of charge, to any person obtaining
-// a copy of this software and associated documentation files (the
-// "Software"), to deal in the Software without restriction, including
-// without limitation the rights to use, copy, modify, merge, publish,
-// distribute, sublicense, and/or sell copies of the Software, and to
-// permit persons to whom the Software is furnished to do so, subject to
-// the following conditions:
-//
-// The above copyright notice and this permission notice shall be
-// included in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-var Scriptaculous = {
- Version: '1.5_rc4',
- require: function(libraryName) {
- // inserting via DOM fails in Safari 2.0, so brute force approach
- document.write('<script type="text/javascript" src="'+libraryName+'"></script>');
- },
- load: function() {
- if((typeof Prototype=='undefined') ||
- parseFloat(Prototype.Version.split(".")[0] + "." +
- Prototype.Version.split(".")[1]) < 1.4)
- throw("script.aculo.us requires the Prototype JavaScript framework >= 1.4.0");
- var scriptTags = document.getElementsByTagName("script");
- for(var i=0;i<scriptTags.length;i++) {
- if(scriptTags[i].src && scriptTags[i].src.match(/scriptaculous\.js(\?.*)?$/)) {
- var path = scriptTags[i].src.replace(/scriptaculous\.js(\?.*)?$/,'');
- this.require(path + 'builder.js');
- this.require(path + 'effects.js');
- this.require(path + 'dragdrop.js');
- this.require(path + 'controls.js');
- this.require(path + 'slider.js');
- break;
- }
- }
- }
-}
-
-Scriptaculous.load();
\ No newline at end of file
diff --git a/web/static/js/scriptaculous/slider.js b/web/static/js/scriptaculous/slider.js
deleted file mode 100644
index 736ed41..0000000
--- a/web/static/js/scriptaculous/slider.js
+++ /dev/null
@@ -1,226 +0,0 @@
-// Copyright (c) 2005 Marty Haught
-//
-// See scriptaculous.js for full license.
-
-if(!Control) var Control = {};
-Control.Slider = Class.create();
-
-// options:
-// axis: 'vertical', or 'horizontal' (default)
-//
-// callbacks:
-// onChange(value)
-// onSlide(value)
-Control.Slider.prototype = {
- initialize: function(handle, track, options) {
- var slider = this;
-
- if(handle instanceof Array) {
- this.handles = handle.collect( function(e) { return $(e) });
- } else {
- this.handles = [$(handle)];
- }
-
- this.track = $(track);
- this.options = options || {};
-
- this.axis = this.options.axis || 'horizontal';
- this.increment = this.options.increment || 1;
- this.step = parseInt(this.options.step || '1');
- this.range = this.options.range || $R(0,1);
-
- this.value = 0; // assure backwards compat
- this.values = this.handles.map( function() { return 0 });
- this.spans = this.options.spans ? this.options.spans.map(function(s){ return $(s) }) : false;
- this.restricted = this.options.restricted || false;
-
- this.maximum = this.options.maximum || this.range.end;
- this.minimum = this.options.minimum || this.range.start;
-
- // Will be used to align the handle onto the track, if necessary
- this.alignX = parseInt(this.options.alignX || '0');
- this.alignY = parseInt(this.options.alignY || '0');
-
- this.trackLength = this.maximumOffset() - this.minimumOffset();
-
- this.active = false;
- this.dragging = false;
- this.disabled = false;
-
- if(this.options.disabled) this.setDisabled();
-
- // Allowed values array
- this.allowedValues = this.options.values ? this.options.values.sortBy(Prototype.K) : false;
- if(this.allowedValues) {
- this.minimum = this.allowedValues.min();
- this.maximum = this.allowedValues.max();
- }
-
- this.eventMouseDown = this.startDrag.bindAsEventListener(this);
- this.eventMouseUp = this.endDrag.bindAsEventListener(this);
- this.eventMouseMove = this.update.bindAsEventListener(this);
-
- // Initialize handles
- this.handles.each( function(h,i) {
- slider.setValue(parseInt(slider.options.sliderValue || slider.range.start), i);
- Element.makePositioned(h); // fix IE
- Event.observe(h, "mousedown", slider.eventMouseDown);
- });
-
- Event.observe(document, "mouseup", this.eventMouseUp);
- Event.observe(document, "mousemove", this.eventMouseMove);
- },
- dispose: function() {
- var slider = this;
- Event.stopObserving(document, "mouseup", this.eventMouseUp);
- Event.stopObserving(document, "mousemove", this.eventMouseMove);
- this.handles.each( function(h) {
- Event.stopObserving(h, "mousedown", slider.eventMouseDown);
- });
- },
- setDisabled: function(){
- this.disabled = true;
- },
- setEnabled: function(){
- this.disabled = false;
- },
- getNearestValue: function(value){
- if(this.allowedValues){
- if(value >= this.allowedValues.max()) return(this.allowedValues.max());
- if(value <= this.allowedValues.min()) return(this.allowedValues.min());
-
- var offset = Math.abs(this.allowedValues[0] - value);
- var newValue = this.allowedValues[0];
- this.allowedValues.each( function(v) {
- var currentOffset = Math.abs(v - value);
- if(currentOffset <= offset){
- newValue = v;
- offset = currentOffset;
- }
- });
- return newValue;
- }
- if(value > this.range.end) return this.range.end;
- if(value < this.range.start) return this.range.start;
- return value;
- },
- setValue: function(sliderValue, handleIdx){
- if(!this.active) {
- this.activeHandle = this.handles[handleIdx];
- this.activeHandleIdx = handleIdx;
- }
- handleIdx = handleIdx || this.activeHandleIdx || 0;
- if(this.restricted) {
- if((handleIdx>0) && (sliderValue<this.values[handleIdx-1]))
- sliderValue = this.values[handleIdx-1];
- if((handleIdx < (this.handles.length-1)) && (sliderValue>this.values[handleIdx+1]))
- sliderValue = this.values[handleIdx+1];
- }
- sliderValue = this.getNearestValue(sliderValue);
- this.values[handleIdx] = sliderValue;
- this.value = this.values[0]; // assure backwards compat
-
- this.handles[handleIdx].style[ this.isVertical() ? 'top' : 'left'] =
- this.translateToPx(sliderValue);
-
- this.drawSpans();
- this.updateFinished();
- },
- setValueBy: function(delta, handleIdx) {
- this.setValue(this.values[handleIdx || this.activeHandleIdx || 0] + delta,
- handleIdx || this.activeHandleIdx || 0);
- },
- translateToPx: function(value) {
- return Math.round((this.trackLength / (this.range.end - this.range.start)) * (value - this.range.start)) + "px";
- },
- translateToValue: function(offset) {
- return ((offset/this.trackLength) * (this.range.end - this.range.start)) + this.range.start;
- },
- getRange: function(range) {
- var v = this.values.sortBy(Prototype.K);
- range = range || 0;
- return $R(v[range],v[range+1]);
- },
- minimumOffset: function(){
- return(this.isVertical() ? this.alignY : this.alignX);
- },
- maximumOffset: function(){
- return(this.isVertical() ?
- this.track.offsetHeight - this.alignY : this.track.offsetWidth - this.alignX);
- },
- isVertical: function(){
- return (this.axis == 'vertical');
- },
- drawSpans: function() {
- var slider = this;
- if(this.spans)
- $R(0, this.spans.length-1).each(function(r) { slider.setSpan(r, slider.getRange(r)) });
- },
- setSpan: function(span, range) {
- if(this.isVertical()) {
- this.spans[span].style.top = this.translateToPx(range.start);
- this.spans[span].style.height = this.translateToPx(range.end - range.start);
- } else {
- this.spans[span].style.left = this.translateToPx(range.start);
- this.spans[span].style.width = this.translateToPx(range.end - range.start);
- }
- },
- startDrag: function(event) {
- if(Event.isLeftClick(event)) {
- if(!this.disabled){
- this.active = true;
-
- // find the handle (prevents issues with Safari)
- var handle = Event.element(event);
- while((this.handles.indexOf(handle) == -1) && handle.parentNode)
- handle = handle.parentNode;
-
- this.activeHandle = handle;
- this.activeHandleIdx = this.handles.indexOf(this.activeHandle);
-
- var pointer = [Event.pointerX(event), Event.pointerY(event)];
- var offsets = Position.cumulativeOffset(this.activeHandle);
- this.offsetX = (pointer[0] - offsets[0]);
- this.offsetY = (pointer[1] - offsets[1]);
-
- }
- Event.stop(event);
- }
- },
- update: function(event) {
- if(this.active) {
- if(!this.dragging) {
- this.dragging = true;
- if(this.activeHandle.style.position=="") style.position = "relative";
- }
- this.draw(event);
- // fix AppleWebKit rendering
- if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
- Event.stop(event);
- }
- },
- draw: function(event) {
- var pointer = [Event.pointerX(event), Event.pointerY(event)];
- var offsets = Position.cumulativeOffset(this.track);
- pointer[0] -= this.offsetX + offsets[0];
- pointer[1] -= this.offsetY + offsets[1];
- this.setValue(this.translateToValue( this.isVertical() ? pointer[1] : pointer[0] ));
- if(this.options.onSlide) this.options.onSlide(this.values.length>1 ? this.values : this.value, this);
- },
- endDrag: function(event) {
- if(this.active && this.dragging) {
- this.finishDrag(event, true);
- Event.stop(event);
- }
- this.active = false;
- this.dragging = false;
- },
- finishDrag: function(event, success) {
- this.active = false;
- this.dragging = false;
- this.updateFinished();
- },
- updateFinished: function() {
- if(this.options.onChange) this.options.onChange(this.values.length>1 ? this.values : this.value, this);
- }
-}
diff --git a/web/static/js/scriptaculous/unittest.js b/web/static/js/scriptaculous/unittest.js
deleted file mode 100644
index 20941ad..0000000
--- a/web/static/js/scriptaculous/unittest.js
+++ /dev/null
@@ -1,363 +0,0 @@
-// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
-// (c) 2005 Jon Tirsen (http://www.tirsen.com)
-// (c) 2005 Michael Schuerig (http://www.schuerig.de/michael/)
-//
-// See scriptaculous.js for full license.
-
-// experimental, Firefox-only
-Event.simulateMouse = function(element, eventName) {
- var options = Object.extend({
- pointerX: 0,
- pointerY: 0,
- buttons: 0
- }, arguments[2] || {});
- var oEvent = document.createEvent("MouseEvents");
- oEvent.initMouseEvent(eventName, true, true, document.defaultView,
- options.buttons, options.pointerX, options.pointerY, options.pointerX, options.pointerY,
- false, false, false, false, 0, $(element));
-
- if(this.mark) Element.remove(this.mark);
- this.mark = document.createElement('div');
- this.mark.appendChild(document.createTextNode(" "));
- document.body.appendChild(this.mark);
- this.mark.style.position = 'absolute';
- this.mark.style.top = options.pointerY + "px";
- this.mark.style.left = options.pointerX + "px";
- this.mark.style.width = "5px";
- this.mark.style.height = "5px;";
- this.mark.style.borderTop = "1px solid red;"
- this.mark.style.borderLeft = "1px solid red;"
-
- if(this.step)
- alert('['+new Date().getTime().toString()+'] '+eventName+'/'+Test.Unit.inspect(options));
-
- $(element).dispatchEvent(oEvent);
-};
-
-// Note: Due to a fix in Firefox 1.0.5/6 that probably fixed "too much", this doesn't work in 1.0.6 or DP2.
-// You need to downgrade to 1.0.4 for now to get this working
-// See https://bugzilla.mozilla.org/show_bug.cgi?id=289940 for the fix that fixed too much
-Event.simulateKey = function(element, eventName) {
- var options = Object.extend({
- ctrlKey: false,
- altKey: false,
- shiftKey: false,
- metaKey: false,
- keyCode: 0,
- charCode: 0
- }, arguments[2] || {});
-
- var oEvent = document.createEvent("KeyEvents");
- oEvent.initKeyEvent(eventName, true, true, window,
- options.ctrlKey, options.altKey, options.shiftKey, options.metaKey,
- options.keyCode, options.charCode );
- $(element).dispatchEvent(oEvent);
-};
-
-Event.simulateKeys = function(element, command) {
- for(var i=0; i<command.length; i++) {
- Event.simulateKey(element,'keypress',{charCode:command.charCodeAt(i)});
- }
-};
-
-var Test = {}
-Test.Unit = {};
-
-// security exception workaround
-Test.Unit.inspect = function(obj) {
- var info = [];
-
- if(typeof obj=="string" ||
- typeof obj=="number") {
- return obj;
- } else {
- for(property in obj)
- if(typeof obj[property]!="function")
- info.push(property + ' => ' +
- (typeof obj[property] == "string" ?
- '"' + obj[property] + '"' :
- obj[property]));
- }
-
- return ("'" + obj + "' #" + typeof obj +
- ": {" + info.join(", ") + "}");
-}
-
-Test.Unit.Logger = Class.create();
-Test.Unit.Logger.prototype = {
- initialize: function(log) {
- this.log = $(log);
- if (this.log) {
- this._createLogTable();
- }
- },
- start: function(testName) {
- if (!this.log) return;
- this.testName = testName;
- this.lastLogLine = document.createElement('tr');
- this.statusCell = document.createElement('td');
- this.nameCell = document.createElement('td');
- this.nameCell.appendChild(document.createTextNode(testName));
- this.messageCell = document.createElement('td');
- this.lastLogLine.appendChild(this.statusCell);
- this.lastLogLine.appendChild(this.nameCell);
- this.lastLogLine.appendChild(this.messageCell);
- this.loglines.appendChild(this.lastLogLine);
- },
- finish: function(status, summary) {
- if (!this.log) return;
- this.lastLogLine.className = status;
- this.statusCell.innerHTML = status;
- this.messageCell.innerHTML = this._toHTML(summary);
- },
- message: function(message) {
- if (!this.log) return;
- this.messageCell.innerHTML = this._toHTML(message);
- },
- summary: function(summary) {
- if (!this.log) return;
- this.logsummary.innerHTML = this._toHTML(summary);
- },
- _createLogTable: function() {
- this.log.innerHTML =
- '<div id="logsummary"></div>' +
- '<table id="logtable">' +
- '<thead><tr><th>Status</th><th>Test</th><th>Message</th></tr></thead>' +
- '<tbody id="loglines"></tbody>' +
- '</table>';
- this.logsummary = $('logsummary')
- this.loglines = $('loglines');
- },
- _toHTML: function(txt) {
- return txt.escapeHTML().replace(/\n/g,"<br/>");
- }
-}
-
-Test.Unit.Runner = Class.create();
-Test.Unit.Runner.prototype = {
- initialize: function(testcases) {
- this.options = Object.extend({
- testLog: 'testlog'
- }, arguments[1] || {});
- this.options.resultsURL = this.parseResultsURLQueryParameter();
- if (this.options.testLog) {
- this.options.testLog = $(this.options.testLog) || null;
- }
- if(this.options.tests) {
- this.tests = [];
- for(var i = 0; i < this.options.tests.length; i++) {
- if(/^test/.test(this.options.tests[i])) {
- this.tests.push(new Test.Unit.Testcase(this.options.tests[i], testcases[this.options.tests[i]], testcases["setup"], testcases["teardown"]));
- }
- }
- } else {
- if (this.options.test) {
- this.tests = [new Test.Unit.Testcase(this.options.test, testcases[this.options.test], testcases["setup"], testcases["teardown"])];
- } else {
- this.tests = [];
- for(var testcase in testcases) {
- if(/^test/.test(testcase)) {
- this.tests.push(new Test.Unit.Testcase(testcase, testcases[testcase], testcases["setup"], testcases["teardown"]));
- }
- }
- }
- }
- this.currentTest = 0;
- this.logger = new Test.Unit.Logger(this.options.testLog);
- setTimeout(this.runTests.bind(this), 1000);
- },
- parseResultsURLQueryParameter: function() {
- return window.location.search.parseQuery()["resultsURL"];
- },
- // Returns:
- // "ERROR" if there was an error,
- // "FAILURE" if there was a failure, or
- // "SUCCESS" if there was neither
- getResult: function() {
- var hasFailure = false;
- for(var i=0;i<this.tests.length;i++) {
- if (this.tests[i].errors > 0) {
- return "ERROR";
- }
- if (this.tests[i].failures > 0) {
- hasFailure = true;
- }
- }
- if (hasFailure) {
- return "FAILURE";
- } else {
- return "SUCCESS";
- }
- },
- postResults: function() {
- if (this.options.resultsURL) {
- new Ajax.Request(this.options.resultsURL,
- { method: 'get', parameters: 'result=' + this.getResult(), asynchronous: false });
- }
- },
- runTests: function() {
- var test = this.tests[this.currentTest];
- if (!test) {
- // finished!
- this.postResults();
- this.logger.summary(this.summary());
- return;
- }
- if(!test.isWaiting) {
- this.logger.start(test.name);
- }
- test.run();
- if(test.isWaiting) {
- this.logger.message("Waiting for " + test.timeToWait + "ms");
- setTimeout(this.runTests.bind(this), test.timeToWait || 1000);
- } else {
- this.logger.finish(test.status(), test.summary());
- this.currentTest++;
- // tail recursive, hopefully the browser will skip the stackframe
- this.runTests();
- }
- },
- summary: function() {
- var assertions = 0;
- var failures = 0;
- var errors = 0;
- var messages = [];
- for(var i=0;i<this.tests.length;i++) {
- assertions += this.tests[i].assertions;
- failures += this.tests[i].failures;
- errors += this.tests[i].errors;
- }
- return (
- this.tests.length + " tests, " +
- assertions + " assertions, " +
- failures + " failures, " +
- errors + " errors");
- }
-}
-
-Test.Unit.Assertions = Class.create();
-Test.Unit.Assertions.prototype = {
- initialize: function() {
- this.assertions = 0;
- this.failures = 0;
- this.errors = 0;
- this.messages = [];
- },
- summary: function() {
- return (
- this.assertions + " assertions, " +
- this.failures + " failures, " +
- this.errors + " errors" + "\n" +
- this.messages.join("\n"));
- },
- pass: function() {
- this.assertions++;
- },
- fail: function(message) {
- this.failures++;
- this.messages.push("Failure: " + message);
- },
- error: function(error) {
- this.errors++;
- this.messages.push(error.name + ": "+ error.message + "(" + Test.Unit.inspect(error) +")");
- },
- status: function() {
- if (this.failures > 0) return 'failed';
- if (this.errors > 0) return 'error';
- return 'passed';
- },
- assert: function(expression) {
- var message = arguments[1] || 'assert: got "' + Test.Unit.inspect(expression) + '"';
- try { expression ? this.pass() :
- this.fail(message); }
- catch(e) { this.error(e); }
- },
- assertEqual: function(expected, actual) {
- var message = arguments[2] || "assertEqual";
- try { (expected == actual) ? this.pass() :
- this.fail(message + ': expected "' + Test.Unit.inspect(expected) +
- '", actual "' + Test.Unit.inspect(actual) + '"'); }
- catch(e) { this.error(e); }
- },
- assertNotEqual: function(expected, actual) {
- var message = arguments[2] || "assertNotEqual";
- try { (expected != actual) ? this.pass() :
- this.fail(message + ': got "' + Test.Unit.inspect(actual) + '"'); }
- catch(e) { this.error(e); }
- },
- assertNull: function(obj) {
- var message = arguments[1] || 'assertNull'
- try { (obj==null) ? this.pass() :
- this.fail(message + ': got "' + Test.Unit.inspect(obj) + '"'); }
- catch(e) { this.error(e); }
- },
- assertHidden: function(element) {
- var message = arguments[1] || 'assertHidden';
- this.assertEqual("none", element.style.display, message);
- },
- assertNotNull: function(object) {
- var message = arguments[1] || 'assertNotNull';
- this.assert(object != null, message);
- },
- assertInstanceOf: function(expected, actual) {
- var message = arguments[2] || 'assertInstanceOf';
- try {
- (actual instanceof expected) ? this.pass() :
- this.fail(message + ": object was not an instance of the expected type"); }
- catch(e) { this.error(e); }
- },
- assertNotInstanceOf: function(expected, actual) {
- var message = arguments[2] || 'assertNotInstanceOf';
- try {
- !(actual instanceof expected) ? this.pass() :
- this.fail(message + ": object was an instance of the not expected type"); }
- catch(e) { this.error(e); }
- },
- _isVisible: function(element) {
- element = $(element);
- if(!element.parentNode) return true;
- this.assertNotNull(element);
- if(element.style && Element.getStyle(element, 'display') == 'none')
- return false;
-
- return this._isVisible(element.parentNode);
- },
- assertNotVisible: function(element) {
- this.assert(!this._isVisible(element), Test.Unit.inspect(element) + " was not hidden and didn't have a hidden parent either. " + ("" || arguments[1]));
- },
- assertVisible: function(element) {
- this.assert(this._isVisible(element), Test.Unit.inspect(element) + " was not visible. " + ("" || arguments[1]));
- }
-}
-
-Test.Unit.Testcase = Class.create();
-Object.extend(Object.extend(Test.Unit.Testcase.prototype, Test.Unit.Assertions.prototype), {
- initialize: function(name, test, setup, teardown) {
- Test.Unit.Assertions.prototype.initialize.bind(this)();
- this.name = name;
- this.test = test || function() {};
- this.setup = setup || function() {};
- this.teardown = teardown || function() {};
- this.isWaiting = false;
- this.timeToWait = 1000;
- },
- wait: function(time, nextPart) {
- this.isWaiting = true;
- this.test = nextPart;
- this.timeToWait = time;
- },
- run: function() {
- try {
- try {
- if (!this.isWaiting) this.setup.bind(this)();
- this.isWaiting = false;
- this.test.bind(this)();
- } finally {
- if(!this.isWaiting) {
- this.teardown.bind(this)();
- }
- }
- }
- catch(e) { this.error(e); }
- }
-});
\ No newline at end of file
diff --git a/web/templates/_elements/header b/web/templates/_elements/header
deleted file mode 100644
index e8942d3..0000000
--- a/web/templates/_elements/header
+++ /dev/null
@@ -1,25 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
-<head>
- <meta http-equiv="content-type" content="text/html; charset=utf-8" />
- <meta name="robots" content="all" />
-
- <title><% $title %></title>
-
- <link rel="stylesheet" type="text/css" href="/css/main.css" media="all" />
-
- <script type="text/javascript" src="/js/prototype.js"></script>
- <script type="text/javascript" src="/js/rico.js"></script>
- <script type="text/javascript" src="/js/behaviour.js"></script>
- <script type="text/javascript" src="/js/jifty.js"></script>
- <script type="text/javascript" src="/js/btdt_behaviour.js"></script>
- <script type="text/javascript" src="/js/bps_util.js"></script>
- <script type="text/javascript" src="/js/combobox.js"></script>
- <script type="text/javascript" src="/js/key_bindings.js"></script>
-</head>
-<%args>
-$title => ""
-</%args>
-<%init>
-$r->content_type('text/html; charset=utf-8');
-</%init>
diff --git a/web/templates/_elements/markup b/web/templates/_elements/markup
index a281a2a..b6b8cca 100644
--- a/web/templates/_elements/markup
+++ b/web/templates/_elements/markup
@@ -3,58 +3,17 @@
<h3>Phrase Emphasis</h3>
-<pre><code>*italic* **bold**
-_italic_ __bold__
-</code></pre>
+<code> <b>**bold**</b> <i>_italic_</i> </code>
<h3>Links</h3>
-<p>Inline:</p>
-
-<pre><code>Show me a [wiki page](WikiPage)</code></pre>
-
-<pre><code>An [example](http://url.com/ "Title")
-</code></pre>
-
-<p>Reference-style labels (titles are optional):</p>
-
-<pre><code>An [example][id]. Then, anywhere
-else in the doc, define the link:
-
- [id]: http://example.com/ "Title"
-</code></pre>
-
-<h3>Images</h3>
-
-<p>Inline (titles are optional):</p>
-
-<pre><code>![alt text](/path/img.jpg "Title")
-</code></pre>
-
-<p>Reference-style:</p>
-
-<pre><code>![alt text][id]
-
-[id]: /url/to/img.jpg "Title"
-</code></pre>
+<code>Show me a [wiki page](WikiPage)</code>
+<code>An [example](http://url.com/ "Title")</code>
<h3>Headers</h3>
-<p>Setext-style:</p>
-
-<pre><code>Header 1
-========
-
-Header 2
---------
-</code></pre>
-
-<p>atx-style (closing #'s are optional):</p>
-
-<pre><code># Header 1 #
-
-## Header 2 ##
-
+<pre><code># Header 1
+## Header 2
###### Header 6
</code></pre>
@@ -64,7 +23,6 @@ Header 2
<pre><code>1. Foo
2. Bar
-
</code></pre>
<p>Unordered, with paragraphs:</p>
@@ -73,47 +31,17 @@ Header 2
With multiple paragraphs.
-* Bar
-</code></pre>
-
-<p>You can nest them:</p>
-
-<pre><code>* Abacus
- * answer
-* Bubbles
- 1. bunk
- 2. bupkis
- * BELITTLER
- 3. burper
-* Cunning
-</code></pre>
-
-<h3>Blockquotes</h3>
-
-<pre><code>> Email-style angle brackets
-> are used for blockquotes.
-
-> > And, they can be nested.
-
-> #### Headers in blockquotes
->
-> * You can quote a list.
-> * Etc.
-</code></pre>
+* Bar</code></pre>
<h3>Code Spans</h3>
-<pre><code>`<code>` spans are delimited
-by backticks.
-
-You can include literal backticks
-like `` `this` ``.
-</code></pre>
+<p><code>`<code>`</code> spans are
+delimited by backticks.</p>
<h3>Preformatted Code Blocks</h3>
<p>Indent every line of a code block
-by at least 4 spaces or 1 tab.</p>
+by at least 4 spaces.</p>
<pre><code>This is a normal paragraph.
@@ -123,21 +51,7 @@ by at least 4 spaces or 1 tab.</p>
<h3>Horizontal Rules</h3>
-<p>Three or more dashes or asterisks:</p>
-
-<pre><code>---
-
-* * *
+<p>Three or more dashes: <code>---</code></p>
-- - - -
-</code></pre>
-
-<h3>Manual Line Breaks</h3>
-
-<p>End a line with two or more spaces:</p>
-
-<pre><code>Roses are red,
-Violets are blue.
-</code></pre>
<address>(Thanks to <a href="http://daringfireball.net/projects/markdown/dingus">Daring Fireball</a>)</address>
</div>
diff --git a/web/templates/_elements/nav b/web/templates/_elements/nav
index c773de9..5baff86 100644
--- a/web/templates/_elements/nav
+++ b/web/templates/_elements/nav
@@ -1,7 +1,16 @@
<%init>
my $top = Jifty->web->navigation;
-$top->child(Home => url => "/", sort_order => 1);
-$top->child(Recent => url => "/recent", label => "Recent Changes", sort_order => 2);
+$top->child(
+ Home =>
+ url => "/",
+ sort_order => 1
+);
+$top->child(
+ Recent =>
+ url => "/recent",
+ label => "Recent Changes",
+ sort_order => 2
+);
return();
</%init>
diff --git a/web/templates/_elements/sidebar b/web/templates/_elements/sidebar
deleted file mode 100644
index 2869f05..0000000
--- a/web/templates/_elements/sidebar
+++ /dev/null
@@ -1,26 +0,0 @@
-<div id="salutation">
-% if (Jifty->web->current_user->id) {
-Hiya, <span class="user"><%Jifty->web->current_user->name%></span>.
-% } else {
-You're not currently signed in.
-% }
-</div>
-<ul class="menu">
-% $m->comp(".menu", item => $_) for (sort { $a->sort_order <=> $b->sort_order} Jifty->web->navigation->children);
-</ul>
-<%def .menu>
-<%args>
-$item
-</%args>
- <li><%
- Jifty->web->link(
- url => $item->url,
- label => $item->label,
- class => $item->active ? "active" : ""
- ) %></li>
-% if (my @kids = $item->children) {
-<ul class="menu submenu">
-% $m->comp(".menu", item => $_) for @kids;
-</ul>
-% }
-</%def>
diff --git a/web/templates/_elements/wrapper b/web/templates/_elements/wrapper
deleted file mode 100644
index 0ec63a4..0000000
--- a/web/templates/_elements/wrapper
+++ /dev/null
@@ -1,27 +0,0 @@
-<& header, title => $title &>
-<body>
- <div id="headers">
- <%Jifty->web->link( url => "/", label => Jifty->config->framework('ApplicationName'))%>
- <h1 class="title"><% $title %></h1>
- </div>
- <& sidebar &>
- <div id="content">
- <a name="content"></a>
- <% Jifty->web->render_messages %>
- <% $m->content |n%>
- <div id="keybindings">
- <script><!--
- writeKeyBindingLegend();
- --></script>
- </div>
- </div>
- <div id="jifty-wait-message">Loading...</div>
-</body>
-</html>
-<%args>
-$title => ""
-</%args>
-<%init>
-$m->comp('nav');
-
-</%init>
diff --git a/web/templates/autohandler b/web/templates/autohandler
deleted file mode 100644
index 0a4476b..0000000
--- a/web/templates/autohandler
+++ /dev/null
@@ -1,25 +0,0 @@
-<%init>
-Jifty->web->handle_request();
-
-if ($m->base_comp->path =~ m|/_elements/|) {
- # Requesting an internal component by hand -- naughty
- $m->redirect("/errors/requested_private_component");
-#} elsif (not Jifty->web->current_user->id and $m->request_comp->path !~ m{^/(?:welcome|dhandler|css|js|images|validator\.xml)} ) {
-# # Not logged in, trying to access a protected page
-# $m->notes->{'login-nextpage'} = $m->{top_path};
-# Jifty->web->redirect('/welcome/');
-}
-</%init>
-<%$m->call_next()%>
-<%def .setup_actions>
-<%init>
-Jifty->web->allow_actions(qr/.*/);
-# this method turns around and calls the setup_actions method
-# it's called by Jifty::Web->setup_page_actions.
-my $delegate = $m->fetch_comp($m->next_comp->path);
-if ($delegate and $delegate->method_exists('setup_actions')) {
- $delegate->call_method('setup_actions');
-}
-
-</%init>
-</%def>
diff --git a/web/templates/dhandler b/web/templates/dhandler
deleted file mode 100644
index 8a2fe56..0000000
--- a/web/templates/dhandler
+++ /dev/null
@@ -1,47 +0,0 @@
-<&| /_elements/wrapper, title => "Something's not quite right" &>
-
-<div id="overview">
-
-<p>You got to a page that we don't think exists. Anyway, the software has logged this error. Sorry about this.</p>
-
-<p><%Jifty->web->link( url => "/", label => 'Go back home...')%></p>
-
-</div>
-</&>
-%# XXX TODO ACTUALLY LOG THIS.
-<%doc>
-Used as a poor man's 404 handler
-</%doc>
-<%init>
-
-# This code loads up any static file and displays it if it would 404 from dynamic content. Failing that, actually 404
-my $file = $m->dhandler_arg;
-my $type = "application/octet-stream";
-if ( $file =~ /\.(gif|png|jpe?g)$/i ) {
- $type = "image/$1";
- $type =~ s/jpg/jpeg/gi;
-} elsif ($file =~ /\.css$/i ) {
- $type ='text/css';
-} elsif ($file =~ /\.js$/i) {
- $type = 'application/x-javascript';
-}
-my $image = Jifty::Util->absolute_path( Jifty->config->framework('Web')->{'StaticRoot'}
- || "static" )
- . "/"
- . $file;
-
-if ( ( -f $image && -r $image ) ) {
- $r->header_out( 'Cache-Control' => 'max-age=3600, must-revalidate' );
- $r->content_type($type);
- open( FILE, "<$image" ) || die;
- {
- local $/ = \16384;
- $m->out($_) while (<FILE>);
- close(FILE);
- }
- $m->abort;
-}
-
-Jifty->log->error("404: user tried to get to ".$m->dhandler_arg);
-$r->header_out( Status => '404');
-</%init>
diff --git a/web/templates/edit/dhandler b/web/templates/edit/dhandler
index 4a5240d..7eff1f2 100644
--- a/web/templates/edit/dhandler
+++ b/web/templates/edit/dhandler
@@ -4,13 +4,18 @@ my $page = Wifty::Model::Page->new();
$page->load_by_cols( name => $name );
my $viewer = Jifty->web->new_action( class => 'UpdatePage', record => $page );
my $top = Jifty->web->navigation;
-$top->child(Show => url => '/view/'.$page->name,, label => 'Show Page', sort_order => 5);
+$top->child(
+ Show =>
+ url => '/view/' . $page->name,
+ label => 'Show Page',
+ sort_order => 5
+);
</%init>
<&|/_elements/wrapper, title => 'Edit: '.$page->name &>
<% Jifty->web->form->start %>
<% Jifty->web->form->next_page( url => '/view/'.$page->name) %>
<% $viewer->form_field('content') %>
<% Jifty->web->form->submit( label => 'Save') %>
-<% Jifty->web->form->end%>
+<% Jifty->web->form->end %>
<& /_elements/markup &>
</&>
diff --git a/web/templates/favicon.ico b/web/templates/favicon.ico
deleted file mode 100644
index e69de29..0000000
diff --git a/web/templates/pages b/web/templates/pages
index 35f05bc..aeddbd4 100644
--- a/web/templates/pages
+++ b/web/templates/pages
@@ -1,12 +1,17 @@
<%init>
my $pages = Wifty::Model::PageCollection->new();
$pages->unlimit();
-
</%init>
<&|/_elements/wrapper, title => 'These are the pages on your wiki!' &>
<ul id="pagelist">
% while (my $page = $pages->next) {
-<li><% Jifty->web->link( label => $page->name, url => '/view/'.$page->name)%></li>
+<li><%
+ Jifty->web->link(
+ label => $page->name,
+ url => '/view/' . $page->name
+ );
+
+ %></li>
% }
</ul>
</&>
diff --git a/web/templates/recent b/web/templates/recent
index 433fb35..d002c6f 100644
--- a/web/templates/recent
+++ b/web/templates/recent
@@ -1,8 +1,8 @@
<%init>
-my $then = DateTime->from_epoch(epoch => (time - (86400*7)));
+my $then = DateTime->from_epoch( epoch => ( time - ( 86400 * 7 ) ) );
my $pages = Wifty::Model::PageCollection->new();
$pages->limit( column => 'updated', operator => '>', value => $then->ymd );
-$pages->order_by( column => 'updated', order => 'desc');
+$pages->order_by( column => 'updated', order => 'desc' );
</%init>
<&|/_elements/wrapper, title => 'Updated this week' &>
<dl id="recentudates">
diff --git a/web/templates/view/dhandler b/web/templates/view/dhandler
index 63c0f18..242ca75 100644
--- a/web/templates/view/dhandler
+++ b/web/templates/view/dhandler
@@ -1,13 +1,12 @@
<%init>
my $name = $m->dhandler_arg();
my $page = Wifty::Model::Page->new();
-$page->load_by_cols( name => $name);
-unless ($page->id) {
- Jifty->web->redirect( '/create/'.$name);
- # XXX TODO: should this use goto or gosub or whatever we're calling it this week?
+$page->load_by_cols( name => $name );
+unless ( $page->id ) {
+ Jifty->web->redirect( '/create/' . $name );
}
my $top = Jifty->web->navigation;
-$top->child(Edit => url => '/edit/'.$page->name , sort_order => 5);
+$top->child( Edit => url => '/edit/' . $page->name, sort_order => 5 );
</%init>
<&|/_elements/wrapper, title => $page->name &>
<% $page->wiki_content |n %>
commit 0057bb6bd47c20f5a06d01411b919706ee4d3965
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Sun Dec 25 01:31:33 2005 +0000
Page history!
diff --git a/lib/Wifty/Model/Page.pm b/lib/Wifty/Model/Page.pm
index 6af8ae8..996f439 100644
--- a/lib/Wifty/Model/Page.pm
+++ b/lib/Wifty/Model/Page.pm
@@ -16,16 +16,25 @@ column updated =>
since '0.0.6';
-#column revisions => refers_to Wifty::Model::Revision by 'page';
+column revisions =>
+ refers_to Wifty::Model::RevisionCollection by 'page';
package Wifty::Model::Page;
use base qw/Wifty::Record/;
+use Wifty::Model::RevisionCollection;
use Text::Markdown;
use HTML::Scrubber;
+=head2 wiki_content [CONTENT]
+
+Wikify either the content of a scalar passed in as an argument or
+this page's "content" attribute.
+
+=cut
+
sub wiki_content {
my $self = shift;
- my $content = $self->content();
+ my $content = shift ||$self->content();
my $scrubber = HTML::Scrubber->new();
$scrubber->default(
@@ -46,7 +55,7 @@ sub wiki_content {
$scrubber->allow(
qw[A B U P BR I HR BR SMALL EM FONT SPAN DIV UL OL LI DL DT DD]);
$scrubber->comment(0);
- return ( markdown( $scrubber->scrub( $self->content ) ) );
+ return ( markdown( $scrubber->scrub( $content ) ) );
}
diff --git a/web/templates/_elements/page_nav b/web/templates/_elements/page_nav
new file mode 100644
index 0000000..8840b35
--- /dev/null
+++ b/web/templates/_elements/page_nav
@@ -0,0 +1,30 @@
+<%init>
+my $subpath = $page . ($rev ? "/rev/$rev" : '');
+my $top = Jifty->web->navigation;
+my $this = $top->child(
+ This =>
+ url => "/view/".$subpath,
+ label => $page,
+ sort_order => 5
+);
+
+$this->child(
+ View =>
+ url => '/view/'.$subpath
+);
+
+$this->child(
+ Edit =>
+ url => '/edit/'.$subpath
+);
+
+$this->child(
+ History =>
+ url => '/history/'.$subpath
+ );
+
+</%init>
+<%args>
+$page => 'HomePage'
+$rev => undef
+</%args>
diff --git a/web/templates/edit/dhandler b/web/templates/edit/dhandler
index 7eff1f2..08d463f 100644
--- a/web/templates/edit/dhandler
+++ b/web/templates/edit/dhandler
@@ -1,20 +1,30 @@
<%init>
-my $name = $m->dhandler_arg();
+my $arg = $m->dhandler_arg();
+my ($name,$rev);
+if ($arg =~ qr{^(.*?)/?(\d?)$}) {
+ $name = $1;
+ $rev = $2;
+}
my $page = Wifty::Model::Page->new();
$page->load_by_cols( name => $name );
+
+my $revision = Wifty::Model::Revision->new();
+if ($rev) {
+$revision->load_by_cols( page => $page->id, id=> $rev);
+}
+
my $viewer = Jifty->web->new_action( class => 'UpdatePage', record => $page );
-my $top = Jifty->web->navigation;
-$top->child(
- Show =>
- url => '/view/' . $page->name,
- label => 'Show Page',
- sort_order => 5
-);
+$m->comp('/_elements/page_nav', page => $page->name, rev => $rev);
+
</%init>
<&|/_elements/wrapper, title => 'Edit: '.$page->name &>
<% Jifty->web->form->start %>
<% Jifty->web->form->next_page( url => '/view/'.$page->name) %>
+% if ($revision->id) {
+<% $viewer->form_field('content', default_value => $revision->content )%>
+% } else {
<% $viewer->form_field('content') %>
+% }
<% Jifty->web->form->submit( label => 'Save') %>
<% Jifty->web->form->end %>
<& /_elements/markup &>
diff --git a/web/templates/history/dhandler b/web/templates/history/dhandler
new file mode 100755
index 0000000..a8c8aa3
--- /dev/null
+++ b/web/templates/history/dhandler
@@ -0,0 +1,20 @@
+<%init>
+my $name = $m->dhandler_arg();
+my $page = Wifty::Model::Page->new();
+$page->load_by_cols( name => $name );
+Jifty->web->redirect( '/create/' . $name ) unless ( $page->id );
+
+my $revisions = $page->revisions;
+$revisions->order_by( column => 'id', order => 'desc');
+$m->comp('/_elements/page_nav', page => $page->name);
+</%init>
+<&|/_elements/wrapper, title => $revisions->count ." revisions of " .$page->name &>
+<ul>
+% while (my $rev = $revisions->next) {
+<dt><% Jifty->web->link( label => $rev->created,
+ url => '/view/'.$page->name.'/'.$rev->id
+ ) %></dt>
+<dd><%length($rev->content)%> bytes</dd>
+% }
+</ul>
+</&>
diff --git a/web/templates/view/dhandler b/web/templates/view/dhandler
index 242ca75..d6a528f 100644
--- a/web/templates/view/dhandler
+++ b/web/templates/view/dhandler
@@ -1,13 +1,28 @@
<%init>
-my $name = $m->dhandler_arg();
+my $arg = $m->dhandler_arg();
+my ($name,$rev);
+if ($arg =~ qr{^(.*?)/?(\d?)$}) {
+ $name = $1;
+ $rev = $2;
+}
my $page = Wifty::Model::Page->new();
$page->load_by_cols( name => $name );
+
+my $revision = Wifty::Model::Revision->new();
+if ($rev) {
+ $revision->load_by_cols( page => $page->id, id => $rev);
+}
+
unless ( $page->id ) {
Jifty->web->redirect( '/create/' . $name );
}
-my $top = Jifty->web->navigation;
-$top->child( Edit => url => '/edit/' . $page->name, sort_order => 5 );
+
+$m->comp('/_elements/page_nav', page => $page->name, rev => $rev);
</%init>
<&|/_elements/wrapper, title => $page->name &>
+% if ($revision->id) {
+<% $page->wiki_content($revision->content) |n%>
+% } else {
<% $page->wiki_content |n %>
+% }
</&>
commit 169b04efaac5e3432d9970961e1662d5a4bfecbb
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Sun Dec 25 01:31:48 2005 +0000
oop. regex
diff --git a/web/templates/edit/dhandler b/web/templates/edit/dhandler
index 08d463f..e378789 100644
--- a/web/templates/edit/dhandler
+++ b/web/templates/edit/dhandler
@@ -1,7 +1,7 @@
<%init>
my $arg = $m->dhandler_arg();
my ($name,$rev);
-if ($arg =~ qr{^(.*?)/?(\d?)$}) {
+if ($arg =~ qr{^(.*?)/?(\d*?)$}) {
$name = $1;
$rev = $2;
}
diff --git a/web/templates/view/dhandler b/web/templates/view/dhandler
index d6a528f..8394e2b 100644
--- a/web/templates/view/dhandler
+++ b/web/templates/view/dhandler
@@ -1,7 +1,7 @@
<%init>
my $arg = $m->dhandler_arg();
my ($name,$rev);
-if ($arg =~ qr{^(.*?)/?(\d?)$}) {
+if ($arg =~ qr{^(.*?)/?(\d*?)$}) {
$name = $1;
$rev = $2;
}
commit 2590de549d7272d5c7d72c0f0653910f9e284923
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Sun Dec 25 01:32:01 2005 +0000
nav
diff --git a/web/templates/_elements/page_nav b/web/templates/_elements/page_nav
index 8840b35..ecdc2b4 100644
--- a/web/templates/_elements/page_nav
+++ b/web/templates/_elements/page_nav
@@ -1,5 +1,5 @@
<%init>
-my $subpath = $page . ($rev ? "/rev/$rev" : '');
+my $subpath = $page . ($rev ? "/$rev" : '');
my $top = Jifty->web->navigation;
my $this = $top->child(
This =>
@@ -20,7 +20,7 @@ $this->child(
$this->child(
History =>
- url => '/history/'.$subpath
+ url => '/history/'.$page
);
</%init>
commit 0f0c11709236c0471f31dc09d76d17f1f19948d9
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Sun Dec 25 01:32:15 2005 +0000
as of date
diff --git a/web/templates/edit/dhandler b/web/templates/edit/dhandler
index e378789..133554f 100644
--- a/web/templates/edit/dhandler
+++ b/web/templates/edit/dhandler
@@ -17,7 +17,7 @@ my $viewer = Jifty->web->new_action( class => 'UpdatePage', record => $page );
$m->comp('/_elements/page_nav', page => $page->name, rev => $rev);
</%init>
-<&|/_elements/wrapper, title => 'Edit: '.$page->name &>
+<&|/_elements/wrapper, title => 'Edit: '.$page->name . ($revision->id ? " as of ".$revision->date : '') &>
<% Jifty->web->form->start %>
<% Jifty->web->form->next_page( url => '/view/'.$page->name) %>
% if ($revision->id) {
diff --git a/web/templates/view/dhandler b/web/templates/view/dhandler
index 8394e2b..0f6c6fe 100644
--- a/web/templates/view/dhandler
+++ b/web/templates/view/dhandler
@@ -19,7 +19,7 @@ unless ( $page->id ) {
$m->comp('/_elements/page_nav', page => $page->name, rev => $rev);
</%init>
-<&|/_elements/wrapper, title => $page->name &>
+<&|/_elements/wrapper, title => $page->name . ($revision->id ? " as of ".$revision->date : '') &>
% if ($revision->id) {
<% $page->wiki_content($revision->content) |n%>
% } else {
commit 9b5181b4c098f2efcc2427f98056a87c9cc96fac
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Sun Dec 25 01:32:27 2005 +0000
as of date
diff --git a/web/templates/edit/dhandler b/web/templates/edit/dhandler
index 133554f..53b818d 100644
--- a/web/templates/edit/dhandler
+++ b/web/templates/edit/dhandler
@@ -17,7 +17,7 @@ my $viewer = Jifty->web->new_action( class => 'UpdatePage', record => $page );
$m->comp('/_elements/page_nav', page => $page->name, rev => $rev);
</%init>
-<&|/_elements/wrapper, title => 'Edit: '.$page->name . ($revision->id ? " as of ".$revision->date : '') &>
+<&|/_elements/wrapper, title => 'Edit: '.$page->name . ($revision->id ? " as of ".$revision->created : '') &>
<% Jifty->web->form->start %>
<% Jifty->web->form->next_page( url => '/view/'.$page->name) %>
% if ($revision->id) {
diff --git a/web/templates/view/dhandler b/web/templates/view/dhandler
index 0f6c6fe..088d45b 100644
--- a/web/templates/view/dhandler
+++ b/web/templates/view/dhandler
@@ -19,7 +19,7 @@ unless ( $page->id ) {
$m->comp('/_elements/page_nav', page => $page->name, rev => $rev);
</%init>
-<&|/_elements/wrapper, title => $page->name . ($revision->id ? " as of ".$revision->date : '') &>
+<&|/_elements/wrapper, title => $page->name . ($revision->id ? " as of ".$revision->created : '') &>
% if ($revision->id) {
<% $page->wiki_content($revision->content) |n%>
% } else {
commit 45db201a4be3ae528cb969d2830fb27fbdf57fef
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Sun Dec 25 01:32:40 2005 +0000
goto latest
diff --git a/web/templates/_elements/page_nav b/web/templates/_elements/page_nav
index ecdc2b4..61993fd 100644
--- a/web/templates/_elements/page_nav
+++ b/web/templates/_elements/page_nav
@@ -8,20 +8,11 @@ my $this = $top->child(
sort_order => 5
);
-$this->child(
- View =>
- url => '/view/'.$subpath
-);
-
-$this->child(
- Edit =>
- url => '/edit/'.$subpath
-);
-$this->child(
- History =>
- url => '/history/'.$page
- );
+$this->child( View => url => '/view/'.$subpath);
+$this->child( Edit => url => '/edit/'.$subpath);
+$this->child( History => url => '/history/'.$page);
+$this->child( Latest => url => '/view/'.$page) if ($rev);
</%init>
<%args>
commit 2c864ec5e9b5181aff6bb0245086c89ba2d0a4f1
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Sun Dec 25 01:32:54 2005 +0000
move around the header styles
diff --git a/web/static/css/app-base.css b/web/static/css/app-base.css
index df6ab06..49800c3 100644
--- a/web/static/css/app-base.css
+++ b/web/static/css/app-base.css
@@ -4,7 +4,7 @@ body {
}
-h1 {
+div#headers h1 {
background: green;
color: white;
padding: 0.2em;
commit fc17cfa282b8e70c3f7adfa1175a5926ceeb0f72
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Sun Dec 25 01:33:08 2005 +0000
updated wifty model class to include password support needed by new currentuser stuff
diff --git a/lib/Wifty/Action/Login.pm b/lib/Wifty/Action/Login.pm
new file mode 100644
index 0000000..2d5e4f8
--- /dev/null
+++ b/lib/Wifty/Action/Login.pm
@@ -0,0 +1,94 @@
+use warnings;
+use strict;
+
+=head1 NAME
+
+Wifty::Action::Login
+
+=cut
+
+package Wifty::Action::Login;
+use base qw/Wifty::Action Jifty::Action/;
+
+=head2 arguments
+
+Return the email and password form fields
+
+=cut
+
+sub arguments {
+ return( { email => { label => 'Email address',
+ mandatory => 1,
+ ajax_validates => 1,
+ } ,
+
+ password => { type => 'password',
+ label => 'Password',
+ mandatory => 1
+ },
+ remember => { type => 'checkbox',
+ label => 'Remember me?',
+ hints => 'If you want, your browser can remember your login for you',
+ default => 0,
+ }
+ });
+
+}
+
+=head2 validate_email ADDRESS
+
+Makes sure that the email submitted is a legal email address and that there's a user in the database with it.
+
+
+=cut
+
+sub validate_email {
+ my $self = shift;
+ my $email = shift;
+
+ unless ( $email =~ /\S\@\S/ ) {
+ return $self->validation_error(email => "That doesn't look like an email address." );
+ }
+
+ my $u = Wifty::Model::User->new(current_user => Wifty::CurrentUser->superuser);
+ $u->load_by_cols( email => $email );
+ return $self->validation_error(email => 'No account has that email address.') unless ($u->id);
+
+
+ return $self->validation_ok('email');
+}
+
+=head2 take_action
+
+Actually check the user's password. If it's right, log them in.
+Otherwise, throw an error.
+
+
+=cut
+
+sub take_action {
+ my $self = shift;
+ my $user = Wifty::CurrentUser->new( email => $self->argument_value('email'));
+
+ unless ( $user->id && $user->password_is($self->argument_value('password'))) {
+ $self->result->error( 'You may have mistyped your email address or password. Give it another shot?' );
+ return;
+ }
+
+ unless ($user->user_object->email_confirmed) {
+ $self->result->error( q{You haven't <a href="/welcome/confirm.html">confirmed your account</a> yet.} );
+ return;
+ }
+
+ # Set up our login message
+ $self->result->message("Welcome back, " . $user->user_object->name . "." );
+
+ # Actually do the signin thing.
+ Jifty->web->current_user($user);
+ Jifty->web->session->expires($self->argument_value('remember') ? '+1y' : undef);
+ Jifty->web->session->set_cookie;
+
+ return 1;
+}
+
+1;
diff --git a/lib/Wifty/Model/User.pm b/lib/Wifty/Model/User.pm
index 61cd92a..93d1651 100644
--- a/lib/Wifty/Model/User.pm
+++ b/lib/Wifty/Model/User.pm
@@ -15,6 +15,9 @@ column password =>,
type is 'text',
render_as 'password';
+column email_confirmed =>
+ type is 'boolean',
+ since '0.0.10';
package Wifty::Model::User;
@@ -29,4 +32,30 @@ sub create {
return($id);
}
+
+=head2 password_is STRING
+
+Returns true if and only if the current user's password matches STRING
+
+=cut
+
+
+sub password_is {
+ my $self = shift;
+ my $string = shift;
+ warn "Checking $string";
+ return 1 if ($self->_value('password') eq $string);
+ return 0;
+}
+
+=head2 password
+
+Never display a password
+
+=cut
+
+sub password {
+ return undef;
+
+}
1;
commit 2729ccbefb26575356e483196c33c2a98cb95a2d
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Sun Dec 25 01:33:21 2005 +0000
Wifty::CurrentUser
diff --git a/lib/Wifty/CurrentUser.pm b/lib/Wifty/CurrentUser.pm
new file mode 100755
index 0000000..78e50fa
--- /dev/null
+++ b/lib/Wifty/CurrentUser.pm
@@ -0,0 +1,38 @@
+use warnings;
+use strict;
+
+
+package Wifty::CurrentUser;
+
+use base qw(Jifty::CurrentUser);
+
+=head2 new PARAMHASH
+
+Instantiate a new current user object, loading the user by paramhash:
+
+ my $item = Wifty::Model::Item->new( Wifty::CurrentUser->new(email => 'user at site'));
+
+if you give the param
+ _bootstrap => 1
+
+your object will be marked as a bootstrap user. You can use that to do an endrun around acls.
+
+=cut
+
+
+
+sub _init {
+ my $self = shift;
+ my %args = (@_);
+
+ if (delete $args{'_bootstrap'} ) {
+ $self->is_bootstrap_user(1);
+ } elsif (keys %args) {
+ $self->user_object(Wifty::Model::User->new(current_user => $self));
+ $self->user_object->load_by_cols(%args);
+ }
+ $self->SUPER::_init(%args);
+}
+
+
+1;
commit 832077c5f3ff99f91eb7027712d7b50c4e8e9ed7
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Sun Dec 25 01:33:36 2005 +0000
login page
diff --git a/etc/config.yml b/etc/config.yml
index ba02778..b821631 100644
--- a/etc/config.yml
+++ b/etc/config.yml
@@ -1,11 +1,11 @@
framework:
- AdminMode: 0
+ AdminMode: 1
LogConfig: etc/btdt.log4perl.conf
Database:
Driver: Pg
Host: localhost
User: postgres
- Version: 0.0.8
+ Version: 0.0.10
Password: ''
RequireSSL: 0
# Mailer: IO
diff --git a/web/templates/_elements/nav b/web/templates/_elements/nav
index 5baff86..e89df55 100644
--- a/web/templates/_elements/nav
+++ b/web/templates/_elements/nav
@@ -1,16 +1,11 @@
<%init>
my $top = Jifty->web->navigation;
-$top->child(
- Home =>
- url => "/",
- sort_order => 1
-);
-$top->child(
- Recent =>
- url => "/recent",
- label => "Recent Changes",
- sort_order => 2
-);
+$top->child( Home => url => "/", sort_order => 1 );
+$top->child( Recent => url => "/recent", label => "Recent Changes", sort_order => 2);
+ if (Jifty->config->framework('AdminMode') ) {
+ $top->child(Administration => url => "/__jifty/admin/", sort_order => 998);
+ $top->child(OnlineDocs => url => "/__jifty/online_docs/", label => 'Online docs', sort_order => 999);
+}
return();
</%init>
diff --git a/web/templates/login b/web/templates/login
new file mode 100755
index 0000000..e29d3f8
--- /dev/null
+++ b/web/templates/login
@@ -0,0 +1,18 @@
+<%init>
+if (Jifty->web->current_user->id) {
+ $m->comp('/_elements/logged_in_already');
+ return();
+}
+my $action = Jifty->web->new_action(class => 'Login', moniker => 'loginbox' );
+
+my $next = Jifty->web->request->continuation || Jifty::Continuation->new(request => Jifty::Request->new(path => "/"));
+</%init>
+<&|/_elements/wrapper, title => 'Login' &>
+<h2>Login</h2>
+<% Jifty->web->form->start(call => $next, name => "loginbox") %>
+<% $action->form_field('email') %>
+<% $action->form_field('password') %>
+<% $action->form_field('remember') %>
+<% Jifty->web->form->submit(label => 'Login', submit => $action) %>
+<% Jifty->web->form->end %>
+</&>
commit 3b0de9a62c20b24b77618b15144266009f55a64b
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Sun Dec 25 01:33:51 2005 +0000
Fixed some of the worst style errors
diff --git a/web/static/css/app-base.css b/web/static/css/app-base.css
index 49800c3..638d94b 100644
--- a/web/static/css/app-base.css
+++ b/web/static/css/app-base.css
@@ -24,50 +24,11 @@ a {
display: none;
}
-div#menu {
- background: green;
-
-}
-
-div#sidebar {
- float: left;
-
-
-}
-
div#content {
background: #ffffff;
padding: 2em;
}
-
-ul.menu {
- display: block;
- border-bottom: 2px solid black;
- padding-bottom: 4px;
- width: 100%;
- margin-left: 0px;
- margin-right: 0px;
-
-}
-
-ul.menu li {
-
- background: #770077;
- color:#ffffff;
- text-align: center;
- display: inline;
- padding: 0.4em 0.4em 4px 0.4em;
- border-bottom: 2px solid black;
-;
-
-}
-
-ul.menu li a {
- color: #ffffff;
-
-}
-
div#salutation {
float: right;
font-style: italic;
@@ -80,16 +41,10 @@ textarea.content {
padding: 5px;
}
-label {
- position: absolute;
- top: 3.4em;
- left: 1em;
- font-size: 2em;
-}
input[type=submit] {
border: 1px solid black;
- font-size: 2em;
+ font-size: 1.5em;
margin: 5px;
}
commit 258a05a50d19f932f9b21426866957509b482416
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Sun Dec 25 01:34:03 2005 +0000
./lib/Jifty/DefaultApp moved to ./share
* Added magic symlink to ./lib/Jifty/auto/Jifty to make share dir
accessible during dev work
* Re-spec application root dir
diff --git a/bin/jifty b/bin/jifty
index 0ba9b77..1e40ae7 100755
--- a/bin/jifty
+++ b/bin/jifty
@@ -5,9 +5,9 @@ use File::Basename qw(dirname);
BEGIN {
my $dir = dirname(__FILE__);
- push @INC, "$dir/../lib";
+ unshift @INC, "$dir/../../Jifty/lib";
+ unshift @INC, "$dir/../lib";
push @INC, "$dir/../../Jifty/deps";
- push @INC, "$dir/../../Jifty/lib";
}
use Jifty::Script;
commit 7eb8241953bf8fc723c589f498e677189e0651e8
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Sun Dec 25 01:34:16 2005 +0000
ACL cleanups. HM should now be acled correctly
diff --git a/lib/Wifty/Model/Revision.pm b/lib/Wifty/Model/Revision.pm
index bd5344e..b1fc3a6 100644
--- a/lib/Wifty/Model/Revision.pm
+++ b/lib/Wifty/Model/Revision.pm
@@ -13,7 +13,7 @@ column created =>
package Wifty::Model::Revision;
use base qw/Wifty::Record/;
-
+use Jifty::RightsFrom column => 'page';
use DateTime;
commit 273f72ddec35508bbe9a7564c5ee9b304eb3c3ed
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Sun Dec 25 01:34:30 2005 +0000
By default, Jifty applications let superuser and bootstrap users do aaaaaaanything.
And regular users can't do anything.
diff --git a/lib/Wifty/Model/User.pm b/lib/Wifty/Model/User.pm
index 93d1651..af89ea5 100644
--- a/lib/Wifty/Model/User.pm
+++ b/lib/Wifty/Model/User.pm
@@ -43,7 +43,6 @@ Returns true if and only if the current user's password matches STRING
sub password_is {
my $self = shift;
my $string = shift;
- warn "Checking $string";
return 1 if ($self->_value('password') eq $string);
return 0;
}
@@ -58,4 +57,21 @@ sub password {
return undef;
}
+
+
+sub current_user_can {
+ my $self = shift;
+ my $right = shift;
+ my %args = (@_);
+
+ if ($right eq 'read') {
+
+ } elsif ($right eq 'write') {
+
+ }
+
+ return $self->SUPER::current_user_can($right, %args);
+}
+
+
1;
commit bbac78c301525b206e2fac8c12691297f4aa31ad
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Sun Dec 25 01:34:43 2005 +0000
Remove Jifty::Script::Command (pushed into App::CLI::Command)
* Make Jifty::Script::App build basic framework
* Extract most of Jifty::Script::Help into App::CLI::Command::Help
* Allow --port option to Jifty::Script::Server
diff --git a/lib/Wifty/Model/Page.pm b/lib/Wifty/Model/Page.pm
index 996f439..88d3002 100644
--- a/lib/Wifty/Model/Page.pm
+++ b/lib/Wifty/Model/Page.pm
@@ -25,6 +25,7 @@ use Wifty::Model::RevisionCollection;
use Text::Markdown;
use HTML::Scrubber;
+
=head2 wiki_content [CONTENT]
Wikify either the content of a scalar passed in as an argument or
@@ -55,7 +56,7 @@ sub wiki_content {
$scrubber->allow(
qw[A B U P BR I HR BR SMALL EM FONT SPAN DIV UL OL LI DL DT DD]);
$scrubber->comment(0);
- return ( markdown( $scrubber->scrub( $content ) ) );
+ return ( $scrubber->scrub( markdown( $content ) ) );
}
diff --git a/web/templates/view/dhandler b/web/templates/view/dhandler
index 088d45b..5ce5e8b 100644
--- a/web/templates/view/dhandler
+++ b/web/templates/view/dhandler
@@ -1,4 +1,5 @@
<%init>
+use Wifty::Model::Page;
my $arg = $m->dhandler_arg();
my ($name,$rev);
if ($arg =~ qr{^(.*?)/?(\d*?)$}) {
commit 31a443aeb7b908bab232e9e6ef6b34fa150cbe87
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Sun Dec 25 01:34:57 2005 +0000
Wifty does acls now
diff --git a/lib/Wifty/Bootstrap.pm b/lib/Wifty/Bootstrap.pm
index 5026527..4f95051 100644
--- a/lib/Wifty/Bootstrap.pm
+++ b/lib/Wifty/Bootstrap.pm
@@ -5,7 +5,7 @@ use Wifty::Model::Page;
sub run {
my $self = shift;
- my $index = Wifty::Model::Page->new();
+ my $index = Wifty::Model::Page->new( current_user => Wifty::CurrentUser->superuser () );
$index->create(
name => 'HomePage',
content => 'Welcome to your Wifty'
commit 72653728486cbb96520571f5d4447f92ed31cf9a
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Sun Dec 25 01:35:13 2005 +0000
First draft. we _clearly_ need to refactor out the email validator, if nothing else.
diff --git a/lib/Wifty/Action/ConfirmAccount.pm b/lib/Wifty/Action/ConfirmAccount.pm
new file mode 100644
index 0000000..0498579
--- /dev/null
+++ b/lib/Wifty/Action/ConfirmAccount.pm
@@ -0,0 +1,62 @@
+use warnings;
+use strict;
+
+=head1 NAME
+
+Wifty::Action::ConfirmEmail - Confirm a user's email address
+
+=head1 DESCRIPTION
+
+This is the link in a user's email to confirm that their email
+email is really theirs. It is not really meant to be rendered on any
+web page, but is used by the confirmation notification.
+
+Note that the use of C<insecure_url_auth_token> here is insecure and wrong!
+(XXX TODO FIXME) If an attacker knew the token calculation algorithm (including
+the non-random salt), they could easily do email confirmation without needed
+to actually have access to the email account, since the algorithm only depends on
+the email address, requested password, and non-random salt.
+
+=cut
+
+package Wifty::Action::ConfirmEmail;
+use base qw/Wifty::Action Jifty::Action/;
+
+use Wifty::Model::User;
+
+
+=head2 actions
+
+A null sub, because the superclass wants to make sure we fill in actions
+
+=cut
+
+sub actions {}
+
+=head2 take_action
+
+Set their confirmed status.
+
+=cut
+
+sub take_action {
+ my $self = shift;
+ my $u = Wifty::Model::User->new(current_user => Wifty::CurrentUser->superuser);
+ $u->load_by_cols( email => Jifty->web->current_user->user_object->email );
+
+ if ($u->email_confirmed) {
+ $self->result->error(email => "You have already confirmed your account.");
+ $self->result->success(1); # but the action is still a success
+ }
+
+ $u->set_email_confirmed('1');
+
+ # Set up our login message
+ $self->result->message( "Welcome to Wifty, " . $u->name . ". Your email address has now been confirmed." );
+
+ # Actually do the login thing.
+ Jifty->web->current_user(Wifty::CurrentUser->new(id => $u->id));
+ return 1;
+}
+
+1;
diff --git a/lib/Wifty/Action/RecoverPassword.pm b/lib/Wifty/Action/RecoverPassword.pm
new file mode 100644
index 0000000..7f6f0c3
--- /dev/null
+++ b/lib/Wifty/Action/RecoverPassword.pm
@@ -0,0 +1,7 @@
+
+use warnings;
+use strict;
+
+package Wifty::Action::RecoverPassword;
+
+1;
diff --git a/lib/Wifty/Action/ResetLostPassword.pm b/lib/Wifty/Action/ResetLostPassword.pm
new file mode 100755
index 0000000..813a781
--- /dev/null
+++ b/lib/Wifty/Action/ResetLostPassword.pm
@@ -0,0 +1,70 @@
+use warnings;
+use strict;
+
+=head1 NAME
+
+Wifty::Action::ResetPassword - Confirm and reset a lost password
+
+=head1 DESCRIPTION
+
+This is the action run by the link in a user's email to confirm that their email
+address is really theirs, when claiming that they lost their password.
+
+
+=cut
+
+package Wifty::Action::ResetPassword;
+use base qw/Wifty::Action Jifty::Action/;
+
+use Wifty::Model::User;
+
+=head2 arguments
+
+ConfirmEmail has the following fields: address, code, password, and password_confirm.
+Note that it can get the first two from the confirm dhandler.
+
+=cut
+
+sub arguments {
+ return( {
+ password => { type => 'password', sticky => 0 },
+ password_confirm => { type => 'password', sticky => 0, label => 'type your password again' },
+ });
+}
+
+=head2 take_action
+
+Resets the password.
+
+=cut
+
+sub take_action {
+ my $self = shift;
+ my $u = Wifty::Model::User->new(current_user => Wifty::CurrentUser->superuser);
+ $u->load_by_cols( email => Jifty->web->current_user->user_object->email );
+
+ unless ($u) {
+ $self->result->error( "You don't exist. I'm not sure how this happened. Really, really sorry. Please email us!");
+ }
+
+ my $pass = $self->argument_value('password');
+ my $pass_c = $self->argument_value('password_confirm');
+
+ # Trying to set a password (ie, submitted the form)
+ unless (defined $pass and defined $pass_c and length $pass and $pass eq $pass_c) {
+ $self->result->error("It looks like you didn't enter the same password into both boxes. Give it another shot?");
+ return;
+ }
+
+ unless ($u->set_password($pass)) {
+ $self->result->error("There was an error setting your password.");
+ return;
+ }
+ # Log in!
+ $self->result->message( "Your password has been reset. Welcome back." );
+ Jifty->web->current_user(Wifty::CurrentUser->new(id => $u->id));
+ return 1;
+
+}
+
+1;
diff --git a/lib/Wifty/Action/SendAccountConfrimation.pm b/lib/Wifty/Action/SendAccountConfrimation.pm
new file mode 100755
index 0000000..dc30667
--- /dev/null
+++ b/lib/Wifty/Action/SendAccountConfrimation.pm
@@ -0,0 +1,78 @@
+use warnings;
+use strict;
+
+=head1 NAME
+
+Wifty::Action::ResendConfirmation
+
+=cut
+
+package Wifty::Action::ResendConfirmation;
+use base qw/Wifty::Action Jifty::Action/;
+
+__PACKAGE__->mk_accessors(qw(user_object));
+
+use Wifty::Model::User;
+
+=head2 arguments
+
+The field for C<ResendConfirmation> is:
+
+=over 4
+
+=item address: the email address
+
+=back
+
+=cut
+
+sub arguments {
+ return ( { address => { label => 'email address', mandatory => 1, default_value => "", }, });
+}
+
+=head2 setup
+
+Create an empty user object to work with
+
+=cut
+
+sub setup {
+ my $self = shift;
+
+ $self->user_object(Wifty::Model::User->new(current_user => Wifty::CurrentUser->superuser));
+}
+
+=head2 validate_address
+
+Make sure their email address is an unconfirmed user.
+
+=cut
+
+sub validate_address {
+ my $self = shift;
+ my $email = shift;
+
+ return $self->validation_error(address => "That doesn't look like an email address." ) unless ( $email =~ /\S\@\S/ );
+
+ $self->user_object(Wifty::Model::User->new(current_user => Wifty::CurrentUser->superuser));
+ $self->user_object->load_by_cols( email => $email );
+ return $self->validation_error(address => "It doesn't look like there's an account by that name.") unless ($self->user_object->id);
+
+ return $self->validation_error(address => "It looks like you're already confirmed.") if ($self->user_object->email_confirmed);
+
+ return $self->validation_ok('address');
+}
+
+=head2 take_action
+
+Create a new unconfirmed user and send out a confirmation email.
+
+=cut
+
+sub take_action {
+ my $self = shift;
+ Wifty::Notification::ConfirmAddress->new( to => $self->user_object )->send;
+ return $self->result->message("Confirmation resent.");
+}
+
+1;
diff --git a/lib/Wifty/Action/SendPasswordReminder.pm b/lib/Wifty/Action/SendPasswordReminder.pm
new file mode 100755
index 0000000..dde794c
--- /dev/null
+++ b/lib/Wifty/Action/SendPasswordReminder.pm
@@ -0,0 +1,87 @@
+use warnings;
+use strict;
+
+=head1 NAME
+
+Wifty::Action::SendLostPasswordConfirmation
+
+=cut
+
+package Wifty::Action::SendLostPasswordConfirmation;
+use base qw/Wifty::Action Jifty::Action/;
+
+__PACKAGE__->mk_accessors(qw(user_object));
+
+use Wifty::Model::User;
+
+=head2 arguments
+
+The field for C<SendLostPasswordConfirmation> is:
+
+=over 4
+
+=item address: the email address
+
+=back
+
+=cut
+
+sub arguments {
+ return (
+ {
+ address => {
+ label => 'email address',
+ mandatory => 1,
+ },
+ }
+ );
+
+}
+
+=head2 setup
+
+Create an empty user object to work with
+
+=cut
+
+sub setup {
+ my $self = shift;
+
+ # Make a blank user object
+ $self->user_object(Wifty::Model::User->new(current_user => Wifty::CurrentUser->superuser));
+}
+
+=head2 validate_address
+
+Make sure there's actually an account by that name.
+
+=cut
+
+sub validate_address {
+ my $self = shift;
+ my $email = shift;
+
+ return $self->validation_error(address => "That doesn't look like an email address." )
+ unless ( $email =~ /\S\@\S/ )
+
+ $self->user_object(Wifty::Model::User->new(current_user => Wifty::CurrentUser->superuser));
+ $self->user_object->load_by_cols( email => $email );
+ return $self->validation_error(address => "It doesn't look like there's an account by that name.")
+ unless ($self->user_object->id);
+
+ return $self->validation_ok('address');
+}
+
+=head2 take_action
+
+Send out a confirmation email giving a link to a password-reset form.
+
+=cut
+
+sub take_action {
+ my $self = shift;
+ Wifty::Notification::ConfirmLostPassword->new( to => $self->user_object )->send;
+ return $self->result->message("A link to reset your password has been sent to your email account.");
+}
+
+1;
diff --git a/lib/Wifty/Action/Signup.pm b/lib/Wifty/Action/Signup.pm
new file mode 100644
index 0000000..1fc264c
--- /dev/null
+++ b/lib/Wifty/Action/Signup.pm
@@ -0,0 +1,111 @@
+use warnings;
+use strict;
+
+=head1 NAME
+
+Wifty::Action::Signup
+
+=cut
+
+package Wifty::Action::Signup;
+use Wifty::Action::CreateUser;
+use base qw/Wifty::Action::CreateUser/;
+
+
+use Wifty::Model::User;
+
+=head2 arguments
+
+
+The fields for C<Signup> are:
+
+=over 4
+
+=item email: the email address
+
+=item password and password_confirm: the requested password
+
+=item name: your full name
+
+=back
+
+=cut
+
+sub arguments {
+ my $self = shift;
+ my $args = $self->SUPER::arguments;
+
+ my %fields = (
+ email => 1,
+ likes_ticky_boxes => 1,
+ name => 1,
+ never_email => 1,
+ notification_email_frequency => 1,
+ password => 1,
+ password_confirm => 1,
+ );
+
+ for ( keys %$args ) { delete $args->{$_} unless ( $fields{$_} ); }
+ $args->{'email'}{'ajax_validates'} = 1;
+ $args->{'password_confirm'}{'label'} = "Type that again?";
+ return $args;
+}
+
+
+=head2 validate_email
+
+Make sure their email address looks sane
+
+=cut
+
+sub validate_email {
+ my $self = shift;
+ my $email = shift;
+
+ unless ( $email =~ /\S\@\S/ ) {
+ return $self->validation_error(email => "That doesn't look like an email address." );
+ }
+
+ my $u = Wifty::Model::User->new(current_user => Wifty::CurrentUser->superuser);
+ $u->load_by_cols( email => $email );
+ if ($u->id) {
+ return $self->validation_error(email => 'It looks like you already have an account. Perhaps you want to <a href="/welcome/">sign in</a> instead?');
+ }
+
+ return $self->validation_ok('email');
+}
+
+
+
+=head2 take_action
+
+Overrides the virtual C<take_action> method on L<Jifty::Action> to call
+the appropriate C<Jifty::Record>'s C<create> method when the action is
+run, thus creating a new object in the database.
+
+Makes sure that the user only specifies things we want them to.
+
+=cut
+
+sub take_action {
+ my $self = shift;
+ my $record = Wifty::Model::User->new(current_user => Wifty::CurrentUser->superuser);
+
+ my %values;
+ $values{$_} = $self->argument_value($_)
+ for grep { defined $self->record->column($_) and defined $self->argument_value($_) } $self->argument_names;
+
+ my ($id) = $record->create(%values);
+ # Handle errors?
+ unless ( $record->id ) {
+ $self->result->error("Something bad happened and we couldn't create your account. Try again later");
+ return;
+ }
+
+ $self->result->message( "Welcome to Wifty, " . $record->name .".");
+
+
+ return 1;
+}
+
+1;
commit c4a48ede445d81debfe3c637f76e71b8422fa782
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Sun Dec 25 01:35:27 2005 +0000
Fix compiler error in SendPasswordReminder
* Fix possible undef warning
* Very premissive ACLs
diff --git a/lib/Wifty/Action/SendPasswordReminder.pm b/lib/Wifty/Action/SendPasswordReminder.pm
index dde794c..4c643d6 100755
--- a/lib/Wifty/Action/SendPasswordReminder.pm
+++ b/lib/Wifty/Action/SendPasswordReminder.pm
@@ -61,8 +61,8 @@ sub validate_address {
my $self = shift;
my $email = shift;
- return $self->validation_error(address => "That doesn't look like an email address." )
- unless ( $email =~ /\S\@\S/ )
+ return $self->validation_error(address => "That doesn't look like an email address." )
+ unless ( $email =~ /\S\@\S/ );
$self->user_object(Wifty::Model::User->new(current_user => Wifty::CurrentUser->superuser));
$self->user_object->load_by_cols( email => $email );
diff --git a/lib/Wifty/Model/Page.pm b/lib/Wifty/Model/Page.pm
index 88d3002..2b7d02b 100644
--- a/lib/Wifty/Model/Page.pm
+++ b/lib/Wifty/Model/Page.pm
@@ -56,7 +56,7 @@ sub wiki_content {
$scrubber->allow(
qw[A B U P BR I HR BR SMALL EM FONT SPAN DIV UL OL LI DL DT DD]);
$scrubber->comment(0);
- return ( $scrubber->scrub( markdown( $content ) ) );
+ return ( $scrubber->scrub( markdown( $content || '') ) );
}
@@ -115,4 +115,19 @@ sub _set {
return ( $val, $msg );
}
+sub current_user_can {
+ my $self = shift;
+ my ($type) = @_;
+
+ # TODO FIXME: For now..
+ return 1;
+
+ # We probably want something like this eventually:
+ if ($type eq "read") {
+ return 1;
+ } else {
+ return $self->current_user->id;
+ }
+}
+
1;
commit ce6d4960ee06b679a8389439a7e1e82641aa62b5
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Sun Dec 25 01:35:40 2005 +0000
No, this order is still wrong, but less wrong than the other way.
I think. The problem is that scrubber escapes < and > once, and then
markdown escapes the &'s therein.
diff --git a/lib/Wifty/Model/Page.pm b/lib/Wifty/Model/Page.pm
index 2b7d02b..59e7384 100644
--- a/lib/Wifty/Model/Page.pm
+++ b/lib/Wifty/Model/Page.pm
@@ -56,7 +56,7 @@ sub wiki_content {
$scrubber->allow(
qw[A B U P BR I HR BR SMALL EM FONT SPAN DIV UL OL LI DL DT DD]);
$scrubber->comment(0);
- return ( $scrubber->scrub( markdown( $content || '') ) );
+ return ( markdown( $scrubber->scrub( $content || '') ) );
}
commit eed56064f973164948a159028cb24f4860d9f1be
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Sun Dec 25 01:35:51 2005 +0000
Only init the log4perl object once
* Updated deps in META.yaml
* Output new testing code for model classes
* Make generated Makefile.PL actually work
* SchemaTool only shows errors
diff --git a/etc/config.yml b/etc/config.yml
index b821631..a589501 100644
--- a/etc/config.yml
+++ b/etc/config.yml
@@ -1,6 +1,5 @@
framework:
AdminMode: 1
- LogConfig: etc/btdt.log4perl.conf
Database:
Driver: Pg
Host: localhost
commit 37e821d912bd81a11cfe0c38ad15ec3d0f64f621
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Sun Dec 25 01:36:04 2005 +0000
Makefile and tests
diff --git a/Makefile.PL b/Makefile.PL
new file mode 100644
index 0000000..89bf3d4
--- /dev/null
+++ b/Makefile.PL
@@ -0,0 +1,6 @@
+use inc::Module::Install;
+name('Wifty');
+version('0.01');
+requires('Jifty');
+
+WriteAll;
diff --git a/t/00-model-Page.t b/t/00-model-Page.t
new file mode 100644
index 0000000..617861e
--- /dev/null
+++ b/t/00-model-Page.t
@@ -0,0 +1,49 @@
+#!/usr/bin/perl -w
+use warnings;
+use strict;
+
+=head1 DESCRIPTION
+
+A basic test harness for the Page model.
+
+=cut
+
+use Jifty::Test tests => 11;
+
+# Make sure we can load the model
+use_ok('Wifty::Model::Page');
+
+# Grab a system use
+my $system_user = Wifty::CurrentUser->superuser;
+ok($system_user, "Found a system user");
+
+# Try testing a create
+my $o = Wifty::Model::Page->new(current_user => $system_user);
+my ($id) = $o->create(name => "Something");
+ok($id, "Page create returned success");
+ok($o->id, "New Page has valid id set");
+is($o->id, $id, "Create returned the right id");
+
+# And another
+$o->create(name => "Something else");
+ok($o->id, "Page create returned another value");
+isnt($o->id, $id, "And it is different from the previous one");
+
+# Searches in general
+my $collection = Wifty::Model::PageCollection->new(current_user => $system_user);
+$collection->unlimit;
+is($collection->count, 3, "Finds three records");
+
+# Searches in specific
+$collection->limit(column => 'id', value => $o->id);
+is($collection->count, 1, "Finds one record with specific id");
+
+# Delete one of them
+$o->delete;
+$collection->redo_search;
+is($collection->count, 0, "Deleted row is gone");
+
+# And the other one is still there
+$collection->unlimit;
+is($collection->count, 2, "Still two left");
+
diff --git a/t/00-model-Revision.t b/t/00-model-Revision.t
new file mode 100644
index 0000000..9da02b8
--- /dev/null
+++ b/t/00-model-Revision.t
@@ -0,0 +1,49 @@
+#!/usr/bin/perl -w
+use warnings;
+use strict;
+
+=head1 DESCRIPTION
+
+A basic test harness for the Revision model.
+
+=cut
+
+use Jifty::Test tests => 11;
+
+# Make sure we can load the model
+use_ok('Wifty::Model::Revision');
+
+# Grab a system use
+my $system_user = Wifty::CurrentUser->superuser;
+ok($system_user, "Found a system user");
+
+# Try testing a create
+my $o = Wifty::Model::Revision->new(current_user => $system_user);
+my ($id) = $o->create();
+ok($id, "Revision create returned success");
+ok($o->id, "New Revision has valid id set");
+is($o->id, $id, "Create returned the right id");
+
+# And another
+$o->create();
+ok($o->id, "Revision create returned another value");
+isnt($o->id, $id, "And it is different from the previous one");
+
+# Searches in general
+my $collection = Wifty::Model::RevisionCollection->new(current_user => $system_user);
+$collection->unlimit;
+is($collection->count, 3, "Finds three records");
+
+# Searches in specific
+$collection->limit(column => 'id', value => $o->id);
+is($collection->count, 1, "Finds one record with specific id");
+
+# Delete one of them
+$o->delete;
+$collection->redo_search;
+is($collection->count, 0, "Deleted row is gone");
+
+# And the other one is still there
+$collection->unlimit;
+is($collection->count, 2, "Still two left");
+
diff --git a/t/00-model-User.t b/t/00-model-User.t
new file mode 100644
index 0000000..06b838a
--- /dev/null
+++ b/t/00-model-User.t
@@ -0,0 +1,49 @@
+#!/usr/bin/perl -w
+use warnings;
+use strict;
+
+=head1 DESCRIPTION
+
+A basic test harness for the User model.
+
+=cut
+
+use Jifty::Test tests => 11;
+
+# Make sure we can load the model
+use_ok('Wifty::Model::User');
+
+# Grab a system use
+my $system_user = Wifty::CurrentUser->superuser;
+ok($system_user, "Found a system user");
+
+# Try testing a create
+my $o = Wifty::Model::User->new(current_user => $system_user);
+my ($id) = $o->create(email => 'an at email', name => 'name');
+ok($id, "User create returned success");
+ok($o->id, "New User has valid id set");
+is($o->id, $id, "Create returned the right id");
+
+# And another
+$o->create(email => 'some at mail', name => 'another');
+ok($o->id, "User create returned another value");
+isnt($o->id, $id, "And it is different from the previous one");
+
+# Searches in general
+my $collection = Wifty::Model::UserCollection->new(current_user => $system_user);
+$collection->unlimit;
+is($collection->count, 2, "Finds two records");
+
+# Searches in specific
+$collection->limit(column => 'id', value => $o->id);
+is($collection->count, 1, "Finds one record with specific id");
+
+# Delete one of them
+$o->delete;
+$collection->redo_search;
+is($collection->count, 0, "Deleted row is gone");
+
+# And the other one is still there
+$collection->unlimit;
+is($collection->count, 1, "Still one left");
+
commit 3c7dbb168fa6e9fdcbfb54b7f7b0176737482873
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Sun Dec 25 01:36:17 2005 +0000
Updated how delegated acl decisions happen
diff --git a/lib/Wifty/Model/Page.pm b/lib/Wifty/Model/Page.pm
index 59e7384..e584723 100644
--- a/lib/Wifty/Model/Page.pm
+++ b/lib/Wifty/Model/Page.pm
@@ -115,18 +115,22 @@ sub _set {
return ( $val, $msg );
}
+
+=head2 current_user_can ACTION
+
+Let everybody create, read and update pages, but not delete the.
+
+=cut
+
sub current_user_can {
my $self = shift;
- my ($type) = @_;
-
- # TODO FIXME: For now..
- return 1;
+ my $type = shift;
# We probably want something like this eventually:
- if ($type eq "read") {
+ if ($type =~ /(?:create|read|update)/i) {
return 1;
} else {
- return $self->current_user->id;
+ return $self->SUPER::current_user_can($type, @_);
}
}
diff --git a/lib/Wifty/Model/Revision.pm b/lib/Wifty/Model/Revision.pm
index b1fc3a6..7d44259 100644
--- a/lib/Wifty/Model/Revision.pm
+++ b/lib/Wifty/Model/Revision.pm
@@ -30,4 +30,24 @@ sub create {
}
+=head2 current_user_can RIGHT
+
+We're using L<Jifty::RightsFrom> to pass off ACL decisions to this
+update's page. But we need to make sure that page history entries aren't
+editable, except by superusers. So we override C<current_user_can>
+to give the arguments a brief massage before handing off to
+C<urrent_user_can> (which we inherit).
+
+=cut
+
+sub current_user_can {
+ my $self = shift;
+ my $right = shift;
+
+ if ($right ne 'read' and not $self->current_user->is_superuser) {
+ return 0;
+ }
+ $self->SUPER::current_user_can($right, @_);
+
+}
1;
commit 3e3618025f43de516c690910ad3473b305e4212f
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Sun Dec 25 01:36:31 2005 +0000
revisions are created by the superuser
diff --git a/lib/Wifty/Model/Page.pm b/lib/Wifty/Model/Page.pm
index e584723..63d1e27 100644
--- a/lib/Wifty/Model/Page.pm
+++ b/lib/Wifty/Model/Page.pm
@@ -88,7 +88,7 @@ sub _add_revision {
my $self = shift;
my %args = (@_);
- my $rev = Wifty::Model::Revision->new();
+ my $rev = Wifty::Model::Revision->new( current_user => Wifty::CurrentUser->superuser);
$rev->create(
page => $self->id,
content => $args{'content'}
commit 43139ed8c3f03fd9e82bbb1efcb974361215066e
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Sun Dec 25 01:36:46 2005 +0000
Wifty really lets you login
diff --git a/lib/Wifty/Action/Login.pm b/lib/Wifty/Action/Login.pm
index 2d5e4f8..ed12a5a 100644
--- a/lib/Wifty/Action/Login.pm
+++ b/lib/Wifty/Action/Login.pm
@@ -76,7 +76,7 @@ sub take_action {
}
unless ($user->user_object->email_confirmed) {
- $self->result->error( q{You haven't <a href="/welcome/confirm.html">confirmed your account</a> yet.} );
+ $self->result->error( q{You haven't confirmed your account yet.} );
return;
}
diff --git a/lib/Wifty/Action/Signup.pm b/lib/Wifty/Action/Signup.pm
index 1fc264c..6e73fc2 100644
--- a/lib/Wifty/Action/Signup.pm
+++ b/lib/Wifty/Action/Signup.pm
@@ -36,11 +36,8 @@ sub arguments {
my $args = $self->SUPER::arguments;
my %fields = (
+ name => 1,
email => 1,
- likes_ticky_boxes => 1,
- name => 1,
- never_email => 1,
- notification_email_frequency => 1,
password => 1,
password_confirm => 1,
);
@@ -62,14 +59,13 @@ sub validate_email {
my $self = shift;
my $email = shift;
- unless ( $email =~ /\S\@\S/ ) {
- return $self->validation_error(email => "That doesn't look like an email address." );
- }
+ return $self->validation_error(email => "That doesn't look like an email address." )
+ unless ( $email =~ /\S\@\S/ ) ;
my $u = Wifty::Model::User->new(current_user => Wifty::CurrentUser->superuser);
$u->load_by_cols( email => $email );
if ($u->id) {
- return $self->validation_error(email => 'It looks like you already have an account. Perhaps you want to <a href="/welcome/">sign in</a> instead?');
+ return $self->validation_error(email => 'It looks like you already have an account. Perhaps you want to <a href="/login">sign in</a> instead?');
}
return $self->validation_ok('email');
@@ -98,11 +94,11 @@ sub take_action {
my ($id) = $record->create(%values);
# Handle errors?
unless ( $record->id ) {
- $self->result->error("Something bad happened and we couldn't create your account. Try again later");
+ $self->result->error("Something bad happened and we couldn't create your account. Try again later. We're really, really sorry.");
return;
}
- $self->result->message( "Welcome to Wifty, " . $record->name .".");
+ $self->result->message( "Welcome to Wifty, " . $record->name .". We've sent a confirmation message to your email box.");
return 1;
diff --git a/lib/Wifty/Model/User.pm b/lib/Wifty/Model/User.pm
index af89ea5..d48e537 100644
--- a/lib/Wifty/Model/User.pm
+++ b/lib/Wifty/Model/User.pm
@@ -22,14 +22,19 @@ column email_confirmed =>
package Wifty::Model::User;
use base qw/Wifty::Record/;
+use Wifty::Notification::ConfirmAddress;
sub since {'0.0.7'}
sub create {
my $self = shift;
my %args = (@_);
- my ($id) = $self->SUPER::create(%args);
- return($id);
+ my (@ret) = $self->SUPER::create(%args);
+
+ if ($self->id and not $self->email_confirmed) {
+ Wifty::Notification::ConfirmAddress->new( to => $self )->send;
+ }
+ return (@ret);
}
@@ -63,10 +68,10 @@ sub current_user_can {
my $self = shift;
my $right = shift;
my %args = (@_);
-
+ return(1);
if ($right eq 'read') {
- } elsif ($right eq 'write') {
+ } elsif ($right eq 'update') {
}
diff --git a/web/templates/login b/web/templates/login
index e29d3f8..becc8aa 100755
--- a/web/templates/login
+++ b/web/templates/login
@@ -15,4 +15,5 @@ my $next = Jifty->web->request->continuation || Jifty::Continuation->new(request
<% $action->form_field('remember') %>
<% Jifty->web->form->submit(label => 'Login', submit => $action) %>
<% Jifty->web->form->end %>
+<% Jifty->web->tangent( label => q{Don't have an account?}, url => '/signup' )%>
</&>
commit b2a2c780478608838ce790eb64ceaaf01d3bbf08
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Sun Dec 25 01:36:59 2005 +0000
Checkpoint
diff --git a/lib/Wifty/Action/ConfirmAccount.pm b/lib/Wifty/Action/ConfirmAccount.pm
index 0498579..bbd6317 100644
--- a/lib/Wifty/Action/ConfirmAccount.pm
+++ b/lib/Wifty/Action/ConfirmAccount.pm
@@ -11,7 +11,7 @@ This is the link in a user's email to confirm that their email
email is really theirs. It is not really meant to be rendered on any
web page, but is used by the confirmation notification.
-Note that the use of C<insecure_url_auth_token> here is insecure and wrong!
+Note that the use of C<auth_token> here is insecure and wrong!
(XXX TODO FIXME) If an attacker knew the token calculation algorithm (including
the non-random salt), they could easily do email confirmation without needed
to actually have access to the email account, since the algorithm only depends on
commit 48e2e1cbccb2c87d14cb057fd4577da45a79363b
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Sun Dec 25 01:37:13 2005 +0000
users can log in and log out of wifty and sign up for accounts.
diff --git a/etc/config.yml b/etc/config.yml
index a589501..514c1e8 100644
--- a/etc/config.yml
+++ b/etc/config.yml
@@ -4,7 +4,7 @@ framework:
Driver: Pg
Host: localhost
User: postgres
- Version: 0.0.10
+ Version: 0.0.15
Password: ''
RequireSSL: 0
# Mailer: IO
diff --git a/lib/Wifty/Action/ConfirmAccount.pm b/lib/Wifty/Action/ConfirmEmail.pm
similarity index 78%
rename from lib/Wifty/Action/ConfirmAccount.pm
rename to lib/Wifty/Action/ConfirmEmail.pm
index bbd6317..c3261a7 100644
--- a/lib/Wifty/Action/ConfirmAccount.pm
+++ b/lib/Wifty/Action/ConfirmEmail.pm
@@ -11,12 +11,6 @@ This is the link in a user's email to confirm that their email
email is really theirs. It is not really meant to be rendered on any
web page, but is used by the confirmation notification.
-Note that the use of C<auth_token> here is insecure and wrong!
-(XXX TODO FIXME) If an attacker knew the token calculation algorithm (including
-the non-random salt), they could easily do email confirmation without needed
-to actually have access to the email account, since the algorithm only depends on
-the email address, requested password, and non-random salt.
-
=cut
package Wifty::Action::ConfirmEmail;
diff --git a/lib/Wifty/Action/Logout.pm b/lib/Wifty/Action/Logout.pm
new file mode 100644
index 0000000..697b9f6
--- /dev/null
+++ b/lib/Wifty/Action/Logout.pm
@@ -0,0 +1,35 @@
+use warnings;
+use strict;
+
+=head1 NAME
+
+Wifty::Action::Logout
+
+=cut
+
+package Wifty::Action::Logout;
+use base qw/Wifty::Action Jifty::Action/;
+
+=head2 arguments
+
+Return the email and password form fields
+
+=cut
+
+sub arguments {
+ return( { });
+}
+
+=head2 take_action
+
+Nuke the current user object
+
+=cut
+
+sub take_action {
+ my $self = shift;
+ Jifty->web->current_user(undef);
+ return 1;
+}
+
+1;
diff --git a/lib/Wifty/Model/User.pm b/lib/Wifty/Model/User.pm
index d48e537..992c231 100644
--- a/lib/Wifty/Model/User.pm
+++ b/lib/Wifty/Model/User.pm
@@ -19,6 +19,11 @@ column email_confirmed =>
type is 'boolean',
since '0.0.10';
+column auth_token =>
+ type is 'text',
+ since '0.0.15';
+
+
package Wifty::Model::User;
use base qw/Wifty::Record/;
@@ -78,5 +83,25 @@ sub current_user_can {
return $self->SUPER::current_user_can($right, %args);
}
+=head2 auth_token
+
+Returns the user's unique authentication token. If the user
+doesn't have one, sets one and returns it.
+
+=cut
+
+
+sub auth_token {
+ my $self = shift;
+ return undef unless ($self->current_user_can( read => 'auth_token'));
+ my $value = $self->_value('auth_token') ;
+ unless ($value) {
+ my $digest =Digest::MD5->new();
+ $digest->add(rand(100));
+ $self->__set('auth_token' => $digest->b64digest);
+ return $digest->b64digest;
+ }
+
+}
1;
diff --git a/lib/Wifty/Notification/ConfirmAddress.pm b/lib/Wifty/Notification/ConfirmAddress.pm
new file mode 100755
index 0000000..3fb3c3a
--- /dev/null
+++ b/lib/Wifty/Notification/ConfirmAddress.pm
@@ -0,0 +1,53 @@
+use warnings;
+use strict;
+
+package Wifty::Notification::ConfirmAddress;
+use base qw/Wifty::Notification/;
+
+=head1 NAME
+
+Hiveminder::Notification::ConfirmAddress
+
+=head1 ARGUMENTS
+
+C<to>, a L<Wifty::Model::User> whose address we are confirming.
+
+=cut
+
+=head2 setup
+
+Sets up the fields of the message.
+
+=cut
+
+sub setup {
+ my $self = shift;
+
+ unless (UNIVERSAL::isa($self->to, "Wifty::Model::User")) {
+ $self->log->error((ref $self) . " called with invalid user argument");
+ return;
+ }
+
+
+ my $letme = Jifty::LetMe->new();
+ $letme->email($self->to->email);
+ $letme->path('confirm_email');
+ my $confirm_url = $letme->as_url;
+
+ $self->subject( "Welcome to Wifty!" );
+
+
+ $self->body(<<"END_BODY");
+
+You're getting this message because you (or somebody claiming to be you)
+signed up for a Wiki running Wifty.
+
+Before you can use Wifty, we need to make sure that we got your email
+address right. Click on the link below to get started:
+
+$confirm_url
+
+END_BODY
+}
+
+1;
diff --git a/web/templates/_elements/sidebar b/web/templates/_elements/sidebar
new file mode 100644
index 0000000..41f4859
--- /dev/null
+++ b/web/templates/_elements/sidebar
@@ -0,0 +1,34 @@
+<div id="salutation">
+% if (Jifty->web->current_user->id and Jifty->web->current_user->user_object) {
+Hiya, <span class="user"><%Jifty->web->current_user->user_object->name%></span>.<br />
+(<% Jifty->web->tangent( label => q{Logout}, url => '/logout' )%>)
+% } else {
+<% Jifty->web->tangent( label => q{You're not currently signed in.}, url => '/login' )%>
+% }
+</div>
+<div id="navigation">
+<ul id="menu">
+<%perl>
+
+
+$m->comp( ".menu", item => $_ )
+ for ( sort { $a->sort_order <=> $b->sort_order }
+ Jifty->web->navigation->children );
+</%perl>
+</ul>
+</div>
+<%def .menu>
+<%args>
+$item
+</%args>
+ <li <% $item->active ? 'class="active"' : '' |n %>><%
+ Jifty->web->link(
+ url => $item->url,
+ label => $item->label,
+ ) %></li>
+% if (my @kids = $item->children) {
+<ul id="submenu">
+% $m->comp(".menu", item => $_) for @kids;
+</ul>
+% }
+</%def>
diff --git a/web/templates/_elements/wrapper b/web/templates/_elements/wrapper
new file mode 100644
index 0000000..15be078
--- /dev/null
+++ b/web/templates/_elements/wrapper
@@ -0,0 +1,31 @@
+<& header, title => $title &>
+<body>
+ <div id="headers">
+ <%Jifty->web->link( url => "/", label => Jifty->config->framework('ApplicationName'))%>
+ <h1 class="title"><% $title %></h1>
+ </div>
+ <& sidebar &>
+ <div id="content">
+ <a name="content"></a>
+% if (Jifty->config->framework('AdminMode') ) {
+<div class="warning admin_mode">
+Alert: Jifty <% Jifty->web->tangent( label => 'administration mode' , url => '/__jifty/admin/')%> is enabled.
+</div>
+
+% }
+ <% Jifty->web->render_messages %>
+ <% $m->content |n%>
+ <div id="keybindings">
+ <script><!--
+ writeKeyBindingLegend();
+ --></script>
+ </div>
+ </div>
+ <div id="jifty-wait-message" style="display: none">Loading...</div>
+</body>
+</html>
+<%args>
+$title => ""
+</%args>
+<%init>
+</%init>
diff --git a/web/templates/let/confirm_email b/web/templates/let/confirm_email
new file mode 100755
index 0000000..6ad94a4
--- /dev/null
+++ b/web/templates/let/confirm_email
@@ -0,0 +1,14 @@
+<%method setup_actions>
+<%perl>
+Jifty->web->allow_actions( 'Wifty::Action::ConfirmEmail');
+Jifty->web->request->add_action(
+ moniker => 'confirm_email',
+ class => 'Wifty::Action::ConfirmEmail',
+);
+Jifty->web->request->add_action(
+ moniker => 'next_page',
+ class => 'Jifty::Action::Redirect',
+ arguments => {url => "/"},
+);
+</%perl>
+</%method>
diff --git a/web/templates/login b/web/templates/login
index becc8aa..b149ebc 100755
--- a/web/templates/login
+++ b/web/templates/login
@@ -1,13 +1,13 @@
<%init>
-if (Jifty->web->current_user->id) {
- $m->comp('/_elements/logged_in_already');
- return();
-}
my $action = Jifty->web->new_action(class => 'Login', moniker => 'loginbox' );
my $next = Jifty->web->request->continuation || Jifty::Continuation->new(request => Jifty::Request->new(path => "/"));
</%init>
<&|/_elements/wrapper, title => 'Login' &>
+
+
+
+% if (not Jifty->web->current_user->id) {
<h2>Login</h2>
<% Jifty->web->form->start(call => $next, name => "loginbox") %>
<% $action->form_field('email') %>
@@ -16,4 +16,10 @@ my $next = Jifty->web->request->continuation || Jifty::Continuation->new(request
<% Jifty->web->form->submit(label => 'Login', submit => $action) %>
<% Jifty->web->form->end %>
<% Jifty->web->tangent( label => q{Don't have an account?}, url => '/signup' )%>
+% }
+% else {
+
+You're alrady logged in.
+
+%}
</&>
diff --git a/web/templates/logout b/web/templates/logout
new file mode 100755
index 0000000..ba5f717
--- /dev/null
+++ b/web/templates/logout
@@ -0,0 +1,12 @@
+<&| /_elements/wrapper, title => "Logged out" &>
+<p>Ok, you're now logged out. Have a good day.</p>
+</&>
+
+
+<%method setup_actions>
+<%perl>
+ Jifty->web->request->add_action( moniker => 'logout',
+ class => 'Wifty::Action::Logout'
+ );
+</%perl>
+</%method>
diff --git a/web/templates/signup b/web/templates/signup
new file mode 100755
index 0000000..2c08fff
--- /dev/null
+++ b/web/templates/signup
@@ -0,0 +1,19 @@
+<%init>
+if (Jifty->web->current_user->id) {
+ $m->comp('/_elements/logged_in_already');
+ return();
+}
+my $action = Jifty->web->new_action(class => 'Signup', moniker => 'signupbox' );
+
+my $next = Jifty->web->request->continuation || Jifty::Continuation->new(request => Jifty::Request->new(path => "/"));
+</%init>
+<&|/_elements/wrapper, title => 'Signup' &>
+<h2>Signup</h2>
+<% Jifty->web->form->start(call => $next, name => "signupbox") %>
+<% $action->form_field('email') %>
+<% $action->form_field('name') %>
+<% $action->form_field('password') %>
+<% $action->form_field('password_confirm') %>
+<% Jifty->web->form->submit(label => 'Signup', submit => $action) %>
+<% Jifty->web->form->end %>
+</&>
commit 816471197e8f6c344d50f12ac7a626f92a1185d8
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Sun Dec 25 01:37:27 2005 +0000
Mostly have recording of who-performs-an-update
diff --git a/etc/config.yml b/etc/config.yml
index 514c1e8..641dc5c 100644
--- a/etc/config.yml
+++ b/etc/config.yml
@@ -4,7 +4,7 @@ framework:
Driver: Pg
Host: localhost
User: postgres
- Version: 0.0.15
+ Version: 0.0.18
Password: ''
RequireSSL: 0
# Mailer: IO
diff --git a/lib/Wifty/Model/Page.pm b/lib/Wifty/Model/Page.pm
index 63d1e27..1cdcbb9 100644
--- a/lib/Wifty/Model/Page.pm
+++ b/lib/Wifty/Model/Page.pm
@@ -3,18 +3,23 @@ use Jifty::DBI::Schema;
column name =>
type is 'text',
+ label is 'Page name',
is mandatory,
is distinct;
column content =>
type is 'text',
- label is 'Page content',
+ label is 'Content',
render_as 'textarea';
column updated =>
type is 'timestamp',
+ label is 'Last updated',
since '0.0.6';
+column updated_by =>
+ refers_to Wifty::Model::User,
+ since '0.0.16';
column revisions =>
refers_to Wifty::Model::RevisionCollection by 'page';
@@ -65,6 +70,7 @@ sub create {
my %args = (@_);
my $now = DateTime->now();
$args{'updated'} = $now->ymd . " " . $now->hms;
+ $args{'updated_by'} = ( $self->current_user? $self->current_user->user_object : undef );
my ($id) = $self->SUPER::create(%args);
if ( $self->id ) {
$self->_add_revision(%args);
@@ -91,7 +97,8 @@ sub _add_revision {
my $rev = Wifty::Model::Revision->new( current_user => Wifty::CurrentUser->superuser);
$rev->create(
page => $self->id,
- content => $args{'content'}
+ content => $args{'content'},
+ by => $args{'updated_by'}
);
}
@@ -100,7 +107,9 @@ sub set_content {
my $self = shift;
my $content = shift;
my ( $val, $msg ) = $self->SUPER::set_content($content);
- $self->_add_revision( content => $content );
+ $self->_add_revision( content => $content,
+ updated_by =>( $self->current_user? $self->current_user->user_object : undef )
+ );
return ( $val, $msg );
}
@@ -112,6 +121,14 @@ sub _set {
column => 'updated',
value => $now->ymd . " " . $now->hms
);
+
+ $self->SUPER::_set(
+ column => 'updated_by',
+ value =>
+ ( $self->current_user? $self->current_user->user_object : undef )
+
+ );
+
return ( $val, $msg );
}
diff --git a/lib/Wifty/Model/Revision.pm b/lib/Wifty/Model/Revision.pm
index 7d44259..d6318bd 100644
--- a/lib/Wifty/Model/Revision.pm
+++ b/lib/Wifty/Model/Revision.pm
@@ -1,21 +1,21 @@
package Wifty::Model::Revision::Schema;
use Jifty::DBI::Schema;
-column page =>
- refers_to Wifty::Model::Page;
+column page => refers_to Wifty::Model::Page;
-column content =>
- type is 'text',
- render_as 'textarea';
+column content => type is 'text', render_as 'textarea';
+
+column created => type is 'timestamp';
+
+column by => refers_to Wifty::Model::User, since '0.0.18';
-column created =>
- type is 'timestamp';
package Wifty::Model::Revision;
use base qw/Wifty::Record/;
use Jifty::RightsFrom column => 'page';
use DateTime;
-
+use Wifty::Model::User;
+use Wifty::Model::Page;
sub since { '0.0.5' }
diff --git a/lib/Wifty/Model/User.pm b/lib/Wifty/Model/User.pm
index 992c231..bcf4a70 100644
--- a/lib/Wifty/Model/User.pm
+++ b/lib/Wifty/Model/User.pm
@@ -3,24 +3,29 @@ use Jifty::DBI::Schema;
column name =>
type is 'text',
+ label is 'Name',
is mandatory,
is distinct;
column email =>
type is 'text',
+ label is 'Email address',
is mandatory,
is distinct;
column password =>,
type is 'text',
+ label is 'Password',
render_as 'password';
column email_confirmed =>
+ label is 'Email address confirmed?',
type is 'boolean',
since '0.0.10';
column auth_token =>
type is 'text',
+ render_as 'Password',
since '0.0.15';
commit 44838be9a6bb3f8ac2dd36425747a79e72f95cbd
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Sun Dec 25 01:37:40 2005 +0000
Updated user model
diff --git a/etc/config.yml b/etc/config.yml
index 641dc5c..5f6e456 100644
--- a/etc/config.yml
+++ b/etc/config.yml
@@ -1,10 +1,13 @@
framework:
- AdminMode: 1
+ AdminMode: 0
+ Web:
+ Port: 80
+ BaseURL: http://jifty.org
Database:
Driver: Pg
Host: localhost
User: postgres
- Version: 0.0.18
+ Version: 0.0.19
Password: ''
RequireSSL: 0
# Mailer: IO
diff --git a/lib/Wifty/Model/User.pm b/lib/Wifty/Model/User.pm
index bcf4a70..1f437d9 100644
--- a/lib/Wifty/Model/User.pm
+++ b/lib/Wifty/Model/User.pm
@@ -21,7 +21,7 @@ column password =>,
column email_confirmed =>
label is 'Email address confirmed?',
type is 'boolean',
- since '0.0.10';
+ since '0.0.19';
column auth_token =>
type is 'text',
commit 94f16d73756face316164254a2925acbb80cc7d0
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Mon Dec 26 02:57:46 2005 +0000
Upgrades to token handling, acls for users enabled.
diff --git a/etc/config.yml b/etc/config.yml
index 5f6e456..eec1480 100644
--- a/etc/config.yml
+++ b/etc/config.yml
@@ -1,5 +1,6 @@
framework:
AdminMode: 0
+ ApplicationName: Wifty
Web:
Port: 80
BaseURL: http://jifty.org
diff --git a/lib/Wifty/Model/User.pm b/lib/Wifty/Model/User.pm
index 1f437d9..a0b23ee 100644
--- a/lib/Wifty/Model/User.pm
+++ b/lib/Wifty/Model/User.pm
@@ -73,19 +73,39 @@ sub password {
}
+=head2 current_user_can
-sub current_user_can {
- my $self = shift;
- my $right = shift;
- my %args = (@_);
- return(1);
- if ($right eq 'read') {
+Allows the current user to see all their own attributes and
+everyone else to see their username.
+
+Allows the current user to update any of their own attributes
+except whether or not their email has been confirmed.
- } elsif ($right eq 'update') {
+Passes everything else off to the superclass.
+=cut
+
+
+sub current_user_can {
+ my $self = shift;
+ my $right = shift;
+ my %args = (@_);
+ Carp::confess if ($right eq 'read' and not $args{'column'});
+ if ( $right eq 'read'
+ and $self->id == $self->current_user->id )
+ {
+ return 1;
+ } elsif ( $right eq 'read' and $args{'column'} eq 'name' ) {
+ return (1);
+
+ } elsif ( $right eq 'update'
+ and $self->id == $self->current_user->id
+ and $args{'column'} ne 'email_confirmed' )
+ {
+ return (1);
}
- return $self->SUPER::current_user_can($right, %args);
+ return $self->SUPER::current_user_can( $right, %args );
}
=head2 auth_token
@@ -98,14 +118,14 @@ doesn't have one, sets one and returns it.
sub auth_token {
my $self = shift;
- return undef unless ($self->current_user_can( read => 'auth_token'));
+ return undef unless ($self->current_user_can( read => column => 'auth_token'));
my $value = $self->_value('auth_token') ;
unless ($value) {
my $digest =Digest::MD5->new();
$digest->add(rand(100));
- $self->__set('auth_token' => $digest->b64digest);
- return $digest->b64digest;
+ $self->__set(column => 'auth_token', value => $digest->b64digest);
}
+ return $self->_value('auth_token') ;
}
diff --git a/t/00-model-User.t b/t/00-model-User.t
index 06b838a..e09777f 100644
--- a/t/00-model-User.t
+++ b/t/00-model-User.t
@@ -8,7 +8,7 @@ A basic test harness for the User model.
=cut
-use Jifty::Test tests => 11;
+use Jifty::Test tests => 12;
# Make sure we can load the model
use_ok('Wifty::Model::User');
@@ -24,6 +24,8 @@ ok($id, "User create returned success");
ok($o->id, "New User has valid id set");
is($o->id, $id, "Create returned the right id");
+ok($o->auth_token, "We have an auth token! ".$o->auth_token);
+
# And another
$o->create(email => 'some at mail', name => 'another');
ok($o->id, "User create returned another value");
commit 0e19cd0fb9b8cdaefd36cc48a35534874ebec788
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Thu Jan 5 19:50:42 2006 +0000
Create now uses the dispatcher!
diff --git a/lib/Wifty/Dispatcher.pm b/lib/Wifty/Dispatcher.pm
new file mode 100644
index 0000000..40531cb
--- /dev/null
+++ b/lib/Wifty/Dispatcher.pm
@@ -0,0 +1,8 @@
+package Wifty::Dispatcher;
+use Jifty::Dispatcher -base;
+
+under 'create/*', run {
+ set page => $1;
+ set action => Jifty->web->new_action( class => 'CreatePage' );
+};
+1;
diff --git a/web/templates/autohandler b/web/templates/autohandler
new file mode 100644
index 0000000..b0bf4c2
--- /dev/null
+++ b/web/templates/autohandler
@@ -0,0 +1,29 @@
+<%init>
+$r->content_type('text/html; charset=utf-8');
+Jifty->web->handle_request();
+
+if ($m->base_comp->path =~ m|/_elements/|) {
+ # Requesting an internal component by hand -- naughty
+ $m->redirect("/errors/requested_private_component");
+#} elsif (not Jifty->web->current_user->id and $m->request_comp->path !~ m{^/(?:welcome|dhandler|css|js|images|validator\.xml)} ) {
+# # Not logged in, trying to access a protected page
+# $m->notes->{'login-nextpage'} = $m->{top_path};
+# Jifty->web->redirect('/welcome/');
+}
+$m->comp('/_elements/nav');
+require Wifty::Dispatcher;
+Wifty::Dispatcher->handle_request();
+return;
+</%init>
+<%def .setup_actions>
+<%init>
+Jifty->web->allow_actions(qr/.*/);
+# this method turns around and calls the setup_actions method
+# it's called by Jifty::Web->setup_page_actions.
+my $delegate = $m->fetch_comp($m->next_comp->path);
+if ($delegate and $delegate->method_exists('setup_actions')) {
+ $delegate->call_method('setup_actions');
+}
+
+</%init>
+</%def>
diff --git a/web/templates/create/dhandler b/web/templates/create/dhandler
index fcbbe89..d8a3afe 100644
--- a/web/templates/create/dhandler
+++ b/web/templates/create/dhandler
@@ -1,7 +1,3 @@
-<%init>
-my $page = $m->dhandler_arg;
-my $action = Jifty->web->new_action( class => 'CreatePage');
-</%init>
<&|/_elements/wrapper, title => 'New page: '. $page&>
<% Jifty->web->form->start %>
<% Jifty->web->form->next_page( url => '/view/'.$page) %>
@@ -11,3 +7,7 @@ my $action = Jifty->web->new_action( class => 'CreatePage');
<% Jifty->web->form->end %>
<& /_elements/markup &>
</&>
+<%args>
+$action
+$page
+</%args>
commit 0c06037f6099182d00a9e0eb7889eac9664a0779
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Thu Jan 5 22:48:00 2006 +0000
Wifty now uses the dispatcher
diff --git a/lib/Wifty/Dispatcher.pm b/lib/Wifty/Dispatcher.pm
index 40531cb..178ee21 100644
--- a/lib/Wifty/Dispatcher.pm
+++ b/lib/Wifty/Dispatcher.pm
@@ -1,8 +1,92 @@
package Wifty::Dispatcher;
use Jifty::Dispatcher -base;
-under 'create/*', run {
- set page => $1;
- set action => Jifty->web->new_action( class => 'CreatePage' );
+on '/', run {
+ redirect( '/view/HomePage');
};
+
+under '/create/*', run {
+ set page => $1;
+ set action => Jifty->web->new_action( class => 'CreatePage' );
+};
+
+
+under ['view/*', 'edit/*'], run {
+ my ( $name, $rev );
+ warn "hey";
+ if ( $1 =~ qr{^(.*?)/?(\d*?)$} ) {
+ $name = $1;
+ $rev = $2;
+ }
+ my $page = Wifty::Model::Page->new();
+ $page->load_by_cols( name => $name );
+ Jifty->web->redirect( '/create/' . $name ) unless ( $page->id );
+ my $revision = Wifty::Model::Revision->new();
+ $revision->load_by_cols( page => $page->id, id => $rev ) if ($rev);
+ set page => $page;
+ set revision => $revision;
+ set viewer => Jifty->web->new_action( class => 'UpdatePage', record => $page );
+};
+
+under 'history/*', run {
+ my $name = $1;
+ my $page = Wifty::Model::Page->new();
+ $page->load_by_cols( name => $name );
+ redirect( '/create/' . $name ) unless ( $page->id );
+
+ my $revisions = $page->revisions;
+ $revisions->order_by( column => 'id', order => 'desc');
+
+ set page => $page;
+ set revisions => $revisions;
+
+};
+
+
+on 'pages', run {
+ my $pages = Wifty::Model::PageCollection->new();
+ $pages->unlimit();
+ set pages => $pages;
+};
+
+on 'recent', run {
+ my $then = DateTime->from_epoch( epoch => ( time - ( 86400 * 7 ) ) );
+ my $pages = Wifty::Model::PageCollection->new();
+ $pages->limit(
+ column => 'updated',
+ operator => '>',
+ value => $then->ymd
+ );
+ $pages->order_by( column => 'updated', order => 'desc' );
+ set pages => $pages;
+};
+
+on 'signup', run {
+ redirect('/') if ( Jifty->web->current_user->id );
+ set 'action' =>
+ Jifty->web->new_action( class => 'Signup', moniker => 'signupbox' );
+
+ set 'next' => Jifty->web->request->continuation
+ || Jifty::Continuation->new(
+ request => Jifty::Request->new( path => "/" ) );
+
+};
+
+on 'login', run {
+ set 'action' =>
+ Jifty->web->new_action( class => 'Login', moniker => 'loginbox' );
+ set 'next' => Jifty->web->request->continuation
+ || Jifty::Continuation->new(
+ request => Jifty::Request->new( path => "/" ) );
+
+};
+
+on 'logout', run {
+ Jifty->web->request->add_action(
+ moniker => 'logout',
+ class => 'Wifty::Action::Logout'
+ );
+};
+
+
1;
diff --git a/web/templates/_elements/wrapper b/web/templates/_elements/wrapper
index 15be078..9569bf8 100644
--- a/web/templates/_elements/wrapper
+++ b/web/templates/_elements/wrapper
@@ -27,5 +27,3 @@ Alert: Jifty <% Jifty->web->tangent( label => 'administration mode' , url => '/_
<%args>
$title => ""
</%args>
-<%init>
-</%init>
diff --git a/web/templates/create/dhandler b/web/templates/create/dhandler
index d8a3afe..91e61d8 100644
--- a/web/templates/create/dhandler
+++ b/web/templates/create/dhandler
@@ -8,6 +8,6 @@
<& /_elements/markup &>
</&>
<%args>
-$action
-$page
+$action => undef
+$page => undef
</%args>
diff --git a/web/templates/edit/dhandler b/web/templates/edit/dhandler
index 53b818d..380691e 100644
--- a/web/templates/edit/dhandler
+++ b/web/templates/edit/dhandler
@@ -1,22 +1,9 @@
-<%init>
-my $arg = $m->dhandler_arg();
-my ($name,$rev);
-if ($arg =~ qr{^(.*?)/?(\d*?)$}) {
- $name = $1;
- $rev = $2;
-}
-my $page = Wifty::Model::Page->new();
-$page->load_by_cols( name => $name );
-
-my $revision = Wifty::Model::Revision->new();
-if ($rev) {
-$revision->load_by_cols( page => $page->id, id=> $rev);
-}
-
-my $viewer = Jifty->web->new_action( class => 'UpdatePage', record => $page );
-$m->comp('/_elements/page_nav', page => $page->name, rev => $rev);
-
-</%init>
+<%args>
+$page
+$revision
+$viewer
+</%args>
+<&/_elements/page_nav, page => $page->name, rev => $revision->id &>
<&|/_elements/wrapper, title => 'Edit: '.$page->name . ($revision->id ? " as of ".$revision->created : '') &>
<% Jifty->web->form->start %>
<% Jifty->web->form->next_page( url => '/view/'.$page->name) %>
diff --git a/web/templates/history/dhandler b/web/templates/history/dhandler
index a8c8aa3..41d133b 100755
--- a/web/templates/history/dhandler
+++ b/web/templates/history/dhandler
@@ -1,13 +1,8 @@
-<%init>
-my $name = $m->dhandler_arg();
-my $page = Wifty::Model::Page->new();
-$page->load_by_cols( name => $name );
-Jifty->web->redirect( '/create/' . $name ) unless ( $page->id );
-
-my $revisions = $page->revisions;
-$revisions->order_by( column => 'id', order => 'desc');
-$m->comp('/_elements/page_nav', page => $page->name);
-</%init>
+<%args>
+$page
+$revisions
+</%args>
+<& /_elements/page_nav, page => $page->name &>
<&|/_elements/wrapper, title => $revisions->count ." revisions of " .$page->name &>
<ul>
% while (my $rev = $revisions->next) {
diff --git a/web/templates/index.html b/web/templates/index.html
deleted file mode 100644
index e5cb700..0000000
--- a/web/templates/index.html
+++ /dev/null
@@ -1 +0,0 @@
-% Jifty->web->redirect( '/view/HomePage');
diff --git a/web/templates/login b/web/templates/login
index b149ebc..ebd8869 100755
--- a/web/templates/login
+++ b/web/templates/login
@@ -1,12 +1,5 @@
-<%init>
-my $action = Jifty->web->new_action(class => 'Login', moniker => 'loginbox' );
-
-my $next = Jifty->web->request->continuation || Jifty::Continuation->new(request => Jifty::Request->new(path => "/"));
-</%init>
<&|/_elements/wrapper, title => 'Login' &>
-
-
% if (not Jifty->web->current_user->id) {
<h2>Login</h2>
<% Jifty->web->form->start(call => $next, name => "loginbox") %>
@@ -18,8 +11,6 @@ my $next = Jifty->web->request->continuation || Jifty::Continuation->new(request
<% Jifty->web->tangent( label => q{Don't have an account?}, url => '/signup' )%>
% }
% else {
-
-You're alrady logged in.
-
-%}
+You're already logged in.
+% }
</&>
diff --git a/web/templates/logout b/web/templates/logout
index ba5f717..f6eee9d 100755
--- a/web/templates/logout
+++ b/web/templates/logout
@@ -1,12 +1,3 @@
<&| /_elements/wrapper, title => "Logged out" &>
<p>Ok, you're now logged out. Have a good day.</p>
</&>
-
-
-<%method setup_actions>
-<%perl>
- Jifty->web->request->add_action( moniker => 'logout',
- class => 'Wifty::Action::Logout'
- );
-</%perl>
-</%method>
diff --git a/web/templates/pages b/web/templates/pages
index aeddbd4..c157fd1 100644
--- a/web/templates/pages
+++ b/web/templates/pages
@@ -1,7 +1,6 @@
-<%init>
-my $pages = Wifty::Model::PageCollection->new();
-$pages->unlimit();
-</%init>
+<%args>
+$pages
+</%args>
<&|/_elements/wrapper, title => 'These are the pages on your wiki!' &>
<ul id="pagelist">
% while (my $page = $pages->next) {
diff --git a/web/templates/recent b/web/templates/recent
index d002c6f..2d1941e 100644
--- a/web/templates/recent
+++ b/web/templates/recent
@@ -1,9 +1,6 @@
-<%init>
-my $then = DateTime->from_epoch( epoch => ( time - ( 86400 * 7 ) ) );
-my $pages = Wifty::Model::PageCollection->new();
-$pages->limit( column => 'updated', operator => '>', value => $then->ymd );
-$pages->order_by( column => 'updated', order => 'desc' );
-</%init>
+<%args>
+$pages
+</%args>
<&|/_elements/wrapper, title => 'Updated this week' &>
<dl id="recentudates">
% while (my $page = $pages->next) {
diff --git a/web/templates/signup b/web/templates/signup
index 2c08fff..1d006db 100755
--- a/web/templates/signup
+++ b/web/templates/signup
@@ -1,12 +1,7 @@
-<%init>
-if (Jifty->web->current_user->id) {
- $m->comp('/_elements/logged_in_already');
- return();
-}
-my $action = Jifty->web->new_action(class => 'Signup', moniker => 'signupbox' );
-
-my $next = Jifty->web->request->continuation || Jifty::Continuation->new(request => Jifty::Request->new(path => "/"));
-</%init>
+<%args>
+$action
+$next
+</%args>
<&|/_elements/wrapper, title => 'Signup' &>
<h2>Signup</h2>
<% Jifty->web->form->start(call => $next, name => "signupbox") %>
diff --git a/web/templates/view/dhandler b/web/templates/view/dhandler
index 5ce5e8b..f81e26e 100644
--- a/web/templates/view/dhandler
+++ b/web/templates/view/dhandler
@@ -1,25 +1,8 @@
-<%init>
-use Wifty::Model::Page;
-my $arg = $m->dhandler_arg();
-my ($name,$rev);
-if ($arg =~ qr{^(.*?)/?(\d*?)$}) {
- $name = $1;
- $rev = $2;
-}
-my $page = Wifty::Model::Page->new();
-$page->load_by_cols( name => $name );
-
-my $revision = Wifty::Model::Revision->new();
-if ($rev) {
- $revision->load_by_cols( page => $page->id, id => $rev);
-}
-
-unless ( $page->id ) {
- Jifty->web->redirect( '/create/' . $name );
-}
-
-$m->comp('/_elements/page_nav', page => $page->name, rev => $rev);
-</%init>
+<%args>
+$page
+$revision
+</%args>
+<& /_elements/page_nav, page => $page->name, rev => $revision->id &>
<&|/_elements/wrapper, title => $page->name . ($revision->id ? " as of ".$revision->created : '') &>
% if ($revision->id) {
<% $page->wiki_content($revision->content) |n%>
commit 5fb201082269c8e2f16e8cd06009e25157d01cbe
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Thu Jan 5 22:49:09 2006 +0000
removed a warning
diff --git a/lib/Wifty/Dispatcher.pm b/lib/Wifty/Dispatcher.pm
index 178ee21..5ec5f28 100644
--- a/lib/Wifty/Dispatcher.pm
+++ b/lib/Wifty/Dispatcher.pm
@@ -13,7 +13,6 @@ under '/create/*', run {
under ['view/*', 'edit/*'], run {
my ( $name, $rev );
- warn "hey";
if ( $1 =~ qr{^(.*?)/?(\d*?)$} ) {
$name = $1;
$rev = $2;
commit 6c8ed75f95e7164d3505b1898147c4ec61e9aafb
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Fri Jan 6 13:38:18 2006 +0000
checkpoint
* wifty checkpoint
diff --git a/lib/Wifty/Dispatcher.pm b/lib/Wifty/Dispatcher.pm
index 5ec5f28..ca16fc6 100644
--- a/lib/Wifty/Dispatcher.pm
+++ b/lib/Wifty/Dispatcher.pm
@@ -1,9 +1,7 @@
package Wifty::Dispatcher;
use Jifty::Dispatcher -base;
-on '/', run {
- redirect( '/view/HomePage');
-};
+on '/', redirect( '/view/HomePage');
under '/create/*', run {
set page => $1;
@@ -11,9 +9,9 @@ under '/create/*', run {
};
-under ['view/*', 'edit/*'], run {
+on qr{(view|edit)/(.*)}, run {
my ( $name, $rev );
- if ( $1 =~ qr{^(.*?)/?(\d*?)$} ) {
+ if ( $2 =~ qr{^(.*?)/?(\d*?)$} ) {
$name = $1;
$rev = $2;
}
@@ -25,9 +23,10 @@ under ['view/*', 'edit/*'], run {
set page => $page;
set revision => $revision;
set viewer => Jifty->web->new_action( class => 'UpdatePage', record => $page );
+ show("/view");
};
-under 'history/*', run {
+on 'history/*', run {
my $name = $1;
my $page = Wifty::Model::Page->new();
$page->load_by_cols( name => $name );
diff --git a/web/templates/create/dhandler b/web/templates/create
similarity index 100%
rename from web/templates/create/dhandler
rename to web/templates/create
diff --git a/web/templates/edit/dhandler b/web/templates/edit
similarity index 100%
rename from web/templates/edit/dhandler
rename to web/templates/edit
diff --git a/web/templates/history/dhandler b/web/templates/history
similarity index 100%
rename from web/templates/history/dhandler
rename to web/templates/history
diff --git a/web/templates/view/dhandler b/web/templates/view
similarity index 100%
rename from web/templates/view/dhandler
rename to web/templates/view
commit 2a77fc37a71b4ebe0f8c04e98cf314ca8308acbc
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Fri Jan 6 22:49:25 2006 +0000
sync wifty to the dispatcher
diff --git a/etc/config.yml b/etc/config.yml
index eec1480..338de16 100644
--- a/etc/config.yml
+++ b/etc/config.yml
@@ -1,6 +1,8 @@
framework:
AdminMode: 0
ApplicationName: Wifty
+ LogConfig: etc/log4perl.conf
+
Web:
Port: 80
BaseURL: http://jifty.org
diff --git a/etc/log4perl.conf b/etc/log4perl.conf
new file mode 100644
index 0000000..b48ff34
--- /dev/null
+++ b/etc/log4perl.conf
@@ -0,0 +1,34 @@
+log4perl.rootLogger=DEBUG, ShowInfo, LogToFile, ErrorsToFile
+
+# Disable debugging output for certain modules; comment these lines to have them
+# show up again
+
+log4perl.logger.Jifty::MasonInterp = INFO, ShowInfo, LogToFile
+
+# If you want to make DEBUG level for Jifty::Some::Module show up on the screen
+# (and not just the log file), add the
+# following line:
+#log4perl.logger.Jifty::Some::Module=DEBUG, ShowCategoryDebug
+
+log4perl.appender.ShowInfo=Log::Log4perl::Appender::ScreenColoredLevels
+log4perl.appender.ShowInfo.layout=Log::Log4perl::Layout::PatternLayout
+log4perl.appender.ShowInfo.layout.ConversionPattern=%d %p> %F{1}:%L %M - %m%n
+#log4perl.appender.ShowInfo.Threshold=INFO
+
+log4perl.appender.LogToFile=Log::Log4perl::Appender::File
+log4perl.appender.LogToFile.filename= sub { Jifty::Util->absolute_path("log/server.log") }
+log4perl.appender.LogToFile.mode=append
+log4perl.appender.LogToFile.layout=Log::Log4perl::Layout::PatternLayout
+log4perl.appender.LogToFile.layout.ConversionPattern=%d %p> %F{1}:%L %M - %m%n
+
+log4perl.appender.ErrorsToFile=Log::Log4perl::Appender::File
+log4perl.appender.ErrorsToFile.filename= sub { Jifty::Util->absolute_path("log/error.log") }
+log4perl.appender.ErrorsToFile.mode=append
+log4perl.appender.ErrorsToFile.layout=Log::Log4perl::Layout::PatternLayout
+log4perl.appender.ErrorsToFile.layout.ConversionPattern=%d %p> %F{1}:%L %M - %m%n
+log4perl.appender.ErrorsToFile.Threshold=WARN
+
+log4perl.appender.ShowCategoryDebug=Log::Log4perl::Appender::ScreenColoredLevels
+log4perl.appender.ShowCategoryDebug.layout=Log::Log4perl::Layout::PatternLayout
+log4perl.appender.ShowCategoryDebug.layout.ConversionPattern=%d %p> %F{1}:%L %M - %m%n
+
diff --git a/lib/Wifty/Dispatcher.pm b/lib/Wifty/Dispatcher.pm
index ca16fc6..212500d 100644
--- a/lib/Wifty/Dispatcher.pm
+++ b/lib/Wifty/Dispatcher.pm
@@ -3,14 +3,16 @@ use Jifty::Dispatcher -base;
on '/', redirect( '/view/HomePage');
-under '/create/*', run {
+on '/create/*', run {
set page => $1;
set action => Jifty->web->new_action( class => 'CreatePage' );
+ show("/create");
};
on qr{(view|edit)/(.*)}, run {
my ( $name, $rev );
+ my $page_name = $1;
if ( $2 =~ qr{^(.*?)/?(\d*?)$} ) {
$name = $1;
$rev = $2;
@@ -23,7 +25,7 @@ on qr{(view|edit)/(.*)}, run {
set page => $page;
set revision => $revision;
set viewer => Jifty->web->new_action( class => 'UpdatePage', record => $page );
- show("/view");
+ show("/$page_name");
};
on 'history/*', run {
@@ -37,7 +39,7 @@ on 'history/*', run {
set page => $page;
set revisions => $revisions;
-
+ show('/history');
};
@@ -79,7 +81,7 @@ on 'login', run {
};
-on 'logout', run {
+before 'logout', run {
Jifty->web->request->add_action(
moniker => 'logout',
class => 'Wifty::Action::Logout'
diff --git a/web/templates/autohandler b/web/templates/autohandler
index b0bf4c2..8009934 100644
--- a/web/templates/autohandler
+++ b/web/templates/autohandler
@@ -1,6 +1,6 @@
<%init>
$r->content_type('text/html; charset=utf-8');
-Jifty->web->handle_request();
+#Jifty->web->handle_request();
if ($m->base_comp->path =~ m|/_elements/|) {
# Requesting an internal component by hand -- naughty
@@ -17,6 +17,7 @@ return;
</%init>
<%def .setup_actions>
<%init>
+# XXX TODO: move all this into the dispatcher
Jifty->web->allow_actions(qr/.*/);
# this method turns around and calls the setup_actions method
# it's called by Jifty::Web->setup_page_actions.
commit d22433343ccfd78a92a2e8e14254c67a2573a16c
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Sun Jan 8 15:35:09 2006 +0000
Switch to SQLite for testing purposes
* Turn off Log4Perl
* Comment out 'by' column since SQLite treats it as reserved
diff --git a/Makefile.PL b/Makefile.PL
index 89bf3d4..456dedf 100644
--- a/Makefile.PL
+++ b/Makefile.PL
@@ -2,5 +2,6 @@ use inc::Module::Install;
name('Wifty');
version('0.01');
requires('Jifty');
-
+requires('Text::Markdown');
+requires('HTML::Scrubber');
WriteAll;
diff --git a/etc/config.yml b/etc/config.yml
index 338de16..a4654c2 100644
--- a/etc/config.yml
+++ b/etc/config.yml
@@ -1,13 +1,14 @@
framework:
AdminMode: 0
ApplicationName: Wifty
- LogConfig: etc/log4perl.conf
+ #LogConfig: etc/log4perl.conf
Web:
Port: 80
BaseURL: http://jifty.org
Database:
- Driver: Pg
+ # Driver: Pg
+ Driver: SQLite
Host: localhost
User: postgres
Version: 0.0.19
diff --git a/lib/Wifty/Model/Page.pm b/lib/Wifty/Model/Page.pm
index 1cdcbb9..3ee988f 100644
--- a/lib/Wifty/Model/Page.pm
+++ b/lib/Wifty/Model/Page.pm
@@ -98,7 +98,7 @@ sub _add_revision {
$rev->create(
page => $self->id,
content => $args{'content'},
- by => $args{'updated_by'}
+ # by => $args{'updated_by'}
);
}
diff --git a/lib/Wifty/Model/Revision.pm b/lib/Wifty/Model/Revision.pm
index d6318bd..8291192 100644
--- a/lib/Wifty/Model/Revision.pm
+++ b/lib/Wifty/Model/Revision.pm
@@ -7,7 +7,7 @@ column content => type is 'text', render_as 'textarea';
column created => type is 'timestamp';
-column by => refers_to Wifty::Model::User, since '0.0.18';
+#column by => refers_to Wifty::Model::User, since '0.0.18';
package Wifty::Model::Revision;
commit 60ce2ebf84b7a6913b9df336822ce40a89104d94
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Sun Jan 8 15:36:58 2006 +0000
typoed the yaml comments
diff --git a/etc/config.yml b/etc/config.yml
index a4654c2..1405062 100644
--- a/etc/config.yml
+++ b/etc/config.yml
@@ -1,13 +1,11 @@
framework:
AdminMode: 0
ApplicationName: Wifty
- #LogConfig: etc/log4perl.conf
Web:
Port: 80
BaseURL: http://jifty.org
Database:
- # Driver: Pg
Driver: SQLite
Host: localhost
User: postgres
commit 6ff5d0dd7c669627cdca6c54ee5251cc924dffa4
Author: Alex Vandiver <alexmv at bestpractical.com>
Date: Wed Jan 11 18:56:16 2006 +0000
Site-specific stuff should go in the siteconfig
* Avoid double-escaping <'s by doing markdown then scrub; loosen
scrubbing to allow all of markdown through
diff --git a/etc/config.yml b/etc/config.yml
index 1405062..d256e28 100644
--- a/etc/config.yml
+++ b/etc/config.yml
@@ -2,9 +2,6 @@ framework:
AdminMode: 0
ApplicationName: Wifty
- Web:
- Port: 80
- BaseURL: http://jifty.org
Database:
Driver: SQLite
Host: localhost
@@ -16,5 +13,3 @@ framework:
# MailerArgs:
# - %log/mail.log%
SiteConfig: etc/site_config.yml
-application:
- MaxWurbles: 9
diff --git a/lib/Wifty/Model/Page.pm b/lib/Wifty/Model/Page.pm
index 3ee988f..4ad0cdb 100644
--- a/lib/Wifty/Model/Page.pm
+++ b/lib/Wifty/Model/Page.pm
@@ -40,7 +40,7 @@ this page's "content" attribute.
sub wiki_content {
my $self = shift;
- my $content = shift ||$self->content();
+ my $content = shift || $self->content() || '';
my $scrubber = HTML::Scrubber->new();
$scrubber->default(
@@ -48,7 +48,7 @@ sub wiki_content {
{ '*' => 0,
id => 1,
class => 1,
- href => qr{^(?:http:|ftp:|https:|/)}i,
+ href => qr{^(?:(?:\w+$)|http:|ftp:|https:|/)}i,
# Match http, ftp and relative urls
face => 1,
@@ -59,9 +59,12 @@ sub wiki_content {
$scrubber->deny(qw[*]);
$scrubber->allow(
- qw[A B U P BR I HR BR SMALL EM FONT SPAN DIV UL OL LI DL DT DD]);
+ qw[H1 H2 H3 H4 H5 A STRONG EM CODE PRE B U P BR I HR BR SPAN DIV UL OL LI DL DT DD]);
$scrubber->comment(0);
- return ( markdown( $scrubber->scrub( $content || '') ) );
+
+ $content = markdown( $content );
+ $content = $scrubber->scrub( $content );
+ return ( $content );
}
commit dd6bf9bb12d0d09a7cc3d22f07068d04fccbc1fc
Author: Alex Vandiver <alexmv at bestpractical.com>
Date: Wed Jan 11 21:59:34 2006 +0000
Make /login compile
* Remove extraneous lines from autohandler
diff --git a/web/templates/autohandler b/web/templates/autohandler
index 8009934..f3ac548 100644
--- a/web/templates/autohandler
+++ b/web/templates/autohandler
@@ -1,14 +1,7 @@
<%init>
-$r->content_type('text/html; charset=utf-8');
-#Jifty->web->handle_request();
-
if ($m->base_comp->path =~ m|/_elements/|) {
# Requesting an internal component by hand -- naughty
$m->redirect("/errors/requested_private_component");
-#} elsif (not Jifty->web->current_user->id and $m->request_comp->path !~ m{^/(?:welcome|dhandler|css|js|images|validator\.xml)} ) {
-# # Not logged in, trying to access a protected page
-# $m->notes->{'login-nextpage'} = $m->{top_path};
-# Jifty->web->redirect('/welcome/');
}
$m->comp('/_elements/nav');
require Wifty::Dispatcher;
diff --git a/web/templates/login b/web/templates/login
index ebd8869..52d75b5 100755
--- a/web/templates/login
+++ b/web/templates/login
@@ -1,3 +1,7 @@
+<%args>
+$action => undef
+$next => undef
+</%args>
<&|/_elements/wrapper, title => 'Login' &>
% if (not Jifty->web->current_user->id) {
commit 58f84da7862163cd03322a319423a64f8c7d73ab
Author: Alex Vandiver <alexmv at bestpractical.com>
Date: Mon Jan 23 22:29:35 2006 +0000
Clean up to work with most recent dispatcher, etc
diff --git a/lib/Wifty/Dispatcher.pm b/lib/Wifty/Dispatcher.pm
index 212500d..b366a33 100644
--- a/lib/Wifty/Dispatcher.pm
+++ b/lib/Wifty/Dispatcher.pm
@@ -1,15 +1,24 @@
package Wifty::Dispatcher;
use Jifty::Dispatcher -base;
-on '/', redirect( '/view/HomePage');
+# Generic restrictions
+under '/', run {
+ Jifty->web->deny_actions('Wifty::ConfirmEmail');
+};
+
+# Default page
+on '/', run {
+ redirect( '/view/HomePage');
+};
+# Create a page
on '/create/*', run {
set page => $1;
set action => Jifty->web->new_action( class => 'CreatePage' );
show("/create");
};
-
+# View or edit a page
on qr{(view|edit)/(.*)}, run {
my ( $name, $rev );
my $page_name = $1;
@@ -28,6 +37,7 @@ on qr{(view|edit)/(.*)}, run {
show("/$page_name");
};
+# View page history
on 'history/*', run {
my $name = $1;
my $page = Wifty::Model::Page->new();
@@ -42,13 +52,14 @@ on 'history/*', run {
show('/history');
};
-
+# List pages
on 'pages', run {
my $pages = Wifty::Model::PageCollection->new();
$pages->unlimit();
set pages => $pages;
};
+# Show recent edits
on 'recent', run {
my $then = DateTime->from_epoch( epoch => ( time - ( 86400 * 7 ) ) );
my $pages = Wifty::Model::PageCollection->new();
@@ -61,6 +72,7 @@ on 'recent', run {
set pages => $pages;
};
+# Sign up for an account
on 'signup', run {
redirect('/') if ( Jifty->web->current_user->id );
set 'action' =>
@@ -72,6 +84,7 @@ on 'signup', run {
};
+# Login
on 'login', run {
set 'action' =>
Jifty->web->new_action( class => 'Login', moniker => 'loginbox' );
@@ -81,6 +94,7 @@ on 'login', run {
};
+# Log out
before 'logout', run {
Jifty->web->request->add_action(
moniker => 'logout',
diff --git a/web/templates/autohandler b/web/templates/autohandler
index f3ac548..6497077 100644
--- a/web/templates/autohandler
+++ b/web/templates/autohandler
@@ -4,20 +4,5 @@ if ($m->base_comp->path =~ m|/_elements/|) {
$m->redirect("/errors/requested_private_component");
}
$m->comp('/_elements/nav');
-require Wifty::Dispatcher;
-Wifty::Dispatcher->handle_request();
-return;
+$m->call_next();
</%init>
-<%def .setup_actions>
-<%init>
-# XXX TODO: move all this into the dispatcher
-Jifty->web->allow_actions(qr/.*/);
-# this method turns around and calls the setup_actions method
-# it's called by Jifty::Web->setup_page_actions.
-my $delegate = $m->fetch_comp($m->next_comp->path);
-if ($delegate and $delegate->method_exists('setup_actions')) {
- $delegate->call_method('setup_actions');
-}
-
-</%init>
-</%def>
commit f51437acf71c1c668b5778f778a9f09df1c36177
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Mon Jan 30 05:11:22 2006 +0000
Removed now-unneeded components
diff --git a/web/templates/_elements/wrapper b/web/templates/_elements/wrapper
deleted file mode 100644
index 9569bf8..0000000
--- a/web/templates/_elements/wrapper
+++ /dev/null
@@ -1,29 +0,0 @@
-<& header, title => $title &>
-<body>
- <div id="headers">
- <%Jifty->web->link( url => "/", label => Jifty->config->framework('ApplicationName'))%>
- <h1 class="title"><% $title %></h1>
- </div>
- <& sidebar &>
- <div id="content">
- <a name="content"></a>
-% if (Jifty->config->framework('AdminMode') ) {
-<div class="warning admin_mode">
-Alert: Jifty <% Jifty->web->tangent( label => 'administration mode' , url => '/__jifty/admin/')%> is enabled.
-</div>
-
-% }
- <% Jifty->web->render_messages %>
- <% $m->content |n%>
- <div id="keybindings">
- <script><!--
- writeKeyBindingLegend();
- --></script>
- </div>
- </div>
- <div id="jifty-wait-message" style="display: none">Loading...</div>
-</body>
-</html>
-<%args>
-$title => ""
-</%args>
diff --git a/web/templates/autohandler b/web/templates/autohandler
deleted file mode 100644
index 6497077..0000000
--- a/web/templates/autohandler
+++ /dev/null
@@ -1,8 +0,0 @@
-<%init>
-if ($m->base_comp->path =~ m|/_elements/|) {
- # Requesting an internal component by hand -- naughty
- $m->redirect("/errors/requested_private_component");
-}
-$m->comp('/_elements/nav');
-$m->call_next();
-</%init>
commit cc26f9412a6fe86ed898abf59bfed16a39cf7fe7
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Mon Jan 30 05:23:11 2006 +0000
Wifty view/edit dispatcher rules were too greedy
diff --git a/lib/Wifty/Dispatcher.pm b/lib/Wifty/Dispatcher.pm
index b366a33..e1c5034 100644
--- a/lib/Wifty/Dispatcher.pm
+++ b/lib/Wifty/Dispatcher.pm
@@ -19,7 +19,7 @@ on '/create/*', run {
};
# View or edit a page
-on qr{(view|edit)/(.*)}, run {
+on qr{^/(view|edit)/(.*)}, run {
my ( $name, $rev );
my $page_name = $1;
if ( $2 =~ qr{^(.*?)/?(\d*?)$} ) {
commit b47d2fa3fbb0c0414ad33f9582b614bf431fadbf
Author: Alex Vandiver <alexmv at bestpractical.com>
Date: Fri Feb 17 22:37:06 2006 +0000
We're not importing the 'markdown' sub from Text::Markdown, so
provide the package name, too
diff --git a/lib/Wifty/Model/Page.pm b/lib/Wifty/Model/Page.pm
index 4ad0cdb..66d0e27 100644
--- a/lib/Wifty/Model/Page.pm
+++ b/lib/Wifty/Model/Page.pm
@@ -62,7 +62,7 @@ sub wiki_content {
qw[H1 H2 H3 H4 H5 A STRONG EM CODE PRE B U P BR I HR BR SPAN DIV UL OL LI DL DT DD]);
$scrubber->comment(0);
- $content = markdown( $content );
+ $content = Text::Markdown::markdown( $content );
$content = $scrubber->scrub( $content );
return ( $content );
commit 4cfd8b2b2d52c3b3189c19d72c7eca161e2458b2
Author: Thomas Sibley <trs at bestpractical.com>
Date: Sat Feb 25 18:38:32 2006 +0000
Fix because of update to jifty
diff --git a/web/templates/_elements/sidebar b/web/templates/_elements/sidebar
index 41f4859..697f839 100644
--- a/web/templates/_elements/sidebar
+++ b/web/templates/_elements/sidebar
@@ -7,28 +7,5 @@ Hiya, <span class="user"><%Jifty->web->current_user->user_object->name%></span>.
% }
</div>
<div id="navigation">
-<ul id="menu">
-<%perl>
-
-
-$m->comp( ".menu", item => $_ )
- for ( sort { $a->sort_order <=> $b->sort_order }
- Jifty->web->navigation->children );
-</%perl>
-</ul>
+<& /_elements/menu &>
</div>
-<%def .menu>
-<%args>
-$item
-</%args>
- <li <% $item->active ? 'class="active"' : '' |n %>><%
- Jifty->web->link(
- url => $item->url,
- label => $item->label,
- ) %></li>
-% if (my @kids = $item->children) {
-<ul id="submenu">
-% $m->comp(".menu", item => $_) for @kids;
-</ul>
-% }
-</%def>
commit cfee2d5723e7ef1749ff7b41254d884ea0e256fc
Author: Alex Vandiver <alexmv at bestpractical.com>
Date: Sat Mar 25 20:01:06 2006 +0000
Fix LetMes -- the dispatcher code needs to be abstracted out still, though
diff --git a/lib/Wifty/Dispatcher.pm b/lib/Wifty/Dispatcher.pm
index e1c5034..d6dd43e 100644
--- a/lib/Wifty/Dispatcher.pm
+++ b/lib/Wifty/Dispatcher.pm
@@ -91,7 +91,6 @@ on 'login', run {
set 'next' => Jifty->web->request->continuation
|| Jifty::Continuation->new(
request => Jifty::Request->new( path => "/" ) );
-
};
# Log out
@@ -102,5 +101,26 @@ before 'logout', run {
);
};
-
+
+## LetMes
+before qr'^/let/(.*)' => run {
+ Jifty->web->deny_actions(qr/.*/);
+
+ my $let_me = Jifty::LetMe->new();
+ $let_me->from_token($1);
+ redirect '/error/let_me/invalid_token' unless $let_me->validate;
+
+ Jifty->web->temporary_current_user($let_me->validated_current_user);
+
+ my %args = %{$let_me->args};
+ set $_ => $args{$_} for keys %args;
+ set let_me => $let_me;
+};
+
+on qr'^/let/', => run {
+ my $let_me = get 'let_me';
+ show '/let/' . $let_me->path;
+};
+
+
1;
diff --git a/web/templates/_elements/sidebar b/web/templates/_elements/sidebar
index 697f839..e849b39 100644
--- a/web/templates/_elements/sidebar
+++ b/web/templates/_elements/sidebar
@@ -1,7 +1,7 @@
<div id="salutation">
% if (Jifty->web->current_user->id and Jifty->web->current_user->user_object) {
Hiya, <span class="user"><%Jifty->web->current_user->user_object->name%></span>.<br />
-(<% Jifty->web->tangent( label => q{Logout}, url => '/logout' )%>)
+(<% Jifty->web->link( label => q{Logout}, url => '/logout' )%>)
% } else {
<% Jifty->web->tangent( label => q{You're not currently signed in.}, url => '/login' )%>
% }
diff --git a/web/templates/let/confirm_email b/web/templates/let/confirm_email
index 6ad94a4..8da28d2 100755
--- a/web/templates/let/confirm_email
+++ b/web/templates/let/confirm_email
@@ -1,14 +1,8 @@
-<%method setup_actions>
-<%perl>
+<%init>
Jifty->web->allow_actions( 'Wifty::Action::ConfirmEmail');
-Jifty->web->request->add_action(
+Jifty->web->new_action(
moniker => 'confirm_email',
class => 'Wifty::Action::ConfirmEmail',
-);
-Jifty->web->request->add_action(
- moniker => 'next_page',
- class => 'Jifty::Action::Redirect',
- arguments => {url => "/"},
-);
-</%perl>
-</%method>
+)->run;
+Jifty->web->redirect("/");
+</%init>
commit f6d11cb26b5ada50b4d6a12f14b0c88dc2fcfee0
Author: Alex Vandiver <alexmv at bestpractical.com>
Date: Wed Apr 5 00:19:15 2006 +0000
New allow and deny actions API
diff --git a/lib/Wifty/Dispatcher.pm b/lib/Wifty/Dispatcher.pm
index d6dd43e..6480c3e 100644
--- a/lib/Wifty/Dispatcher.pm
+++ b/lib/Wifty/Dispatcher.pm
@@ -3,7 +3,7 @@ use Jifty::Dispatcher -base;
# Generic restrictions
under '/', run {
- Jifty->web->deny_actions('Wifty::ConfirmEmail');
+ Jifty->api->deny('ConfirmEmail');
};
# Default page
@@ -104,7 +104,7 @@ before 'logout', run {
## LetMes
before qr'^/let/(.*)' => run {
- Jifty->web->deny_actions(qr/.*/);
+ Jifty->api->deny(qr/^Wifty::Dispatcher/);
my $let_me = Jifty::LetMe->new();
$let_me->from_token($1);
diff --git a/web/templates/let/confirm_email b/web/templates/let/confirm_email
index 8da28d2..86a69a1 100755
--- a/web/templates/let/confirm_email
+++ b/web/templates/let/confirm_email
@@ -1,5 +1,5 @@
<%init>
-Jifty->web->allow_actions( 'Wifty::Action::ConfirmEmail');
+Jifty->api->allow( 'ConfirmEmail');
Jifty->web->new_action(
moniker => 'confirm_email',
class => 'Wifty::Action::ConfirmEmail',
commit edf57d2bfee8fda9938c87daf214841cfe18dd8b
Author: Alex Vandiver <alexmv at bestpractical.com>
Date: Thu Apr 6 19:56:50 2006 +0000
Remove backup file
* Specify template and static directories
diff --git a/etc/config.yml b/etc/config.yml
index d256e28..eb96db9 100644
--- a/etc/config.yml
+++ b/etc/config.yml
@@ -13,3 +13,7 @@ framework:
# MailerArgs:
# - %log/mail.log%
SiteConfig: etc/site_config.yml
+
+ Web:
+ StaticRoot: share/web/static
+ TemplateRoot: share/web/templates
diff --git a/web/static/css/.base.css.swp b/web/static/css/.base.css.swp
deleted file mode 100644
index 79c4cf5..0000000
Binary files a/web/static/css/.base.css.swp and /dev/null differ
commit 85a981a0ce25005445597f597bd778db43728cd8
Author: Alex Vandiver <alexmv at bestpractical.com>
Date: Thu Apr 6 19:57:03 2006 +0000
Move files into share directory
diff --git a/web/static/css/app-base.css b/share/web/static/css/app-base.css
similarity index 100%
rename from web/static/css/app-base.css
rename to share/web/static/css/app-base.css
diff --git a/web/templates/_elements/markup b/share/web/templates/_elements/markup
similarity index 100%
rename from web/templates/_elements/markup
rename to share/web/templates/_elements/markup
diff --git a/web/templates/_elements/nav b/share/web/templates/_elements/nav
similarity index 100%
rename from web/templates/_elements/nav
rename to share/web/templates/_elements/nav
diff --git a/web/templates/_elements/page_nav b/share/web/templates/_elements/page_nav
similarity index 100%
rename from web/templates/_elements/page_nav
rename to share/web/templates/_elements/page_nav
diff --git a/web/templates/_elements/sidebar b/share/web/templates/_elements/sidebar
similarity index 100%
rename from web/templates/_elements/sidebar
rename to share/web/templates/_elements/sidebar
diff --git a/web/templates/create b/share/web/templates/create
similarity index 100%
rename from web/templates/create
rename to share/web/templates/create
diff --git a/web/templates/edit b/share/web/templates/edit
similarity index 100%
rename from web/templates/edit
rename to share/web/templates/edit
diff --git a/web/templates/history b/share/web/templates/history
old mode 100755
new mode 100644
similarity index 100%
rename from web/templates/history
rename to share/web/templates/history
diff --git a/web/templates/let/confirm_email b/share/web/templates/let/confirm_email
old mode 100755
new mode 100644
similarity index 100%
rename from web/templates/let/confirm_email
rename to share/web/templates/let/confirm_email
diff --git a/web/templates/login b/share/web/templates/login
old mode 100755
new mode 100644
similarity index 100%
rename from web/templates/login
rename to share/web/templates/login
diff --git a/web/templates/logout b/share/web/templates/logout
old mode 100755
new mode 100644
similarity index 100%
rename from web/templates/logout
rename to share/web/templates/logout
diff --git a/web/templates/pages b/share/web/templates/pages
similarity index 100%
rename from web/templates/pages
rename to share/web/templates/pages
diff --git a/web/templates/recent b/share/web/templates/recent
similarity index 100%
rename from web/templates/recent
rename to share/web/templates/recent
diff --git a/web/templates/signup b/share/web/templates/signup
old mode 100755
new mode 100644
similarity index 100%
rename from web/templates/signup
rename to share/web/templates/signup
diff --git a/web/templates/view b/share/web/templates/view
similarity index 100%
rename from web/templates/view
rename to share/web/templates/view
commit fa4cba8ff720c97dd72d054794907e9e4461b458
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Sun Apr 23 21:48:10 2006 +0000
We were using an illegal method call
diff --git a/lib/Wifty/Model/Page.pm b/lib/Wifty/Model/Page.pm
index 66d0e27..76f4fea 100644
--- a/lib/Wifty/Model/Page.pm
+++ b/lib/Wifty/Model/Page.pm
@@ -109,7 +109,7 @@ sub _add_revision {
sub set_content {
my $self = shift;
my $content = shift;
- my ( $val, $msg ) = $self->SUPER::set_content($content);
+ my ( $val, $msg ) = $self->_set(column => 'content', value => $content);
$self->_add_revision( content => $content,
updated_by =>( $self->current_user? $self->current_user->user_object : undef )
);
commit e4df55d05b057c9be536806a636dfe80c7a1181f
Author: Alex Vandiver <alexmv at bestpractical.com>
Date: Wed May 3 20:36:36 2006 +0000
Diffs of revisions
diff --git a/Makefile.PL b/Makefile.PL
index 456dedf..e79fd94 100644
--- a/Makefile.PL
+++ b/Makefile.PL
@@ -4,4 +4,5 @@ version('0.01');
requires('Jifty');
requires('Text::Markdown');
requires('HTML::Scrubber');
+requires('Text::Diff::HTML');
WriteAll;
diff --git a/lib/Wifty/Model/Revision.pm b/lib/Wifty/Model/Revision.pm
index 8291192..46e36f9 100644
--- a/lib/Wifty/Model/Revision.pm
+++ b/lib/Wifty/Model/Revision.pm
@@ -30,6 +30,50 @@ sub create {
}
+sub previous {
+ my $self = shift;
+ return undef unless $self->id;
+
+ my $revisions = Wifty::Model::RevisionCollection->new;
+ $revisions->limit(
+ column => 'page',
+ value => $self->page->id,
+ quote_value => 0,
+ case_sensitive => 1
+ );
+ $revisions->limit(
+ column => 'id',
+ operator => '<',
+ value => $self->id,
+ quote_value => 0,
+ case_sensitive => 1
+ );
+ $revisions->order_by( { column => 'id' } );
+ return $revisions->last;
+}
+
+sub next {
+ my $self = shift;
+ return undef unless $self->id;
+
+ my $revisions = Wifty::Model::RevisionCollection->new;
+ $revisions->limit(
+ column => 'page',
+ value => $self->page->id,
+ quote_value => 0,
+ case_sensitive => 1
+ );
+ $revisions->limit(
+ column => 'id',
+ operator => '>',
+ value => $self->id,
+ quote_value => 0,
+ case_sensitive => 1
+ );
+ $revisions->order_by( { column => 'id' } );
+ return $revisions->first;
+}
+
=head2 current_user_can RIGHT
We're using L<Jifty::RightsFrom> to pass off ACL decisions to this
diff --git a/share/web/static/css/app-base.css b/share/web/static/css/app-base.css
index 638d94b..de47f2e 100644
--- a/share/web/static/css/app-base.css
+++ b/share/web/static/css/app-base.css
@@ -61,6 +61,8 @@ div#syntax {
right: 2em;
}
-
-
-
+.file span { display: block; clear: both; }
+.file .fileheader, .file .hunkheader {color: #888; }
+.file .hunk .ctx { background: #eee;}
+.file .hunk ins { background: #dfd; text-decoration: none; display: block; }
+.file .hunk del { background: #fdd; text-decoration: none; display: block; }
diff --git a/share/web/templates/_elements/diff b/share/web/templates/_elements/diff
new file mode 100644
index 0000000..858d4c0
--- /dev/null
+++ b/share/web/templates/_elements/diff
@@ -0,0 +1,24 @@
+<%args>
+$page
+$from =>undef
+$to => undef
+</%args>
+<%init>
+
+$to ||= $page->revisions->last;
+$from ||= $to->previous || Wifty::Model::Revision->new;
+
+my $before = $to->previous;
+my $after = $to->next;
+
+use Text::Diff ();
+my $diff = Text::Diff::diff(\($from->content), \($to->content), { STYLE => 'Text::Diff::HTML' });
+
+</%init>
+% if ($before) {
+<span style="float: left"><% Jifty->web->link(url => "/view/".$page->name."/".$before->id, label => "Previous revision") %></span>
+% }
+% if ($after) {
+<span style="float: right"><% Jifty->web->link(url => "/view/".$page->name."/".$after->id, label => "Next revision") %></span>
+% }
+<pre><% $diff |n%></pre><hr />
diff --git a/share/web/templates/view b/share/web/templates/view
index f81e26e..35c7668 100644
--- a/share/web/templates/view
+++ b/share/web/templates/view
@@ -5,6 +5,8 @@ $revision
<& /_elements/page_nav, page => $page->name, rev => $revision->id &>
<&|/_elements/wrapper, title => $page->name . ($revision->id ? " as of ".$revision->created : '') &>
% if ($revision->id) {
+<& _elements/diff, page => $page, to => $revision &>
+
<% $page->wiki_content($revision->content) |n%>
% } else {
<% $page->wiki_content |n %>
commit 324075cf48863db338e1c29adde74fe2ad07d47f
Author: Eric Wilhelm <ewilhelm at cpan.org>
Date: Thu May 4 18:45:24 2006 +0000
share/web/templates/_elements/markup - made 'Wiki Syntax Help' a DHTML flyout
share/web/static/css/app-base.css - width tweak
diff --git a/share/web/static/css/app-base.css b/share/web/static/css/app-base.css
index de47f2e..0416cc6 100644
--- a/share/web/static/css/app-base.css
+++ b/share/web/static/css/app-base.css
@@ -55,7 +55,7 @@ div#syntax {
border: 1px solid #333;
padding: 3px;
font-size: 0.8em;
- width: 25%;
+ width: 20em;
position: absolute;
top: 10em;
right: 2em;
diff --git a/share/web/templates/_elements/markup b/share/web/templates/_elements/markup
index b6b8cca..2fcea61 100644
--- a/share/web/templates/_elements/markup
+++ b/share/web/templates/_elements/markup
@@ -1,5 +1,32 @@
+<script>
+ // javascript flyout by Eric Wilhelm
+ // TODO use images for minimize/maximize button
+ var targetDiv = 'syntax_content';
+ var divCont;
+ var effectDone = false;
+ var minimize = '-';
+ var maximize = '+';
+
+ function toggleEffect() {
+ if ( !effectDone ) {
+ effectDone = true;
+ divCont = $(targetDiv).innerHTML;
+ $(targetDiv).innerHTML = '';
+ $('toggle').innerHTML = maximize;
+ }
+ else {
+ effectDone = false;
+ $(targetDiv).innerHTML = divCont;
+ $('toggle').innerHTML = minimize;
+ }
+ }
+
+</script>
<div id="syntax">
-<h2>Wiki Syntax</h2>
+<div><a href="javascript:toggleEffect()"><b>Wiki Syntax Help</b></a>
+<a id="toggle" style="color:#5b5b5b;text-decoration:none;" href="javascript:toggleEffect()"></a>
+</div>
+<div id="syntax_content">
<h3>Phrase Emphasis</h3>
@@ -54,4 +81,8 @@ by at least 4 spaces.</p>
<p>Three or more dashes: <code>---</code></p>
<address>(Thanks to <a href="http://daringfireball.net/projects/markdown/dingus">Daring Fireball</a>)</address>
+</div>
</div>
+<script>
+toggleEffect();
+</script>
commit 94aa491e5756101ba8b49668a022e5a15175d4b9
Author: Eric Wilhelm <ewilhelm at cpan.org>
Date: Thu May 4 19:31:23 2006 +0000
share/web/templates/_elements/markup - just using scriptaculous Element.toggle() for now (has no hooks for minimize/maximize icon switching?)
diff --git a/share/web/templates/_elements/markup b/share/web/templates/_elements/markup
index 2fcea61..7bfa695 100644
--- a/share/web/templates/_elements/markup
+++ b/share/web/templates/_elements/markup
@@ -1,30 +1,5 @@
-<script>
- // javascript flyout by Eric Wilhelm
- // TODO use images for minimize/maximize button
- var targetDiv = 'syntax_content';
- var divCont;
- var effectDone = false;
- var minimize = '-';
- var maximize = '+';
-
- function toggleEffect() {
- if ( !effectDone ) {
- effectDone = true;
- divCont = $(targetDiv).innerHTML;
- $(targetDiv).innerHTML = '';
- $('toggle').innerHTML = maximize;
- }
- else {
- effectDone = false;
- $(targetDiv).innerHTML = divCont;
- $('toggle').innerHTML = minimize;
- }
- }
-
-</script>
<div id="syntax">
-<div><a href="javascript:toggleEffect()"><b>Wiki Syntax Help</b></a>
-<a id="toggle" style="color:#5b5b5b;text-decoration:none;" href="javascript:toggleEffect()"></a>
+<div><a href="javascript:;" onclick="Element.toggle('syntax_content');return(false);"><b>Wiki Syntax Help</b></a>
</div>
<div id="syntax_content">
@@ -84,5 +59,8 @@ by at least 4 spaces.</p>
</div>
</div>
<script>
-toggleEffect();
+ // javascript flyout by Eric Wilhelm
+ // TODO use images for minimize/maximize button
+ // Is there a way to add a callback?
+ Element.toggle('syntax_content');
</script>
commit 3fa93108747e6903145536d9612faa33fca75bdf
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Sun May 7 22:27:10 2006 +0000
Cleaned up the edit page after the CSS change
diff --git a/share/web/templates/edit b/share/web/templates/edit
index 380691e..f0c7bee 100644
--- a/share/web/templates/edit
+++ b/share/web/templates/edit
@@ -7,11 +7,7 @@ $viewer
<&|/_elements/wrapper, title => 'Edit: '.$page->name . ($revision->id ? " as of ".$revision->created : '') &>
<% Jifty->web->form->start %>
<% Jifty->web->form->next_page( url => '/view/'.$page->name) %>
-% if ($revision->id) {
-<% $viewer->form_field('content', default_value => $revision->content )%>
-% } else {
-<% $viewer->form_field('content') %>
-% }
+<% $viewer->form_field('content', ($revision->id ? (default_value => $revision->content) : (undef, undef)), rows=> 30, cols => 80 )%>
<% Jifty->web->form->submit( label => 'Save') %>
<% Jifty->web->form->end %>
<& /_elements/markup &>
commit b31a1b9b0a59f64d4ee6304a50d59eb06f4e194c
Author: Nelson Elhage <nelhage at bestpractical.com>
Date: Tue May 9 22:51:15 2006 +0000
Typo fix
diff --git a/lib/Wifty/Model/Revision.pm b/lib/Wifty/Model/Revision.pm
index 46e36f9..8b9eadd 100644
--- a/lib/Wifty/Model/Revision.pm
+++ b/lib/Wifty/Model/Revision.pm
@@ -80,7 +80,7 @@ We're using L<Jifty::RightsFrom> to pass off ACL decisions to this
update's page. But we need to make sure that page history entries aren't
editable, except by superusers. So we override C<current_user_can>
to give the arguments a brief massage before handing off to
-C<urrent_user_can> (which we inherit).
+C<current_user_can> (which we inherit).
=cut
commit be8d64ea2cca86b22ac68005d810bdc14ba701be
Author: Nelson Elhage <nelhage at bestpractical.com>
Date: Wed May 10 22:14:08 2006 +0000
Fixing the saving of updated_by by Wifty, and keeping track of
revisions' authors.
diff --git a/etc/config.yml b/etc/config.yml
index eb96db9..b92a460 100644
--- a/etc/config.yml
+++ b/etc/config.yml
@@ -6,7 +6,7 @@ framework:
Driver: SQLite
Host: localhost
User: postgres
- Version: 0.0.19
+ Version: 0.0.20
Password: ''
RequireSSL: 0
# Mailer: IO
diff --git a/lib/Wifty/Model/Page.pm b/lib/Wifty/Model/Page.pm
index 76f4fea..4b4bf18 100644
--- a/lib/Wifty/Model/Page.pm
+++ b/lib/Wifty/Model/Page.pm
@@ -99,9 +99,9 @@ sub _add_revision {
my $rev = Wifty::Model::Revision->new( current_user => Wifty::CurrentUser->superuser);
$rev->create(
- page => $self->id,
- content => $args{'content'},
- # by => $args{'updated_by'}
+ page => $self->id,
+ content => $args{'content'},
+ created_by => $args{'updated_by'}
);
}
@@ -127,9 +127,7 @@ sub _set {
$self->SUPER::_set(
column => 'updated_by',
- value =>
- ( $self->current_user? $self->current_user->user_object : undef )
-
+ value => ( $self->current_user? $self->current_user->user_object->id : undef )
);
return ( $val, $msg );
@@ -138,7 +136,7 @@ sub _set {
=head2 current_user_can ACTION
-Let everybody create, read and update pages, but not delete the.
+Let everybody create, read and update pages, but not delete them.
=cut
diff --git a/lib/Wifty/Model/Revision.pm b/lib/Wifty/Model/Revision.pm
index 8b9eadd..7fe23b3 100644
--- a/lib/Wifty/Model/Revision.pm
+++ b/lib/Wifty/Model/Revision.pm
@@ -7,7 +7,7 @@ column content => type is 'text', render_as 'textarea';
column created => type is 'timestamp';
-#column by => refers_to Wifty::Model::User, since '0.0.18';
+column created_by => refers_to Wifty::Model::User, since '0.0.20';
package Wifty::Model::Revision;
diff --git a/share/web/templates/history b/share/web/templates/history
index 41d133b..34c9db3 100644
--- a/share/web/templates/history
+++ b/share/web/templates/history
@@ -8,7 +8,13 @@ $revisions
% while (my $rev = $revisions->next) {
<dt><% Jifty->web->link( label => $rev->created,
url => '/view/'.$page->name.'/'.$rev->id
- ) %></dt>
+ ) %>
+% if($rev->created_by) {
+ (<% $rev->created_by->name %>)
+% } else {
+ (Anonymous)
+% }
+</dt>
<dd><%length($rev->content)%> bytes</dd>
% }
</ul>
diff --git a/share/web/templates/recent b/share/web/templates/recent
index 2d1941e..dd18b2b 100644
--- a/share/web/templates/recent
+++ b/share/web/templates/recent
@@ -5,7 +5,13 @@ $pages
<dl id="recentudates">
% while (my $page = $pages->next) {
<dt><% Jifty->web->link( label => $page->name, url => '/view/'.$page->name)%></dt>
-<dd><%$page->updated%></dd>
-% }
+<dd><%$page->updated%>
+% if($page->updated_by) {
+ (<% $page->updated_by->name %>)
+% } else {
+ (Anonymous)
+% }
+</dd>
+% }
</dl>
</&>
commit 445c15cb1d666862ecf957d0f5fc92e28ee4ce34
Author: Nelson Elhage <nelhage at bestpractical.com>
Date: Sat May 13 20:25:50 2006 +0000
Fixing dependencies between Wifty models
diff --git a/lib/Wifty/Model/Page.pm b/lib/Wifty/Model/Page.pm
index 4b4bf18..cdb4f44 100644
--- a/lib/Wifty/Model/Page.pm
+++ b/lib/Wifty/Model/Page.pm
@@ -1,5 +1,6 @@
package Wifty::Model::Page::Schema;
use Jifty::DBI::Schema;
+use Wifty::Model::User;
column name =>
type is 'text',
commit 8c0bdf25b8a092b0e147223356bcdb369dfbf4e2
Author: Nelson Elhage <nelhage at bestpractical.com>
Date: Sat May 13 21:04:11 2006 +0000
Adding some additional Wifty Model tests
diff --git a/t/01-models.t b/t/01-models.t
new file mode 100644
index 0000000..289db28
--- /dev/null
+++ b/t/01-models.t
@@ -0,0 +1,42 @@
+#!/usr/bin/perl -w
+use warnings;
+use strict;
+
+=head1 DESCRIPTION
+
+A slightly more complicated test harness for the interactions between
+model classes.
+
+=cut
+
+
+use Jifty::Test tests => 9;
+
+use_ok('Wifty::Model::Page');
+use_ok('Wifty::Model::User');
+use_ok('Wifty::Model::Revision');
+
+my $system_user = Wifty::CurrentUser->superuser;
+
+my $user = Wifty::Model::User->new(current_user => $system_user);
+$user->create(email => 'test at email', name => 'Test User');
+ok($user, "Created a user model object");
+
+my $current_user = Wifty::CurrentUser->new(id => $user->id);
+ok($current_user, "Created a Wifty::CurrentUser");
+
+my $page = Wifty::Model::Page->new(current_user => $current_user);
+$page->create(name => "TestPage", content => "Test Content");
+is($page->updated_by->id, $user->id, "Model::Page set updated_by correctly");
+
+$page->set(content => "Second test");
+
+my $revs = Wifty::Model::RevisionCollection->new(current_user => $current_user);
+$revs->limit(column => "page", value => $page->id);
+
+is($revs->count, 1, "Model::Page stored a revision");
+
+my $revision = $revs->next;
+
+is($revision->page->id, $page->id, "Revision is of the correct page");
+is($revision->created_by->id, $current_user->id, "Revision has the correct creator");
commit 9cba41808a3e0b52dd736d07a329da40986d8818
Author: Nelson Elhage <nelhage at bestpractical.com>
Date: Mon May 15 19:24:30 2006 +0000
Commenting some tests
diff --git a/t/01-models.t b/t/01-models.t
index 289db28..95fb190 100644
--- a/t/01-models.t
+++ b/t/01-models.t
@@ -18,6 +18,7 @@ use_ok('Wifty::Model::Revision');
my $system_user = Wifty::CurrentUser->superuser;
+# Create a test user
my $user = Wifty::Model::User->new(current_user => $system_user);
$user->create(email => 'test at email', name => 'Test User');
ok($user, "Created a user model object");
@@ -25,12 +26,14 @@ ok($user, "Created a user model object");
my $current_user = Wifty::CurrentUser->new(id => $user->id);
ok($current_user, "Created a Wifty::CurrentUser");
+#Create a page and check it
my $page = Wifty::Model::Page->new(current_user => $current_user);
$page->create(name => "TestPage", content => "Test Content");
is($page->updated_by->id, $user->id, "Model::Page set updated_by correctly");
$page->set(content => "Second test");
+# Make sure the page is creating revisions
my $revs = Wifty::Model::RevisionCollection->new(current_user => $current_user);
$revs->limit(column => "page", value => $page->id);
commit 32f066f6104ce3ee0f79921b0056bff9008d9b55
Author: Nelson Elhage <nelhage at bestpractical.com>
Date: Mon May 15 19:24:33 2006 +0000
Removing an errant semicolon in the pages template
diff --git a/share/web/templates/pages b/share/web/templates/pages
index c157fd1..c39f5ae 100644
--- a/share/web/templates/pages
+++ b/share/web/templates/pages
@@ -8,7 +8,7 @@ $pages
Jifty->web->link(
label => $page->name,
url => '/view/' . $page->name
- );
+ )
%></li>
% }
commit 7eb648940d236a1e0d74f9124bca0f96e3c77a88
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Sun May 28 04:49:25 2006 +0000
(empty commit message)
commit 0f75399b1a8c2b46676d1c8fe2f5c831dd56945a
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Sun May 28 04:49:34 2006 +0000
CSS tweaks from beppu
diff --git a/share/web/static/css/app-base.css b/share/web/static/css/app-base.css
index 0416cc6..275e4d7 100644
--- a/share/web/static/css/app-base.css
+++ b/share/web/static/css/app-base.css
@@ -1,6 +1,6 @@
body {
background-color: #dddddd;
-
+ font-size: 85%;
}
@@ -15,6 +15,10 @@ div#headers h1 {
}
+div#headers { margin-left: 10px; }
+div#headers a { position: absolute; top: 0.5em; right: 1em; }
+
+
a {
color: black;
font-style: bold;
commit 98764189e5a270fc8ef9b1f490a5d9b5a6161574
Author: Nelson Elhage <nelhage at bestpractical.com>
Date: Sat Jul 1 20:35:24 2006 +0000
Jifty::Test -> Wifty::Test
diff --git a/t/01-models.t b/t/01-models.t
index 95fb190..962aebb 100644
--- a/t/01-models.t
+++ b/t/01-models.t
@@ -10,7 +10,7 @@ model classes.
=cut
-use Jifty::Test tests => 9;
+use Wifty::Test tests => 9;
use_ok('Wifty::Model::Page');
use_ok('Wifty::Model::User');
commit b7dbbb26216a4b71cd6d68ca149b2b85085ca8e8
Author: Nelson Elhage <nelhage at bestpractical.com>
Date: Sat Jul 1 20:35:47 2006 +0000
Adding some test users
diff --git a/lib/Wifty/Test.pm b/lib/Wifty/Test.pm
new file mode 100644
index 0000000..3aeb25e
--- /dev/null
+++ b/lib/Wifty/Test.pm
@@ -0,0 +1,53 @@
+use warnings;
+use strict;
+
+package Wifty::Test;
+use base qw/Jifty::Test/;
+
+=head2 setup
+
+Set up for testing. Calls L<Jifty::Test/setup> and L</setup_db>.
+
+=cut
+
+sub setup {
+ my $class = shift;
+ $class->SUPER::setup;
+ $class->setup_db;
+}
+
+=head2 setup_db
+
+Add two users to the database:
+
+Some User <someuser at localhost>, password 'sekrit'
+Other User <otheruser at localhost>, password 'motdepasse'
+
+This should be kept in sync with C<t/0-test-database>.
+
+=cut
+
+sub setup_db {
+ my $class = shift;
+
+ my $admin = Wifty::CurrentUser->superuser;
+
+ my $someuser = Wifty::Model::User->new(current_user => $admin);
+ $someuser->create(
+ name => 'Some User',
+ email => 'someuser at localhost',
+ password => 'sekrit',
+ email_confirmed => 1,
+ );
+
+ my $otheruser = Wifty::Model::User->new(current_user => $admin);
+ $otheruser->create(
+ name => 'Other User',
+ email => 'otheruser at localhost',
+ password => 'motdepasse',
+ email_confirmed => 1,
+ );
+
+}
+
+1;
commit 126512772ae2b649da7c2031c0673c13162c26b1
Author: Nelson Elhage <nelhage at bestpractical.com>
Date: Sat Jul 1 20:36:12 2006 +0000
Test the users created in Wifty::Test
diff --git a/t/0-test-database.t b/t/0-test-database.t
new file mode 100644
index 0000000..d836aa1
--- /dev/null
+++ b/t/0-test-database.t
@@ -0,0 +1,35 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+
+=head1 DESCRIPTION
+
+Test the models set up by L<Wifty::Test>
+
+=cut
+
+use Wifty::Test no_plan => 1;
+
+my $admin = Wifty::CurrentUser->superuser;
+
+my $users = Wifty::Model::UserCollection->new(current_user => $admin);
+isa_ok($users, 'Wifty::Model::UserCollection');
+
+$users->unlimit;
+
+is($users->count, 2, "Got two users");
+
+my $user = $users->next;
+
+isa_ok($user, 'Wifty::Model::User');
+is($user->name, 'Some User', 'name ok');
+is($user->email, 'someuser at localhost', 'email ok');
+ok($user->password_is('sekrit'), 'password ok');
+is($user->email_confirmed, '1');
+
+$user = $users->next;
+isa_ok($user, 'Wifty::Model::User');
+is($user->name, 'Other User', 'name ok');
+is($user->email, 'otheruser at localhost', 'email ok');
+ok($user->password_is('motdepasse'), 'password ok');
+is($user->email_confirmed, '1');
commit 5c45f9708825f0637d129b3fb9e1338621506962
Author: Nelson Elhage <nelhage at bestpractical.com>
Date: Sat Jul 1 20:36:39 2006 +0000
Don't you love plans?
diff --git a/t/0-test-database.t b/t/0-test-database.t
index d836aa1..39d523b 100644
--- a/t/0-test-database.t
+++ b/t/0-test-database.t
@@ -8,7 +8,7 @@ Test the models set up by L<Wifty::Test>
=cut
-use Wifty::Test no_plan => 1;
+use Wifty::Test test => 12;
my $admin = Wifty::CurrentUser->superuser;
commit b472c8fc8c97ceb66ae8263a48013ed7c4777e36
Author: Nelson Elhage <nelhage at bestpractical.com>
Date: Sat Jul 1 20:37:02 2006 +0000
This was breaking tests
diff --git a/lib/Wifty/Model/User.pm b/lib/Wifty/Model/User.pm
index a0b23ee..9a61bf1 100644
--- a/lib/Wifty/Model/User.pm
+++ b/lib/Wifty/Model/User.pm
@@ -90,12 +90,11 @@ sub current_user_can {
my $self = shift;
my $right = shift;
my %args = (@_);
- Carp::confess if ($right eq 'read' and not $args{'column'});
if ( $right eq 'read'
and $self->id == $self->current_user->id )
{
return 1;
- } elsif ( $right eq 'read' and $args{'column'} eq 'name' ) {
+ } elsif ( $right eq 'read' and $args{'column'} and $args{'column'} eq 'name' ) {
return (1);
} elsif ( $right eq 'update'
commit e1388eb49af73d8b8e138ba2cc0c34d824844336
Author: Nelson Elhage <nelhage at bestpractical.com>
Date: Sat Jul 1 20:37:28 2006 +0000
Fixing t/0-test-database and adding login tests
diff --git a/t/0-test-database.t b/t/0-test-database.t
index 39d523b..3a5330d 100644
--- a/t/0-test-database.t
+++ b/t/0-test-database.t
@@ -8,7 +8,7 @@ Test the models set up by L<Wifty::Test>
=cut
-use Wifty::Test test => 12;
+use Wifty::Test tests => 12;
my $admin = Wifty::CurrentUser->superuser;
diff --git a/t/02-login.t b/t/02-login.t
new file mode 100644
index 0000000..87bf108
--- /dev/null
+++ b/t/02-login.t
@@ -0,0 +1,55 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+
+=head1 DESCRIPTION
+
+Test that we can log in to Wifty
+
+=cut
+
+use constant PER_TRIAL => 3;
+
+use Wifty::Test tests => 5 + PER_TRIAL * 4;
+use Jifty::Test::WWW::Mechanize;
+
+my $server = Wifty::Test->make_server;
+
+my $URL = $server->started_ok;
+
+ok($URL, "Started a test server");
+
+my $mech = Jifty::Test::WWW::Mechanize->new();
+
+$mech->get_ok($URL, "Got the homepage");
+ok($mech->find_link(text_regex => qr/currently signed in/), 'Got the signin link');
+$mech->follow_link_ok(text_regex => qr/currently signed in/);
+
+sub try_login {
+ my $mech = shift;
+ my $user = shift;
+ my $pass = shift;
+
+ {
+ local $Test::Builder::Level = $Test::Builder::Level;
+ $Test::Builder::Level++;
+ $mech->fill_in_action_ok('loginbox', email => $user, password => $pass);
+ $mech->submit_html_ok();
+ }
+}
+
+# Try logging in with a bad user
+try_login($mech, 'baduser at localhost', 'notmypassword');
+$mech->content_contains('No account has that email address', "Login failed with bad username");
+
+# With a blank password
+try_login($mech, 'someuser at localost', '');
+$mech->content_contains('need to fill in this field','Login fails with no password');
+
+# With the wrong password
+try_login($mech, 'someuser at localhost', 'badmemory');
+$mech->content_contains('may have mistyped','Login fails with wrong password');
+
+# Try a correct login
+try_login($mech, 'someuser at localhost', 'sekrit');
+$mech->content_contains('Welcome back','Logged in');
commit e7159328976f0d6c84b03159e6e7fe357237088b
Author: Audrey Tang <audreyt at audreyt.org>
Date: Fri Jul 21 06:53:18 2006 +0000
Wifty: Updated actions to use declarative parameters.
diff --git a/lib/Wifty/Action/Login.pm b/lib/Wifty/Action/Login.pm
index ed12a5a..09643bc 100644
--- a/lib/Wifty/Action/Login.pm
+++ b/lib/Wifty/Action/Login.pm
@@ -8,32 +8,27 @@ Wifty::Action::Login
=cut
package Wifty::Action::Login;
-use base qw/Wifty::Action Jifty::Action/;
-
-=head2 arguments
-
-Return the email and password form fields
-
-=cut
-
-sub arguments {
- return( { email => { label => 'Email address',
- mandatory => 1,
- ajax_validates => 1,
- } ,
-
- password => { type => 'password',
- label => 'Password',
- mandatory => 1
- },
- remember => { type => 'checkbox',
- label => 'Remember me?',
- hints => 'If you want, your browser can remember your login for you',
- default => 0,
- }
- });
-
-}
+use base qw/Wifty::Action/;
+use Jifty::Param::Schema;
+use Jifty::Action schema {
+
+param email =>
+ label is 'Email address',
+ is mandatory,
+ ajax validates;
+
+param password =>
+ type is 'password',
+ label is 'Password',
+ is mandatory;
+
+param remember =>
+ type is 'checkbox',
+ label is 'Remember me?',
+ hints is 'If you want, your browser can remember your login for you',
+ default is 0;
+
+};
=head2 validate_email ADDRESS
diff --git a/lib/Wifty/Action/Logout.pm b/lib/Wifty/Action/Logout.pm
index 697b9f6..3a35611 100644
--- a/lib/Wifty/Action/Logout.pm
+++ b/lib/Wifty/Action/Logout.pm
@@ -10,16 +10,6 @@ Wifty::Action::Logout
package Wifty::Action::Logout;
use base qw/Wifty::Action Jifty::Action/;
-=head2 arguments
-
-Return the email and password form fields
-
-=cut
-
-sub arguments {
- return( { });
-}
-
=head2 take_action
Nuke the current user object
diff --git a/lib/Wifty/Action/ResetLostPassword.pm b/lib/Wifty/Action/ResetLostPassword.pm
index 813a781..a98292f 100755
--- a/lib/Wifty/Action/ResetLostPassword.pm
+++ b/lib/Wifty/Action/ResetLostPassword.pm
@@ -14,23 +14,22 @@ address is really theirs, when claiming that they lost their password.
=cut
package Wifty::Action::ResetPassword;
-use base qw/Wifty::Action Jifty::Action/;
-
use Wifty::Model::User;
+use base qw/Wifty::Action/;
-=head2 arguments
+use Jifty::Param::Schema;
+use Jifty::Action schema {
-ConfirmEmail has the following fields: address, code, password, and password_confirm.
-Note that it can get the first two from the confirm dhandler.
+param password =>
+ type is 'password',
+ ! is sticky;
-=cut
+param password_confirm =>
+ type is 'password',
+ label is 'type your password again',
+ ! is sticky;
-sub arguments {
- return( {
- password => { type => 'password', sticky => 0 },
- password_confirm => { type => 'password', sticky => 0, label => 'type your password again' },
- });
-}
+};
=head2 take_action
diff --git a/lib/Wifty/Action/SendAccountConfrimation.pm b/lib/Wifty/Action/SendAccountConfrimation.pm
index dc30667..1073862 100755
--- a/lib/Wifty/Action/SendAccountConfrimation.pm
+++ b/lib/Wifty/Action/SendAccountConfrimation.pm
@@ -8,27 +8,21 @@ Wifty::Action::ResendConfirmation
=cut
package Wifty::Action::ResendConfirmation;
-use base qw/Wifty::Action Jifty::Action/;
-
-__PACKAGE__->mk_accessors(qw(user_object));
use Wifty::Model::User;
+use base qw/Wifty::Action/;
-=head2 arguments
-
-The field for C<ResendConfirmation> is:
-
-=over 4
-
-=item address: the email address
+__PACKAGE__->mk_accessors(qw(user_object));
-=back
+use Jifty::Param::Schema;
+use Jifty::Action schema {
-=cut
+param address =>
+ label is 'email address',
+ is mandatory,
+ default is '';
-sub arguments {
- return ( { address => { label => 'email address', mandatory => 1, default_value => "", }, });
-}
+};
=head2 setup
commit 72dbbffdaa4e2723cd0069c1fd3f0abb9b021acf
Author: Nelson Elhage <nelhage at bestpractical.com>
Date: Wed Jul 26 14:56:17 2006 +0000
Fixing history display with anonymous edits
diff --git a/share/web/templates/history b/share/web/templates/history
index 34c9db3..f44c6b4 100644
--- a/share/web/templates/history
+++ b/share/web/templates/history
@@ -9,7 +9,7 @@ $revisions
<dt><% Jifty->web->link( label => $rev->created,
url => '/view/'.$page->name.'/'.$rev->id
) %>
-% if($rev->created_by) {
+% if($rev->created_by->id) {
(<% $rev->created_by->name %>)
% } else {
(Anonymous)
commit 903410aa12ede77243fd55cbbdcbec5c788e6611
Author: Nelson Elhage <nelhage at bestpractical.com>
Date: Wed Jul 26 14:56:52 2006 +0000
Adding simple access controls -- the ability to require users to be logged in to edit and create pages
diff --git a/lib/Wifty/Dispatcher.pm b/lib/Wifty/Dispatcher.pm
index 6480c3e..e9bbfcd 100644
--- a/lib/Wifty/Dispatcher.pm
+++ b/lib/Wifty/Dispatcher.pm
@@ -15,7 +15,13 @@ on '/', run {
on '/create/*', run {
set page => $1;
set action => Jifty->web->new_action( class => 'CreatePage' );
- show("/create");
+
+ my $p = Wifty::Model::Page->new();
+ if($p->current_user_can('create')) {
+ show("/create");
+ } else {
+ show("/no_such_page");
+ }
};
# View or edit a page
diff --git a/lib/Wifty/Model/Page.pm b/lib/Wifty/Model/Page.pm
index cdb4f44..54f3d7a 100644
--- a/lib/Wifty/Model/Page.pm
+++ b/lib/Wifty/Model/Page.pm
@@ -137,7 +137,9 @@ sub _set {
=head2 current_user_can ACTION
-Let everybody create, read and update pages, but not delete them.
+Let everybody read pages. If RequireAuth is set in the app config,
+only allow logged-in users to create and edit pages. Otherwise, allow
+anyone.
=cut
@@ -145,12 +147,17 @@ sub current_user_can {
my $self = shift;
my $type = shift;
- # We probably want something like this eventually:
- if ($type =~ /(?:create|read|update)/i) {
+ if ($type eq 'create' || $type eq 'update') {
+ return 0 if
+ Jifty->config->app('RequireAuth')
+ && !$self->current_user->is_superuser
+ && !$self->current_user->id;
+ return 1;
+ } elsif($type eq 'read') {
return 1;
- } else {
- return $self->SUPER::current_user_can($type, @_);
}
+
+ return $self->SUPER::current_user_can($type, @_);
}
1;
diff --git a/share/web/templates/_elements/page_nav b/share/web/templates/_elements/page_nav
index 61993fd..4b27a7f 100644
--- a/share/web/templates/_elements/page_nav
+++ b/share/web/templates/_elements/page_nav
@@ -8,9 +8,11 @@ my $this = $top->child(
sort_order => 5
);
+my $page_obj = Wifty::Model::Page->new();
+$page_obj->load_by_cols(name => $page);
$this->child( View => url => '/view/'.$subpath);
-$this->child( Edit => url => '/edit/'.$subpath);
+$this->child( Edit => url => '/edit/'.$subpath) if $page_obj->current_user_can('update');
$this->child( History => url => '/history/'.$page);
$this->child( Latest => url => '/view/'.$page) if ($rev);
diff --git a/share/web/templates/edit b/share/web/templates/edit
index f0c7bee..d8ca040 100644
--- a/share/web/templates/edit
+++ b/share/web/templates/edit
@@ -3,12 +3,23 @@ $page
$revision
$viewer
</%args>
+<%init>
+my $can_edit = $page->current_user_can('update');
+</%init>
<&/_elements/page_nav, page => $page->name, rev => $revision->id &>
<&|/_elements/wrapper, title => 'Edit: '.$page->name . ($revision->id ? " as of ".$revision->created : '') &>
<% Jifty->web->form->start %>
+% unless($can_edit) {
+ <p> You don't have permission to edit this page. Perhaps
+ <% Jifty->web->tangent(url => '/login', label => 'logging in') %>
+ would help. In the mean time, though, you're welcome to view and
+ copy the source of this page. </p>
+% }
<% Jifty->web->form->next_page( url => '/view/'.$page->name) %>
<% $viewer->form_field('content', ($revision->id ? (default_value => $revision->content) : (undef, undef)), rows=> 30, cols => 80 )%>
+% if($can_edit) {
<% Jifty->web->form->submit( label => 'Save') %>
+% }
<% Jifty->web->form->end %>
<& /_elements/markup &>
</&>
diff --git a/share/web/templates/no_such_page b/share/web/templates/no_such_page
new file mode 100644
index 0000000..fbdc076
--- /dev/null
+++ b/share/web/templates/no_such_page
@@ -0,0 +1,11 @@
+<&|/_elements/wrapper, title => 'No such page: '. $page&>
+
+ <p>Unfortunately, you've tried to reach a page that doesn't exist
+ yet, and you don't have permissions to create pages. If you
+ <% Jifty->web->tangent(url => '/login', label => 'login') %>,
+ you'll be able to create new pages of your own.</p>
+
+</&>
+<%args>
+$page => undef
+</%args>
commit c94d216a00e5ff9585d2a1a1a9ad09b16e9c0dac
Author: Nelson Elhage <nelhage at bestpractical.com>
Date: Wed Jul 26 14:57:30 2006 +0000
Adding commented-out RequireAuth to config.yml
diff --git a/etc/config.yml b/etc/config.yml
index b92a460..c48dc8d 100644
--- a/etc/config.yml
+++ b/etc/config.yml
@@ -17,3 +17,5 @@ framework:
Web:
StaticRoot: share/web/static
TemplateRoot: share/web/templates
+application:
+# RequireAuth: 1
commit 0f1bac5e31f479d9c507ddca9147f666b1d449ac
Author: Nelson Elhage <nelhage at bestpractical.com>
Date: Wed Jul 26 15:44:35 2006 +0000
Fixing recent for anonymous users
diff --git a/share/web/templates/recent b/share/web/templates/recent
index dd18b2b..08bc6f0 100644
--- a/share/web/templates/recent
+++ b/share/web/templates/recent
@@ -6,7 +6,7 @@ $pages
% while (my $page = $pages->next) {
<dt><% Jifty->web->link( label => $page->name, url => '/view/'.$page->name)%></dt>
<dd><%$page->updated%>
-% if($page->updated_by) {
+% if($page->updated_by->id) {
(<% $page->updated_by->name %>)
% } else {
(Anonymous)
commit 368fcc465fa1d2ada1a62ddff543de44cd88e3d2
Author: Thomas Sibley <trs at bestpractical.com>
Date: Mon Jul 31 05:59:48 2006 +0000
Add WikiName preference
* Give wifty a big layout/styling overhaul to make it look nicer by default (and more like a wiki)
diff --git a/etc/config.yml b/etc/config.yml
index c48dc8d..b1a8d98 100644
--- a/etc/config.yml
+++ b/etc/config.yml
@@ -19,3 +19,4 @@ framework:
TemplateRoot: share/web/templates
application:
# RequireAuth: 1
+ WikiName: A Wiki
diff --git a/share/web/static/css/app-base.css b/share/web/static/css/app-base.css
index 275e4d7..d6aeb11 100644
--- a/share/web/static/css/app-base.css
+++ b/share/web/static/css/app-base.css
@@ -1,72 +1,132 @@
body {
- background-color: #dddddd;
- font-size: 85%;
+ background-color: #ddd;
+ font-size: 76%;
+ margin: 0 1.5em 1.5em 1.5em;
+}
+a {
+ color: #c12d06;
}
-div#headers h1 {
- background: green;
- color: white;
- padding: 0.2em;
- border: 0;
- margin: 0;
- margin: -10px 0 0 -10px ;
- margin-right: -10px;
+#salutation {
+ position: absolute;
+ top: 0.5em;
+ right: 1.8em;
+ font-family: sans-serif;
+ font-size: 0.9em;
+}
+#header {
+ margin-top: 2.3em;
}
-div#headers { margin-left: 10px; }
-div#headers a { position: absolute; top: 0.5em; right: 1em; }
+#wikiheader {
+ float: right;
+ text-align: right;
+ padding-right: 1px;
+ width: 28%;
+}
+#wikiname {
+ margin: 0;
+ font-size: 1.7em;
+}
-a {
- color: black;
- font-style: bold;
+#wikiname a {
+ color: black;
+ text-decoration: none;
}
-#jifty-wait-message {
- display: none;
+#pageheader {
+ margin-top: 1em;
+ width: 70%;
+ padding-left: 1px;
}
-div#content {
- background: #ffffff;
- padding: 2em;
+h1, h2, h3, h4, h5, h6 {
+ font-family: sans-serif;
}
-div#salutation {
- float: right;
- font-style: italic;
+h1 { font-size: 1.5em; }
+h2 { font-size: 1.3em; }
+h3 { font-size: 1.2em; }
+h4 { font-size: 1.1em; }
+
+#pagename {
+ margin: 0.2em 0 0 0;
+ font-size: 2em;
}
-textarea.content {
- height: 50em;
- background: #ddd;
- border: 1px solid black;
- padding: 5px;
+#content {
+ background: white;
+ padding: 0.3em 1em 0.5em 1em;
+ border: 1px solid #ccc;
+ clear: both;
+ margin-top: 0.5em;
+ font-family: sans-serif;
+ font-size: 1.1em;
}
+* html #content { margin-top: 0; }
-input[type=submit] {
- border: 1px solid black;
- font-size: 1.5em;
- margin: 5px;
-
+#update #content, #create #content {
+ position: relative;
}
-div#syntax {
+#content h1,
+#content h2,
+#content h3,
+#content h4
+{
+ margin: 0.2em 0;
+}
+
+#syntax {
float: right;
background: white;
- border: 1px solid #333;
- padding: 3px;
- font-size: 0.8em;
- width: 20em;
+ border: 1px solid #888;
+ padding: 0.2em 0.4em;
+ font-size: 0.9em;
+ width: 25%;
position: absolute;
- top: 10em;
- right: 2em;
+ top: 1em;
+ right: 1em;
+}
+
+#syntax code, #syntax tt {
+ font-size: 1.2em;
+}
+
+.diff {
+ font-size: 1.1em;
+ overflow: auto;
+}
+
+.file span {
+ display: block;
+ clear: both;
+}
+
+.file .fileheader, .file .hunkheader {
+ color: #666;
}
-.file span { display: block; clear: both; }
-.file .fileheader, .file .hunkheader {color: #888; }
-.file .hunk .ctx { background: #eee;}
-.file .hunk ins { background: #dfd; text-decoration: none; display: block; }
-.file .hunk del { background: #fdd; text-decoration: none; display: block; }
+.file .hunk .ctx {
+ background: #eee;
+}
+
+.file .hunk ins {
+ background: #dfd;
+ text-decoration: none;
+ display: block;
+}
+
+.file .hunk del {
+ background: #fdd;
+ text-decoration: none;
+ display: block;
+}
+
+#jifty-wait-message {
+ display: none;
+}
diff --git a/share/web/static/css/app.css b/share/web/static/css/app.css
new file mode 100644
index 0000000..2f5ed0f
--- /dev/null
+++ b/share/web/static/css/app.css
@@ -0,0 +1,96 @@
+div.argument-content {
+ width: 70%;
+}
+
+* html div.form_field,
+* html form .submit_button { position: relative; }
+
+label.argument-content {
+ display: none !important;
+}
+
+textarea.argument-content {
+ width: 100%;
+ font-size: 1.2em;
+}
+
+* html textarea.argument-content {
+ font-size: 1em;
+}
+
+form .submit_button input {
+ margin-left: 0;
+ font-size: 1.1em;
+ color: white;
+ background: #3d4286;
+ border: 1px outset #3d4286;
+}
+
+#recentupdates dt {
+ float: left;
+ clear: left;
+ width: 45%;
+}
+
+#recentupdates dd {
+ margin-left: 2em;
+ padding-left: 0;
+ margin-bottom: 0.5em;
+ float: left;
+ width: 50%;
+}
+
+* html #recentupdates dt,
+* html #recentupdates dd { position: relative; }
+
+#history dd {
+ font-size: 0.95em;
+ color: #444;
+ margin-left: 2em;
+ padding-left: 0;
+ margin-bottom: 0.5em;
+}
+
+hr {
+ border: none;
+ border-top: 1px solid #777;
+}
+
+.revision_nav {
+ margin-top: 0.5em;
+}
+
+/* Login box */
+
+#login-box form {
+ padding-bottom: 0;
+}
+
+#login-box label {
+ width: 10em;
+}
+
+#login-box input.text,
+#login-box input.password {
+ font-size: 1.1em;
+}
+
+#login-box span.error {
+ font-size: 0.8em;
+}
+
+#login-box .argument-remember .hints {
+ float: left;
+ clear: none;
+ width: 50%;
+ font-size: 0.8em;
+ padding: 0 0 0 0.3em;
+}
+
+* html #login-box .argument-remember .hints {
+ width: auto;
+}
+
+#login-box input.argument-remember {
+ float: left;
+}
diff --git a/share/web/static/css/base.css b/share/web/static/css/base.css
new file mode 100644
index 0000000..27e44e2
--- /dev/null
+++ b/share/web/static/css/base.css
@@ -0,0 +1,68 @@
+.error {
+ color: #a00000;
+}
+
+.warning {
+ color: #00a0a0;
+}
+
+hr.clear {
+ clear: both;
+ visibility: hidden;
+ height: 0;
+ padding: 0;
+ margin: 0;
+}
+
+.messages .message {
+ display: block;
+}
+
+div#messages, div#errors {
+ background-color: rgb(240,234,183);
+ border: 1px solid rgb(230,224,173);
+ margin-top: 10px;
+ margin-bottom: 10px;
+ padding: 5px;
+ font-size: 1.2em;
+}
+
+div.spacer {
+ clear: both;
+}
+
+.next-page, .prev-page {
+ display: block;
+ float: left;
+ margin: 0.5em 0;
+ padding: 0.2em 0.5em 0.5em 0.5em;
+ border-top: 1px solid gray;
+}
+
+.next-page { padding-right: 1em; }
+.prev-page { padding-left: 1em; }
+
+div#jifty-wait-message {
+ color: red;
+ background: black;
+ font-size: 2em;
+ position: fixed;
+ top: 10px;
+ right: 10px;
+ z-index: 42;
+}
+
+div.warning {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ background-color: red;
+ color: white;
+ padding: .5em;
+ border-bottom: 1px solid #000;
+}
+
+div.warning a {
+ color: white;
+}
diff --git a/share/web/static/css/forms.css b/share/web/static/css/forms.css
new file mode 100644
index 0000000..981390f
--- /dev/null
+++ b/share/web/static/css/forms.css
@@ -0,0 +1,107 @@
+/* buttons */
+
+input.button {
+ margin-top: 0.6em;
+ padding: 0.15em 1em;
+ font-weight: bold;
+}
+
+* html input.button {
+ padding: 0 0.1em;
+}
+
+/* fields */
+
+input.text, input.date, input.password, input.combo-text, textarea, select {
+ border-top: 1px solid #7c7c7c;
+ border-left: 1px solid #c3c3c3;
+ border-right: 1px solid #c3c3c3;
+ border-bottom: 1px solid #ddd;
+ background: #fff url(/static/images/css/fieldbg.gif) repeat-x top;
+ padding: 0.2em;
+ font-size: 1em;
+}
+
+label, span.label {
+ font-size: 0.9em;
+}
+
+.form_field .hints {
+ font-size: 0.9em;
+ color: #777;
+}
+
+/* layout */
+
+.form_field {
+ clear: both;
+}
+
+label, span.label {
+ display: block;
+ width: 20%;
+ float: left;
+ text-align: right;
+ margin-right: 0.5em;
+}
+
+* html label, * html span.label {
+ width: 22%;
+}
+
+form .hints {
+ display: block;
+ clear: both;
+}
+
+html>body form .hints {
+ padding: 0.2em 0 0.2em 21%;
+}
+
+* html form .hints {
+ padding-left: 11.5%;
+}
+
+form .error {
+ display: block;
+ clear: both;
+}
+
+.form_field {
+ padding: 0.3em 0 0 0;
+}
+
+.inline .hints {
+ padding-left: 0;
+}
+
+.inline label, .inline span.label {
+ float: none;
+ width: auto;
+ text-align: left;
+ margin-right: auto;
+ font-weight: bold;
+}
+
+.inline .form_field {
+ float: left;
+ clear: none;
+ margin-right: 0.5em;
+}
+
+.inline .button {
+ margin-top: 1.1em;
+}
+
+.button_line {
+ border-top: 1px solid #ccc;
+ padding-right: 5em;
+ margin-top: 1.5em;
+ clear: both;
+ direction: rtl;
+}
+
+form .line {
+ clear: both;
+}
+
diff --git a/share/web/static/css/keybindings.css b/share/web/static/css/keybindings.css
new file mode 100644
index 0000000..87f864e
--- /dev/null
+++ b/share/web/static/css/keybindings.css
@@ -0,0 +1,25 @@
+div#keybindings {
+ color: #666666;
+ margin-top: 2em;
+}
+
+dl.keybindings .keybinding {
+ display: inline;
+}
+
+dl.keybindings dt {
+ margin: 0;
+ font-weight: bold;
+ display: inline;
+
+}
+dl.keybindings dt:after {
+ content: ":";
+
+}
+dl.keybindings dd {
+ margin-right: 1.5em;
+ margin-left: 0.5em;
+ display: inline;
+ white-space: nowrap;
+}
diff --git a/share/web/static/css/main.css b/share/web/static/css/main.css
new file mode 100644
index 0000000..f1e909f
--- /dev/null
+++ b/share/web/static/css/main.css
@@ -0,0 +1,9 @@
+ at import "app-base.css";
+ at import "base.css";
+ at import "nav.css";
+ at import "keybindings.css";
+ at import "forms.css";
+ at import "halos.css";
+ at import "app.css";
+ at import "autocomplete.css";
+ at import "notices.css";
diff --git a/share/web/static/css/nav.css b/share/web/static/css/nav.css
new file mode 100644
index 0000000..b71722f
--- /dev/null
+++ b/share/web/static/css/nav.css
@@ -0,0 +1,21 @@
+ul.menu {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ font-size: 1.2em;
+ font-family: sans-serif;
+}
+
+ul.menu li {
+ display: inline;
+ padding-right: 1em;
+}
+
+#pageheader ul.menu {
+ margin-top: -0.1em;
+}
+
+#wikiheader ul.menu li {
+ padding-right: 0;
+ padding-left: 1em;
+}
\ No newline at end of file
diff --git a/share/web/templates/_elements/diff b/share/web/templates/_elements/diff
index 858d4c0..0e874d7 100644
--- a/share/web/templates/_elements/diff
+++ b/share/web/templates/_elements/diff
@@ -15,10 +15,16 @@ use Text::Diff ();
my $diff = Text::Diff::diff(\($from->content), \($to->content), { STYLE => 'Text::Diff::HTML' });
</%init>
+<div class="revision_nav">
% if ($before) {
-<span style="float: left"><% Jifty->web->link(url => "/view/".$page->name."/".$before->id, label => "Previous revision") %></span>
+<span class="prev"><% Jifty->web->link(url => "/view/".$page->name."/".$before->id, label => "Previous revision") %></span>
+% }
+% if ( $before and $after ) {
+ |
% }
% if ($after) {
-<span style="float: right"><% Jifty->web->link(url => "/view/".$page->name."/".$after->id, label => "Next revision") %></span>
+<span class="next"><% Jifty->web->link(url => "/view/".$page->name."/".$after->id, label => "Next revision") %></span>
% }
-<pre><% $diff |n%></pre><hr />
+</div>
+<pre class="diff"><% $diff |n%></pre>
+<hr />
diff --git a/share/web/templates/_elements/header b/share/web/templates/_elements/header
new file mode 100644
index 0000000..d89c29d
--- /dev/null
+++ b/share/web/templates/_elements/header
@@ -0,0 +1,18 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
+ <meta name="robots" content="all" />
+
+ <title><% _( $title ) %> - <% _( $wikiname ) %></title>
+
+ <% Jifty->web->include_css %>
+ <% Jifty->web->include_javascript %>
+</head>
+<%args>
+$title => ""
+$wikiname => ""
+</%args>
+<%init>
+$r->content_type('text/html; charset=utf-8');
+</%init>
diff --git a/share/web/templates/_elements/markup b/share/web/templates/_elements/markup
index 7bfa695..cd83f25 100644
--- a/share/web/templates/_elements/markup
+++ b/share/web/templates/_elements/markup
@@ -1,5 +1,5 @@
<div id="syntax">
-<div><a href="javascript:;" onclick="Element.toggle('syntax_content');return(false);"><b>Wiki Syntax Help</b></a>
+<div><a href="#" onclick="Element.toggle('syntax_content');return(false);"><b>Wiki Syntax Help</b></a>
</div>
<div id="syntax_content">
diff --git a/share/web/templates/_elements/nav b/share/web/templates/_elements/nav
index e89df55..69b953c 100644
--- a/share/web/templates/_elements/nav
+++ b/share/web/templates/_elements/nav
@@ -1,11 +1,12 @@
<%init>
my $top = Jifty->web->navigation;
-$top->child( Home => url => "/", sort_order => 1 );
-$top->child( Recent => url => "/recent", label => "Recent Changes", sort_order => 2);
- if (Jifty->config->framework('AdminMode') ) {
- $top->child(Administration => url => "/__jifty/admin/", sort_order => 998);
- $top->child(OnlineDocs => url => "/__jifty/online_docs/", label => 'Online docs', sort_order => 999);
+$top->child( Home => url => "/", sort_order => 1 );
+$top->child( Recent => url => "/recent", label => "Recent Changes", sort_order => 2 );
+
+if ( Jifty->config->framework('AdminMode') ) {
+ $top->child( Administration => url => "/__jifty/admin/", sort_order => 998);
+ $top->child( OnlineDocs => url => "/__jifty/online_docs/", label => 'Online docs', sort_order => 999);
}
-return();
+return;
</%init>
diff --git a/share/web/templates/_elements/page_nav b/share/web/templates/_elements/page_nav
index 4b27a7f..a6dd135 100644
--- a/share/web/templates/_elements/page_nav
+++ b/share/web/templates/_elements/page_nav
@@ -1,20 +1,14 @@
<%init>
my $subpath = $page . ($rev ? "/$rev" : '');
-my $top = Jifty->web->navigation;
-my $this = $top->child(
- This =>
- url => "/view/".$subpath,
- label => $page,
- sort_order => 5
-);
+my $top = Jifty->web->page_navigation;
my $page_obj = Wifty::Model::Page->new();
$page_obj->load_by_cols(name => $page);
-$this->child( View => url => '/view/'.$subpath);
-$this->child( Edit => url => '/edit/'.$subpath) if $page_obj->current_user_can('update');
-$this->child( History => url => '/history/'.$page);
-$this->child( Latest => url => '/view/'.$page) if ($rev);
+$top->child( View => url => '/view/'.$subpath);
+$top->child( Edit => url => '/edit/'.$subpath);
+$top->child( History => url => '/history/'.$page);
+$top->child( Latest => url => '/view/'.$page) if ($rev);
</%init>
<%args>
diff --git a/share/web/templates/_elements/salutation b/share/web/templates/_elements/salutation
new file mode 100644
index 0000000..bfd36c6
--- /dev/null
+++ b/share/web/templates/_elements/salutation
@@ -0,0 +1,9 @@
+<div id="salutation">
+% if ( Jifty->web->current_user->id and Jifty->web->current_user->user_object ) {
+ Hiya, <span class="user"><% Jifty->web->current_user->user_object->name %></span>.
+ (<% Jifty->web->link( label => q{Logout}, url => '/logout' )%>)
+% } else {
+ You're not currently signed in.
+ <% Jifty->web->tangent( label => q{Sign in}, url => '/login' ) %>.
+% }
+</div>
diff --git a/share/web/templates/_elements/sidebar b/share/web/templates/_elements/sidebar
deleted file mode 100644
index e849b39..0000000
--- a/share/web/templates/_elements/sidebar
+++ /dev/null
@@ -1,11 +0,0 @@
-<div id="salutation">
-% if (Jifty->web->current_user->id and Jifty->web->current_user->user_object) {
-Hiya, <span class="user"><%Jifty->web->current_user->user_object->name%></span>.<br />
-(<% Jifty->web->link( label => q{Logout}, url => '/logout' )%>)
-% } else {
-<% Jifty->web->tangent( label => q{You're not currently signed in.}, url => '/login' )%>
-% }
-</div>
-<div id="navigation">
-<& /_elements/menu &>
-</div>
diff --git a/share/web/templates/_elements/wrapper b/share/web/templates/_elements/wrapper
new file mode 100644
index 0000000..2ba5a29
--- /dev/null
+++ b/share/web/templates/_elements/wrapper
@@ -0,0 +1,49 @@
+<& /_elements/header, title => $title, wikiname => $wikiname &>
+% Jifty->handler->stash->{'in_body'} = 1;
+<body<% $id && qq[ id="$id"]|n%>>
+% if (Jifty->config->framework('AdminMode') ) {
+ <div class="warning admin_mode">
+ <%_('Alert')%>: <% Jifty->web->tangent( label => _('Administration mode is enabled'),
+ url => '/__jifty/admin/') %>.
+ </div>
+% }
+
+ <div id="header">
+ <div id="wikiheader">
+ <h1 id="wikiname">
+ <% Jifty->web->link( url => "/", label => _($wikiname) ) %>
+ </h1>
+
+ <% Jifty->web->navigation->render_as_menu %>
+ </div>
+
+ <div id="pageheader">
+ <h1 id="pagename"><% _($title) %></h1>
+
+ <% Jifty->web->page_navigation->render_as_menu %>
+ </div>
+ </div>
+
+ <& /_elements/salutation &>
+
+ <hr class="clear" />
+
+ <div id="content">
+ <% Jifty->web->render_messages %>
+ <% $m->content |n%>
+ <& /_elements/keybindings &>
+ <hr class="clear" />
+ </div>
+
+ <div id="jifty-wait-message" style="display: none"><%_('Loading...')%></div>
+% Jifty::Mason::Halo->render_component_tree() if Jifty->config->framework('DevelMode');
+</body>
+</html>
+% Jifty->handler->stash->{'in_body'} = 0;
+<%args>
+$title => ""
+$id => ''
+</%args>
+<%init>
+my $wikiname = Jifty->config->app('WikiName') || "Wifty";
+</%init>
diff --git a/share/web/templates/create b/share/web/templates/create
index 91e61d8..002545b 100644
--- a/share/web/templates/create
+++ b/share/web/templates/create
@@ -1,9 +1,15 @@
-<&|/_elements/wrapper, title => 'New page: '. $page&>
+<&|/_elements/wrapper, title => 'New page: '. $page, id => 'create'&>
<% Jifty->web->form->start %>
+<div class="form_wrapper">
<% Jifty->web->form->next_page( url => '/view/'.$page) %>
<% $action->form_field('name', render_as => 'hidden', default_value => $page) %>
-<% $action->form_field('content')%>
-<% Jifty->web->form->submit( label => 'Save')%>
+<div class="inline">
+<% $action->form_field('content', rows => 30)%>
+</div>
+<div class="line">
+<% Jifty->web->form->submit( label => 'Create' )%>
+</div>
+</div>
<% Jifty->web->form->end %>
<& /_elements/markup &>
</&>
diff --git a/share/web/templates/edit b/share/web/templates/edit
index d8ca040..9e8084c 100644
--- a/share/web/templates/edit
+++ b/share/web/templates/edit
@@ -7,19 +7,25 @@ $viewer
my $can_edit = $page->current_user_can('update');
</%init>
<&/_elements/page_nav, page => $page->name, rev => $revision->id &>
-<&|/_elements/wrapper, title => 'Edit: '.$page->name . ($revision->id ? " as of ".$revision->created : '') &>
+<&|/_elements/wrapper, title => 'Edit: '.$page->name . ($revision->id ? " as of ".$revision->created : ''), id => "update" &>
<% Jifty->web->form->start %>
+<div class="form_wrapper">
+<div class="inline">
% unless($can_edit) {
- <p> You don't have permission to edit this page. Perhaps
+ <p style="width: 70%"> You don't have permission to edit this page. Perhaps
<% Jifty->web->tangent(url => '/login', label => 'logging in') %>
would help. In the mean time, though, you're welcome to view and
copy the source of this page. </p>
% }
<% Jifty->web->form->next_page( url => '/view/'.$page->name) %>
-<% $viewer->form_field('content', ($revision->id ? (default_value => $revision->content) : (undef, undef)), rows=> 30, cols => 80 )%>
+<% $viewer->form_field('content', ($revision->id ? (default_value => $revision->content) : (undef, undef)), rows => 30 )%>
+</div>
% if($can_edit) {
+<div class="line">
<% Jifty->web->form->submit( label => 'Save') %>
+</div>
% }
+</div>
<% Jifty->web->form->end %>
<& /_elements/markup &>
</&>
diff --git a/share/web/templates/history b/share/web/templates/history
index f44c6b4..d5741c1 100644
--- a/share/web/templates/history
+++ b/share/web/templates/history
@@ -4,7 +4,7 @@ $revisions
</%args>
<& /_elements/page_nav, page => $page->name &>
<&|/_elements/wrapper, title => $revisions->count ." revisions of " .$page->name &>
-<ul>
+<dl id="history">
% while (my $rev = $revisions->next) {
<dt><% Jifty->web->link( label => $rev->created,
url => '/view/'.$page->name.'/'.$rev->id
@@ -17,5 +17,5 @@ $revisions
</dt>
<dd><%length($rev->content)%> bytes</dd>
% }
-</ul>
+</dl>
</&>
diff --git a/share/web/templates/login b/share/web/templates/login
index 52d75b5..5884b65 100644
--- a/share/web/templates/login
+++ b/share/web/templates/login
@@ -4,17 +4,23 @@ $next => undef
</%args>
<&|/_elements/wrapper, title => 'Login' &>
-% if (not Jifty->web->current_user->id) {
-<h2>Login</h2>
+% if ( not Jifty->web->current_user->id ) {
+<div id="login-box">
<% Jifty->web->form->start(call => $next, name => "loginbox") %>
<% $action->form_field('email') %>
<% $action->form_field('password') %>
<% $action->form_field('remember') %>
<% Jifty->web->form->submit(label => 'Login', submit => $action) %>
<% Jifty->web->form->end %>
-<% Jifty->web->tangent( label => q{Don't have an account?}, url => '/signup' )%>
+</div>
+
+<p><% Jifty->web->tangent( label => q{Don't have an account?}, url => '/signup' )%></p>
+
% }
% else {
-You're already logged in.
+<p>
+You're already logged in as <% Jifty->web->current_user->user_object->name %>.
+If this isn't you, <% Jifty->web->tangent( url => '/logout', label => 'click here') %>.
+</p>
% }
</&>
diff --git a/share/web/templates/recent b/share/web/templates/recent
index 08bc6f0..0464768 100644
--- a/share/web/templates/recent
+++ b/share/web/templates/recent
@@ -2,7 +2,7 @@
$pages
</%args>
<&|/_elements/wrapper, title => 'Updated this week' &>
-<dl id="recentudates">
+<dl id="recentupdates">
% while (my $page = $pages->next) {
<dt><% Jifty->web->link( label => $page->name, url => '/view/'.$page->name)%></dt>
<dd><%$page->updated%>
diff --git a/share/web/templates/signup b/share/web/templates/signup
index 1d006db..70103a1 100644
--- a/share/web/templates/signup
+++ b/share/web/templates/signup
@@ -3,7 +3,7 @@ $action
$next
</%args>
<&|/_elements/wrapper, title => 'Signup' &>
-<h2>Signup</h2>
+<p>Just a few bits of information are all that's needed.</p>
<% Jifty->web->form->start(call => $next, name => "signupbox") %>
<% $action->form_field('email') %>
<% $action->form_field('name') %>
diff --git a/share/web/templates/view b/share/web/templates/view
index 35c7668..0a4e256 100644
--- a/share/web/templates/view
+++ b/share/web/templates/view
@@ -5,7 +5,7 @@ $revision
<& /_elements/page_nav, page => $page->name, rev => $revision->id &>
<&|/_elements/wrapper, title => $page->name . ($revision->id ? " as of ".$revision->created : '') &>
% if ($revision->id) {
-<& _elements/diff, page => $page, to => $revision &>
+<& /_elements/diff, page => $page, to => $revision &>
<% $page->wiki_content($revision->content) |n%>
% } else {
diff --git a/t/02-login.t b/t/02-login.t
index 87bf108..74c5d94 100644
--- a/t/02-login.t
+++ b/t/02-login.t
@@ -22,8 +22,8 @@ ok($URL, "Started a test server");
my $mech = Jifty::Test::WWW::Mechanize->new();
$mech->get_ok($URL, "Got the homepage");
-ok($mech->find_link(text_regex => qr/currently signed in/), 'Got the signin link');
-$mech->follow_link_ok(text_regex => qr/currently signed in/);
+ok($mech->find_link(text_regex => qr/Sign in/), 'Got the signin link');
+$mech->follow_link_ok(text_regex => qr/Sign in/);
sub try_login {
my $mech = shift;
commit 6cfc5b727093028b6e5782ae53d43e8226f06eeb
Author: Nelson Elhage <nelhage at bestpractical.com>
Date: Mon Aug 28 19:12:49 2006 +0000
This makes Wifty admin mode slightly less hideous.
diff --git a/share/web/static/css/forms.css b/share/web/static/css/forms.css
index 981390f..338f291 100644
--- a/share/web/static/css/forms.css
+++ b/share/web/static/css/forms.css
@@ -105,3 +105,12 @@ form .line {
clear: both;
}
+/* So the admin ui is one row per line */
+
+.jifty_admin.item.inline {
+ clear: both;
+}
+
+.jifty_admin .editlink {
+ float: right;
+}
commit a03a86f5481c3462d3b85e6670415e4501e9ff46
Author: Alex Vandiver <alexmv at bestpractical.com>
Date: Wed Aug 30 06:22:28 2006 +0000
Step one of aligning packages and filenames
diff --git a/lib/Wifty/Action/ResetLostPassword.pm b/lib/Wifty/Action/ResetLostPassword.pm
index a98292f..927d7c8 100755
--- a/lib/Wifty/Action/ResetLostPassword.pm
+++ b/lib/Wifty/Action/ResetLostPassword.pm
@@ -3,7 +3,7 @@ use strict;
=head1 NAME
-Wifty::Action::ResetPassword - Confirm and reset a lost password
+Wifty::Action::ResetLostPassword - Confirm and reset a lost password
=head1 DESCRIPTION
@@ -13,7 +13,7 @@ address is really theirs, when claiming that they lost their password.
=cut
-package Wifty::Action::ResetPassword;
+package Wifty::Action::ResetLostPassword;
use Wifty::Model::User;
use base qw/Wifty::Action/;
diff --git a/lib/Wifty/Action/SendAccountConfrimation.pm b/lib/Wifty/Action/SendAccountConfrimation.pm
index 1073862..265a8c2 100755
--- a/lib/Wifty/Action/SendAccountConfrimation.pm
+++ b/lib/Wifty/Action/SendAccountConfrimation.pm
@@ -3,11 +3,11 @@ use strict;
=head1 NAME
-Wifty::Action::ResendConfirmation
+Wifty::Action::SendAccountConfirmation
=cut
-package Wifty::Action::ResendConfirmation;
+package Wifty::Action::SendAccountConfirmation;
use Wifty::Model::User;
use base qw/Wifty::Action/;
diff --git a/lib/Wifty/Action/SendPasswordReminder.pm b/lib/Wifty/Action/SendPasswordReminder.pm
index 4c643d6..0ae5ab4 100755
--- a/lib/Wifty/Action/SendPasswordReminder.pm
+++ b/lib/Wifty/Action/SendPasswordReminder.pm
@@ -3,11 +3,11 @@ use strict;
=head1 NAME
-Wifty::Action::SendLostPasswordConfirmation
+Wifty::Action::SendPasswordReminder
=cut
-package Wifty::Action::SendLostPasswordConfirmation;
+package Wifty::Action::SendPasswordReminder;
use base qw/Wifty::Action Jifty::Action/;
__PACKAGE__->mk_accessors(qw(user_object));
@@ -16,7 +16,7 @@ use Wifty::Model::User;
=head2 arguments
-The field for C<SendLostPasswordConfirmation> is:
+The field for C<SendPasswordReminder> is:
=over 4
commit b003dd5a514cb9abddaa165472b8b53f82bc3c66
Author: Alex Vandiver <alexmv at bestpractical.com>
Date: Wed Aug 30 06:22:36 2006 +0000
Step two of aligning packages and filenames
diff --git a/lib/Wifty/Action/SendAccountConfrimation.pm b/lib/Wifty/Action/SendAccountConfirmation.pm
similarity index 100%
rename from lib/Wifty/Action/SendAccountConfrimation.pm
rename to lib/Wifty/Action/SendAccountConfirmation.pm
commit 9b432651278e2e84f4037a248be195fb787e38dc
Author: John Peacock <jpeacock at cpan.org>
Date: Wed Aug 30 23:51:23 2006 +0000
Update Wifty to use the Login plugin.
Delete unnecessary files.
diff --git a/etc/config.yml b/etc/config.yml
index b1a8d98..4fad165 100644
--- a/etc/config.yml
+++ b/etc/config.yml
@@ -1,6 +1,7 @@
framework:
AdminMode: 0
ApplicationName: Wifty
+ AdminEmail: 'wifty at example.com'
Database:
Driver: SQLite
@@ -9,7 +10,10 @@ framework:
Version: 0.0.20
Password: ''
RequireSSL: 0
-# Mailer: IO
+ Plugins:
+ - Login: {}
+ Mailer: SMTP
+ MailerArgs: ['69.17.117.59']
# MailerArgs:
# - %log/mail.log%
SiteConfig: etc/site_config.yml
diff --git a/lib/Wifty/Action/ConfirmEmail.pm b/lib/Wifty/Action/ConfirmEmail.pm
deleted file mode 100644
index c3261a7..0000000
--- a/lib/Wifty/Action/ConfirmEmail.pm
+++ /dev/null
@@ -1,56 +0,0 @@
-use warnings;
-use strict;
-
-=head1 NAME
-
-Wifty::Action::ConfirmEmail - Confirm a user's email address
-
-=head1 DESCRIPTION
-
-This is the link in a user's email to confirm that their email
-email is really theirs. It is not really meant to be rendered on any
-web page, but is used by the confirmation notification.
-
-=cut
-
-package Wifty::Action::ConfirmEmail;
-use base qw/Wifty::Action Jifty::Action/;
-
-use Wifty::Model::User;
-
-
-=head2 actions
-
-A null sub, because the superclass wants to make sure we fill in actions
-
-=cut
-
-sub actions {}
-
-=head2 take_action
-
-Set their confirmed status.
-
-=cut
-
-sub take_action {
- my $self = shift;
- my $u = Wifty::Model::User->new(current_user => Wifty::CurrentUser->superuser);
- $u->load_by_cols( email => Jifty->web->current_user->user_object->email );
-
- if ($u->email_confirmed) {
- $self->result->error(email => "You have already confirmed your account.");
- $self->result->success(1); # but the action is still a success
- }
-
- $u->set_email_confirmed('1');
-
- # Set up our login message
- $self->result->message( "Welcome to Wifty, " . $u->name . ". Your email address has now been confirmed." );
-
- # Actually do the login thing.
- Jifty->web->current_user(Wifty::CurrentUser->new(id => $u->id));
- return 1;
-}
-
-1;
diff --git a/lib/Wifty/Action/Login.pm b/lib/Wifty/Action/Login.pm
deleted file mode 100644
index 09643bc..0000000
--- a/lib/Wifty/Action/Login.pm
+++ /dev/null
@@ -1,89 +0,0 @@
-use warnings;
-use strict;
-
-=head1 NAME
-
-Wifty::Action::Login
-
-=cut
-
-package Wifty::Action::Login;
-use base qw/Wifty::Action/;
-use Jifty::Param::Schema;
-use Jifty::Action schema {
-
-param email =>
- label is 'Email address',
- is mandatory,
- ajax validates;
-
-param password =>
- type is 'password',
- label is 'Password',
- is mandatory;
-
-param remember =>
- type is 'checkbox',
- label is 'Remember me?',
- hints is 'If you want, your browser can remember your login for you',
- default is 0;
-
-};
-
-=head2 validate_email ADDRESS
-
-Makes sure that the email submitted is a legal email address and that there's a user in the database with it.
-
-
-=cut
-
-sub validate_email {
- my $self = shift;
- my $email = shift;
-
- unless ( $email =~ /\S\@\S/ ) {
- return $self->validation_error(email => "That doesn't look like an email address." );
- }
-
- my $u = Wifty::Model::User->new(current_user => Wifty::CurrentUser->superuser);
- $u->load_by_cols( email => $email );
- return $self->validation_error(email => 'No account has that email address.') unless ($u->id);
-
-
- return $self->validation_ok('email');
-}
-
-=head2 take_action
-
-Actually check the user's password. If it's right, log them in.
-Otherwise, throw an error.
-
-
-=cut
-
-sub take_action {
- my $self = shift;
- my $user = Wifty::CurrentUser->new( email => $self->argument_value('email'));
-
- unless ( $user->id && $user->password_is($self->argument_value('password'))) {
- $self->result->error( 'You may have mistyped your email address or password. Give it another shot?' );
- return;
- }
-
- unless ($user->user_object->email_confirmed) {
- $self->result->error( q{You haven't confirmed your account yet.} );
- return;
- }
-
- # Set up our login message
- $self->result->message("Welcome back, " . $user->user_object->name . "." );
-
- # Actually do the signin thing.
- Jifty->web->current_user($user);
- Jifty->web->session->expires($self->argument_value('remember') ? '+1y' : undef);
- Jifty->web->session->set_cookie;
-
- return 1;
-}
-
-1;
diff --git a/lib/Wifty/Action/Logout.pm b/lib/Wifty/Action/Logout.pm
deleted file mode 100644
index 3a35611..0000000
--- a/lib/Wifty/Action/Logout.pm
+++ /dev/null
@@ -1,25 +0,0 @@
-use warnings;
-use strict;
-
-=head1 NAME
-
-Wifty::Action::Logout
-
-=cut
-
-package Wifty::Action::Logout;
-use base qw/Wifty::Action Jifty::Action/;
-
-=head2 take_action
-
-Nuke the current user object
-
-=cut
-
-sub take_action {
- my $self = shift;
- Jifty->web->current_user(undef);
- return 1;
-}
-
-1;
diff --git a/lib/Wifty/Action/RecoverPassword.pm b/lib/Wifty/Action/RecoverPassword.pm
deleted file mode 100644
index 7f6f0c3..0000000
--- a/lib/Wifty/Action/RecoverPassword.pm
+++ /dev/null
@@ -1,7 +0,0 @@
-
-use warnings;
-use strict;
-
-package Wifty::Action::RecoverPassword;
-
-1;
diff --git a/lib/Wifty/Action/ResetLostPassword.pm b/lib/Wifty/Action/ResetLostPassword.pm
deleted file mode 100755
index 927d7c8..0000000
--- a/lib/Wifty/Action/ResetLostPassword.pm
+++ /dev/null
@@ -1,69 +0,0 @@
-use warnings;
-use strict;
-
-=head1 NAME
-
-Wifty::Action::ResetLostPassword - Confirm and reset a lost password
-
-=head1 DESCRIPTION
-
-This is the action run by the link in a user's email to confirm that their email
-address is really theirs, when claiming that they lost their password.
-
-
-=cut
-
-package Wifty::Action::ResetLostPassword;
-use Wifty::Model::User;
-use base qw/Wifty::Action/;
-
-use Jifty::Param::Schema;
-use Jifty::Action schema {
-
-param password =>
- type is 'password',
- ! is sticky;
-
-param password_confirm =>
- type is 'password',
- label is 'type your password again',
- ! is sticky;
-
-};
-
-=head2 take_action
-
-Resets the password.
-
-=cut
-
-sub take_action {
- my $self = shift;
- my $u = Wifty::Model::User->new(current_user => Wifty::CurrentUser->superuser);
- $u->load_by_cols( email => Jifty->web->current_user->user_object->email );
-
- unless ($u) {
- $self->result->error( "You don't exist. I'm not sure how this happened. Really, really sorry. Please email us!");
- }
-
- my $pass = $self->argument_value('password');
- my $pass_c = $self->argument_value('password_confirm');
-
- # Trying to set a password (ie, submitted the form)
- unless (defined $pass and defined $pass_c and length $pass and $pass eq $pass_c) {
- $self->result->error("It looks like you didn't enter the same password into both boxes. Give it another shot?");
- return;
- }
-
- unless ($u->set_password($pass)) {
- $self->result->error("There was an error setting your password.");
- return;
- }
- # Log in!
- $self->result->message( "Your password has been reset. Welcome back." );
- Jifty->web->current_user(Wifty::CurrentUser->new(id => $u->id));
- return 1;
-
-}
-
-1;
diff --git a/lib/Wifty/Action/SendAccountConfirmation.pm b/lib/Wifty/Action/SendAccountConfirmation.pm
deleted file mode 100755
index 265a8c2..0000000
--- a/lib/Wifty/Action/SendAccountConfirmation.pm
+++ /dev/null
@@ -1,72 +0,0 @@
-use warnings;
-use strict;
-
-=head1 NAME
-
-Wifty::Action::SendAccountConfirmation
-
-=cut
-
-package Wifty::Action::SendAccountConfirmation;
-
-use Wifty::Model::User;
-use base qw/Wifty::Action/;
-
-__PACKAGE__->mk_accessors(qw(user_object));
-
-use Jifty::Param::Schema;
-use Jifty::Action schema {
-
-param address =>
- label is 'email address',
- is mandatory,
- default is '';
-
-};
-
-=head2 setup
-
-Create an empty user object to work with
-
-=cut
-
-sub setup {
- my $self = shift;
-
- $self->user_object(Wifty::Model::User->new(current_user => Wifty::CurrentUser->superuser));
-}
-
-=head2 validate_address
-
-Make sure their email address is an unconfirmed user.
-
-=cut
-
-sub validate_address {
- my $self = shift;
- my $email = shift;
-
- return $self->validation_error(address => "That doesn't look like an email address." ) unless ( $email =~ /\S\@\S/ );
-
- $self->user_object(Wifty::Model::User->new(current_user => Wifty::CurrentUser->superuser));
- $self->user_object->load_by_cols( email => $email );
- return $self->validation_error(address => "It doesn't look like there's an account by that name.") unless ($self->user_object->id);
-
- return $self->validation_error(address => "It looks like you're already confirmed.") if ($self->user_object->email_confirmed);
-
- return $self->validation_ok('address');
-}
-
-=head2 take_action
-
-Create a new unconfirmed user and send out a confirmation email.
-
-=cut
-
-sub take_action {
- my $self = shift;
- Wifty::Notification::ConfirmAddress->new( to => $self->user_object )->send;
- return $self->result->message("Confirmation resent.");
-}
-
-1;
diff --git a/lib/Wifty/Action/SendPasswordReminder.pm b/lib/Wifty/Action/SendPasswordReminder.pm
deleted file mode 100755
index 0ae5ab4..0000000
--- a/lib/Wifty/Action/SendPasswordReminder.pm
+++ /dev/null
@@ -1,87 +0,0 @@
-use warnings;
-use strict;
-
-=head1 NAME
-
-Wifty::Action::SendPasswordReminder
-
-=cut
-
-package Wifty::Action::SendPasswordReminder;
-use base qw/Wifty::Action Jifty::Action/;
-
-__PACKAGE__->mk_accessors(qw(user_object));
-
-use Wifty::Model::User;
-
-=head2 arguments
-
-The field for C<SendPasswordReminder> is:
-
-=over 4
-
-=item address: the email address
-
-=back
-
-=cut
-
-sub arguments {
- return (
- {
- address => {
- label => 'email address',
- mandatory => 1,
- },
- }
- );
-
-}
-
-=head2 setup
-
-Create an empty user object to work with
-
-=cut
-
-sub setup {
- my $self = shift;
-
- # Make a blank user object
- $self->user_object(Wifty::Model::User->new(current_user => Wifty::CurrentUser->superuser));
-}
-
-=head2 validate_address
-
-Make sure there's actually an account by that name.
-
-=cut
-
-sub validate_address {
- my $self = shift;
- my $email = shift;
-
- return $self->validation_error(address => "That doesn't look like an email address." )
- unless ( $email =~ /\S\@\S/ );
-
- $self->user_object(Wifty::Model::User->new(current_user => Wifty::CurrentUser->superuser));
- $self->user_object->load_by_cols( email => $email );
- return $self->validation_error(address => "It doesn't look like there's an account by that name.")
- unless ($self->user_object->id);
-
- return $self->validation_ok('address');
-}
-
-=head2 take_action
-
-Send out a confirmation email giving a link to a password-reset form.
-
-=cut
-
-sub take_action {
- my $self = shift;
- Wifty::Notification::ConfirmLostPassword->new( to => $self->user_object )->send;
- return $self->result->message("A link to reset your password has been sent to your email account.");
-}
-
-1;
diff --git a/lib/Wifty/Action/Signup.pm b/lib/Wifty/Action/Signup.pm
deleted file mode 100644
index 6e73fc2..0000000
--- a/lib/Wifty/Action/Signup.pm
+++ /dev/null
@@ -1,107 +0,0 @@
-use warnings;
-use strict;
-
-=head1 NAME
-
-Wifty::Action::Signup
-
-=cut
-
-package Wifty::Action::Signup;
-use Wifty::Action::CreateUser;
-use base qw/Wifty::Action::CreateUser/;
-
-
-use Wifty::Model::User;
-
-=head2 arguments
-
-
-The fields for C<Signup> are:
-
-=over 4
-
-=item email: the email address
-
-=item password and password_confirm: the requested password
-
-=item name: your full name
-
-=back
-
-=cut
-
-sub arguments {
- my $self = shift;
- my $args = $self->SUPER::arguments;
-
- my %fields = (
- name => 1,
- email => 1,
- password => 1,
- password_confirm => 1,
- );
-
- for ( keys %$args ) { delete $args->{$_} unless ( $fields{$_} ); }
- $args->{'email'}{'ajax_validates'} = 1;
- $args->{'password_confirm'}{'label'} = "Type that again?";
- return $args;
-}
-
-
-=head2 validate_email
-
-Make sure their email address looks sane
-
-=cut
-
-sub validate_email {
- my $self = shift;
- my $email = shift;
-
- return $self->validation_error(email => "That doesn't look like an email address." )
- unless ( $email =~ /\S\@\S/ ) ;
-
- my $u = Wifty::Model::User->new(current_user => Wifty::CurrentUser->superuser);
- $u->load_by_cols( email => $email );
- if ($u->id) {
- return $self->validation_error(email => 'It looks like you already have an account. Perhaps you want to <a href="/login">sign in</a> instead?');
- }
-
- return $self->validation_ok('email');
-}
-
-
-
-=head2 take_action
-
-Overrides the virtual C<take_action> method on L<Jifty::Action> to call
-the appropriate C<Jifty::Record>'s C<create> method when the action is
-run, thus creating a new object in the database.
-
-Makes sure that the user only specifies things we want them to.
-
-=cut
-
-sub take_action {
- my $self = shift;
- my $record = Wifty::Model::User->new(current_user => Wifty::CurrentUser->superuser);
-
- my %values;
- $values{$_} = $self->argument_value($_)
- for grep { defined $self->record->column($_) and defined $self->argument_value($_) } $self->argument_names;
-
- my ($id) = $record->create(%values);
- # Handle errors?
- unless ( $record->id ) {
- $self->result->error("Something bad happened and we couldn't create your account. Try again later. We're really, really sorry.");
- return;
- }
-
- $self->result->message( "Welcome to Wifty, " . $record->name .". We've sent a confirmation message to your email box.");
-
-
- return 1;
-}
-
-1;
diff --git a/lib/Wifty/CurrentUser.pm b/lib/Wifty/CurrentUser.pm
deleted file mode 100755
index 78e50fa..0000000
--- a/lib/Wifty/CurrentUser.pm
+++ /dev/null
@@ -1,38 +0,0 @@
-use warnings;
-use strict;
-
-
-package Wifty::CurrentUser;
-
-use base qw(Jifty::CurrentUser);
-
-=head2 new PARAMHASH
-
-Instantiate a new current user object, loading the user by paramhash:
-
- my $item = Wifty::Model::Item->new( Wifty::CurrentUser->new(email => 'user at site'));
-
-if you give the param
- _bootstrap => 1
-
-your object will be marked as a bootstrap user. You can use that to do an endrun around acls.
-
-=cut
-
-
-
-sub _init {
- my $self = shift;
- my %args = (@_);
-
- if (delete $args{'_bootstrap'} ) {
- $self->is_bootstrap_user(1);
- } elsif (keys %args) {
- $self->user_object(Wifty::Model::User->new(current_user => $self));
- $self->user_object->load_by_cols(%args);
- }
- $self->SUPER::_init(%args);
-}
-
-
-1;
diff --git a/lib/Wifty/Dispatcher.pm b/lib/Wifty/Dispatcher.pm
index e9bbfcd..00f57d8 100644
--- a/lib/Wifty/Dispatcher.pm
+++ b/lib/Wifty/Dispatcher.pm
@@ -78,55 +78,5 @@ on 'recent', run {
set pages => $pages;
};
-# Sign up for an account
-on 'signup', run {
- redirect('/') if ( Jifty->web->current_user->id );
- set 'action' =>
- Jifty->web->new_action( class => 'Signup', moniker => 'signupbox' );
-
- set 'next' => Jifty->web->request->continuation
- || Jifty::Continuation->new(
- request => Jifty::Request->new( path => "/" ) );
-
-};
-
-# Login
-on 'login', run {
- set 'action' =>
- Jifty->web->new_action( class => 'Login', moniker => 'loginbox' );
- set 'next' => Jifty->web->request->continuation
- || Jifty::Continuation->new(
- request => Jifty::Request->new( path => "/" ) );
-};
-
-# Log out
-before 'logout', run {
- Jifty->web->request->add_action(
- moniker => 'logout',
- class => 'Wifty::Action::Logout'
- );
-};
-
-
-## LetMes
-before qr'^/let/(.*)' => run {
- Jifty->api->deny(qr/^Wifty::Dispatcher/);
-
- my $let_me = Jifty::LetMe->new();
- $let_me->from_token($1);
- redirect '/error/let_me/invalid_token' unless $let_me->validate;
-
- Jifty->web->temporary_current_user($let_me->validated_current_user);
-
- my %args = %{$let_me->args};
- set $_ => $args{$_} for keys %args;
- set let_me => $let_me;
-};
-
-on qr'^/let/', => run {
- my $let_me = get 'let_me';
- show '/let/' . $let_me->path;
-};
-
1;
diff --git a/lib/Wifty/Model/Page.pm b/lib/Wifty/Model/Page.pm
index 54f3d7a..ad43928 100644
--- a/lib/Wifty/Model/Page.pm
+++ b/lib/Wifty/Model/Page.pm
@@ -128,7 +128,9 @@ sub _set {
$self->SUPER::_set(
column => 'updated_by',
- value => ( $self->current_user? $self->current_user->user_object->id : undef )
+ value => ( $self->current_user->user_object
+ ? $self->current_user->user_object->id
+ : undef )
);
return ( $val, $msg );
diff --git a/lib/Wifty/Model/User.pm b/lib/Wifty/Model/User.pm
index 9a61bf1..a97d6dd 100644
--- a/lib/Wifty/Model/User.pm
+++ b/lib/Wifty/Model/User.pm
@@ -1,131 +1,4 @@
-package Wifty::Model::User::Schema;
-use Jifty::DBI::Schema;
-
-column name =>
- type is 'text',
- label is 'Name',
- is mandatory,
- is distinct;
-
-column email =>
- type is 'text',
- label is 'Email address',
- is mandatory,
- is distinct;
-
-column password =>,
- type is 'text',
- label is 'Password',
- render_as 'password';
-
-column email_confirmed =>
- label is 'Email address confirmed?',
- type is 'boolean',
- since '0.0.19';
-
-column auth_token =>
- type is 'text',
- render_as 'Password',
- since '0.0.15';
-
-
-
package Wifty::Model::User;
-use base qw/Wifty::Record/;
-use Wifty::Notification::ConfirmAddress;
-
-sub since {'0.0.7'}
-
-sub create {
- my $self = shift;
- my %args = (@_);
- my (@ret) = $self->SUPER::create(%args);
-
- if ($self->id and not $self->email_confirmed) {
- Wifty::Notification::ConfirmAddress->new( to => $self )->send;
- }
- return (@ret);
-}
-
-
-=head2 password_is STRING
-
-Returns true if and only if the current user's password matches STRING
-
-=cut
-
-
-sub password_is {
- my $self = shift;
- my $string = shift;
- return 1 if ($self->_value('password') eq $string);
- return 0;
-}
-
-=head2 password
-
-Never display a password
-
-=cut
-
-sub password {
- return undef;
-
-}
-
-=head2 current_user_can
-
-Allows the current user to see all their own attributes and
-everyone else to see their username.
-
-Allows the current user to update any of their own attributes
-except whether or not their email has been confirmed.
-
-Passes everything else off to the superclass.
-
-=cut
-
-
-sub current_user_can {
- my $self = shift;
- my $right = shift;
- my %args = (@_);
- if ( $right eq 'read'
- and $self->id == $self->current_user->id )
- {
- return 1;
- } elsif ( $right eq 'read' and $args{'column'} and $args{'column'} eq 'name' ) {
- return (1);
-
- } elsif ( $right eq 'update'
- and $self->id == $self->current_user->id
- and $args{'column'} ne 'email_confirmed' )
- {
- return (1);
- }
-
- return $self->SUPER::current_user_can( $right, %args );
-}
-
-=head2 auth_token
-
-Returns the user's unique authentication token. If the user
-doesn't have one, sets one and returns it.
-
-=cut
-
-
-sub auth_token {
- my $self = shift;
- return undef unless ($self->current_user_can( read => column => 'auth_token'));
- my $value = $self->_value('auth_token') ;
- unless ($value) {
- my $digest =Digest::MD5->new();
- $digest->add(rand(100));
- $self->__set(column => 'auth_token', value => $digest->b64digest);
- }
- return $self->_value('auth_token') ;
-
-}
+use base qw/Jifty::Plugin::Login::Model::User/;
1;
diff --git a/lib/Wifty/Notification/ConfirmAddress.pm b/lib/Wifty/Notification/ConfirmAddress.pm
deleted file mode 100755
index 3fb3c3a..0000000
--- a/lib/Wifty/Notification/ConfirmAddress.pm
+++ /dev/null
@@ -1,53 +0,0 @@
-use warnings;
-use strict;
-
-package Wifty::Notification::ConfirmAddress;
-use base qw/Wifty::Notification/;
-
-=head1 NAME
-
-Hiveminder::Notification::ConfirmAddress
-
-=head1 ARGUMENTS
-
-C<to>, a L<Wifty::Model::User> whose address we are confirming.
-
-=cut
-
-=head2 setup
-
-Sets up the fields of the message.
-
-=cut
-
-sub setup {
- my $self = shift;
-
- unless (UNIVERSAL::isa($self->to, "Wifty::Model::User")) {
- $self->log->error((ref $self) . " called with invalid user argument");
- return;
- }
-
-
- my $letme = Jifty::LetMe->new();
- $letme->email($self->to->email);
- $letme->path('confirm_email');
- my $confirm_url = $letme->as_url;
-
- $self->subject( "Welcome to Wifty!" );
-
-
- $self->body(<<"END_BODY");
-
-You're getting this message because you (or somebody claiming to be you)
-signed up for a Wiki running Wifty.
-
-Before you can use Wifty, we need to make sure that we got your email
-address right. Click on the link below to get started:
-
-$confirm_url
-
-END_BODY
-}
-
-1;
commit 3f3bff22674bd70b995becaebc1cfb16e3924d42
Author: Nelson Elhage <nelhage at bestpractical.com>
Date: Sun Sep 3 02:59:55 2006 +0000
Trying out a new idea for a Jifty idiom with Wifty.
Make a Wifty::Form::Field::WikiPage J::W::Form::Field subclass, and
have C<content> fields C<render_as> it. Then, rendering pages by
calling C<form_value> for C<content> field of an appropriate
C<UpdatePage> action. Note that this also makes admin mode look nicer.
diff --git a/lib/Wifty/Dispatcher.pm b/lib/Wifty/Dispatcher.pm
index 00f57d8..aae97fe 100644
--- a/lib/Wifty/Dispatcher.pm
+++ b/lib/Wifty/Dispatcher.pm
@@ -39,7 +39,11 @@ on qr{^/(view|edit)/(.*)}, run {
$revision->load_by_cols( page => $page->id, id => $rev ) if ($rev);
set page => $page;
set revision => $revision;
- set viewer => Jifty->web->new_action( class => 'UpdatePage', record => $page );
+ my $viewer = Jifty->web->new_action( class => 'UpdatePage', record => $page );
+ if($rev) {
+ $viewer->argument_value(content => $revision->content);
+ }
+ set viewer => $viewer;
show("/$page_name");
};
diff --git a/lib/Wifty/Form/Field/WikiPage.pm b/lib/Wifty/Form/Field/WikiPage.pm
new file mode 100644
index 0000000..99fa3a2
--- /dev/null
+++ b/lib/Wifty/Form/Field/WikiPage.pm
@@ -0,0 +1,80 @@
+use warnings;
+use strict;
+
+=head1 NAME
+
+Wifty::Form::Field::WikiPage
+
+=head1 DESCRIPTION
+
+A L<Jifty::Web::Form::Field> subclass that renders itself as a text
+field on update, and wikifies itself on read-only display.
+
+=cut
+
+package Wifty::Form::Field::WikiPage;
+use base qw(Jifty::Web::Form::Field::Textarea);
+
+=head2 render_value
+
+Render a wikified view of this field's content.
+
+=cut
+
+sub render_value {
+ my $self = shift;
+ my $field;
+ my $field = '<span';
+ $field .= qq! class="@{[ $self->classes ]}"> !;
+ $field .= $self->wiki_content;
+ $field .= qq!</span>\n!;
+ Jifty->web->out($field);
+ return '';
+
+}
+
+
+=head2 wiki_content
+
+Wikify this field's C<current_value>
+
+=cut
+
+
+sub wiki_content {
+ my $self = shift;
+ my $content = $self->current_value;
+ my $scrubber = HTML::Scrubber->new();
+
+ $scrubber->default(
+ 0,
+ { '*' => 0,
+ id => 1,
+ class => 1,
+ href => qr{^(?:(?:\w+$)|http:|ftp:|https:|/)}i,
+
+ # Match http, ftp and relative urls
+ face => 1,
+ size => 1,
+ target => 1
+ }
+ );
+
+ $scrubber->deny(qw[*]);
+ $scrubber->allow(
+ qw[H1 H2 H3 H4 H5 A STRONG EM CODE PRE B U P BR I HR BR SPAN DIV UL OL LI DL DT DD]);
+ $scrubber->comment(0);
+
+ $content = Text::Markdown::markdown( $content );
+ $content = $scrubber->scrub( $content );
+ return ( $content );
+
+}
+
+=head1 SEE ALSO
+
+L<Text::Markdown>, L<Jifty::Web::Form::Field::Textarea>
+
+=cut
+
+1;
diff --git a/lib/Wifty/Model/Page.pm b/lib/Wifty/Model/Page.pm
index ad43928..f39c5d4 100644
--- a/lib/Wifty/Model/Page.pm
+++ b/lib/Wifty/Model/Page.pm
@@ -11,7 +11,7 @@ column name =>
column content =>
type is 'text',
label is 'Content',
- render_as 'textarea';
+ render_as 'Wifty::Form::Field::WikiPage';
column updated =>
type is 'timestamp',
@@ -32,43 +32,6 @@ use Text::Markdown;
use HTML::Scrubber;
-=head2 wiki_content [CONTENT]
-
-Wikify either the content of a scalar passed in as an argument or
-this page's "content" attribute.
-
-=cut
-
-sub wiki_content {
- my $self = shift;
- my $content = shift || $self->content() || '';
- my $scrubber = HTML::Scrubber->new();
-
- $scrubber->default(
- 0,
- { '*' => 0,
- id => 1,
- class => 1,
- href => qr{^(?:(?:\w+$)|http:|ftp:|https:|/)}i,
-
- # Match http, ftp and relative urls
- face => 1,
- size => 1,
- target => 1
- }
- );
-
- $scrubber->deny(qw[*]);
- $scrubber->allow(
- qw[H1 H2 H3 H4 H5 A STRONG EM CODE PRE B U P BR I HR BR SPAN DIV UL OL LI DL DT DD]);
- $scrubber->comment(0);
-
- $content = Text::Markdown::markdown( $content );
- $content = $scrubber->scrub( $content );
- return ( $content );
-
-}
-
sub create {
my $self = shift;
my %args = (@_);
diff --git a/lib/Wifty/Model/Revision.pm b/lib/Wifty/Model/Revision.pm
index 7fe23b3..f75545e 100644
--- a/lib/Wifty/Model/Revision.pm
+++ b/lib/Wifty/Model/Revision.pm
@@ -3,7 +3,7 @@ use Jifty::DBI::Schema;
column page => refers_to Wifty::Model::Page;
-column content => type is 'text', render_as 'textarea';
+column content => type is 'text', render_as 'Wifty::Form::Field::WikiPage';
column created => type is 'timestamp';
diff --git a/share/web/templates/edit b/share/web/templates/edit
index 9e8084c..c036545 100644
--- a/share/web/templates/edit
+++ b/share/web/templates/edit
@@ -18,7 +18,7 @@ my $can_edit = $page->current_user_can('update');
copy the source of this page. </p>
% }
<% Jifty->web->form->next_page( url => '/view/'.$page->name) %>
-<% $viewer->form_field('content', ($revision->id ? (default_value => $revision->content) : (undef, undef)), rows => 30 )%>
+<% $viewer->form_field('content', rows => 30 )%>
</div>
% if($can_edit) {
<div class="line">
commit 673ffc3e3e50c94dc35ebbc0775f77f0b8537dd7
Author: Nelson Elhage <nelhage at bestpractical.com>
Date: Sun Sep 3 03:00:44 2006 +0000
Oops, forgot the view template in the last commit.
diff --git a/share/web/templates/view b/share/web/templates/view
index 0a4e256..75133b9 100644
--- a/share/web/templates/view
+++ b/share/web/templates/view
@@ -1,14 +1,15 @@
<%args>
$page
$revision
+$viewer
</%args>
<& /_elements/page_nav, page => $page->name, rev => $revision->id &>
<&|/_elements/wrapper, title => $page->name . ($revision->id ? " as of ".$revision->created : '') &>
+
% if ($revision->id) {
<& /_elements/diff, page => $page, to => $revision &>
-
-<% $page->wiki_content($revision->content) |n%>
-% } else {
-<% $page->wiki_content |n %>
% }
+
+<% $viewer->form_value('content', label => "") %>
+
</&>
commit d1645d29ab81f9507b39615509244e13c3f33c4f
Author: Nelson Elhage <nelhage at bestpractical.com>
Date: Sun Sep 3 03:01:09 2006 +0000
Some Wifty admin mode styling.
diff --git a/share/web/static/css/forms.css b/share/web/static/css/forms.css
index 338f291..125bec9 100644
--- a/share/web/static/css/forms.css
+++ b/share/web/static/css/forms.css
@@ -113,4 +113,11 @@ form .line {
.jifty_admin .editlink {
float: right;
+ border-left: 1px solid black;
+ border-bottom: 1px solid black;
+ padding: 0 0 10px 10px;
+}
+
+.jifty_admin hr {
+ clear: both;
}
commit e1671a4fb01e227d5ee3d7adc79e714403424aab
Author: Nelson Elhage <nelhage at bestpractical.com>
Date: Sun Sep 3 03:32:10 2006 +0000
Moving the '30 rows' into WikiPage.pm, so it affects admin mode as well.
diff --git a/lib/Wifty/Form/Field/WikiPage.pm b/lib/Wifty/Form/Field/WikiPage.pm
index 99fa3a2..261813a 100644
--- a/lib/Wifty/Form/Field/WikiPage.pm
+++ b/lib/Wifty/Form/Field/WikiPage.pm
@@ -71,6 +71,14 @@ sub wiki_content {
}
+=head2 rows
+
+C<WikiPage> forms have 30 rows in their textarea by default
+
+=cut
+
+sub rows { 30 };
+
=head1 SEE ALSO
L<Text::Markdown>, L<Jifty::Web::Form::Field::Textarea>
diff --git a/share/web/templates/edit b/share/web/templates/edit
index c036545..c7b0ab3 100644
--- a/share/web/templates/edit
+++ b/share/web/templates/edit
@@ -18,7 +18,7 @@ my $can_edit = $page->current_user_can('update');
copy the source of this page. </p>
% }
<% Jifty->web->form->next_page( url => '/view/'.$page->name) %>
-<% $viewer->form_field('content', rows => 30 )%>
+<% $viewer->form_field('content')%>
</div>
% if($can_edit) {
<div class="line">
commit 8af14b855fb20beb1685076b130c49becd90ea24
Author: Nelson Elhage <nelhage at bestpractical.com>
Date: Sun Sep 3 04:19:28 2006 +0000
Some search CSS fixes for Wifty.
diff --git a/share/web/static/css/app.css b/share/web/static/css/app.css
index 2f5ed0f..e265b24 100644
--- a/share/web/static/css/app.css
+++ b/share/web/static/css/app.css
@@ -9,6 +9,14 @@ label.argument-content {
display: none !important;
}
+.jifty_admin label.argument-content {
+ display: inline !important;
+}
+
+.jifty_admin div.argument-content {
+ width: 100%;
+}
+
textarea.argument-content {
width: 100%;
font-size: 1.2em;
commit 587dc0d0b3d6f675c80fc2381e9176c417d3cb45
Author: John Peacock <jpeacock at cpan.org>
Date: Tue Oct 10 09:17:58 2006 +0000
Remove duplicate "my $field;" line.
diff --git a/lib/Wifty/Form/Field/WikiPage.pm b/lib/Wifty/Form/Field/WikiPage.pm
index 261813a..6c7bec5 100644
--- a/lib/Wifty/Form/Field/WikiPage.pm
+++ b/lib/Wifty/Form/Field/WikiPage.pm
@@ -23,7 +23,6 @@ Render a wikified view of this field's content.
sub render_value {
my $self = shift;
- my $field;
my $field = '<span';
$field .= qq! class="@{[ $self->classes ]}"> !;
$field .= $self->wiki_content;
commit 53468fd7b768704754ed53a4ab6186946bc3aee3
Author: John Peacock <jpeacock at cpan.org>
Date: Wed Oct 11 01:00:23 2006 +0000
Can't see the salutation link when Admin mode is enabled (red text on
slightly different red background). Black at least shows up. ;-)
diff --git a/share/web/static/css/app-base.css b/share/web/static/css/app-base.css
index d6aeb11..72446d2 100644
--- a/share/web/static/css/app-base.css
+++ b/share/web/static/css/app-base.css
@@ -16,6 +16,10 @@ a {
font-size: 0.9em;
}
+#salutation a {
+ color: black;
+}
+
#header {
margin-top: 2.3em;
}
commit dd5f2e511f7cf96b0210ecb5f73849519a6d683c
Author: Nelson Elhage <nelhage at bestpractical.com>
Date: Thu Oct 19 19:52:03 2006 +0000
Backing out the SMTP mailer
diff --git a/etc/config.yml b/etc/config.yml
index 4fad165..df6b32e 100644
--- a/etc/config.yml
+++ b/etc/config.yml
@@ -12,10 +12,9 @@ framework:
RequireSSL: 0
Plugins:
- Login: {}
- Mailer: SMTP
- MailerArgs: ['69.17.117.59']
-# MailerArgs:
-# - %log/mail.log%
+ Mailer: IO
+ MailerArgs:
+ - %log/mail.log%
SiteConfig: etc/site_config.yml
Web:
commit f4035a321b58e9ca653ca48a6cb5d68f81def410
Author: Nelson Elhage <nelhage at bestpractical.com>
Date: Thu Oct 19 20:58:13 2006 +0000
Adding a simplistic search page to Wifty
diff --git a/lib/Wifty/Dispatcher.pm b/lib/Wifty/Dispatcher.pm
index aae97fe..e19d01f 100644
--- a/lib/Wifty/Dispatcher.pm
+++ b/lib/Wifty/Dispatcher.pm
@@ -69,6 +69,19 @@ on 'pages', run {
set pages => $pages;
};
+on 'search', run {
+ my $search = Jifty->web->response->result('search');
+ my $collection = undef;
+ if($search) {
+ $collection = $search->content('search');
+ }
+ my $action = Jifty->web->new_action(class => 'SearchPage', moniker => 'search');
+ $action->sticky_on_success(1);
+
+ set search => $action;
+ set pages => $collection;
+};
+
# Show recent edits
on 'recent', run {
my $then = DateTime->from_epoch( epoch => ( time - ( 86400 * 7 ) ) );
diff --git a/share/web/templates/_elements/nav b/share/web/templates/_elements/nav
index 69b953c..808bd8c 100644
--- a/share/web/templates/_elements/nav
+++ b/share/web/templates/_elements/nav
@@ -2,6 +2,7 @@
my $top = Jifty->web->navigation;
$top->child( Home => url => "/", sort_order => 1 );
$top->child( Recent => url => "/recent", label => "Recent Changes", sort_order => 2 );
+$top->child( Search => url => "/search", label => "Search", sort_order => 3 );
if ( Jifty->config->framework('AdminMode') ) {
$top->child( Administration => url => "/__jifty/admin/", sort_order => 998);
commit b2eca96edab7ffe51bf420d54ae980272328ef53
Author: Nelson Elhage <nelhage at bestpractical.com>
Date: Thu Oct 19 21:04:41 2006 +0000
Actually adding the search page, and restyling it slightly
diff --git a/share/web/static/css/app.css b/share/web/static/css/app.css
index e265b24..290e344 100644
--- a/share/web/static/css/app.css
+++ b/share/web/static/css/app.css
@@ -34,13 +34,13 @@ form .submit_button input {
border: 1px outset #3d4286;
}
-#recentupdates dt {
+.pagelist dt {
float: left;
clear: left;
width: 45%;
}
-#recentupdates dd {
+.pagelist dd {
margin-left: 2em;
padding-left: 0;
margin-bottom: 0.5em;
@@ -48,8 +48,8 @@ form .submit_button input {
width: 50%;
}
-* html #recentupdates dt,
-* html #recentupdates dd { position: relative; }
+* html .pagelist dt,
+* html .pagelist dd { position: relative; }
#history dd {
font-size: 0.95em;
diff --git a/share/web/templates/recent b/share/web/templates/_elements/page_list
similarity index 76%
copy from share/web/templates/recent
copy to share/web/templates/_elements/page_list
index 0464768..f4bfd15 100644
--- a/share/web/templates/recent
+++ b/share/web/templates/_elements/page_list
@@ -1,8 +1,8 @@
<%args>
$pages
+$id
</%args>
-<&|/_elements/wrapper, title => 'Updated this week' &>
-<dl id="recentupdates">
+<dl id="<%$id%>" class="pagelist">
% while (my $page = $pages->next) {
<dt><% Jifty->web->link( label => $page->name, url => '/view/'.$page->name)%></dt>
<dd><%$page->updated%>
@@ -14,4 +14,3 @@ $pages
</dd>
% }
</dl>
-</&>
diff --git a/share/web/templates/pages b/share/web/templates/pages
index c39f5ae..4495c8e 100644
--- a/share/web/templates/pages
+++ b/share/web/templates/pages
@@ -2,15 +2,5 @@
$pages
</%args>
<&|/_elements/wrapper, title => 'These are the pages on your wiki!' &>
-<ul id="pagelist">
-% while (my $page = $pages->next) {
-<li><%
- Jifty->web->link(
- label => $page->name,
- url => '/view/' . $page->name
- )
-
- %></li>
-% }
-</ul>
+<& /_elements/page_list, pages => $pages, id => 'allpages' &>
</&>
diff --git a/share/web/templates/recent b/share/web/templates/recent
index 0464768..071ac48 100644
--- a/share/web/templates/recent
+++ b/share/web/templates/recent
@@ -2,16 +2,5 @@
$pages
</%args>
<&|/_elements/wrapper, title => 'Updated this week' &>
-<dl id="recentupdates">
-% while (my $page = $pages->next) {
-<dt><% Jifty->web->link( label => $page->name, url => '/view/'.$page->name)%></dt>
-<dd><%$page->updated%>
-% if($page->updated_by->id) {
- (<% $page->updated_by->name %>)
-% } else {
- (Anonymous)
-% }
-</dd>
-% }
-</dl>
+<& /_elements/page_list, pages => $pages, id => 'recentupdates' &>
</&>
diff --git a/share/web/templates/search b/share/web/templates/search
new file mode 100644
index 0000000..6d77876
--- /dev/null
+++ b/share/web/templates/search
@@ -0,0 +1,19 @@
+<%args>
+$pages
+$search
+</%args>
+<%init>
+warn $search;
+</%init>
+<&|/_elements/wrapper, title => 'Search' &>
+<% Jifty->web->form->start %>
+ <div id="searchbox" class="inline">
+ <% $search->form_field('contains', label => 'Find pages containing:') %>
+ <% $search->button(label => 'Search') %>
+ </div>
+
+<% Jifty->web->form->end %>
+% if($pages) {
+<& /_elements/page_list, pages => $pages, id => 'searchresults' &>
+% }
+</&>
commit af80295d518132ef9038f6d1b3a7ad8fca375a9e
Author: Nelson Elhage <nelhage at bestpractical.com>
Date: Thu Oct 19 21:41:45 2006 +0000
Adding a search bar to the menu
diff --git a/share/web/static/css/app-base.css b/share/web/static/css/app-base.css
index 72446d2..71e20ef 100644
--- a/share/web/static/css/app-base.css
+++ b/share/web/static/css/app-base.css
@@ -28,7 +28,12 @@ a {
float: right;
text-align: right;
padding-right: 1px;
- width: 28%;
+ width: 50%;
+}
+
+#wikiheader label {
+ display: inline;
+ float: none;
}
#wikiname {
diff --git a/share/web/templates/_elements/wrapper b/share/web/templates/_elements/wrapper
index 2ba5a29..67b2e13 100644
--- a/share/web/templates/_elements/wrapper
+++ b/share/web/templates/_elements/wrapper
@@ -15,6 +15,7 @@
</h1>
<% Jifty->web->navigation->render_as_menu %>
+ <& /_elements/search_box &>
</div>
<div id="pageheader">
commit 605c6fdb9e9aa81c048e9a7288f98c16553bf835
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Thu Oct 19 21:42:18 2006 +0000
Added support for Kwiki style markup, wiki logos and a kwiki (most recent version) importer
diff --git a/Makefile.PL b/Makefile.PL
index e79fd94..8273f33 100644
--- a/Makefile.PL
+++ b/Makefile.PL
@@ -5,4 +5,5 @@ requires('Jifty');
requires('Text::Markdown');
requires('HTML::Scrubber');
requires('Text::Diff::HTML');
+recommends('Text::KwikiFormatish');
WriteAll;
diff --git a/bin/import_kwiki b/bin/import_kwiki
new file mode 100644
index 0000000..fa05624
--- /dev/null
+++ b/bin/import_kwiki
@@ -0,0 +1,53 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Text::KwikiFormatish;
+
+use Jifty;
+Jifty->new();
+
+get_pages('/tmp/svkwiki/data/database');
+
+
+
+sub import_page {
+ my $path = shift;
+ my $name = shift;
+ my $page = Wifty::Model::Page->new(current_user => Wifty::CurrentUser->superuser);
+ open( my $file, "<", $path)||die $!;
+ my @content = <$file>;
+ # chomp(@content);
+ close $file;
+ my $content = join("\n", at content);
+ return unless $content;
+ my $html = Text::KwikiFormatish::format( $content);
+
+ $name =~ s/\s*//g;
+ $page->load_by_cols(name =>$name);
+ if ($page->id) {
+ $page->set_content($content);
+ }
+ else {
+ my ($ret)= $page->create( name => $name, content => $content);
+ warn $ret;
+ }
+}
+
+sub get_pages {
+ my $src = shift;
+ File::Find::find(
+ { wanted => sub {
+ &import_page($File::Find::name, $_)
+ },
+ follow => 0
+
+ },
+
+ $src
+ );
+
+
+
+}
diff --git a/etc/config.yml b/etc/config.yml
index df6b32e..b84a8a3 100644
--- a/etc/config.yml
+++ b/etc/config.yml
@@ -23,3 +23,7 @@ framework:
application:
# RequireAuth: 1
WikiName: A Wiki
+ Formatter: Markdown
+ # The formatter options are "Markdown" and "Kwiki"
+ # Logo: http://svk.bestpractical.com/svk-logo.png
+ # The logo points to the url to a logo image
diff --git a/lib/Wifty/Form/Field/WikiPage.pm b/lib/Wifty/Form/Field/WikiPage.pm
index 6c7bec5..cee01e1 100644
--- a/lib/Wifty/Form/Field/WikiPage.pm
+++ b/lib/Wifty/Form/Field/WikiPage.pm
@@ -15,6 +15,10 @@ field on update, and wikifies itself on read-only display.
package Wifty::Form::Field::WikiPage;
use base qw(Jifty::Web::Form::Field::Textarea);
+use HTML::Scrubber;
+
+
+
=head2 render_value
Render a wikified view of this field's content.
@@ -50,7 +54,7 @@ sub wiki_content {
{ '*' => 0,
id => 1,
class => 1,
- href => qr{^(?:(?:\w+$)|http:|ftp:|https:|/)}i,
+ href => qr{^(?:(?:\w+$)|http:|ftp:|https:|\.?/)}i,
# Match http, ftp and relative urls
face => 1,
@@ -64,8 +68,15 @@ sub wiki_content {
qw[H1 H2 H3 H4 H5 A STRONG EM CODE PRE B U P BR I HR BR SPAN DIV UL OL LI DL DT DD]);
$scrubber->comment(0);
- $content = Text::Markdown::markdown( $content );
- $content = $scrubber->scrub( $content );
+ if (Jifty->config->app('Formatter') eq 'Markdown' ) {
+ require Text::Markdown;
+ $content = Text::Markdown::markdown( $content );
+ }
+ elsif (Jifty->config->app('Formatter') eq 'Kwiki') {
+ require Text::KwikiFormatish;
+ $content = Text::KwikiFormatish::format( $content);
+ }
+ #$content = $scrubber->scrub( $content );
return ( $content );
}
diff --git a/lib/Wifty/Model/Page.pm b/lib/Wifty/Model/Page.pm
index f39c5d4..96a31df 100644
--- a/lib/Wifty/Model/Page.pm
+++ b/lib/Wifty/Model/Page.pm
@@ -28,8 +28,6 @@ column revisions =>
package Wifty::Model::Page;
use base qw/Wifty::Record/;
use Wifty::Model::RevisionCollection;
-use Text::Markdown;
-use HTML::Scrubber;
sub create {
diff --git a/share/web/static/css/app-base.css b/share/web/static/css/app-base.css
index 71e20ef..94becf6 100644
--- a/share/web/static/css/app-base.css
+++ b/share/web/static/css/app-base.css
@@ -24,6 +24,10 @@ a {
margin-top: 2.3em;
}
+#logo {
+ float: right;
+}
+
#wikiheader {
float: right;
text-align: right;
diff --git a/share/web/templates/_elements/markup b/share/web/templates/_elements/markup
index cd83f25..24e4c9c 100644
--- a/share/web/templates/_elements/markup
+++ b/share/web/templates/_elements/markup
@@ -1,3 +1,7 @@
+<%init>
+return undef unless (Jifty->config->app('Formatter') eq 'Markdown');
+</%init>
+
<div id="syntax">
<div><a href="#" onclick="Element.toggle('syntax_content');return(false);"><b>Wiki Syntax Help</b></a>
</div>
diff --git a/share/web/templates/_elements/wrapper b/share/web/templates/_elements/wrapper
index 67b2e13..f06fbe2 100644
--- a/share/web/templates/_elements/wrapper
+++ b/share/web/templates/_elements/wrapper
@@ -7,7 +7,9 @@
url => '/__jifty/admin/') %>.
</div>
% }
-
+ <div id="logo">
+ <% Jifty->config->app('Logo') ? '<img src="'.Jifty->config->app('Logo').'" alt="" />' : '' |n %>
+ </div>
<div id="header">
<div id="wikiheader">
<h1 id="wikiname">
commit c838c52bc714617d5c95ae13ba2699909f1d24f1
Author: Nelson Elhage <nelhage at bestpractical.com>
Date: Thu Oct 19 21:46:37 2006 +0000
Actually adding the search box component
diff --git a/share/web/templates/_elements/search_box b/share/web/templates/_elements/search_box
new file mode 100644
index 0000000..c487d97
--- /dev/null
+++ b/share/web/templates/_elements/search_box
@@ -0,0 +1,10 @@
+<%init>
+my $action = Jifty->web->new_action(class => 'SearchPage', moniker => 'search');
+$action->sticky_on_success(1);
+</%init>
+<span>
+<% Jifty->web->form->start %>
+<% Jifty->web->form->next_page(url => '/search') %>
+<% $action->form_field('contains', label => 'Search:') %>
+<% Jifty->web->form->end %>
+</span>
commit 6a82a76c7f38ec1e9a73d5319d471a5a5ed2063a
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Wed Nov 1 04:49:39 2006 +0000
importer updates for svk wiki conversion
diff --git a/bin/import_kwiki b/bin/import_kwiki
index fa05624..68011b4 100644
--- a/bin/import_kwiki
+++ b/bin/import_kwiki
@@ -1,4 +1,4 @@
-#!/usr/bin/perl
+#!/opt/local/bin/perl
use warnings;
use strict;
@@ -8,7 +8,7 @@ use Text::KwikiFormatish;
use Jifty;
Jifty->new();
-get_pages('/tmp/svkwiki/data/database');
+get_pages(shift @ARGV);
@@ -21,17 +21,17 @@ sub import_page {
# chomp(@content);
close $file;
my $content = join("\n", at content);
- return unless $content;
+ return unless length($content) > 4;
my $html = Text::KwikiFormatish::format( $content);
- $name =~ s/\s*//g;
+ #$name =~ s/\s*//g;
$page->load_by_cols(name =>$name);
if ($page->id) {
$page->set_content($content);
}
else {
my ($ret)= $page->create( name => $name, content => $content);
- warn $ret;
+ warn $ret. ": $name";
}
}
diff --git a/lib/Wifty/Dispatcher.pm b/lib/Wifty/Dispatcher.pm
index e19d01f..2b7ee8d 100644
--- a/lib/Wifty/Dispatcher.pm
+++ b/lib/Wifty/Dispatcher.pm
@@ -83,7 +83,7 @@ on 'search', run {
};
# Show recent edits
-on 'recent', run {
+on 'recent*', run {
my $then = DateTime->from_epoch( epoch => ( time - ( 86400 * 7 ) ) );
my $pages = Wifty::Model::PageCollection->new();
$pages->limit(
commit 37cf4fccdcca55653ddce8758ebf37733dc14735
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Wed Nov 1 14:39:17 2006 +0000
page numbers ending in digits were triggering the wrong regex
diff --git a/lib/Wifty/Dispatcher.pm b/lib/Wifty/Dispatcher.pm
index 2b7ee8d..0cd5b94 100644
--- a/lib/Wifty/Dispatcher.pm
+++ b/lib/Wifty/Dispatcher.pm
@@ -28,7 +28,7 @@ on '/create/*', run {
on qr{^/(view|edit)/(.*)}, run {
my ( $name, $rev );
my $page_name = $1;
- if ( $2 =~ qr{^(.*?)/?(\d*?)$} ) {
+ if ( $2 =~ qr{^(.*?)(/\d*)?$} ) {
$name = $1;
$rev = $2;
}
commit 2fb98886582f72f3b9d388160dadc191885457de
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Wed Nov 1 17:58:40 2006 +0000
better chomp() import
diff --git a/bin/import_kwiki b/bin/import_kwiki
index 68011b4..20bc777 100644
--- a/bin/import_kwiki
+++ b/bin/import_kwiki
@@ -18,7 +18,7 @@ sub import_page {
my $page = Wifty::Model::Page->new(current_user => Wifty::CurrentUser->superuser);
open( my $file, "<", $path)||die $!;
my @content = <$file>;
- # chomp(@content);
+ chomp(@content);
close $file;
my $content = join("\n", at content);
return unless length($content) > 4;
commit b0ecb51eea8af3ba9bba081f6d1c9960113ff19b
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Thu Nov 2 22:28:33 2006 +0000
Regex messup made it impossible to view history
diff --git a/lib/Wifty/Dispatcher.pm b/lib/Wifty/Dispatcher.pm
index 0cd5b94..ac6563d 100644
--- a/lib/Wifty/Dispatcher.pm
+++ b/lib/Wifty/Dispatcher.pm
@@ -28,7 +28,7 @@ on '/create/*', run {
on qr{^/(view|edit)/(.*)}, run {
my ( $name, $rev );
my $page_name = $1;
- if ( $2 =~ qr{^(.*?)(/\d*)?$} ) {
+ if ( $2 =~ qr{^(.*?)(?:/(\d*))?$} ) {
$name = $1;
$rev = $2;
}
commit 5d5d1d5afa59efad2568e3c28b5231b8b0948e10
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Mon Nov 13 00:12:25 2006 +0000
First draft of declarative templates for Wifty. Only works on the Template::Declare branch of jifty
diff --git a/lib/Wifty/View.pm b/lib/Wifty/View.pm
new file mode 100644
index 0000000..59077b3
--- /dev/null
+++ b/lib/Wifty/View.pm
@@ -0,0 +1,642 @@
+use warnings;
+use strict;
+
+
+=head1 NAME
+
+Wifty::View
+
+=head1 DESCRIPTION
+
+This code is only useful on the new Jifty "Declarative tempaltes" branch. It shouldn't get in the way
+if you're running a traditional (0.610 or before) Jifty.
+
+=cut
+
+package Wifty::View;
+use base qw/Jifty::View::Declare::Templates/;
+# includes my application's plugins' View libraries as superclasses.
+use Template::Declare::Tags;
+use Jifty::View::Declare::Templates;
+
+private template page_list => sub {
+
+ # actually creates: sub _jifty_ui_template_page_list
+ #
+ my ( $pages, $id ) = get(qw(pages id));
+ with( id => $id, class => "pagelist" ), dl {
+
+ while ( my $page = $pages->next ) {
+ dt {
+ hyperlink(
+ label => $page->name,
+ url => '/view/' . $page->name
+ );
+ };
+ dd {
+ outs( $page->updated );
+ outs(
+ ' - ('
+ . (
+ $page->updated_by->id
+ ? $page->updated_by->name
+ : _('Anonymous')
+ )
+ . ')'
+ );
+ };
+ }
+ };
+};
+
+private template nav => sub {
+ my $top = Jifty->web->navigation;
+ $top->child( Home => url => "/", sort_order => 1 );
+ $top->child(
+ Recent =>
+ url => "/recent",
+ label => "Recent Changes",
+ sort_order => 2
+ );
+ $top->child(
+ Search =>
+ url => "/search",
+ label => "Search",
+ sort_order => 3
+ );
+
+ if ( Jifty->config->framework('AdminMode') ) {
+ $top->child(
+ Administration =>
+ url => "/__jifty/admin/",
+ sort_order => 998
+ );
+ $top->child(
+ OnlineDocs =>
+ url => "/__jifty/online_docs/",
+ label => 'Online docs',
+ sort_order => 999
+ );
+ }
+
+};
+
+private template page_nav => sub {
+ my %args = (page => 'HomePage', rev => undef, @_);
+ my $page = $args{'page'};
+ my $rev = $args{'rev'};
+
+ $page ||= 'HomePage';
+ my $subpath = $page . ( $rev ? "/$rev" : '' );
+ my $top = Jifty->web->page_navigation;
+
+ my $page_obj = Wifty::Model::Page->new();
+ $page_obj->load_by_cols( name => $page );
+
+ $top->child( View => url => '/view/' . $subpath );
+ $top->child( Edit => url => '/edit/' . $subpath );
+ $top->child( History => url => '/history/' . $page );
+ $top->child( Latest => url => '/view/' . $page ) if ($rev);
+
+};
+
+private template wrapper => sub {
+ # it's actually called with args.
+ my ($args, $coderef ) = (@_);
+ my $title = $args->{title};
+ my $id = $args->{id};
+ my $wikiname = Jifty->config->app('WikiName') || "Wifty";
+
+ show('nav');
+ show( 'header', title => $args->{'title'}, wikiname => $wikiname );
+
+ with( id => $args->{id} ), body {
+
+ if ( Jifty->config->framework('AdminMode') ) {
+ with( class => 'warning admin_mode' ), div {
+ _('Alert') . ":"
+ . tangent(
+ label => _('Administration mode is enabled'),
+ url => '/__jifty/admin/'
+ )
+ . ".";
+ }
+ }
+ with( id => 'logo' ), div {
+ Jifty->config->app('Logo')
+ ? '<img src="' . Jifty->config->app('Logo') . '" alt="" />'
+ : '';
+ };
+ with( id => 'header' ), div {
+ with( id => 'wikiheader' ), div {
+ with( id => "wikiname" ), h1 {
+ hyperlink( url => "/", label => _($wikiname) );
+ };
+ outs(Jifty->web->navigation->render_as_menu);
+ show('search_box');
+
+ };
+ with( id => 'pageheader' ), div {
+ with( id => "pagename" ), h1 {
+ _( $args->{title} );
+ };
+
+ outs (Jifty->web->page_navigation->render_as_menu);
+ }
+ };
+
+ show('salutation');
+
+ with( class => "clear" ), hr {};
+ with( id => 'content' ), div {
+ Jifty->web->render_messages;
+ my $buf = '';
+ {
+ local $Template::Declare::Tags::BUFFER ='';
+ $coderef->();
+ $buf = $Template::Declare::Tags::BUFFER;
+ warn "My buffer is $buf";
+ }
+ outs($buf);
+ with( class => "clear" ), hr { };
+
+ }
+ }
+
+};
+
+
+private template search_box => sub {
+ my $action = new_action( class => 'SearchPage' );
+ $action->sticky_on_success(1);
+ span {
+ form {
+
+ form_next_page( url => '/search' );
+ param( $action, 'contains', label => 'Search:' );
+ }
+ };
+};
+
+private template salutation => sub {
+ with (id => 'salutation'),
+ div {
+
+ if ( Jifty->web->current_user->id and Jifty->web->current_user->user_object ) {
+ outs('Hiya, ');
+ with class => 'user',
+ span { Jifty->web->current_user->user_object->name };
+ outs('(' . hyperlink( label => q{Logout}, url => '/logout' ) .')');
+ } else {
+ outs("You're not currently signed in.") . tangent( label => q{Sign in}, url => '/login' ) . "."; }
+ }
+};
+
+
+private template diff => sub {
+ my %args = ( page => undef, from => undef, to => undef, @_);
+
+ my $to = $args{'to'} ||$args{page}->revisions->last;
+ my $from = $args{'from'}|| $to->previous || Wifty::Model::Revision->new;
+
+ my $before = $to->previous;
+ my $after = $to->next;
+
+ use Text::Diff ();
+ my $diff = Text::Diff::diff(
+ \( $from->content ),
+ \( $to->content ),
+ { STYLE => 'Text::Diff::HTML' }
+ );
+
+ with( class => 'revision_nav' ), div {
+ if ($before) {
+ span {
+ with class => "prev";
+ hyperlink(
+ url => "/view/" . $args{page}->name . "/" . $before->id,
+ label => "Previous revision"
+ );
+ };
+ }
+ outs('|') if ( $before and $after );
+
+ if ($after) {
+ with( class => "next" ), span {
+ hyperlink(
+ url => "/view/" . $args{'page'}->name . "/" . $after->id,
+ label => "Next revision"
+ );
+ };
+ }
+ };
+ with class => "diff", pre {
+ $diff;
+ };
+ hr {}
+
+};
+
+template create => sub {
+ my ( $action, $page ) = get(qw(action page));
+ show(
+ 'wrapper',
+ {title => 'New page: ' . $page, id => 'create' },
+ sub {p{
+ form {
+ with( class => 'form_wrapper' ), div {
+ form_next_page( url => '/view/' . $page );
+ param($action => 'name', render_as => 'hidden', default_value => $page);
+ with( class => 'inline' ), div { param ($action => 'content', rows => 30 ); };
+ with( class => 'line' ), div { form_submit( label => 'Create' );
+ };
+ };
+ };
+ show('markup');
+ };
+ }
+ );
+};
+
+template edit => sub {
+ my ( $page, $revision, $viewer ) = get(qw(page revision viewer));
+ my $can_edit = $page->current_user_can('update');
+ show( 'page_nav', page => $page->name, rev => $revision->id );
+ show(
+ 'wrapper',
+ { title => 'Edit: ' . $page->name . ( $revision->id ? " as of " . $revision->created : '' ), id => "update" },
+ sub {
+ form {
+ with( class => 'form_wrapper' ), div {
+ with( class => 'inline' ), div {
+ unless ($can_edit) { with( style => "width: 70%" ), p { q{You don't have permission to edit this page. Perhaps} . tangent( url => '/login', label => 'logging in') . q{would help. In the mean time, though, you're welcome to view and} . q{copy the source of this page.}; } }
+ form_next_page( url => '/view/' . $page->name );
+ param($viewer => 'content');
+ if ($can_edit) { with( class => 'line' ), div { form_submit( label => 'Save' ); } }
+ };
+ };
+ show('markup');
+ };
+
+ }
+
+
+ );
+};
+
+template history => sub {
+ my ( $page, $revisions ) = get(qw(page revisions));
+ # XXX TODO, this isn't right
+ show( 'page_nav', page => $page->name );
+ show(
+ 'wrapper',
+ { title => $revisions->count . " revisions of " . $page->name },
+ sub {
+ with( id => "history" ),
+
+ dl {
+ while ( my $rev = $revisions->next ) {
+ dt {
+ hyperlink(
+ label => $rev->created,
+ url => '/view/' . $page->name . '/' . $rev->id
+ );
+ if ( $rev->created_by->id ) {
+ '(' . $rev->created_by->name . ')';
+ } else {
+ '(Anonymous)';
+ }
+ };
+ dd { length( $rev->content ) . ' bytes' };
+ }
+ };
+ }
+ );
+
+};
+
+template login => sub {
+ my ( $action, $next, ) = get(qw(action next));
+ show(
+ 'wrapper',
+ { title => 'Login' },
+ sub {
+ if ( not current_user->id ) {
+ with( id => 'login-box' ), div {
+ with( call => $next, name => "loginbox" ), form {
+ param($action => 'email');
+ param($action => 'password');
+ param($action => 'remember');
+ form_submit(
+ label => 'Login',
+ submit => $action
+ );
+ };
+ };
+
+ p {
+ tangent(
+ label => q{Don't have an account?},
+ url => '/signup'
+ );
+ };
+
+ } else {
+ p {
+ "You're already logged in as "
+ . current_user->user_object->name . "."
+ . "If this isn't you, "
+ . tangent(
+ url => '/logout',
+ label => 'click here'
+ )
+ . ".";
+ }
+ }
+ }
+ );
+};
+
+template logout => sub {
+ show(
+ 'wrapper',
+ { title => "Logged out" },
+ sub {
+ p { _("Ok, you're now logged out. Have a good day.") };
+ }
+ );
+};
+
+template no_such_page => sub {
+ my ( $page ) = get(qw(page));
+ show(
+ 'wrapper',
+ { title => 'No such page: ' . $page },
+ sub {
+
+ p {
+ q{Unfortunately, you've tried to reach a page that doesn't exist }
+ . q{yet, and you don't have permissions to create pages. If you }
+ . tangent( url => '/login', label => 'login' )
+ . q{, you'll be able to create new pages of your own.}
+
+ }
+
+ }
+ );
+};
+
+template pages => sub {
+ my ($pages ) = get(qw(pages));
+ show(
+ 'wrapper',
+ { title => 'These are the pages on your wiki!' },
+ sub {
+ show( 'page_list', pages => $pages, id => 'allpages' );
+ }
+ );
+
+};
+
+template recent => sub {
+ my ( $pages ) = get(qw(pages));
+ show(
+ 'wrapper',
+ { title => 'Updated this week' },
+ sub {
+ show( 'page_list', pages => $pages, id => 'recentupdates' );
+ }
+ );
+
+};
+
+template recent_atom => sub {
+ my ( $pages) = get(qw(pages));
+ use XML::Atom::SimpleFeed;
+ use Data::UUID;
+ my $feed = XML::Atom::SimpleFeed->new(
+ title => 'Recently changed pages',
+ link => Jifty->web->url,
+ updated => '2009-12-31T00:00:00Z',
+ author => 'John Doe',
+ id => 'urn:uuid:' . Data::UViewD->new->create_str()
+ );
+
+ while ( my $page = $pages->next ) {
+
+ $feed->add_entry(
+ title => $page->name,
+ link => Jifty->web->url . '/view/' . $page->name,
+ id => 'urn:uuid:' . Data::UViewD->new->create_str(),
+ summary => $page->content,
+ updated => $page->updated
+ );
+ }
+ $feed->print;
+};
+
+template search => sub {
+ my ( $pages, $search ) = get(qw(pages search));
+ show( 'wrapper',
+ { title => 'Search' },
+ sub {
+ form {
+ with( id => "searchbox", class => 'inline'), div {
+ param($search => 'contains', label => 'Find pages containing:' );
+ form_submit( label => 'Search', submit => $search);
+ };
+
+ };
+ if ($pages) {
+ show( 'page_list' => pages => $pages, id => 'searchresults' );
+ }
+ }
+ );
+};
+
+template signup => sub {
+ my ( $action, $next ) = get(qw(action next));
+ show(
+ 'wrapper',
+ { title => 'Signup' },
+ sub {
+ p {q{Just a few bits of information are all that's needed.}};
+ with( call => $next, name => "signupbox" ), form {
+ param ($action => 'email');
+ param ($action => 'name');
+ param ($action => 'password');
+ param ($action => 'password_confirm');
+ form_submit( label => 'Signup', submit => $action );
+ };
+ }
+ );
+
+};
+
+template view => sub {
+ my ( $page, $revision, $viewer ) = get(qw(page revision viewer));
+ show( 'page_nav', page => $page->name, rev => $revision->id );
+ show(
+ 'wrapper',
+ { title => $page->name
+ . ( $revision->id ? " as of " . $revision->created : '' )
+ },
+ sub {
+ if ( $revision->id ) {
+ show( 'diff', page => $page, to => $revision );
+ }
+
+ param($viewer => 'content', label => '', render_mode => 'read');
+ #$viewer->form_value( 'content', label => "" );
+
+ }
+ );
+
+};
+
+private template header => sub {
+ my %args = ( title=> undef, wikiname => undef, @_);
+
+ my ( $title, $wikiname ) = ($args{'title'}, $args{'wikiname'});
+ # $HTML::Mason::r->content_type('text/html; charset=utf-8');
+ outs(
+ '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'
+ );
+
+ with(
+ xmlns => "http://www.w3.org/1999/xhtml",
+ 'xml:lang' => "en"
+ ),
+ html {
+ head {
+ with(
+ 'http-equiv' => "content-type",
+ 'content' => "text/html; charset=utf-8"
+ ),
+ meta {};
+ with(
+ name => "robots",
+ content => "all"
+ ),
+ meta {};
+ title { _($title) . ' - ' . _($wikiname) };
+
+ Jifty->web->include_css;
+ Jifty->web->include_javascript;
+
+ }
+ }
+};
+
+template markup => sub {
+ return undef unless ( Jifty->config->app('Formatter') eq 'Markdown' );
+
+ with( id => 'syntax' ), div {
+ div {
+ with(
+ href => "#",
+ onclick => "Element.toggle('syntax_content');return(false);"
+ ),
+ a {
+ b {'Wiki Syntax Help'};
+ }
+ };
+ with( id => 'syntax_content' ), div {
+ h3 {'Phrase Emphasis'};
+ code {
+ b { '**bold**'; };
+ i {'_italic_'};
+ };
+
+ h3 {'Links'};
+
+ code {'Show me a [wiki page](WikiPage)'};
+ code {'An [example](http://url.com/ "Title")'};
+
+ h3 {'Headers'};
+
+ pre {
+ code {
+ join( "\n",
+ '# Header 1',
+ '## Header 2',
+ '###### Header 6' );
+ }
+ };
+
+ h3 {'Lists'};
+
+ p {'Ordered, without paragraphs:'};
+
+ pre {
+ code {
+ join( "\n", '1. Foo', '2. Bar' );
+ }
+ };
+
+ p {' Unordered, with paragraphs:'};
+
+ pre {
+ code {
+ join( "\n",
+ '* A list item.',
+ 'With multiple paragraphs.',
+ '* Bar' );
+ };
+
+ h3 {'Code Spans'};
+
+ p {
+ code {'`<code>`'}
+ . 'spans are delimited by backticks.';
+ };
+
+ h3 {'Preformatted Code Blocks'};
+
+ p {'Indent every line of a code block by at least 4 spaces.'};
+
+ pre {
+ code {
+ 'This is a normal paragraph.' . "\n\n" . "\n"
+ . ' This is a preformatted' . "\n"
+ . ' code block.';
+ };
+ };
+
+ h3 {'Horizontal Rules'};
+
+ p {
+ 'Three or more dashes: ' . code {'---'};
+ };
+
+ address {
+ '(Thanks to <a href="http://daringfireball.net/projects/markdown/dingus">Daring Fireball</a>)';
+ }
+ }
+ };
+ script {
+ qq{
+ // javascript flyout by Eric Wilhelm
+ // TODO use images for minimize/maximize button
+ // Is there a way to add a callback?
+ Element.toggle('syntax_content');
+ };
+ };
+ };
+};
+
+package Wifty::View::let;
+use Template::Declare::Tags;
+
+# /let/confirm_email
+
+template confirm_email => sub {
+ Jifty->api->allow('ConfirmEmail');
+ new_action(
+ moniker => 'confirm_email',
+ class => 'Wifty::Action::ConfirmEmail'
+ )->run;
+ redirect("/");
+};
+
+1;
commit 194cffbf5a778fb41b445d7b44b93702e0ec991c
Author: Audrey Tang <audreyt at audreyt.org>
Date: Sun Dec 3 01:59:09 2006 +0000
Wifty::View - Replace old syntax:
with( x => 1, y => 2 ), tag {
...;
};
with new syntax:
tag {{ x is 1, y is 2 }
...;
};
diff --git a/lib/Wifty/View.pm b/lib/Wifty/View.pm
index 59077b3..bb82830 100644
--- a/lib/Wifty/View.pm
+++ b/lib/Wifty/View.pm
@@ -20,12 +20,10 @@ use Template::Declare::Tags;
use Jifty::View::Declare::Templates;
private template page_list => sub {
-
# actually creates: sub _jifty_ui_template_page_list
#
- my ( $pages, $id ) = get(qw(pages id));
- with( id => $id, class => "pagelist" ), dl {
-
+ my ( $pages, $id ) = get(qw(pages id));
+ dl {{ id is $id, class is "pagelist" }
while ( my $page = $pages->next ) {
dt {
hyperlink(
@@ -110,10 +108,9 @@ private template wrapper => sub {
show('nav');
show( 'header', title => $args->{'title'}, wikiname => $wikiname );
- with( id => $args->{id} ), body {
-
+ body {{ id is $args->{id} }
if ( Jifty->config->framework('AdminMode') ) {
- with( class => 'warning admin_mode' ), div {
+ div {{ class is 'warning admin_mode' }
_('Alert') . ":"
. tangent(
label => _('Administration mode is enabled'),
@@ -122,22 +119,22 @@ private template wrapper => sub {
. ".";
}
}
- with( id => 'logo' ), div {
+ div {{ id is 'logo' }
Jifty->config->app('Logo')
? '<img src="' . Jifty->config->app('Logo') . '" alt="" />'
: '';
};
- with( id => 'header' ), div {
- with( id => 'wikiheader' ), div {
- with( id => "wikiname" ), h1 {
+ div {{ id is 'header' }
+ div {{ id is 'wikiheader' }
+ h1 {{ id is 'wikiname' }
hyperlink( url => "/", label => _($wikiname) );
};
outs(Jifty->web->navigation->render_as_menu);
show('search_box');
};
- with( id => 'pageheader' ), div {
- with( id => "pagename" ), h1 {
+ div {{ id is 'pageheader' }
+ h1 {{ id is 'pagename' }
_( $args->{title} );
};
@@ -147,18 +144,18 @@ private template wrapper => sub {
show('salutation');
- with( class => "clear" ), hr {};
- with( id => 'content' ), div {
+ hr {{ class is 'clear' }};
+ div {{ id is 'content' }
Jifty->web->render_messages;
my $buf = '';
{
local $Template::Declare::Tags::BUFFER ='';
$coderef->();
$buf = $Template::Declare::Tags::BUFFER;
- warn "My buffer is $buf";
+ #warn "My buffer is $buf";
}
outs($buf);
- with( class => "clear" ), hr { };
+ hr {{ class is 'clear' }};
}
}
@@ -171,21 +168,18 @@ private template search_box => sub {
$action->sticky_on_success(1);
span {
form {
-
form_next_page( url => '/search' );
- param( $action, 'contains', label => 'Search:' );
+ render_param( $action, 'contains', label => 'Search:' );
}
};
};
private template salutation => sub {
- with (id => 'salutation'),
- div {
+ div {{ id is 'salutation' }
if ( Jifty->web->current_user->id and Jifty->web->current_user->user_object ) {
outs('Hiya, ');
- with class => 'user',
- span { Jifty->web->current_user->user_object->name };
+ span {{ class is 'user' } Jifty->web->current_user->user_object->name };
outs('(' . hyperlink( label => q{Logout}, url => '/logout' ) .')');
} else {
outs("You're not currently signed in.") . tangent( label => q{Sign in}, url => '/login' ) . "."; }
@@ -209,10 +203,9 @@ private template diff => sub {
{ STYLE => 'Text::Diff::HTML' }
);
- with( class => 'revision_nav' ), div {
+ div {{ class is 'revision_nav' }
if ($before) {
- span {
- with class => "prev";
+ span {{ class is "prev" }
hyperlink(
url => "/view/" . $args{page}->name . "/" . $before->id,
label => "Previous revision"
@@ -222,7 +215,7 @@ private template diff => sub {
outs('|') if ( $before and $after );
if ($after) {
- with( class => "next" ), span {
+ span {{ class is "next" }
hyperlink(
url => "/view/" . $args{'page'}->name . "/" . $after->id,
label => "Next revision"
@@ -230,9 +223,7 @@ private template diff => sub {
};
}
};
- with class => "diff", pre {
- $diff;
- };
+ pre {{ class is 'diff' } $diff };
hr {}
};
@@ -244,11 +235,14 @@ template create => sub {
{title => 'New page: ' . $page, id => 'create' },
sub {p{
form {
- with( class => 'form_wrapper' ), div {
+ div {{ class is 'form_wrapper' }
form_next_page( url => '/view/' . $page );
- param($action => 'name', render_as => 'hidden', default_value => $page);
- with( class => 'inline' ), div { param ($action => 'content', rows => 30 ); };
- with( class => 'line' ), div { form_submit( label => 'Create' );
+ render_param($action => 'name', render_as => 'hidden', default_value => $page);
+ div {{ class is 'inline' }
+ render_param($action => 'content', rows => 30 );
+ };
+ div {{ class is 'inline' }
+ form_submit( label => 'Create' );
};
};
};
@@ -267,12 +261,12 @@ template edit => sub {
{ title => 'Edit: ' . $page->name . ( $revision->id ? " as of " . $revision->created : '' ), id => "update" },
sub {
form {
- with( class => 'form_wrapper' ), div {
- with( class => 'inline' ), div {
- unless ($can_edit) { with( style => "width: 70%" ), p { q{You don't have permission to edit this page. Perhaps} . tangent( url => '/login', label => 'logging in') . q{would help. In the mean time, though, you're welcome to view and} . q{copy the source of this page.}; } }
+ div {{ class is 'form_wrapper' }
+ div {{ class is 'inline' }
+ unless ($can_edit) { p {{ style is "width: 70%" } q{You don't have permission to edit this page. Perhaps} . tangent( url => '/login', label => 'logging in') . q{would help. In the mean time, though, you're welcome to view and} . q{copy the source of this page.}; } }
form_next_page( url => '/view/' . $page->name );
- param($viewer => 'content');
- if ($can_edit) { with( class => 'line' ), div { form_submit( label => 'Save' ); } }
+ render_param($viewer => 'content');
+ if ($can_edit) { div {{ class is 'line' } form_submit( label => 'Save' ); } }
};
};
show('markup');
@@ -292,9 +286,7 @@ template history => sub {
'wrapper',
{ title => $revisions->count . " revisions of " . $page->name },
sub {
- with( id => "history" ),
-
- dl {
+ dl {{ id is 'history' }
while ( my $rev = $revisions->next ) {
dt {
hyperlink(
@@ -322,11 +314,11 @@ template login => sub {
{ title => 'Login' },
sub {
if ( not current_user->id ) {
- with( id => 'login-box' ), div {
- with( call => $next, name => "loginbox" ), form {
- param($action => 'email');
- param($action => 'password');
- param($action => 'remember');
+ div {{ id is 'login-box' }
+ form {{ call is $next, name is "loginbox" }
+ render_param($action => 'email');
+ render_param($action => 'password');
+ render_param($action => 'remember');
form_submit(
label => 'Login',
submit => $action
@@ -441,8 +433,8 @@ template search => sub {
{ title => 'Search' },
sub {
form {
- with( id => "searchbox", class => 'inline'), div {
- param($search => 'contains', label => 'Find pages containing:' );
+ div {{ id is "searchbox", class is 'inline' }
+ render_param($search => 'contains', label => 'Find pages containing:' );
form_submit( label => 'Search', submit => $search);
};
@@ -455,17 +447,17 @@ template search => sub {
};
template signup => sub {
- my ( $action, $next ) = get(qw(action next));
+ my ( $action, $next ) = get(qw(action next));
show(
'wrapper',
{ title => 'Signup' },
sub {
p {q{Just a few bits of information are all that's needed.}};
- with( call => $next, name => "signupbox" ), form {
- param ($action => 'email');
- param ($action => 'name');
- param ($action => 'password');
- param ($action => 'password_confirm');
+ form {{ call is $next, name is "signupbox" }
+ render_param($action => 'email');
+ render_param($action => 'name');
+ render_param($action => 'password');
+ render_param($action => 'password_confirm');
form_submit( label => 'Signup', submit => $action );
};
}
@@ -486,7 +478,7 @@ template view => sub {
show( 'diff', page => $page, to => $revision );
}
- param($viewer => 'content', label => '', render_mode => 'read');
+ render_param($viewer => 'content', label => '', render_mode => 'read');
#$viewer->form_value( 'content', label => "" );
}
@@ -514,11 +506,7 @@ private template header => sub {
'content' => "text/html; charset=utf-8"
),
meta {};
- with(
- name => "robots",
- content => "all"
- ),
- meta {};
+ meta {{ name is 'robots', content is 'all' }};
title { _($title) . ' - ' . _($wikiname) };
Jifty->web->include_css;
@@ -531,17 +519,14 @@ private template header => sub {
template markup => sub {
return undef unless ( Jifty->config->app('Formatter') eq 'Markdown' );
- with( id => 'syntax' ), div {
+ div {{ id is 'syntax' }
div {
- with(
- href => "#",
- onclick => "Element.toggle('syntax_content');return(false);"
- ),
- a {
- b {'Wiki Syntax Help'};
- }
+ a {{
+ href is "#",
+ onclick is "Element.toggle('syntax_content');return(false);"
+ } b {'Wiki Syntax Help'}; }
};
- with( id => 'syntax_content' ), div {
+ div {{ id is 'syntax_content' }
h3 {'Phrase Emphasis'};
code {
b { '**bold**'; };
commit 9186ba91f8a80b1075104db2d398b1da4f8bcf11
Author: Audrey Tang <audreyt at audreyt.org>
Date: Sun Dec 3 15:59:00 2006 +0000
Wifty::View - Squash all with() now that we can represent
http-equiv and xml:lang with "is" syntax.
diff --git a/lib/Wifty/View.pm b/lib/Wifty/View.pm
index bb82830..cceb15f 100644
--- a/lib/Wifty/View.pm
+++ b/lib/Wifty/View.pm
@@ -495,17 +495,9 @@ private template header => sub {
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'
);
- with(
- xmlns => "http://www.w3.org/1999/xhtml",
- 'xml:lang' => "en"
- ),
- html {
+ html {{ xmlns is "http://www.w3.org/1999/xhtml", xml__lang is "en" }
head {
- with(
- 'http-equiv' => "content-type",
- 'content' => "text/html; charset=utf-8"
- ),
- meta {};
+ meta {{ http_equiv is "content-type", content is "text/html; charset=utf-8" }};
meta {{ name is 'robots', content is 'all' }};
title { _($title) . ' - ' . _($wikiname) };
commit 2c5c197a507456d7cc6e5fbd505ddb6bf836c08e
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Fri Jan 26 15:17:00 2007 +0000
modernizing model column declarations
diff --git a/lib/Wifty/Model/Page.pm b/lib/Wifty/Model/Page.pm
index 96a31df..e46b053 100644
--- a/lib/Wifty/Model/Page.pm
+++ b/lib/Wifty/Model/Page.pm
@@ -1,7 +1,14 @@
-package Wifty::Model::Page::Schema;
+
+package Wifty::Model::Page;
+use warnings;
+use strict;
+
+use base qw/Wifty::Record/;
use Jifty::DBI::Schema;
use Wifty::Model::User;
+use Jifty::Record schema {
+
column name =>
type is 'text',
label is 'Page name',
@@ -24,9 +31,9 @@ column updated_by =>
column revisions =>
refers_to Wifty::Model::RevisionCollection by 'page';
+};
+
-package Wifty::Model::Page;
-use base qw/Wifty::Record/;
use Wifty::Model::RevisionCollection;
diff --git a/lib/Wifty/Model/Revision.pm b/lib/Wifty/Model/Revision.pm
index f75545e..0a89354 100644
--- a/lib/Wifty/Model/Revision.pm
+++ b/lib/Wifty/Model/Revision.pm
@@ -1,17 +1,23 @@
-package Wifty::Model::Revision::Schema;
+
+package Wifty::Model::Revision;
+use warnings;
+use strict;
+
+
+use base qw/Wifty::Record/;
+
use Jifty::DBI::Schema;
+use Jifty::Record schema {
column page => refers_to Wifty::Model::Page;
column content => type is 'text', render_as 'Wifty::Form::Field::WikiPage';
column created => type is 'timestamp';
-
column created_by => refers_to Wifty::Model::User, since '0.0.20';
+};
-package Wifty::Model::Revision;
-use base qw/Wifty::Record/;
use Jifty::RightsFrom column => 'page';
use DateTime;
use Wifty::Model::User;
commit b4a9f1ebac8484e865434585de70634897691ba8
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Sat Jan 27 03:30:51 2007 +0000
cleaned up a warning
diff --git a/lib/Wifty/Model/Page.pm b/lib/Wifty/Model/Page.pm
index e46b053..28c255c 100644
--- a/lib/Wifty/Model/Page.pm
+++ b/lib/Wifty/Model/Page.pm
@@ -75,6 +75,8 @@ sub _add_revision {
}
+{
+no warnings qw'redefine';
sub set_content {
my $self = shift;
my $content = shift;
@@ -84,6 +86,7 @@ sub set_content {
);
return ( $val, $msg );
}
+};
sub _set {
my $self = shift;
commit f4e2f9283f9b50d6b7a7912fc6e3806f3160da08
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Sat Jan 27 03:31:20 2007 +0000
unfixing the warning avoidance pending a better core test
diff --git a/lib/Wifty/Model/Page.pm b/lib/Wifty/Model/Page.pm
index 28c255c..e46b053 100644
--- a/lib/Wifty/Model/Page.pm
+++ b/lib/Wifty/Model/Page.pm
@@ -75,8 +75,6 @@ sub _add_revision {
}
-{
-no warnings qw'redefine';
sub set_content {
my $self = shift;
my $content = shift;
@@ -86,7 +84,6 @@ sub set_content {
);
return ( $val, $msg );
}
-};
sub _set {
my $self = shift;
commit 3e786d57668e8d9532d4586ade22b8f5d94924c3
Author: Kevin Falcone <falcone at bestpractical.com>
Date: Wed Feb 21 22:31:41 2007 +0000
move use of the RevisionCollection object before the schema
declaration so we can refers_to it
diff --git a/lib/Wifty/Model/Page.pm b/lib/Wifty/Model/Page.pm
index e46b053..c6cf3b4 100644
--- a/lib/Wifty/Model/Page.pm
+++ b/lib/Wifty/Model/Page.pm
@@ -6,6 +6,7 @@ use strict;
use base qw/Wifty::Record/;
use Jifty::DBI::Schema;
use Wifty::Model::User;
+use Wifty::Model::RevisionCollection;
use Jifty::Record schema {
@@ -33,10 +34,6 @@ column revisions =>
refers_to Wifty::Model::RevisionCollection by 'page';
};
-
-use Wifty::Model::RevisionCollection;
-
-
sub create {
my $self = shift;
my %args = (@_);
commit 65a99da68ff8381f6d6ccc70e25f621dc97b0abd
Author: Kevin Falcone <falcone at bestpractical.com>
Date: Wed Feb 21 22:32:18 2007 +0000
if the set_content fails, don't create a transaction
diff --git a/lib/Wifty/Model/Page.pm b/lib/Wifty/Model/Page.pm
index c6cf3b4..5064538 100644
--- a/lib/Wifty/Model/Page.pm
+++ b/lib/Wifty/Model/Page.pm
@@ -76,9 +76,12 @@ sub set_content {
my $self = shift;
my $content = shift;
my ( $val, $msg ) = $self->_set(column => 'content', value => $content);
- $self->_add_revision( content => $content,
- updated_by =>( $self->current_user? $self->current_user->user_object : undef )
- );
+
+ if ($val) {
+ $self->_add_revision( content => $content,
+ updated_by =>( $self->current_user? $self->current_user->user_object : undef )
+ );
+ }
return ( $val, $msg );
}
commit 084d2d011ecf98456fed50e09263894b0a0e5708
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Wed Feb 21 22:36:52 2007 +0000
numeric pagenames lost badly
diff --git a/lib/Wifty/Dispatcher.pm b/lib/Wifty/Dispatcher.pm
index ac6563d..9de8e77 100644
--- a/lib/Wifty/Dispatcher.pm
+++ b/lib/Wifty/Dispatcher.pm
@@ -28,7 +28,7 @@ on '/create/*', run {
on qr{^/(view|edit)/(.*)}, run {
my ( $name, $rev );
my $page_name = $1;
- if ( $2 =~ qr{^(.*?)(?:/(\d*))?$} ) {
+ if ( $2 =~ qr{^(.*?)(?:/(\d+))?$} ) {
$name = $1;
$rev = $2;
}
commit 509ca19ef364b98cae49e48b8e4f707fe2c1b46f
Author: Chia-liang Kao <clkao at bestpractical.com>
Date: Tue Apr 3 06:50:59 2007 +0000
merge from trs' local
Move Wifty::View so that it isn't automatically loaded. It's not ready for use yet (still contains much early TD syntax).
diff --git a/lib/Wifty/View.pm b/lib/Wifty/View-not-ready-yet.pm
similarity index 100%
rename from lib/Wifty/View.pm
rename to lib/Wifty/View-not-ready-yet.pm
commit a62ee0f7450a6f3a95ab75b80b7fb6749530e2a5
Author: Kevin Riggle <kevinr at bestpractical.com>
Date: Fri Apr 27 03:38:35 2007 +0000
bin/jifty was out of date
diff --git a/bin/jifty b/bin/jifty
index 1e40ae7..8f0e01c 100755
--- a/bin/jifty
+++ b/bin/jifty
@@ -1,14 +1,15 @@
-#!/usr/bin/perl
+#!/usr/bin/env perl
use warnings;
use strict;
use File::Basename qw(dirname);
+use UNIVERSAL::require;
BEGIN {
- my $dir = dirname(__FILE__);
- unshift @INC, "$dir/../../Jifty/lib";
- unshift @INC, "$dir/../lib";
- push @INC, "$dir/../../Jifty/deps";
+ Jifty::Util->require or die $UNIVERSAL::require::ERROR;
+ my $root = Jifty::Util->app_root;
+ unshift @INC, "$root/lib" if ($root);
}
use Jifty::Script;
+local $SIG{INT} = sub { warn "Stopped\n"; exit; };
Jifty::Script->dispatch();
commit d1428d7c52a880f83af58075a568fb2b3896bace
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Fri Oct 26 20:42:12 2007 +0000
fixing for text wikiformat bogosity with newlines
diff --git a/lib/Wifty/Form/Field/WikiPage.pm b/lib/Wifty/Form/Field/WikiPage.pm
index cee01e1..fcc1760 100644
--- a/lib/Wifty/Form/Field/WikiPage.pm
+++ b/lib/Wifty/Form/Field/WikiPage.pm
@@ -63,6 +63,8 @@ sub wiki_content {
}
);
+ $content =~ s/(?:\n\r|\r\n|\r)/\n/g;
+
$scrubber->deny(qw[*]);
$scrubber->allow(
qw[H1 H2 H3 H4 H5 A STRONG EM CODE PRE B U P BR I HR BR SPAN DIV UL OL LI DL DT DD]);
commit dcf36825f275bfd641891ba964fab7e46ccaaf62
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Sun Oct 28 03:36:27 2007 +0000
fetch only one record from the DB
diff --git a/lib/Wifty/Model/Revision.pm b/lib/Wifty/Model/Revision.pm
index 0a89354..88fd8eb 100644
--- a/lib/Wifty/Model/Revision.pm
+++ b/lib/Wifty/Model/Revision.pm
@@ -54,8 +54,9 @@ sub previous {
quote_value => 0,
case_sensitive => 1
);
- $revisions->order_by( { column => 'id' } );
- return $revisions->last;
+ $revisions->order_by( { column => 'id', order => 'desc' } );
+ $revisions->rows_per_page(1);
+ return $revisions->first;
}
sub next {
@@ -77,6 +78,7 @@ sub next {
case_sensitive => 1
);
$revisions->order_by( { column => 'id' } );
+ $revisions->rows_per_page(1);
return $revisions->first;
}
commit 2fe256e2665e2a7db15e6937cb84e4440898ba5d
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Sun Nov 30 00:51:15 2008 +0000
switch from deprecated Login plugin to User + Auth::Password plugins
diff --git a/etc/config.yml b/etc/config.yml
index b84a8a3..470e5a9 100644
--- a/etc/config.yml
+++ b/etc/config.yml
@@ -1,4 +1,7 @@
+---
framework:
+ ConfigFileVersion: 4
+
AdminMode: 0
ApplicationName: Wifty
AdminEmail: 'wifty at example.com'
@@ -11,7 +14,12 @@ framework:
Password: ''
RequireSSL: 0
Plugins:
- - Login: {}
+ - SkeletonApp: {}
+ - CompressedCSSandJS: {}
+ - User: {}
+ - Authentication::Password:
+ login_by: email
+
Mailer: IO
MailerArgs:
- %log/mail.log%
diff --git a/lib/Wifty/Model/User.pm b/lib/Wifty/Model/User.pm
index a97d6dd..bc483eb 100644
--- a/lib/Wifty/Model/User.pm
+++ b/lib/Wifty/Model/User.pm
@@ -1,4 +1,13 @@
package Wifty::Model::User;
-use base qw/Jifty::Plugin::Login::Model::User/;
+
+use Jifty::DBI::Schema;
+use Wifty::Record schema {
+ # column definitions
+};
+
+# import columns: name, email and email_confirmed
+use Jifty::Plugin::User::Mixin::Model::User;
+# import columns: password, auth_token
+use Jifty::Plugin::Authentication::Password::Mixin::Model::User;
1;
commit c77871b3cc2654d30071b24dc3d997780cfb8c11
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Sun Nov 30 00:53:17 2008 +0000
remove some templates which are part of old Login plugin
diff --git a/share/web/templates/let/confirm_email b/share/web/templates/let/confirm_email
deleted file mode 100644
index 86a69a1..0000000
--- a/share/web/templates/let/confirm_email
+++ /dev/null
@@ -1,8 +0,0 @@
-<%init>
-Jifty->api->allow( 'ConfirmEmail');
-Jifty->web->new_action(
- moniker => 'confirm_email',
- class => 'Wifty::Action::ConfirmEmail',
-)->run;
-Jifty->web->redirect("/");
-</%init>
diff --git a/share/web/templates/login b/share/web/templates/login
deleted file mode 100644
index 5884b65..0000000
--- a/share/web/templates/login
+++ /dev/null
@@ -1,26 +0,0 @@
-<%args>
-$action => undef
-$next => undef
-</%args>
-<&|/_elements/wrapper, title => 'Login' &>
-
-% if ( not Jifty->web->current_user->id ) {
-<div id="login-box">
-<% Jifty->web->form->start(call => $next, name => "loginbox") %>
-<% $action->form_field('email') %>
-<% $action->form_field('password') %>
-<% $action->form_field('remember') %>
-<% Jifty->web->form->submit(label => 'Login', submit => $action) %>
-<% Jifty->web->form->end %>
-</div>
-
-<p><% Jifty->web->tangent( label => q{Don't have an account?}, url => '/signup' )%></p>
-
-% }
-% else {
-<p>
-You're already logged in as <% Jifty->web->current_user->user_object->name %>.
-If this isn't you, <% Jifty->web->tangent( url => '/logout', label => 'click here') %>.
-</p>
-% }
-</&>
diff --git a/share/web/templates/logout b/share/web/templates/logout
deleted file mode 100644
index f6eee9d..0000000
--- a/share/web/templates/logout
+++ /dev/null
@@ -1,3 +0,0 @@
-<&| /_elements/wrapper, title => "Logged out" &>
-<p>Ok, you're now logged out. Have a good day.</p>
-</&>
diff --git a/share/web/templates/signup b/share/web/templates/signup
deleted file mode 100644
index 70103a1..0000000
--- a/share/web/templates/signup
+++ /dev/null
@@ -1,14 +0,0 @@
-<%args>
-$action
-$next
-</%args>
-<&|/_elements/wrapper, title => 'Signup' &>
-<p>Just a few bits of information are all that's needed.</p>
-<% Jifty->web->form->start(call => $next, name => "signupbox") %>
-<% $action->form_field('email') %>
-<% $action->form_field('name') %>
-<% $action->form_field('password') %>
-<% $action->form_field('password_confirm') %>
-<% Jifty->web->form->submit(label => 'Signup', submit => $action) %>
-<% Jifty->web->form->end %>
-</&>
commit f982223dff6fe6a4d49ca41529cf77533d356037
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Sun Nov 30 00:53:41 2008 +0000
fix test
diff --git a/t/02-login.t b/t/02-login.t
index 74c5d94..1136987 100644
--- a/t/02-login.t
+++ b/t/02-login.t
@@ -33,18 +33,21 @@ sub try_login {
{
local $Test::Builder::Level = $Test::Builder::Level;
$Test::Builder::Level++;
- $mech->fill_in_action_ok('loginbox', email => $user, password => $pass);
+ $mech->fill_in_action_ok(
+ $mech->moniker_for("Wifty::Action::Login"),
+ email => $user, password => $pass
+ );
$mech->submit_html_ok();
}
}
# Try logging in with a bad user
try_login($mech, 'baduser at localhost', 'notmypassword');
-$mech->content_contains('No account has that email address', "Login failed with bad username");
+$mech->content_contains("It doesn't look like there's an account by that name", "Login failed with bad username");
# With a blank password
try_login($mech, 'someuser at localost', '');
-$mech->content_contains('need to fill in this field','Login fails with no password');
+$mech->content_contains('Please fill in this field','Login fails with no password');
# With the wrong password
try_login($mech, 'someuser at localhost', 'badmemory');
commit 97d54ccc03432784677a9cdd69723ee9bcb9119d
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Sun Nov 30 03:12:32 2008 +0000
we will not need these templates
diff --git a/lib/Wifty/View-not-ready-yet.pm b/lib/Wifty/View-not-ready-yet.pm
index cceb15f..58fc261 100644
--- a/lib/Wifty/View-not-ready-yet.pm
+++ b/lib/Wifty/View-not-ready-yet.pm
@@ -307,58 +307,6 @@ template history => sub {
};
-template login => sub {
- my ( $action, $next, ) = get(qw(action next));
- show(
- 'wrapper',
- { title => 'Login' },
- sub {
- if ( not current_user->id ) {
- div {{ id is 'login-box' }
- form {{ call is $next, name is "loginbox" }
- render_param($action => 'email');
- render_param($action => 'password');
- render_param($action => 'remember');
- form_submit(
- label => 'Login',
- submit => $action
- );
- };
- };
-
- p {
- tangent(
- label => q{Don't have an account?},
- url => '/signup'
- );
- };
-
- } else {
- p {
- "You're already logged in as "
- . current_user->user_object->name . "."
- . "If this isn't you, "
- . tangent(
- url => '/logout',
- label => 'click here'
- )
- . ".";
- }
- }
- }
- );
-};
-
-template logout => sub {
- show(
- 'wrapper',
- { title => "Logged out" },
- sub {
- p { _("Ok, you're now logged out. Have a good day.") };
- }
- );
-};
-
template no_such_page => sub {
my ( $page ) = get(qw(page));
show(
@@ -446,25 +394,6 @@ template search => sub {
);
};
-template signup => sub {
- my ( $action, $next ) = get(qw(action next));
- show(
- 'wrapper',
- { title => 'Signup' },
- sub {
- p {q{Just a few bits of information are all that's needed.}};
- form {{ call is $next, name is "signupbox" }
- render_param($action => 'email');
- render_param($action => 'name');
- render_param($action => 'password');
- render_param($action => 'password_confirm');
- form_submit( label => 'Signup', submit => $action );
- };
- }
- );
-
-};
-
template view => sub {
my ( $page, $revision, $viewer ) = get(qw(page revision viewer));
show( 'page_nav', page => $page->name, rev => $revision->id );
@@ -602,18 +531,4 @@ template markup => sub {
};
};
-package Wifty::View::let;
-use Template::Declare::Tags;
-
-# /let/confirm_email
-
-template confirm_email => sub {
- Jifty->api->allow('ConfirmEmail');
- new_action(
- moniker => 'confirm_email',
- class => 'Wifty::Action::ConfirmEmail'
- )->run;
- redirect("/");
-};
-
1;
commit ebd40518d454a1e38f97c074cb422bdbfc1aa703
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Sun Nov 30 03:15:24 2008 +0000
replace _elements/page_nav with a function in dispatcher
diff --git a/lib/Wifty/Dispatcher.pm b/lib/Wifty/Dispatcher.pm
index 9de8e77..690fee0 100644
--- a/lib/Wifty/Dispatcher.pm
+++ b/lib/Wifty/Dispatcher.pm
@@ -35,6 +35,9 @@ on qr{^/(view|edit)/(.*)}, run {
my $page = Wifty::Model::Page->new();
$page->load_by_cols( name => $name );
Jifty->web->redirect( '/create/' . $name ) unless ( $page->id );
+
+ setup_page_nav($name, $rev);
+
my $revision = Wifty::Model::Revision->new();
$revision->load_by_cols( page => $page->id, id => $rev ) if ($rev);
set page => $page;
@@ -54,6 +57,8 @@ on 'history/*', run {
$page->load_by_cols( name => $name );
redirect( '/create/' . $name ) unless ( $page->id );
+ setup_page_nav($name);
+
my $revisions = $page->revisions;
$revisions->order_by( column => 'id', order => 'desc');
@@ -95,5 +100,15 @@ on 'recent*', run {
set pages => $pages;
};
+sub setup_page_nav {
+ my ($page, $rev) = @_;
+
+ my $subpath = $page . ($rev ? "/$rev" : '');
+ my $top = Jifty->web->page_navigation;
+ $top->child( View => url => '/view/'.$subpath);
+ $top->child( Edit => url => '/edit/'.$subpath);
+ $top->child( History => url => '/history/'.$page);
+ $top->child( Latest => url => '/view/'.$page) if $rev;
+}
1;
diff --git a/share/web/templates/_elements/page_nav b/share/web/templates/_elements/page_nav
deleted file mode 100644
index a6dd135..0000000
--- a/share/web/templates/_elements/page_nav
+++ /dev/null
@@ -1,17 +0,0 @@
-<%init>
-my $subpath = $page . ($rev ? "/$rev" : '');
-my $top = Jifty->web->page_navigation;
-
-my $page_obj = Wifty::Model::Page->new();
-$page_obj->load_by_cols(name => $page);
-
-$top->child( View => url => '/view/'.$subpath);
-$top->child( Edit => url => '/edit/'.$subpath);
-$top->child( History => url => '/history/'.$page);
-$top->child( Latest => url => '/view/'.$page) if ($rev);
-
-</%init>
-<%args>
-$page => 'HomePage'
-$rev => undef
-</%args>
diff --git a/share/web/templates/edit b/share/web/templates/edit
index c7b0ab3..37d4512 100644
--- a/share/web/templates/edit
+++ b/share/web/templates/edit
@@ -6,7 +6,6 @@ $viewer
<%init>
my $can_edit = $page->current_user_can('update');
</%init>
-<&/_elements/page_nav, page => $page->name, rev => $revision->id &>
<&|/_elements/wrapper, title => 'Edit: '.$page->name . ($revision->id ? " as of ".$revision->created : ''), id => "update" &>
<% Jifty->web->form->start %>
<div class="form_wrapper">
diff --git a/share/web/templates/history b/share/web/templates/history
index d5741c1..5fa39a8 100644
--- a/share/web/templates/history
+++ b/share/web/templates/history
@@ -2,7 +2,6 @@
$page
$revisions
</%args>
-<& /_elements/page_nav, page => $page->name &>
<&|/_elements/wrapper, title => $revisions->count ." revisions of " .$page->name &>
<dl id="history">
% while (my $rev = $revisions->next) {
diff --git a/share/web/templates/view b/share/web/templates/view
index 75133b9..cf08c0f 100644
--- a/share/web/templates/view
+++ b/share/web/templates/view
@@ -3,7 +3,6 @@ $page
$revision
$viewer
</%args>
-<& /_elements/page_nav, page => $page->name, rev => $revision->id &>
<&|/_elements/wrapper, title => $page->name . ($revision->id ? " as of ".$revision->created : '') &>
% if ($revision->id) {
commit 02921c220d34e95871dd79ca1e8c98bbe24642da
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Sun Nov 30 03:19:18 2008 +0000
replace _elements/nav with a dispatcher rule
diff --git a/lib/Wifty/Dispatcher.pm b/lib/Wifty/Dispatcher.pm
index 690fee0..fa9e563 100644
--- a/lib/Wifty/Dispatcher.pm
+++ b/lib/Wifty/Dispatcher.pm
@@ -6,6 +6,13 @@ under '/', run {
Jifty->api->deny('ConfirmEmail');
};
+before '*', run {
+ my $top = Jifty->web->navigation;
+ $top->child( Home => url => "/", sort_order => 1 );
+ $top->child( Recent => url => "/recent", label => "Recent Changes", sort_order => 2 );
+ $top->child( Search => url => "/search", label => "Search", sort_order => 3 );
+};
+
# Default page
on '/', run {
redirect( '/view/HomePage');
diff --git a/share/web/templates/_elements/nav b/share/web/templates/_elements/nav
deleted file mode 100644
index 808bd8c..0000000
--- a/share/web/templates/_elements/nav
+++ /dev/null
@@ -1,13 +0,0 @@
-<%init>
-my $top = Jifty->web->navigation;
-$top->child( Home => url => "/", sort_order => 1 );
-$top->child( Recent => url => "/recent", label => "Recent Changes", sort_order => 2 );
-$top->child( Search => url => "/search", label => "Search", sort_order => 3 );
-
-if ( Jifty->config->framework('AdminMode') ) {
- $top->child( Administration => url => "/__jifty/admin/", sort_order => 998);
- $top->child( OnlineDocs => url => "/__jifty/online_docs/", label => 'Online docs', sort_order => 999);
-}
-
-return;
-</%init>
commit 76aa1d95c0e91066f7e7dfb2dec3b66d79917ed3
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Mon Dec 1 05:05:00 2008 +0000
remove empty dir
commit 4989ab7aa115ae9731cf5425f6a4ca9359a75e0c
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Mon Dec 1 05:09:49 2008 +0000
replace all mason templates with TD, a little changes in page layout
diff --git a/lib/Wifty/View.pm b/lib/Wifty/View.pm
new file mode 100644
index 0000000..e0beb53
--- /dev/null
+++ b/lib/Wifty/View.pm
@@ -0,0 +1,291 @@
+use warnings;
+use strict;
+
+package Wifty::View;
+use Jifty::View::Declare -base;
+
+template 'view' => page {
+ my ( $page, $revision, $viewer ) = get(qw(page revision viewer));
+ my $rev = $revision->id;
+ my $title = $rev
+ ? _('%1 as of %2', $page->name, $revision->created)
+ : $page->name;
+ { title is $title }
+ show( 'diff', page => $page, to => $revision ) if $rev;
+ render_param($viewer => 'content', label => '', render_mode => 'read');
+};
+
+template 'edit' => page {
+ my ( $page, $revision, $viewer ) = get(qw(page revision viewer));
+
+ my $title = $revision->id
+ ? _('Edit page %1 as of %2', $page->name, $revision->created)
+ : _('Edit page %1');
+ { title is $title }
+
+ my $can_edit = $page->current_user_can('update');
+
+ show('markup');
+
+ form { div { attr { class is 'form_wrapper' };
+ div { attr { class is 'inline' };
+ unless ( $can_edit ) {
+ }
+ form_next_page url => '/view/'.$page->name;
+ render_action $viewer, ['content'];
+ };
+ if ( $can_edit ) {
+ div { attr { class is 'line' };
+ form_submit label => _('Save')
+ }
+ }
+ } };
+};
+
+template create => page {
+ my ($action, $page) = get(qw(action page));
+
+ { title is _("New page '%1'", $page), id is 'create' };
+
+ div {
+ show('markup');
+
+ form { div { attr { class is 'form_wrapper' };
+ form_next_page url => '/view/' . $page;
+ render_param $action, 'name',
+ render_as => 'hidden',
+ default_value => $page;
+
+ render_param $action, 'content', rows => 30;
+ form_submit( label => _('Create') );
+ } }
+ };
+};
+
+template no_such_page => page {
+ my ($page) = get(qw(page));
+
+ { title is _("No '%1' page", $page) }
+
+ p {
+ q{Unfortunately, you've tried to reach a page that doesn't exist }
+ . q{yet, and you don't have permissions to create pages. If you }
+ . tangent( url => '/login', label => 'login' )
+ . q{, you'll be able to create new pages of your own.}
+ }
+};
+
+template history => page {
+ my ( $page, $revisions ) = get(qw(page revisions));
+ { title is $revisions->count . " revisions of " . $page->name }
+
+ dl { { id is 'history' }
+ while ( my $rev = $revisions->next ) {
+ dt {
+ hyperlink(
+ label => $rev->created,
+ url => '/view/' . $page->name . '/' . $rev->id
+ );
+ if ( $rev->created_by->id ) {
+ '(' . $rev->created_by->name . ')';
+ } else {
+ '(Anonymous)';
+ }
+ };
+ dd { length( $rev->content ) . ' bytes' };
+ }
+ };
+};
+
+template recent => page {
+ my ($pages) = get(qw(pages));
+ { title is _('Updated this week') }
+
+ show( 'page_list', pages => $pages, id => 'recentupdates' );
+};
+
+template pages => page {
+ my ($pages ) = get(qw(pages));
+ { title is _('These are the pages on your wiki!') }
+
+ show( 'page_list', pages => $pages, id => 'allpages' );
+};
+
+template search => page {
+ my ( $pages, $search ) = get(qw(pages search));
+
+ form { div { { id is "searchbox", class is 'inline' }
+ render_param $search => 'contains', label => _('Find pages containing:');
+ form_submit label => 'Search', submit => $search;
+ }; };
+ if ( $pages ) {
+ show( 'page_list' => pages => $pages, id => 'searchresults' );
+ }
+};
+
+private template 'search_box' => sub {
+ my $action = new_action(class => 'SearchPage', moniker => 'search');
+ $action->sticky_on_success(1);
+ span { form {
+ form_next_page url => '/search';
+ render_param $action, 'contains', label => 'Search:';
+ } };
+};
+
+private template 'menu' => sub {
+ my $wikiname = Jifty->config->app('WikiName') || "Wifty";
+ h1 { attr { id is 'wikiname' }
+ Jifty->web->link( url => "/", label => _($wikiname) )
+ }
+ div { attr { id => "navigation" };
+ Jifty->web->navigation->render_as_menu;
+ };
+};
+
+private template 'heading_in_wrapper' => sub {
+ h1 { attr { class => 'title' }; outs_raw(get('title')) };
+ Jifty->web->page_navigation->render_as_menu;
+# show('/search_box');
+ hr { {class is 'clear'} }
+};
+
+
+private template markup => sub {
+ return undef unless Jifty->config->app('Formatter') eq 'Markdown';
+
+ div {{ id is 'syntax' }
+ div {
+ a {{
+ href is "#",
+ onclick is 'jQuery("syntax_content").toggle();return(false);'
+ } b {_('Wiki Syntax Help')} }
+ };
+ div {{ id is 'syntax_content' }
+ h3 {'Phrase Emphasis'};
+ code {
+ b {'**bold**'; };
+ i {'_italic_'};
+ };
+
+ h3 {'Links'};
+
+ code {'Show me a [wiki page](WikiPage)'};
+ code {'An [example](http://url.com/ "Title")'};
+
+ h3 {'Headers'};
+
+ code { pre { join "\n",
+ '# Header 1', '## Header 1', '###### Header 6'
+ } };
+
+ h3 {'Lists'};
+
+ p {'Ordered, without paragraphs:'};
+
+ code { pre { join "\n", '1. Foo', '2. Bar' } };
+
+ p {'Unordered, with paragraphs:'};
+
+ code { pre { join "\n",
+ '* A list item.',
+ 'With multiple paragraphs.',
+ '* Bar',
+ } };
+
+ h3 {'Code Spans'};
+
+ p { code {'`<code>`'}; outs(' - spans are delimited by backticks') };
+
+ h3 {'Preformatted Code Blocks'};
+
+ p {'Indent every line of a code block by at least 4 spaces.'};
+
+ code {
+ pre {
+ 'This is a normal paragraph.' . "\n\n" . "\n"
+ . ' This is a preformatted' . "\n"
+ . ' code block.';
+ };
+ };
+
+ h3 {'Horizontal Rules'};
+
+ p {
+ outs('Three or more dashes: '); code {'---'};
+ };
+
+ address {
+ outs_raw '(Thanks to <a href="http://daringfireball.net/projects/markdown/dingus">Daring Fireball</a>)';
+ }
+ };
+ script { outs_raw 'jQuery("syntax_content").toggle();' };
+ };
+};
+
+private template page_list => sub {
+ my ( $pages, $id ) = get(qw(pages id));
+ dl {{ id is $id, class is "pagelist" }
+ while ( my $page = $pages->next ) {
+ dt {
+ hyperlink(
+ label => $page->name,
+ url => '/view/' . $page->name
+ );
+ };
+ dd {
+ outs( $page->updated );
+ outs(
+ ' - ('
+ . (
+ $page->updated_by->id
+ ? $page->updated_by->name
+ : _('Anonymous')
+ )
+ . ')'
+ );
+ };
+ }
+ };
+};
+
+private template diff => sub {
+ my ($page, $from, $to) = get(qw(page from to));
+
+ $to ||= $page->revisions->last;
+ $from ||= $to->previous || Wifty::Model::Revision->new;
+
+ my $before = $to->previous;
+ my $after = $to->next;
+
+ use Text::Diff ();
+ my $diff = Text::Diff::diff(
+ \( $from->content ),
+ \( $to->content ),
+ { STYLE => 'Text::Diff::HTML' }
+ );
+
+ div {{ class is 'revision_nav' }
+ if ($before) {
+ span {{ class is "prev" }
+ hyperlink(
+ url => "/view/" . $page->name . "/" . $before->id,
+ label => _("Previous revision")
+ );
+ };
+ }
+ outs('|') if $before and $after;
+ if ($after) {
+ span {{ class is "next" }
+ hyperlink(
+ url => "/view/" . $page->name . "/" . $after->id,
+ label => _("Next revision")
+ );
+ };
+ }
+ };
+ pre {{ class is 'diff' } outs_raw($diff) };
+ hr {}
+};
+
+
+1;
diff --git a/lib/Wifty/View/Page.pm b/lib/Wifty/View/Page.pm
new file mode 100644
index 0000000..c896e24
--- /dev/null
+++ b/lib/Wifty/View/Page.pm
@@ -0,0 +1,21 @@
+use strict;
+use warnings;
+
+package Wifty::View::Page;
+use base qw(Jifty::View::Declare::Page);
+use Jifty::View::Declare::Helpers;
+
+sub render_body {
+ my ($self, $body_code) = @_;
+
+ my $logo = Jifty->config->app('Logo');
+ return $self->SUPER::render_body( $body_code ) unless $logo;
+
+ return $self->SUPER::render_body( sub {
+ div { attr { id is "logo" } img { src is $logo, alt is '' } };
+ $body_code->();
+ });
+}
+
+1;
+
commit cf6f22515b5fd043088b66611450a6b35648ba51
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Mon Dec 1 05:11:12 2008 +0000
delete all implemented TD templates from View-not-ready-yet
diff --git a/lib/Wifty/View-not-ready-yet.pm b/lib/Wifty/View-not-ready-yet.pm
index 58fc261..04a4022 100644
--- a/lib/Wifty/View-not-ready-yet.pm
+++ b/lib/Wifty/View-not-ready-yet.pm
@@ -19,337 +19,6 @@ use base qw/Jifty::View::Declare::Templates/;
use Template::Declare::Tags;
use Jifty::View::Declare::Templates;
-private template page_list => sub {
- # actually creates: sub _jifty_ui_template_page_list
- #
- my ( $pages, $id ) = get(qw(pages id));
- dl {{ id is $id, class is "pagelist" }
- while ( my $page = $pages->next ) {
- dt {
- hyperlink(
- label => $page->name,
- url => '/view/' . $page->name
- );
- };
- dd {
- outs( $page->updated );
- outs(
- ' - ('
- . (
- $page->updated_by->id
- ? $page->updated_by->name
- : _('Anonymous')
- )
- . ')'
- );
- };
- }
- };
-};
-
-private template nav => sub {
- my $top = Jifty->web->navigation;
- $top->child( Home => url => "/", sort_order => 1 );
- $top->child(
- Recent =>
- url => "/recent",
- label => "Recent Changes",
- sort_order => 2
- );
- $top->child(
- Search =>
- url => "/search",
- label => "Search",
- sort_order => 3
- );
-
- if ( Jifty->config->framework('AdminMode') ) {
- $top->child(
- Administration =>
- url => "/__jifty/admin/",
- sort_order => 998
- );
- $top->child(
- OnlineDocs =>
- url => "/__jifty/online_docs/",
- label => 'Online docs',
- sort_order => 999
- );
- }
-
-};
-
-private template page_nav => sub {
- my %args = (page => 'HomePage', rev => undef, @_);
- my $page = $args{'page'};
- my $rev = $args{'rev'};
-
- $page ||= 'HomePage';
- my $subpath = $page . ( $rev ? "/$rev" : '' );
- my $top = Jifty->web->page_navigation;
-
- my $page_obj = Wifty::Model::Page->new();
- $page_obj->load_by_cols( name => $page );
-
- $top->child( View => url => '/view/' . $subpath );
- $top->child( Edit => url => '/edit/' . $subpath );
- $top->child( History => url => '/history/' . $page );
- $top->child( Latest => url => '/view/' . $page ) if ($rev);
-
-};
-
-private template wrapper => sub {
- # it's actually called with args.
- my ($args, $coderef ) = (@_);
- my $title = $args->{title};
- my $id = $args->{id};
- my $wikiname = Jifty->config->app('WikiName') || "Wifty";
-
- show('nav');
- show( 'header', title => $args->{'title'}, wikiname => $wikiname );
-
- body {{ id is $args->{id} }
- if ( Jifty->config->framework('AdminMode') ) {
- div {{ class is 'warning admin_mode' }
- _('Alert') . ":"
- . tangent(
- label => _('Administration mode is enabled'),
- url => '/__jifty/admin/'
- )
- . ".";
- }
- }
- div {{ id is 'logo' }
- Jifty->config->app('Logo')
- ? '<img src="' . Jifty->config->app('Logo') . '" alt="" />'
- : '';
- };
- div {{ id is 'header' }
- div {{ id is 'wikiheader' }
- h1 {{ id is 'wikiname' }
- hyperlink( url => "/", label => _($wikiname) );
- };
- outs(Jifty->web->navigation->render_as_menu);
- show('search_box');
-
- };
- div {{ id is 'pageheader' }
- h1 {{ id is 'pagename' }
- _( $args->{title} );
- };
-
- outs (Jifty->web->page_navigation->render_as_menu);
- }
- };
-
- show('salutation');
-
- hr {{ class is 'clear' }};
- div {{ id is 'content' }
- Jifty->web->render_messages;
- my $buf = '';
- {
- local $Template::Declare::Tags::BUFFER ='';
- $coderef->();
- $buf = $Template::Declare::Tags::BUFFER;
- #warn "My buffer is $buf";
- }
- outs($buf);
- hr {{ class is 'clear' }};
-
- }
- }
-
-};
-
-
-private template search_box => sub {
- my $action = new_action( class => 'SearchPage' );
- $action->sticky_on_success(1);
- span {
- form {
- form_next_page( url => '/search' );
- render_param( $action, 'contains', label => 'Search:' );
- }
- };
-};
-
-private template salutation => sub {
- div {{ id is 'salutation' }
-
- if ( Jifty->web->current_user->id and Jifty->web->current_user->user_object ) {
- outs('Hiya, ');
- span {{ class is 'user' } Jifty->web->current_user->user_object->name };
- outs('(' . hyperlink( label => q{Logout}, url => '/logout' ) .')');
- } else {
- outs("You're not currently signed in.") . tangent( label => q{Sign in}, url => '/login' ) . "."; }
- }
-};
-
-
-private template diff => sub {
- my %args = ( page => undef, from => undef, to => undef, @_);
-
- my $to = $args{'to'} ||$args{page}->revisions->last;
- my $from = $args{'from'}|| $to->previous || Wifty::Model::Revision->new;
-
- my $before = $to->previous;
- my $after = $to->next;
-
- use Text::Diff ();
- my $diff = Text::Diff::diff(
- \( $from->content ),
- \( $to->content ),
- { STYLE => 'Text::Diff::HTML' }
- );
-
- div {{ class is 'revision_nav' }
- if ($before) {
- span {{ class is "prev" }
- hyperlink(
- url => "/view/" . $args{page}->name . "/" . $before->id,
- label => "Previous revision"
- );
- };
- }
- outs('|') if ( $before and $after );
-
- if ($after) {
- span {{ class is "next" }
- hyperlink(
- url => "/view/" . $args{'page'}->name . "/" . $after->id,
- label => "Next revision"
- );
- };
- }
- };
- pre {{ class is 'diff' } $diff };
- hr {}
-
-};
-
-template create => sub {
- my ( $action, $page ) = get(qw(action page));
- show(
- 'wrapper',
- {title => 'New page: ' . $page, id => 'create' },
- sub {p{
- form {
- div {{ class is 'form_wrapper' }
- form_next_page( url => '/view/' . $page );
- render_param($action => 'name', render_as => 'hidden', default_value => $page);
- div {{ class is 'inline' }
- render_param($action => 'content', rows => 30 );
- };
- div {{ class is 'inline' }
- form_submit( label => 'Create' );
- };
- };
- };
- show('markup');
- };
- }
- );
-};
-
-template edit => sub {
- my ( $page, $revision, $viewer ) = get(qw(page revision viewer));
- my $can_edit = $page->current_user_can('update');
- show( 'page_nav', page => $page->name, rev => $revision->id );
- show(
- 'wrapper',
- { title => 'Edit: ' . $page->name . ( $revision->id ? " as of " . $revision->created : '' ), id => "update" },
- sub {
- form {
- div {{ class is 'form_wrapper' }
- div {{ class is 'inline' }
- unless ($can_edit) { p {{ style is "width: 70%" } q{You don't have permission to edit this page. Perhaps} . tangent( url => '/login', label => 'logging in') . q{would help. In the mean time, though, you're welcome to view and} . q{copy the source of this page.}; } }
- form_next_page( url => '/view/' . $page->name );
- render_param($viewer => 'content');
- if ($can_edit) { div {{ class is 'line' } form_submit( label => 'Save' ); } }
- };
- };
- show('markup');
- };
-
- }
-
-
- );
-};
-
-template history => sub {
- my ( $page, $revisions ) = get(qw(page revisions));
- # XXX TODO, this isn't right
- show( 'page_nav', page => $page->name );
- show(
- 'wrapper',
- { title => $revisions->count . " revisions of " . $page->name },
- sub {
- dl {{ id is 'history' }
- while ( my $rev = $revisions->next ) {
- dt {
- hyperlink(
- label => $rev->created,
- url => '/view/' . $page->name . '/' . $rev->id
- );
- if ( $rev->created_by->id ) {
- '(' . $rev->created_by->name . ')';
- } else {
- '(Anonymous)';
- }
- };
- dd { length( $rev->content ) . ' bytes' };
- }
- };
- }
- );
-
-};
-
-template no_such_page => sub {
- my ( $page ) = get(qw(page));
- show(
- 'wrapper',
- { title => 'No such page: ' . $page },
- sub {
-
- p {
- q{Unfortunately, you've tried to reach a page that doesn't exist }
- . q{yet, and you don't have permissions to create pages. If you }
- . tangent( url => '/login', label => 'login' )
- . q{, you'll be able to create new pages of your own.}
-
- }
-
- }
- );
-};
-
-template pages => sub {
- my ($pages ) = get(qw(pages));
- show(
- 'wrapper',
- { title => 'These are the pages on your wiki!' },
- sub {
- show( 'page_list', pages => $pages, id => 'allpages' );
- }
- );
-
-};
-
-template recent => sub {
- my ( $pages ) = get(qw(pages));
- show(
- 'wrapper',
- { title => 'Updated this week' },
- sub {
- show( 'page_list', pages => $pages, id => 'recentupdates' );
- }
- );
-
-};
-
template recent_atom => sub {
my ( $pages) = get(qw(pages));
use XML::Atom::SimpleFeed;
@@ -375,46 +44,6 @@ template recent_atom => sub {
$feed->print;
};
-template search => sub {
- my ( $pages, $search ) = get(qw(pages search));
- show( 'wrapper',
- { title => 'Search' },
- sub {
- form {
- div {{ id is "searchbox", class is 'inline' }
- render_param($search => 'contains', label => 'Find pages containing:' );
- form_submit( label => 'Search', submit => $search);
- };
-
- };
- if ($pages) {
- show( 'page_list' => pages => $pages, id => 'searchresults' );
- }
- }
- );
-};
-
-template view => sub {
- my ( $page, $revision, $viewer ) = get(qw(page revision viewer));
- show( 'page_nav', page => $page->name, rev => $revision->id );
- show(
- 'wrapper',
- { title => $page->name
- . ( $revision->id ? " as of " . $revision->created : '' )
- },
- sub {
- if ( $revision->id ) {
- show( 'diff', page => $page, to => $revision );
- }
-
- render_param($viewer => 'content', label => '', render_mode => 'read');
- #$viewer->form_value( 'content', label => "" );
-
- }
- );
-
-};
-
private template header => sub {
my %args = ( title=> undef, wikiname => undef, @_);
@@ -437,98 +66,4 @@ private template header => sub {
}
};
-template markup => sub {
- return undef unless ( Jifty->config->app('Formatter') eq 'Markdown' );
-
- div {{ id is 'syntax' }
- div {
- a {{
- href is "#",
- onclick is "Element.toggle('syntax_content');return(false);"
- } b {'Wiki Syntax Help'}; }
- };
- div {{ id is 'syntax_content' }
- h3 {'Phrase Emphasis'};
- code {
- b { '**bold**'; };
- i {'_italic_'};
- };
-
- h3 {'Links'};
-
- code {'Show me a [wiki page](WikiPage)'};
- code {'An [example](http://url.com/ "Title")'};
-
- h3 {'Headers'};
-
- pre {
- code {
- join( "\n",
- '# Header 1',
- '## Header 2',
- '###### Header 6' );
- }
- };
-
- h3 {'Lists'};
-
- p {'Ordered, without paragraphs:'};
-
- pre {
- code {
- join( "\n", '1. Foo', '2. Bar' );
- }
- };
-
- p {' Unordered, with paragraphs:'};
-
- pre {
- code {
- join( "\n",
- '* A list item.',
- 'With multiple paragraphs.',
- '* Bar' );
- };
-
- h3 {'Code Spans'};
-
- p {
- code {'`<code>`'}
- . 'spans are delimited by backticks.';
- };
-
- h3 {'Preformatted Code Blocks'};
-
- p {'Indent every line of a code block by at least 4 spaces.'};
-
- pre {
- code {
- 'This is a normal paragraph.' . "\n\n" . "\n"
- . ' This is a preformatted' . "\n"
- . ' code block.';
- };
- };
-
- h3 {'Horizontal Rules'};
-
- p {
- 'Three or more dashes: ' . code {'---'};
- };
-
- address {
- '(Thanks to <a href="http://daringfireball.net/projects/markdown/dingus">Daring Fireball</a>)';
- }
- }
- };
- script {
- qq{
- // javascript flyout by Eric Wilhelm
- // TODO use images for minimize/maximize button
- // Is there a way to add a callback?
- Element.toggle('syntax_content');
- };
- };
- };
-};
-
1;
commit af8a97feaf21879bd2393c8eb473b05ae15dc5f0
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Mon Dec 1 05:11:56 2008 +0000
delete all mason templates we are on TD
diff --git a/share/web/templates/_elements/diff b/share/web/templates/_elements/diff
deleted file mode 100644
index 0e874d7..0000000
--- a/share/web/templates/_elements/diff
+++ /dev/null
@@ -1,30 +0,0 @@
-<%args>
-$page
-$from =>undef
-$to => undef
-</%args>
-<%init>
-
-$to ||= $page->revisions->last;
-$from ||= $to->previous || Wifty::Model::Revision->new;
-
-my $before = $to->previous;
-my $after = $to->next;
-
-use Text::Diff ();
-my $diff = Text::Diff::diff(\($from->content), \($to->content), { STYLE => 'Text::Diff::HTML' });
-
-</%init>
-<div class="revision_nav">
-% if ($before) {
-<span class="prev"><% Jifty->web->link(url => "/view/".$page->name."/".$before->id, label => "Previous revision") %></span>
-% }
-% if ( $before and $after ) {
- |
-% }
-% if ($after) {
-<span class="next"><% Jifty->web->link(url => "/view/".$page->name."/".$after->id, label => "Next revision") %></span>
-% }
-</div>
-<pre class="diff"><% $diff |n%></pre>
-<hr />
diff --git a/share/web/templates/_elements/header b/share/web/templates/_elements/header
deleted file mode 100644
index d89c29d..0000000
--- a/share/web/templates/_elements/header
+++ /dev/null
@@ -1,18 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
-<head>
- <meta http-equiv="content-type" content="text/html; charset=utf-8" />
- <meta name="robots" content="all" />
-
- <title><% _( $title ) %> - <% _( $wikiname ) %></title>
-
- <% Jifty->web->include_css %>
- <% Jifty->web->include_javascript %>
-</head>
-<%args>
-$title => ""
-$wikiname => ""
-</%args>
-<%init>
-$r->content_type('text/html; charset=utf-8');
-</%init>
diff --git a/share/web/templates/_elements/markup b/share/web/templates/_elements/markup
deleted file mode 100644
index 24e4c9c..0000000
--- a/share/web/templates/_elements/markup
+++ /dev/null
@@ -1,70 +0,0 @@
-<%init>
-return undef unless (Jifty->config->app('Formatter') eq 'Markdown');
-</%init>
-
-<div id="syntax">
-<div><a href="#" onclick="Element.toggle('syntax_content');return(false);"><b>Wiki Syntax Help</b></a>
-</div>
-<div id="syntax_content">
-
-<h3>Phrase Emphasis</h3>
-
-<code> <b>**bold**</b> <i>_italic_</i> </code>
-
-<h3>Links</h3>
-
-<code>Show me a [wiki page](WikiPage)</code>
-<code>An [example](http://url.com/ "Title")</code>
-
-<h3>Headers</h3>
-
-<pre><code># Header 1
-## Header 2
-###### Header 6
-</code></pre>
-
-<h3>Lists</h3>
-
-<p>Ordered, without paragraphs:</p>
-
-<pre><code>1. Foo
-2. Bar
-</code></pre>
-
-<p>Unordered, with paragraphs:</p>
-
-<pre><code>* A list item.
-
- With multiple paragraphs.
-
-* Bar</code></pre>
-
-<h3>Code Spans</h3>
-
-<p><code>`<code>`</code> spans are
-delimited by backticks.</p>
-
-<h3>Preformatted Code Blocks</h3>
-
-<p>Indent every line of a code block
-by at least 4 spaces.</p>
-
-<pre><code>This is a normal paragraph.
-
- This is a preformatted
- code block.
-</code></pre>
-
-<h3>Horizontal Rules</h3>
-
-<p>Three or more dashes: <code>---</code></p>
-
-<address>(Thanks to <a href="http://daringfireball.net/projects/markdown/dingus">Daring Fireball</a>)</address>
-</div>
-</div>
-<script>
- // javascript flyout by Eric Wilhelm
- // TODO use images for minimize/maximize button
- // Is there a way to add a callback?
- Element.toggle('syntax_content');
-</script>
diff --git a/share/web/templates/_elements/page_list b/share/web/templates/_elements/page_list
deleted file mode 100644
index f4bfd15..0000000
--- a/share/web/templates/_elements/page_list
+++ /dev/null
@@ -1,16 +0,0 @@
-<%args>
-$pages
-$id
-</%args>
-<dl id="<%$id%>" class="pagelist">
-% while (my $page = $pages->next) {
-<dt><% Jifty->web->link( label => $page->name, url => '/view/'.$page->name)%></dt>
-<dd><%$page->updated%>
-% if($page->updated_by->id) {
- (<% $page->updated_by->name %>)
-% } else {
- (Anonymous)
-% }
-</dd>
-% }
-</dl>
diff --git a/share/web/templates/_elements/salutation b/share/web/templates/_elements/salutation
deleted file mode 100644
index bfd36c6..0000000
--- a/share/web/templates/_elements/salutation
+++ /dev/null
@@ -1,9 +0,0 @@
-<div id="salutation">
-% if ( Jifty->web->current_user->id and Jifty->web->current_user->user_object ) {
- Hiya, <span class="user"><% Jifty->web->current_user->user_object->name %></span>.
- (<% Jifty->web->link( label => q{Logout}, url => '/logout' )%>)
-% } else {
- You're not currently signed in.
- <% Jifty->web->tangent( label => q{Sign in}, url => '/login' ) %>.
-% }
-</div>
diff --git a/share/web/templates/_elements/search_box b/share/web/templates/_elements/search_box
deleted file mode 100644
index c487d97..0000000
--- a/share/web/templates/_elements/search_box
+++ /dev/null
@@ -1,10 +0,0 @@
-<%init>
-my $action = Jifty->web->new_action(class => 'SearchPage', moniker => 'search');
-$action->sticky_on_success(1);
-</%init>
-<span>
-<% Jifty->web->form->start %>
-<% Jifty->web->form->next_page(url => '/search') %>
-<% $action->form_field('contains', label => 'Search:') %>
-<% Jifty->web->form->end %>
-</span>
diff --git a/share/web/templates/_elements/wrapper b/share/web/templates/_elements/wrapper
deleted file mode 100644
index f06fbe2..0000000
--- a/share/web/templates/_elements/wrapper
+++ /dev/null
@@ -1,52 +0,0 @@
-<& /_elements/header, title => $title, wikiname => $wikiname &>
-% Jifty->handler->stash->{'in_body'} = 1;
-<body<% $id && qq[ id="$id"]|n%>>
-% if (Jifty->config->framework('AdminMode') ) {
- <div class="warning admin_mode">
- <%_('Alert')%>: <% Jifty->web->tangent( label => _('Administration mode is enabled'),
- url => '/__jifty/admin/') %>.
- </div>
-% }
- <div id="logo">
- <% Jifty->config->app('Logo') ? '<img src="'.Jifty->config->app('Logo').'" alt="" />' : '' |n %>
- </div>
- <div id="header">
- <div id="wikiheader">
- <h1 id="wikiname">
- <% Jifty->web->link( url => "/", label => _($wikiname) ) %>
- </h1>
-
- <% Jifty->web->navigation->render_as_menu %>
- <& /_elements/search_box &>
- </div>
-
- <div id="pageheader">
- <h1 id="pagename"><% _($title) %></h1>
-
- <% Jifty->web->page_navigation->render_as_menu %>
- </div>
- </div>
-
- <& /_elements/salutation &>
-
- <hr class="clear" />
-
- <div id="content">
- <% Jifty->web->render_messages %>
- <% $m->content |n%>
- <& /_elements/keybindings &>
- <hr class="clear" />
- </div>
-
- <div id="jifty-wait-message" style="display: none"><%_('Loading...')%></div>
-% Jifty::Mason::Halo->render_component_tree() if Jifty->config->framework('DevelMode');
-</body>
-</html>
-% Jifty->handler->stash->{'in_body'} = 0;
-<%args>
-$title => ""
-$id => ''
-</%args>
-<%init>
-my $wikiname = Jifty->config->app('WikiName') || "Wifty";
-</%init>
diff --git a/share/web/templates/create b/share/web/templates/create
deleted file mode 100644
index 002545b..0000000
--- a/share/web/templates/create
+++ /dev/null
@@ -1,19 +0,0 @@
-<&|/_elements/wrapper, title => 'New page: '. $page, id => 'create'&>
-<% Jifty->web->form->start %>
-<div class="form_wrapper">
-<% Jifty->web->form->next_page( url => '/view/'.$page) %>
-<% $action->form_field('name', render_as => 'hidden', default_value => $page) %>
-<div class="inline">
-<% $action->form_field('content', rows => 30)%>
-</div>
-<div class="line">
-<% Jifty->web->form->submit( label => 'Create' )%>
-</div>
-</div>
-<% Jifty->web->form->end %>
-<& /_elements/markup &>
-</&>
-<%args>
-$action => undef
-$page => undef
-</%args>
diff --git a/share/web/templates/edit b/share/web/templates/edit
deleted file mode 100644
index 37d4512..0000000
--- a/share/web/templates/edit
+++ /dev/null
@@ -1,30 +0,0 @@
-<%args>
-$page
-$revision
-$viewer
-</%args>
-<%init>
-my $can_edit = $page->current_user_can('update');
-</%init>
-<&|/_elements/wrapper, title => 'Edit: '.$page->name . ($revision->id ? " as of ".$revision->created : ''), id => "update" &>
-<% Jifty->web->form->start %>
-<div class="form_wrapper">
-<div class="inline">
-% unless($can_edit) {
- <p style="width: 70%"> You don't have permission to edit this page. Perhaps
- <% Jifty->web->tangent(url => '/login', label => 'logging in') %>
- would help. In the mean time, though, you're welcome to view and
- copy the source of this page. </p>
-% }
-<% Jifty->web->form->next_page( url => '/view/'.$page->name) %>
-<% $viewer->form_field('content')%>
-</div>
-% if($can_edit) {
-<div class="line">
-<% Jifty->web->form->submit( label => 'Save') %>
-</div>
-% }
-</div>
-<% Jifty->web->form->end %>
-<& /_elements/markup &>
-</&>
diff --git a/share/web/templates/history b/share/web/templates/history
deleted file mode 100644
index 5fa39a8..0000000
--- a/share/web/templates/history
+++ /dev/null
@@ -1,20 +0,0 @@
-<%args>
-$page
-$revisions
-</%args>
-<&|/_elements/wrapper, title => $revisions->count ." revisions of " .$page->name &>
-<dl id="history">
-% while (my $rev = $revisions->next) {
-<dt><% Jifty->web->link( label => $rev->created,
- url => '/view/'.$page->name.'/'.$rev->id
- ) %>
-% if($rev->created_by->id) {
- (<% $rev->created_by->name %>)
-% } else {
- (Anonymous)
-% }
-</dt>
-<dd><%length($rev->content)%> bytes</dd>
-% }
-</dl>
-</&>
diff --git a/share/web/templates/no_such_page b/share/web/templates/no_such_page
deleted file mode 100644
index fbdc076..0000000
--- a/share/web/templates/no_such_page
+++ /dev/null
@@ -1,11 +0,0 @@
-<&|/_elements/wrapper, title => 'No such page: '. $page&>
-
- <p>Unfortunately, you've tried to reach a page that doesn't exist
- yet, and you don't have permissions to create pages. If you
- <% Jifty->web->tangent(url => '/login', label => 'login') %>,
- you'll be able to create new pages of your own.</p>
-
-</&>
-<%args>
-$page => undef
-</%args>
diff --git a/share/web/templates/pages b/share/web/templates/pages
deleted file mode 100644
index 4495c8e..0000000
--- a/share/web/templates/pages
+++ /dev/null
@@ -1,6 +0,0 @@
-<%args>
-$pages
-</%args>
-<&|/_elements/wrapper, title => 'These are the pages on your wiki!' &>
-<& /_elements/page_list, pages => $pages, id => 'allpages' &>
-</&>
diff --git a/share/web/templates/recent b/share/web/templates/recent
deleted file mode 100644
index 071ac48..0000000
--- a/share/web/templates/recent
+++ /dev/null
@@ -1,6 +0,0 @@
-<%args>
-$pages
-</%args>
-<&|/_elements/wrapper, title => 'Updated this week' &>
-<& /_elements/page_list, pages => $pages, id => 'recentupdates' &>
-</&>
diff --git a/share/web/templates/search b/share/web/templates/search
deleted file mode 100644
index 6d77876..0000000
--- a/share/web/templates/search
+++ /dev/null
@@ -1,19 +0,0 @@
-<%args>
-$pages
-$search
-</%args>
-<%init>
-warn $search;
-</%init>
-<&|/_elements/wrapper, title => 'Search' &>
-<% Jifty->web->form->start %>
- <div id="searchbox" class="inline">
- <% $search->form_field('contains', label => 'Find pages containing:') %>
- <% $search->button(label => 'Search') %>
- </div>
-
-<% Jifty->web->form->end %>
-% if($pages) {
-<& /_elements/page_list, pages => $pages, id => 'searchresults' &>
-% }
-</&>
diff --git a/share/web/templates/view b/share/web/templates/view
deleted file mode 100644
index cf08c0f..0000000
--- a/share/web/templates/view
+++ /dev/null
@@ -1,14 +0,0 @@
-<%args>
-$page
-$revision
-$viewer
-</%args>
-<&|/_elements/wrapper, title => $page->name . ($revision->id ? " as of ".$revision->created : '') &>
-
-% if ($revision->id) {
-<& /_elements/diff, page => $page, to => $revision &>
-% }
-
-<% $viewer->form_value('content', label => "") %>
-
-</&>
commit 45763595867e66c91116bb2e9eb65dde568dc737
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Mon Dec 1 05:12:32 2008 +0000
fix css a little
diff --git a/share/web/static/css/app-base.css b/share/web/static/css/app-base.css
index 94becf6..f4cdd23 100644
--- a/share/web/static/css/app-base.css
+++ b/share/web/static/css/app-base.css
@@ -102,7 +102,6 @@ h4 { font-size: 1.1em; }
font-size: 0.9em;
width: 25%;
position: absolute;
- top: 1em;
right: 1em;
}
commit d52aed342b1663cc108450b329e2dd4786ef41ef
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Mon Dec 1 05:18:07 2008 +0000
adjust default config a little
diff --git a/etc/config.yml b/etc/config.yml
index 470e5a9..2ca00cd 100644
--- a/etc/config.yml
+++ b/etc/config.yml
@@ -29,9 +29,14 @@ framework:
StaticRoot: share/web/static
TemplateRoot: share/web/templates
application:
-# RequireAuth: 1
+ # name of this wiki
WikiName: A Wiki
- Formatter: Markdown
+
+ # can anonymous users change wiki?
+ RequireAuth: 1
+
# The formatter options are "Markdown" and "Kwiki"
- # Logo: http://svk.bestpractical.com/svk-logo.png
+ Formatter: Markdown
+
# The logo points to the url to a logo image
+ # Logo: http://www.bestpractical.com/images/svk-logo.png
commit 367731ac3db9b407233cb98b48ff7836866e66c0
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Sat Dec 6 14:00:12 2008 +0000
split diff temlate into:
** diff - just render a diff from rev x to y
** diff/with_nav - render diff with prev/next navigation buttons
** helpers/diff - helper for regions with toggle visibility link
diff --git a/lib/Wifty/View.pm b/lib/Wifty/View.pm
index e0beb53..6c13c36 100644
--- a/lib/Wifty/View.pm
+++ b/lib/Wifty/View.pm
@@ -11,7 +11,7 @@ template 'view' => page {
? _('%1 as of %2', $page->name, $revision->created)
: $page->name;
{ title is $title }
- show( 'diff', page => $page, to => $revision ) if $rev;
+ show( 'diff/with_nav', page => $page, to => $revision ) if $rev;
render_param($viewer => 'content', label => '', render_mode => 'read');
};
@@ -248,21 +248,55 @@ private template page_list => sub {
};
};
-private template diff => sub {
- my ($page, $from, $to) = get(qw(page from to));
-
- $to ||= $page->revisions->last;
- $from ||= $to->previous || Wifty::Model::Revision->new;
+template 'helpers/diff' => sub {
+ my ($from, $to, $show) = get(qw(from to show));
+ hyperlink
+ label => $show? _('hide diff') : _('show diff'),
+ onclick => {
+ refresh_self => 1,
+ args => {
+ show => !$show,
+ from => $from,
+ to => $to
+ },
+ },
+ ;
+ if ( $show ) {
+ # XXX: check why show(x, key => $value, key => $value)
+ # doesn't work
+ set(
+ to => Wifty::Model::Revision->load_by_cols(id => $to),
+ from => Wifty::Model::Revision->load_by_cols(id => $from),
+ );
+ show('/diff');
+ }
+};
- my $before = $to->previous;
- my $after = $to->next;
+private template 'diff' => sub {
+ my ($from, $to) = get(qw(from to));
+ if ( $to && !$from ) {
+ $from = $to->previous;
+ }
+ elsif ( !$to && $from ) {
+ $to = $from->next;
+ }
use Text::Diff ();
my $diff = Text::Diff::diff(
- \( $from->content ),
- \( $to->content ),
+ \( $from? $from->content : '' ),
+ \( $to ? $to->content : '' ),
{ STYLE => 'Text::Diff::HTML' }
);
+ pre {{ class is 'diff' } outs_raw($diff) };
+};
+
+private template 'diff/with_nav' => sub {
+ my ($page, $from, $to) = get(qw(page from to));
+
+ $to ||= $page->revisions->last;
+
+ my $before = $to->previous;
+ my $after = $to->next;
div {{ class is 'revision_nav' }
if ($before) {
@@ -283,7 +317,8 @@ private template diff => sub {
};
}
};
- pre {{ class is 'diff' } outs_raw($diff) };
+ set(to => $to);
+ show('diff');
hr {}
};
commit 964b9375d4543de01f9d0b35aae07b52ad7b104c
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Sat Dec 6 14:01:07 2008 +0000
fetch revisions once
diff --git a/lib/Wifty/View.pm b/lib/Wifty/View.pm
index 6c13c36..ce3cd57 100644
--- a/lib/Wifty/View.pm
+++ b/lib/Wifty/View.pm
@@ -77,6 +77,7 @@ template no_such_page => page {
template history => page {
my ( $page, $revisions ) = get(qw(page revisions));
+ $revisions->do_search; # avoid count+fetch
{ title is $revisions->count . " revisions of " . $page->name }
dl { { id is 'history' }
commit 969ff34cc021001885af0172847862b375605acf
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Sat Dec 6 14:04:36 2008 +0000
string inside if block don't work without outs
* dl for history is not suitable - use ul
* add diff region
* needs CSS
diff --git a/lib/Wifty/View.pm b/lib/Wifty/View.pm
index ce3cd57..c2a501a 100644
--- a/lib/Wifty/View.pm
+++ b/lib/Wifty/View.pm
@@ -80,21 +80,24 @@ template history => page {
$revisions->do_search; # avoid count+fetch
{ title is $revisions->count . " revisions of " . $page->name }
- dl { { id is 'history' }
- while ( my $rev = $revisions->next ) {
- dt {
- hyperlink(
- label => $rev->created,
- url => '/view/' . $page->name . '/' . $rev->id
- );
- if ( $rev->created_by->id ) {
- '(' . $rev->created_by->name . ')';
- } else {
- '(Anonymous)';
- }
- };
- dd { length( $rev->content ) . ' bytes' };
- }
+ ul { { id is 'history' }
+ while ( my $rev = $revisions->next ) { li {
+ hyperlink(
+ label => $rev->created,
+ url => '/view/' . $page->name . '/' . $rev->id
+ );
+ if ( $rev->created_by->id ) {
+ outs(' ', '(' . $rev->created_by->name . ')');
+ } else {
+ outs(' ', _('(Anonymous)'));
+ }
+ outs( ' ', _('%1 bytes', length $rev->content ) );
+ render_region(
+ 'revision-'. $rev->id .'-diff',
+ path => '/helpers/diff',
+ defaults => { page => $page->id, to => $rev->id },
+ )
+ } }
};
};
commit 38ad981e65b619da0568ae95d2b170e1fb259f44
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Sat Dec 6 14:15:24 2008 +0000
allow to read users' data
diff --git a/lib/Wifty/Model/User.pm b/lib/Wifty/Model/User.pm
index bc483eb..9d514b1 100644
--- a/lib/Wifty/Model/User.pm
+++ b/lib/Wifty/Model/User.pm
@@ -10,4 +10,15 @@ use Jifty::Plugin::User::Mixin::Model::User;
# import columns: password, auth_token
use Jifty::Plugin::Authentication::Password::Mixin::Model::User;
+sub current_user_can {
+ my $self = shift;
+ my $type = shift;
+
+ if ( $type eq 'read' ) {
+ return 1;
+ }
+
+ return $self->SUPER::current_user_can($type, @_);
+}
+
1;
commit 41cc3fa398bdf2330df2aec87d759278721f1a2f
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Sat Dec 6 15:50:09 2008 +0000
typo
diff --git a/lib/Wifty/View.pm b/lib/Wifty/View.pm
index c2a501a..1dbd8ec 100644
--- a/lib/Wifty/View.pm
+++ b/lib/Wifty/View.pm
@@ -322,7 +322,7 @@ private template 'diff/with_nav' => sub {
}
};
set(to => $to);
- show('diff');
+ show('/diff');
hr {}
};
commit d84528fccfbb8796c2792d1aa7caa1f461a38140
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Sat Dec 6 16:17:17 2008 +0000
friendly_name method
diff --git a/lib/Wifty/Model/User.pm b/lib/Wifty/Model/User.pm
index 9d514b1..010ae66 100644
--- a/lib/Wifty/Model/User.pm
+++ b/lib/Wifty/Model/User.pm
@@ -10,6 +10,12 @@ use Jifty::Plugin::User::Mixin::Model::User;
# import columns: password, auth_token
use Jifty::Plugin::Authentication::Password::Mixin::Model::User;
+sub friendly_name {
+ my $self = shift;
+ return _('Anonymous') unless $self->id;
+ return $self->name;
+}
+
sub current_user_can {
my $self = shift;
my $type = shift;
diff --git a/lib/Wifty/View.pm b/lib/Wifty/View.pm
index 1dbd8ec..35ad043 100644
--- a/lib/Wifty/View.pm
+++ b/lib/Wifty/View.pm
@@ -86,11 +86,7 @@ template history => page {
label => $rev->created,
url => '/view/' . $page->name . '/' . $rev->id
);
- if ( $rev->created_by->id ) {
- outs(' ', '(' . $rev->created_by->name . ')');
- } else {
- outs(' ', _('(Anonymous)'));
- }
+ outs( ' (' . $rev->created_by->friendly_name . ')' );
outs( ' ', _('%1 bytes', length $rev->content ) );
render_region(
'revision-'. $rev->id .'-diff',
@@ -238,15 +234,7 @@ private template page_list => sub {
};
dd {
outs( $page->updated );
- outs(
- ' - ('
- . (
- $page->updated_by->id
- ? $page->updated_by->name
- : _('Anonymous')
- )
- . ')'
- );
+ outs( ' - ('. $page->updated_by->friendly_name .')' );
};
}
};
commit 7f09136bb3d3967dab8143f22f0acd4f80a23ab8
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Sat Dec 6 16:17:51 2008 +0000
fine tune current_user_can in the User model
diff --git a/lib/Wifty/Model/User.pm b/lib/Wifty/Model/User.pm
index 010ae66..0115d97 100644
--- a/lib/Wifty/Model/User.pm
+++ b/lib/Wifty/Model/User.pm
@@ -19,12 +19,15 @@ sub friendly_name {
sub current_user_can {
my $self = shift;
my $type = shift;
+ my $column = shift;
if ( $type eq 'read' ) {
- return 1;
+ return 1 if $column eq 'name';
+ my $cu = $self->current_user;
+ return 1 if $self->id && ($cu->id||0) == $self->id;
}
- return $self->SUPER::current_user_can($type, @_);
+ return $self->SUPER::current_user_can($type, $column, @_);
}
1;
commit 34ed26d9a5c6b30c18da0f3d5f5551a38de1dc15
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Sun Dec 7 03:08:36 2008 +0000
column passed as named argument into rights checker
diff --git a/lib/Wifty/Model/User.pm b/lib/Wifty/Model/User.pm
index 0115d97..fd1e28f 100644
--- a/lib/Wifty/Model/User.pm
+++ b/lib/Wifty/Model/User.pm
@@ -19,15 +19,15 @@ sub friendly_name {
sub current_user_can {
my $self = shift;
my $type = shift;
- my $column = shift;
+ my %args = @_;
if ( $type eq 'read' ) {
- return 1 if $column eq 'name';
+ return 1 if $args{'column'} eq 'name';
my $cu = $self->current_user;
return 1 if $self->id && ($cu->id||0) == $self->id;
}
- return $self->SUPER::current_user_can($type, $column, @_);
+ return $self->SUPER::current_user_can($type, %args);
}
1;
commit 479cadd8201860e24e5c905714807b796afa7acd
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Sun Dec 7 03:09:31 2008 +0000
tweak css a little, history is ul now, inline region
diff --git a/share/web/static/css/app.css b/share/web/static/css/app.css
index 290e344..9f95109 100644
--- a/share/web/static/css/app.css
+++ b/share/web/static/css/app.css
@@ -51,14 +51,24 @@ form .submit_button input {
* html .pagelist dt,
* html .pagelist dd { position: relative; }
-#history dd {
+#history {
+ margin-left: 0em;
+ padding-left: 1em;
+ list-style-type: none;
+}
+
+#history li {
font-size: 0.95em;
- color: #444;
- margin-left: 2em;
+ margin-left: 0em;
padding-left: 0;
margin-bottom: 0.5em;
}
+#history div.jifty-region {
+ display: inline;
+ margin-left: 0.5em;
+}
+
hr {
border: none;
border-top: 1px solid #777;
commit 653e80cf9acdff9e3d69200f1a0fa8282fdc49cc
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Sun Dec 7 06:29:39 2008 +0000
add revision method in the Page model
diff --git a/lib/Wifty/Model/Page.pm b/lib/Wifty/Model/Page.pm
index 5064538..14f7c8d 100644
--- a/lib/Wifty/Model/Page.pm
+++ b/lib/Wifty/Model/Page.pm
@@ -104,6 +104,18 @@ sub _set {
return ( $val, $msg );
}
+sub revision {
+ my $self = shift;
+ my $rev = shift;
+ return undef unless $self->id;
+ return undef unless $rev;
+
+ my $res = new Wifty::Model::Revision;
+ $res->load_by_cols( page => $self->id, id => $rev );
+ return undef unless $res->id;
+ return $res;
+}
+
=head2 current_user_can ACTION
commit f7f851aafb676582fcf6e2931b792ecaa44c23ff
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Sun Dec 7 06:31:56 2008 +0000
tidy revision model
diff --git a/lib/Wifty/Model/Revision.pm b/lib/Wifty/Model/Revision.pm
index 88fd8eb..85ea17a 100644
--- a/lib/Wifty/Model/Revision.pm
+++ b/lib/Wifty/Model/Revision.pm
@@ -9,12 +9,20 @@ use base qw/Wifty::Record/;
use Jifty::DBI::Schema;
use Jifty::Record schema {
-column page => refers_to Wifty::Model::Page;
-
-column content => type is 'text', render_as 'Wifty::Form::Field::WikiPage';
-
-column created => type is 'timestamp';
-column created_by => refers_to Wifty::Model::User, since '0.0.20';
+ column page =>
+ refers_to Wifty::Model::Page
+ ;
+ column content =>
+ type is 'text',
+ render_as 'Wifty::Form::Field::WikiPage'
+ ;
+ column created =>
+ type is 'timestamp'
+ ;
+ column created_by =>
+ refers_to Wifty::Model::User,
+ since '0.0.20'
+ ;
};
@@ -77,7 +85,7 @@ sub next {
quote_value => 0,
case_sensitive => 1
);
- $revisions->order_by( { column => 'id' } );
+ $revisions->order_by( { column => 'id', order => 'asc' } );
$revisions->rows_per_page(1);
return $revisions->first;
}
commit e9c49491a59d77ac75f69f95ba1c86eced6df9de
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Sun Dec 7 06:33:04 2008 +0000
don't use diff with navigation
* fix diff shower
diff --git a/lib/Wifty/View.pm b/lib/Wifty/View.pm
index 35ad043..b217a27 100644
--- a/lib/Wifty/View.pm
+++ b/lib/Wifty/View.pm
@@ -6,12 +6,10 @@ use Jifty::View::Declare -base;
template 'view' => page {
my ( $page, $revision, $viewer ) = get(qw(page revision viewer));
- my $rev = $revision->id;
- my $title = $rev
+ my $title = $revision->id
? _('%1 as of %2', $page->name, $revision->created)
: $page->name;
{ title is $title }
- show( 'diff/with_nav', page => $page, to => $revision ) if $rev;
render_param($viewer => 'content', label => '', render_mode => 'read');
};
@@ -266,10 +264,10 @@ template 'helpers/diff' => sub {
private template 'diff' => sub {
my ($from, $to) = get(qw(from to));
- if ( $to && !$from ) {
+ if ( $to && !($from && $from->id) ) {
$from = $to->previous;
}
- elsif ( !$to && $from ) {
+ elsif ( !($to && $to->id) && $from ) {
$to = $from->next;
}
commit 6986699ce72ca26a8a44db54fa9b020d16d36d93
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Sun Dec 7 06:34:10 2008 +0000
add history navigation into page nav
diff --git a/lib/Wifty/Dispatcher.pm b/lib/Wifty/Dispatcher.pm
index fa9e563..c6729fc 100644
--- a/lib/Wifty/Dispatcher.pm
+++ b/lib/Wifty/Dispatcher.pm
@@ -33,25 +33,23 @@ on '/create/*', run {
# View or edit a page
on qr{^/(view|edit)/(.*)}, run {
- my ( $name, $rev );
my $page_name = $1;
- if ( $2 =~ qr{^(.*?)(?:/(\d+))?$} ) {
- $name = $1;
- $rev = $2;
- }
+ my ( $name, $rev ) = ($2 =~ qr{^(.*?)(?:/(\d+))?$});
+
my $page = Wifty::Model::Page->new();
$page->load_by_cols( name => $name );
- Jifty->web->redirect( '/create/' . $name ) unless ( $page->id );
+ Jifty->web->redirect( '/create/' . $name )
+ unless $page->id;
+
+ $rev = $page->revision($rev);
- setup_page_nav($name, $rev);
+ setup_page_nav($page_name, $page, $rev);
- my $revision = Wifty::Model::Revision->new();
- $revision->load_by_cols( page => $page->id, id => $rev ) if ($rev);
set page => $page;
- set revision => $revision;
+ set revision => $rev || new Wifty::Model::Revision;
my $viewer = Jifty->web->new_action( class => 'UpdatePage', record => $page );
- if($rev) {
- $viewer->argument_value(content => $revision->content);
+ if ( $rev ) {
+ $viewer->argument_value(content => $rev->content);
}
set viewer => $viewer;
show("/$page_name");
@@ -64,7 +62,7 @@ on 'history/*', run {
$page->load_by_cols( name => $name );
redirect( '/create/' . $name ) unless ( $page->id );
- setup_page_nav($name);
+ setup_page_nav('view', $page);
my $revisions = $page->revisions;
$revisions->order_by( column => 'id', order => 'desc');
@@ -108,14 +106,23 @@ on 'recent*', run {
};
sub setup_page_nav {
- my ($page, $rev) = @_;
+ my ($prefix, $page, $rev) = @_;
- my $subpath = $page . ($rev ? "/$rev" : '');
+ my $name = $page->name;
+
+ my $subpath = $name;
+ $subpath .= '/'. $rev->id if $rev;
my $top = Jifty->web->page_navigation;
- $top->child( View => url => '/view/'.$subpath);
- $top->child( Edit => url => '/edit/'.$subpath);
- $top->child( History => url => '/history/'.$page);
- $top->child( Latest => url => '/view/'.$page) if $rev;
+ $top->child( View => url => '/view/'. $subpath);
+ $top->child( Edit => url => '/edit/'. $subpath);
+ if ( my $prev = ($rev? $rev : $page->revisions->last)->previous ) {
+ $top->child( Older => url => join '/', '', $prefix, $name, $prev->id );
+ }
+ $top->child( History => url => '/history/'. $name);
+ if ( $rev and my $next = $rev->next ) {
+ $top->child( Newer => url => join '/', '', $prefix, $name, $next->id );
+ $top->child( Latest => url => join '/', '', $prefix, $name );
+ }
}
1;
commit f99d814b7957c174f37bf87cd460d1824d284706
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Sun Dec 7 06:43:09 2008 +0000
localize nav labels
diff --git a/lib/Wifty/Dispatcher.pm b/lib/Wifty/Dispatcher.pm
index c6729fc..0de9625 100644
--- a/lib/Wifty/Dispatcher.pm
+++ b/lib/Wifty/Dispatcher.pm
@@ -8,9 +8,9 @@ under '/', run {
before '*', run {
my $top = Jifty->web->navigation;
- $top->child( Home => url => "/", sort_order => 1 );
- $top->child( Recent => url => "/recent", label => "Recent Changes", sort_order => 2 );
- $top->child( Search => url => "/search", label => "Search", sort_order => 3 );
+ $top->child( Home => url => "/", label => _("Home") );
+ $top->child( Recent => url => "/recent", label => _("Recent Changes") );
+ $top->child( Search => url => "/search", label => _("Search") );
};
# Default page
@@ -113,15 +113,27 @@ sub setup_page_nav {
my $subpath = $name;
$subpath .= '/'. $rev->id if $rev;
my $top = Jifty->web->page_navigation;
- $top->child( View => url => '/view/'. $subpath);
- $top->child( Edit => url => '/edit/'. $subpath);
+ $top->child( View => url => '/view/'. $subpath, label => _('View') );
+ $top->child( Edit => url => '/edit/'. $subpath, label => _('Edit') );
if ( my $prev = ($rev? $rev : $page->revisions->last)->previous ) {
- $top->child( Older => url => join '/', '', $prefix, $name, $prev->id );
+ $top->child(
+ Older => label => _('Previous Version'),
+ url => join '/', '', $prefix, $name, $prev->id
+ );
}
- $top->child( History => url => '/history/'. $name);
+ $top->child(
+ History => label => _('History'),
+ url => '/history/'. $name
+ );
if ( $rev and my $next = $rev->next ) {
- $top->child( Newer => url => join '/', '', $prefix, $name, $next->id );
- $top->child( Latest => url => join '/', '', $prefix, $name );
+ $top->child(
+ Newer => label => _('Next Version'),
+ url => join '/', '', $prefix, $name, $next->id
+ );
+ $top->child(
+ Latest => label => _('Latest'),
+ url => join '/', '', $prefix, $name
+ );
}
}
commit 7104f615413aa41347005eb9ce37955e129a8c8b
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Sun Dec 7 07:07:44 2008 +0000
add ru.po
diff --git a/share/po/ru.po b/share/po/ru.po
new file mode 100644
index 0000000..f0c04e7
--- /dev/null
+++ b/share/po/ru.po
@@ -0,0 +1,116 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL at ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Ruslan Zakirov <ruz at bestpractical.com>\n"
+"Language-Team: LANGUAGE <LL at li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: lib/Wifty/View-not-ready-yet.pm:59
+msgid " name is 'robots', content is 'all' "
+msgstr ""
+
+#: lib/Wifty/View-not-ready-yet.pm:58
+msgid ""
+" xmlns is \"http://www.w3.org/1999/xhtml\", xml__lang is \"en\" }\n"
+" head {\n"
+" meta {{ http_equiv is \"content-type\", content is \"text/html; charset=utf-8\" "
+msgstr ""
+
+#: lib/Wifty/View.pm:10
+#. ($page->name, $revision->created)
+msgid "%1 as of %2"
+msgstr "%1 Ð¾Ñ %2"
+
+#: lib/Wifty/View.pm:88
+#. (length $rev->content)
+msgid "%1 bytes"
+msgstr "%1 байÑ"
+
+#: lib/Wifty/Model/User.pm:15
+msgid "Anonymous"
+msgstr "Ðноним"
+
+#: lib/Wifty/View.pm:21
+msgid "Edit page %1"
+msgstr "ÐзмениÑÑ ÑÑÑаниÑÑ %1"
+
+#: lib/Wifty/View.pm:20
+#. ($page->name, $revision->created)
+msgid "Edit page %1 as of %2"
+msgstr "ÐзмениÑÑ ÑÑÑаниÑÑ %1 Ð¾Ñ %2"
+
+#: lib/Wifty/View.pm:116
+msgid "Find pages containing:"
+msgstr "ÐайÑи ÑÑÑаниÑÑ ÑодеÑжаÑие:"
+
+#: lib/Wifty/Dispatcher.pm:125
+msgid "History"
+msgstr "ÐÑÑоÑиÑ"
+
+#: lib/Wifty/Dispatcher.pm:134
+msgid "Latest"
+msgstr "ÐоÑледнÑÑ"
+
+#: lib/Wifty/View.pm:46
+#. ($page)
+msgid "New page '%1'"
+msgstr "ÐÐ¾Ð²Ð°Ñ ÑÑÑаниÑа '%1'"
+
+#: lib/Wifty/Dispatcher.pm:130
+msgid "Next Version"
+msgstr "СледÑÑÑÐ°Ñ ÐеÑÑиÑ"
+
+#: lib/Wifty/View.pm:305
+msgid "Next revision"
+msgstr ""
+
+#: lib/Wifty/View.pm:66
+#. ($page)
+msgid "No '%1' page"
+msgstr "ÐÐµÑ ÑÑÑаниÑÑ '%1'"
+
+#: lib/Wifty/Dispatcher.pm:120
+msgid "Previous Version"
+msgstr "ÐÑедÑдÑÑÐ°Ñ Ð²ÐµÑÑиÑ"
+
+#: lib/Wifty/View.pm:296
+msgid "Previous revision"
+msgstr ""
+
+#: lib/Wifty/Dispatcher.pm:12
+msgid "Recent Changes"
+msgstr "ÐоÑледние ÐзменениÑ"
+
+#: lib/Wifty/View.pm:107
+msgid "These are the pages on your wiki!"
+msgstr "ÐÑе ÑÑÑаниÑÑ Ð½Ð° ÑÑой wiki!"
+
+#: lib/Wifty/View.pm:100
+msgid "Updated this week"
+msgstr "ÐÐ±Ð½Ð¾Ð²Ð»ÐµÐ½Ñ Ð½Ð° ÑÑой неделе"
+
+#: lib/Wifty/Dispatcher.pm:116
+msgid "View"
+msgstr "ÐÑоÑмоÑÑеÑÑ"
+
+#: lib/Wifty/View.pm:159
+msgid "Wiki Syntax Help"
+msgstr "СпÑавка по wiki-ÑинÑакÑиÑÑ"
+
+#: lib/Wifty/View.pm:244
+msgid "hide diff"
+msgstr "ÑкÑÑÑÑ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ"
+
+#: lib/Wifty/View.pm:244
+msgid "show diff"
+msgstr "показаÑÑ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ"
diff --git a/share/po/wifty.pot b/share/po/wifty.pot
new file mode 100644
index 0000000..639125f
--- /dev/null
+++ b/share/po/wifty.pot
@@ -0,0 +1,116 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL at ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
+"Language-Team: LANGUAGE <LL at li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: lib/Wifty/View-not-ready-yet.pm:59
+msgid " name is 'robots', content is 'all' "
+msgstr ""
+
+#: lib/Wifty/View-not-ready-yet.pm:58
+msgid ""
+" xmlns is \"http://www.w3.org/1999/xhtml\", xml__lang is \"en\" }\n"
+" head {\n"
+" meta {{ http_equiv is \"content-type\", content is \"text/html; charset=utf-8\" "
+msgstr ""
+
+#: lib/Wifty/View.pm:10
+#. ($page->name, $revision->created)
+msgid "%1 as of %2"
+msgstr ""
+
+#: lib/Wifty/View.pm:88
+#. (length $rev->content)
+msgid "%1 bytes"
+msgstr ""
+
+#: lib/Wifty/Model/User.pm:15
+msgid "Anonymous"
+msgstr ""
+
+#: lib/Wifty/View.pm:21
+msgid "Edit page %1"
+msgstr ""
+
+#: lib/Wifty/View.pm:20
+#. ($page->name, $revision->created)
+msgid "Edit page %1 as of %2"
+msgstr ""
+
+#: lib/Wifty/View.pm:116
+msgid "Find pages containing:"
+msgstr ""
+
+#: lib/Wifty/Dispatcher.pm:125
+msgid "History"
+msgstr ""
+
+#: lib/Wifty/Dispatcher.pm:134
+msgid "Latest"
+msgstr ""
+
+#: lib/Wifty/View.pm:46
+#. ($page)
+msgid "New page '%1'"
+msgstr ""
+
+#: lib/Wifty/Dispatcher.pm:130
+msgid "Next Version"
+msgstr ""
+
+#: lib/Wifty/View.pm:305
+msgid "Next revision"
+msgstr ""
+
+#: lib/Wifty/View.pm:66
+#. ($page)
+msgid "No '%1' page"
+msgstr ""
+
+#: lib/Wifty/Dispatcher.pm:120
+msgid "Previous Version"
+msgstr ""
+
+#: lib/Wifty/View.pm:296
+msgid "Previous revision"
+msgstr ""
+
+#: lib/Wifty/Dispatcher.pm:12
+msgid "Recent Changes"
+msgstr ""
+
+#: lib/Wifty/View.pm:107
+msgid "These are the pages on your wiki!"
+msgstr ""
+
+#: lib/Wifty/View.pm:100
+msgid "Updated this week"
+msgstr ""
+
+#: lib/Wifty/Dispatcher.pm:116
+msgid "View"
+msgstr ""
+
+#: lib/Wifty/View.pm:159
+msgid "Wiki Syntax Help"
+msgstr ""
+
+#: lib/Wifty/View.pm:244
+msgid "hide diff"
+msgstr ""
+
+#: lib/Wifty/View.pm:244
+msgid "show diff"
+msgstr ""
commit 5e43509b70b1f30636106c36747c72070c4541f5
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Fri Dec 12 16:25:31 2008 +0000
add feeds
diff --git a/lib/Wifty/View/Feeds.pm b/lib/Wifty/View/Feeds.pm
new file mode 100644
index 0000000..919b9e2
--- /dev/null
+++ b/lib/Wifty/View/Feeds.pm
@@ -0,0 +1,61 @@
+use warnings;
+use strict;
+
+package Wifty::View::Feeds;
+use Jifty::View::Declare -base;
+
+use XML::Atom::SimpleFeed;
+use Data::UUID;
+
+# XXX: don't know how to redispatch to private template
+# right from dispatcher
+template 'atom/recent' => sub {
+ set(type => 'full');
+ show('../atom');
+};
+
+template 'atom/recent/diff' => sub {
+ set(type => 'diff');
+ show('../../atom');
+};
+
+template 'atom/recent/headlines' => sub {
+ set(type => 'headlines');
+ show('../../atom');
+};
+
+# XXX: id rendering is not correct
+private template 'atom' => sub {
+ my ($pages, $type) = get(qw(pages type));
+ my $wikiname = Jifty->config->app('WikiName');
+ my $title = $wikiname
+ ? _('Recently changed pages on %1 wiki', $wikiname)
+ : _('Recently changed pages on some wiki');
+ my $feed = XML::Atom::SimpleFeed->new(
+ title => $title,
+ link => Jifty->web->url,
+ id => 'urn:uuid:' . Data::UUID->new->create_str()
+ );
+
+ while ( my $page = $pages->next ) {
+ my $summary = '';
+ if ( !$type || $type eq 'full' ) {
+ $summary = $page->viewer->form_field('content')->wiki_content;
+ }
+ elsif ( $type eq 'diff' ) {
+ $summary = '<pre>'. $page->revisions->last->diff_from .'</pre>';
+ }
+
+ $feed->add_entry(
+ id => 'urn:uuid:' . Data::UUID->new->create_str(),
+ link => Jifty->web->url . '/view/' . $page->name,
+ title => $page->name,
+ author => $page->updated_by->friendly_name,
+ updated => $page->updated,
+ summary => $summary,
+ );
+ }
+ $feed->print;
+};
+
+1;
commit 7e08edf853ed4854de3a5eaf86295aa95d97177a
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Fri Dec 12 16:27:37 2008 +0000
add feeds to the dispatcher
diff --git a/lib/Wifty/Dispatcher.pm b/lib/Wifty/Dispatcher.pm
index 0de9625..babb431 100644
--- a/lib/Wifty/Dispatcher.pm
+++ b/lib/Wifty/Dispatcher.pm
@@ -93,17 +93,24 @@ on 'search', run {
};
# Show recent edits
+under 'feeds/atom/recent', run {
+ set pages => recent_changes();
+};
on 'recent*', run {
+ set pages => recent_changes();
+};
+
+sub recent_changes {
my $then = DateTime->from_epoch( epoch => ( time - ( 86400 * 7 ) ) );
my $pages = Wifty::Model::PageCollection->new();
$pages->limit(
column => 'updated',
operator => '>',
- value => $then->ymd
+ value => $then->ymd,
);
$pages->order_by( column => 'updated', order => 'desc' );
- set pages => $pages;
-};
+ return $pages;
+}
sub setup_page_nav {
my ($prefix, $page, $rev) = @_;
commit 8325a813f926d38ff4663a4d1af9dd2c0a9e66bf
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Fri Dec 12 16:29:33 2008 +0000
add viewer methods in Revison and Page models
* add diff_to, diff_from, _diff methods in Revision model
diff --git a/lib/Wifty/Model/Page.pm b/lib/Wifty/Model/Page.pm
index 14f7c8d..5612125 100644
--- a/lib/Wifty/Model/Page.pm
+++ b/lib/Wifty/Model/Page.pm
@@ -85,6 +85,11 @@ sub set_content {
return ( $val, $msg );
}
+sub viewer {
+ my $self = shift;
+ return Jifty->web->new_action( class => 'UpdatePage', record => $self );
+}
+
sub _set {
my $self = shift;
my ( $val, $msg ) = $self->SUPER::_set(@_);
diff --git a/lib/Wifty/Model/Revision.pm b/lib/Wifty/Model/Revision.pm
index 85ea17a..53413bd 100644
--- a/lib/Wifty/Model/Revision.pm
+++ b/lib/Wifty/Model/Revision.pm
@@ -90,6 +90,42 @@ sub next {
return $revisions->first;
}
+sub diff_from {
+ my $to = shift;
+ my $from = shift;
+ unless ( $from && $from->id ) {
+ $from = $to->previous;
+ }
+ return $to->_diff( $from, $to, @_ );
+}
+
+sub diff_to {
+ my $from = shift;
+ my $to = shift;
+ unless ( $to && $to->id ) {
+ $to = $from->next;
+ }
+ return $from->_diff( $from, $to, @_ );
+}
+
+sub _diff {
+ my $self = shift;
+ my ($from, $to, %opt) = @_;
+ require Text::Diff;
+ return Text::Diff::diff(
+ \( $from? $from->content : '' ),
+ \( $to ? $to->content : '' ),
+ { STYLE => 'Text::Diff::HTML', %opt }
+ );
+}
+
+sub viewer {
+ my $self = shift;
+ my $viewer = $self->page->viewer;
+ $viewer->argument_value( content => $self->content );
+ return $viewer;
+}
+
=head2 current_user_can RIGHT
We're using L<Jifty::RightsFrom> to pass off ACL decisions to this
commit 95819ea95b2b0d0503108fc46a74019d2654c66b
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Fri Dec 12 16:31:21 2008 +0000
use a new viewer method
diff --git a/lib/Wifty/Dispatcher.pm b/lib/Wifty/Dispatcher.pm
index babb431..6aae73e 100644
--- a/lib/Wifty/Dispatcher.pm
+++ b/lib/Wifty/Dispatcher.pm
@@ -47,11 +47,7 @@ on qr{^/(view|edit)/(.*)}, run {
set page => $page;
set revision => $rev || new Wifty::Model::Revision;
- my $viewer = Jifty->web->new_action( class => 'UpdatePage', record => $page );
- if ( $rev ) {
- $viewer->argument_value(content => $rev->content);
- }
- set viewer => $viewer;
+ set viewer => $rev? $rev->viewer: $page->viewer;
show("/$page_name");
};
commit bc2cd450cdf18caa8049f5cd6df1e4ffef069e5f
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Fri Dec 12 16:33:27 2008 +0000
use ViewDeclarePage
diff --git a/etc/config.yml b/etc/config.yml
index 2ca00cd..23e9ace 100644
--- a/etc/config.yml
+++ b/etc/config.yml
@@ -19,6 +19,7 @@ framework:
- User: {}
- Authentication::Password:
login_by: email
+ - ViewDeclarePage: {}
Mailer: IO
MailerArgs:
commit e990591a50d79a58869533678a53a5780e69606d
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Fri Dec 12 16:34:44 2008 +0000
switch to new page plugin
diff --git a/lib/Wifty/View.pm b/lib/Wifty/View.pm
index b217a27..11e2482 100644
--- a/lib/Wifty/View.pm
+++ b/lib/Wifty/View.pm
@@ -130,24 +130,6 @@ private template 'search_box' => sub {
} };
};
-private template 'menu' => sub {
- my $wikiname = Jifty->config->app('WikiName') || "Wifty";
- h1 { attr { id is 'wikiname' }
- Jifty->web->link( url => "/", label => _($wikiname) )
- }
- div { attr { id => "navigation" };
- Jifty->web->navigation->render_as_menu;
- };
-};
-
-private template 'heading_in_wrapper' => sub {
- h1 { attr { class => 'title' }; outs_raw(get('title')) };
- Jifty->web->page_navigation->render_as_menu;
-# show('/search_box');
- hr { {class is 'clear'} }
-};
-
-
private template markup => sub {
return undef unless Jifty->config->app('Formatter') eq 'Markdown';
diff --git a/lib/Wifty/View/Page.pm b/lib/Wifty/View/Page.pm
index c896e24..971a6dd 100644
--- a/lib/Wifty/View/Page.pm
+++ b/lib/Wifty/View/Page.pm
@@ -2,20 +2,43 @@ use strict;
use warnings;
package Wifty::View::Page;
-use base qw(Jifty::View::Declare::Page);
+use base qw(Jifty::Plugin::ViewDeclarePage::Page);
use Jifty::View::Declare::Helpers;
-sub render_body {
- my ($self, $body_code) = @_;
+sub render_page {
+ my $self = shift;
- my $logo = Jifty->config->app('Logo');
- return $self->SUPER::render_body( $body_code ) unless $logo;
+ if ( my $logo = Jifty->config->app('Logo') ) {
+ div { attr { id is "logo" }
+ img { src is $logo, alt is '' }
+ };
+ }
- return $self->SUPER::render_body( sub {
- div { attr { id is "logo" } img { src is $logo, alt is '' } };
- $body_code->();
- });
+ return $self->SUPER::render_page( @_ );
}
-1;
+sub render_navigation {
+ my $self = shift;
+ my $wikiname = Jifty->config->app('WikiName') || "Wifty";
+ h1 { attr { id is 'wikiname' }
+ Jifty->web->link( url => "/", label => _($wikiname) )
+ };
+ return $self->SUPER::render_navigation( @_ );
+}
+
+sub render_title_inhead {
+ my $self = shift;
+ my $title = shift;
+ my $wikiname = Jifty->config->app('WikiName') || "Wifty";
+ return $self->SUPER::render_title_inhead( $title .' - '. $wikiname );
+}
+sub render_title_inpage {
+ my $self = shift;
+ $self->SUPER::render_title_inpage( @_ );
+# show('/search_box');
+ hr { {class is 'clear'} };
+ return '';
+}
+
+1;
commit b2aaa93d9ed91c1518a5c3e90d8ab874add3eb23
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Fri Dec 12 16:36:04 2008 +0000
actually bind feeds into templates tree
diff --git a/lib/Wifty/View.pm b/lib/Wifty/View.pm
index 11e2482..f364257 100644
--- a/lib/Wifty/View.pm
+++ b/lib/Wifty/View.pm
@@ -4,6 +4,9 @@ use strict;
package Wifty::View;
use Jifty::View::Declare -base;
+require Wifty::View::Feeds;
+alias Wifty::View::Feeds under 'feeds/';
+
template 'view' => page {
my ( $page, $revision, $viewer ) = get(qw(page revision viewer));
my $title = $revision->id
commit 242972de5867c1f1cab8b4b5c8d6a17bf382086a
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Fri Dec 12 16:37:01 2008 +0000
use new *diff* methods
diff --git a/lib/Wifty/View.pm b/lib/Wifty/View.pm
index f364257..f5f6965 100644
--- a/lib/Wifty/View.pm
+++ b/lib/Wifty/View.pm
@@ -249,20 +249,14 @@ template 'helpers/diff' => sub {
private template 'diff' => sub {
my ($from, $to) = get(qw(from to));
- if ( $to && !($from && $from->id) ) {
- $from = $to->previous;
+ if ( $to && $to->id ) {
+ pre {{ class is 'diff' } outs_raw( $to->diff_from( $from ) ) };
}
- elsif ( !($to && $to->id) && $from ) {
- $to = $from->next;
+ elsif ( $from && $from->id ) {
+ pre {{ class is 'diff' } outs_raw( $from->diff_to( $to ) ) };
+ } else {
+ die "illegal arguments for diff";
}
-
- use Text::Diff ();
- my $diff = Text::Diff::diff(
- \( $from? $from->content : '' ),
- \( $to ? $to->content : '' ),
- { STYLE => 'Text::Diff::HTML' }
- );
- pre {{ class is 'diff' } outs_raw($diff) };
};
private template 'diff/with_nav' => sub {
commit 0626e65376935833579275e48c7aa119fd50f38e
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Fri Dec 12 16:44:22 2008 +0000
announce atom feeds right from the page
diff --git a/lib/Wifty/View.pm b/lib/Wifty/View.pm
index f5f6965..2d6d83e 100644
--- a/lib/Wifty/View.pm
+++ b/lib/Wifty/View.pm
@@ -101,8 +101,23 @@ template history => page {
template recent => page {
my ($pages) = get(qw(pages));
{ title is _('Updated this week') }
-
show( 'page_list', pages => $pages, id => 'recentupdates' );
+
+ add rel "alternate",
+ type => "application/atom+xml",
+ title => _('Updated this week') .' '. _('(full content)'),
+ href => '/feeds/atom/recent',
+ ;
+ add rel "alternate",
+ type => "application/atom+xml",
+ title => _('Updated this week') .' '. _('(headlines)'),
+ href => '/feeds/atom/recent/headlines',
+ ;
+ add rel "alternate",
+ type => "application/atom+xml",
+ title => _('Updated this week') .' '. _('(diffs)'),
+ href => '/feeds/atom/recent/diffs',
+ ;
};
template pages => page {
commit 3457f38ce9b666aab2f8a27e636a93a8ba7995ad
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Sat Dec 13 23:37:00 2008 +0000
add created/created_by on Page
diff --git a/etc/config.yml b/etc/config.yml
index 23e9ace..7f93d3a 100644
--- a/etc/config.yml
+++ b/etc/config.yml
@@ -10,7 +10,7 @@ framework:
Driver: SQLite
Host: localhost
User: postgres
- Version: 0.0.20
+ Version: 0.0.21
Password: ''
RequireSSL: 0
Plugins:
diff --git a/lib/Wifty/Model/Page.pm b/lib/Wifty/Model/Page.pm
index 5612125..4a53260 100644
--- a/lib/Wifty/Model/Page.pm
+++ b/lib/Wifty/Model/Page.pm
@@ -30,6 +30,14 @@ column updated_by =>
refers_to Wifty::Model::User,
since '0.0.16';
+column created =>
+ type is 'timestamp',
+ since '0.0.21';
+
+column created_by =>
+ refers_to Wifty::Model::User,
+ since '0.0.21';
+
column revisions =>
refers_to Wifty::Model::RevisionCollection by 'page';
};
@@ -38,8 +46,9 @@ sub create {
my $self = shift;
my %args = (@_);
my $now = DateTime->now();
- $args{'updated'} = $now->ymd . " " . $now->hms;
- $args{'updated_by'} = ( $self->current_user? $self->current_user->user_object : undef );
+ $args{'created'} = $args{'updated'} = $now->ymd . " " . $now->hms;
+ $args{'created_by'} = $args{'updated_by'}
+ = $self->current_user? $self->current_user->user_object : undef;
my ($id) = $self->SUPER::create(%args);
if ( $self->id ) {
$self->_add_revision(%args);
diff --git a/lib/Wifty/Upgrade.pm b/lib/Wifty/Upgrade.pm
new file mode 100644
index 0000000..b59f1f2
--- /dev/null
+++ b/lib/Wifty/Upgrade.pm
@@ -0,0 +1,24 @@
+use strict;
+use warnings;
+
+package Wifty::Upgrade;
+
+use base qw(Jifty::Upgrade);
+use Jifty::Upgrade qw( since rename );
+
+since '0.0.21' => sub {
+ my $pages = Wifty::Model::PageCollection->new(
+ current_user => Jifty->app_class('CurrentUser')->superuser
+ );
+ $pages->unlimit;
+
+ while ( my $page = $pages->next ) {
+ my $first_rev = $page->revisions->first;
+ my ($status, $msg) = $page->set_created( $first_rev? $first_rev->created : $page->updated );
+ Jifty->log->error("Couldn't set created:". $msg) unless $status;
+ ($status, $msg) = $page->set_created_by( $first_rev? $first_rev->created_by : $page->updated_by );
+ Jifty->log->error("Couldn't set created_by:". $msg) unless $status;
+ }
+};
+
+1;
commit db0f755504f7075318ef87fd37a052e25ff239a4
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Sat Dec 13 23:38:07 2008 +0000
add PageCollection class with recently_{updated,created}
diff --git a/lib/Wifty/Model/PageCollection.pm b/lib/Wifty/Model/PageCollection.pm
new file mode 100644
index 0000000..22899c5
--- /dev/null
+++ b/lib/Wifty/Model/PageCollection.pm
@@ -0,0 +1,26 @@
+use strict;
+use warnings;
+
+package Wifty::Model::PageCollection;
+use base qw(Jifty::Collection);
+
+sub recently_created { return (shift)->_recently('created', @_) }
+sub recently_updated { return (shift)->_recently('updated', @_) }
+
+sub _recently {
+ my $proto = shift;
+ my $self = ref($proto)? $proto : new $proto;
+ my $column = shift;
+ my $time = shift || 7*24*60*60;
+
+ my $then = DateTime->from_epoch( epoch => time - $time );
+ $self->limit(
+ column => $column,
+ operator => '>',
+ value => $then->ymd,
+ );
+ $self->order_by( column => $column, order => 'desc' );
+ return $self;
+}
+
+1;
commit 4e1a1c55e3e936fec8717856e81e4d83ee2dceb9
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Sat Dec 13 23:39:17 2008 +0000
remove old experimental View, we have all this implemented
diff --git a/lib/Wifty/View-not-ready-yet.pm b/lib/Wifty/View-not-ready-yet.pm
deleted file mode 100644
index 04a4022..0000000
--- a/lib/Wifty/View-not-ready-yet.pm
+++ /dev/null
@@ -1,69 +0,0 @@
-use warnings;
-use strict;
-
-
-=head1 NAME
-
-Wifty::View
-
-=head1 DESCRIPTION
-
-This code is only useful on the new Jifty "Declarative tempaltes" branch. It shouldn't get in the way
-if you're running a traditional (0.610 or before) Jifty.
-
-=cut
-
-package Wifty::View;
-use base qw/Jifty::View::Declare::Templates/;
-# includes my application's plugins' View libraries as superclasses.
-use Template::Declare::Tags;
-use Jifty::View::Declare::Templates;
-
-template recent_atom => sub {
- my ( $pages) = get(qw(pages));
- use XML::Atom::SimpleFeed;
- use Data::UUID;
- my $feed = XML::Atom::SimpleFeed->new(
- title => 'Recently changed pages',
- link => Jifty->web->url,
- updated => '2009-12-31T00:00:00Z',
- author => 'John Doe',
- id => 'urn:uuid:' . Data::UViewD->new->create_str()
- );
-
- while ( my $page = $pages->next ) {
-
- $feed->add_entry(
- title => $page->name,
- link => Jifty->web->url . '/view/' . $page->name,
- id => 'urn:uuid:' . Data::UViewD->new->create_str(),
- summary => $page->content,
- updated => $page->updated
- );
- }
- $feed->print;
-};
-
-private template header => sub {
- my %args = ( title=> undef, wikiname => undef, @_);
-
- my ( $title, $wikiname ) = ($args{'title'}, $args{'wikiname'});
- # $HTML::Mason::r->content_type('text/html; charset=utf-8');
- outs(
- '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'
- );
-
- html {{ xmlns is "http://www.w3.org/1999/xhtml", xml__lang is "en" }
- head {
- meta {{ http_equiv is "content-type", content is "text/html; charset=utf-8" }};
- meta {{ name is 'robots', content is 'all' }};
- title { _($title) . ' - ' . _($wikiname) };
-
- Jifty->web->include_css;
- Jifty->web->include_javascript;
-
- }
- }
-};
-
-1;
commit bd2178cad7872d7c3f2ef214d08eefbee0a333a4
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Sat Dec 13 23:43:31 2008 +0000
split recent into recent changes and recent additions
diff --git a/lib/Wifty/Dispatcher.pm b/lib/Wifty/Dispatcher.pm
index 6aae73e..1c30dc4 100644
--- a/lib/Wifty/Dispatcher.pm
+++ b/lib/Wifty/Dispatcher.pm
@@ -8,9 +8,10 @@ under '/', run {
before '*', run {
my $top = Jifty->web->navigation;
- $top->child( Home => url => "/", label => _("Home") );
- $top->child( Recent => url => "/recent", label => _("Recent Changes") );
- $top->child( Search => url => "/search", label => _("Search") );
+ $top->child( Home => url => "/", label => _("Home") );
+ $top->child( Recent => url => "/recent/changes", label => _("Recent Changes") );
+ $top->child( New => url => "/recent/additions", label => _("New") );
+ $top->child( Search => url => "/search", label => _("Search") );
};
# Default page
@@ -88,26 +89,31 @@ on 'search', run {
set pages => $collection;
};
-# Show recent edits
+# Show recent
+
+# backwards compat
+on '/recent', run {
+ redirect('/recent/changes');
+};
+
under 'feeds/atom/recent', run {
set pages => recent_changes();
};
-on 'recent*', run {
- set pages => recent_changes();
+on qr{^/recent/(.+)}, run {
+ my $type = $1;
+ if ( $type eq 'changes' ) {
+ set title => _('Updated this week');
+ set pages => Wifty::Model::PageCollection->recently_updated;
+ } elsif ( $type eq 'additions' ) {
+ set title => _('Created this week');
+ set pages => Wifty::Model::PageCollection->recently_created;
+ } else {
+ redirect('/recent/changes');
+ }
+ set( type => $type );
+ show('/recent');
};
-sub recent_changes {
- my $then = DateTime->from_epoch( epoch => ( time - ( 86400 * 7 ) ) );
- my $pages = Wifty::Model::PageCollection->new();
- $pages->limit(
- column => 'updated',
- operator => '>',
- value => $then->ymd,
- );
- $pages->order_by( column => 'updated', order => 'desc' );
- return $pages;
-}
-
sub setup_page_nav {
my ($prefix, $page, $rev) = @_;
diff --git a/lib/Wifty/View.pm b/lib/Wifty/View.pm
index 2d6d83e..2d45c61 100644
--- a/lib/Wifty/View.pm
+++ b/lib/Wifty/View.pm
@@ -99,25 +99,14 @@ template history => page {
};
template recent => page {
- my ($pages) = get(qw(pages));
- { title is _('Updated this week') }
- show( 'page_list', pages => $pages, id => 'recentupdates' );
-
- add rel "alternate",
- type => "application/atom+xml",
- title => _('Updated this week') .' '. _('(full content)'),
- href => '/feeds/atom/recent',
- ;
- add rel "alternate",
- type => "application/atom+xml",
- title => _('Updated this week') .' '. _('(headlines)'),
- href => '/feeds/atom/recent/headlines',
- ;
- add rel "alternate",
- type => "application/atom+xml",
- title => _('Updated this week') .' '. _('(diffs)'),
- href => '/feeds/atom/recent/diffs',
- ;
+ my ($pages, $title, $type) = get(qw(pages title type));
+
+ title is $title;
+
+ set( id => 'recent-'. $type ); show( 'page_list' );
+
+ set( path => "recent/$type" );
+ show('/feeds/pages_links');
};
template pages => page {
commit 45b618fdf1050568f8b383d028715a5f1fa7e834
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Mon Dec 15 14:44:43 2008 +0000
delete overriden main.css so we get new things from jifty
like jGrowl messages
diff --git a/share/web/static/css/main.css b/share/web/static/css/main.css
deleted file mode 100644
index f1e909f..0000000
--- a/share/web/static/css/main.css
+++ /dev/null
@@ -1,9 +0,0 @@
- at import "app-base.css";
- at import "base.css";
- at import "nav.css";
- at import "keybindings.css";
- at import "forms.css";
- at import "halos.css";
- at import "app.css";
- at import "autocomplete.css";
- at import "notices.css";
commit 9ff2906b7a051c87ee00b746e1401f3c34112e49
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Mon Dec 15 14:47:55 2008 +0000
get rid of some css files
diff --git a/share/web/static/css/app.css b/share/web/static/css/app.css
index 9f95109..c36034f 100644
--- a/share/web/static/css/app.css
+++ b/share/web/static/css/app.css
@@ -1,3 +1,12 @@
+
+hr.clear {
+ clear: both;
+ visibility: hidden;
+ height: 0;
+ padding: 0;
+ margin: 0;
+}
+
div.argument-content {
width: 70%;
}
@@ -112,3 +121,4 @@ hr {
#login-box input.argument-remember {
float: left;
}
+
diff --git a/share/web/static/css/base.css b/share/web/static/css/base.css
deleted file mode 100644
index 27e44e2..0000000
--- a/share/web/static/css/base.css
+++ /dev/null
@@ -1,68 +0,0 @@
-.error {
- color: #a00000;
-}
-
-.warning {
- color: #00a0a0;
-}
-
-hr.clear {
- clear: both;
- visibility: hidden;
- height: 0;
- padding: 0;
- margin: 0;
-}
-
-.messages .message {
- display: block;
-}
-
-div#messages, div#errors {
- background-color: rgb(240,234,183);
- border: 1px solid rgb(230,224,173);
- margin-top: 10px;
- margin-bottom: 10px;
- padding: 5px;
- font-size: 1.2em;
-}
-
-div.spacer {
- clear: both;
-}
-
-.next-page, .prev-page {
- display: block;
- float: left;
- margin: 0.5em 0;
- padding: 0.2em 0.5em 0.5em 0.5em;
- border-top: 1px solid gray;
-}
-
-.next-page { padding-right: 1em; }
-.prev-page { padding-left: 1em; }
-
-div#jifty-wait-message {
- color: red;
- background: black;
- font-size: 2em;
- position: fixed;
- top: 10px;
- right: 10px;
- z-index: 42;
-}
-
-div.warning {
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- background-color: red;
- color: white;
- padding: .5em;
- border-bottom: 1px solid #000;
-}
-
-div.warning a {
- color: white;
-}
diff --git a/share/web/static/css/forms.css b/share/web/static/css/forms.css
deleted file mode 100644
index 125bec9..0000000
--- a/share/web/static/css/forms.css
+++ /dev/null
@@ -1,123 +0,0 @@
-/* buttons */
-
-input.button {
- margin-top: 0.6em;
- padding: 0.15em 1em;
- font-weight: bold;
-}
-
-* html input.button {
- padding: 0 0.1em;
-}
-
-/* fields */
-
-input.text, input.date, input.password, input.combo-text, textarea, select {
- border-top: 1px solid #7c7c7c;
- border-left: 1px solid #c3c3c3;
- border-right: 1px solid #c3c3c3;
- border-bottom: 1px solid #ddd;
- background: #fff url(/static/images/css/fieldbg.gif) repeat-x top;
- padding: 0.2em;
- font-size: 1em;
-}
-
-label, span.label {
- font-size: 0.9em;
-}
-
-.form_field .hints {
- font-size: 0.9em;
- color: #777;
-}
-
-/* layout */
-
-.form_field {
- clear: both;
-}
-
-label, span.label {
- display: block;
- width: 20%;
- float: left;
- text-align: right;
- margin-right: 0.5em;
-}
-
-* html label, * html span.label {
- width: 22%;
-}
-
-form .hints {
- display: block;
- clear: both;
-}
-
-html>body form .hints {
- padding: 0.2em 0 0.2em 21%;
-}
-
-* html form .hints {
- padding-left: 11.5%;
-}
-
-form .error {
- display: block;
- clear: both;
-}
-
-.form_field {
- padding: 0.3em 0 0 0;
-}
-
-.inline .hints {
- padding-left: 0;
-}
-
-.inline label, .inline span.label {
- float: none;
- width: auto;
- text-align: left;
- margin-right: auto;
- font-weight: bold;
-}
-
-.inline .form_field {
- float: left;
- clear: none;
- margin-right: 0.5em;
-}
-
-.inline .button {
- margin-top: 1.1em;
-}
-
-.button_line {
- border-top: 1px solid #ccc;
- padding-right: 5em;
- margin-top: 1.5em;
- clear: both;
- direction: rtl;
-}
-
-form .line {
- clear: both;
-}
-
-/* So the admin ui is one row per line */
-
-.jifty_admin.item.inline {
- clear: both;
-}
-
-.jifty_admin .editlink {
- float: right;
- border-left: 1px solid black;
- border-bottom: 1px solid black;
- padding: 0 0 10px 10px;
-}
-
-.jifty_admin hr {
- clear: both;
-}
diff --git a/share/web/static/css/keybindings.css b/share/web/static/css/keybindings.css
deleted file mode 100644
index 87f864e..0000000
--- a/share/web/static/css/keybindings.css
+++ /dev/null
@@ -1,25 +0,0 @@
-div#keybindings {
- color: #666666;
- margin-top: 2em;
-}
-
-dl.keybindings .keybinding {
- display: inline;
-}
-
-dl.keybindings dt {
- margin: 0;
- font-weight: bold;
- display: inline;
-
-}
-dl.keybindings dt:after {
- content: ":";
-
-}
-dl.keybindings dd {
- margin-right: 1.5em;
- margin-left: 0.5em;
- display: inline;
- white-space: nowrap;
-}
diff --git a/share/web/static/css/nav.css b/share/web/static/css/nav.css
index b71722f..2fba9e8 100644
--- a/share/web/static/css/nav.css
+++ b/share/web/static/css/nav.css
@@ -18,4 +18,4 @@ ul.menu li {
#wikiheader ul.menu li {
padding-right: 0;
padding-left: 1em;
-}
\ No newline at end of file
+}
commit eaee8ab839a9036b6d4f142ebd21ee0dab0b847c
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Mon Dec 15 14:49:23 2008 +0000
return back default wifty layout we had before
diff --git a/lib/Wifty/View/Page.pm b/lib/Wifty/View/Page.pm
index 971a6dd..44293ac 100644
--- a/lib/Wifty/View/Page.pm
+++ b/lib/Wifty/View/Page.pm
@@ -8,13 +8,38 @@ use Jifty::View::Declare::Helpers;
sub render_page {
my $self = shift;
+ my $wikiname = Jifty->config->app('WikiName') || "Wifty";
+
if ( my $logo = Jifty->config->app('Logo') ) {
div { attr { id is "logo" }
img { src is $logo, alt is '' }
};
}
- return $self->SUPER::render_page( @_ );
+ Template::Declare->new_buffer_frame;
+ $self->instrument_content;
+ my $content = Template::Declare->end_buffer_frame->data;
+
+ div { attr { id is "header" }
+ div { attr { id is "wikiheader" }
+ $self->render_navigation;
+ }
+ div { attr { id is "pageheader" }
+ h1 { attr { id is "pagename" }; outs( $self->_title ) };
+ Jifty->web->page_navigation->render_as_menu;
+ }
+ };
+ $self->render_salutation;
+ hr { attr { class is 'clear' } };
+
+ Jifty->web->render_messages;
+
+ div { attr { id is "content" };
+ outs_raw( $content );
+ hr { attr { class is 'clear' } };
+ };
+ $self->render_jifty_page_detritus;
+ return '';
}
sub render_navigation {
@@ -23,9 +48,17 @@ sub render_navigation {
h1 { attr { id is 'wikiname' }
Jifty->web->link( url => "/", label => _($wikiname) )
};
- return $self->SUPER::render_navigation( @_ );
+ $self->SUPER::render_navigation( @_ );
+ show('/search_box');
+ return '';
}
+=head2 render_title_inhead
+
+Adds " - <wikiname>" after page title.
+
+=cut
+
sub render_title_inhead {
my $self = shift;
my $title = shift;
@@ -33,12 +66,6 @@ sub render_title_inhead {
return $self->SUPER::render_title_inhead( $title .' - '. $wikiname );
}
-sub render_title_inpage {
- my $self = shift;
- $self->SUPER::render_title_inpage( @_ );
-# show('/search_box');
- hr { {class is 'clear'} };
- return '';
-}
+sub render_title_inpage { return '' }
1;
commit 8c964d6def0d799cd82b87fd1268b1be2827eb9c
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Mon Dec 15 15:25:24 2008 +0000
rework feeds:
** headlines are default
** cover feeds for new pages
* under/on doesn't work the way it should, use just on
diff --git a/lib/Wifty/Dispatcher.pm b/lib/Wifty/Dispatcher.pm
index 1c30dc4..ffb54c0 100644
--- a/lib/Wifty/Dispatcher.pm
+++ b/lib/Wifty/Dispatcher.pm
@@ -92,14 +92,8 @@ on 'search', run {
# Show recent
# backwards compat
-on '/recent', run {
- redirect('/recent/changes');
-};
-
-under 'feeds/atom/recent', run {
- set pages => recent_changes();
-};
-on qr{^/recent/(.+)}, run {
+on 'recent' => run { redirect('/recent/changes') };
+on qr{^/recent/(changes|additions)}, run {
my $type = $1;
if ( $type eq 'changes' ) {
set title => _('Updated this week');
@@ -107,13 +101,32 @@ on qr{^/recent/(.+)}, run {
} elsif ( $type eq 'additions' ) {
set title => _('Created this week');
set pages => Wifty::Model::PageCollection->recently_created;
- } else {
- redirect('/recent/changes');
}
- set( type => $type );
+ set type => $type;
show('/recent');
};
+on 'feeds/atom/recent' => run { redirect('/feeds/atom/recent/changes/headlines') };
+on qr{^/feeds/atom/recent/(changes|additions)(?:/(full|headlines|diff))?$} => run {
+ my $wikiname = Jifty->config->app('WikiName');
+ my $show = $1;
+ my $show_as = $2 || 'headlines';
+ my ($pages, $title);
+ if ( $show eq 'changes' ) {
+ $pages = Wifty::Model::PageCollection->recently_updated;
+ $title = $wikiname
+ ? _('Recently changed pages on %1 wiki', $wikiname)
+ : _('Recently changed pages on some wiki');
+ } else {
+ $pages = Wifty::Model::PageCollection->recently_created;
+ $title = $wikiname
+ ? _('Recently added pages on %1 wiki', $wikiname)
+ : _('Recently added pages on some wiki');
+ }
+ set( title => $title ); set( pages => $pages ); set( show_as => $show_as );
+ show('/feeds/atom/pages');
+};
+
sub setup_page_nav {
my ($prefix, $page, $rev) = @_;
diff --git a/lib/Wifty/View/Feeds.pm b/lib/Wifty/View/Feeds.pm
index 919b9e2..0a62f52 100644
--- a/lib/Wifty/View/Feeds.pm
+++ b/lib/Wifty/View/Feeds.pm
@@ -7,30 +7,31 @@ use Jifty::View::Declare -base;
use XML::Atom::SimpleFeed;
use Data::UUID;
-# XXX: don't know how to redispatch to private template
-# right from dispatcher
-template 'atom/recent' => sub {
- set(type => 'full');
- show('../atom');
-};
-
-template 'atom/recent/diff' => sub {
- set(type => 'diff');
- show('../../atom');
-};
-
-template 'atom/recent/headlines' => sub {
- set(type => 'headlines');
- show('../../atom');
+private template 'pages_links' => sub {
+ my ($title, $path) = get(qw(title path));
+
+ add rel "alternate",
+ type => "application/atom+xml",
+ title => $title .' '. _('(headlines)'),
+ href => "/feeds/atom/$path/headlines",
+ ;
+ add rel "alternate",
+ type => "application/atom+xml",
+ title => $title .' '. _('(full content)'),
+ href => "/feeds/atom/$path/full",
+ ;
+ add rel "alternate",
+ type => "application/atom+xml",
+ title => $title .' '. _('(diffs)'),
+ href => "/feeds/atom/$path/diffs",
+ ;
};
# XXX: id rendering is not correct
-private template 'atom' => sub {
- my ($pages, $type) = get(qw(pages type));
- my $wikiname = Jifty->config->app('WikiName');
- my $title = $wikiname
- ? _('Recently changed pages on %1 wiki', $wikiname)
- : _('Recently changed pages on some wiki');
+# XXX: don't know how to dispatch to private template
+template 'atom/pages' => sub {
+ my ($pages, $title, $show_as) = get(qw(pages title show_as));
+ $show_as ||= 'headlines';
my $feed = XML::Atom::SimpleFeed->new(
title => $title,
link => Jifty->web->url,
@@ -39,10 +40,10 @@ private template 'atom' => sub {
while ( my $page = $pages->next ) {
my $summary = '';
- if ( !$type || $type eq 'full' ) {
+ if ( $show_as eq 'full' ) {
$summary = $page->viewer->form_field('content')->wiki_content;
}
- elsif ( $type eq 'diff' ) {
+ elsif ( $show_as eq 'diff' ) {
$summary = '<pre>'. $page->revisions->last->diff_from .'</pre>';
}
commit f8cdc037d6394a29ea35bb5be93a99969ac08847
Author: Thomas Sibley <trs at bestpractical.com>
Date: Mon Dec 15 15:32:08 2008 +0000
Add a requirement on XML::Atom::SimpleFeed
diff --git a/Makefile.PL b/Makefile.PL
index 8273f33..6fcb19b 100644
--- a/Makefile.PL
+++ b/Makefile.PL
@@ -5,5 +5,6 @@ requires('Jifty');
requires('Text::Markdown');
requires('HTML::Scrubber');
requires('Text::Diff::HTML');
+requires('XML::Atom::SimpleFeed');
recommends('Text::KwikiFormatish');
WriteAll;
commit a21d14398ca55e2059afccca13e2e7dffc95ab6e
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Tue Dec 16 23:43:45 2008 +0000
'title is' => page_title
diff --git a/lib/Wifty/View.pm b/lib/Wifty/View.pm
index 2d45c61..b03364a 100644
--- a/lib/Wifty/View.pm
+++ b/lib/Wifty/View.pm
@@ -9,20 +9,21 @@ alias Wifty::View::Feeds under 'feeds/';
template 'view' => page {
my ( $page, $revision, $viewer ) = get(qw(page revision viewer));
- my $title = $revision->id
+ page_title is
+ $revision->id
? _('%1 as of %2', $page->name, $revision->created)
: $page->name;
- { title is $title }
+
render_param($viewer => 'content', label => '', render_mode => 'read');
};
template 'edit' => page {
my ( $page, $revision, $viewer ) = get(qw(page revision viewer));
- my $title = $revision->id
+ page_title is
+ $revision->id
? _('Edit page %1 as of %2', $page->name, $revision->created)
: _('Edit page %1');
- { title is $title }
my $can_edit = $page->current_user_can('update');
@@ -46,7 +47,8 @@ template 'edit' => page {
template create => page {
my ($action, $page) = get(qw(action page));
- { title is _("New page '%1'", $page), id is 'create' };
+ id is 'create';
+ page_title is _("New page '%1'", $page);
div {
show('markup');
@@ -66,7 +68,7 @@ template create => page {
template no_such_page => page {
my ($page) = get(qw(page));
- { title is _("No '%1' page", $page) }
+ page_title is _("No '%1' page", $page);
p {
q{Unfortunately, you've tried to reach a page that doesn't exist }
@@ -79,7 +81,8 @@ template no_such_page => page {
template history => page {
my ( $page, $revisions ) = get(qw(page revisions));
$revisions->do_search; # avoid count+fetch
- { title is $revisions->count . " revisions of " . $page->name }
+
+ page_title is _('%1 revision(s) of %2', $revisions->count, $page->name);
ul { { id is 'history' }
while ( my $rev = $revisions->next ) { li {
@@ -101,7 +104,7 @@ template history => page {
template recent => page {
my ($pages, $title, $type) = get(qw(pages title type));
- title is $title;
+ page_title is $title;
set( id => 'recent-'. $type ); show( 'page_list' );
@@ -111,7 +114,8 @@ template recent => page {
template pages => page {
my ($pages ) = get(qw(pages));
- { title is _('These are the pages on your wiki!') }
+
+ page_title is _('These are the pages on your wiki!');
show( 'page_list', pages => $pages, id => 'allpages' );
};
@@ -119,6 +123,8 @@ template pages => page {
template search => page {
my ( $pages, $search ) = get(qw(pages search));
+ page_title is _('Search');
+
form { div { { id is "searchbox", class is 'inline' }
render_param $search => 'contains', label => _('Find pages containing:');
form_submit label => 'Search', submit => $search;
commit 6c382fc5b08e1f42bbe398138f1759998cd2e05c
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Tue Dec 16 23:49:06 2008 +0000
publish feeds in head and content where we want them to be
right from templates using new page class plugin
* add simple css
diff --git a/lib/Wifty/View/Feeds.pm b/lib/Wifty/View/Feeds.pm
index 0a62f52..f54851b 100644
--- a/lib/Wifty/View/Feeds.pm
+++ b/lib/Wifty/View/Feeds.pm
@@ -10,21 +10,24 @@ use Data::UUID;
private template 'pages_links' => sub {
my ($title, $path) = get(qw(title path));
- add rel "alternate",
- type => "application/atom+xml",
- title => $title .' '. _('(headlines)'),
- href => "/feeds/atom/$path/headlines",
- ;
- add rel "alternate",
- type => "application/atom+xml",
- title => $title .' '. _('(full content)'),
- href => "/feeds/atom/$path/full",
- ;
- add rel "alternate",
- type => "application/atom+xml",
- title => $title .' '. _('(diffs)'),
- href => "/feeds/atom/$path/diffs",
- ;
+ ul { attr { class is 'atom-feeds' };
+ li { add rel "alternate",
+ type => "application/atom+xml",
+ title => $title .' '. _('(headlines)'),
+ href => "/feeds/atom/$path/headlines",
+ }
+ li { add rel "alternate",
+ type => "application/atom+xml",
+ title => $title .' '. _('(full content)'),
+ href => "/feeds/atom/$path/full",
+ }
+ li { add rel "alternate",
+ type => "application/atom+xml",
+ title => $title .' '. _('(diffs)'),
+ href => "/feeds/atom/$path/diffs",
+ }
+ };
+ return '';
};
# XXX: id rendering is not correct
diff --git a/lib/Wifty/View/Page.pm b/lib/Wifty/View/Page.pm
index 44293ac..448e157 100644
--- a/lib/Wifty/View/Page.pm
+++ b/lib/Wifty/View/Page.pm
@@ -68,4 +68,18 @@ sub render_title_inhead {
sub render_title_inpage { return '' }
+sub render_link_inpage {
+ my $self = shift;
+ my %link = @_;
+ if ( ($link{rel}||'') eq 'alternate' && ($link{type}||'') eq 'application/atom+xml' ) {
+ a { attr { href => $link{'href'} };
+ img { attr {
+ src => '/static/images/feed-icon-14x14.png',
+ width => 14, heigth => 14,
+ title => $link{'title'}
+ } }
+ }
+ }
+ return '';
+}
1;
diff --git a/share/web/static/css/app.css b/share/web/static/css/app.css
index c36034f..1a346ec 100644
--- a/share/web/static/css/app.css
+++ b/share/web/static/css/app.css
@@ -122,3 +122,20 @@ hr {
float: left;
}
+ul.atom-feeds {
+ clear: both;
+ margin-left: 0em;
+ padding-left: 0em;
+ list-style-type: none;
+}
+
+ul.atom-feeds a img {
+ border: none;
+}
+
+ul.atom-feeds li {
+ margin-left: 1em;
+ padding-left: 0;
+ display: inline;
+}
+
commit 043197f6c03975c182325fc4fee443a7ca6d542f
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Tue Dec 16 23:50:18 2008 +0000
add standard feed image
diff --git a/share/web/static/images/feed-icon-14x14.png b/share/web/static/images/feed-icon-14x14.png
new file mode 100755
index 0000000..b3c949d
Binary files /dev/null and b/share/web/static/images/feed-icon-14x14.png differ
commit 90d8bb4f5774e2a2baf62abc3d4da3b93f48ae12
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Fri Dec 19 22:20:37 2008 +0000
make diff/diffs the same
diff --git a/lib/Wifty/View/Feeds.pm b/lib/Wifty/View/Feeds.pm
index f54851b..d5a2661 100644
--- a/lib/Wifty/View/Feeds.pm
+++ b/lib/Wifty/View/Feeds.pm
@@ -46,7 +46,7 @@ template 'atom/pages' => sub {
if ( $show_as eq 'full' ) {
$summary = $page->viewer->form_field('content')->wiki_content;
}
- elsif ( $show_as eq 'diff' ) {
+ elsif ( $show_as eq 'diff' or $show_as eq 'diffs' ) {
$summary = '<pre>'. $page->revisions->last->diff_from .'</pre>';
}
commit 92d43f5f78efb276049ce4d1dbd90c3bcc04efca
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Fri Dec 19 22:21:33 2008 +0000
the same for urls
diff --git a/lib/Wifty/Dispatcher.pm b/lib/Wifty/Dispatcher.pm
index ffb54c0..c3a9a88 100644
--- a/lib/Wifty/Dispatcher.pm
+++ b/lib/Wifty/Dispatcher.pm
@@ -107,7 +107,7 @@ on qr{^/recent/(changes|additions)}, run {
};
on 'feeds/atom/recent' => run { redirect('/feeds/atom/recent/changes/headlines') };
-on qr{^/feeds/atom/recent/(changes|additions)(?:/(full|headlines|diff))?$} => run {
+on qr{^/feeds/atom/recent/(changes|additions)(?:/(full|headlines?|diffs?))?$} => run {
my $wikiname = Jifty->config->app('WikiName');
my $show = $1;
my $show_as = $2 || 'headlines';
commit a8535dac8f77be215aa5070145d4bb61a9b437cc
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Fri Dec 19 22:57:54 2008 +0000
generating different ids each time is totally incorrect, unique
url is better
diff --git a/lib/Wifty/View/Feeds.pm b/lib/Wifty/View/Feeds.pm
index d5a2661..971653f 100644
--- a/lib/Wifty/View/Feeds.pm
+++ b/lib/Wifty/View/Feeds.pm
@@ -30,7 +30,6 @@ private template 'pages_links' => sub {
return '';
};
-# XXX: id rendering is not correct
# XXX: don't know how to dispatch to private template
template 'atom/pages' => sub {
my ($pages, $title, $show_as) = get(qw(pages title show_as));
@@ -38,22 +37,24 @@ template 'atom/pages' => sub {
my $feed = XML::Atom::SimpleFeed->new(
title => $title,
link => Jifty->web->url,
- id => 'urn:uuid:' . Data::UUID->new->create_str()
);
while ( my $page = $pages->next ) {
+ my $last_rev = $page->revisions->last;
my $summary = '';
if ( $show_as eq 'full' ) {
$summary = $page->viewer->form_field('content')->wiki_content;
}
elsif ( $show_as eq 'diff' or $show_as eq 'diffs' ) {
- $summary = '<pre>'. $page->revisions->last->diff_from .'</pre>';
+ $summary = {
+ content => $last_rev->diff_from,
+ type => 'xhtml',
+ };
}
$feed->add_entry(
- id => 'urn:uuid:' . Data::UUID->new->create_str(),
- link => Jifty->web->url . '/view/' . $page->name,
title => $page->name,
+ link => Jifty->web->url . '/view/' . $page->name .'/'. $last_rev->id,
author => $page->updated_by->friendly_name,
updated => $page->updated,
summary => $summary,
commit 1e751baa9e0db298f0357663bcb54a5e9e9342f5
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Fri Dec 19 23:02:18 2008 +0000
update config
** if we have log4perl.conf in repo then we should use it
** describe View:*
** no more dependecy on SkelletonApp
** path to po files
diff --git a/etc/config.yml b/etc/config.yml
index 7f93d3a..1760bf1 100644
--- a/etc/config.yml
+++ b/etc/config.yml
@@ -1,20 +1,25 @@
---
framework:
ConfigFileVersion: 4
-
- AdminMode: 0
+
ApplicationName: Wifty
AdminEmail: 'wifty at example.com'
+ AdminMode: 0
+ DevelMode: 1
+ LogConfig: etc/log4perl.conf
+
Database:
+ AutoUpgrade: 1
+ CheckSchema: 1
Driver: SQLite
Host: localhost
User: postgres
Version: 0.0.21
Password: ''
RequireSSL: 0
+
Plugins:
- - SkeletonApp: {}
- CompressedCSSandJS: {}
- User: {}
- Authentication::Password:
@@ -26,6 +31,15 @@ framework:
- %log/mail.log%
SiteConfig: etc/site_config.yml
+ L10N:
+ PoDir: share/po
+
+ View:
+ FallbackHandler: Jifty::View::Declare::Handler
+ Handlers:
+ - Jifty::View::Static::Handler
+ - Jifty::View::Declare::Handler
+
Web:
StaticRoot: share/web/static
TemplateRoot: share/web/templates
commit 5e7abdaac58134b8d191a82672e1389361e448e4
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Sat Dec 20 00:33:49 2008 +0000
return back description for users that can not edit pages
diff --git a/lib/Wifty/View.pm b/lib/Wifty/View.pm
index b03364a..fdf4779 100644
--- a/lib/Wifty/View.pm
+++ b/lib/Wifty/View.pm
@@ -31,8 +31,12 @@ template 'edit' => page {
form { div { attr { class is 'form_wrapper' };
div { attr { class is 'inline' };
- unless ( $can_edit ) {
- }
+ unless ( $can_edit ) { p {
+ outs(_("You don't have permission to edit this page."));
+ outs(' '. _("Perhaps logging in would help."));
+ outs(' '. _("In the mean time, though, you're welcome to view and copy the source of this page."). ' ');
+ tangent(url => '/login', label => _('Login'));
+ } }
form_next_page url => '/view/'.$page->name;
render_action $viewer, ['content'];
};
commit f205047021dac03ddf8dafa9f2cbbf6124b6d59f
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Sat Dec 20 00:42:06 2008 +0000
'attr is ...' inside 'attr {}' makes no sense, prefer attr
* add back style="wifth: 70%"
diff --git a/lib/Wifty/View.pm b/lib/Wifty/View.pm
index fdf4779..8d94321 100644
--- a/lib/Wifty/View.pm
+++ b/lib/Wifty/View.pm
@@ -29,9 +29,9 @@ template 'edit' => page {
show('markup');
- form { div { attr { class is 'form_wrapper' };
- div { attr { class is 'inline' };
- unless ( $can_edit ) { p {
+ form { div { attr { class => 'form_wrapper' };
+ div { attr { class => 'inline' };
+ unless ( $can_edit ) { p { attr { style => "width: 70%" };
outs(_("You don't have permission to edit this page."));
outs(' '. _("Perhaps logging in would help."));
outs(' '. _("In the mean time, though, you're welcome to view and copy the source of this page."). ' ');
@@ -41,7 +41,7 @@ template 'edit' => page {
render_action $viewer, ['content'];
};
if ( $can_edit ) {
- div { attr { class is 'line' };
+ div { attr { class => 'line' };
form_submit label => _('Save')
}
}
@@ -57,7 +57,7 @@ template create => page {
div {
show('markup');
- form { div { attr { class is 'form_wrapper' };
+ form { div { attr { class => 'form_wrapper' };
form_next_page url => '/view/' . $page;
render_param $action, 'name',
render_as => 'hidden',
commit f7d6ffee727b0f00b8bee30a9f02a3bc3041035a
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Sat Dec 20 01:09:01 2008 +0000
use __set method in Upgrade script to avoid updating 'updated'
and 'upgrated_by' columns in _set method during upgrade
diff --git a/lib/Wifty/Upgrade.pm b/lib/Wifty/Upgrade.pm
index b59f1f2..244445b 100644
--- a/lib/Wifty/Upgrade.pm
+++ b/lib/Wifty/Upgrade.pm
@@ -14,10 +14,18 @@ since '0.0.21' => sub {
while ( my $page = $pages->next ) {
my $first_rev = $page->revisions->first;
- my ($status, $msg) = $page->set_created( $first_rev? $first_rev->created : $page->updated );
- Jifty->log->error("Couldn't set created:". $msg) unless $status;
- ($status, $msg) = $page->set_created_by( $first_rev? $first_rev->created_by : $page->updated_by );
- Jifty->log->error("Couldn't set created_by:". $msg) unless $status;
+ my $created = $first_rev? $first_rev->created : $page->updated;
+ if ( $created ) {
+ my ($status, $msg) = $page->__set( column => 'created', value => $created );
+ Jifty->log->error("Couldn't set created:". $msg)
+ unless $status;
+ }
+ my $created_by = ( $first_rev? $first_rev->created_by : $page->updated_by )->id;
+ if ( $created_by ) {
+ my ($status, $msg) = $page->__set( column => 'created_by', value => $created_by );
+ Jifty->log->error("Couldn't set created_by:". $msg)
+ unless $status;
+ }
}
};
commit 1e99679cdcc56fc2d8ce529c1c02f691aafb2eb6
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Tue Dec 23 02:05:55 2008 +0000
we never used scrubber, delete unused code
diff --git a/lib/Wifty/Form/Field/WikiPage.pm b/lib/Wifty/Form/Field/WikiPage.pm
index fcc1760..715537d 100644
--- a/lib/Wifty/Form/Field/WikiPage.pm
+++ b/lib/Wifty/Form/Field/WikiPage.pm
@@ -47,21 +47,6 @@ Wikify this field's C<current_value>
sub wiki_content {
my $self = shift;
my $content = $self->current_value;
- my $scrubber = HTML::Scrubber->new();
-
- $scrubber->default(
- 0,
- { '*' => 0,
- id => 1,
- class => 1,
- href => qr{^(?:(?:\w+$)|http:|ftp:|https:|\.?/)}i,
-
- # Match http, ftp and relative urls
- face => 1,
- size => 1,
- target => 1
- }
- );
$content =~ s/(?:\n\r|\r\n|\r)/\n/g;
@@ -71,16 +56,14 @@ sub wiki_content {
$scrubber->comment(0);
if (Jifty->config->app('Formatter') eq 'Markdown' ) {
- require Text::Markdown;
- $content = Text::Markdown::markdown( $content );
+ require Text::Markdown;
+ $content = Text::Markdown::markdown( $content );
}
elsif (Jifty->config->app('Formatter') eq 'Kwiki') {
require Text::KwikiFormatish;
- $content = Text::KwikiFormatish::format( $content);
+ $content = Text::KwikiFormatish::format( $content );
}
- #$content = $scrubber->scrub( $content );
return ( $content );
-
}
=head2 rows
commit 8e09eb0320516f77d3180a6ac76b59c2fc460e96
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Tue Dec 23 02:06:49 2008 +0000
delete all notes on scrubber
diff --git a/lib/Wifty/Form/Field/WikiPage.pm b/lib/Wifty/Form/Field/WikiPage.pm
index 715537d..43f4b03 100644
--- a/lib/Wifty/Form/Field/WikiPage.pm
+++ b/lib/Wifty/Form/Field/WikiPage.pm
@@ -50,11 +50,6 @@ sub wiki_content {
$content =~ s/(?:\n\r|\r\n|\r)/\n/g;
- $scrubber->deny(qw[*]);
- $scrubber->allow(
- qw[H1 H2 H3 H4 H5 A STRONG EM CODE PRE B U P BR I HR BR SPAN DIV UL OL LI DL DT DD]);
- $scrubber->comment(0);
-
if (Jifty->config->app('Formatter') eq 'Markdown' ) {
require Text::Markdown;
$content = Text::Markdown::markdown( $content );
commit b13470ea8143145d7b6b7eed778bf53d0fbfa4b2
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Wed Dec 24 17:54:33 2008 +0000
generate url once
diff --git a/lib/Wifty/View/Feeds.pm b/lib/Wifty/View/Feeds.pm
index 971653f..30b79d5 100644
--- a/lib/Wifty/View/Feeds.pm
+++ b/lib/Wifty/View/Feeds.pm
@@ -34,9 +34,10 @@ private template 'pages_links' => sub {
template 'atom/pages' => sub {
my ($pages, $title, $show_as) = get(qw(pages title show_as));
$show_as ||= 'headlines';
+
+ my $url = Jifty->web->url;
my $feed = XML::Atom::SimpleFeed->new(
- title => $title,
- link => Jifty->web->url,
+ title => $title, link => $url
);
while ( my $page = $pages->next ) {
@@ -54,7 +55,7 @@ template 'atom/pages' => sub {
$feed->add_entry(
title => $page->name,
- link => Jifty->web->url . '/view/' . $page->name .'/'. $last_rev->id,
+ link => $url . '/view/' . $page->name .'/'. $last_rev->id,
author => $page->updated_by->friendly_name,
updated => $page->updated,
summary => $summary,
commit 97f18fe1b3296afd4521c90a8d19b2047f392fe7
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Wed Dec 24 17:56:21 2008 +0000
send HTTP headers manually :(
diff --git a/lib/Wifty/View/Feeds.pm b/lib/Wifty/View/Feeds.pm
index 30b79d5..aacf8f7 100644
--- a/lib/Wifty/View/Feeds.pm
+++ b/lib/Wifty/View/Feeds.pm
@@ -35,6 +35,11 @@ template 'atom/pages' => sub {
my ($pages, $title, $show_as) = get(qw(pages title show_as));
$show_as ||= 'headlines';
+ # XXX: investigation required. Why Jifty doesn't send HTTP headers
+ # for us?
+ Jifty->handler->apache->content_type('application/atom+xml');
+ Jifty->handler->send_http_header;
+
my $url = Jifty->web->url;
my $feed = XML::Atom::SimpleFeed->new(
title => $title, link => $url
commit bd8e63900bef7b5915a41716700ce62de1c957af
Author: Thomas Sibley <trs at bestpractical.com>
Date: Thu Dec 25 00:21:24 2008 +0000
Use at least a little bit of text to differentiate the feed types
diff --git a/lib/Wifty/View/Page.pm b/lib/Wifty/View/Page.pm
index 448e157..504142b 100644
--- a/lib/Wifty/View/Page.pm
+++ b/lib/Wifty/View/Page.pm
@@ -72,12 +72,14 @@ sub render_link_inpage {
my $self = shift;
my %link = @_;
if ( ($link{rel}||'') eq 'alternate' && ($link{type}||'') eq 'application/atom+xml' ) {
+ my ($type) = $link{'href'} =~ m{/(\w+)$};
a { attr { href => $link{'href'} };
img { attr {
src => '/static/images/feed-icon-14x14.png',
width => 14, heigth => 14,
title => $link{'title'}
- } }
+ } };
+ outs(" " . ucfirst $type);
}
}
return '';
commit 86d952defa24dcf03913f931161c6676e92cf918
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Sun Dec 28 23:43:31 2008 +0000
render diff for feeds as text and wrap into pre
diff --git a/lib/Wifty/View/Feeds.pm b/lib/Wifty/View/Feeds.pm
index aacf8f7..d422353 100644
--- a/lib/Wifty/View/Feeds.pm
+++ b/lib/Wifty/View/Feeds.pm
@@ -53,7 +53,10 @@ template 'atom/pages' => sub {
}
elsif ( $show_as eq 'diff' or $show_as eq 'diffs' ) {
$summary = {
- content => $last_rev->diff_from,
+ content =>
+ '<pre>'. Jifty->web->escape(
+ $last_rev->diff_from( undef, STYLE => 'Text::Diff::Unified' )
+ ) .'</pre>',
type => 'xhtml',
};
}
commit e134a8619c5ddb15ce65f9844752f924a1b48e4a
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Sat Jan 3 22:04:35 2009 +0000
use prefix '/view/', however to make it work we need a new
release of the kwiki formatter:
https://rt.cpan.org/Public/Bug/Display.html?id=42090
diff --git a/lib/Wifty/Form/Field/WikiPage.pm b/lib/Wifty/Form/Field/WikiPage.pm
index 43f4b03..f1805e8 100644
--- a/lib/Wifty/Form/Field/WikiPage.pm
+++ b/lib/Wifty/Form/Field/WikiPage.pm
@@ -55,8 +55,12 @@ sub wiki_content {
$content = Text::Markdown::markdown( $content );
}
elsif (Jifty->config->app('Formatter') eq 'Kwiki') {
+ # XXX: we need a new release of Text::KwikiFormatish
+ # https://rt.cpan.org/Public/Bug/Display.html?id=42090
require Text::KwikiFormatish;
- $content = Text::KwikiFormatish::format( $content );
+ $content = Text::KwikiFormatish::format(
+ $content, prefix => '/view/',
+ );
}
return ( $content );
}
commit ee0c988b7ed1ec5c3059fcf7014d895af5a92ad7
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Mon Jan 26 19:07:49 2009 +0000
Added a basic robots.txt
diff --git a/lib/Wifty/View.pm b/lib/Wifty/View.pm
index 8d94321..ed2c6cd 100644
--- a/lib/Wifty/View.pm
+++ b/lib/Wifty/View.pm
@@ -7,6 +7,14 @@ use Jifty::View::Declare -base;
require Wifty::View::Feeds;
alias Wifty::View::Feeds under 'feeds/';
+template 'robots.txt' => sub {
+outs_raw('User-agent: *
+Disallow: /history
+Disallow: /search
+');
+
+};
+
template 'view' => page {
my ( $page, $revision, $viewer ) = get(qw(page revision viewer));
page_title is
commit e5d7bf123abf59615de75249f7dd90f589d1b903
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Mon May 11 17:33:20 2009 +0000
bump version of Wifty
diff --git a/Makefile.PL b/Makefile.PL
index 6fcb19b..f61b9b4 100644
--- a/Makefile.PL
+++ b/Makefile.PL
@@ -1,6 +1,6 @@
use inc::Module::Install;
name('Wifty');
-version('0.01');
+version('0.02');
requires('Jifty');
requires('Text::Markdown');
requires('HTML::Scrubber');
commit 6aa10d42ad51be9860052a9d993512076bcf1a51
Author: Alex Vandiver <alexmv at bestpractical.com>
Date: Mon May 11 18:57:43 2009 +0000
Move to new Template::Declare buffer API
diff --git a/lib/Wifty/View/Page.pm b/lib/Wifty/View/Page.pm
index 504142b..1d33b32 100644
--- a/lib/Wifty/View/Page.pm
+++ b/lib/Wifty/View/Page.pm
@@ -16,9 +16,9 @@ sub render_page {
};
}
- Template::Declare->new_buffer_frame;
+ Template::Declare->buffer->push( private => 1 );
$self->instrument_content;
- my $content = Template::Declare->end_buffer_frame->data;
+ my $content = Template::Declare->buffer->pop;
div { attr { id is "header" }
div { attr { id is "wikiheader" }
commit 45043a7d10a0666ab1bad19921fa4cc4e1caf3fd
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Mon May 11 20:19:26 2009 +0000
add Users' stats I had in the checkout for months
diff --git a/lib/Wifty/Dispatcher.pm b/lib/Wifty/Dispatcher.pm
index c3a9a88..a01a08e 100644
--- a/lib/Wifty/Dispatcher.pm
+++ b/lib/Wifty/Dispatcher.pm
@@ -11,6 +11,13 @@ before '*', run {
$top->child( Home => url => "/", label => _("Home") );
$top->child( Recent => url => "/recent/changes", label => _("Recent Changes") );
$top->child( New => url => "/recent/additions", label => _("New") );
+ if ( Jifty->web->current_user->id ) {
+ $top->child(
+ Stats => url =>
+ "/user/". Jifty->web->escape_uri(Jifty->web->current_user->username),
+ label => _("Stats"),
+ );
+ }
$top->child( Search => url => "/search", label => _("Search") );
};
@@ -127,6 +134,14 @@ on qr{^/feeds/atom/recent/(changes|additions)(?:/(full|headlines?|diffs?))?$} =>
show('/feeds/atom/pages');
};
+on 'user/*' => run {
+ my $user = Wifty::Model::User->load_by_cols( name => URI::Escape::uri_unescape($1) );
+ abort(404) unless $user && $user->id;
+
+ set(user => $user);
+ show('/user/stats');
+};
+
sub setup_page_nav {
my ($prefix, $page, $rev) = @_;
diff --git a/lib/Wifty/View.pm b/lib/Wifty/View.pm
index ed2c6cd..38738d9 100644
--- a/lib/Wifty/View.pm
+++ b/lib/Wifty/View.pm
@@ -7,6 +7,9 @@ use Jifty::View::Declare -base;
require Wifty::View::Feeds;
alias Wifty::View::Feeds under 'feeds/';
+require Wifty::View::Users;
+alias Wifty::View::Users under 'user/';
+
template 'robots.txt' => sub {
outs_raw('User-agent: *
Disallow: /history
@@ -102,7 +105,9 @@ template history => page {
label => $rev->created,
url => '/view/' . $page->name . '/' . $rev->id
);
- outs( ' (' . $rev->created_by->friendly_name . ')' );
+ outs(' (');
+ user($rev->created_by);
+ outs(')');
outs( ' ', _('%1 bytes', length $rev->content ) );
render_region(
'revision-'. $rev->id .'-diff',
@@ -228,23 +233,48 @@ private template markup => sub {
};
private template page_list => sub {
- my ( $pages, $id ) = get(qw(pages id));
- dl {{ id is $id, class is "pagelist" }
- while ( my $page = $pages->next ) {
- dt {
- hyperlink(
- label => $page->name,
- url => '/view/' . $page->name
- );
- };
- dd {
- outs( $page->updated );
- outs( ' - ('. $page->updated_by->friendly_name .')' );
- };
- }
+ my ($pages, $id, $hide) = get(qw(pages id hide));
+ my %hide = map {$_ => 1} @{ $hide || [] };
+
+ table { attr { id => $id, class => "pagelist" };
+ unless ( $hide{'header'} ) { row {
+ th {_('Page')};
+ th {_('Updated')} unless $hide{'updated'};
+ th {_('Created')} unless $hide{'created'};
+ } }
+ while ( my $page = $pages->next ) { row {
+ cell { hyperlink(
+ label => $page->name,
+ url => '/view/' . $page->name
+ ) };
+ unless ( $hide{'updated'} ) {
+ cell { date_user( $page->updated, $page->updated_by ) }
+ }
+ unless ( $hide{'created'} ) {
+ cell { date_user( $page->created, $page->created_by ) }
+ }
+ } }
};
};
+sub date_user {
+ my ($date, $user) = @_;
+ return outs( _('%1 by %2', $date, $user->friendly_name ) )
+ unless $user->id;
+
+ return a { attr { href => '/user/'. $user->name };
+ _('%1 by %2', $date, $user->friendly_name)
+ }
+}
+
+sub user {
+ my $user = shift;
+ return $user->friendly_name unless $user->id;
+ return a { attr { href => '/user/'. $user->name };
+ $user->friendly_name
+ }
+}
+
template 'helpers/diff' => sub {
my ($from, $to, $show) = get(qw(from to show));
hyperlink
diff --git a/lib/Wifty/View/Users.pm b/lib/Wifty/View/Users.pm
new file mode 100644
index 0000000..998d6e5
--- /dev/null
+++ b/lib/Wifty/View/Users.pm
@@ -0,0 +1,31 @@
+use warnings;
+use strict;
+
+package Wifty::View::Users;
+use Jifty::View::Declare -base;
+
+template stats => page {
+ my ($user) = get('user');
+ page_title is _('Statistics of user %1', $user->friendly_name );
+
+ set(type => 'updated'); show('recently');
+ set(type => 'created'); show('recently');
+};
+
+private template recently => sub {
+ my ($user, $type) = get('user', 'type');
+
+ my $method = 'recently_'. $type;
+ my $pages = Wifty::Model::PageCollection->$method;
+ $pages->limit( column => $type .'_by', value => $user->id );
+
+ h1 { $type eq 'updated'? _('Recenly updated') : _('Recently created') };
+ set(
+ pages => $pages,
+ id => 'recent-user-updates',
+ hide => [$type],
+ );
+ show('/page_list');
+};
+
+1;
commit 6acd1b80a19a48c0b31a362bf1785dc7b5558893
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Tue May 12 06:15:22 2009 +0000
add admin column to the users table
diff --git a/lib/Wifty/Model/User.pm b/lib/Wifty/Model/User.pm
index fd1e28f..b7866c0 100644
--- a/lib/Wifty/Model/User.pm
+++ b/lib/Wifty/Model/User.pm
@@ -3,6 +3,12 @@ package Wifty::Model::User;
use Jifty::DBI::Schema;
use Wifty::Record schema {
# column definitions
+ column admin =>
+ type is 'integer',
+ is mandatory,
+ default is 0,
+ since '0.0.22',
+ ;
};
# import columns: name, email and email_confirmed
commit 165d2dfe90e4eea084ef5cebac22d0d68c4c7550
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Tue May 12 06:18:05 2009 +0000
add BlackList model
diff --git a/lib/Wifty/Model/BlackList.pm b/lib/Wifty/Model/BlackList.pm
new file mode 100644
index 0000000..2527cbf
--- /dev/null
+++ b/lib/Wifty/Model/BlackList.pm
@@ -0,0 +1,95 @@
+package Wifty::Model::BlackList;
+use warnings;
+use strict;
+
+use List::Compare;
+
+use base qw/Wifty::Record/;
+use Jifty::DBI::Schema;
+use Wifty::Model::User;
+use Wifty::Model::RevisionCollection;
+
+use Jifty::Record schema {
+ column type =>
+ type is 'varchar(32)',
+ label is 'value',
+ is mandatory,
+ ;
+
+ column value =>
+ type is 'varchar(255)',
+ label is 'value',
+ is mandatory,
+ ;
+
+ column created =>
+ type is 'timestamp',
+ ;
+
+ column created_by =>
+ refers_to Wifty::Model::User,
+ ;
+};
+
+sub since { '0.0.23' }
+
+sub create {
+ my $self = shift;
+ my %args = (@_);
+ my $now = DateTime->now();
+ $args{'created'} ||= $now->ymd . " " . $now->hms;
+ $args{'created_by'} ||= $self->current_user? $self->current_user->user_object : undef;
+ return $self->SUPER::create(%args);
+}
+
+=head2 current_user_can ACTION
+
+=cut
+
+sub current_user_can {
+ my $self = shift;
+ my $type = shift;
+
+ return 1 if $self->current_user->is_superuser;
+ return 0 unless $self->current_user->id;
+ return 1 if $self->current_user->user_object->admin;
+ return 0;
+}
+
+sub update_list {
+ my $self = shift;
+ my %args = (@_);
+
+ my $current = Jifty->app_class('Model::BlackListCollection')->new;
+ $current->limit( column => 'type', value => $args{'type'} );
+
+ my $values = delete $args{'values'} || [];
+ unless ( @$values ) {
+ while ( my $e = $current->next ) {
+ my ($status, $msg) = $e->delete;
+ return ($status, $msg) unless $status;
+ }
+ return (1, "Done");
+ }
+
+ my $now = DateTime->now();
+ $args{'created'} ||= $now->ymd . " " . $now->hms;
+ $args{'created_by'} ||= $self->current_user? $self->current_user->user_object : undef;
+
+ my %current = map { $_->value => $_ } @$current;
+
+ my $lc = List::Compare->new(
+ '--unsorted', $values, [keys %current]
+ );
+ foreach my $e ( map $current{$_}, $lc->get_Ronly ) {
+ my ($status, $msg) = $e->delete;
+ return ($status, $msg) unless $status;
+ }
+ foreach my $e ( $lc->get_Lonly ) {
+ my ($status, $msg) = $self->create( %args, value => $e );
+ return ($status, $msg) unless $status;
+ }
+ return (1, 'Done');
+}
+
+1;
commit 52d7cdda198444b8c1d82472b3e348c8bbd2e1cf
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Tue May 12 06:19:18 2009 +0000
add EditIPsBlackList action
diff --git a/lib/Wifty/Action/EditIPsBlackList.pm b/lib/Wifty/Action/EditIPsBlackList.pm
new file mode 100644
index 0000000..e03d653
--- /dev/null
+++ b/lib/Wifty/Action/EditIPsBlackList.pm
@@ -0,0 +1,82 @@
+use strict;
+use warnings;
+
+=head1 NAME
+
+Wifty::Action::EditIPsBlackList
+
+=cut
+
+package Wifty::Action::EditIPsBlackList;
+use base qw/Wifty::Action Jifty::Action/;
+
+use Regexp::Common qw(RE_net_IPv4);
+my $re_ip = $RE{net}{IPv4};
+
+use Jifty::Param::Schema;
+use Jifty::Action schema {
+ param 'ips' =>
+ label is 'Block IPs',
+ render as 'Textarea',
+ default is defer {
+ my $list = Jifty->app_class('Model::BlackListCollection')->new;
+ $list->limit( column => 'type', value => 'IP' );
+ $list->order_by({ column => 'value', order => 'asc' });
+ return join "\n", map $_->value, @$list;
+ },
+ ;
+};
+
+sub canonicalize_ips {
+ my $self = shift;
+ my $ips = shift;
+
+ my @ips;
+ my @not_ips;
+
+ foreach my $part ( grep /\S/, split /[^0-9.]+/, $ips ) {
+ unless ( $part =~ /^$re_ip$/ ) {
+ push @not_ips, $part;
+ } else {
+ push @ips, $part;
+ }
+ }
+
+ $self->canonicalization_note('ips' => "Some values have been dropped as don't look like IP")
+ if @not_ips;
+
+ return \@ips;
+}
+
+=head2 take_action
+
+=cut
+
+sub take_action {
+ my $self = shift;
+
+ my $ips = $self->argument_value('ips');
+
+ my ($status, $msg) = Jifty->app_class('Model::BlackList')->new->update_list(
+ type => 'IP',
+ values => $ips,
+ );
+ Jifty->log->error("$status $msg");
+ return $self->result->error( $msg )
+ unless $status;
+
+ return $self->report_success;
+}
+
+=head2 report_success
+
+=cut
+
+sub report_success {
+ my $self = shift;
+ # Your success message here
+ $self->result->message('Success');
+}
+
+1;
+
commit af2911a6f942d18cd618a37ae9d6ef2da15c7527
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Tue May 12 06:22:32 2009 +0000
extend behaviour of current_user_can in array context
* fail the check if IP is blacklisted
diff --git a/lib/Wifty/Model/Page.pm b/lib/Wifty/Model/Page.pm
index 4a53260..7b45dea 100644
--- a/lib/Wifty/Model/Page.pm
+++ b/lib/Wifty/Model/Page.pm
@@ -144,10 +144,17 @@ sub current_user_can {
my $type = shift;
if ($type eq 'create' || $type eq 'update') {
- return 0 if
+ return wantarray? (0, 'require_auth'): 0 if
Jifty->config->app('RequireAuth')
&& !$self->current_user->is_superuser
&& !$self->current_user->id;
+
+ if ( my $ip = $ENV{'REMOTE_HOST'} ) {
+ my $block = Jifty->app_class('Model::BlackList')->load_by_cols(
+ type => 'IP', value => $ip
+ );
+ return wantarray? (0, 'black_ip'): 0 if $block && $block->id;
+ }
return 1;
} elsif($type eq 'read') {
return 1;
commit c2a50922419ccb9b2c0b17d0ddd87eb1e2cab9e8
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Tue May 12 06:23:39 2009 +0000
add /admin path
diff --git a/lib/Wifty/View.pm b/lib/Wifty/View.pm
index 38738d9..7f64afb 100644
--- a/lib/Wifty/View.pm
+++ b/lib/Wifty/View.pm
@@ -28,6 +28,14 @@ template 'view' => page {
render_param($viewer => 'content', label => '', render_mode => 'read');
};
+template 'admin' => page {
+ page_title is 'Admin wiki';
+ form {
+ render_action( new_action( class => 'EditIPsBlackList') );
+ form_submit(label => _("Update"));
+ }
+};
+
template 'edit' => page {
my ( $page, $revision, $viewer ) = get(qw(page revision viewer));
commit 59a57fbfd6a95e30bfcdc2f37ab40273544afb91
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Tue May 12 06:24:46 2009 +0000
dispatch /admin, mention in the menu
diff --git a/lib/Wifty/Dispatcher.pm b/lib/Wifty/Dispatcher.pm
index a01a08e..e06820a 100644
--- a/lib/Wifty/Dispatcher.pm
+++ b/lib/Wifty/Dispatcher.pm
@@ -1,6 +1,9 @@
package Wifty::Dispatcher;
use Jifty::Dispatcher -base;
+use strict;
+use warnings;
+
# Generic restrictions
under '/', run {
Jifty->api->deny('ConfirmEmail');
@@ -11,16 +14,26 @@ before '*', run {
$top->child( Home => url => "/", label => _("Home") );
$top->child( Recent => url => "/recent/changes", label => _("Recent Changes") );
$top->child( New => url => "/recent/additions", label => _("New") );
- if ( Jifty->web->current_user->id ) {
+ my $cu = Jifty->web->current_user;
+ if ( $cu->id ) {
$top->child(
Stats => url =>
- "/user/". Jifty->web->escape_uri(Jifty->web->current_user->username),
+ "/user/". Jifty->web->escape_uri($cu->username),
label => _("Stats"),
);
+ $top->child(
+ Admin => url => "/admin/", label => _("Administration"),
+ ) if $cu->user_object->admin;
}
$top->child( Search => url => "/search", label => _("Search") );
};
+before qr{^/admin\b}, run {
+ my $cu = Jifty->web->current_user;
+ abort(403) unless $cu->id;
+ abort(403) unless $cu->user_object->admin;
+};
+
# Default page
on '/', run {
redirect( '/view/HomePage');
commit e902df149b53fd95defa6932a72f98c23b2241ca
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Tue May 12 06:31:34 2009 +0000
redispatch errors
diff --git a/lib/Wifty/Dispatcher.pm b/lib/Wifty/Dispatcher.pm
index e06820a..9e78edb 100644
--- a/lib/Wifty/Dispatcher.pm
+++ b/lib/Wifty/Dispatcher.pm
@@ -155,6 +155,15 @@ on 'user/*' => run {
show('/user/stats');
};
+sub error {
+ my ($action, $reason) = @_;
+ foreach my $page ( map { "error/$_" } "$action/$reason", "$reason", "$action", "" ) {
+ next unless $Jifty::Dispatcher::Dispatcher->template_exists($page);
+ show($page);
+ return;
+ }
+}
+
sub setup_page_nav {
my ($prefix, $page, $rev) = @_;
commit 890ec91db454afd1568c811e38ecf26ea5ce312a
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Tue May 12 06:32:57 2009 +0000
use new extended current_user_can and error helper to
issue better errors
diff --git a/lib/Wifty/Dispatcher.pm b/lib/Wifty/Dispatcher.pm
index 9e78edb..d2667b6 100644
--- a/lib/Wifty/Dispatcher.pm
+++ b/lib/Wifty/Dispatcher.pm
@@ -41,15 +41,16 @@ on '/', run {
# Create a page
on '/create/*', run {
- set page => $1;
- set action => Jifty->web->new_action( class => 'CreatePage' );
-
- my $p = Wifty::Model::Page->new();
- if($p->current_user_can('create')) {
- show("/create");
- } else {
- show("/no_such_page");
- }
+ set page => $1;
+ set action => Jifty->web->new_action( class => 'CreatePage' );
+
+ my $p = Wifty::Model::Page->new;
+ my ($can, $reason) = $p->current_user_can('create');
+ unless ( $can ) {
+ error( create => $reason);
+ } else {
+ show("/create");
+ }
};
# View or edit a page
commit 4c7e1bebc23117cf0c3a37967b9471ae32bebe20
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Tue May 12 06:35:47 2009 +0000
show logging in hint only when user is not logged in
diff --git a/lib/Wifty/View.pm b/lib/Wifty/View.pm
index 7f64afb..3cd0489 100644
--- a/lib/Wifty/View.pm
+++ b/lib/Wifty/View.pm
@@ -52,9 +52,11 @@ template 'edit' => page {
div { attr { class => 'inline' };
unless ( $can_edit ) { p { attr { style => "width: 70%" };
outs(_("You don't have permission to edit this page."));
- outs(' '. _("Perhaps logging in would help."));
outs(' '. _("In the mean time, though, you're welcome to view and copy the source of this page."). ' ');
- tangent(url => '/login', label => _('Login'));
+ unless ( Jifty->web->current_user->id ) {
+ outs(' '. _("Perhaps logging in would help."));
+ tangent(url => '/login', label => _('Login'));
+ }
} }
form_next_page url => '/view/'.$page->name;
render_action $viewer, ['content'];
commit e5b8760b1988b9dca7f2ddfc87870253ca4e5bf1
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Tue May 12 06:38:13 2009 +0000
move page_not_found error into /error/... space
diff --git a/lib/Wifty/View.pm b/lib/Wifty/View.pm
index 3cd0489..4f9b991 100644
--- a/lib/Wifty/View.pm
+++ b/lib/Wifty/View.pm
@@ -90,19 +90,6 @@ template create => page {
};
};
-template no_such_page => page {
- my ($page) = get(qw(page));
-
- page_title is _("No '%1' page", $page);
-
- p {
- q{Unfortunately, you've tried to reach a page that doesn't exist }
- . q{yet, and you don't have permissions to create pages. If you }
- . tangent( url => '/login', label => 'login' )
- . q{, you'll be able to create new pages of your own.}
- }
-};
-
template history => page {
my ( $page, $revisions ) = get(qw(page revisions));
$revisions->do_search; # avoid count+fetch
@@ -353,5 +340,26 @@ private template 'diff/with_nav' => sub {
hr {}
};
+template 'error/create/require_auth' => page {
+ my ($page) = get(qw(page));
+
+ page_title is _("No '%1' page", $page);
+
+ p {
+ q{Unfortunately, you've tried to reach a page that doesn't exist }
+ . q{yet, and you don't have permissions to create pages. If you }
+ . tangent( url => '/login', label => 'login' )
+ . q{, you'll be able to create new pages of your own.}
+ }
+};
+
+template 'error/black_ip' => page {
+ page_title is _("You're blacklisted");
+
+ p {
+ q{Unfortunately, your IP address has been blocked.}
+ .q{ You can not change any content on this wiki.}
+ }
+};
1;
commit 166cf4bbf45945e4f175d6973465ea7237622d2d
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Tue May 12 07:25:02 2009 +0000
update DB version
diff --git a/etc/config.yml b/etc/config.yml
index 1760bf1..9a253f8 100644
--- a/etc/config.yml
+++ b/etc/config.yml
@@ -10,12 +10,12 @@ framework:
LogConfig: etc/log4perl.conf
Database:
+ Version: 0.0.23
AutoUpgrade: 1
CheckSchema: 1
Driver: SQLite
Host: localhost
User: postgres
- Version: 0.0.21
Password: ''
RequireSSL: 0
commit 41d53386158b2e38f98bad39005c482db6d19530
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Tue May 12 07:58:40 2009 +0000
add RevisionCollection with methods abstracted from Revision
diff --git a/lib/Wifty/Model/RevisionCollection.pm b/lib/Wifty/Model/RevisionCollection.pm
new file mode 100644
index 0000000..1474b95
--- /dev/null
+++ b/lib/Wifty/Model/RevisionCollection.pm
@@ -0,0 +1,54 @@
+use strict;
+use warnings;
+
+package Wifty::Model::RevisionCollection;
+use base qw(Jifty::Collection);
+
+use Scalar::Util qw(blessed);
+
+sub limit_by_page {
+ my $self = shift;
+ my $page = shift;
+ if ( blessed $page ) {
+ $page = $page->can('page')? $page->page->id : $page->id;
+ }
+ return $self->limit(
+ @_,
+ column => 'page',
+ value => $page,
+ quote_value => 0,
+ case_sensitive => 1
+ );
+}
+
+sub newer_than {
+ my $self = shift;
+ my $rev = shift;
+ $rev = $rev->id if blessed $rev;
+
+ return $self->limit(
+ @_,
+ column => 'id',
+ operator => '>',
+ value => $rev,
+ quote_value => 0,
+ case_sensitive => 1
+ );
+}
+
+sub older_than {
+ my $self = shift;
+ my $rev = shift;
+ $rev = $rev->id if blessed $rev;
+
+ return $self->limit(
+ @_,
+ column => 'id',
+ operator => '<',
+ value => $rev,
+ quote_value => 0,
+ case_sensitive => 1
+ );
+}
+
+1;
commit dfd77a30ce5506d6cb37c54e5613942922b1f2d6
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Tue May 12 08:00:49 2009 +0000
use new methods in the collection class
diff --git a/lib/Wifty/Model/Revision.pm b/lib/Wifty/Model/Revision.pm
index 53413bd..c97321b 100644
--- a/lib/Wifty/Model/Revision.pm
+++ b/lib/Wifty/Model/Revision.pm
@@ -33,7 +33,6 @@ use Wifty::Model::Page;
sub since { '0.0.5' }
-
sub create {
my $self = shift;
my %args = (@_);
@@ -49,19 +48,8 @@ sub previous {
return undef unless $self->id;
my $revisions = Wifty::Model::RevisionCollection->new;
- $revisions->limit(
- column => 'page',
- value => $self->page->id,
- quote_value => 0,
- case_sensitive => 1
- );
- $revisions->limit(
- column => 'id',
- operator => '<',
- value => $self->id,
- quote_value => 0,
- case_sensitive => 1
- );
+ $revisions->limit_by_page($self);
+ $revisions->older_than($self);
$revisions->order_by( { column => 'id', order => 'desc' } );
$revisions->rows_per_page(1);
return $revisions->first;
@@ -72,19 +60,8 @@ sub next {
return undef unless $self->id;
my $revisions = Wifty::Model::RevisionCollection->new;
- $revisions->limit(
- column => 'page',
- value => $self->page->id,
- quote_value => 0,
- case_sensitive => 1
- );
- $revisions->limit(
- column => 'id',
- operator => '>',
- value => $self->id,
- quote_value => 0,
- case_sensitive => 1
- );
+ $revisions->limit_by_page($self);
+ $revisions->newer_than($self);
$revisions->order_by( { column => 'id', order => 'asc' } );
$revisions->rows_per_page(1);
return $revisions->first;
@@ -144,6 +121,6 @@ sub current_user_can {
return 0;
}
$self->SUPER::current_user_can($right, @_);
-
}
+
1;
commit c79fc28a941f2db13c3420633516f0163f99e9e2
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Tue May 12 08:02:08 2009 +0000
add ip to revisions
diff --git a/etc/config.yml b/etc/config.yml
index 9a253f8..2dd4db3 100644
--- a/etc/config.yml
+++ b/etc/config.yml
@@ -10,7 +10,7 @@ framework:
LogConfig: etc/log4perl.conf
Database:
- Version: 0.0.23
+ Version: 0.0.24
AutoUpgrade: 1
CheckSchema: 1
Driver: SQLite
diff --git a/lib/Wifty/Model/Revision.pm b/lib/Wifty/Model/Revision.pm
index c97321b..f2bc1e7 100644
--- a/lib/Wifty/Model/Revision.pm
+++ b/lib/Wifty/Model/Revision.pm
@@ -17,11 +17,15 @@ use Jifty::Record schema {
render_as 'Wifty::Form::Field::WikiPage'
;
column created =>
- type is 'timestamp'
+ type is 'timestamp',
;
column created_by =>
refers_to Wifty::Model::User,
- since '0.0.20'
+ since '0.0.20',
+ ;
+ column ip =>
+ type is 'varchar(15)',
+ since '0.0.24',
;
};
@@ -38,7 +42,8 @@ sub create {
my %args = (@_);
my $now = DateTime->now();
- $args{'created'} = $now->ymd." ".$now->hms;
+ $args{'created'} ||= $now->ymd." ".$now->hms;
+ $args{'ip'} ||= $ENV{'REMOTE_HOST'};
$self->SUPER::create(%args);
}
commit 48ab906c6a89ed51ac0099c7b80203201f6db3a6
Author: Alex Vandiver <alexmv at bestpractical.com>
Date: Tue May 12 23:15:57 2009 +0000
Add List::Compare dep
diff --git a/Makefile.PL b/Makefile.PL
index f61b9b4..1dec409 100644
--- a/Makefile.PL
+++ b/Makefile.PL
@@ -6,5 +6,6 @@ requires('Text::Markdown');
requires('HTML::Scrubber');
requires('Text::Diff::HTML');
requires('XML::Atom::SimpleFeed');
+requires('List::Compare');
recommends('Text::KwikiFormatish');
WriteAll;
commit 57cab1bc1e3f85b7b7c810ef2dd2295cd053c480
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Tue May 12 23:19:56 2009 +0000
jifty still need mason handler for some stuff
diff --git a/etc/config.yml b/etc/config.yml
index 2dd4db3..c8a3ce4 100644
--- a/etc/config.yml
+++ b/etc/config.yml
@@ -39,6 +39,7 @@ framework:
Handlers:
- Jifty::View::Static::Handler
- Jifty::View::Declare::Handler
+ - Jifty::View::Mason::Handler
Web:
StaticRoot: share/web/static
commit f3eb1ab4a580ae74586e670e4723b9ff69049484
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Tue May 12 23:21:03 2009 +0000
explicit return on abort for clarity
diff --git a/lib/Wifty/Dispatcher.pm b/lib/Wifty/Dispatcher.pm
index d2667b6..00c2d98 100644
--- a/lib/Wifty/Dispatcher.pm
+++ b/lib/Wifty/Dispatcher.pm
@@ -30,8 +30,8 @@ before '*', run {
before qr{^/admin\b}, run {
my $cu = Jifty->web->current_user;
- abort(403) unless $cu->id;
- abort(403) unless $cu->user_object->admin;
+ return abort(403) unless $cu->id;
+ return abort(403) unless $cu->user_object->admin;
};
# Default page
commit 3c154ddf567cd035bd892556898aaba9d361ffe2
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Tue May 12 23:22:14 2009 +0000
only admins can see IPs
* show IP in the history
diff --git a/lib/Wifty/Model/Revision.pm b/lib/Wifty/Model/Revision.pm
index f2bc1e7..e1970c9 100644
--- a/lib/Wifty/Model/Revision.pm
+++ b/lib/Wifty/Model/Revision.pm
@@ -121,11 +121,22 @@ C<current_user_can> (which we inherit).
sub current_user_can {
my $self = shift;
my $right = shift;
-
- if ($right ne 'read' and not $self->current_user->is_superuser) {
- return 0;
+ my %args = @_;
+
+ return 1 if $self->current_user->is_superuser;
+
+ if ( $right eq 'read' ) {
+ return 0
+ if $args{'column'}
+ && $args{'column'} eq 'ip'
+ && !(
+ $self->current_user->id
+ && $self->current_user->user_object->admin
+ );
+ return 1;
}
- $self->SUPER::current_user_can($right, @_);
+
+ $self->SUPER::current_user_can($right, %args);
}
1;
diff --git a/lib/Wifty/View.pm b/lib/Wifty/View.pm
index 4f9b991..4af28d8 100644
--- a/lib/Wifty/View.pm
+++ b/lib/Wifty/View.pm
@@ -104,6 +104,9 @@ template history => page {
);
outs(' (');
user($rev->created_by);
+ if ( my $ip = $rev->ip ) { # only admins can see IPs
+ outs( ' - '. $ip );
+ }
outs(')');
outs( ' ', _('%1 bytes', length $rev->content ) );
render_region(
commit 5faf176969022fbb5b2019d9249aca161c392c6b
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Tue May 12 23:23:46 2009 +0000
announce new requirements in the makefile
diff --git a/Makefile.PL b/Makefile.PL
index 1dec409..5059089 100644
--- a/Makefile.PL
+++ b/Makefile.PL
@@ -7,5 +7,7 @@ requires('HTML::Scrubber');
requires('Text::Diff::HTML');
requires('XML::Atom::SimpleFeed');
requires('List::Compare');
+requires('Regexp::Common');
+requires('Scalar::Util');
recommends('Text::KwikiFormatish');
WriteAll;
commit 94e56c4dee5911b28346972be17d675bee181f7e
Author: sunnavy <sunnavy at bestpractical.com>
Date: Fri Mar 19 00:39:28 2010 +0000
update login tests
diff --git a/t/02-login.t b/t/02-login.t
index 1136987..3dc21bd 100644
--- a/t/02-login.t
+++ b/t/02-login.t
@@ -22,8 +22,8 @@ ok($URL, "Started a test server");
my $mech = Jifty::Test::WWW::Mechanize->new();
$mech->get_ok($URL, "Got the homepage");
-ok($mech->find_link(text_regex => qr/Sign in/), 'Got the signin link');
-$mech->follow_link_ok(text_regex => qr/Sign in/);
+ok($mech->find_link(text_regex => qr/Login/), 'Got the login link');
+$mech->follow_link_ok(text_regex => qr/Login/);
sub try_login {
my $mech = shift;
@@ -47,7 +47,7 @@ $mech->content_contains("It doesn't look like there's an account by that name",
# With a blank password
try_login($mech, 'someuser at localost', '');
-$mech->content_contains('Please fill in this field','Login fails with no password');
+$mech->content_contains("fill in the 'password' field",'Login fails with no password');
# With the wrong password
try_login($mech, 'someuser at localhost', 'badmemory');
@@ -56,3 +56,4 @@ $mech->content_contains('may have mistyped','Login fails with wrong password');
# Try a correct login
try_login($mech, 'someuser at localhost', 'sekrit');
$mech->content_contains('Welcome back','Logged in');
+
-----------------------------------------------------------------------
More information about the Bps-public-commit
mailing list