<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array">
    <added>
      <filename>config/boot.rb</filename>
    </added>
    <added>
      <filename>lib/tasks/release.rake</filename>
    </added>
    <added>
      <filename>lib/tasks/schemas.rake</filename>
    </added>
    <added>
      <filename>lib/tasks/sweep_cache.rake</filename>
    </added>
    <added>
      <filename>public/javascripts/scriptaculous.js</filename>
    </added>
    <added>
      <filename>public/javascripts/slider.js</filename>
    </added>
    <added>
      <filename>script/performance/benchmarker</filename>
    </added>
    <added>
      <filename>script/performance/profiler</filename>
    </added>
  </added>
  <modified type="array">
    <modified>
      <diff>@@ -1,360 +1,10 @@
+# Add your own tasks in files placed in lib/tasks ending in .rake,
+# for example lib/tasks/switchtower.rake, and they will automatically be available to Rake.
+
+require(File.join(File.dirname(__FILE__), 'config', 'boot'))
+
 require 'rake'
 require 'rake/testtask'
 require 'rake/rdoctask'
-require 'rake/gempackagetask'
-require 'rake/contrib/rubyforgepublisher'
-
-PKG_VERSION = &quot;2.5.5&quot;
-PKG_NAME = &quot;typo&quot;
-PKG_FILE_NAME = &quot;#{PKG_NAME}-#{PKG_VERSION}&quot;
-RUBY_FORGE_PROJECT = 'typo'
-RUBY_FORGE_USER = 'xal'
-RELEASE_NAME = &quot;#{PKG_NAME}-#{PKG_VERSION}&quot;
-
-$VERBOSE = nil
-TEST_CHANGES_SINCE = Time.now - 600
-
-desc &quot;Run all the tests on a fresh test database&quot;
-task :default =&gt; [ :test_units, :test_functional ]
-
-
-desc 'Require application environment.'
-task :environment do
-  unless defined? RAILS_ROOT
-    require File.dirname(__FILE__) + '/config/environment'
-  end
-end
-
-desc &quot;Generate API documentation, show coding stats&quot;
-task :doc =&gt; [ :appdoc, :stats ]
-
-
-# Look up tests for recently modified sources.
-def recent_tests(source_pattern, test_path, touched_since = 10.minutes.ago)
-  FileList[source_pattern].map do |path|
-    if File.mtime(path) &gt; touched_since
-      test = &quot;#{test_path}/#{File.basename(path, '.rb')}_test.rb&quot;
-      test if File.exists?(test)
-    end
-  end.compact
-end
-
-desc 'Test recent changes.'
-Rake::TestTask.new(:recent =&gt; [ :clone_structure_to_test ]) do |t|
-  since = TEST_CHANGES_SINCE
-  touched = FileList['test/**/*_test.rb'].select { |path| File.mtime(path) &gt; since } +
-    recent_tests('app/models/*.rb', 'test/unit', since) +
-    recent_tests('app/controllers/*.rb', 'test/functional', since)
-
-  t.libs &lt;&lt; 'test'
-  t.verbose = false
-  t.test_files = touched.uniq
-end
-task :test_recent =&gt; [ :clone_structure_to_test ]
-
-desc &quot;Run the unit tests in test/unit&quot;
-Rake::TestTask.new(&quot;test_units&quot;) { |t|
-  t.libs &lt;&lt; &quot;test&quot;
-  t.pattern = 'test/unit/**/*_test.rb'
-  t.verbose = false
-}
-task :test_units =&gt; [ :clone_structure_to_test ]
-
-desc &quot;Run the functional tests in test/functional&quot;
-Rake::TestTask.new(&quot;test_functional&quot;) { |t|
-  t.libs &lt;&lt; &quot;test&quot;
-  t.pattern = 'test/functional/**/*_test.rb'
-  t.verbose = false
-}
-task :test_functional =&gt; [ :clone_structure_to_test ]
-
-desc &quot;Generate documentation for the application&quot;
-Rake::RDocTask.new(&quot;appdoc&quot;) { |rdoc|
-  rdoc.rdoc_dir = 'doc/app'
-  rdoc.title    = &quot;Rails Application Documentation&quot;
-  rdoc.options &lt;&lt; '--line-numbers --inline-source'
-  rdoc.rdoc_files.include('doc/README_FOR_APP')
-  rdoc.rdoc_files.include('app/**/*.rb')
-}
-
-desc &quot;Generate documentation for the Rails framework&quot;
-Rake::RDocTask.new(&quot;apidoc&quot;) { |rdoc|
-  rdoc.rdoc_dir = 'doc/api'
-  rdoc.template = &quot;#{ENV['template']}.rb&quot; if ENV['template']
-  rdoc.title    = &quot;Rails Framework Documentation&quot;
-  rdoc.options &lt;&lt; '--line-numbers --inline-source'
-  rdoc.rdoc_files.include('README')
-  rdoc.rdoc_files.include('CHANGELOG')
-  rdoc.rdoc_files.include('vendor/rails/railties/CHANGELOG')
-  rdoc.rdoc_files.include('vendor/rails/railties/MIT-LICENSE')
-  rdoc.rdoc_files.include('vendor/rails/activerecord/README')
-  rdoc.rdoc_files.include('vendor/rails/activerecord/CHANGELOG')
-  rdoc.rdoc_files.include('vendor/rails/activerecord/lib/active_record/**/*.rb')
-  rdoc.rdoc_files.exclude('vendor/rails/activerecord/lib/active_record/vendor/*')
-  rdoc.rdoc_files.include('vendor/rails/actionpack/README')
-  rdoc.rdoc_files.include('vendor/rails/actionpack/CHANGELOG')
-  rdoc.rdoc_files.include('vendor/rails/actionpack/lib/action_controller/**/*.rb')
-  rdoc.rdoc_files.include('vendor/rails/actionpack/lib/action_view/**/*.rb')
-  rdoc.rdoc_files.include('vendor/rails/actionmailer/README')
-  rdoc.rdoc_files.include('vendor/rails/actionmailer/CHANGELOG')
-  rdoc.rdoc_files.include('vendor/rails/actionmailer/lib/action_mailer/base.rb')
-  rdoc.rdoc_files.include('vendor/rails/actionwebservice/README')
-  rdoc.rdoc_files.include('vendor/rails/actionwebservice/CHANGELOG')
-  rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service.rb')
-  rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service/*.rb')
-  rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service/api/*.rb')
-  rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service/client/*.rb')
-  rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service/container/*.rb')
-  rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service/dispatcher/*.rb')
-  rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service/protocol/*.rb')
-  rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service/support/*.rb')
-  rdoc.rdoc_files.include('vendor/rails/activesupport/README')
-  rdoc.rdoc_files.include('vendor/rails/activesupport/CHANGELOG')
-  rdoc.rdoc_files.include('vendor/rails/activesupport/lib/active_support/**/*.rb')
-}
-
-desc &quot;Report code statistics (KLOCs, etc) from the application&quot;
-task :stats =&gt; [ :environment ] do
-  require 'code_statistics'
-  CodeStatistics.new(
-    [&quot;Helpers&quot;, &quot;app/helpers&quot;], 
-    [&quot;Controllers&quot;, &quot;app/controllers&quot;], 
-    [&quot;APIs&quot;, &quot;app/apis&quot;],
-    [&quot;Components&quot;, &quot;components&quot;],
-    [&quot;Functionals&quot;, &quot;test/functional&quot;],
-    [&quot;Models&quot;, &quot;app/models&quot;],
-    [&quot;Units&quot;, &quot;test/unit&quot;]
-  ).to_s
-end
-
-desc &quot;Recreate the test databases from the development structure&quot;
-task :clone_structure_to_test =&gt; [ :db_structure_dump, :purge_test_database ] do
-  abcs = ActiveRecord::Base.configurations
-  case abcs[&quot;test&quot;][&quot;adapter&quot;]
-    when  &quot;mysql&quot;
-      ActiveRecord::Base.establish_connection(:test)
-      ActiveRecord::Base.connection.execute('SET foreign_key_checks = 0')
-      IO.readlines(&quot;db/#{RAILS_ENV}_structure.sql&quot;).join.split(&quot;\n\n&quot;).each do |table|
-        ActiveRecord::Base.connection.execute(table)
-      end
-    when &quot;postgresql&quot;
-      ENV['PGHOST']     = abcs[&quot;test&quot;][&quot;host&quot;] if abcs[&quot;test&quot;][&quot;host&quot;]
-      ENV['PGPORT']     = abcs[&quot;test&quot;][&quot;port&quot;].to_s if abcs[&quot;test&quot;][&quot;port&quot;]
-      ENV['PGPASSWORD'] = abcs[&quot;test&quot;][&quot;password&quot;]
-      `psql -U &quot;#{abcs[&quot;test&quot;][&quot;username&quot;]}&quot; -f db/#{RAILS_ENV}_structure.sql #{abcs[&quot;test&quot;][&quot;database&quot;]}`
-    when &quot;sqlite&quot;, &quot;sqlite3&quot;
-      `#{abcs[RAILS_ENV][&quot;adapter&quot;]} #{abcs[&quot;test&quot;][&quot;dbfile&quot;]} &lt; db/#{RAILS_ENV}_structure.sql`
-    else 
-      raise &quot;Unknown database adapter '#{abcs[&quot;test&quot;][&quot;adapter&quot;]}'&quot;
-  end
-end
-
-desc &quot;Dump the database structure to a SQL file&quot;
-task :db_structure_dump =&gt; :environment do
-  abcs = ActiveRecord::Base.configurations
-  case abcs[RAILS_ENV][&quot;adapter&quot;] 
-    when &quot;mysql&quot;
-      ActiveRecord::Base.establish_connection(abcs[RAILS_ENV])
-      File.open(&quot;db/#{RAILS_ENV}_structure.sql&quot;, &quot;w+&quot;) { |f| f &lt;&lt; ActiveRecord::Base.connection.structure_dump }
-    when &quot;postgresql&quot;
-      ENV['PGHOST']     = abcs[RAILS_ENV][&quot;host&quot;] if abcs[RAILS_ENV][&quot;host&quot;]
-      ENV['PGPORT']     = abcs[RAILS_ENV][&quot;port&quot;].to_s if abcs[RAILS_ENV][&quot;port&quot;]
-      ENV['PGPASSWORD'] = abcs[RAILS_ENV][&quot;password&quot;]
-      `pg_dump -U &quot;#{abcs[RAILS_ENV][&quot;username&quot;]}&quot; -s -x -f db/#{RAILS_ENV}_structure.sql #{abcs[RAILS_ENV][&quot;database&quot;]}`
-    when &quot;sqlite&quot;, &quot;sqlite3&quot;
-      `#{abcs[RAILS_ENV][&quot;adapter&quot;]} #{abcs[RAILS_ENV][&quot;dbfile&quot;]} .schema &gt; db/#{RAILS_ENV}_structure.sql`
-    else 
-      raise &quot;Unknown database adapter '#{abcs[&quot;test&quot;][&quot;adapter&quot;]}'&quot;
-  end
-end
-
-desc &quot;Empty the test database&quot;
-task :purge_test_database =&gt; :environment do
-  abcs = ActiveRecord::Base.configurations
-  case abcs[&quot;test&quot;][&quot;adapter&quot;]
-    when &quot;mysql&quot;
-      ActiveRecord::Base.establish_connection(abcs[RAILS_ENV])
-      ActiveRecord::Base.connection.recreate_database(abcs[&quot;test&quot;][&quot;database&quot;])
-    when &quot;postgresql&quot;
-      ENV['PGHOST']     = abcs[&quot;test&quot;][&quot;host&quot;] if abcs[&quot;test&quot;][&quot;host&quot;]
-      ENV['PGPORT']     = abcs[&quot;test&quot;][&quot;port&quot;].to_s if abcs[&quot;test&quot;][&quot;port&quot;]
-      ENV['PGPASSWORD'] = abcs[&quot;test&quot;][&quot;password&quot;]
-      `dropdb -U &quot;#{abcs[&quot;test&quot;][&quot;username&quot;]}&quot; #{abcs[&quot;test&quot;][&quot;database&quot;]}`
-      `createdb -T template0 -U &quot;#{abcs[&quot;test&quot;][&quot;username&quot;]}&quot; #{abcs[&quot;test&quot;][&quot;database&quot;]}`
-    when &quot;sqlite&quot;,&quot;sqlite3&quot;
-      File.delete(abcs[&quot;test&quot;][&quot;dbfile&quot;]) if File.exist?(abcs[&quot;test&quot;][&quot;dbfile&quot;])
-    else 
-      raise &quot;Unknown database adapter '#{abcs[&quot;test&quot;][&quot;adapter&quot;]}'&quot;
-  end
-end
-
-
-spec = Gem::Specification.new do |s|
-  s.name = PKG_NAME
-  s.version = PKG_VERSION
-  s.summary = &quot;Modern weblog engine.&quot;
-  s.has_rdoc = false
-  s.files  = Dir.glob('**/*', File::FNM_DOTMATCH).reject do |f| 
-     [ /\.$/, /sqlite$/, /\.log$/, /^pkg/, /\.svn/, /^vendor\/rails/, 
-     /^public\/(files|xml|articles|pages|index.html)/, 
-     /^public\/(stylesheets|javascripts|images)\/theme/, /\~$/, 
-     /\/\._/, /\/#/ ].any? {|regex| f =~ regex }
-  end
-  s.require_path = '.'
-  s.author = &quot;Tobias Luetke&quot;
-  s.email = &quot;tobi@leetsoft.com&quot;
-  s.homepage = &quot;http://typo.leetsoft.com&quot;  
-  s.rubyforge_project = &quot;typo&quot;
-end
-
-Rake::GemPackageTask.new(spec) do |p|
-  p.gem_spec = spec
-  p.need_tar = true
-  p.need_zip = true
-end
-
-desc &quot;Migrate the database according to the migrate scripts in db/migrate (only supported on PG/MySQL). A specific version can be targetted with VERSION=x&quot;
-task :migrate =&gt; :environment do
-  ActiveRecord::Migrator.migrate(File.dirname(__FILE__) + '/db/migrate/', ENV[&quot;VERSION&quot;] ? ENV[&quot;VERSION&quot;].to_i : nil)
-end
-
-desc &quot;Create new db/schema files using the migrations.  Requires schema_generator.&quot;
-task :schemas do
-  `./script/generate schema --force`
-  `sed s/InnoDB/MyISAM/ &lt; db/schema.mysql.sql &gt; db/schema.mysql-v3.sql`
-end
-
-desc &quot;Force a sweeping run of typo's static page caches (all of them!)&quot;
-task :sweep_cache =&gt; :environment do
-  PageCache.sweep_all
-  puts &quot;Cache swept.&quot;
-end
-
-desc &quot;Publish the zip/tgz&quot;
-task :leetsoft_upload =&gt; [:package] do
-  Rake::SshFilePublisher.new(&quot;leetsoft.com&quot;, &quot;dist/pkg&quot;, &quot;pkg&quot;, &quot;#{PKG_FILE_NAME}.zip&quot;).upload
-  Rake::SshFilePublisher.new(&quot;leetsoft.com&quot;, &quot;dist/pkg&quot;, &quot;pkg&quot;, &quot;#{PKG_FILE_NAME}.tgz&quot;).upload
-end
-
-desc &quot;Publish the release files to RubyForge.&quot;
-task :tag_svn do
-  system(&quot;svn cp svn://leetsoft.com/typo/trunk svn://leetsoft.com/typo/tags/release_#{PKG_VERSION.gsub(/\./,'_')} -m 'tag release #{PKG_VERSION}'&quot;)
-end
-
-desc &quot;Publish the release files to RubyForge.&quot;
-task :rubyforge_upload =&gt; [:package] do
-  files = [&quot;tgz&quot;, &quot;zip&quot;].map { |ext| &quot;pkg/#{PKG_FILE_NAME}.#{ext}&quot; }
-
-  if RUBY_FORGE_PROJECT then
-    require 'net/http'
-    require 'open-uri'
-
-    project_uri = &quot;http://rubyforge.org/projects/#{RUBY_FORGE_PROJECT}/&quot;
-    project_data = open(project_uri) { |data| data.read }
-    group_id = project_data[/[?&amp;]group_id=(\d+)/, 1]
-    raise &quot;Couldn't get group id&quot; unless group_id
-
-    # This echos password to shell which is a bit sucky
-    if ENV[&quot;RUBY_FORGE_PASSWORD&quot;]
-      password = ENV[&quot;RUBY_FORGE_PASSWORD&quot;]
-    else
-      print &quot;#{RUBY_FORGE_USER}@rubyforge.org's password: &quot;
-      password = STDIN.gets.chomp
-    end
-
-    login_response = Net::HTTP.start(&quot;rubyforge.org&quot;, 80) do |http|
-      data = [
-        &quot;login=1&quot;,
-        &quot;form_loginname=#{RUBY_FORGE_USER}&quot;,
-        &quot;form_pw=#{password}&quot;
-      ].join(&quot;&amp;&quot;)
-      http.post(&quot;/account/login.php&quot;, data)
-    end
-
-    cookie = login_response[&quot;set-cookie&quot;]
-    raise &quot;Login failed&quot; unless cookie
-    headers = { &quot;Cookie&quot; =&gt; cookie }
-
-    release_uri = &quot;http://rubyforge.org/frs/admin/?group_id=#{group_id}&quot;
-    release_data = open(release_uri, headers) { |data| data.read }
-    package_id = release_data[/[?&amp;]package_id=(\d+)/, 1]
-    raise &quot;Couldn't get package id&quot; unless package_id
-
-    first_file = true
-    release_id = &quot;&quot;
-
-    files.each do |filename|
-      basename  = File.basename(filename)
-      file_ext  = File.extname(filename)
-      file_data = File.open(filename, &quot;rb&quot;) { |file| file.read }
-
-      puts &quot;Releasing #{basename}...&quot;
-
-      release_response = Net::HTTP.start(&quot;rubyforge.org&quot;, 80) do |http|
-        release_date = Time.now.strftime(&quot;%Y-%m-%d %H:%M&quot;)
-        type_map = {
-          &quot;.zip&quot;    =&gt; &quot;3000&quot;,
-          &quot;.tgz&quot;    =&gt; &quot;3110&quot;,
-          &quot;.gz&quot;     =&gt; &quot;3110&quot;,
-          &quot;.gem&quot;    =&gt; &quot;1400&quot;
-        }; type_map.default = &quot;9999&quot;
-        type = type_map[file_ext]
-        boundary = &quot;rubyqMY6QN9bp6e4kS21H4y0zxcvoor&quot;
-
-        query_hash = if first_file then
-          {
-            &quot;group_id&quot; =&gt; group_id,
-            &quot;package_id&quot; =&gt; package_id,
-            &quot;release_name&quot; =&gt; RELEASE_NAME,
-            &quot;release_date&quot; =&gt; release_date,
-            &quot;type_id&quot; =&gt; type,
-            &quot;processor_id&quot; =&gt; &quot;8000&quot;, # Any
-            &quot;release_notes&quot; =&gt; &quot;&quot;,
-            &quot;release_changes&quot; =&gt; &quot;&quot;,
-            &quot;preformatted&quot; =&gt; &quot;1&quot;,
-            &quot;submit&quot; =&gt; &quot;1&quot;
-          }
-        else
-          {
-            &quot;group_id&quot; =&gt; group_id,
-            &quot;release_id&quot; =&gt; release_id,
-            &quot;package_id&quot; =&gt; package_id,
-            &quot;step2&quot; =&gt; &quot;1&quot;,
-            &quot;type_id&quot; =&gt; type,
-            &quot;processor_id&quot; =&gt; &quot;8000&quot;, # Any
-            &quot;submit&quot; =&gt; &quot;Add This File&quot;
-          }
-        end
-
-        query = &quot;?&quot; + query_hash.map do |(name, value)|
-          [name, URI.encode(value)].join(&quot;=&quot;)
-        end.join(&quot;&amp;&quot;)
-
-        data = [
-          &quot;--&quot; + boundary,
-          &quot;Content-Disposition: form-data; name=\&quot;userfile\&quot;; filename=\&quot;#{basename}\&quot;&quot;,
-          &quot;Content-Type: application/octet-stream&quot;,
-          &quot;Content-Transfer-Encoding: binary&quot;,
-          &quot;&quot;, file_data, &quot;&quot;
-          ].join(&quot;\x0D\x0A&quot;)
-
-        release_headers = headers.merge(
-          &quot;Content-Type&quot; =&gt; &quot;multipart/form-data; boundary=#{boundary}&quot;
-        )
-
-        target = first_file ? &quot;/frs/admin/qrs.php&quot; : &quot;/frs/admin/editrelease.php&quot;
-        http.post(target + query, data, release_headers)
-      end
-
-      if first_file then
-        release_id = release_response.body[/release_id=(\d+)/, 1]
-        raise(&quot;Couldn't get release id&quot;) unless release_id
-      end
-
-      first_file = false
-    end
-  end
-end
 
-desc &quot;Upload the package to leetsoft, rubyforge and tag the release in svn&quot;
-task :release =&gt; [:sweep_cache, :package, :leetsoft_upload, :rubyforge_upload, :tag_svn ]
+require 'tasks/rails'
\ No newline at end of file</diff>
      <filename>Rakefile</filename>
    </modified>
    <modified>
      <diff>@@ -44,7 +44,8 @@ class Theme
   end  
 
   def self.search_theme_directory
-    Dir.glob(&quot;#{themes_root}/[-_a-zA-Z0-9]*&quot;).collect do |file|
+    glob = &quot;#{themes_root}/[-_a-zA-Z0-9]*&quot;
+    Dir.glob(glob).collect do |file|
       file if File.directory?(file)      
     end.compact
   end  </diff>
      <filename>app/models/theme.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,43 +1,70 @@
-RAILS_ROOT = File.dirname(__FILE__) + &quot;/../&quot;
-RAILS_ENV  = ENV['RAILS_ENV'] || 'development'
-
-
-# Mocks first.
-ADDITIONAL_LOAD_PATHS = [&quot;#{RAILS_ROOT}/test/mocks/#{RAILS_ENV}&quot;]
-
-# Then model subdirectories.
-ADDITIONAL_LOAD_PATHS.concat(Dir[&quot;#{RAILS_ROOT}/app/models/[_a-z]*&quot;])
-ADDITIONAL_LOAD_PATHS.concat(Dir[&quot;#{RAILS_ROOT}/components/[_a-z]*&quot;])
-
-# Followed by the standard includes.
-ADDITIONAL_LOAD_PATHS.concat %w(
-  app 
-  app/models 
-  app/controllers 
-  app/helpers 
-  app/apis 
-  components 
-  config 
-  lib 
-  vendor 
-  vendor/rubypants
-  vendor/redcloth/lib
-  vendor/bluecloth/lib
-  vendor/flickr
-  vendor/syntax/lib
-  vendor/sparklines/lib
-  vendor/uuidtools/lib
-  vendor/rails/railties
-  vendor/rails/railties/lib
-  vendor/rails/actionpack/lib
-  vendor/rails/activesupport/lib
-  vendor/rails/activerecord/lib
-  vendor/rails/actionmailer/lib
-  vendor/rails/actionwebservice/lib
-).map { |dir| &quot;#{RAILS_ROOT}/#{dir}&quot; }.select { |dir| File.directory?(dir) }
-
-# Prepend to $LOAD_PATH
-ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) }
+# Be sure to restart your webserver when you modify this file.
+
+# Uncomment below to force Rails into production mode
+# (Use only when you can't set environment variables through your web/app server)
+# ENV['RAILS_ENV'] = 'production'
+
+# Bootstrap the Rails environment, frameworks, and default configuration
+require File.join(File.dirname(__FILE__), 'boot')
+
+Rails::Initializer.run do |config|
+  # Skip frameworks you're not going to use
+  # config.frameworks -= [ :action_web_service, :action_mailer ]
+
+  # Add additional load paths for your own custom dirs
+  # config.load_paths += %W( #{RAILS_ROOT}/app/services )
+  config.load_paths += %W(
+    vendor/rubypants
+    vendor/redcloth/lib
+    vendor/bluecloth/lib
+    vendor/flickr
+    vendor/syntax/lib
+    vendor/sparklines/lib
+    vendor/uuidtools/lib
+    vendor/rails/railties
+    vendor/rails/railties/lib
+    vendor/rails/actionpack/lib
+    vendor/rails/activesupport/lib
+    vendor/rails/activerecord/lib
+    vendor/rails/actionmailer/lib
+    vendor/rails/actionwebservice/lib
+  ).map {|dir| &quot;#{RAILS_ROOT}/#{dir}&quot;}.select { |dir| File.directory?(dir) }
+
+  # Force all environments to use the same logger level 
+  # (by default production uses :info, the others :debug)
+  # config.log_level = :debug
+
+  # Use the database for sessions instead of the file system
+  # (create the session table with 'rake create_sessions_table')
+  config.action_controller.session_store = :active_record_store
+
+  # Enable page/fragment caching by setting a file-based store
+  # (remember to create the caching directory and make it readable to the application)
+  config.action_controller.fragment_cache_store = :file_store, &quot;#{RAILS_ROOT}/cache&quot;
+
+  # Activate observers that should always be running
+  # config.active_record.observers = :cacher, :garbage_collector
+
+  # Make Active Record use UTC-base instead of local time
+  # config.active_record.default_timezone = :utc
+  
+  # Use Active Record's schema dumper instead of SQL when creating the test database
+  # (enables use of different database adapters for development and test environments)
+  # config.active_record.schema_format = :ruby
+
+  # See Rails::Configuration for more options
+end
+
+# Add new inflection rules using the following format 
+# (all these examples are active by default):
+# Inflector.inflections do |inflect|
+#   inflect.plural /^(ox)$/i, '\1en'
+#   inflect.singular /^(ox)en/i, '\1'
+#   inflect.irregular 'person', 'people'
+#   inflect.uncountable %w( fish sheep )
+# end
+
+# Include your application configuration below
 
 # Load included libraries.
 require 'redcloth' 
@@ -46,56 +73,11 @@ require 'rubypants'
 require 'flickr'
 require 'uuidtools'
 
-# Require Rails libraries.
-require 'rubygems' unless File.directory?(&quot;#{RAILS_ROOT}/vendor/rails&quot;)
-
-# force Rails 0.13.1
-#require_gem 'activesupport',     '= 1.1.1'
-#require_gem 'activerecord',      '= 1.11.1'
-#require_gem 'actionpack',  '= 1.9.1'
-#require_gem 'actionmailer',      '= 1.0.1'
-#require_gem 'actionwebservice', '= 0.8.1'
-
-require 'active_support'
-require 'active_record'
-require 'action_controller'
-require 'action_mailer'
-require 'action_web_service'
-
-# Environment-specific configuration.
 require_dependency 'migrator'
 require_dependency 'renderfix'
 require_dependency 'rails_patch/components'
 require_dependency 'theme'
 require_dependency 'login_system'
-require_dependency &quot;environments/#{RAILS_ENV}&quot;
-ActiveRecord::Base.configurations = File.open(&quot;#{RAILS_ROOT}/config/database.yml&quot;) { |f| YAML::load(f) }
-ActiveRecord::Base.establish_connection
-
-
-# Configure defaults if the included environment did not.
-begin
-  RAILS_DEFAULT_LOGGER = Logger.new(&quot;#{RAILS_ROOT}/log/#{RAILS_ENV}.log&quot;)
-  RAILS_DEFAULT_LOGGER.level = (RAILS_ENV == 'production' ? Logger::INFO : Logger::DEBUG)
-rescue StandardError
-  RAILS_DEFAULT_LOGGER = Logger.new(STDERR)
-  RAILS_DEFAULT_LOGGER.level = Logger::WARN
-  RAILS_DEFAULT_LOGGER.warn(
-    &quot;Rails Error: Unable to access log file. Please ensure that log/#{RAILS_ENV}.log exists and is chmod 0666. &quot; +
-    &quot;The log level has been raised to WARN and the output directed to STDERR until the problem is fixed.&quot;
-  )
-end
-
-[ActiveRecord, ActionController, ActionMailer].each { |mod| mod::Base.logger ||= RAILS_DEFAULT_LOGGER }
-[ActionController, ActionMailer].each { |mod| mod::Base.template_root ||= &quot;#{RAILS_ROOT}/app/views/&quot; }
-ActionController::Routing::Routes.reload
-
-Controllers = Dependencies::LoadingModule.root(
-  File.join(RAILS_ROOT, 'app', 'controllers'),
-  File.join(RAILS_ROOT, 'components')
-)
-
-# Include your app's configuration here:
 $KCODE = 'u'
 require_dependency 'jcode'
 require_dependency 'aggregations/audioscrobbler'
@@ -110,9 +92,6 @@ require_dependency 'spam_protection'
 require_dependency 'xmlrpc_fix'
 require_dependency 'guid'
 
-ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS.update(:database_manager =&gt; CGI::Session::ActiveRecordStore)      
-ActionController::Base.fragment_cache_store = ActionController::Caching::Fragments::FileStore.new(&quot;#{RAILS_ROOT}/cache/fragment/&quot;)
-
 ActiveSupport::CoreExtensions::Time::Conversions::DATE_FORMATS.merge!(
   :long_weekday =&gt; '%a %B %e, %Y %H:%M'
 )</diff>
      <filename>config/environment.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,7 +1,17 @@
-require 'active_support/whiny_nil'
+# In the development environment your application's code is reloaded on
+# every request.  This slows down response time but is perfect for development
+# since you don't have to restart the webserver when you make code changes.
+config.cache_classes     = false
 
-Dependencies.mechanism                             = :load
-ActionController::Base.consider_all_requests_local = true
-ActionController::Base.perform_caching             = false
-Migrator.offer_migration_when_available            = true
-BREAKPOINT_SERVER_PORT = 42531
\ No newline at end of file
+# Log error messages when you accidentally call methods on nil.
+config.whiny_nils        = true
+
+# Enable the breakpoint server that script/breakpointer connects to
+config.breakpoint_server = true
+
+# Show full error reports and disable caching
+config.action_controller.consider_all_requests_local = true
+config.action_controller.perform_caching             = false
+
+# Don't care if the mailer can't send
+config.action_mailer.raise_delivery_errors = false</diff>
      <filename>config/environments/development.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,4 +1,19 @@
-Dependencies.mechanism                             = :require
-ActionController::Base.consider_all_requests_local = false
-ActionController::Base.perform_caching             = true
-Migrator.offer_migration_when_available            = true
\ No newline at end of file
+# The production environment is meant for finished, &quot;live&quot; apps.
+# Code is not reloaded between requests
+config.cache_classes = true
+
+# Use a different logger for distributed setups
+# config.logger        = SyslogLogger.new
+
+
+# Full error reports are disabled and caching is turned on
+config.action_controller.consider_all_requests_local = false
+config.action_controller.perform_caching             = true
+
+# Enable serving of images, stylesheets, and javascripts from an asset server
+# config.action_controller.asset_host                  = &quot;http://assets.example.com&quot;
+
+# Disable delivery errors if you bad email addresses should just be ignored
+# config.action_mailer.raise_delivery_errors = false
+
+Migrator.offer_migration_when_available            = true</diff>
      <filename>config/environments/production.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,7 +1,24 @@
-require 'active_support/whiny_nil'
+# The test environment is used exclusively to run your application's
+# test suite.  You never need to work with it otherwise.  Remember that
+# your test database is &quot;scratch space&quot; for the test suite and is wiped
+# and recreated between test runs.  Don't rely on the data there!
+config.cache_classes = true
 
-Dependencies.mechanism                             = :require
-ActionController::Base.consider_all_requests_local = true
-ActionController::Base.perform_caching             = false
-ActionMailer::Base.delivery_method                 = :test
-Migrator.offer_migration_when_available            = false
\ No newline at end of file
+# Log error messages when you accidentally call methods on nil.
+config.whiny_nils    = true
+
+# Show full error reports and disable caching
+config.action_controller.consider_all_requests_local = true
+config.action_controller.perform_caching             = false
+
+# Tell ActionMailer not to deliver emails to the real world.
+# The :test delivery method accumulates sent emails in the
+# ActionMailer::Base.deliveries array.
+config.action_mailer.delivery_method = :test
+
+# Overwrite the default settings for fixtures in tests. See Fixtures 
+# for more details about these settings.
+
+#config.transactional_fixtures = true
+#config.instantiated_fixtures = false
+#onfig.pre_loaded_fixtures = false
\ No newline at end of file</diff>
      <filename>config/environments/test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -3,20 +3,32 @@ AddHandler fastcgi-script .fcgi
 AddHandler cgi-script .cgi
 Options +FollowSymLinks +ExecCGI
 
+# If you don't want Rails to look in certain directories,
+# use the following rewrite rules so that Apache won't rewrite certain requests
+# 
+# Example:
+#   RewriteCond %{REQUEST_URI} ^/notrails.*
+#   RewriteRule .* - [L]
+
 # Redirect all requests not available on the filesystem to Rails
 RewriteEngine On
 
-# Uncomment this if you're not running Typo in the root of your
-# webserver's URL space (i.e. http://www.example.com/blog):
+# If Typo is accessed via an Alias directive, then you MUST also set 
+# the RewriteBase in this htaccess file.
 #
-# RewriteBase /blog/
+# Example:
+#   Alias /blog /path/to/typo/public
+#   RewriteBase /blog
 
-# try to suggest current rss subscribers that the url moved permently
-RewriteRule ^xml/([a-z]+)$ /xml/$1/feed.xml [R=301]
 RewriteRule ^$ index.html [QSA]
 RewriteRule ^([^.]+)$ $1.html [QSA]
 RewriteCond %{REQUEST_FILENAME} !-f
 RewriteRule ^(.*)$ dispatch.fcgi [QSA,L]
 
-# In case Rails experiences terminal errors
-ErrorDocument 500 &quot;&lt;h2&gt;Application Error&lt;/h2&gt;Typo could not be reached
+# In case Typo experiences terminal errors
+# Instead of displaying this message you can supply a file here which will be rendered instead
+# 
+# Example:
+#   ErrorDocument 500 /500.html
+
+ErrorDocument 500 &quot;&lt;h2&gt;Application error&lt;/h2&gt;Typo failed to start properly&quot;
\ No newline at end of file</diff>
      <filename>public/.htaccess</filename>
    </modified>
    <modified>
      <diff>@@ -1,3 +1,5 @@
+&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.01 Transitional//EN&quot;
+   &quot;http://www.w3.org/TR/html4/loose.dtd&quot;&gt;
 &lt;html&gt;
 &lt;body&gt;
   &lt;h1&gt;File not found&lt;/h1&gt;</diff>
      <filename>public/404.html</filename>
    </modified>
    <modified>
      <diff>@@ -1,3 +1,5 @@
+&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.01 Transitional//EN&quot;
+   &quot;http://www.w3.org/TR/html4/loose.dtd&quot;&gt;
 &lt;html&gt;
 &lt;body&gt;
   &lt;h1&gt;Application error (Apache)&lt;/h1&gt;</diff>
      <filename>public/500.html</filename>
    </modified>
    <modified>
      <diff>@@ -1,4 +1,4 @@
-#!/usr/bin/env ruby
+#!/usr/bin/ruby
 
 require File.dirname(__FILE__) + &quot;/../config/environment&quot; unless defined?(RAILS_ROOT)
 
@@ -7,4 +7,4 @@ require File.dirname(__FILE__) + &quot;/../config/environment&quot; unless defined?(RAILS_
 require &quot;dispatcher&quot;
 
 ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun)
-Dispatcher.dispatch
+Dispatcher.dispatch
\ No newline at end of file</diff>
      <filename>public/dispatch.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,41 +1,12 @@
 // 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
 // 
-// Permission is hereby granted, free of charge, to any person obtaining
-// a copy of this software and associated documentation files (the
-// &quot;Software&quot;), 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 &quot;AS IS&quot;, 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.
-
-Element.collectTextNodesIgnoreClass = function(element, ignoreclass) {
-  var children = $(element).childNodes;
-  var text     = &quot;&quot;;
-  var classtest = new RegExp(&quot;^([^ ]+ )*&quot; + ignoreclass+ &quot;( [^ ]+)*$&quot;,&quot;i&quot;);
-  
-  for (var i = 0; i &lt; children.length; i++) {
-    if(children[i].nodeType==3) {
-      text+=children[i].nodeValue;
-    } else {
-      if((!children[i].className.match(classtest)) &amp;&amp; children[i].hasChildNodes())
-        text += Element.collectTextNodesIgnoreClass(children[i], ignoreclass);
-    }
-  }
-  
-  return text;
-}
+// See scriptaculous.js for full license.
 
 // Autocompleter.Base handles all the autocompletion functionality 
 // that's independent of the data source for autocompletion. This
@@ -46,7 +17,7 @@ Element.collectTextNodesIgnoreClass = function(element, ignoreclass) {
 // 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.getEntry(), NOT by directly accessing
+// 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.
@@ -57,7 +28,7 @@ Element.collectTextNodesIgnoreClass = function(element, ignoreclass) {
 // 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: new Array (',', '\n') } which
+// 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.
@@ -65,79 +36,79 @@ Element.collectTextNodesIgnoreClass = function(element, ignoreclass) {
 var Autocompleter = {}
 Autocompleter.Base = function() {};
 Autocompleter.Base.prototype = {
-  base_initialize: function(element, update, options) {
+  baseInitialize: function(element, update, options) {
     this.element     = $(element); 
     this.update      = $(update);  
-    this.has_focus   = false; 
+    this.hasFocus    = false; 
     this.changed     = false; 
     this.active      = false; 
     this.index       = 0;     
-    this.entry_count = 0;
+    this.entryCount  = 0;
 
     if (this.setOptions)
       this.setOptions(options);
     else
-      this.options = {}
-     
-    this.options.tokens       = this.options.tokens || new Array();
+      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.min_chars    = this.options.min_chars || 1;
+    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';
-          var offsets = Position.cumulativeOffset(element);
-          update.style.left = offsets[0] + 'px';
-          update.style.top  = (offsets[1] + element.offsetHeight) + 'px';
-          update.style.width = element.offsetWidth + 'px';
+        Position.clone(element, update, {setHeight: false, offsetTop: element.offsetHeight});
       }
-      new Effect.Appear(update,{duration:0.15});
+      Effect.Appear(update,{duration:0.15});
     };
     this.options.onHide = this.options.onHide || 
     function(element, update){ new Effect.Fade(update,{duration:0.15}) };
-    
-    if(this.options.indicator)
-      this.indicator = $(this.options.indicator);
 
     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, &quot;blur&quot;, this.onBlur.bindAsEventListener(this));
     Event.observe(this.element, &quot;keypress&quot;, this.onKeyPress.bindAsEventListener(this));
   },
 
   show: function() {
-    if(this.update.style.display=='none') this.options.onShow(this.element, this.update);
-    if(!this.iefix &amp;&amp; (navigator.appVersion.indexOf('MSIE')&gt;0) &amp;&amp; this.update.style.position=='absolute') {
+    if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
+    if(!this.iefix &amp;&amp; (navigator.appVersion.indexOf('MSIE')&gt;0) &amp;&amp; (Element.getStyle(this.update, 'position')=='absolute')) {
       new Insertion.After(this.update, 
        '&lt;iframe id=&quot;' + this.update.id + '_iefix&quot; '+
-       'style=&quot;display:none;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);&quot; ' +
+       'style=&quot;display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);&quot; ' +
        'src=&quot;javascript:false;&quot; frameborder=&quot;0&quot; scrolling=&quot;no&quot;&gt;&lt;/iframe&gt;');
       this.iefix = $(this.update.id+'_iefix');
     }
-    if(this.iefix) {
-      Position.clone(this.update, this.iefix);
-      this.iefix.style.zIndex = 1;
-      this.update.style.zIndex = 2;
-      Element.show(this.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() {
-    if(this.update.style.display=='') this.options.onHide(this.element, this.update);
+    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.indicator) Element.show(this.indicator);
+    if(this.options.indicator) Element.show(this.options.indicator);
   },
-  
+
   stopIndicator: function() {
-    if(this.indicator) Element.hide(this.indicator);
+    if(this.options.indicator) Element.hide(this.options.indicator);
   },
 
   onKeyPress: function(event) {
@@ -145,22 +116,23 @@ Autocompleter.Base.prototype = {
       switch(event.keyCode) {
        case Event.KEY_TAB:
        case Event.KEY_RETURN:
-         this.select_entry();
+         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.mark_previous();
+         this.markPrevious();
          this.render();
          if(navigator.appVersion.indexOf('AppleWebKit')&gt;0) Event.stop(event);
          return;
        case Event.KEY_DOWN:
-         this.mark_next();
+         this.markNext();
          this.render();
          if(navigator.appVersion.indexOf('AppleWebKit')&gt;0) Event.stop(event);
          return;
@@ -168,15 +140,15 @@ Autocompleter.Base.prototype = {
      else 
       if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN) 
         return;
-    
+
     this.changed = true;
-    this.has_focus = 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) 
@@ -190,92 +162,97 @@ Autocompleter.Base.prototype = {
   onClick: function(event) {
     var element = Event.findElement(event, 'LI');
     this.index = element.autocompleteIndex;
-    this.select_entry();
-    Event.stop(event);
+    this.selectEntry();
+    this.hide();
   },
   
   onBlur: function(event) {
     // needed to make click events working
     setTimeout(this.hide.bind(this), 250);
-    this.has_focus = false;
+    this.hasFocus = false;
     this.active = false;     
   }, 
   
   render: function() {
-    if(this.entry_count &gt; 0) {
-      for (var i = 0; i &lt; this.entry_count; i++)
+    if(this.entryCount &gt; 0) {
+      for (var i = 0; i &lt; this.entryCount; i++)
         this.index==i ? 
-          Element.addClassName(this.get_entry(i),&quot;selected&quot;) : 
-          Element.removeClassName(this.get_entry(i),&quot;selected&quot;);
-        
-      if(this.has_focus) { 
-        if(this.get_current_entry().scrollIntoView) 
-          this.get_current_entry().scrollIntoView(false);
+          Element.addClassName(this.getEntry(i),&quot;selected&quot;) : 
+          Element.removeClassName(this.getEntry(i),&quot;selected&quot;);
         
+      if(this.hasFocus) { 
         this.show();
         this.active = true;
       }
     } else this.hide();
   },
   
-  mark_previous: function() {
+  markPrevious: function() {
     if(this.index &gt; 0) this.index--
-      else this.index = this.entry_count-1;
+      else this.index = this.entryCount-1;
   },
   
-  mark_next: function() {
-    if(this.index &lt; this.entry_count-1) this.index++
+  markNext: function() {
+    if(this.index &lt; this.entryCount-1) this.index++
       else this.index = 0;
   },
   
-  get_entry: function(index) {
+  getEntry: function(index) {
     return this.update.firstChild.childNodes[index];
   },
   
-  get_current_entry: function() {
-    return this.get_entry(this.index);
+  getCurrentEntry: function() {
+    return this.getEntry(this.index);
   },
   
-  select_entry: function() {
+  selectEntry: function() {
     this.active = false;
-    value = Element.collectTextNodesIgnoreClass(this.get_current_entry(), 'informal').unescapeHTML();
-    this.updateElement(value);
-    this.element.focus();
+    this.updateElement(this.getCurrentEntry());
   },
 
-  updateElement: function(value) {
-    var last_token_pos = this.findLastToken();
-    if (last_token_pos != -1) {
-      var new_value = this.element.value.substr(0, last_token_pos + 1);
-      var whitespace = this.element.value.substr(last_token_pos + 1).match(/^\s+/);
+  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)
-        new_value += whitespace[0];
-      this.element.value = new_value + value;
+        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 &amp;&amp; this.has_focus) {
+    if(!this.changed &amp;&amp; this.hasFocus) {
       this.update.innerHTML = choices;
       Element.cleanWhitespace(this.update);
       Element.cleanWhitespace(this.update.firstChild);
 
       if(this.update.firstChild &amp;&amp; this.update.firstChild.childNodes) {
-        this.entry_count = 
+        this.entryCount = 
           this.update.firstChild.childNodes.length;
-        for (var i = 0; i &lt; this.entry_count; i++) {
-          entry = this.get_entry(i);
+        for (var i = 0; i &lt; this.entryCount; i++) {
+          var entry = this.getEntry(i);
           entry.autocompleteIndex = i;
           this.addObservers(entry);
         }
       } else { 
-        this.entry_count = 0;
+        this.entryCount = 0;
       }
-      
+
       this.stopIndicator();
-      
+
       this.index = 0;
       this.render();
     }
@@ -288,7 +265,7 @@ Autocompleter.Base.prototype = {
 
   onObserverEvent: function() {
     this.changed = false;   
-    if(this.getEntry().length&gt;=this.options.min_chars) {
+    if(this.getToken().length&gt;=this.options.minChars) {
       this.startIndicator();
       this.getUpdatedChoices();
     } else {
@@ -297,58 +274,56 @@ Autocompleter.Base.prototype = {
     }
   },
 
-  getEntry: function() {
-    var token_pos = this.findLastToken();
-    if (token_pos != -1)
-      var ret = this.element.value.substr(token_pos + 1).replace(/^\s+/,'').replace(/\s+$/,'');
+  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 last_token_pos = -1;
+    var lastTokenPos = -1;
 
     for (var i=0; i&lt;this.options.tokens.length; i++) {
-      var this_token_pos = this.element.value.lastIndexOf(this.options.tokens[i]);
-      if (this_token_pos &gt; last_token_pos)
-        last_token_pos = this_token_pos;
+      var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]);
+      if (thisTokenPos &gt; lastTokenPos)
+        lastTokenPos = thisTokenPos;
     }
-    return last_token_pos;
+    return lastTokenPos;
   }
 }
 
 Ajax.Autocompleter = Class.create();
-Ajax.Autocompleter.prototype = Object.extend(new Autocompleter.Base(), 
-Object.extend(new Ajax.Base(), {
+Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), {
   initialize: function(element, update, url, options) {
-	  this.base_initialize(element, update, options);
+	  this.baseInitialize(element, update, options);
     this.options.asynchronous  = true;
-    this.options.onComplete    = this.onComplete.bind(this)
-    this.options.method        = 'post';
+    this.options.onComplete    = this.onComplete.bind(this);
     this.options.defaultParams = this.options.parameters || null;
     this.url                   = url;
   },
-  
+
   getUpdatedChoices: function() {
-    entry = encodeURIComponent(this.element.name) + '=' + 
-      encodeURIComponent(this.getEntry());
-      
+    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 += '&amp;' + 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
@@ -362,22 +337,22 @@ Object.extend(new Ajax.Base(), {
 // Extra local autocompletion options:
 // - choices - How many autocompletion choices to offer
 //
-// - partial_search - If false, the autocompleter will match entered
+// - 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 full_search to true (default: off).
+//                    the option fullSearch to true (default: off).
 //
-// - full_search - Search anywhere in autocomplete array strings.
+// - fullSsearch - Search anywhere in autocomplete array strings.
 //
-// - partial_chars - How many characters to enter before triggering
-//                   a partial match (unlike min_chars, which defines
+// - 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.
 //
-// - ignore_case - Whether to ignore case when autocompleting.
+// - ignoreCase - Whether to ignore case when autocompleting.
 //                 Defaults to true.
 //
 // It's possible to pass in a custom function as the 'selector' 
@@ -388,7 +363,7 @@ Object.extend(new Ajax.Base(), {
 Autocompleter.Local = Class.create();
 Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
   initialize: function(element, update, array, options) {
-    this.base_initialize(element, update, options);
+    this.baseInitialize(element, update, options);
     this.options.array = array;
   },
 
@@ -399,41 +374,42 @@ Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
   setOptions: function(options) {
     this.options = Object.extend({
       choices: 10,
-      partial_search: true,
-      partial_chars: 2,
-      ignore_case: true,
-      full_search: false,
+      partialSearch: true,
+      partialChars: 2,
+      ignoreCase: true,
+      fullSearch: false,
       selector: function(instance) {
-        var ret       = new Array(); // Beginning matches
-        var partial   = new Array(); // Inside matches
-        var entry     = instance.getEntry();
+        var ret       = []; // Beginning matches
+        var partial   = []; // Inside matches
+        var entry     = instance.getToken();
         var count     = 0;
-        
+
         for (var i = 0; i &lt; instance.options.array.length &amp;&amp;  
-            ret.length &lt; instance.options.choices ; i++) { 
+          ret.length &lt; instance.options.choices ; i++) { 
+
           var elem = instance.options.array[i];
-          var found_pos = instance.options.ignore_case ? 
+          var foundPos = instance.options.ignoreCase ? 
             elem.toLowerCase().indexOf(entry.toLowerCase()) : 
             elem.indexOf(entry);
 
-          while (found_pos != -1) {
-            if (found_pos == 0 &amp;&amp; elem.length != entry.length) { 
+          while (foundPos != -1) {
+            if (foundPos == 0 &amp;&amp; elem.length != entry.length) { 
               ret.push(&quot;&lt;li&gt;&lt;strong&gt;&quot; + elem.substr(0, entry.length) + &quot;&lt;/strong&gt;&quot; + 
                 elem.substr(entry.length) + &quot;&lt;/li&gt;&quot;);
               break;
-            } else if (entry.length &gt;= instance.options.partial_chars &amp;&amp; 
-              instance.options.partial_search &amp;&amp; found_pos != -1) {
-              if (instance.options.full_search || /\s/.test(elem.substr(found_pos-1,1))) {
-                partial.push(&quot;&lt;li&gt;&quot; + elem.substr(0, found_pos) + &quot;&lt;strong&gt;&quot; +
-                  elem.substr(found_pos, entry.length) + &quot;&lt;/strong&gt;&quot; + elem.substr(
-                  found_pos + entry.length) + &quot;&lt;/li&gt;&quot;);
+            } else if (entry.length &gt;= instance.options.partialChars &amp;&amp; 
+              instance.options.partialSearch &amp;&amp; foundPos != -1) {
+              if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
+                partial.push(&quot;&lt;li&gt;&quot; + elem.substr(0, foundPos) + &quot;&lt;strong&gt;&quot; +
+                  elem.substr(foundPos, entry.length) + &quot;&lt;/strong&gt;&quot; + elem.substr(
+                  foundPos + entry.length) + &quot;&lt;/li&gt;&quot;);
                 break;
               }
             }
 
-            found_pos = instance.options.ignore_case ? 
-              elem.toLowerCase().indexOf(entry.toLowerCase(), found_pos + 1) : 
-              elem.indexOf(entry, found_pos + 1);
+            foundPos = instance.options.ignoreCase ? 
+              elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : 
+              elem.indexOf(entry, foundPos + 1);
 
           }
         }
@@ -444,3 +420,289 @@ Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
     }, options || {});
   }
 });
+
+// AJAX in-place editor
+//
+// see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor
+
+Ajax.InPlaceEditor = Class.create();
+Ajax.InPlaceEditor.defaultHighlightColor = &quot;#FFFF99&quot;;
+Ajax.InPlaceEditor.prototype = {
+  initialize: function(element, url, options) {
+    this.url = url;
+    this.element = $(element);
+
+    this.options = Object.extend({
+      okText: &quot;ok&quot;,
+      cancelText: &quot;cancel&quot;,
+      savingText: &quot;Saving...&quot;,
+      clickToEditText: &quot;Click to edit&quot;,
+      okText: &quot;ok&quot;,
+      rows: 1,
+      onComplete: function(transport, element) {
+        new Effect.Highlight(element, {startcolor: this.options.highlightcolor});
+      },
+      onFailure: function(transport) {
+        alert(&quot;Error communicating with the server: &quot; + 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: &quot;#FFFFFF&quot;,
+      externalControl:	null,
+      ajaxOptions: {}
+    }, options || {});
+
+    if(!this.options.formId &amp;&amp; this.element.id) {
+      this.options.formId = this.element.id + &quot;-inplaceeditor&quot;;
+      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 = &quot;transparent&quot;;
+    }
+    
+    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() {
+    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.focus(this.editField);
+    // stop the event to avoid a page refresh in Safari
+    if (arguments.length &gt; 1) {
+      Event.stop(arguments[0]);
+    }
+  },
+  createForm: function() {
+    this.form = document.createElement(&quot;form&quot;);
+    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(&quot;br&quot;);
+      this.form.appendChild(br);
+    }
+
+    okButton = document.createElement(&quot;input&quot;);
+    okButton.type = &quot;submit&quot;;
+    okButton.value = this.options.okText;
+    this.form.appendChild(okButton);
+
+    cancelLink = document.createElement(&quot;a&quot;);
+    cancelLink.href = &quot;#&quot;;
+    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(/&lt;br/i) || string.match(/&lt;p&gt;/i);
+  },
+  convertHTMLLineBreaks: function(string) {
+    return string.replace(/&lt;br&gt;/gi, &quot;\n&quot;).replace(/&lt;br\/&gt;/gi, &quot;\n&quot;).replace(/&lt;\/p&gt;/gi, &quot;\n&quot;).replace(/&lt;p&gt;/gi, &quot;&quot;);
+  },
+  createEditField: function() {
+    var text;
+    if(this.options.loadTextURL) {
+      text = this.options.loadingText;
+    } else {
+      text = this.getText();
+    }
+    
+    if (this.options.rows == 1 &amp;&amp; !this.hasHTMLLineBreaks(text)) {
+      this.options.textarea = false;
+      var textField = document.createElement(&quot;input&quot;);
+      textField.type = &quot;text&quot;;
+      textField.name = &quot;value&quot;;
+      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(&quot;textarea&quot;);
+      textArea.name = &quot;value&quot;;
+      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 &gt; 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);
+    }
+  }
+};
\ No newline at end of file</diff>
      <filename>public/javascripts/controls.js</filename>
    </modified>
    <modified>
      <diff>@@ -2,193 +2,79 @@
 // 
 // Element.Class part Copyright (c) 2005 by Rick Olson
 // 
-// Permission is hereby granted, free of charge, to any person obtaining
-// a copy of this software and associated documentation files (the
-// &quot;Software&quot;), 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 &quot;AS IS&quot;, 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.
-
-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) {
-      element = $(element);
-      return element.className.split(' ');
-    },
-
-    // functions adapted from original functions by Gavin Kistner
-    remove: function(element) {
-      element = $(element);
-      var regEx;
-      for(var i = 1; i &lt; arguments.length; i++) {
-        regEx = new RegExp(&quot;^&quot; + arguments[i] + &quot;\\b\\s*|\\s*\\b&quot; + arguments[i] + &quot;\\b&quot;, 'g');
-        element.className = element.className.replace(regEx, '')
-      }
-    },
-
-    add: function(element) {
-      element = $(element);
-      for(var i = 1; i &lt; arguments.length; i++) {
-        Element.Class.remove(element, arguments[i]);
-        element.className += (element.className.length &gt; 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 &lt; arguments.length; i++) {
-        regEx = new RegExp(&quot;\\b&quot; + arguments[i] + &quot;\\b&quot;);
-        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 &lt; arguments.length; i++) {
-        if((typeof arguments[i] == 'object') &amp;&amp; 
-          (arguments[i].constructor == Array)) {
-          for(var j = 0; j &lt; arguments[i].length; j++) {
-            regEx = new RegExp(&quot;\\b&quot; + arguments[i][j] + &quot;\\b&quot;);
-            if(regEx.test(element.className)) return true;
-          }
-        } else {
-          regEx = new RegExp(&quot;\\b&quot; + arguments[i] + &quot;\\b&quot;);
-          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 &lt; children.length; i++) {
-        if (Element.Class.has(children[i], className)) {
-          elements.push(children[i]);
-          break;
-        }
-      }
-      
-      return elements;
-    }
-}
+// See scriptaculous.js for full license.
 
 /*--------------------------------------------------------------------------*/
 
 var Droppables = {
-  drops: false,
-  
+  drops: [],
+
   remove: function(element) {
-    for(var i = 0; i &lt; this.drops.length; i++)
-      if(this.drops[i].element == element)
-        this.drops.splice(i,1);
+    this.drops = this.drops.reject(function(d) { return d.element==element });
   },
-  
+
   add: function(element) {
-    var element = $(element);
+    element = $(element);
     var options = Object.extend({
       greedy:     true,
       hoverclass: null  
     }, arguments[1] || {});
-    
+
     // cache containers
     if(options.containment) {
-      options._containers = new Array();
+      options._containers = [];
       var containment = options.containment;
       if((typeof containment == 'object') &amp;&amp; 
         (containment.constructor == Array)) {
-        for(var i=0; i&lt;containment.length; i++)
-          options._containers.push($(containment[i]));
+        containment.each( function(c) { options._containers.push($(c)) });
       } else {
         options._containers.push($(containment));
       }
-      options._containers_length = 
-        options._containers.length-1;
     }
-    
+
     Element.makePositioned(element); // fix IE
-    
     options.element = element;
-    
-    // activate the droppable    
-    if(!this.drops) this.drops = [];
+
     this.drops.push(options);
   },
-  
-  is_contained: function(element, drop) {
-    var containers = drop._containers;
+
+  isContained: function(element, drop) {
     var parentNode = element.parentNode;
-    var i = drop._containers_length;
-    do { if(parentNode==containers[i]) return true; } while (i--);
-    return false;
+    return drop._containers.detect(function(c) { return parentNode == c });
   },
-  
-  is_affected: function(pX, pY, element, drop) {
+
+  isAffected: function(pX, pY, element, drop) {
     return (
       (drop.element!=element) &amp;&amp;
       ((!drop._containers) ||
-        this.is_contained(element, drop)) &amp;&amp;
+        this.isContained(element, drop)) &amp;&amp;
       ((!drop.accept) ||
         (Element.Class.has_any(element, drop.accept))) &amp;&amp;
       Position.within(drop.element, pX, pY) );
   },
-  
+
   deactivate: function(drop) {
-    Element.Class.remove(drop.element, drop.hoverclass);
+    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) {
+    if(drop.hoverclass)
       Element.Class.add(drop.element, drop.hoverclass);
-      this.last_active = drop;
-    }
+    this.last_active = drop;
   },
-  
+
   show: function(event, element) {
-    if(!this.drops) return;
+    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.is_affected(pX, pY, element, drop)) {
+      if(this.isAffected(pX, pY, element, drop)) {
         if(drop.onHover)
            drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
         if(drop.greedy) { 
@@ -197,43 +83,41 @@ var Droppables = {
         }
       }
     } while (i--);
+    
+    if(this.last_active) this.deactivate(this.last_active);
   },
-  
+
   fire: function(event, element) {
     if(!this.last_active) return;
     Position.prepare();
-    
-    if (this.is_affected(Event.pointerX(event), Event.pointerY(event), element, this.last_active))
+
+    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);
-    
+        this.last_active.onDrop(element, this.last_active.element, event);
   },
-  
+
   reset: function() {
     if(this.last_active)
       this.deactivate(this.last_active);
   }
 }
 
-Draggables = {
-  observers: new Array(),
+var Draggables = {
+  observers: [],
   addObserver: function(observer) {
     this.observers.push(observer);    
   },
   removeObserver: function(element) {  // element instead of obsever fixes mem leaks
-    for(var i = 0; i &lt; this.observers.length; i++)
-      if(this.observers[i].element &amp;&amp; (this.observers[i].element == element))
-        this.observers.splice(i,1);
+    this.observers = this.observers.reject( function(o) { return o.element==element });
   },
   notify: function(eventName, draggable) {  // 'onStart', 'onEnd'
-    for(var i = 0; i &lt; this.observers.length; i++)
-      this.observers[i][eventName](draggable);
+    this.observers.invoke(eventName, draggable);
   }
 }
 
 /*--------------------------------------------------------------------------*/
 
-Draggable = Class.create();
+var Draggable = Class.create();
 Draggable.prototype = {
   initialize: function(element) {
     var options = Object.extend({
@@ -242,7 +126,8 @@ Draggable.prototype = {
         new Effect.Opacity(element, {duration:0.2, from:1.0, to:0.7}); 
       },
       reverteffect: function(element, top_offset, left_offset) {
-        new Effect.MoveBy(element, -top_offset, -left_offset, {duration:0.4});
+        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}); 
@@ -250,40 +135,50 @@ Draggable.prototype = {
       zindex: 1000,
       revert: false
     }, arguments[1] || {});
-    
+
     this.element      = $(element);
-    this.handle       = options.handle ? $(options.handle) : this.element;
-    
-    Element.makePositioned(this.element); // fix IE
-    
+    if(options.handle &amp;&amp; (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.originalZ    = parseInt(this.element.style.zIndex || &quot;0&quot;);
-    
+
     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);
     
-    Event.observe(this.handle, &quot;mousedown&quot;, this.eventMouseDown);
+    this.registerEvents();
+  },
+  destroy: function() {
+    Event.stopObserving(this.handle, &quot;mousedown&quot;, this.eventMouseDown);
+    this.unregisterEvents();
+  },
+  registerEvents: function() {
     Event.observe(document, &quot;mouseup&quot;, this.eventMouseUp);
     Event.observe(document, &quot;mousemove&quot;, this.eventMouseMove);
     Event.observe(document, &quot;keypress&quot;, this.eventKeypress);
+    Event.observe(this.handle, &quot;mousedown&quot;, this.eventMouseDown);
   },
-  destroy: function() {
-    Event.stopObserving(this.handle, &quot;mousedown&quot;, this.eventMouseDown);
-    Event.stopObserving(document, &quot;mouseup&quot;, this.eventMouseUp);
-    Event.stopObserving(document, &quot;mousemove&quot;, this.eventMouseMove);
-    Event.stopObserving(document, &quot;keypress&quot;, this.eventKeypress);
+  unregisterEvents: function() {
+    //if(!this.active) return;
+    //Event.stopObserving(document, &quot;mouseup&quot;, this.eventMouseUp);
+    //Event.stopObserving(document, &quot;mousemove&quot;, this.eventMouseMove);
+    //Event.stopObserving(document, &quot;keypress&quot;, this.eventKeypress);
   },
   currentLeft: function() {
     return parseInt(this.element.style.left || '0');
@@ -293,27 +188,42 @@ Draggable.prototype = {
   },
   startDrag: function(event) {
     if(Event.isLeftClick(event)) {
-      this.active = true;
       
-      var style = this.element.style;
-      this.originalY = this.element.offsetTop  - this.currentTop()  - this.originalTop;
-      this.originalX = this.element.offsetLeft - this.currentLeft() - this.originalLeft;
-      this.offsetY =  event.clientY - this.originalY - this.originalTop;
-      this.offsetX =  event.clientX - this.originalX - this.originalLeft;
+      // abort on form elements, fixes a Firefox issue
+      var src = Event.element(event);
+      if(src.tagName &amp;&amp; (
+        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 &amp;&amp; typeof revert == 'function') revert = revert(this.element);
-      
+
     if(revert &amp;&amp; this.options.reverteffect) {
       this.options.reverteffect(this.element, 
       this.currentTop()-this.originalTop,
@@ -322,12 +232,14 @@ Draggable.prototype = {
       this.originalLeft = this.currentLeft();
       this.originalTop  = this.currentTop();
     }
-    
-    this.element.style.zIndex = this.originalZ;
-      
+
+    if(this.options.zindex)
+      this.element.style.zIndex = this.originalZ;
+
     if(this.options.endeffect) 
       this.options.endeffect(this.element);
-      
+
+
     Droppables.reset();
   },
   keyPress: function(event) {
@@ -347,13 +259,15 @@ Draggable.prototype = {
     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;
-    this.originalX = this.element.offsetLeft - this.currentLeft() - this.originalLeft;
-    this.originalY = this.element.offsetTop  - this.currentTop()  - this.originalTop;
     if((!this.options.constraint) || (this.options.constraint=='horizontal'))
-      style.left = ((event.clientX - this.originalX) - this.offsetX) + &quot;px&quot;;
+      style.left = (pointer[0] - offsets[0] - this.offsetX) + &quot;px&quot;;
     if((!this.options.constraint) || (this.options.constraint=='vertical'))
-      style.top  = ((event.clientY - this.originalY) - this.offsetY) + &quot;px&quot;;
+      style.top  = (pointer[1] - offsets[1] - this.offsetY) + &quot;px&quot;;
     if(style.visibility==&quot;hidden&quot;) style.visibility = &quot;&quot;; // fix gecko rendering
   },
   update: function(event) {
@@ -361,19 +275,32 @@ Draggable.prototype = {
       if(!this.dragging) {
         var style = this.element.style;
         this.dragging = true;
-        if(style.position==&quot;&quot;) style.position = &quot;relative&quot;;
-        style.zIndex = this.options.zindex;
+        
+        if(Element.getStyle(this.element,'position')=='') 
+          style.position = &quot;relative&quot;;
+        
+        if(this.options.zindex) {
+          this.options.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')&gt;0) window.scrollBy(0,0); 
-      
+
       Event.stop(event);
    }
   }
@@ -381,7 +308,7 @@ Draggable.prototype = {
 
 /*--------------------------------------------------------------------------*/
 
-SortableObserver = Class.create();
+var SortableObserver = Class.create();
 SortableObserver.prototype = {
   initialize: function(element, observer) {
     this.element   = $(element);
@@ -391,147 +318,199 @@ SortableObserver.prototype = {
   onStart: function() {
     this.lastValue = Sortable.serialize(this.element);
   },
-  onEnd: function() {    
+  onEnd: function() {
+    Sortable.unmark();
     if(this.lastValue != Sortable.serialize(this.element))
       this.observer(this.element)
   }
 }
 
-Sortable = {
+var Sortable = {
   sortables: new Array(),
   options: function(element){
-    var element = $(element);
-    for(var i=0;i&lt;this.sortables.length;i++)
-      if(this.sortables[i].element == element)
-        return this.sortables[i];
-    return null;        
+    element = $(element);
+    return this.sortables.detect(function(s) { return s.element == element });
   },
   destroy: function(element){
-    var element = $(element);
-    for(var i=0;i&lt;this.sortables.length;i++) {
-      if(this.sortables[i].element == element) {
-        var s = this.sortables[i];
-        Draggables.removeObserver(s.element);
-        for(var j=0;j&lt;s.droppables.length;j++)
-          Droppables.remove(s.droppables[j]);
-        for(var j=0;j&lt;s.draggables.length;j++)
-          s.draggables[j].destroy();
-        this.sortables.splice(i,1);
-      }
-    }
+    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) {
-    var element = $(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:    function() {},
       onUpdate:    function() {}
     }, 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:      handle };
+      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: function(element, dropon, overlap) { 
-        if(overlap&gt;0.5) {
-          if(dropon.previousSibling != element) {
-            var oldParentNode = element.parentNode;
-            element.style.visibility = &quot;hidden&quot;; // fix gecko rendering
-            dropon.parentNode.insertBefore(element, dropon);
-            if(dropon.parentNode!=oldParentNode &amp;&amp; oldParentNode.sortable) 
-              oldParentNode.sortable.onChange(element);
-            if(dropon.parentNode.sortable)
-              dropon.parentNode.sortable.onChange(element);
-          }
-        } else {                
-          var nextElement = dropon.nextSibling || null;
-          if(nextElement != element) {
-            var oldParentNode = element.parentNode;
-            element.style.visibility = &quot;hidden&quot;; // fix gecko rendering
-            dropon.parentNode.insertBefore(element, nextElement);
-            if(dropon.parentNode!=oldParentNode &amp;&amp; oldParentNode.sortable) 
-              oldParentNode.sortable.onChange(element);
-            if(dropon.parentNode.sortable)
-              dropon.parentNode.sortable.onChange(element);
-          }
-        }
-      }
+      onHover:     Sortable.onHover,
+      greedy:      !options.dropOnEmpty
     }
 
     // fix for gecko engine
     Element.cleanWhitespace(element); 
-    
+
     options.draggables = [];
     options.droppables = [];
-    
-    // make it so 
-    var elements = element.childNodes;
-    for (var i = 0; i &lt; elements.length; i++) 
-      if(elements[i].tagName &amp;&amp; elements[i].tagName==options.tag.toUpperCase() &amp;&amp;
-        (!options.only || (Element.Class.has(elements[i], options.only)))) {
-        
-        // handles are per-draggable
-        var handle = options.handle ? 
-          Element.Class.childrenWith(elements[i], options.handle)[0] : elements[i];
-        
-        options.draggables.push(new Draggable(elements[i], Object.extend(options_for_draggable, { handle: handle })));
-        
-        Droppables.add(elements[i], options_for_droppable);
-        options.droppables.push(elements[i]);
-        
-      }
-      
+
+    // 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 &amp;&amp; e.tagName==options.tag.toUpperCase() &amp;&amp;
+        (!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&gt;0 ? elements.flatten() : null);
+  },
+
+  onHover: function(element, dropon, overlap) {
+    if(overlap&gt;0.5) {
+      Sortable.mark(dropon, 'before');
+      if(dropon.previousSibling != element) {
+        var oldParentNode = element.parentNode;
+        element.style.visibility = &quot;hidden&quot;; // 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 = &quot;hidden&quot;; // 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) {
+      dropon.appendChild(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 &amp;&amp; !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(&quot;body&quot;).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) {
-    var element = $(element);
+    element = $(element);
     var sortableOptions = this.options(element);
     var options = Object.extend({
       tag:  sortableOptions.tag,
       only: sortableOptions.only,
-      name: element.id
+      name: element.id,
+      format: sortableOptions.format || /^[^_]*_(.*)$/
     }, arguments[1] || {});
-    
-    var items = $(element).childNodes;
-    var queryComponents = new Array();
- 
-    for(var i=0; i&lt;items.length; i++)
-      if(items[i].tagName &amp;&amp; items[i].tagName==options.tag.toUpperCase() &amp;&amp;
-        (!options.only || (Element.Class.has(items[i], options.only))))
-        queryComponents.push(
-          encodeURIComponent(options.name) + &quot;[]=&quot; + 
-          encodeURIComponent(items[i].id.split(&quot;_&quot;)[1]));
-
-    return queryComponents.join(&quot;&amp;&quot;);
+    return $(this.findElements(element, options) || []).collect( function(item) {
+      return (encodeURIComponent(options.name) + &quot;[]=&quot; + 
+              encodeURIComponent(item.id.match(options.format) ? item.id.match(options.format)[1] : ''));
+    }).join(&quot;&amp;&quot;);
   }
-} 
\ No newline at end of file
+}
\ No newline at end of file</diff>
      <filename>public/javascripts/dragdrop.js</filename>
    </modified>
    <modified>
      <diff>@@ -1,30 +1,341 @@
 // Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
-//
-// Parts (c) 2005 Justin Palmer (http://encytemedia.com/)
-// Parts (c) 2005 Mark Pilgrim (http://diveintomark.org/)
+// Contributors:
+//  Justin Palmer (http://encytemedia.com/)
+//  Mark Pilgrim (http://diveintomark.org/)
+//  Martin Bialasinki
 // 
-// Permission is hereby granted, free of charge, to any person obtaining
-// a copy of this software and associated documentation files (the
-// &quot;Software&quot;), 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 &quot;AS IS&quot;, 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.
+// See scriptaculous.js for full license.
+
+Object.debug = function(obj) {
+  var info = [];
+  
+  if(typeof obj in [&quot;string&quot;,&quot;number&quot;]) {
+    return obj;
+  } else {
+    for(property in obj)
+      if(typeof obj[property]!=&quot;function&quot;)
+        info.push(property + ' =&gt; ' + 
+          (typeof obj[property] == &quot;string&quot; ?
+            '&quot;' + obj[property] + '&quot;' :
+            obj[property]));
+  }
+  
+  return (&quot;'&quot; + obj + &quot;' #&quot; + typeof obj + 
+    &quot;: {&quot; + info.join(&quot;, &quot;) + &quot;}&quot;);
+}
+
+
+/*--------------------------------------------------------------------------*/
+
+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 &lt; 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 = &quot;&lt;&quot; + elementName + &quot;&gt;&lt;/&quot; + elementName + &quot;&gt;&quot;;
+    var element = parentElement.firstChild || null;
+      
+    // see if browser added wrapping tags
+    if(element &amp;&amp; (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 = &quot;&lt;&quot; +elementName + &quot; &quot; +
+              attrs + &quot;&gt;&lt;/&quot; + elementName + &quot;&gt;&quot;;
+            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) +
+          '=&quot;' + attributes[attribute].toString().escapeHTML() + '&quot;');
+    return attrs.join(&quot; &quot;);
+  },
+  _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');
+  }
+}
+
+/* ------------- element ext -------------- */
+
+// converts rgb() and #xxx to #xxxxxx format,
+// returns self (or first argument) if not convertable
+String.prototype.parseColor = function() {
+  color = &quot;#&quot;;
+  if(this.slice(0,4) == &quot;rgb(&quot;) {
+    var cols = this.slice(4,this.length-1).split(',');
+    var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i&lt;3);
+  } else {
+    if(this.slice(0,1) == '#') {
+      if(this.length==4) for(var i=1;i&lt;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     = &quot;&quot;;
+  var classtest = new RegExp(&quot;^([^ ]+ )*&quot; + ignoreclass+ &quot;( [^ ]+)*$&quot;,&quot;i&quot;);
+
+  for (var i = 0; i &lt; children.length; i++) {
+    if(children[i].nodeType==3) {
+      text+=children[i].nodeValue;
+    } else {
+      if((!children[i].className.match(classtest)) &amp;&amp; children[i].hasChildNodes())
+        text += Element.collectTextNodesIgnoreClass(children[i], ignoreclass);
+    }
+  }
+
+  return text;
+}
+
+Element.setContentZoom = function(element, percent) {
+  element = $(element);
+  element.style.fontSize = (percent/100) + &quot;em&quot;;  
+  if(navigator.appVersion.indexOf('AppleWebKit')&gt;0) window.scrollBy(0,0);
+}
+
+Element.getOpacity = function(element){
+  var opacity;
+  if (opacity = Element.getStyle(element, &quot;opacity&quot;))
+    return parseFloat(opacity);
+  if (opacity = (Element.getStyle(element, &quot;filter&quot;) || '').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 &lt; 0.00001) value = 0;
+    els.opacity = value;
+    if(/MSIE/.test(navigator.userAgent))
+      els.filter = Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'') + 
+        &quot;alpha(opacity=&quot;+value*100+&quot;)&quot;;
+  }  
+}
+
+Element.getInlineOpacity = function(element){
+  element= $(element);
+  var op;
+  op = element.style.opacity;
+  if (typeof op != &quot;undefined&quot; &amp;&amp; op != &quot;&quot;) return op;
+  return &quot;&quot;;
+}
+
+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(' ');
+      });
+    },
 
-Effect = {}
-Effect2 = Effect; // deprecated
+    add: function(element) {
+      element = $(element);
+      for(var i = 1; i &lt; arguments.length; i++) {
+        Element.Class.remove(element, arguments[i]);
+        element.className += (element.className.length &gt; 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 &lt; arguments.length; i++) {
+        if((typeof arguments[i] == 'object') &amp;&amp; 
+          (arguments[i].constructor == Array)) {
+          for(var j = 0; j &lt; arguments[i].length; j++) {
+            regEx = new RegExp(&quot;(^|\\s)&quot; + arguments[i][j] + &quot;(\\s|$)&quot;);
+            if(!regEx.test(element.className)) return false;
+          }
+        } else {
+          regEx = new RegExp(&quot;(^|\\s)&quot; + arguments[i] + &quot;(\\s|$)&quot;);
+          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 &lt; arguments.length; i++) {
+        if((typeof arguments[i] == 'object') &amp;&amp; 
+          (arguments[i].constructor == Array)) {
+          for(var j = 0; j &lt; arguments[i].length; j++) {
+            regEx = new RegExp(&quot;(^|\\s)&quot; + arguments[i][j] + &quot;(\\s|$)&quot;);
+            if(regEx.test(element.className)) return true;
+          }
+        } else {
+          regEx = new RegExp(&quot;(^|\\s)&quot; + arguments[i] + &quot;(\\s|$)&quot;);
+          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 &lt; children.length; i++)
+        if (Element.Class.has(children[i], className))
+          elements.push(children[i]);
+
+      return elements;
+    }
+}
+
+/*--------------------------------------------------------------------------*/
+
+var Effect = {
+  tagifyText: function(element) {
+    var tagifyStyle = &quot;position:relative&quot;;
+    if(/MSIE/.test(navigator.userAgent)) tagifyStyle += &quot;;zoom:1&quot;;
+    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 == &quot; &quot; ? String.fromCharCode(160) : character), 
+              child);
+        });
+        Element.remove(child);
+      }
+    });
+  },
+  multiple: function(element, effect) {
+    var elements;
+    if(((typeof element == 'object') || 
+        (typeof element == 'function')) &amp;&amp; 
+       (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 ------------- */
 
@@ -40,7 +351,7 @@ Effect.Transitions.reverse  = function(pos) {
   return 1-pos;
 }
 Effect.Transitions.flicker = function(pos) {
-  return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random(0.25);
+  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;
@@ -56,73 +367,111 @@ Effect.Transitions.full = function(pos) {
   return 1;
 }
 
-/* ------------- element ext -------------- */
-
-Element.makePositioned = function(element) {
-  element = $(element);
-  if(element.style.position == &quot;&quot;)
-    element.style.position = &quot;relative&quot;;
-}
-
-Element.makeClipping = function(element) {
-  element = $(element);
-  element._overflow = element.style.overflow || 'visible';
-  if(element._overflow!='hidden') element.style.overflow = 'hidden';
-}
+/* ------------- core effects ------------- */
 
-Element.undoClipping = function(element) {
-  element = $(element);
-  if(element._overflow!='hidden') element.style.overflow = element._overflow;
+Effect.Queue = {
+  effects:  [],
+  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);
+  }
 }
 
-/* ------------- core effects ------------- */
-
 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. 100fps
+      fps:        25.0,  // max. 25fps due to Effect.Queue implementation
       sync:       false, // true for combining
       from:       0.0,
-      to:         1.0
+      to:         1.0,
+      delay:      0.0,
+      queue:      'parallel'
     }, options || {});
   },
   start: function(options) {
     this.setOptions(options || {});
     this.currentFrame = 0;
-    this.startOn      = new Date().getTime();
+    this.state        = 'idle';
+    this.startOn      = this.options.delay*1000;
     this.finishOn     = this.startOn + (this.options.duration*1000);
-    if(this.options.beforeStart) this.options.beforeStart(this);
-    if(!this.options.sync) this.loop();  
+    this.event('beforeStart');
+    if(!this.options.sync) Effect.Queue.add(this);
   },
-  loop: function() {
-    var timePos = new Date().getTime();
-    if(timePos &gt;= this.finishOn) {
-      this.render(this.options.to);
-      if(this.finish) this.finish(); 
-      if(this.options.afterFinish) this.options.afterFinish(this);
-      return;  
-    }
-    var pos   = (timePos - this.startOn) / (this.finishOn - this.startOn);
-    var frame = Math.round(pos * this.options.fps * this.options.duration);
-    if(frame &gt; this.currentFrame) {
-      this.render(pos);
-      this.currentFrame = frame;
+  loop: function(timePos) {
+    if(timePos &gt;= this.startOn) {
+      if(timePos &gt;= 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 &gt; this.currentFrame) {
+        this.render(pos);
+        this.currentFrame = frame;
+      }
     }
-    this.timeout = setTimeout(this.loop.bind(this), 10);
   },
   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; 
-    if(this.options.beforeUpdate) this.options.beforeUpdate(this);
+    pos += this.options.from;
+    this.position = pos;
+    this.event('beforeUpdate');
     if(this.update) this.update(pos);
-    if(this.options.afterUpdate) this.options.afterUpdate(this);  
+    this.event('afterUpdate');
   },
   cancel: function() {
-    if(this.timeout) clearTimeout(this.timeout);
+    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);
   }
 }
 
@@ -133,35 +482,34 @@ Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), {
     this.start(arguments[1]);
   },
   update: function(position) {
-    for (var i = 0; i &lt; this.effects.length; i++)
-      this.effects[i].render(position);  
+    this.effects.invoke('render', position);
   },
   finish: function(position) {
-    for (var i = 0; i &lt; this.effects.length; i++)
-      if(this.effects[i].finish) this.effects[i].finish(position);
+    this.effects.each( function(effect) {
+      effect.render(1.0);
+      effect.cancel();
+      effect.event('beforeFinish');
+      if(effect.finish) effect.finish(position);
+      effect.event('afterFinish');
+    });
   }
 });
 
-// Internet Explorer caveat: works only on elements the have
-// a 'layout', meaning having a given width or height. 
-// There is no way to safely set this automatically.
 Effect.Opacity = Class.create();
 Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), {
   initialize: function(element) {
     this.element = $(element);
-    options = Object.extend({
-      from: 0.0,
+    // make this work on IE on elements without 'layout'
+    if(/MSIE/.test(navigator.userAgent) &amp;&amp; (!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) {
-    this.setOpacity(position);
-  }, 
-  setOpacity: function(opacity) {
-    opacity = (opacity == 1) ? 0.99999 : opacity;
-    this.element.style.opacity = opacity;
-    this.element.style.filter = &quot;alpha(opacity:&quot;+opacity*100+&quot;)&quot;;
+    Element.setOpacity(this.element, position);
   }
 });
 
@@ -169,16 +517,23 @@ Effect.MoveBy = Class.create();
 Object.extend(Object.extend(Effect.MoveBy.prototype, Effect.Base.prototype), {
   initialize: function(element, toTop, toLeft) {
     this.element      = $(element);
-    this.originalTop  = parseFloat(this.element.style.top || '0');
-    this.originalLeft = parseFloat(this.element.style.left || '0');
     this.toTop        = toTop;
     this.toLeft       = toLeft;
-    Element.makePositioned(this.element);
     this.start(arguments[3]);
   },
+  setup: function() {
+    // Bug in Opera: Opera returns the &quot;real&quot; position of a static element or
+    // relative element that does not have top/left explicitly set.
+    // ==&gt; 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) {
-    topd  = this.toTop  * position + this.originalTop;
-    leftd = this.toLeft * position + this.originalLeft;
+    var topd  = this.toTop  * position + this.originalTop;
+    var leftd = this.toLeft * position + this.originalLeft;
     this.setPosition(topd, leftd);
   },
   setPosition: function(topd, leftd) {
@@ -191,55 +546,77 @@ Effect.Scale = Class.create();
 Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), {
   initialize: function(element, percent) {
     this.element = $(element)
-    options = Object.extend({
+    var options = Object.extend({
       scaleX: true,
       scaleY: true,
       scaleContent: true,
       scaleFromCenter: false,
       scaleMode: 'box',        // 'box' or 'contents' or {} with provided values
-      scaleFrom: 100.0
+      scaleFrom: 100.0,
+      scaleTo:   percent
     }, arguments[2] || {});
-    this.originalTop    = this.element.offsetTop;
-    this.originalLeft   = this.element.offsetLeft;
-    if(this.element.style.fontSize==&quot;&quot;) this.sizeEm = 1.0;
-    if(this.element.style.fontSize &amp;&amp; this.element.style.fontSize.indexOf(&quot;em&quot;)&gt;0)
-      this.sizeEm      = parseFloat(this.element.style.fontSize);
-    this.factor = (percent/100.0) - (options.scaleFrom/100.0);
-    if(options.scaleMode=='box') {
-      this.originalHeight = this.element.clientHeight;
-      this.originalWidth  = this.element.clientWidth; 
-    } else 
-    if(options.scaleMode=='contents') {
-      this.originalHeight = this.element.scrollHeight;
-      this.originalWidth  = this.element.scrollWidth;
-    } else {
-      this.originalHeight = options.scaleMode.originalHeight;
-      this.originalWidth  = options.scaleMode.originalWidth;
-    }
     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') || &quot;100%&quot;;
+    ['em','px','%'].each( function(fontSizeType) {
+      if(fontSize.indexOf(fontSizeType)&gt;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) {
-    currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
-    if(this.options.scaleContent &amp;&amp; this.sizeEm) 
-      this.element.style.fontSize = this.sizeEm*currentScale + &quot;em&quot;;
-    this.setDimensions(
-      this.originalWidth * currentScale, 
-      this.originalHeight * currentScale);
+    var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
+    if(this.options.scaleContent &amp;&amp; this.fontSize)
+      this.element.style.fontSize = this.fontSize*currentScale + this.fontSizeType;
+    this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
   },
-
-  setDimensions: function(width, height) {
-    if(this.options.scaleX) this.element.style.width = width + 'px';
-    if(this.options.scaleY) this.element.style.height = height + 'px';
+  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) {
-      topd  = (height - this.originalHeight)/2;
-      leftd = (width  - this.originalWidth)/2;
-      if(this.element.style.position=='absolute') {
-        if(this.options.scaleY) this.element.style.top = this.originalTop-topd + &quot;px&quot;;
-        if(this.options.scaleX) this.element.style.left = this.originalLeft-leftd + &quot;px&quot;;
+      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 + &quot;px&quot;;
+        if(this.options.scaleX) els.left = this.originalLeft-leftd + &quot;px&quot;;
       } else {
-        if(this.options.scaleY) this.element.style.top = -topd + &quot;px&quot;;
-        if(this.options.scaleX) this.element.style.left = -leftd + &quot;px&quot;;
+        if(this.options.scaleY) els.top = -topd + &quot;px&quot;;
+        if(this.options.scaleX) els.left = -leftd + &quot;px&quot;;
       }
     }
   }
@@ -249,44 +626,39 @@ Effect.Highlight = Class.create();
 Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), {
   initialize: function(element) {
     this.element = $(element);
-    
-    // try to parse current background color as default for endcolor
-    // browser stores this as: &quot;rgb(255, 255, 255)&quot;, convert to &quot;#ffffff&quot; format
-    var endcolor = &quot;#ffffff&quot;;
-    var current = this.element.style.backgroundColor;
-    if(current &amp;&amp; current.slice(0,4) == &quot;rgb(&quot;) {
-      endcolor = &quot;#&quot;;
-      var cols = current.slice(4,current.length-1).split(',');
-      var i=0; do { endcolor += parseInt(cols[i]).toColorPart() } while (++i&lt;3); }
-      
     var options = Object.extend({
-      startcolor:   &quot;#ffff99&quot;,
-      endcolor:     endcolor,
-      restorecolor: current 
+      startcolor:   &quot;#ffff99&quot;
     }, arguments[1] || {});
-    
+    this.start(options);
+  },
+  setup: function() {
+    // Disable background image during the effect
+    this.oldBgImage = this.element.style.backgroundImage;
+    this.element.style.backgroundImage = &quot;none&quot;;
+    if(!this.options.endcolor)
+      this.options.endcolor = Element.getStyle(this.element, 'background-color').parseColor('#ffffff');
+    if (typeof this.options.restorecolor == &quot;undefined&quot;)
+      this.options.restorecolor = this.element.style.backgroundColor;
     // init color calculations
     this.colors_base = [
-      parseInt(options.startcolor.slice(1,3),16),
-      parseInt(options.startcolor.slice(3,5),16),
-      parseInt(options.startcolor.slice(5),16) ];
+      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(options.endcolor.slice(1,3),16)-this.colors_base[0],
-      parseInt(options.endcolor.slice(3,5),16)-this.colors_base[1],
-      parseInt(options.endcolor.slice(5),16)-this.colors_base[2] ];
-
-    this.start(options);
+      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 colors = [
-      Math.round(this.colors_base[0]+(this.colors_delta[0]*position)),
-      Math.round(this.colors_base[1]+(this.colors_delta[1]*position)),
-      Math.round(this.colors_base[2]+(this.colors_delta[2]*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 = &quot;#&quot; +
       colors[0].toColorPart() + colors[1].toColorPart() + colors[2].toColorPart();
   },
   finish: function() {
     this.element.style.backgroundColor = this.options.restorecolor;
+    this.element.style.backgroundImage = this.oldBgImage;
   }
 });
 
@@ -294,6 +666,9 @@ 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 ? 
@@ -302,8 +677,7 @@ Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), {
         (document.documentElement.clientHeight ? 
           document.documentElement.clientHeight : document.body.clientHeight);
     this.scrollStart = Position.deltaY;
-    this.delta  = (offsets[1] &gt; max ? max : offsets[1]) - this.scrollStart;
-    this.start(arguments[1] || {});
+    this.delta = (offsets[1] &gt; max ? max : offsets[1]) - this.scrollStart;
   },
   update: function(position) {
     Position.prepare();
@@ -312,51 +686,61 @@ Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), {
   }
 });
 
-/* ------------- prepackaged effects ------------- */
+/* ------------- combination effects ------------- */
 
 Effect.Fade = function(element) {
-  options = Object.extend({
-  from: 1.0,
+  var oldOpacity = Element.getInlineOpacity(element);
+  var options = Object.extend({
+  from: Element.getOpacity(element) || 1.0,
   to:   0.0,
-  afterFinish: function(effect) 
-    { Element.hide(effect.element);
-      effect.setOpacity(1); } 
+  afterFinishInternal: function(effect) 
+    { if (effect.options.to == 0) {
+        Element.hide(effect.element);
+        Element.setInlineOpacity(effect.element, oldOpacity);
+      }  
+    }
   }, arguments[1] || {});
-  new Effect.Opacity(element,options);
+  return new Effect.Opacity(element,options);
 }
 
 Effect.Appear = function(element) {
-  options = Object.extend({
-  from: 0.0,
+  var options = Object.extend({
+  from: (Element.getStyle(element, &quot;display&quot;) == &quot;none&quot; ? 0.0 : Element.getOpacity(element) || 0.0),
   to:   1.0,
-  beforeStart: function(effect)  
-    { effect.setOpacity(0);
-      Element.show(effect.element); },
-  afterUpdate: function(effect)  
-    { Element.show(effect.element); }
+  beforeSetup: function(effect)  
+    { Element.setOpacity(effect.element, effect.options.from);
+      Element.show(effect.element); }
   }, arguments[1] || {});
-  new Effect.Opacity(element,options);
+  return new Effect.Opacity(element,options);
 }
 
 Effect.Puff = function(element) {
-  new Effect.Parallel(
-   [ new Effect.Scale(element, 200, { sync: true, scaleFromCenter: true }), 
-     new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0 } ) ], 
-     { duration: 1.0, 
-      afterUpdate: function(effect) 
+  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'; },
-      afterFinish: function(effect)
-       { Element.hide(effect.effects[0].element); }
-     }
+      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);
-  new Effect.Scale(element, 0, 
+  return new Effect.Scale(element, 0, 
     Object.extend({ scaleContent: false, 
       scaleX: false, 
-      afterFinish: function(effect) 
+      restoreAfterFinish: true,
+      afterFinishInternal: function(effect)
         { 
           Element.hide(effect.element);
           Element.undoClipping(effect.element);
@@ -366,120 +750,179 @@ Effect.BlindUp = function(element) {
 }
 
 Effect.BlindDown = function(element) {
-  $(element).style.height   = '0px';
-  Element.makeClipping(element);
-  Element.show(element);
-  new Effect.Scale(element, 100, 
+  element = $(element);
+  var oldHeight = element.style.height;
+  var elementDimensions = Element.getDimensions(element);
+  return new Effect.Scale(element, 100, 
     Object.extend({ scaleContent: false, 
-      scaleX: false, 
-      scaleMode: 'contents',
+      scaleX: false,
       scaleFrom: 0,
-      afterFinish: function(effect) {
+      scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
+      restoreAfterFinish: true,
+      afterSetup: function(effect) {
+        Element.makeClipping(effect.element);
+        effect.element.style.height = &quot;0px&quot;;
+        Element.show(effect.element); 
+      },  
+      afterFinishInternal: function(effect) {
         Element.undoClipping(effect.element);
+        effect.element.style.height = oldHeight;
       }
     }, arguments[1] || {})
   );
 }
 
 Effect.SwitchOff = function(element) {
-  new Effect.Appear(element,
-    { duration: 0.4,
-     transition: Effect.Transitions.flicker,
-     afterFinish: function(effect)
-      { effect.element.style.overflow = 'hidden';
-        new Effect.Scale(effect.element, 1, 
-         { duration: 0.3, scaleFromCenter: true,
-          scaleX: false, scaleContent: false,
-          afterUpdate: function(effect) { 
-           if(effect.element.style.position==&quot;&quot;)
-             effect.element.style.position = 'relative'; },
-          afterFinish: function(effect) { Element.hide(effect.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) {
-  new Effect.Parallel(
+  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, from: 1.0 } ) ], 
-    { duration: 0.5, 
-     afterFinish: function(effect)
-       { Element.hide(effect.effects[0].element); } 
-    });
+      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) {
-  new Effect.MoveBy(element, 0, 20, 
-    { duration: 0.05, afterFinish: function(effect) {
+  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, afterFinish: function(effect) { 
+    { duration: 0.1, afterFinishInternal: function(effect) {
   new Effect.MoveBy(effect.element, 0, 40, 
-    { duration: 0.1, afterFinish: function(effect) {  
+    { duration: 0.1, afterFinishInternal: function(effect) {
   new Effect.MoveBy(effect.element, 0, -40, 
-    { duration: 0.1, afterFinish: function(effect) {  
+    { duration: 0.1, afterFinishInternal: function(effect) {
   new Effect.MoveBy(effect.element, 0, 40, 
-    { duration: 0.1, afterFinish: function(effect) {  
+    { duration: 0.1, afterFinishInternal: function(effect) {
   new Effect.MoveBy(effect.element, 0, -20, 
-    { duration: 0.05, afterFinish: function(effect) {  
+    { 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.style.height   = '0px';
-  Element.makeClipping(element);
   Element.cleanWhitespace(element);
-  Element.makePositioned(element.firstChild);
-  Element.show(element);
-  new Effect.Scale(element, 100, 
+  // 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, 
-    scaleMode: 'contents',
     scaleFrom: 0,
-    afterUpdate: function(effect) 
-      { effect.element.firstChild.style.bottom = 
-          (effect.originalHeight - effect.element.clientHeight) + 'px'; },
-    afterFinish: function(effect) 
-      {  Element.undoClipping(effect.element); }
+    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 = &quot;&quot;;
+      Element.makeClipping(effect.element);
+      element.style.height = '0';
+      Element.show(element); 
+    },  
+    afterUpdateInternal: function(effect) { 
+      effect.element.firstChild.style.bottom = 
+        (effect.originalHeight - 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.makeClipping(element);
   Element.cleanWhitespace(element);
-  Element.makePositioned(element.firstChild);
-  Element.show(element);
-  new Effect.Scale(element, 0, 
+  var oldInnerBottom = element.firstChild.style.bottom;
+  return new Effect.Scale(element, 0, 
    Object.extend({ scaleContent: false, 
     scaleX: false, 
-    afterUpdate: function(effect) 
-      { effect.element.firstChild.style.bottom = 
-          (effect.originalHeight - effect.element.clientHeight) + 'px'; },
-    afterFinish: function(effect)
-      { 
+    scaleMode: 'box',
+    scaleFrom: 100,
+    restoreAfterFinish: true,
+    beforeStartInternal: function(effect) { 
+      Element.makePositioned(effect.element.firstChild);
+      if (window.opera) effect.element.firstChild.style.top = &quot;&quot;;
+      Element.makeClipping(effect.element);
+      Element.show(element); 
+    },  
+    afterUpdateInternal: function(effect) { 
+     effect.element.firstChild.style.bottom = 
+       (effect.originalHeight - effect.element.clientHeight) + 'px'; },
+    afterFinishInternal: function(effect) { 
         Element.hide(effect.element);
-        Element.undoClipping(effect.element);
-      }
+        Element.undoClipping(effect.element); 
+        Element.undoPositioned(effect.element.firstChild);
+        effect.element.firstChild.style.bottom = oldInnerBottom; }
    }, arguments[1] || {})
   );
 }
 
 Effect.Squish = function(element) {
- new Effect.Scale(element, 0, 
-   { afterFinish: function(effect) { Element.hide(effect.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 originalWidth = element.clientWidth;
-  var originalHeight = element.clientHeight;
-  element.style.overflow = 'hidden';
-  Element.show(element);
+  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;
@@ -517,18 +960,40 @@ Effect.Grow = function(element) {
       break;
   }
   
-  new Effect.MoveBy(element, initialMoveY, initialMoveX, { 
+  return new Effect.MoveBy(element, initialMoveY, initialMoveX, { 
     duration: 0.01, 
-    beforeUpdate: function(effect) { $(element).style.height = '0px'; },
-    afterFinish: function(effect) {
+    beforeSetup: function(effect) { 
+      Element.hide(effect.element);
+      Element.makeClipping(effect.element);
+      Element.makePositioned(effect.element);
+    },
+    afterFinishInternal: function(effect) {
       new Effect.Parallel(
-        [ new Effect.Opacity(element, { sync: true, to: 1.0, from: 0.0, transition: opacityTransition }),
-          new Effect.MoveBy(element, moveY, moveX, { sync: true, transition: moveTransition }),
-          new Effect.Scale(element, 100, { 
+        [ 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: 0, scaleTo: 100, transition: scaleTransition })],
-        options); }
-    });
+            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;
+               Element.setInlineOpacity(el, oldOpacity);
+             }
+           }, options)
+      )
+    }
+  });
 }
 
 Effect.Shrink = function(element) {
@@ -537,8 +1002,11 @@ Effect.Shrink = function(element) {
   
   var originalWidth = element.clientWidth;
   var originalHeight = element.clientHeight;
-  element.style.overflow = 'hidden';
-  Element.show(element);
+  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;
@@ -569,44 +1037,65 @@ Effect.Shrink = function(element) {
       break;
   }
   
-  new Effect.Parallel(
+  return new Effect.Parallel(
     [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: opacityTransition }),
-      new Effect.Scale(element, 0, { sync: true, transition: moveTransition }),
-      new Effect.MoveBy(element, moveY, moveX, { sync: true, transition: scaleTransition }) ],
-    options);
+      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);
-  new Effect.Opacity(element, 
-    Object.extend(Object.extend({  duration: 3.0,
-       afterFinish: function(effect) { Element.show(effect.element); }
+  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).style.overflow = 'hidden';
- new Effect.Scale(element, 5, Object.extend({   
-   scaleContent: false,
-   scaleTo: 100,
-   scaleX: false,
-   afterFinish: function(effect) {
-   new Effect.Scale(element, 1, { 
-     scaleContent: false, 
-     scaleTo: 0,
-     scaleY: false,
-     afterFinish: function(effect) { Element.hide(effect.element) } });
- }}, arguments[1] || {}));
-}
-
-// old: new Effect.ContentZoom(element, percent)
-// new: Element.setContentZoom(element, percent) 
-
-Element.setContentZoom = function(element, percent) {
-  var element = $(element);
-  element.style.fontSize = (percent/100) + &quot;em&quot;;  
-  if(navigator.appVersion.indexOf('AppleWebKit')&gt;0) window.scrollBy(0,0);
+  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>
      <filename>public/javascripts/effects.js</filename>
    </modified>
    <modified>
      <diff>@@ -1,8 +1,8 @@
-/*  Prototype JavaScript framework, version 1.3.1
+/*  Prototype JavaScript framework, version 1.4.0_rc0
  *  (c) 2005 Sam Stephenson &lt;sam@conio.net&gt;
  *
  *  THIS FILE IS AUTOMATICALLY GENERATED. When sending patches, please diff
- *  against the source tree, available from the Prototype darcs repository. 
+ *  against the source tree, available from the Prototype darcs repository.
  *
  *  Prototype is freely distributable under the terms of an MIT-style license.
  *
@@ -11,13 +11,15 @@
 /*--------------------------------------------------------------------------*/
 
 var Prototype = {
-  Version: '1.3.1',
-  emptyFunction: function() {}
+  Version: '1.4.0_rc0',
+
+  emptyFunction: function() {},
+  K: function(x) {return x}
 }
 
 var Class = {
   create: function() {
-    return function() { 
+    return function() {
       this.initialize.apply(this, arguments);
     }
   }
@@ -32,29 +34,47 @@ Object.extend = function(destination, source) {
   return destination;
 }
 
-Object.prototype.extend = function(object) {
-  return Object.extend.apply(this, [this, object]);
+Object.inspect = function(object) {
+  try {
+    if (object == undefined) return 'undefined';
+    if (object == null) return 'null';
+    return object.inspect ? object.inspect() : object.toString();
+  } catch (e) {
+    if (e instanceof RangeError) return '...';
+    throw e;
+  }
 }
 
 Function.prototype.bind = function(object) {
   var __method = this;
   return function() {
-    __method.apply(object, arguments);
+    return __method.apply(object, arguments);
   }
 }
 
 Function.prototype.bindAsEventListener = function(object) {
   var __method = this;
   return function(event) {
-    __method.call(object, event || window.event);
+    return __method.call(object, event || window.event);
   }
 }
 
-Number.prototype.toColorPart = function() {
-  var digits = this.toString(16);
-  if (this &lt; 16) return '0' + digits;
-  return digits;
-}
+Object.extend(Number.prototype, {
+  toColorPart: function() {
+    var digits = this.toString(16);
+    if (this &lt; 16) return '0' + digits;
+    return digits;
+  },
+
+  succ: function() {
+    return this + 1;
+  },
+
+  times: function(iterator) {
+    $R(0, this, true).each(iterator);
+    return this;
+  }
+});
 
 var Try = {
   these: function() {
@@ -90,10 +110,10 @@ PeriodicalExecuter.prototype = {
 
   onTimerEvent: function() {
     if (!this.currentlyExecuting) {
-      try { 
+      try {
         this.currentlyExecuting = true;
-        this.callback(); 
-      } finally { 
+        this.callback();
+      } finally {
         this.currentlyExecuting = false;
       }
     }
@@ -110,7 +130,7 @@ function $() {
     if (typeof element == 'string')
       element = document.getElementById(element);
 
-    if (arguments.length == 1) 
+    if (arguments.length == 1)
       return element;
 
     elements.push(element);
@@ -118,36 +138,7 @@ function $() {
 
   return elements;
 }
-
-if (!Array.prototype.push) {
-  Array.prototype.push = function() {
-		var startLength = this.length;
-		for (var i = 0; i &lt; 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 &lt; parameters.length; i++)
-      parameterStrings[i] = 'parameters[' + i + ']';
-    
-    object.__apply__ = this;
-    var result = eval('object.__apply__(' + 
-      parameterStrings[i].join(', ') + ')');
-    object.__apply__ = null;
-    
-    return result;
-  }
-}
-
-String.prototype.extend({
+Object.extend(String.prototype, {
   stripTags: function() {
     return this.replace(/&lt;\/?[^&gt;]+&gt;/gi, '');
   },
@@ -162,9 +153,368 @@ String.prototype.extend({
   unescapeHTML: function() {
     var div = document.createElement('div');
     div.innerHTML = this.stripTags();
-    return div.childNodes[0].nodeValue;
+    return div.childNodes[0] ? div.childNodes[0].nodeValue : '';
+  },
+
+  toQueryParams: function() {
+    var pairs = this.match(/^\??(.*)$/)[1].split('&amp;');
+    return pairs.inject({}, function(params, pairString) {
+      var pair = pairString.split('=');
+      params[pair[0]] = pair[1];
+      return params;
+    });
+  },
+
+  toArray: function() {
+    return this.split('');
+  },
+
+  camelize: function() {
+    var oStringList = this.split('-');
+    if (oStringList.length == 1) return oStringList[0];
+
+    var camelizedString = this.indexOf('-') == 0
+      ? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1)
+      : oStringList[0];
+
+    for (var i = 1, len = oStringList.length; i &lt; len; i++) {
+      var s = oStringList[i];
+      camelizedString += s.charAt(0).toUpperCase() + s.substring(1);
+    }
+
+    return camelizedString;
+  },
+
+  inspect: function() {
+    return &quot;'&quot; + this.replace('\\', '\\\\').replace(&quot;'&quot;, '\\\'') + &quot;'&quot;;
+  }
+});
+
+String.prototype.parseQuery = String.prototype.toQueryParams;
+
+var $break    = new Object();
+var $continue = new Object();
+
+var Enumerable = {
+  each: function(iterator) {
+    var index = 0;
+    try {
+      this._each(function(value) {
+        try {
+          iterator(value, index++);
+        } catch (e) {
+          if (e != $continue) throw e;
+        }
+      });
+    } catch (e) {
+      if (e != $break) throw e;
+    }
+  },
+
+  all: function(iterator) {
+    var result = true;
+    this.each(function(value, index) {
+      if (!(result &amp;= (iterator || Prototype.K)(value, index)))
+        throw $break;
+    });
+    return result;
+  },
+
+  any: function(iterator) {
+    var result = true;
+    this.each(function(value, index) {
+      if (result &amp;= (iterator || Prototype.K)(value, index))
+        throw $break;
+    });
+    return result;
+  },
+
+  collect: function(iterator) {
+    var results = [];
+    this.each(function(value, index) {
+      results.push(iterator(value, index));
+    });
+    return results;
+  },
+
+  detect: function (iterator) {
+    var result;
+    this.each(function(value, index) {
+      if (iterator(value, index)) {
+        result = value;
+        throw $break;
+      }
+    });
+    return result;
+  },
+
+  findAll: function(iterator) {
+    var results = [];
+    this.each(function(value, index) {
+      if (iterator(value, index))
+        results.push(value);
+    });
+    return results;
+  },
+
+  grep: function(pattern, iterator) {
+    var results = [];
+    this.each(function(value, index) {
+      var stringValue = value.toString();
+      if (stringValue.match(pattern))
+        results.push((iterator || Prototype.K)(value, index));
+    })
+    return results;
+  },
+
+  include: function(object) {
+    var found = false;
+    this.each(function(value) {
+      if (value == object) {
+        found = true;
+        throw $break;
+      }
+    });
+    return found;
+  },
+
+  inject: function(memo, iterator) {
+    this.each(function(value, index) {
+      memo = iterator(memo, value, index);
+    });
+    return memo;
+  },
+
+  invoke: function(method) {
+    var args = $A(arguments).slice(1);
+    return this.collect(function(value) {
+      return value[method].apply(value, args);
+    });
+  },
+
+  max: function(iterator) {
+    var result;
+    this.each(function(value, index) {
+      value = (iterator || Prototype.K)(value, index);
+      if (value &gt;= (result || value))
+        result = value;
+    });
+    return result;
+  },
+
+  min: function(iterator) {
+    var result;
+    this.each(function(value, index) {
+      value = (iterator || Prototype.K)(value, index);
+      if (value &lt;= (result || value))
+        result = value;
+    });
+    return result;
+  },
+
+  partition: function(iterator) {
+    var trues = [], falses = [];
+    this.each(function(value, index) {
+      ((iterator || Prototype.K)(value, index) ?
+        trues : falses).push(value);
+    });
+    return [trues, falses];
+  },
+
+  pluck: function(property) {
+    var results = [];
+    this.each(function(value, index) {
+      results.push(value[property]);
+    });
+    return results;
+  },
+
+  reject: function(iterator) {
+    var results = [];
+    this.each(function(value, index) {
+      if (!iterator(value, index))
+        results.push(value);
+    });
+    return results;
+  },
+
+  sortBy: function(iterator) {
+    return this.collect(function(value, index) {
+      return {value: value, criteria: iterator(value, index)};
+    }).sort(function(left, right) {
+      var a = left.criteria, b = right.criteria;
+      return a &lt; b ? -1 : a &gt; b ? 1 : 0;
+    }).pluck('value');
+  },
+
+  toArray: function() {
+    return this.collect(Prototype.K);
+  },
+
+  zip: function() {
+    var iterator = Prototype.K, args = $A(arguments);
+    if (typeof args.last() == 'function')
+      iterator = args.pop();
+
+    var collections = [this].concat(args).map($A);
+    return this.map(function(value, index) {
+      iterator(value = collections.pluck(index));
+      return value;
+    });
+  },
+
+  inspect: function() {
+    return '#&lt;Enumerable:' + this.toArray().inspect() + '&gt;';
+  }
+}
+
+Object.extend(Enumerable, {
+  map:     Enumerable.collect,
+  find:    Enumerable.detect,
+  select:  Enumerable.findAll,
+  member:  Enumerable.include,
+  entries: Enumerable.toArray
+});
+var $A = Array.from = function(iterable) {
+  if (iterable.toArray) {
+    return iterable.toArray();
+  } else {
+    var results = [];
+    for (var i = 0; i &lt; iterable.length; i++)
+      results.push(iterable[i]);
+    return results;
+  }
+}
+
+Object.extend(Array.prototype, Enumerable);
+
+Object.extend(Array.prototype, {
+  _each: function(iterator) {
+    for (var i = 0; i &lt; this.length; i++)
+      iterator(this[i]);
+  },
+
+  first: function() {
+    return this[0];
+  },
+
+  last: function() {
+    return this[this.length - 1];
+  },
+
+  compact: function() {
+    return this.select(function(value) {
+      return value != undefined || value != null;
+    });
+  },
+
+  flatten: function() {
+    return this.inject([], function(array, value) {
+      return array.concat(value.constructor == Array ?
+        value.flatten() : [value]);
+    });
+  },
+
+  without: function() {
+    var values = $A(arguments);
+    return this.select(function(value) {
+      return !values.include(value);
+    });
+  },
+
+  indexOf: function(object) {
+    for (var i = 0; i &lt; this.length; i++)
+      if (this[i] == object) return i;
+    return false;
+  },
+
+  reverse: function() {
+    var result = [];
+    for (var i = this.length; i &gt; 0; i--)
+      result.push(this[i-1]);
+    return result;
+  },
+
+  inspect: function() {
+    return '[' + this.map(Object.inspect).join(', ') + ']';
   }
 });
+var Hash = {
+  _each: function(iterator) {
+    for (key in this) {
+      var value = this[key];
+      if (typeof value == 'function') continue;
+
+      var pair = [key, value];
+      pair.key = key;
+      pair.value = value;
+      iterator(pair);
+    }
+  },
+
+  keys: function() {
+    return this.pluck('key');
+  },
+
+  values: function() {
+    return this.pluck('value');
+  },
+
+  merge: function(hash) {
+    return $H(hash).inject($H(this), function(mergedHash, pair) {
+      mergedHash[pair.key] = pair.value;
+      return mergedHash;
+    });
+  },
+
+  toQueryString: function() {
+    return this.map(function(pair) {
+      return pair.map(encodeURIComponent).join('=');
+    }).join('&amp;');
+  },
+
+  inspect: function() {
+    return '#&lt;Hash:{' + this.map(function(pair) {
+      return pair.map(Object.inspect).join(': ');
+    }).join(', ') + '}&gt;';
+  }
+}
+
+function $H(object) {
+  var hash = Object.extend({}, object || {});
+  Object.extend(hash, Enumerable);
+  Object.extend(hash, Hash);
+  return hash;
+}
+var Range = Class.create();
+Object.extend(Range.prototype, Enumerable);
+Object.extend(Range.prototype, {
+  initialize: function(start, end, exclusive) {
+    this.start = start;
+    this.end = end;
+    this.exclusive = exclusive;
+  },
+
+  _each: function(iterator) {
+    var value = this.start;
+    do {
+      iterator(value);
+      value = value.succ();
+    } while (this.include(value));
+  },
+
+  include: function(value) {
+    if (value &lt; this.start)
+      return false;
+    if (this.exclusive)
+      return value &lt; this.end;
+    return value &lt;= this.end;
+  }
+});
+
+var $R = function(start, end, exclusive) {
+  return new Range(start, end, exclusive);
+}
 
 var Ajax = {
   getTransport: function() {
@@ -173,9 +523,51 @@ var Ajax = {
       function() {return new ActiveXObject('Microsoft.XMLHTTP')},
       function() {return new XMLHttpRequest()}
     ) || false;
-  }
+  },
+
+  activeRequestCount: 0
 }
 
+Ajax.Responders = {
+  responders: [],
+
+  _each: function(iterator) {
+    this.responders._each(iterator);
+  },
+
+  register: function(responderToAdd) {
+    if (!this.include(responderToAdd))
+      this.responders.push(responderToAdd);
+  },
+
+  unregister: function(responderToRemove) {
+    this.responders = this.responders.without(responderToRemove);
+  },
+
+  dispatch: function(callback, request, transport, json) {
+    this.each(function(responder) {
+      if (responder[callback] &amp;&amp; typeof responder[callback] == 'function') {
+        try {
+          responder[callback].apply(responder, [request, transport, json]);
+        } catch (e) {
+        }
+      }
+    });
+  }
+};
+
+Object.extend(Ajax.Responders, Enumerable);
+
+Ajax.Responders.register({
+  onCreate: function() {
+    Ajax.activeRequestCount++;
+  },
+
+  onComplete: function() {
+    Ajax.activeRequestCount--;
+  }
+});
+
 Ajax.Base = function() {};
 Ajax.Base.prototype = {
   setOptions: function(options) {
@@ -183,12 +575,13 @@ Ajax.Base.prototype = {
       method:       'post',
       asynchronous: true,
       parameters:   ''
-    }.extend(options || {});
+    }
+    Object.extend(this.options, options || {});
   },
 
   responseIsSuccess: function() {
     return this.transport.status == undefined
-        || this.transport.status == 0 
+        || this.transport.status == 0
         || (this.transport.status &gt;= 200 &amp;&amp; this.transport.status &lt; 300);
   },
 
@@ -198,10 +591,10 @@ Ajax.Base.prototype = {
 }
 
 Ajax.Request = Class.create();
-Ajax.Request.Events = 
+Ajax.Request.Events =
   ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
 
-Ajax.Request.prototype = (new Ajax.Base()).extend({
+Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
   initialize: function(url, options) {
     this.transport = Ajax.getTransport();
     this.setOptions(options);
@@ -213,10 +606,13 @@ Ajax.Request.prototype = (new Ajax.Base()).extend({
     if (parameters.length &gt; 0) parameters += '&amp;_=';
 
     try {
+      this.url = url;
       if (this.options.method == 'get')
-        url += '?' + parameters;
+        this.url += '?' + parameters;
+
+      Ajax.Responders.dispatch('onCreate', this, this.transport);
 
-      this.transport.open(this.options.method, url,
+      this.transport.open(this.options.method, this.url,
         this.options.asynchronous);
 
       if (this.options.asynchronous) {
@@ -234,17 +630,17 @@ Ajax.Request.prototype = (new Ajax.Base()).extend({
   },
 
   setRequestHeaders: function() {
-    var requestHeaders = 
+    var requestHeaders =
       ['X-Requested-With', 'XMLHttpRequest',
        'X-Prototype-Version', Prototype.Version];
 
     if (this.options.method == 'post') {
-      requestHeaders.push('Content-type', 
+      requestHeaders.push('Content-type',
         'application/x-www-form-urlencoded');
 
       /* Force &quot;Connection: close&quot; for Mozilla browsers to work around
        * a bug where XMLHttpReqeuest sends an incorrect Content-length
-       * header. See Mozilla Bugzilla #246651. 
+       * header. See Mozilla Bugzilla #246651.
        */
       if (this.transport.overrideMimeType)
         requestHeaders.push('Connection', 'close');
@@ -263,15 +659,26 @@ Ajax.Request.prototype = (new Ajax.Base()).extend({
       this.respondToReadyState(this.transport.readyState);
   },
 
+  evalJSON: function() {
+    try {
+      var json = this.transport.getResponseHeader('X-JSON'), object;
+      object = eval(json);
+      return object;
+    } catch (e) {
+    }
+  },
+
   respondToReadyState: function(readyState) {
     var event = Ajax.Request.Events[readyState];
+    var transport = this.transport, json = this.evalJSON();
 
     if (event == 'Complete')
       (this.options['on' + this.transport.status]
-       || this.options['on' + this.responseIsSuccess() ? 'Success' : 'Failure']
-       || Prototype.emptyFunction)(this.transport);
+       || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')]
+       || Prototype.emptyFunction)(transport, json);
 
-    (this.options['on' + event] || Prototype.emptyFunction)(this.transport);
+    (this.options['on' + event] || Prototype.emptyFunction)(transport, json);
+    Ajax.Responders.dispatch('on' + event, this, transport, json);
 
     /* Avoid memory leak in MSIE: clean up the oncomplete event handler */
     if (event == 'Complete')
@@ -282,7 +689,7 @@ Ajax.Request.prototype = (new Ajax.Base()).extend({
 Ajax.Updater = Class.create();
 Ajax.Updater.ScriptFragment = '(?:&lt;script.*?&gt;)((\n|.)*?)(?:&lt;\/script&gt;)';
 
-Ajax.Updater.prototype.extend(Ajax.Request.prototype).extend({
+Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), {
   initialize: function(container, url, options) {
     this.containers = {
       success: container.success ? $(container.success) : $(container),
@@ -294,9 +701,9 @@ Ajax.Updater.prototype.extend(Ajax.Request.prototype).extend({
     this.setOptions(options);
 
     var onComplete = this.options.onComplete || Prototype.emptyFunction;
-    this.options.onComplete = (function() {
+    this.options.onComplete = (function(transport, object) {
       this.updateContent();
-      onComplete(this.transport);
+      onComplete(transport, object);
     }).bind(this);
 
     this.request(url);
@@ -320,8 +727,7 @@ Ajax.Updater.prototype.extend(Ajax.Request.prototype).extend({
 
     if (this.responseIsSuccess()) {
       if (this.onComplete)
-        setTimeout((function() {this.onComplete(
-          this.transport)}).bind(this), 10);
+        setTimeout(this.onComplete.bind(this), 10);
     }
 
     if (this.options.evalScripts &amp;&amp; scripts) {
@@ -335,13 +741,13 @@ Ajax.Updater.prototype.extend(Ajax.Request.prototype).extend({
 });
 
 Ajax.PeriodicalUpdater = Class.create();
-Ajax.PeriodicalUpdater.prototype = (new Ajax.Base()).extend({
+Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), {
   initialize: function(container, url, options) {
     this.setOptions(options);
     this.onComplete = this.options.onComplete;
 
     this.frequency = (this.options.frequency || 2);
-    this.decay = 1;
+    this.decay = (this.options.decay || 1);
 
     this.updater = {};
     this.container = container;
@@ -358,17 +764,17 @@ Ajax.PeriodicalUpdater.prototype = (new Ajax.Base()).extend({
   stop: function() {
     this.updater.onComplete = undefined;
     clearTimeout(this.timer);
-    (this.onComplete || Ajax.emptyFunction).apply(this, arguments);
+    (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
   },
 
   updateComplete: function(request) {
     if (this.options.decay) {
-      this.decay = (request.responseText == this.lastText ? 
+      this.decay = (request.responseText == this.lastText ?
         this.decay * this.options.decay : 1);
 
       this.lastText = request.responseText;
     }
-    this.timer = setTimeout(this.onTimerEvent.bind(this), 
+    this.timer = setTimeout(this.onTimerEvent.bind(this),
       this.decay * this.frequency * 1000);
   },
 
@@ -376,23 +782,13 @@ Ajax.PeriodicalUpdater.prototype = (new Ajax.Base()).extend({
     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 &lt; children.length; i++) {
-    var child = children[i];
-    var classNames = child.className.split(' ');
-    for (var j = 0; j &lt; classNames.length; j++) {
-      if (classNames[j] == className) {
-        elements.push(child);
-        break;
-      }
-    }
-  }
-  
-  return elements;
+document.getElementsByClassName = function(className, parentElement) {
+  var children = (document.body || $(parentElement)).getElementsByTagName('*');
+  return $A(children).inject([], function(elements, child) {
+    if (Element.hasClassName(child, className))
+      elements.push(child);
+    return elements;
+  });
 }
 
 /*--------------------------------------------------------------------------*/
@@ -402,11 +798,14 @@ if (!window.Element) {
 }
 
 Object.extend(Element, {
+  visible: function(element) {
+    return $(element).style.display != 'none';
+  },
+
   toggle: function() {
     for (var i = 0; i &lt; arguments.length; i++) {
       var element = $(arguments[i]);
-      element.style.display = 
-        (element.style.display == 'none' ? '' : 'none');
+      Element[Element.visible(element) ? 'hide' : 'show'](element);
     }
   },
 
@@ -428,54 +827,131 @@ Object.extend(Element, {
     element = $(element);
     element.parentNode.removeChild(element);
   },
-   
+
   getHeight: function(element) {
     element = $(element);
-    return element.offsetHeight; 
+    return element.offsetHeight;
+  },
+
+  classNames: function(element) {
+    return new Element.ClassNames(element);
   },
 
   hasClassName: function(element, className) {
-    element = $(element);
-    if (!element)
-      return;
-    var a = element.className.split(' ');
-    for (var i = 0; i &lt; a.length; i++) {
-      if (a[i] == className)
-        return true;
-    }
-    return false;
+    if (!(element = $(element))) return;
+    return Element.classNames(element).include(className);
   },
 
   addClassName: function(element, className) {
-    element = $(element);
-    Element.removeClassName(element, className);
-    element.className += ' ' + className;
+    if (!(element = $(element))) return;
+    return Element.classNames(element).add(className);
   },
 
   removeClassName: function(element, className) {
-    element = $(element);
-    if (!element)
-      return;
-    var newClassName = '';
-    var a = element.className.split(' ');
-    for (var i = 0; i &lt; a.length; i++) {
-      if (a[i] != className) {
-        if (i &gt; 0)
-          newClassName += ' ';
-        newClassName += a[i];
-      }
-    }
-    element.className = newClassName;
+    if (!(element = $(element))) return;
+    return Element.classNames(element).remove(className);
   },
-  
+
   // removes whitespace-only text node children
   cleanWhitespace: function(element) {
-    var element = $(element);
+    element = $(element);
     for (var i = 0; i &lt; element.childNodes.length; i++) {
       var node = element.childNodes[i];
-      if (node.nodeType == 3 &amp;&amp; !/\S/.test(node.nodeValue)) 
+      if (node.nodeType == 3 &amp;&amp; !/\S/.test(node.nodeValue))
         Element.remove(node);
     }
+  },
+
+  empty: function(element) {
+    return $(element).innerHTML.match(/^\s*$/);
+  },
+
+  scrollTo: function(element) {
+    element = $(element);
+    var x = element.x ? element.x : element.offsetLeft,
+        y = element.y ? element.y : element.offsetTop;
+    window.scrollTo(x, y);
+  },
+
+  getStyle: function(element, style) {
+    element = $(element);
+    var value = element.style[style.camelize()];
+    if (!value) {
+      if (document.defaultView &amp;&amp; document.defaultView.getComputedStyle) {
+        var css = document.defaultView.getComputedStyle(element, null);
+        value = css ? css.getPropertyValue(style) : null;
+      } else if (element.currentStyle) {
+        value = element.currentStyle[style.camelize()];
+      }
+    }
+
+    if (window.opera &amp;&amp; ['left', 'top', 'right', 'bottom'].include(style))
+      if (Element.getStyle(element, 'position') == 'static') value = 'auto';
+
+    return value == 'auto' ? null : value;
+  },
+
+  getDimensions: function(element) {
+    element = $(element);
+    if (Element.getStyle(element, 'display') != 'none')
+      return {width: element.offsetWidth, height: element.offsetHeight};
+
+    // All *Width and *Height properties give 0 on elements with display none,
+    // so enable the element temporarily
+    var els = element.style;
+    var originalVisibility = els.visibility;
+    var originalPosition = els.position;
+    els.visibility = 'hidden';
+    els.position = 'absolute';
+    els.display = '';
+    var originalWidth = element.clientWidth;
+    var originalHeight = element.clientHeight;
+    els.display = 'none';
+    els.position = originalPosition;
+    els.visibility = originalVisibility;
+    return {width: originalWidth, height: originalHeight};
+  },
+
+  makePositioned: function(element) {
+    element = $(element);
+    var pos = Element.getStyle(element, 'position');
+    if (pos == 'static' || !pos) {
+      element._madePositioned = true;
+      element.style.position = 'relative';
+      // Opera returns the offset relative to the positioning context, when an
+      // element is position relative but top and left have not been defined
+      if (window.opera) {
+        element.style.top = 0;
+        element.style.left = 0;
+      }
+    }
+  },
+
+  undoPositioned: function(element) {
+    element = $(element);
+    if (element._madePositioned) {
+      element._madePositioned = undefined;
+      element.style.position =
+        element.style.top =
+        element.style.left =
+        element.style.bottom =
+        element.style.right = '';
+    }
+  },
+
+  makeClipping: function(element) {
+    element = $(element);
+    if (element._overflow) return;
+    element._overflow = element.style.overflow;
+    if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden')
+      element.style.overflow = 'hidden';
+  },
+
+  undoClipping: function(element) {
+    element = $(element);
+    if (element._overflow) return;
+    element.style.overflow = element._overflow;
+    element._overflow = undefined;
   }
 });
 
@@ -492,67 +968,124 @@ Abstract.Insertion.prototype = {
   initialize: function(element, content) {
     this.element = $(element);
     this.content = content;
-    
+
     if (this.adjacency &amp;&amp; this.element.insertAdjacentHTML) {
-      this.element.insertAdjacentHTML(this.adjacency, this.content);
+      try {
+        this.element.insertAdjacentHTML(this.adjacency, this.content);
+      } catch (e) {
+        if (this.element.tagName.toLowerCase() == 'tbody') {
+          this.insertContent(this.contentFromAnonymousTable());
+        } else {
+          throw e;
+        }
+      }
     } else {
       this.range = this.element.ownerDocument.createRange();
       if (this.initializeRange) this.initializeRange();
-      this.fragment = this.range.createContextualFragment(this.content);
-      this.insertContent();
+      this.insertContent([this.range.createContextualFragment(this.content)]);
     }
+  },
+
+  contentFromAnonymousTable: function() {
+    var div = document.createElement('div');
+    div.innerHTML = '&lt;table&gt;&lt;tbody&gt;' + this.content + '&lt;/tbody&gt;&lt;/table&gt;';
+    return $A(div.childNodes[0].childNodes[0].childNodes);
   }
 }
 
 var Insertion = new Object();
 
 Insertion.Before = Class.create();
-Insertion.Before.prototype = (new Abstract.Insertion('beforeBegin')).extend({
+Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), {
   initializeRange: function() {
     this.range.setStartBefore(this.element);
   },
-  
-  insertContent: function() {
-    this.element.parentNode.insertBefore(this.fragment, this.element);
+
+  insertContent: function(fragments) {
+    fragments.each((function(fragment) {
+      this.element.parentNode.insertBefore(fragment, this.element);
+    }).bind(this));
   }
 });
 
 Insertion.Top = Class.create();
-Insertion.Top.prototype = (new Abstract.Insertion('afterBegin')).extend({
+Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), {
   initializeRange: function() {
     this.range.selectNodeContents(this.element);
     this.range.collapse(true);
   },
-  
-  insertContent: function() {  
-    this.element.insertBefore(this.fragment, this.element.firstChild);
+
+  insertContent: function(fragments) {
+    fragments.reverse().each((function(fragment) {
+      this.element.insertBefore(fragment, this.element.firstChild);
+    }).bind(this));
   }
 });
 
 Insertion.Bottom = Class.create();
-Insertion.Bottom.prototype = (new Abstract.Insertion('beforeEnd')).extend({
+Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), {
   initializeRange: function() {
     this.range.selectNodeContents(this.element);
     this.range.collapse(this.element);
   },
-  
-  insertContent: function() {
-    this.element.appendChild(this.fragment);
+
+  insertContent: function(fragments) {
+    fragments.each((function(fragment) {
+      this.element.appendChild(fragment);
+    }).bind(this));
   }
 });
 
 Insertion.After = Class.create();
-Insertion.After.prototype = (new Abstract.Insertion('afterEnd')).extend({
+Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), {
   initializeRange: function() {
     this.range.setStartAfter(this.element);
   },
-  
-  insertContent: function() {
-    this.element.parentNode.insertBefore(this.fragment, 
-      this.element.nextSibling);
+
+  insertContent: function(fragments) {
+    fragments.each((function(fragment) {
+      this.element.parentNode.insertBefore(fragment,
+        this.element.nextSibling);
+    }).bind(this));
   }
 });
 
+/*--------------------------------------------------------------------------*/
+
+Element.ClassNames = Class.create();
+Element.ClassNames.prototype = {
+  initialize: function(element) {
+    this.element = $(element);
+  },
+
+  _each: function(iterator) {
+    this.element.className.split(/\s+/).select(function(name) {
+      return name.length &gt; 0;
+    })._each(iterator);
+  },
+
+  set: function(className) {
+    this.element.className = className;
+  },
+
+  add: function(classNameToAdd) {
+    if (this.include(classNameToAdd)) return;
+    this.set(this.toArray().concat(classNameToAdd).join(' '));
+  },
+
+  remove: function(classNameToRemove) {
+    if (!this.include(classNameToRemove)) return;
+    this.set(this.select(function(className) {
+      return className != classNameToRemove;
+    }));
+  },
+
+  toString: function() {
+    return this.toArray().join(' ');
+  }
+}
+
+Object.extend(Element.ClassNames.prototype, Enumerable);
 var Field = {
   clear: function() {
     for (var i = 0; i &lt; arguments.length; i++)
@@ -562,17 +1095,17 @@ var Field = {
   focus: function(element) {
     $(element).focus();
   },
-  
+
   present: function() {
     for (var i = 0; i &lt; arguments.length; i++)
       if ($(arguments[i]).value == '') return false;
     return true;
   },
-  
+
   select: function(element) {
     $(element).select();
   },
-   
+
   activate: function(element) {
     $(element).focus();
     $(element).select();
@@ -585,16 +1118,16 @@ var Form = {
   serialize: function(form) {
     var elements = Form.getElements($(form));
     var queryComponents = new Array();
-    
+
     for (var i = 0; i &lt; elements.length; i++) {
       var queryComponent = Form.Element.serialize(elements[i]);
       if (queryComponent)
         queryComponents.push(queryComponent);
     }
-    
+
     return queryComponents.join('&amp;');
   },
-  
+
   getElements: function(form) {
     var form = $(form);
     var elements = new Array();
@@ -606,19 +1139,19 @@ var Form = {
     }
     return elements;
   },
-  
+
   getInputs: function(form, typeName, name) {
     var form = $(form);
     var inputs = form.getElementsByTagName('input');
-    
+
     if (!typeName &amp;&amp; !name)
       return inputs;
-      
+
     var matchingInputs = new Array();
     for (var i = 0; i &lt; inputs.length; i++) {
       var input = inputs[i];
       if ((typeName &amp;&amp; input.type != typeName) ||
-          (name &amp;&amp; input.name != name)) 
+          (name &amp;&amp; input.name != name))
         continue;
       matchingInputs.push(input);
     }
@@ -665,18 +1198,18 @@ Form.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]);                   
+      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) 
+
+    if (parameter)
       return parameter[1];
   }
 }
@@ -689,7 +1222,7 @@ Form.Element.Serializers = {
       case 'password':
       case 'text':
         return Form.Element.Serializers.textarea(element);
-      case 'checkbox':  
+      case 'checkbox':
       case 'radio':
         return Form.Element.Serializers.inputSelector(element);
     }
@@ -706,17 +1239,30 @@ Form.Element.Serializers = {
   },
 
   select: function(element) {
-    var value = '';
-    if (element.type == 'select-one') {
-      var index = element.selectedIndex;
-      if (index &gt;= 0)
-        value = element.options[index].value || element.options[index].text;
-    } else {
-      value = new Array();
-      for (var i = 0; i &lt; element.length; i++) {
-        var opt = element.options[i];
-        if (opt.selected)
-          value.push(opt.value || opt.text);
+    return Form.Element.Serializers[element.type == 'select-one' ?
+      'selectOne' : 'selectMany'](element);
+  },
+
+  selectOne: function(element) {
+    var value = '', opt, index = element.selectedIndex;
+    if (index &gt;= 0) {
+      opt = element.options[index];
+      value = opt.value;
+      if (!value &amp;&amp; !('value' in opt))
+        value = opt.text;
+    }
+    return [element.name, value];
+  },
+
+  selectMany: function(element) {
+    var value = new Array();
+    for (var i = 0; i &lt; element.length; i++) {
+      var opt = element.options[i];
+      if (opt.selected) {
+        var optValue = opt.value;
+        if (!optValue &amp;&amp; !('value' in opt))
+          optValue = opt.text;
+        value.push(optValue);
       }
     }
     return [element.name, value];
@@ -735,15 +1281,15 @@ Abstract.TimedObserver.prototype = {
     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) {
@@ -754,14 +1300,14 @@ Abstract.TimedObserver.prototype = {
 }
 
 Form.Element.Observer = Class.create();
-Form.Element.Observer.prototype = (new Abstract.TimedObserver()).extend({
+Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
   getValue: function() {
     return Form.Element.getValue(this.element);
   }
 });
 
 Form.Observer = Class.create();
-Form.Observer.prototype = (new Abstract.TimedObserver()).extend({
+Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
   getValue: function() {
     return Form.serialize(this.element);
   }
@@ -774,14 +1320,14 @@ 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) {
@@ -789,22 +1335,22 @@ Abstract.EventObserver.prototype = {
       this.lastValue = value;
     }
   },
-  
+
   registerFormCallbacks: function() {
     var elements = Form.getElements(this.element);
     for (var i = 0; i &lt; elements.length; i++)
       this.registerCallback(elements[i]);
   },
-  
+
   registerCallback: function(element) {
     if (element.type) {
       switch (element.type.toLowerCase()) {
-        case 'checkbox':  
+        case 'checkbox':
         case 'radio':
           element.target = this;
           element.prev_onclick = element.onclick || Prototype.emptyFunction;
           element.onclick = function() {
-            this.prev_onclick(); 
+            this.prev_onclick();
             this.target.onElementEvent();
           }
           break;
@@ -816,30 +1362,28 @@ Abstract.EventObserver.prototype = {
           element.target = this;
           element.prev_onchange = element.onchange || Prototype.emptyFunction;
           element.onchange = function() {
-            this.prev_onchange(); 
+            this.prev_onchange();
             this.target.onElementEvent();
           }
           break;
       }
-    }    
+    }
   }
 }
 
 Form.Element.EventObserver = Class.create();
-Form.Element.EventObserver.prototype = (new Abstract.EventObserver()).extend({
+Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
   getValue: function() {
     return Form.Element.getValue(this.element);
   }
 });
 
 Form.EventObserver = Class.create();
-Form.EventObserver.prototype = (new Abstract.EventObserver()).extend({
+Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
   getValue: function() {
     return Form.serialize(this.element);
   }
 });
-
-
 if (!window.Event) {
   var Event = new Object();
 }
@@ -865,21 +1409,22 @@ Object.extend(Event, {
   },
 
   pointerX: function(event) {
-    return event.pageX || (event.clientX + 
+    return event.pageX || (event.clientX +
       (document.documentElement.scrollLeft || document.body.scrollLeft));
   },
 
   pointerY: function(event) {
-    return event.pageY || (event.clientY + 
+    return event.pageY || (event.clientY +
       (document.documentElement.scrollTop || document.body.scrollTop));
   },
 
   stop: function(event) {
-    if (event.preventDefault) { 
-      event.preventDefault(); 
-      event.stopPropagation(); 
+    if (event.preventDefault) {
+      event.preventDefault();
+      event.stopPropagation();
     } else {
       event.returnValue = false;
+      event.cancelBubble = true;
     }
   },
 
@@ -894,7 +1439,7 @@ Object.extend(Event, {
   },
 
   observers: false,
-  
+
   _observeAndCache: function(element, name, observer, useCapture) {
     if (!this.observers) this.observers = [];
     if (element.addEventListener) {
@@ -905,7 +1450,7 @@ Object.extend(Event, {
       element.attachEvent('on' + name, observer);
     }
   },
-  
+
   unloadCache: function() {
     if (!Event.observers) return;
     for (var i = 0; i &lt; Event.observers.length; i++) {
@@ -918,24 +1463,24 @@ Object.extend(Event, {
   observe: function(element, name, observer, useCapture) {
     var element = $(element);
     useCapture = useCapture || false;
-    
+
     if (name == 'keypress' &amp;&amp;
-        ((navigator.appVersion.indexOf('AppleWebKit') &gt; 0) 
+        (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
         || 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' &amp;&amp;
-        ((navigator.appVersion.indexOf('AppleWebKit') &gt; 0) 
+        (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
         || element.detachEvent))
       name = 'keydown';
-    
+
     if (element.removeEventListener) {
       element.removeEventListener(name, observer, useCapture);
     } else if (element.detachEvent) {
@@ -946,24 +1491,22 @@ Object.extend(Event, {
 
 /* 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, 
+  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 
+    this.deltaX =  window.pageXOffset
+                || document.documentElement.scrollLeft
+                || document.body.scrollLeft
                 || 0;
-    this.deltaY =  window.pageYOffset 
-                || document.documentElement.scrollTop 
-                || document.body.scrollTop 
+    this.deltaY =  window.pageYOffset
+                || document.documentElement.scrollTop
+                || document.body.scrollTop
                 || 0;
   },
 
@@ -971,7 +1514,7 @@ var Position = {
     var valueT = 0, valueL = 0;
     do {
       valueT += element.scrollTop  || 0;
-      valueL += element.scrollLeft || 0; 
+      valueL += element.scrollLeft || 0;
       element = element.parentNode;
     } while (element);
     return [valueL, valueT];
@@ -987,6 +1530,31 @@ var Position = {
     return [valueL, valueT];
   },
 
+  positionedOffset: function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+      element = element.offsetParent;
+      if (element) {
+        p = Element.getStyle(element, 'position');
+        if (p == 'relative' || p == 'absolute') break;
+      }
+    } while (element);
+    return [valueL, valueT];
+  },
+
+  offsetParent: function(element) {
+    if (element.offsetParent) return element.offsetParent;
+    if (element == document.body) return element;
+
+    while ((element = element.parentNode) &amp;&amp; element != document.body)
+      if (Element.getStyle(element, 'position') != 'static')
+        return element;
+
+    return document.body;
+  },
+
   // caches x/y coordinate pair to use with overlap
   within: function(element, x, y) {
     if (this.includeScrollOffsets)
@@ -997,7 +1565,7 @@ var Position = {
 
     return (y &gt;= this.offset[1] &amp;&amp;
             y &lt;  this.offset[1] + element.offsetHeight &amp;&amp;
-            x &gt;= this.offset[0] &amp;&amp; 
+            x &gt;= this.offset[0] &amp;&amp;
             x &lt;  this.offset[0] + element.offsetWidth);
   },
 
@@ -1010,18 +1578,18 @@ var Position = {
 
     return (this.ycomp &gt;= this.offset[1] &amp;&amp;
             this.ycomp &lt;  this.offset[1] + element.offsetHeight &amp;&amp;
-            this.xcomp &gt;= this.offset[0] &amp;&amp; 
+            this.xcomp &gt;= this.offset[0] &amp;&amp;
             this.xcomp &lt;  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) / 
+  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) / 
+      return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
         element.offsetWidth;
   },
 
@@ -1034,5 +1602,123 @@ var Position = {
     target.style.left   = offsets[0] + 'px';
     target.style.width  = source.offsetWidth + 'px';
     target.style.height = source.offsetHeight + 'px';
+  },
+
+  page: function(forElement) {
+    var valueT = 0, valueL = 0;
+
+    var element = forElement;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+
+      // Safari fix
+      if (element.offsetParent==document.body)
+        if (Element.getStyle(element,'position')=='absolute') break;
+
+    } while (element = element.offsetParent);
+
+    element = forElement;
+    do {
+      valueT -= element.scrollTop  || 0;
+      valueL -= element.scrollLeft || 0;
+    } while (element = element.parentNode);
+
+    return [valueL, valueT];
+  },
+
+  clone: function(source, target) {
+    var options = Object.extend({
+      setLeft:    true,
+      setTop:     true,
+      setWidth:   true,
+      setHeight:  true,
+      offsetTop:  0,
+      offsetLeft: 0
+    }, arguments[2] || {})
+
+    // find page position of source
+    source = $(source);
+    var p = Position.page(source);
+
+    // find coordinate system to use
+    target = $(target);
+    var delta = [0, 0];
+    var parent = null;
+    // delta [0,0] will do fine with position: fixed elements,
+    // position:absolute needs offsetParent deltas
+    if (Element.getStyle(target,'position') == 'absolute') {
+      parent = Position.offsetParent(target);
+      delta = Position.page(parent);
+    }
+
+    // correct by body offsets (fixes Safari)
+    if (parent == document.body) {
+      delta[0] -= document.body.offsetLeft;
+      delta[1] -= document.body.offsetTop;
+    }
+
+    // set position
+    if(options.setLeft)   target.style.left  = (p[0] - delta[0] + options.offsetLeft) + 'px';
+    if(options.setTop)    target.style.top   = (p[1] - delta[1] + options.offsetTop) + 'px';
+    if(options.setWidth)  target.style.width = source.offsetWidth + 'px';
+    if(options.setHeight) target.style.height = source.offsetHeight + 'px';
+  },
+
+  absolutize: function(element) {
+    element = $(element);
+    if (element.style.position == 'absolute') return;
+    Position.prepare();
+
+    var offsets = Position.positionedOffset(element);
+    var top     = offsets[1];
+    var left    = offsets[0];
+    var width   = element.clientWidth;
+    var height  = element.clientHeight;
+
+    element._originalLeft   = left - parseFloat(element.style.left  || 0);
+    element._originalTop    = top  - parseFloat(element.style.top || 0);
+    element._originalWidth  = element.style.width;
+    element._originalHeight = element.style.height;
+
+    element.style.position = 'absolute';
+    element.style.top    = top + 'px';;
+    element.style.left   = left + 'px';;
+    element.style.width  = width + 'px';;
+    element.style.height = height + 'px';;
+  },
+
+  relativize: function(element) {
+    element = $(element);
+    if (element.style.position == 'relative') return;
+    Position.prepare();
+
+    element.style.position = 'relative';
+    var top  = parseFloat(element.style.top  || 0) - (element._originalTop || 0);
+    var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);
+
+    element.style.top    = top + 'px';
+    element.style.left   = left + 'px';
+    element.style.height = element._originalHeight;
+    element.style.width  = element._originalWidth;
   }
 }
+
+// Safari returns margins on body which is incorrect if the child is absolutely
+// positioned.  For performance reasons, redefine Position.cumulativeOffset for
+// KHTML/WebKit only.
+if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) {
+  Position.cumulativeOffset = function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+      if (element.offsetParent == document.body)
+        if (Element.getStyle(element, 'position') == 'absolute') break;
+
+      element = element.offsetParent;
+    } while (element);
+
+    return [valueL, valueT];
+  }
+}
\ No newline at end of file</diff>
      <filename>public/javascripts/prototype.js</filename>
    </modified>
    <modified>
      <diff>@@ -1,4 +1,3 @@
 #!/usr/bin/env ruby
-require 'rubygems'
-require_gem 'rails'
-require 'breakpoint_client'
+require File.dirname(__FILE__) + '/../config/boot'
+require 'commands/breakpointer'</diff>
      <filename>script/breakpointer</filename>
    </modified>
    <modified>
      <diff>@@ -1,23 +1,3 @@
 #!/usr/bin/env ruby
-irb = RUBY_PLATFORM =~ /mswin32/ ? 'irb.bat' : 'irb'
-
-require 'optparse'
-options = { :sandbox =&gt; false, :irb =&gt; irb }
-OptionParser.new do |opt|
-  opt.on('-s', '--sandbox', 'Rollback database modifications on exit.') { |options[:sandbox]| }
-  opt.on(&quot;--irb=[#{irb}]&quot;, 'Invoke a different irb.') { |options[:irb]| }
-  opt.parse!(ARGV)
-end
-
-libs =  &quot; -r irb/completion&quot;
-libs &lt;&lt; &quot; -r #{File.dirname(__FILE__)}/../config/environment&quot;
-libs &lt;&lt; &quot; -r console_sandbox&quot; if options[:sandbox]
-
-ENV['RAILS_ENV'] = ARGV.first || 'development'
-if options[:sandbox]
-  puts &quot;Loading #{ENV['RAILS_ENV']} environment in sandbox.&quot;
-  puts &quot;Any modifications you make will be rolled back on exit.&quot;
-else
-  puts &quot;Loading #{ENV['RAILS_ENV']} environment.&quot;
-end
-exec &quot;#{options[:irb]} #{libs} --prompt-mode simple&quot;
+require File.dirname(__FILE__) + '/../config/boot'
+require 'commands/console'</diff>
      <filename>script/console</filename>
    </modified>
    <modified>
      <diff>@@ -1,7 +1,3 @@
 #!/usr/bin/env ruby
-require File.dirname(__FILE__) + '/../config/environment'
-require 'rails_generator'
-require 'rails_generator/scripts/destroy'
-
-ARGV.shift if ['--help', '-h'].include?(ARGV[0])
-Rails::Generator::Scripts::Destroy.new.run(ARGV)
+require File.dirname(__FILE__) + '/../config/boot'
+require 'commands/destroy'</diff>
      <filename>script/destroy</filename>
    </modified>
    <modified>
      <diff>@@ -1,7 +1,3 @@
 #!/usr/bin/env ruby
-require File.dirname(__FILE__) + '/../config/environment'
-require 'rails_generator'
-require 'rails_generator/scripts/generate'
-
-ARGV.shift if ['--help', '-h'].include?(ARGV[0])
-Rails::Generator::Scripts::Generate.new.run(ARGV)
+require File.dirname(__FILE__) + '/../config/boot'
+require 'commands/generate'</diff>
      <filename>script/generate</filename>
    </modified>
    <modified>
      <diff>@@ -1,131 +1,3 @@
-#!/usr/local/bin/ruby
-
-require 'optparse'
-require 'net/http'
-require 'uri'
-
-def nudge(url, iterations)
-  print &quot;Nudging #{url}: &quot;
-  iterations.times {  Net::HTTP.get_response(URI.parse(url)); print &quot;.&quot;; STDOUT.flush }
-  puts
-end
-
-if RUBY_PLATFORM =~ /mswin32/ then abort(&quot;Reaper is only for Unix&quot;) end
-
-class ProgramProcess
-  class &lt;&lt; self
-    def process_keywords(action, *keywords)
-      processes = keywords.collect { |keyword| find_by_keyword(keyword) }.flatten
-
-      if processes.empty?
-        puts &quot;Couldn't find any process matching: #{keywords.join(&quot; or &quot;)}&quot;
-      else
-        processes.each do |process|
-          puts &quot;#{action.capitalize}ing #{process}&quot;
-          process.send(action)
-        end
-      end      
-    end
-
-    def find_by_keyword(keyword)
-      process_lines_with_keyword(keyword).split(&quot;\n&quot;).collect { |line|
-        next if line.include?(&quot;inq&quot;) || line.include?(&quot;ps ax&quot;) || line.include?(&quot;grep&quot;)
-        pid, *command = line.split
-        new(pid, command.join(&quot; &quot;))
-      }.compact
-    end
-
-    private
-      def process_lines_with_keyword(keyword)
-        `ps ax -o 'pid command' | grep #{keyword}`
-      end
-  end
-
-  def initialize(pid, command)
-    @pid, @command = pid, command
-  end
-
-  def find
-  end
-
-  def reload
-    `kill -s HUP #{@pid}`
-  end
-
-  def graceful
-    `kill -s TERM #{@pid}`
-  end
-
-  def kill
-    `kill -9 #{@pid}`
-  end
-
-  def usr1
-    `kill -s USR1 #{@pid}`
-  end
-
-  def to_s
-    &quot;[#{@pid}] #{@command}&quot;
-  end
-end
-
-OPTIONS = {
-  :action      =&gt; &quot;graceful&quot;,
-  :dispatcher  =&gt; File.expand_path(File.dirname(__FILE__) + '/../../public/dispatch.fcgi'),
-  :spinner     =&gt; File.expand_path(File.dirname(__FILE__) + '/spinner'),
-  :toggle_spin =&gt; true,
-  :iterations  =&gt; 10,
-  :nudge       =&gt; false
-}
-
-ARGV.options do |opts|
-  opts.banner = &quot;Usage: reaper [options]&quot;
-
-  opts.separator &quot;&quot;
-
-  opts.on &lt;&lt;-EOF
-  Description:
-    The reaper is used to reload, gracefully exit, and forcefully exit FCGI processes
-    running a Rails Dispatcher. This is commonly done when a new version of the application
-    is available, so the existing processes can be updated to use the latest code.
-
-    The reaper actions are:
-
-    * reload  : Only reloads the application, but not the framework (like the development environment)
-    * graceful: Marks all of the processes for exit after the next request
-    * kill    : Forcefully exists all processes regardless of whether they're currently serving a request
-
-    Graceful exist is the most common and default action. But since the processes won't exist until after
-    their next request, it's often necessary to ensure that such a request occurs right after they've been
-    marked. That's what nudging is for. 
-
-    A nudge is simply a request to a URL where the dispatcher is serving. You should perform one nudge per
-    FCGI process you have running if they're setup in a round-robin. Be sure to do one nudge per FCGI process
-    across all your servers. So three servers with 10 processes each should nudge 30 times to be sure all processes
-    are restarted.
-
-  Examples:
-    reaper -a reload
-    reaper -n http://www.example.com -i 10 # gracefully exit, nudge 10 times
-  EOF
-
-  opts.on(&quot;  Options:&quot;)
-
-  opts.on(&quot;-a&quot;, &quot;--action=name&quot;, &quot;reload|graceful|kill (default: #{OPTIONS[:action]})&quot;, String)  { |OPTIONS[:action]| }
-  opts.on(&quot;-d&quot;, &quot;--dispatcher=path&quot;, &quot;default: #{OPTIONS[:dispatcher]}&quot;, String)                 { |OPTIONS[:dispatcher]| }
-  opts.on(&quot;-s&quot;, &quot;--spinner=path&quot;, &quot;default: #{OPTIONS[:spinner]}&quot;, String)                       { |OPTIONS[:spinner]| }
-  opts.on(&quot;-t&quot;, &quot;--toggle-spin&quot;, &quot;Whether to send a USR1 to the spinner before and after the reaping (default: true)&quot;) { |OPTIONS[:toggle_spin]| }
-  opts.on(&quot;-n&quot;, &quot;--nudge=url&quot;, &quot;Should point to URL that's handled by the FCGI process&quot;, String) { |OPTIONS[:nudge]| }
-  opts.on(&quot;-i&quot;, &quot;--iterations=number&quot;, &quot;One nudge per FCGI process running (default: #{OPTIONS[:iterations]})&quot;, Integer) { |OPTIONS[:iterations]| }
-
-  opts.separator &quot;&quot;
-
-  opts.on(&quot;-h&quot;, &quot;--help&quot;, &quot;Show this help message.&quot;) { puts opts; exit }
-
-  opts.parse!
-end
-
-ProgramProcess.process_keywords(&quot;usr1&quot;, OPTIONS[:spinner]) if OPTIONS[:toggle_spin]
-ProgramProcess.process_keywords(OPTIONS[:action], OPTIONS[:dispatcher])
-nudge(OPTIONS[:nudge], OPTIONS[:iterations]) if OPTIONS[:nudge]
-ProgramProcess.process_keywords(&quot;usr1&quot;, OPTIONS[:spinner]) if OPTIONS[:toggle_spin]
\ No newline at end of file
+#!/usr/bin/env ruby
+require File.dirname(__FILE__) + '/../../config/boot'
+require 'commands/process/reaper'</diff>
      <filename>script/process/reaper</filename>
    </modified>
    <modified>
      <diff>@@ -1,54 +1,3 @@
-#!/usr/local/bin/ruby
-
-require 'optparse'
-
-def spawn(port)
-  print &quot;Starting FCGI on port: #{port}\n  &quot;
-  system(&quot;#{OPTIONS[:spawner]} -f #{OPTIONS[:dispatcher]} -p #{port}&quot;)
-end
-
-OPTIONS = {
-  :environment =&gt; &quot;production&quot;,
-  :spawner     =&gt; '/usr/bin/env spawn-fcgi',
-  :dispatcher  =&gt; File.expand_path(File.dirname(__FILE__) + '/../../public/dispatch.fcgi'),
-  :port        =&gt; 8000,
-  :instances   =&gt; 3
-}
-
-ARGV.options do |opts|
-  opts.banner = &quot;Usage: spawner [options]&quot;
-
-  opts.separator &quot;&quot;
-
-  opts.on &lt;&lt;-EOF
-  Description:
-    The spawner is a wrapper for spawn-fcgi that makes it easier to start multiple FCGI
-    processes running the Rails dispatcher. The spawn-fcgi command is included with the lighttpd 
-    web server, but can be used with both Apache and lighttpd (and any other web server supporting
-    externally managed FCGI processes).
-
-    You decide a starting port (default is 8000) and the number of FCGI process instances you'd 
-    like to run. So if you pick 9100 and 3 instances, you'll start processes on 9100, 9101, and 9102.
-
-  Examples:
-    spawner               # starts instances on 8000, 8001, and 8002
-    spawner -p 9100 -i 10 # starts 10 instances counting from 9100 to 9109
-  EOF
-
-  opts.on(&quot;  Options:&quot;)
-
-  opts.on(&quot;-p&quot;, &quot;--port=number&quot;, Integer, &quot;Starting port number (default: #{OPTIONS[:port]})&quot;)                   { |OPTIONS[:port]| }
-  opts.on(&quot;-i&quot;, &quot;--instances=number&quot;, Integer, &quot;Number of instances (default: #{OPTIONS[:instances]})&quot;)          { |OPTIONS[:instances]| }
-  opts.on(&quot;-e&quot;, &quot;--environment=name&quot;, String, &quot;test|development|production (default: #{OPTIONS[:environment]})&quot;) { |OPTIONS[:environment]| }
-  opts.on(&quot;-s&quot;, &quot;--spawner=path&quot;,    String, &quot;default: #{OPTIONS[:spawner]}&quot;)                                    { |OPTIONS[:spawner]| }
-  opts.on(&quot;-d&quot;, &quot;--dispatcher=path&quot;, String, &quot;default: #{OPTIONS[:dispatcher]}&quot;) { |dispatcher| OPTIONS[:dispatcher] = File.expand_path(dispatcher) }
-
-  opts.separator &quot;&quot;
-
-  opts.on(&quot;-h&quot;, &quot;--help&quot;, &quot;Show this help message.&quot;) { puts opts; exit }
-
-  opts.parse!
-end
-
-ENV[&quot;RAILS_ENV&quot;] = OPTIONS[:environment]
-OPTIONS[:instances].times { |i| spawn(OPTIONS[:port] + i) }
\ No newline at end of file
+#!/usr/bin/env ruby
+require File.dirname(__FILE__) + '/../../config/boot'
+require 'commands/process/spawner'</diff>
      <filename>script/process/spawner</filename>
    </modified>
    <modified>
      <diff>@@ -1,67 +1,3 @@
-#!/usr/local/bin/ruby
-
-require 'optparse'
-
-def daemonize
-  exit if fork                   # Parent exits, child continues.
-  Process.setsid                 # Become session leader.
-  exit if fork                   # Zap session leader. See [1].
-  Dir.chdir &quot;/&quot;                  # Release old working directory.
-  File.umask 0000                # Ensure sensible umask. Adjust as needed.
-  STDIN.reopen &quot;/dev/null&quot;       # Free file descriptors and
-  STDOUT.reopen &quot;/dev/null&quot;, &quot;a&quot; # point them somewhere sensible.
-  STDERR.reopen STDOUT           # STDOUT/ERR should better go to a logfile.
-end
-
-OPTIONS = {
-  :high_interval =&gt; 5.0,
-  :low_interval  =&gt; 0.5,
-  :command  =&gt; File.expand_path(File.dirname(__FILE__) + '/spawner'),
-  :daemon   =&gt; false
-}
-
-ARGV.options do |opts|
-  opts.banner = &quot;Usage: spinner [options]&quot;
-
-  opts.separator &quot;&quot;
-
-  opts.on &lt;&lt;-EOF
-  Description:
-    The spinner is a protection loop for the spawner, which will attempt to restart any FCGI processes
-    that might have been restarted or outright crashed. It's a brute-force attempt that'll just try
-    to run the spawner every X number of seconds, so it does pose a light load on the server.
-
-  Examples:
-    spinner # attempts to run the spawner with default settings every second with output on the terminal
-    spinner -i 3 -d # only run the spawner every 3 seconds and detach from the terminal to become a daemon
-    spinner -c '/path/to/app/script/process/spawner -p 9000 -i 10' -d # using custom spawner
-  EOF
-
-  opts.on(&quot;  Options:&quot;)
-
-  opts.on(&quot;-c&quot;, &quot;--command=path&quot;,    String)      { |OPTIONS[:command]| }
-  opts.on(&quot;-h&quot;, &quot;--high-interval=seconds&quot;, Float) { |OPTIONS[:high_interval]| }
-  opts.on(&quot;-l&quot;, &quot;--low-interval=seconds&quot;, Float)  { |OPTIONS[:low_interval]| }
-  opts.on(&quot;-d&quot;, &quot;--daemon&quot;)                       { |OPTIONS[:daemon]| }
-
-  opts.separator &quot;&quot;
-
-  opts.on(&quot;-h&quot;, &quot;--help&quot;, &quot;Show this help message.&quot;) { puts opts; exit }
-
-  opts.parse!
-end
-
-daemonize if OPTIONS[:daemon]
-
-trap(OPTIONS[:daemon] ? &quot;TERM&quot; : &quot;INT&quot;) { exit }
-trap(&quot;USR1&quot;) do
-  $interval = ($interval == OPTIONS[:high_interval] ? OPTIONS[:low_interval] : OPTIONS[:high_interval])
-  puts &quot;New interval: #{$interval}&quot;
-end
-
-$interval = OPTIONS[:high_interval]
-
-loop do
-  system(OPTIONS[:command])
-  sleep($interval)
-end
\ No newline at end of file
+#!/usr/bin/env ruby
+require File.dirname(__FILE__) + '/../../config/boot'
+require 'commands/process/spinner'</diff>
      <filename>script/process/spinner</filename>
    </modified>
    <modified>
      <diff>@@ -1,29 +1,3 @@
 #!/usr/bin/env ruby
-require 'optparse'
-
-options = { :environment =&gt; &quot;development&quot; }
-
-ARGV.options do |opts|
-  script_name = File.basename($0)
-  opts.banner = &quot;Usage: runner 'puts Person.find(1).name' [options]&quot;
-
-  opts.separator &quot;&quot;
-
-  opts.on(&quot;-e&quot;, &quot;--environment=name&quot;, String,
-          &quot;Specifies the environment for the runner to operate under (test/development/production).&quot;,
-          &quot;Default: development&quot;) { |options[:environment]| }
-
-  opts.separator &quot;&quot;
-
-  opts.on(&quot;-h&quot;, &quot;--help&quot;,
-          &quot;Show this help message.&quot;) { puts opts; exit }
-
-  opts.parse!
-end
-
-ENV[&quot;RAILS_ENV&quot;] = options[:environment]
-
-#!/usr/bin/env ruby
-
-require File.dirname(__FILE__) + '/../config/environment'
-eval(ARGV.first)
\ No newline at end of file
+require File.dirname(__FILE__) + '/../config/boot'
+require 'commands/runner'
\ No newline at end of file</diff>
      <filename>script/runner</filename>
    </modified>
    <modified>
      <diff>@@ -1,49 +1,3 @@
 #!/usr/bin/env ruby
-
-require 'webrick'
-require 'optparse'
-
-OPTIONS = {
-  :port        =&gt; 3000,
-  :ip          =&gt; &quot;0.0.0.0&quot;,
-  :environment =&gt; &quot;development&quot;,
-  :server_root =&gt; File.expand_path(File.dirname(__FILE__) + &quot;/../public/&quot;),
-  :server_type =&gt; WEBrick::SimpleServer
-}
-
-ARGV.options do |opts|
-  script_name = File.basename($0)
-  opts.banner = &quot;Usage: ruby #{script_name} [options]&quot;
-
-  opts.separator &quot;&quot;
-
-  opts.on(&quot;-p&quot;, &quot;--port=port&quot;, Integer,
-          &quot;Runs Rails on the specified port.&quot;,
-          &quot;Default: 3000&quot;) { |OPTIONS[:port]| }
-  opts.on(&quot;-b&quot;, &quot;--binding=ip&quot;, String,
-          &quot;Binds Rails to the specified ip.&quot;,
-          &quot;Default: 0.0.0.0&quot;) { |OPTIONS[:ip]| }
-  opts.on(&quot;-e&quot;, &quot;--environment=name&quot;, String,
-          &quot;Specifies the environment to run this server under (test/development/production).&quot;,
-          &quot;Default: development&quot;) { |OPTIONS[:environment]| }
-  opts.on(&quot;-d&quot;, &quot;--daemon&quot;,
-          &quot;Make Rails run as a Daemon (only works if fork is available -- meaning on *nix).&quot;
-          ) { OPTIONS[:server_type] = WEBrick::Daemon }
-
-  opts.separator &quot;&quot;
-
-  opts.on(&quot;-h&quot;, &quot;--help&quot;,
-          &quot;Show this help message.&quot;) { puts opts; exit }
-
-  opts.parse!
-end
-
-ENV[&quot;RAILS_ENV&quot;] = OPTIONS[:environment]
-require File.dirname(__FILE__) + &quot;/../config/environment&quot;
-require 'webrick_server'
-
-OPTIONS['working_directory'] = File.expand_path(RAILS_ROOT)
-
-puts &quot;=&gt; Rails application started on http://#{OPTIONS[:ip]}:#{OPTIONS[:port]}&quot;
-puts &quot;=&gt; Ctrl-C to shutdown server; call with --help for options&quot; if OPTIONS[:server_type] == WEBrick::SimpleServer
-DispatchServlet.dispatch(OPTIONS)
+require File.dirname(__FILE__) + '/../config/boot'
+require 'commands/server'
\ No newline at end of file</diff>
      <filename>script/server</filename>
    </modified>
    <modified>
      <diff>@@ -23,7 +23,7 @@ class AccountsControllerTest &lt; Test::Unit::TestCase
     post :login, :user_login =&gt; &quot;bob&quot;, :user_password =&gt; &quot;test&quot;
     assert_session_has :user
 
-    assert_equal @bob, @response.session[:user]
+    assert_equal users(:bob), @response.session[:user]
     
     assert_redirect_url &quot;http://localhost/bogus/location&quot;
   end</diff>
      <filename>test/functional/accounts_controller_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -11,7 +11,7 @@ class Admin::ArticlePreviewTest &lt; Test::Unit::TestCase
     @controller = Admin::ContentController.new
     @request = ActionController::TestRequest.new
     @response = ActionController::TestResponse.new
-    @request.session = {:user =&gt; @tobi}
+    @request.session = {:user =&gt; users(:tobi)}
 
     @art_count = Article.find(:all).size
   end</diff>
      <filename>test/functional/admin/article_preview_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -12,7 +12,7 @@ class Admin::BlacklistControllerTest &lt; Test::Unit::TestCase
     @request    = ActionController::TestRequest.new
     @response   = ActionController::TestResponse.new
 
-    @request.session = { :user =&gt; @tobi }
+    @request.session = { :user =&gt; users(:tobi) }
   end
 
   def test_index</diff>
      <filename>test/functional/admin/blacklist_controller_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -12,7 +12,7 @@ class Admin::CategoriesControllerTest &lt; Test::Unit::TestCase
     @request    = ActionController::TestRequest.new
     @response   = ActionController::TestResponse.new
 
-    @request.session = { :user =&gt; @tobi }
+    @request.session = { :user =&gt; users(:tobi) }
   end
 
   def test_index
@@ -71,18 +71,18 @@ class Admin::CategoriesControllerTest &lt; Test::Unit::TestCase
   end
 
   def test_order
-    assert_equal @software, Category.find(:first, :order =&gt; :position)
-    get :order, :category_list =&gt; [@personal.id, @hardware.id, @software.id]
+    assert_equal categories(:software), Category.find(:first, :order =&gt; :position)
+    get :order, :category_list =&gt; [categories(:personal).id, categories(:hardware).id, categories(:software).id]
     assert_response :success
-    assert_equal @personal, Category.find(:first, :order =&gt; :position)
+    assert_equal categories(:personal), Category.find(:first, :order =&gt; :position)
   end
   
   def test_asort
-    assert_equal @software, Category.find(:first, :order =&gt; :position)
+    assert_equal categories(:software), Category.find(:first, :order =&gt; :position)
     get :asort
     assert_response :success
     assert_template &quot;_categories&quot;
-    assert_equal @hardware, Category.find(:first, :order =&gt; :position)
+    assert_equal categories(:hardware), Category.find(:first, :order =&gt; :position)
   end
   
   def test_category_container</diff>
      <filename>test/functional/admin/categories_controller_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -13,7 +13,7 @@ class Admin::CommentsControllerTest &lt; Test::Unit::TestCase
     @request    = ActionController::TestRequest.new
     @response   = ActionController::TestResponse.new
     
-    @request.session = { :user =&gt; @tobi }
+    @request.session = { :user =&gt; users(:tobi) }
   end
 
   def test_index</diff>
      <filename>test/functional/admin/comments_controller_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -13,7 +13,7 @@ class Admin::ContentControllerTest &lt; Test::Unit::TestCase
     @controller = Admin::ContentController.new
     @request    = ActionController::TestRequest.new
     @response   = ActionController::TestResponse.new
-    @request.session = { :user =&gt; @tobi }
+    @request.session = { :user =&gt; users(:tobi) }
   end
 
   def test_index
@@ -52,7 +52,7 @@ class Admin::ContentControllerTest &lt; Test::Unit::TestCase
     assert_equal num_articles + 1, Article.find_all.size
 
     new_article = Article.find(:first, :order =&gt; &quot;id DESC&quot;)
-    assert_equal @tobi, new_article.user
+    assert_equal users(:tobi), new_article.user
     assert_equal 1, new_article.categories.size
     assert_equal [1], new_article.categories.collect {|c| c.id}
   end
@@ -165,7 +165,7 @@ class Admin::ContentControllerTest &lt; Test::Unit::TestCase
   end
 
   def test_resource_container
-    get :show, :id =&gt; @article1.id # article without attachments 
+    get :show, :id =&gt; contents(:article1).id # article without attachments 
     Resource.find(:all).each do |resource|
       assert_tag( :tag =&gt; 'a',
                   :attributes =&gt;{</diff>
      <filename>test/functional/admin/content_controller_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -11,7 +11,7 @@ class Admin::GeneralControllerTest &lt; Test::Unit::TestCase
     @controller = Admin::GeneralController.new
     @request    = ActionController::TestRequest.new
     @response   = ActionController::TestResponse.new
-    @request.session = { :user =&gt; @tobi }
+    @request.session = { :user =&gt; users(:tobi) }
   end
 
   def test_index</diff>
      <filename>test/functional/admin/general_controller_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -11,7 +11,7 @@ class Admin::PagesControllerTest &lt; Test::Unit::TestCase
     @controller = Admin::PagesController.new
     @request    = ActionController::TestRequest.new
     @response   = ActionController::TestResponse.new
-    @request.session = { :user =&gt; @tobi }
+    @request.session = { :user =&gt; users(:tobi) }
   end
 
   def test_index
@@ -33,12 +33,12 @@ class Admin::PagesControllerTest &lt; Test::Unit::TestCase
   end
 
   def test_show
-    get :show, :id =&gt; @first_page.id
+    get :show, :id =&gt; contents(:first_page).id
     assert_response :success
     assert_template &quot;show&quot;
     
     assert_not_nil assigns(:page)
-    assert_equal @first_page, assigns(:page)
+    assert_equal contents(:first_page), assigns(:page)
   end
 
   def test_new
@@ -47,7 +47,7 @@ class Admin::PagesControllerTest &lt; Test::Unit::TestCase
     assert_template &quot;new&quot;
     assert_not_nil assigns(:page)
     
-    assert_equal @tobi, assigns(:page).user
+    assert_equal users(:tobi), assigns(:page).user
     assert_equal TextFilter.find_by_name(config[:text_filter]), assigns(:page).text_filter
 
     post :new, :page =&gt; { :name =&gt; &quot;new_page&quot;, :title =&gt; &quot;New Page Title&quot;,
@@ -66,32 +66,32 @@ class Admin::PagesControllerTest &lt; Test::Unit::TestCase
   end
   
   def test_edit
-    get :edit, :id =&gt; @markdown_page.id
+    get :edit, :id =&gt; contents(:markdown_page).id
     assert_response :success
     assert_template &quot;edit&quot;
     assert_not_nil assigns(:page)
     
-    assert_equal @markdown_page, assigns(:page)
+    assert_equal contents(:markdown_page), assigns(:page)
 
-    post :edit, :id =&gt; @markdown_page.id, :page =&gt; { :name =&gt; &quot;markdown-page&quot;, :title =&gt; &quot;Markdown Page&quot;,
+    post :edit, :id =&gt; contents(:markdown_page).id, :page =&gt; { :name =&gt; &quot;markdown-page&quot;, :title =&gt; &quot;Markdown Page&quot;,
         :body =&gt; &quot;Adding a [link](http://typo.leetsoft.com/) here&quot; }
 
     
-    assert_equal &quot;&quot;, @markdown_page.reload.body_html.to_s
+    assert_equal &quot;&quot;, contents(:markdown_page).reload.body_html.to_s
 
-    assert_redirected_to :action =&gt; &quot;show&quot;, :id =&gt; @markdown_page.id
+    assert_redirected_to :action =&gt; &quot;show&quot;, :id =&gt; contents(:markdown_page).id
     assert_equal &quot;Page was successfully updated.&quot;, flash[:notice]
   end
 
   def test_destroy
-    post :destroy, :id =&gt; @another_page.id
+    post :destroy, :id =&gt; contents(:another_page).id
     assert_redirected_to :action =&gt; &quot;list&quot;
-    assert_raise(ActiveRecord::RecordNotFound) { Page.find(@another_page.id) }
+    assert_raise(ActiveRecord::RecordNotFound) { Page.find(contents(:another_page).id) }
   end
 
   def test_preview
     get :preview, :page =&gt; { :name =&gt; &quot;preview-page&quot;, :title =&gt; &quot;Preview Page&quot;,
-      :text_filter_id =&gt; @markdown_filter.id, :body =&gt; &quot;testing the *preview*&quot; }
+      :text_filter_id =&gt; text_filters(:markdown_filter).id, :body =&gt; &quot;testing the *preview*&quot; }
     assert_response :success
     assert_not_nil assigns(:page)
     assert_template &quot;preview&quot;</diff>
      <filename>test/functional/admin/pages_controller_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -11,7 +11,7 @@ class Admin::ResourcesControllerTest &lt; Test::Unit::TestCase
     @controller = Admin::ResourcesController.new
     @request    = ActionController::TestRequest.new
     @response   = ActionController::TestResponse.new
-    @request.session = { :user =&gt; @tobi }
+    @request.session = { :user =&gt; users(:tobi) }
   end
 
   def test_list</diff>
      <filename>test/functional/admin/resources_controller_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -12,7 +12,7 @@ class Admin::ThemesControllerTest &lt; Test::Unit::TestCase
     @request    = ActionController::TestRequest.new
     @response   = ActionController::TestResponse.new
     
-    @request.session = { :user =&gt; @tobi }
+    @request.session = { :user =&gt; users(:tobi) }
   end
 
   # Replace this with your real tests.</diff>
      <filename>test/functional/admin/themes_controller_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -12,7 +12,7 @@ class Admin::TrackbacksControllerTest &lt; Test::Unit::TestCase
     @request    = ActionController::TestRequest.new
     @response   = ActionController::TestResponse.new
     
-    @request.session = { :user =&gt; @tobi }
+    @request.session = { :user =&gt; users(:tobi) }
   end
 
   def test_index</diff>
      <filename>test/functional/admin/trackbacks_controller_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -12,7 +12,7 @@ class Admin::UsersControllerTest &lt; Test::Unit::TestCase
     @request    = ActionController::TestRequest.new
     @response   = ActionController::TestResponse.new
     
-    @request.session = { :user =&gt; @tobi }    
+    @request.session = { :user =&gt; users(:tobi) }    
   end
 
   def test_index</diff>
      <filename>test/functional/admin/users_controller_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -42,7 +42,7 @@ class ArticlesControllerTest &lt; Test::Unit::TestCase
   end
 
   def test_simple_tag_pagination
-    @limit_article_display.update_attribute(:value, 1)
+    settings(:limit_article_display).update_attribute(:value, 1)
     get :tag, :id =&gt; &quot;foo&quot;
     assert_equal 1, assigns(:articles).size
     assert_tag(:tag =&gt; 'p',
@@ -75,7 +75,7 @@ class ArticlesControllerTest &lt; Test::Unit::TestCase
     assert_response :success
     assert_template &quot;read&quot;
     assert_not_nil assigns(:article)
-    assert_equal @article3, assigns(:article)
+    assert_equal contents(:article3), assigns(:article)
   end
 
   # Posts for given day
@@ -160,13 +160,13 @@ class ArticlesControllerTest &lt; Test::Unit::TestCase
   end
   
   def test_comment_user_set
-    @request.session = { :user =&gt; @tobi }
+    @request.session = { :user =&gt; users(:tobi) }
     post :comment, { :id =&gt; 2, :comment =&gt; {'body' =&gt; 'foo', 'author' =&gt; 'bob' }}
     assert_response :success
 
     comment = Comment.find(:first, :order =&gt; 'created_at desc')
     assert comment
-    assert_equal @tobi, comment.user
+    assert_equal users(:tobi), comment.user
 
     get :read, {:id =&gt; 2}
     assert_response :success
@@ -286,13 +286,13 @@ class ArticlesControllerTest &lt; Test::Unit::TestCase
   end
 
   def test_read_article_with_comments_and_trackbacks
-    get :read, :id =&gt; @article1.id
+    get :read, :id =&gt; contents(:article1).id
     assert_response :success
     assert_template &quot;read&quot;
     
     assert_tag :tag =&gt; &quot;ol&quot;,
       :attributes =&gt; { :id =&gt; &quot;commentList&quot;},
-      :children =&gt; { :count =&gt; @article1.comments.size,
+      :children =&gt; { :count =&gt; contents(:article1).comments.size,
         :only =&gt; { :tag =&gt; &quot;li&quot; } }
         
     assert_tag :tag =&gt; &quot;li&quot;,
@@ -300,12 +300,12 @@ class ArticlesControllerTest &lt; Test::Unit::TestCase
 
     assert_tag :tag =&gt; &quot;ol&quot;,
       :attributes =&gt; { :id =&gt; &quot;trackbackList&quot; },
-      :children =&gt; { :count =&gt; @article1.trackbacks.size,
+      :children =&gt; { :count =&gt; contents(:article1).trackbacks.size,
         :only =&gt; { :tag =&gt; &quot;li&quot; } }
   end
 
   def test_read_article_no_comments_no_trackbacks
-    get :read, :id =&gt; @article3.id
+    get :read, :id =&gt; contents(:article3).id
     assert_response :success
     assert_template &quot;read&quot;
 </diff>
      <filename>test/functional/articles_controller_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -48,7 +48,7 @@ class BackendControllerTest &lt; Test::Unit::TestCase
     assert_equal &quot;new post *body*&quot;, new_post.body
     assert_equal &quot;&lt;p&gt;new post &lt;strong&gt;body&lt;/strong&gt;&lt;/p&gt;&quot;, new_post.html(@controller, :body)
     assert_equal &quot;textile&quot;, new_post.text_filter.name
-    assert_equal @tobi, new_post.user
+    assert_equal users(:tobi), new_post.user
   end
   
   def test_blogger_new_post_no_title
@@ -70,7 +70,7 @@ class BackendControllerTest &lt; Test::Unit::TestCase
     new_post = Article.find(result)
     assert_equal &quot;new post title&quot;, new_post.title
     assert_equal &quot;new post body&quot;, new_post.body
-    assert_equal [@software, @hardware], new_post.categories.sort_by { |c| c.id }
+    assert_equal [categories(:software), categories(:hardware)], new_post.categories.sort_by { |c| c.id }
   end
 
   def test_blogger_new_post_with_non_existing_categories
@@ -79,7 +79,7 @@ class BackendControllerTest &lt; Test::Unit::TestCase
     result = invoke_layered :blogger, :newPost, *args
     assert_not_nil result
     new_post = Article.find(result)
-    assert_equal [@hardware], new_post.categories
+    assert_equal [categories(:hardware)], new_post.categories
   end
 
   def test_blogger_fail_authentication
@@ -196,7 +196,7 @@ class BackendControllerTest &lt; Test::Unit::TestCase
 
   def test_mt_get_post_categories
     article = Article.find(1)
-    article.categories &lt;&lt; @software
+    article.categories &lt;&lt; categories(:software)
 
     args = [ 1, 'tobi', 'whatever' ]
     
@@ -216,7 +216,7 @@ class BackendControllerTest &lt; Test::Unit::TestCase
       [MovableTypeStructs::CategoryPerPost.new('categoryName' =&gt; 'personal', 'categoryId' =&gt; 3, 'isPrimary' =&gt; 1)] ]
     
     result = invoke_layered :mt, :setPostCategories, *args
-    assert_equal [@personal], Article.find(2).categories
+    assert_equal [categories(:personal)], Article.find(2).categories
 
     args = [ 2, 'tobi', 'whatever',
       [MovableTypeStructs::CategoryPerPost.new('categoryName' =&gt; 'Software', 'categoryId' =&gt; 1, 'isPrimary' =&gt; 1),
@@ -224,7 +224,7 @@ class BackendControllerTest &lt; Test::Unit::TestCase
 
      result = invoke_layered :mt, :setPostCategories, *args
 
-     assert Article.find(2).categories.include?(@hardware)
+     assert Article.find(2).categories.include?(categories(:hardware))
 
   end
 </diff>
      <filename>test/functional/backend_controller_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -263,7 +263,7 @@ class XmlControllerTest &lt; Test::Unit::TestCase
     get :feed, :format =&gt; 'rss20', :type =&gt; 'feed'
     assert_response :success
     xml = REXML::Document.new(@response.body)
-    assert_equal @article2.created_at.rfc822, REXML::XPath.match(xml, '/rss/channel/item/pubDate').first.text
+    assert_equal contents(:article2).created_at.rfc822, REXML::XPath.match(xml, '/rss/channel/item/pubDate').first.text
   end
   
   def test_rsd</diff>
      <filename>test/functional/xml_controller_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,5 +1,5 @@
 class Theme
   def self.themes_root
-    RAILS_ROOT + &quot;test/mocks/themes&quot;
+    RAILS_ROOT + &quot;/test/mocks/themes&quot;
   end
 end
\ No newline at end of file</diff>
      <filename>test/mocks/test/theme_mock.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,38 +1,24 @@
 ENV[&quot;RAILS_ENV&quot;] = &quot;test&quot;
-
-# Expand the path to environment so that Ruby does not load it multiple times
-# File.expand_path can be removed if Ruby 1.9 is in use.
 require File.expand_path(File.dirname(__FILE__) + &quot;/../config/environment&quot;)
-require 'application'
-
-require 'test/unit'
-require 'active_record/fixtures'
-require 'action_controller/test_process'
-require 'action_web_service/test_invoke'
-require 'breakpoint'
+require 'test_help'
 
-# Set salt to 'change-me' because thats what the fixtures assume. 
 User.salt = 'change-me'
 
-Test::Unit::TestCase.fixture_path = File.dirname(__FILE__) + &quot;/fixtures/&quot;
-
 class Test::Unit::TestCase
-  # Turn these off to use instantiated fixtures.  This is really only useful with MySQL with
-  # MyISAM tables.
-  # self.use_transactional_fixtures = true
-  #self.use_instantiated_fixtures  = false
-
-  def create_fixtures(*table_names)
-    Fixtures.create_fixtures(File.dirname(__FILE__) + &quot;/fixtures&quot;, table_names)
-  end
+  # Turn off transactional fixtures if you're working with MyISAM tables in MySQL
+  self.use_transactional_fixtures = true
   
+  # Instantiated fixtures are slow, but give you @david where you otherwise would need people(:david)
+  self.use_instantiated_fixtures  = false
+
+  # Add more helper methods to be used by all tests here...
   def assert_xml(xml)
     assert_nothing_raised do
       assert REXML::Document.new(xml)
     end
   end
 
-  # Add more helper methods to be used by all tests here...
+   # Add more helper methods to be used by all tests here...
   def find_tag_in(source, conditions)
     HTML::Document.new(source).find(conditions)
   end
@@ -47,7 +33,7 @@ class Test::Unit::TestCase
     assert !tag, &quot;expected no tag, but tag found matching #{opts.inspect} in:\n#{source.inspect}&quot;
   end
 end
-
+  
 # Extend HTML::Tag to understand URI matching
 begin
   require 'html/document'</diff>
      <filename>test/test_helper.rb</filename>
    </modified>
    <modified>
      <diff>@@ -23,12 +23,12 @@ class ArticleTest &lt; Test::Unit::TestCase
   end
   
   def test_permalink
-    assert_equal @article3, Article.find_by_date(2004,06,01)
-    assert_equal [@article2, @article1], Article.find_all_by_date(Time.now.year)  
+    assert_equal contents(:article3), Article.find_by_date(2004,06,01)
+    assert_equal [contents(:article2), contents(:article1)], Article.find_all_by_date(Time.now.year)  
   end
 
   def test_permalink_with_title
-    assert_equal @article3, Article.find_by_permalink(2004, 06, 01, &quot;article-3&quot;)  
+    assert_equal contents(:article3), Article.find_by_permalink(2004, 06, 01, &quot;article-3&quot;)  
     assert_nil Article.find_by_permalink(2005, 06, 01, &quot;article-5&quot;)  
   end
   
@@ -42,18 +42,18 @@ class ArticleTest &lt; Test::Unit::TestCase
   end
   
   def test_perma_title
-    assert_equal &quot;article-1&quot;, @article1.stripped_title
-    assert_equal &quot;article-2&quot;, @article2.stripped_title
-    assert_equal &quot;article-3&quot;, @article3.stripped_title
+    assert_equal &quot;article-1&quot;, contents(:article1).stripped_title
+    assert_equal &quot;article-2&quot;, contents(:article2).stripped_title
+    assert_equal &quot;article-3&quot;, contents(:article3).stripped_title
   end
   
   def test_urls
-    urls = @article4.html_urls
+    urls = contents(:article4).html_urls
     assert_equal [&quot;http://www.example.com/public&quot;], urls
   end
   
   def test_send_pings
-#    @article1.send_pings(&quot;example.com&quot;, &quot;http://localhost/post/5?param=1&quot;)
+#    contents(:article1).send_pings(&quot;example.com&quot;, &quot;http://localhost/post/5?param=1&quot;)
 #    ping = Net::HTTP.pings.last
 #    assert_equal &quot;localhost&quot;,ping.host
 #    assert_equal 80, ping.port
@@ -62,8 +62,8 @@ class ArticleTest &lt; Test::Unit::TestCase
   end
 
   def test_send_multiple_pings
-    @article1.send_pings(&quot;example.com&quot;, [&quot;http://localhost/post/5?param=1&quot;, &quot;http://127.0.0.1/article/5&quot;])
-    assert_equal 4, @article1.pings.size
+    contents(:article1).send_pings(&quot;example.com&quot;, [&quot;http://localhost/post/5?param=1&quot;, &quot;http://127.0.0.1/article/5&quot;])
+    assert_equal 4, contents(:article1).pings.size
     assert_equal 4, Net::HTTP.pings.size # we don't actually ping example.com domains
     
     ping = Net::HTTP.pings[0]
@@ -122,40 +122,40 @@ class ArticleTest &lt; Test::Unit::TestCase
   end
 
   def test_find_published_by_tag_name
-    articles = Article.find_published_by_tag_name(@foo_tag.name)
+    articles = Article.find_published_by_tag_name(tags(:foo_tag).name)
 
     assert_equal 2, articles.size
-    assert_equal [@article2, @article1], articles
+    assert_equal [contents(:article2), contents(:article1)], articles
   end
   
   def test_find_published_by_category
     articles = Article.find_published_by_category_permalink('personal')
     assert_equal 3, articles.size
-    assert articles.include?(@article1)
-    assert articles.include?(@article2)
-    assert articles.include?(@article3)
+    assert articles.include?(contents(:article1))
+    assert articles.include?(contents(:article2))
+    assert articles.include?(contents(:article3))
     
     articles = Article.find_published_by_category_permalink('foobar')
     assert_equal 0, articles.size
 
     articles = Article.find_published_by_category_permalink('software')
     assert_equal 1, articles.size
-    assert articles.include?(@article1)
+    assert articles.include?(contents(:article1))
 
     articles = Article.find_published_by_category_permalink('personal', :limit =&gt; 1)
     assert_equal 1, articles.size
-    assert articles.include?(@article2)
+    assert articles.include?(contents(:article2))
 
     articles = Article.find_published_by_category_permalink('personal', :limit =&gt; 1, :order =&gt; 'created_at ASC')
     assert_equal 1, articles.size
-    assert articles.include?(@article3)
+    assert articles.include?(contents(:article3))
   end
 
   def test_destroy_file_upload_associations
-    assert_equal 2, @article1.resources.size
-    @article1.resources &lt;&lt; @resource1 &lt;&lt; @resource2
-    assert_equal 4, @article1.resources.size
-    @article1.destroy
-    assert_equal 0, Resource.find(:all, :conditions =&gt; &quot;article_id = #{@article1.id}&quot;).size
+    assert_equal 2, contents(:article1).resources.size
+    contents(:article1).resources &lt;&lt; resources(:resource1) &lt;&lt; resources(:resource2)
+    assert_equal 4, contents(:article1).resources.size
+    contents(:article1).destroy
+    assert_equal 0, Resource.find(:all, :conditions =&gt; &quot;article_id = #{contents(:article1).id}&quot;).size
   end
 end</diff>
      <filename>test/unit/article_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -14,9 +14,9 @@ class CategoryTest &lt; Test::Unit::TestCase
   def test_find_all_with_article_counters
     c = Category.find_all_with_article_counters
     
-    assert_equal @software, c[0]
-    assert_equal @hardware, c[1]
-    assert_equal @personal, c[2]
+    assert_equal categories(:software), c[0]
+    assert_equal categories(:hardware), c[1]
+    assert_equal categories(:personal), c[2]
 
     assert_equal 1, c[0].article_counter
     assert_equal 1, c[1].article_counter
@@ -24,14 +24,14 @@ class CategoryTest &lt; Test::Unit::TestCase
   end
 
   def test_reorder
-    assert_equal @software, Category.find(:first, :order =&gt; :position)
-    Category.reorder([@personal.id, @hardware.id, @software.id])
-    assert_equal @personal, Category.find(:first, :order =&gt; :position)
+    assert_equal categories(:software), Category.find(:first, :order =&gt; :position)
+    Category.reorder([categories(:personal).id, categories(:hardware).id, categories(:software).id])
+    assert_equal categories(:personal), Category.find(:first, :order =&gt; :position)
   end
   
   def test_reorder_alpha
-    assert_equal @software, Category.find(:first, :order =&gt; :position)
+    assert_equal categories(:software), Category.find(:first, :order =&gt; :position)
     Category.reorder_alpha
-    assert_equal @hardware, Category.find(:first, :order =&gt; :position)
+    assert_equal categories(:hardware), Category.find(:first, :order =&gt; :position)
   end
 end</diff>
      <filename>test/unit/category_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -10,13 +10,13 @@ class CommentTest &lt; Test::Unit::TestCase
   end
 
   def test_save_regular
-    assert @comment2.save
-    assert_equal &quot;http://www.google.com&quot;, @comment2.url
+    assert contents(:comment2).save
+    assert_equal &quot;http://www.google.com&quot;, contents(:comment2).url
   end
   
   def test_save_spam
-    assert @spam_comment.save
-    assert_equal &quot;http://fakeurl.com&quot;, @spam_comment.url
+    assert contents(:spam_comment).save
+    assert_equal &quot;http://fakeurl.com&quot;, contents(:spam_comment).url
   end
   
   def test_create_comment
@@ -67,7 +67,7 @@ class CommentTest &lt; Test::Unit::TestCase
     c = Comment.new
     c.author = &quot;Old Spammer&quot;
     c.body = &quot;Old trackback body&quot;
-    c.article = @article3
+    c.article = contents(:article3)
 
     assert ! c.save
     assert c.errors.invalid?('article_id')
@@ -79,8 +79,8 @@ class CommentTest &lt; Test::Unit::TestCase
   end
 
   def test_article_relation
-    assert_equal true, @comment2.has_article?
-    assert_equal 1, @comment2.article.id
+    assert_equal true, contents(:comment2).has_article?
+    assert_equal 1, contents(:comment2).article.id
   end
 
   def test_xss_rejection</diff>
      <filename>test/unit/comment_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -21,7 +21,7 @@ class ObserverTest &lt; Test::Unit::TestCase
   end
 
   def test_simple_observation
-    art = Article.find(@article1.id)
+    art = Article.find(contents(:article1).id)
     assert art.add_observer(self)
     art.changed
     art.notify_observers(art, :test)
@@ -31,10 +31,10 @@ class ObserverTest &lt; Test::Unit::TestCase
   end
 
   def test_content_observation
-    @article1.add_observer(self)
-    @article1.body = 'A new body'
+    contents(:article1).add_observer(self)
+    contents(:article1).body = 'A new body'
 
-    assert_equal @article1, @informant
+    assert_equal contents(:article1), @informant
     assert_equal :body, @symbol
   end
 end</diff>
      <filename>test/unit/observer_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -10,7 +10,7 @@ class PingTest &lt; Test::Unit::TestCase
   end
 
   def test_send_ping
-    ping = @article1.pings.build(&quot;url&quot; =&gt; &quot;http://localhost/post/5?param=1&quot;)
+    ping = contents(:article1).pings.build(&quot;url&quot; =&gt; &quot;http://localhost/post/5?param=1&quot;)
     ping.send_ping(&quot;example.com&quot;)
     
     ping = Net::HTTP.pings.last</diff>
      <filename>test/unit/ping_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -4,71 +4,68 @@ class ResourceTest &lt; Test::Unit::TestCase
   fixtures :resources
 
   def setup
-    @resource = Resource.find(1)
-    @resource2 = Resource.find(2)
-    
     # put the files on disk as if it were uploaded 
     FileUtils.mkpath(&quot;#{RAILS_ROOT}/public/files&quot;)
-    [ @resource, @resource2 ].each { |f| FileUtils.touch(f.fullpath) }
+    [ resources(:resource1), resources(:resource2) ].each { |f| FileUtils.touch(f.fullpath) }
   end
 
   def teardown
     # remove the files on disk
-    [ @resource, @resource2 ].each { |f| 
+    [ resources(:resource1), resources(:resource2) ].each { |f| 
       File.unlink(f.fullpath) if File.exist?(f.fullpath) 
     }
   end
   
   def test_fullpath
-    assert_equal @resource.fullpath, &quot;#{RAILS_ROOT}/public/files/#{@resource.filename}&quot;
+    assert_equal resources(:resource1).fullpath, &quot;#{RAILS_ROOT}/public/files/#{resources(:resource1).filename}&quot;
   end
   
   def test_create
-    assert_not_nil @resource
-    assert_not_nil @resource2
+    assert_not_nil resources(:resource1)
+    assert_not_nil resources(:resource2)
 
-    f1 = Resource.create(:filename =&gt; @resource['filename'],
-                            :mime =&gt; @resource['mime'],
+    f1 = Resource.create(:filename =&gt; resources(:resource1).filename,
+                            :mime =&gt; resources(:resource1).mime,
                             :created_at =&gt; Time.now)
     assert_not_nil f1
-    f2 = Resource.create(:filename =&gt; @resource2['filename'],
-                            :mime =&gt; @resource2['mime'],
+    f2 = Resource.create(:filename =&gt; resources(:resource2).filename,
+                            :mime =&gt; resources(:resource2).mime,
                             :created_at =&gt; Time.now)
     assert_not_nil f2
 
-    assert @resource.filename != f1.filename
-    assert @resource2.filename != f2.filename
+    assert resources(:resource1).filename != f1.filename
+    assert resources(:resource2).filename != f2.filename
     f1.destroy
     f2.destroy
   end
 
   def test_read
-    assert_not_nil @resource
-    f = Resource.find_by_filename(@resources['resource1']['filename'])
+    assert_not_nil resources(:resource1)
+    f = Resource.find_by_filename(resources(:resource1).filename)
     assert_not_nil f
-    assert_equal f, @resource
+    assert_equal f, resources(:resource1)
   end
   
   def test_update
-    assert_not_nil @resource
-    assert_not_nil @resource2
+    assert_not_nil resources(:resource1)
+    assert_not_nil resources(:resource2)
     
-    f = @resources['resource2']
-    assert @resource2.save
-    assert_equal f[&quot;filename&quot;], @resource2.filename
+    f = resources(:resource2)
+    assert resources(:resource2).save
+    assert_equal f.filename, resources(:resource2).filename
 
-    @resource.filename = f['filename']
-    assert !@resource.save
+    resources(:resource1).filename = f.filename
+    assert !resources(:resource1).save
 
-    @resource.filename = Resource.find(1).filename
-    assert @resource.save
+    resources(:resource1).filename = Resource.find(1).filename
+    assert resources(:resource1).save
   end
 
   def test_destroy
-    assert_not_nil @resource
+    assert_not_nil resources(:resource1)
     # blow it away, ensure that the file is removed from the public/files dir
-    assert @resource.destroy
-    assert !File.exist?(@resource.fullpath)
-    assert_raise(ActiveRecord::RecordNotFound) { Resource.find(@resource.id) }
+    assert resources(:resource1).destroy
+    assert !File.exist?(resources(:resource1).fullpath)
+    assert_raise(ActiveRecord::RecordNotFound) { Resource.find(resources(:resource1).id) }
   end
 end</diff>
      <filename>test/unit/resource_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -14,7 +14,7 @@ class TagTest &lt; Test::Unit::TestCase
 
   def test_get
     tag=Tag.get('foo')
-    assert_equal @foo_tag, tag
+    assert_equal tags(:foo_tag), tag
 
     tag2=Tag.get('zzz')
     assert_kind_of Tag, tag2
@@ -40,11 +40,11 @@ class TagTest &lt; Test::Unit::TestCase
   def test_article
     a1=Article.create(:title =&gt; 'Article 1')
     assert_kind_of Article, a1
-    a1.tags &lt;&lt; @foo_tag
-    a1.tags &lt;&lt; @bar_tag
+    a1.tags &lt;&lt; tags(:foo_tag)
+    a1.tags &lt;&lt; tags(:bar_tag)
 
     assert_equal 2, a1.tags.size
-    assert_equal [@foo_tag,@bar_tag].sort_by {|i| i.id}, a1.tags.sort_by {|i| i.id}
+    assert_equal [tags(:foo_tag),tags(:bar_tag)].sort_by {|i| i.id}, a1.tags.sort_by {|i| i.id}
   end
 
   def test_find_all_with_article_counters</diff>
      <filename>test/unit/tag_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -17,7 +17,7 @@ class TextFilterTest &lt; Test::Unit::TestCase
   def test_markdownsmartypants_fixture
     ms = TextFilter.find_by_name('markdown smartypants')
 
-    assert_equal @markdownsmartypants_filter, ms
+    assert_equal text_filters(:markdownsmartypants_filter), ms
     assert_equal 'markdown', ms.markup
     assert_equal [:smartypants], ms.filters
     assert_equal Hash.new, ms.params
@@ -25,7 +25,7 @@ class TextFilterTest &lt; Test::Unit::TestCase
 
   def test_textile_fixture
     tx = TextFilter.find_by_name('textile')
-    assert_equal @textile_filter, tx
+    assert_equal text_filters(:textile_filter), tx
     assert_equal 'textile', tx.markup
     assert_equal [], tx.filters
   end</diff>
      <filename>test/unit/text_filter_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -19,7 +19,7 @@ class ThemeTest &lt; Test::Unit::TestCase
   
   def test_themes_root
     # Overridden in theme_mock
-    assert_equal RAILS_ROOT + &quot;test/mocks/themes&quot;, Theme.themes_root
+    assert_equal RAILS_ROOT + &quot;/test/mocks/themes&quot;, Theme.themes_root
   end
 
   def test_current_theme_path</diff>
      <filename>test/unit/theme_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -5,8 +5,7 @@ class UserTest &lt; Test::Unit::TestCase
   fixtures :users
     
   def test_auth
-    
-    assert_equal  @bob, User.authenticate(&quot;bob&quot;, &quot;test&quot;)    
+    assert_equal  users(:bob), User.authenticate(&quot;bob&quot;, &quot;test&quot;)    
     assert_nil    User.authenticate(&quot;nonbob&quot;, &quot;test&quot;)
     
   end
@@ -19,7 +18,6 @@ class UserTest &lt; Test::Unit::TestCase
   end
   
   def test_disallowed_passwords
-    
     u = User.new    
     u.login = &quot;nonbob&quot;
 
@@ -42,7 +40,6 @@ class UserTest &lt; Test::Unit::TestCase
   end
   
   def test_bad_logins
-
     u = User.new  
     u.password = u.password_confirmation = &quot;bobs_secure_password&quot;
 
@@ -90,7 +87,6 @@ class UserTest &lt; Test::Unit::TestCase
     u.password = u.password_confirmation = &quot;bobs_secure_password&quot;
       
     assert u.save  
-    
   end
   
   def test_sha1</diff>
      <filename>test/unit/user_test.rb</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>22a446dd99a8b909e0ad94751ad7bc07d0e45c18</id>
    </parent>
  </parents>
  <author>
    <name>scott</name>
    <email>scott@google.com</email>
  </author>
  <url>http://github.com/fdv/typo/commit/3f913f897829fb026ef98df2d57f8da94ee5339d</url>
  <id>3f913f897829fb026ef98df2d57f8da94ee5339d</id>
  <committed-date>2005-11-04T08:07:05-08:00</committed-date>
  <authored-date>2005-11-04T08:07:05-08:00</authored-date>
  <message>Upgrading to Rails 0.14.2.  Ran 'rails .', patched up the Rakefile to use .rake tasks, fixed environment.rb somewhat, and updated all of the tests to stop using instantiated fixtures.

git-svn-id: http://svn.typosphere.org/typo/trunk@717 820eb932-12ee-0310-9ca8-eeb645f39767</message>
  <tree>c477dcb8efdb7f79d20a1277df184ba241c9a7c1</tree>
  <committer>
    <name>scott</name>
    <email>scott@google.com</email>
  </committer>
</commit>
