You are here: Home > Latest news from Darcs > Removes src directory (repo migration leftover)

Revision 20080305220455-9043f-5f2e84...

Removes src directory (repo migration leftover)

src/app/controllers/application.rb
src/app/controllers/report_controller.rb
src/app/core/filesystem.rb
src/app/core/runner.rb
src/app/helpers/application_helper.rb
src/app/models/headline.rb
src/app/reporters/svn_connection.rb
src/app/reporters/svn_reporter.rb
src/app/reporters/svn_settings.rb
src/app/views/report/subversion.rhtml
src/app/views/subversion/subversion.rhtml
src/config/boot.rb
src/config/database.yml
src/config/environment.rb
src/config/environments/development.rb
src/config/environments/production.rb
src/config/environments/test.rb
src/config/report/subversion.yml
src/config/routes.rb
src/db/create.sql
src/doc/README_FOR_APP
src/public/404.html
src/public/500.html
src/public/dispatch.cgi
src/public/dispatch.fcgi
src/public/dispatch.rb
src/public/.htaccess
src/public/index.html
src/public/javascripts/controls.js
src/public/javascripts/dragdrop.js
src/public/javascripts/effects.js
src/public/javascripts/prototype.js
src/public/robots.txt
src/Rakefile
src/README
src/README.en
src/script/about
src/script/breakpointer
src/script/console
src/script/destroy
src/script/generate
src/script/performance/benchmarker
src/script/performance/profiler
src/script/plugin
src/script/process/reaper
src/script/process/spawner
src/script/process/spinner
src/script/runner
src/script/server
src/test/fixtures/headlines.yml
src/test/functional/subversion_controller_test.rb
src/test/mocks/filesystem.rb
src/test/mocks/runner.rb
src/test/stubs/svn_connection.rb
src/test/stubs/svn_settings.rb
src/test/test_helper.rb
src/test/unit/headline_test.rb
src/test/unit/svn_connection_test.rb
src/test/unit/svn_reporter_test.rb
src/test/unit/svn_settings_test.rb
src/test/unit/ts_all_suites.rb
src/test/unit/ts_svn.rb

Changes to application.rb

1
# Filters added to this controller will be run for all controllers in the application.
 
2
# Likewise, all the methods added will be available for all controllers.
 
3
class ApplicationController < ActionController::Base
 
4
end
 

Changes to report_controller.rb

1
require 'reporters/svn_reporter'
 
2
 
3
class ReportController < ApplicationController
 
4
 
5
    def subversion
 
6
        reporter = SubversionReporter.new
 
7
        @headlines = reporter.latest_headlines(5)
 
8
    end
 
9
 
10
end
 

Changes to filesystem.rb

1
class FileSystem
 
2
 
3
    def open(filename)
 
4
        File.open(filename)
 
5
    end
 
6
 
7
end
 

Changes to runner.rb

1
class Runner
 
2
 
3
    def run(command)
 
4
        `#{command}`
 
5
    end
 
6
 
7
end
 

Changes to application_helper.rb

1
# Methods added to this helper will be available to all templates in the application.
 
2
module ApplicationHelper
 
3
end
 

Changes to headline.rb

1
class Headline < ActiveRecord::Base
 
2
end
 

Changes to svn_connection.rb

1
require 'core/runner'
 
2
require 'reporters/svn_settings'
 
3
 
4
class SubversionConnection
 
5
    
 
6
    def initialize
 
7
        initialize(SubversionSettingsProvider.new, Runner.new)
 
8
    end
 
9
 
10
    def initialize(settings=SubversionSettingsProvider.new, runner=Runner.new)
 
11
        @settings, @runner = settings, runner
 
12
    end
 
13
 
14
    #TODO parametize the revision list size
 
15
    def log
 
16
        @runner.run "svn log #{@settings.getRepoURL}"
 
17
    end
 
18
    
 
19
end                                                           
 

Changes to svn_reporter.rb

1
require 'date'
 
2
require 'reporters/svn_connection'
 
3
 
4
require 'models/headline'
 
5
 
6
class SubversionReporter
 
7
 
8
    def initialize(connection = SubversionConnection.new)
 
9
        @connection = connection
 
10
    end
 
11
 
12
    def latest_headline
 
13
        return latest_headlines(1).first
 
14
    end
 
15
    
 
16
    def latest_headlines(num)
 
17
        remain = @connection.log
 
18
        hls = Array.new
 
19
        num.times do
 
20
            hl, remain = svn_parse_entry(remain)
 
21
            hls.push hl
 
22
        end
 
23
        return hls
 
24
    end
 
25
    
 
26
private
 
27
    
 
28
    def svn_parse_entry(text)
 
29
        remain = /^-+\n/.match(text).post_match
 
30
        md = /^r\d+\s*\|\s*(\w+)\s*\|\s(\d+)-(\d+)-(\d+)\s+(\d+):(\d+):(\d+)[^|]*\|\s*(\d+)\s*[^\n]*\n/.match(remain)
 
31
        remain = md.post_match
 
32
        author = md[1]
 
33
        year, month, day, hour, min, sec = md[2..7].collect do |s| s.to_i end
 
34
        numlines = md[8].to_i
 
35
        revDate = DateTime.new(year, month, day, hour, min, sec)
 
36
        md = /^[^\n]*\n([^\r\n]*)\n/.match(remain)
 
37
        title = md[1]
 
38
        (numlines).times do
 
39
            remain = /\n/.match(remain).post_match
 
40
        end
 
41
        return Headline.new(:author => author,
 
42
                            :event_date => revDate,
 
43
                            :title => title), remain
 
44
    end
 
45
 
46
end
 

Changes to svn_settings.rb

1
require 'yaml'
 
2
 
3
require 'core/filesystem'
 
4
 
5
class SubversionSettingsProvider
 
6
    
 
7
    # The settings provider is responsible for reading the settings from the
 
8
    # config file and providing them to clients
 
9
 
10
    def initialize(fs=FileSystem.new)
 
11
        @filesystem = fs
 
12
    end
 
13
 
14
    #TODO parametize the repo location
 
15
    def getRepoURL
 
16
        file = @filesystem.open(File.expand_path(File.dirname(__FILE__) +
 
17
                                '/../../config/report/subversion.yml'))
 
18
        confs = YAML.load file
 
19
        return confs['repo']
 
20
    end
 
21
    
 
22
end
 

Changes to subversion.rhtml

1
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
 
2
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
3
<html>
 
4
  <head>
 
5
    <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
 
6
    <title>Últimas notícias do Subversion</title>
 
7
  </head>
 
8
  <body>
 
9
    <p><h1>Últimas notícias do Subversion</h1></p>
 
10
    <% @headlines.each do |headline|%>
 
11
    <p>
 
12
      <%= headline.title %><br/>
 
13
      <font size="-1">
 
14
        <i><%= headline.author %></i>
 
15
        <%= headline.event_date %>
 
16
      </font>
 
17
      <hr/>
 
18
    </p>
 
19
    <% end %>
 
20
  </body>
 
21
</html>
 

Changes to subversion.rhtml

1
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
 
2
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
3
<html>
 
4
  <head>
 
5
    <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
 
6
    <title>Últimas notícias do Subversion</title>
 
7
  </head>
 
8
  <body>
 
9
    <p><h1>Últimas notícias do Subversion</h1></p>
 
10
    <% @headlines.each do |headline|%>
 
11
    <p>
 
12
      <%= headline.title %><br/>
 
13
      <font size="-1"><i><%= headline.author %></i> <%= headline.date %></font>
 
14
      <hr/>
 
15
    </p>
 
16
    <% end %>
 
17
  </body>
 
18
</html>
 

Changes to boot.rb

1
# Don't change this file. Configuration is done in config/environment.rb and config/environments/*.rb
 
2
 
3
unless defined?(RAILS_ROOT)
 
4
  root_path = File.join(File.dirname(__FILE__), '..')
 
5
  unless RUBY_PLATFORM =~ /mswin32/
 
6
    require 'pathname'
 
7
    root_path = Pathname.new(root_path).cleanpath(true).to_s
 
8
  end
 
9
  RAILS_ROOT = root_path
 
10
end
 
11
 
12
if File.directory?("#{RAILS_ROOT}/vendor/rails")
 
13
  require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer"
 
14
else
 
15
  require 'rubygems'
 
16
  require 'initializer'
 
17
end
 
18
 
19
Rails::Initializer.run(:set_load_path)
 

Changes to database.yml

1
# MySQL (default setup).  Versions 4.1 and 5.0 are recommended.
 
2
#
 
3
# Get the fast C bindings:
 
4
#   gem install mysql
 
5
#   (on OS X: gem install mysql -- --include=/usr/local/lib)
 
6
# And be sure to use new-style password hashing:
 
7
#   http://dev.mysql.com/doc/refman/5.0/en/old-client.html
 
8
development:
 
9
  adapter: mysql
 
10
  database: motiro_development
 
11
  host: 127.0.0.1
 
12
  port: 3306
 
13
  username: motiro
 
14
  password:
 
15
 
16
# Warning: The database defined as 'test' will be erased and
 
17
# re-generated from your development database when you run 'rake'.
 
18
# Do not set this db to the same as development or production.
 
19
test:
 
20
  adapter: mysql
 
21
  database: motiro_test
 
22
  host: 127.0.0.1
 
23
  port: 3306
 
24
  username: motiro
 
25
  password:
 
26
 
27
production:
 
28
  adapter: mysql
 
29
  database: motiro_production
 
30
  host: 127.0.0.1
 
31
  port: 3306
 
32
  username: motiro
 
33
  password: 
 
34
 

Changes to environment.rb

1
# Be sure to restart your web server when you modify this file.
 
2
 
3
# Uncomment below to force Rails into production mode when 
 
4
# you don't control web/app server and can't set it the proper way
 
5
# ENV['RAILS_ENV'] ||= 'production'
 
6
 
7
# Bootstrap the Rails environment, frameworks, and default configuration
 
8
require File.join(File.dirname(__FILE__), 'boot')
 
9
 
10
Rails::Initializer.run do |config|
 
11
  # Settings in config/environments/* take precedence those specified here
 
12
  
 
13
  # Skip frameworks you're not going to use
 
14
  # config.frameworks -= [ :action_web_service, :action_mailer ]
 
15
 
16
  # Add additional load paths for your own custom dirs
 
17
  # config.load_paths += %W( #{RAILS_ROOT}/extras )
 
18
 
19
  # Force all environments to use the same logger level 
 
20
  # (by default production uses :info, the others :debug)
 
21
  # config.log_level = :debug
 
22
 
23
  # Use the database for sessions instead of the file system
 
24
  # (create the session table with 'rake create_sessions_table')
 
25
  # config.action_controller.session_store = :active_record_store
 
26
 
27
  # Enable page/fragment caching by setting a file-based store
 
28
  # (remember to create the caching directory and make it readable to the application)
 
29
  # config.action_controller.fragment_cache_store = :file_store, "#{RAILS_ROOT}/cache"
 
30
 
31
  # Activate observers that should always be running
 
32
  # config.active_record.observers = :cacher, :garbage_collector
 
33
 
34
  # Make Active Record use UTC-base instead of local time
 
35
  # config.active_record.default_timezone = :utc
 
36
  
 
37
  # Use Active Record's schema dumper instead of SQL when creating the test database
 
38
  # (enables use of different database adapters for development and test environments)
 
39
  # config.active_record.schema_format = :ruby
 
40
 
41
  # See Rails::Configuration for more options
 
42
end
 
43
 
44
# Add new inflection rules using the following format 
 
45
# (all these examples are active by default):
 
46
# Inflector.inflections do |inflect|
 
47
#   inflect.plural /^(ox)$/i, '\1en'
 
48
#   inflect.singular /^(ox)en/i, '\1'
 
49
#   inflect.irregular 'person', 'people'
 
50
#   inflect.uncountable %w( fish sheep )
 
51
# end
 
52
 
53
# Include your application configuration below
 

Changes to development.rb

1
# Settings specified here will take precedence over those in config/environment.rb
 
2
 
3
# In the development environment your application's code is reloaded on
 
4
# every request.  This slows down response time but is perfect for development
 
5
# since you don't have to restart the webserver when you make code changes.
 
6
config.cache_classes     = false
 
7
 
8
# Log error messages when you accidentally call methods on nil.
 
9
config.whiny_nils        = true
 
10
 
11
# Enable the breakpoint server that script/breakpointer connects to
 
12
config.breakpoint_server = true
 
13
 
14
# Show full error reports and disable caching
 
15
config.action_controller.consider_all_requests_local = true
 
16
config.action_controller.perform_caching             = false
 
17
 
18
# Don't care if the mailer can't send
 
19
config.action_mailer.raise_delivery_errors = false
 

Changes to production.rb

1
# Settings specified here will take precedence over those in config/environment.rb
 
2
 
3
# The production environment is meant for finished, "live" apps.
 
4
# Code is not reloaded between requests
 
5
config.cache_classes = true
 
6
 
7
# Use a different logger for distributed setups
 
8
# config.logger        = SyslogLogger.new
 
9
 
10
 
11
# Full error reports are disabled and caching is turned on
 
12
config.action_controller.consider_all_requests_local = false
 
13
config.action_controller.perform_caching             = true
 
14
 
15
# Enable serving of images, stylesheets, and javascripts from an asset server
 
16
# config.action_controller.asset_host                  = "http://assets.example.com"
 
17
 
18
# Disable delivery errors if you bad email addresses should just be ignored
 
19
# config.action_mailer.raise_delivery_errors = false
 

Changes to test.rb

1
# Settings specified here will take precedence over those in config/environment.rb
 
2
 
3
# The test environment is used exclusively to run your application's
 
4
# test suite.  You never need to work with it otherwise.  Remember that
 
5
# your test database is "scratch space" for the test suite and is wiped
 
6
# and recreated between test runs.  Don't rely on the data there!
 
7
config.cache_classes = true
 
8
 
9
# Log error messages when you accidentally call methods on nil.
 
10
config.whiny_nils    = true
 
11
 
12
# Show full error reports and disable caching
 
13
config.action_controller.consider_all_requests_local = true
 
14
config.action_controller.perform_caching             = false
 
15
 
16
# Tell ActionMailer not to deliver emails to the real world.
 
17
# The :test delivery method accumulates sent emails in the
 
18
# ActionMailer::Base.deliveries array.
 
19
config.action_mailer.delivery_method = :test
 

Changes to subversion.yml

1
repo: svn://svn.berlios.de/motiro
 
2
 

Changes to routes.rb

1
ActionController::Routing::Routes.draw do |map|
 
2
  # Add your own custom routes here.
 
3
  # The priority is based upon order of creation: first created -> highest priority.
 
4
  
 
5
  # Here's a sample route:
 
6
  # map.connect 'products/:id', :controller => 'catalog', :action => 'view'
 
7
  # Keep in mind you can assign values other than :controller and :action
 
8
 
9
  # You can have the root of your site routed by hooking up '' 
 
10
  # -- just remember to delete public/index.html.
 
11
  # map.connect '', :controller => "welcome"
 
12
 
13
  # Allow downloading Web Service WSDL as a file with an extension
 
14
  # instead of a file named 'wsdl'
 
15
  map.connect ':controller/service.wsdl', :action => 'wsdl'
 
16
 
17
  # Install the default route as the lowest priority.
 
18
  map.connect ':controller/:action/:id'
 
19
end
 

Changes to create.sql

1
drop table if exists headlines;
 
2
create table headlines (
 
3
	id int not null auto_increment,
 
4
	author varchar(40) not null,
 
5
	title varchar(100) not null,
 
6
	event_date varchar(30) not null,
 
7
	primary key (id)
 
8
);
 

Changes to README_FOR_APP

1
Use this README file to introduce your application and point to useful places in the API for learning more.
 
2
Run "rake appdoc" to generate API documentation for your models and controllers.
 

Changes to 404.html

1
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
 
2
   "http://www.w3.org/TR/html4/loose.dtd">
 
3
<html>
 
4
<body>
 
5
  <h1>File not found</h1>
 
6
  <p>Change this error message for pages not found in public/404.html</p>
 
7
</body>
 
8
</html>
 

Changes to 500.html

1
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
 
2
   "http://www.w3.org/TR/html4/loose.dtd">
 
3
<html>
 
4
<body>
 
5
  <h1>Application error (Apache)</h1>
 
6
  <p>Change this error message for exceptions thrown outside of an action (like in Dispatcher setups or broken Ruby code) in public/500.html</p>
 
7
</body>
 
8
</html>
 

Changes to dispatch.cgi

1
#!/home/thiagob/programas/ruby-1.8.4/bin/ruby
 
2
 
3
require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT)
 
4
 
5
# If you're using RubyGems and mod_ruby, this require should be changed to an absolute path one, like:
 
6
# "/usr/local/lib/ruby/gems/1.8/gems/rails-0.8.0/lib/dispatcher" -- otherwise performance is severely impaired
 
7
require "dispatcher"
 
8
 
9
ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun)
 
10
Dispatcher.dispatch
 

Changes to dispatch.fcgi

1
#!/home/thiagob/programas/ruby-1.8.4/bin/ruby
 
2
#
 
3
# You may specify the path to the FastCGI crash log (a log of unhandled
 
4
# exceptions which forced the FastCGI instance to exit, great for debugging)
 
5
# and the number of requests to process before running garbage collection.
 
6
#
 
7
# By default, the FastCGI crash log is RAILS_ROOT/log/fastcgi.crash.log
 
8
# and the GC period is nil (turned off).  A reasonable number of requests
 
9
# could range from 10-100 depending on the memory footprint of your app.
 
10
#
 
11
# Example:
 
12
#   # Default log path, normal GC behavior.
 
13
#   RailsFCGIHandler.process!
 
14
#
 
15
#   # Default log path, 50 requests between GC.
 
16
#   RailsFCGIHandler.process! nil, 50
 
17
#
 
18
#   # Custom log path, normal GC behavior.
 
19
#   RailsFCGIHandler.process! '/var/log/myapp_fcgi_crash.log'
 
20
#
 
21
require File.dirname(__FILE__) + "/../config/environment"
 
22
require 'fcgi_handler'
 
23
 
24
RailsFCGIHandler.process!
 

Changes to dispatch.rb

1
#!/home/thiagob/programas/ruby-1.8.4/bin/ruby
 
2
 
3
require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT)
 
4
 
5
# If you're using RubyGems and mod_ruby, this require should be changed to an absolute path one, like:
 
6
# "/usr/local/lib/ruby/gems/1.8/gems/rails-0.8.0/lib/dispatcher" -- otherwise performance is severely impaired
 
7
require "dispatcher"
 
8
 
9
ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun)
 
10
Dispatcher.dispatch
 

Changes to .htaccess

1
# General Apache options
 
2
AddHandler fastcgi-script .fcgi
 
3
AddHandler cgi-script .cgi
 
4
Options +FollowSymLinks +ExecCGI
 
5
 
6
# If you don't want Rails to look in certain directories,
 
7
# use the following rewrite rules so that Apache won't rewrite certain requests
 
8
# 
 
9
# Example:
 
10
#   RewriteCond %{REQUEST_URI} ^/notrails.*
 
11
#   RewriteRule .* - [L]
 
12
 
13
# Redirect all requests not available on the filesystem to Rails
 
14
# By default the cgi dispatcher is used which is very slow
 
15
# 
 
16
# For better performance replace the dispatcher with the fastcgi one
 
17
#
 
18
# Example:
 
19
#   RewriteRule ^(.*)$ dispatch.fcgi [QSA,L]
 
20
RewriteEngine On
 
21
 
22
# If your Rails application is accessed via an Alias directive,
 
23
# then you MUST also set the RewriteBase in this htaccess file.
 
24
#
 
25
# Example:
 
26
#   Alias /myrailsapp /path/to/myrailsapp/public
 
27
#   RewriteBase /myrailsapp
 
28
 
29
RewriteRule ^$ index.html [QSA]
 
30
RewriteRule ^([^.]+)$ $1.html [QSA]
 
31
RewriteCond %{REQUEST_FILENAME} !-f
 
32
RewriteRule ^(.*)$ dispatch.cgi [QSA,L]
 
33
 
34
# In case Rails experiences terminal errors
 
35
# Instead of displaying this message you can supply a file here which will be rendered instead
 
36
# 
 
37
# Example:
 
38
#   ErrorDocument 500 /500.html
 
39
 
40
ErrorDocument 500 "<h2>Application error</h2>Rails application failed to start properly"
 

Changes to index.html

1
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
 
2
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
3
<html>
 
4
  <head>
 
5
    <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
 
6
    <title>Motiro: Bem-vindo</title>
 
7
  </head>
 
8
  <body>
 
9
      <h1>Motiro</h1>
 
10
      <p>
 
11
        Seja bem-vindo! Esta é a página principal do projeto Motiro.
 
12
      </p>
 
13
      <p>
 
14
        Estamos tentando construir um portal para reunir diversas
 
15
        aplicações de apoio e controle ao desenvolvimento de software.
 
16
        Alguns exemplos são aplicações de gerêcia de configuração,
 
17
        controle de bugs e listas de discussão. O papel do portal é
 
18
        fazer com que elas estejam integradas e funcionando de forma 
 
19
        ubíqua, permitindo ao desenvolvedor ter acesso a diversos tipos
 
20
        de informação sobre seu projeto em um único local.
 
21
      </p>
 
22
      <p>
 
23
        Confuso? Dê uma olhada em volta. Esta página é um exemplo de 
 
24
        portal Motiro em funcionamento.
 
25
      </p>
 
26
      <p>
 
27
        Interessado? Use esta página para conseguir mais informações. Se
 
28
        quiser nos contactar, estamos todos reunidos discutindo no grupo
 
29
        <a href="http://groups.google.com/group/motiro">motiro@googlegroups.com</a>.
 
30
        Junte-se ao mutirão!
 
31
      </p>
 
32
  </body>
 
33
</html>
 

Changes to controls.js

1
// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
 
2
//           (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
 
3
//           (c) 2005 Jon Tirsen (http://www.tirsen.com)
 
4
// Contributors:
 
5
//  Richard Livsey
 
6
//  Rahul Bhargava
 
7
//  Rob Wills
 
8
// 
 
9
// See scriptaculous.js for full license.
 
10
 
11
// Autocompleter.Base handles all the autocompletion functionality 
 
12
// that's independent of the data source for autocompletion. This
 
13
// includes drawing the autocompletion menu, observing keyboard
 
14
// and mouse events, and similar.
 
15
//
 
16
// Specific autocompleters need to provide, at the very least, 
 
17
// a getUpdatedChoices function that will be invoked every time
 
18
// the text inside the monitored textbox changes. This method 
 
19
// should get the text for which to provide autocompletion by
 
20
// invoking this.getToken(), NOT by directly accessing
 
21
// this.element.value. This is to allow incremental tokenized
 
22
// autocompletion. Specific auto-completion logic (AJAX, etc)
 
23
// belongs in getUpdatedChoices.
 
24
//
 
25
// Tokenized incremental autocompletion is enabled automatically
 
26
// when an autocompleter is instantiated with the 'tokens' option
 
27
// in the options parameter, e.g.:
 
28
// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
 
29
// will incrementally autocomplete with a comma as the token.
 
30
// Additionally, ',' in the above example can be replaced with
 
31
// a token array, e.g. { tokens: [',', '\n'] } which
 
32
// enables autocompletion on multiple tokens. This is most 
 
33
// useful when one of the tokens is \n (a newline), as it 
 
34
// allows smart autocompletion after linebreaks.
 
35
 
36
var Autocompleter = {}
 
37
Autocompleter.Base = function() {};
 
38
Autocompleter.Base.prototype = {
 
39
  baseInitialize: function(element, update, options) {
 
40
    this.element     = $(element); 
 
41
    this.update      = $(update);  
 
42
    this.hasFocus    = false; 
 
43
    this.changed     = false; 
 
44
    this.active      = false; 
 
45
    this.index       = 0;     
 
46
    this.entryCount  = 0;
 
47
 
48
    if (this.setOptions)
 
49
      this.setOptions(options);
 
50
    else
 
51
      this.options = options || {};
 
52
 
53
    this.options.paramName    = this.options.paramName || this.element.name;
 
54
    this.options.tokens       = this.options.tokens || [];
 
55
    this.options.frequency    = this.options.frequency || 0.4;
 
56
    this.options.minChars     = this.options.minChars || 1;
 
57
    this.options.onShow       = this.options.onShow || 
 
58
    function(element, update){ 
 
59
      if(!update.style.position || update.style.position=='absolute') {
 
60
        update.style.position = 'absolute';
 
61
        Position.clone(element, update, {setHeight: false, offsetTop: element.offsetHeight});
 
62
      }
 
63
      Effect.Appear(update,{duration:0.15});
 
64
    };
 
65
    this.options.onHide = this.options.onHide || 
 
66
    function(element, update){ new Effect.Fade(update,{duration:0.15}) };
 
67
 
68
    if (typeof(this.options.tokens) == 'string') 
 
69
      this.options.tokens = new Array(this.options.tokens);
 
70
 
71
    this.observer = null;
 
72
    
 
73
    this.element.setAttribute('autocomplete','off');
 
74
 
75
    Element.hide(this.update);
 
76
 
77
    Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this));
 
78
    Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this));
 
79
  },
 
80
 
81
  show: function() {
 
82
    if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
 
83
    if(!this.iefix && 
 
84
      (navigator.appVersion.indexOf('MSIE')>0) &&
 
85
      (navigator.userAgent.indexOf('Opera')<0) &&
 
86
      (Element.getStyle(this.update, 'position')=='absolute')) {
 
87
      new Insertion.After(this.update, 
 
88
       '<iframe id="' + this.update.id + '_iefix" '+
 
89
       'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
 
90
       'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
 
91
      this.iefix = $(this.update.id+'_iefix');
 
92
    }
 
93
    if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
 
94
  },
 
95
  
 
96
  fixIEOverlapping: function() {
 
97
    Position.clone(this.update, this.iefix);
 
98
    this.iefix.style.zIndex = 1;
 
99
    this.update.style.zIndex = 2;
 
100
    Element.show(this.iefix);
 
101
  },
 
102
 
103
  hide: function() {
 
104
    this.stopIndicator();
 
105
    if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
 
106
    if(this.iefix) Element.hide(this.iefix);
 
107
  },
 
108
 
109
  startIndicator: function() {
 
110
    if(this.options.indicator) Element.show(this.options.indicator);
 
111
  },
 
112
 
113
  stopIndicator: function() {
 
114
    if(this.options.indicator) Element.hide(this.options.indicator);
 
115
  },
 
116
 
117
  onKeyPress: function(event) {
 
118
    if(this.active)
 
119
      switch(event.keyCode) {
 
120
       case Event.KEY_TAB:
 
121
       case Event.KEY_RETURN:
 
122
         this.selectEntry();
 
123
         Event.stop(event);
 
124
       case Event.KEY_ESC:
 
125
         this.hide();
 
126
         this.active = false;
 
127
         Event.stop(event);
 
128
         return;
 
129
       case Event.KEY_LEFT:
 
130
       case Event.KEY_RIGHT:
 
131
         return;
 
132
       case Event.KEY_UP:
 
133
         this.markPrevious();
 
134
         this.render();
 
135
         if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
 
136
         return;
 
137
       case Event.KEY_DOWN:
 
138
         this.markNext();
 
139
         this.render();
 
140
         if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
 
141
         return;
 
142
      }
 
143
     else 
 
144
      if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN) 
 
145
        return;
 
146
 
147
    this.changed = true;
 
148
    this.hasFocus = true;
 
149
 
150
    if(this.observer) clearTimeout(this.observer);
 
151
      this.observer = 
 
152
        setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
 
153
  },
 
154
 
155
  onHover: function(event) {
 
156
    var element = Event.findElement(event, 'LI');
 
157
    if(this.index != element.autocompleteIndex) 
 
158
    {
 
159
        this.index = element.autocompleteIndex;
 
160
        this.render();
 
161
    }
 
162
    Event.stop(event);
 
163
  },
 
164
  
 
165
  onClick: function(event) {
 
166
    var element = Event.findElement(event, 'LI');
 
167
    this.index = element.autocompleteIndex;
 
168
    this.selectEntry();
 
169
    this.hide();
 
170
  },
 
171
  
 
172
  onBlur: function(event) {
 
173
    // needed to make click events working
 
174
    setTimeout(this.hide.bind(this), 250);
 
175
    this.hasFocus = false;
 
176
    this.active = false;     
 
177
  }, 
 
178
  
 
179
  render: function() {
 
180
    if(this.entryCount > 0) {
 
181
      for (var i = 0; i < this.entryCount; i++)
 
182
        this.index==i ? 
 
183
          Element.addClassName(this.getEntry(i),"selected") : 
 
184
          Element.removeClassName(this.getEntry(i),"selected");
 
185
        
 
186
      if(this.hasFocus) { 
 
187
        this.show();
 
188
        this.active = true;
 
189
      }
 
190
    } else {
 
191
      this.active = false;
 
192
      this.hide();
 
193
    }
 
194
  },
 
195
  
 
196
  markPrevious: function() {
 
197
    if(this.index > 0) this.index--
 
198
      else this.index = this.entryCount-1;
 
199
  },
 
200
  
 
201
  markNext: function() {
 
202
    if(this.index < this.entryCount-1) this.index++
 
203
      else this.index = 0;
 
204
  },
 
205
  
 
206
  getEntry: function(index) {
 
207
    return this.update.firstChild.childNodes[index];
 
208
  },
 
209
  
 
210
  getCurrentEntry: function() {
 
211
    return this.getEntry(this.index);
 
212
  },
 
213
  
 
214
  selectEntry: function() {
 
215
    this.active = false;
 
216
    this.updateElement(this.getCurrentEntry());
 
217
  },
 
218
 
219
  updateElement: function(selectedElement) {
 
220
    if (this.options.updateElement) {
 
221
      this.options.updateElement(selectedElement);
 
222
      return;
 
223
    }
 
224
 
225
    var value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
 
226
    var lastTokenPos = this.findLastToken();
 
227
    if (lastTokenPos != -1) {
 
228
      var newValue = this.element.value.substr(0, lastTokenPos + 1);
 
229
      var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/);
 
230
      if (whitespace)
 
231
        newValue += whitespace[0];
 
232
      this.element.value = newValue + value;
 
233
    } else {
 
234
      this.element.value = value;
 
235
    }
 
236
    this.element.focus();
 
237
    
 
238
    if (this.options.afterUpdateElement)
 
239
      this.options.afterUpdateElement(this.element, selectedElement);
 
240
  },
 
241
 
242
  updateChoices: function(choices) {
 
243
    if(!this.changed && this.hasFocus) {
 
244
      this.update.innerHTML = choices;
 
245
      Element.cleanWhitespace(this.update);
 
246
      Element.cleanWhitespace(this.update.firstChild);
 
247
 
248
      if(this.update.firstChild && this.update.firstChild.childNodes) {
 
249
        this.entryCount = 
 
250
          this.update.firstChild.childNodes.length;
 
251
        for (var i = 0; i < this.entryCount; i++) {
 
252
          var entry = this.getEntry(i);
 
253
          entry.autocompleteIndex = i;
 
254
          this.addObservers(entry);
 
255
        }
 
256
      } else { 
 
257
        this.entryCount = 0;
 
258
      }
 
259
 
260
      this.stopIndicator();
 
261
 
262
      this.index = 0;
 
263
      this.render();
 
264
    }
 
265
  },
 
266
 
267
  addObservers: function(element) {
 
268
    Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
 
269
    Event.observe(element, "click", this.onClick.bindAsEventListener(this));
 
270
  },
 
271
 
272
  onObserverEvent: function() {
 
273
    this.changed = false;   
 
274
    if(this.getToken().length>=this.options.minChars) {
 
275
      this.startIndicator();
 
276
      this.getUpdatedChoices();
 
277
    } else {
 
278
      this.active = false;
 
279
      this.hide();
 
280
    }
 
281
  },
 
282
 
283
  getToken: function() {
 
284
    var tokenPos = this.findLastToken();
 
285
    if (tokenPos != -1)
 
286
      var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,'');
 
287
    else
 
288
      var ret = this.element.value;
 
289
 
290
    return /\n/.test(ret) ? '' : ret;
 
291
  },
 
292
 
293
  findLastToken: function() {
 
294
    var lastTokenPos = -1;
 
295
 
296
    for (var i=0; i<this.options.tokens.length; i++) {
 
297
      var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]);
 
298
      if (thisTokenPos > lastTokenPos)
 
299
        lastTokenPos = thisTokenPos;
 
300
    }
 
301
    return lastTokenPos;
 
302
  }
 
303
}
 
304
 
305
Ajax.Autocompleter = Class.create();
 
306
Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), {
 
307
  initialize: function(element, update, url, options) {
 
308
	  this.baseInitialize(element, update, options);
 
309
    this.options.asynchronous  = true;
 
310
    this.options.onComplete    = this.onComplete.bind(this);
 
311
    this.options.defaultParams = this.options.parameters || null;
 
312
    this.url                   = url;
 
313
  },
 
314
 
315
  getUpdatedChoices: function() {
 
316
    entry = encodeURIComponent(this.options.paramName) + '=' + 
 
317
      encodeURIComponent(this.getToken());
 
318
 
319
    this.options.parameters = this.options.callback ?
 
320
      this.options.callback(this.element, entry) : entry;
 
321
 
322
    if(this.options.defaultParams) 
 
323
      this.options.parameters += '&' + this.options.defaultParams;
 
324
 
325
    new Ajax.Request(this.url, this.options);
 
326
  },
 
327
 
328
  onComplete: function(request) {
 
329
    this.updateChoices(request.responseText);
 
330
  }
 
331
 
332
});
 
333
 
334
// The local array autocompleter. Used when you'd prefer to
 
335
// inject an array of autocompletion options into the page, rather
 
336
// than sending out Ajax queries, which can be quite slow sometimes.
 
337
//
 
338
// The constructor takes four parameters. The first two are, as usual,
 
339
// the id of the monitored textbox, and id of the autocompletion menu.
 
340
// The third is the array you want to autocomplete from, and the fourth
 
341
// is the options block.
 
342
//
 
343
// Extra local autocompletion options:
 
344
// - choices - How many autocompletion choices to offer
 
345
//
 
346
// - partialSearch - If false, the autocompleter will match entered
 
347
//                    text only at the beginning of strings in the 
 
348
//                    autocomplete array. Defaults to true, which will
 
349
//                    match text at the beginning of any *word* in the
 
350
//                    strings in the autocomplete array. If you want to
 
351
//                    search anywhere in the string, additionally set
 
352
//                    the option fullSearch to true (default: off).
 
353
//
 
354
// - fullSsearch - Search anywhere in autocomplete array strings.
 
355
//
 
356
// - partialChars - How many characters to enter before triggering
 
357
//                   a partial match (unlike minChars, which defines
 
358
//                   how many characters are required to do any match
 
359
//                   at all). Defaults to 2.
 
360
//
 
361
// - ignoreCase - Whether to ignore case when autocompleting.
 
362
//                 Defaults to true.
 
363
//
 
364
// It's possible to pass in a custom function as the 'selector' 
 
365
// option, if you prefer to write your own autocompletion logic.
 
366
// In that case, the other options above will not apply unless
 
367
// you support them.
 
368
 
369
Autocompleter.Local = Class.create();
 
370
Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
 
371
  initialize: function(element, update, array, options) {
 
372
    this.baseInitialize(element, update, options);
 
373
    this.options.array = array;
 
374
  },
 
375
 
376
  getUpdatedChoices: function() {
 
377
    this.updateChoices(this.options.selector(this));
 
378
  },
 
379
 
380
  setOptions: function(options) {
 
381
    this.options = Object.extend({
 
382
      choices: 10,
 
383
      partialSearch: true,
 
384
      partialChars: 2,
 
385
      ignoreCase: true,
 
386
      fullSearch: false,
 
387
      selector: function(instance) {
 
388
        var ret       = []; // Beginning matches
 
389
        var partial   = []; // Inside matches
 
390
        var entry     = instance.getToken();
 
391
        var count     = 0;
 
392
 
393
        for (var i = 0; i < instance.options.array.length &&  
 
394
          ret.length < instance.options.choices ; i++) { 
 
395
 
396
          var elem = instance.options.array[i];
 
397
          var foundPos = instance.options.ignoreCase ? 
 
398
            elem.toLowerCase().indexOf(entry.toLowerCase()) : 
 
399
            elem.indexOf(entry);
 
400
 
401
          while (foundPos != -1) {
 
402
            if (foundPos == 0 && elem.length != entry.length) { 
 
403
              ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" + 
 
404
                elem.substr(entry.length) + "</li>");
 
405
              break;
 
406
            } else if (entry.length >= instance.options.partialChars && 
 
407
              instance.options.partialSearch && foundPos != -1) {
 
408
              if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
 
409
                partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
 
410
                  elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
 
411
                  foundPos + entry.length) + "</li>");
 
412
                break;
 
413
              }
 
414
            }
 
415
 
416
            foundPos = instance.options.ignoreCase ? 
 
417
              elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : 
 
418
              elem.indexOf(entry, foundPos + 1);
 
419
 
420
          }
 
421
        }
 
422
        if (partial.length)
 
423
          ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
 
424
        return "<ul>" + ret.join('') + "</ul>";
 
425
      }
 
426
    }, options || {});
 
427
  }
 
428
});
 
429
 
430
// AJAX in-place editor
 
431
//
 
432
// see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor
 
433
 
434
// Use this if you notice weird scrolling problems on some browsers,
 
435
// the DOM might be a bit confused when this gets called so do this
 
436
// waits 1 ms (with setTimeout) until it does the activation
 
437
Field.scrollFreeActivate = function(field) {
 
438
  setTimeout(function() {
 
439
    Field.activate(field);
 
440
  }, 1);
 
441
}
 
442
 
443
Ajax.InPlaceEditor = Class.create();
 
444
Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99";
 
445
Ajax.InPlaceEditor.prototype = {
 
446
  initialize: function(element, url, options) {
 
447
    this.url = url;
 
448
    this.element = $(element);
 
449
 
450
    this.options = Object.extend({
 
451
      okText: "ok",
 
452
      cancelText: "cancel",
 
453
      savingText: "Saving...",
 
454
      clickToEditText: "Click to edit",
 
455
      okText: "ok",
 
456
      rows: 1,
 
457
      onComplete: function(transport, element) {
 
458
        new Effect.Highlight(element, {startcolor: this.options.highlightcolor});
 
459
      },
 
460
      onFailure: function(transport) {
 
461
        alert("Error communicating with the server: " + transport.responseText.stripTags());
 
462
      },
 
463
      callback: function(form) {
 
464
        return Form.serialize(form);
 
465
      },
 
466
      handleLineBreaks: true,
 
467
      loadingText: 'Loading...',
 
468
      savingClassName: 'inplaceeditor-saving',
 
469
      loadingClassName: 'inplaceeditor-loading',
 
470
      formClassName: 'inplaceeditor-form',
 
471
      highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor,
 
472
      highlightendcolor: "#FFFFFF",
 
473
      externalControl:	null,
 
474
      ajaxOptions: {}
 
475
    }, options || {});
 
476
 
477
    if(!this.options.formId && this.element.id) {
 
478
      this.options.formId = this.element.id + "-inplaceeditor";
 
479
      if ($(this.options.formId)) {
 
480
        // there's already a form with that name, don't specify an id
 
481
        this.options.formId = null;
 
482
      }
 
483
    }
 
484
    
 
485
    if (this.options.externalControl) {
 
486
      this.options.externalControl = $(this.options.externalControl);
 
487
    }
 
488
    
 
489
    this.originalBackground = Element.getStyle(this.element, 'background-color');
 
490
    if (!this.originalBackground) {
 
491
      this.originalBackground = "transparent";
 
492
    }
 
493
    
 
494
    this.element.title = this.options.clickToEditText;
 
495
    
 
496
    this.onclickListener = this.enterEditMode.bindAsEventListener(this);
 
497
    this.mouseoverListener = this.enterHover.bindAsEventListener(this);
 
498
    this.mouseoutListener = this.leaveHover.bindAsEventListener(this);
 
499
    Event.observe(this.element, 'click', this.onclickListener);
 
500
    Event.observe(this.element, 'mouseover', this.mouseoverListener);
 
501
    Event.observe(this.element, 'mouseout', this.mouseoutListener);
 
502
    if (this.options.externalControl) {
 
503
      Event.observe(this.options.externalControl, 'click', this.onclickListener);
 
504
      Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener);
 
505
      Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener);
 
506
    }
 
507
  },
 
508
  enterEditMode: function(evt) {
 
509
    if (this.saving) return;
 
510
    if (this.editing) return;
 
511
    this.editing = true;
 
512
    this.onEnterEditMode();
 
513
    if (this.options.externalControl) {
 
514
      Element.hide(this.options.externalControl);
 
515
    }
 
516
    Element.hide(this.element);
 
517
    this.createForm();
 
518
    this.element.parentNode.insertBefore(this.form, this.element);
 
519
    Field.scrollFreeActivate(this.editField);
 
520
    // stop the event to avoid a page refresh in Safari
 
521
    if (evt) {
 
522
      Event.stop(evt);
 
523
    }
 
524
    return false;
 
525
  },
 
526
  createForm: function() {
 
527
    this.form = document.createElement("form");
 
528
    this.form.id = this.options.formId;
&