Merged tracks-mu-import branch changes r113:130 into the trunk

git-svn-id: http://www.rousette.org.uk/svn/tracks-repos/trunk@131 a4c988fc-2ded-0310-b66e-134b36920a42
This commit is contained in:
nic 2005-08-08 01:54:05 +00:00
parent 2d2f9fcca8
commit 91641500a7
75 changed files with 4054 additions and 1375 deletions

View file

@ -1,3 +1,172 @@
*0.13.1* (11 July, 2005)
* Fixed that each request with the WEBrick adapter would open a new database connection #1685 [Sam Stephenson]
* Added support for SQL Server in the database rake tasks #1652 [ken.barker@gmail.com] Note: osql and scptxfr may need to be installed on your development environment. This involves getting the .exes and a .rll (scptxfr) from a production SQL Server (not developer level SQL Server). Add their location to your Environment PATH and you are all set.
* Added a VERSION parameter to the migrate task that allows you to do "rake migrate VERSION=34" to migrate to the 34th version traveling up or down depending on the current version
* Extend Ruby version check to include RUBY_RELEASE_DATE >= '2005-12-25', the final Ruby 1.8.2 release #1674 [court3nay@gmail.com]
* Improved documentation for environment config files #1625 [court3nay@gmail.com]
*0.13.0* (6 July, 2005)
* Changed the default logging level in config/environment.rb to INFO for production (so SQL statements won't be logged)
* Added migration generator: ./script/generate migration add_system_settings
* Added "migrate" as rake task to execute all the pending migrations from db/migrate
* Fixed that model generator would make fixtures plural, even if ActiveRecord::Base.pluralize_table_names was false #1185 [Marcel Molina]
* Added a DOCTYPE of HTML transitional to the HTML files generated by Rails #1124 [Michael Koziarski]
* SIGTERM also gracefully exits dispatch.fcgi. Ignore SIGUSR1 on Windows.
* Add the option to manually manage garbage collection in the FastCGI dispatcher. Set the number of requests between GC runs in your public/dispatch.fcgi [skaes@web.de]
* Allow dynamic application reloading for dispatch.fcgi processes by sending a SIGHUP. If the process is currently handling a request, the request will be allowed to complete first. This allows production fcgi's to be reloaded without having to restart them.
* RailsFCGIHandler (dispatch.fcgi) no longer tries to explicitly flush $stdout (CgiProcess#out always calls flush)
* Fixed rakefile actions against PostgreSQL when the password is all numeric #1462 [michael@schubert.cx]
* ActionMailer::Base subclasses are reloaded with the other rails components #1262
* Made the WEBrick adapter not use a mutex around action performance if ActionController::Base.allow_concurrency is true (default is false)
* Fixed that mailer generator generated fixtures/plural while units expected fixtures/singular #1457 [Scott Barron]
* Added a 'whiny nil' that's aim to ensure that when users pass nil to methods where that isn't appropriate, instead of NoMethodError? and the name of some method used by the framework users will see a message explaining what type of object was expected. Only active in test and development environments by default #1209 [Michael Koziarski]
* Fixed the test_helper.rb to be safe for requiring controllers from multiple spots, like app/controllers/article_controller.rb and app/controllers/admin/article_controller.rb, without reloading the environment twice #1390 [Nicholas Seckar]
* Fixed Webrick to escape + characters in URL's the same way that lighttpd and apache do #1397 [Nicholas Seckar]
* Added -e/--environment option to script/runner #1408 [fbeausoleil@ftml.net]
* Modernize the scaffold generator to use the simplified render and test methods and to change style from @params["id"] to params[:id]. #1367
* Added graceful exit from pressing CTRL-C during the run of the rails command #1150 [Caleb Tennis]
* Allow graceful exits for dispatch.fcgi processes by sending a SIGUSR1. If the process is currently handling a request, the request will be allowed to complete and then will terminate itself. If a request is not being handled, the process is terminated immediately (via #exit). This basically works like restart graceful on Apache. [Jamis Buck]
* Made dispatch.fcgi more robust by catching fluke errors and retrying unless its a permanent condition. [Jamis Buck]
* Added console --profile for profiling an IRB session #1154 [Jeremy Kemper]
* Changed console_sandbox into console --sandbox #1154 [Jeremy Kemper]
*0.12.1* (20th April, 2005)
* Upgraded to Active Record 1.10.1, Action Pack 1.8.1, Action Mailer 0.9.1, Action Web Service 0.7.1
*0.12.0* (19th April, 2005)
* Fixed that purge_test_database would use database settings from the development environment when recreating the test database #1122 [rails@cogentdude.com]
* Added script/benchmarker to easily benchmark one or more statement a number of times from within the environment. Examples:
# runs the one statement 10 times
script/benchmarker 10 'Person.expensive_method(10)'
# pits the two statements against each other with 50 runs each
script/benchmarker 50 'Person.expensive_method(10)' 'Person.cheap_method(10)'
* Added script/profiler to easily profile a single statement from within the environment. Examples:
script/profiler 'Person.expensive_method(10)'
script/profiler 'Person.expensive_method(10)' 10 # runs the statement 10 times
* Added Rake target clear_logs that'll truncate all the *.log files in log/ to zero #1079 [Lucas Carlson]
* Added lazy typing for generate, such that ./script/generate cn == ./script/generate controller and the likes #1051 [k@v2studio.com]
* Fixed that ownership is brought over in pg_dump during tests for PostgreSQL #1060 [pburleson@gmail.com]
* Upgraded to Active Record 1.10.0, Action Pack 1.8.0, Action Mailer 0.9.0, Action Web Service 0.7.0, Active Support 1.0.4
*0.11.1* (27th March, 2005)
* Fixed the dispatch.fcgi use of a logger
* Upgraded to Active Record 1.9.1, Action Pack 1.7.0, Action Mailer 0.8.1, Action Web Service 0.6.2, Active Support 1.0.3
*0.11.0* (22th March, 2005)
* Removed SCRIPT_NAME from the WEBrick environment to prevent conflicts with PATH_INFO #896 [Nicholas Seckar]
* Removed ?$1 from the dispatch.f/cgi redirect line to get rid of 'complete/path/from/request.html' => nil being in the @params now that the ENV["REQUEST_URI"] is used to determine the path #895 [dblack/Nicholas Seckar]
* Added additional error handling to the FastCGI dispatcher to catch even errors taking down the entire process
* Improved the generated scaffold code a lot to take advantage of recent Rails developments #882 [Tobias Luetke]
* Combined the script/environment.rb used for gems and regular files version. If vendor/rails/* has all the frameworks, then files version is used, otherwise gems #878 [Nicholas Seckar]
* Changed .htaccess to allow dispatch.* to be called from a sub-directory as part of the push with Action Pack to make Rails work on non-vhost setups #826 [Nicholas Seckar/Tobias Luetke]
* Added script/runner which can be used to run code inside the environment by eval'ing the first parameter. Examples:
./script/runner 'ReminderService.deliver'
./script/runner 'Mailer.receive(STDIN.read)'
This makes it easier to do CRON and postfix scripts without actually making a script just to trigger 1 line of code.
* Fixed webrick_server cookie handling to allow multiple cookes to be set at once #800, #813 [dave@cherryville.org]
* Fixed the Rakefile's interaction with postgresql to:
1. Use PGPASSWORD and PGHOST in the environment to fix prompting for
passwords when connecting to a remote db and local socket connections.
2. Add a '-x' flag to pg_dump which stops it dumping privileges #807 [rasputnik]
3. Quote the user name and use template0 when dumping so the functions doesn't get dumped too #855 [pburleson]
4. Use the port if available #875 [madrobby]
* Upgraded to Active Record 1.9.0, Action Pack 1.6.0, Action Mailer 0.8.0, Action Web Service 0.6.1, Active Support 1.0.2
*0.10.1* (7th March, 2005)
* Fixed rake stats to ignore editor backup files like model.rb~ #791 [skanthak]
* Added exception shallowing if the DRb server can't be started (not worth making a fuss about to distract new users) #779 [Tobias Luetke]
* Added an empty favicon.ico file to the public directory of new applications (so the logs are not spammed by its absence)
* Fixed that scaffold generator new template should use local variable instead of instance variable #778 [Dan Peterson]
* Allow unit tests to run on a remote server for PostgreSQL #781 [adamm@galacticasoftware.com]
* Added web_service generator (run ./script/generate web_service for help) #776 [Leon Bredt]
* Added app/apis and components to code statistics report #729 [Scott Barron]
* Fixed WEBrick server to use ABSOLUTE_RAILS_ROOT instead of working_directory #687 [Nicholas Seckar]
* Fixed rails_generator to be usable without RubyGems #686 [Cristi BALAN]
* Fixed -h/--help for generate and destroy generators #331
* Added begin/rescue around the FCGI dispatcher so no uncaught exceptions can bubble up to kill the process (logs to log/fastcgi.crash.log)
* Fixed that association#count would produce invalid sql when called sequentialy #659 [kanis@comcard.de]
* Fixed test/mocks/testing to the correct test/mocks/test #740
* Added early failure if the Ruby version isn't 1.8.2 or above #735
* Removed the obsolete -i/--index option from the WEBrick servlet #743
* Upgraded to Active Record 1.8.0, Action Pack 1.5.1, Action Mailer 0.7.1, Action Web Service 0.6.0, Active Support 1.0.1
*0.10.0* (24th February, 2005)
* Changed default IP binding for WEBrick from 127.0.0.1 to 0.0.0.0 so that the server is accessible both locally and remotely #696 [Marcel]
@ -25,17 +194,17 @@
Views : components/list/items/show.rhtml
* Added --sandbox option to script/console that'll roll back all changes made to the database when you quit #672 [bitsweat]
* Added --sandbox option to script/console that'll roll back all changes made to the database when you quit #672 [Jeremy Kemper]
* Added 'recent' as a rake target that'll run tests for files that changed in the last 10 minutes #612 [bitsweat]
* Added 'recent' as a rake target that'll run tests for files that changed in the last 10 minutes #612 [Jeremy Kemper]
* Changed script/console to default to development environment and drop --no-inspect #650 [bitsweat]
* Changed script/console to default to development environment and drop --no-inspect #650 [Jeremy Kemper]
* Added that the 'fixture :posts' syntax can be used for has_and_belongs_to_many fixtures where a model doesn't exist #572 [bitsweat]
* Added that the 'fixture :posts' syntax can be used for has_and_belongs_to_many fixtures where a model doesn't exist #572 [Jeremy Kemper]
* Added that running test_units and test_functional now performs the clone_structure_to_test as well #566 [rasputnik]
* Added new generator framework that informs about its doings on generation and enables updating and destruction of generated artifacts. See the new script/destroy and script/update for more details #487 [bitsweat]
* Added new generator framework that informs about its doings on generation and enables updating and destruction of generated artifacts. See the new script/destroy and script/update for more details #487 [Jeremy Kemper]
* Added Action Web Service as a new add-on framework for Action Pack [Leon Bredt]
@ -239,7 +408,7 @@
Nothing changes inside the files themselves.
* Fixed a few references in the tests generated by new_mailer [bitsweat]
* Fixed a few references in the tests generated by new_mailer [Jeremy Kemper]
* Added support for mocks in testing with test/mocks
@ -248,7 +417,7 @@
*0.8.5* (9)
* Made dev-util available to all tests, so you can insert breakpoints in any test case to get an IRB prompt at that point [bitsweat]:
* Made dev-util available to all tests, so you can insert breakpoints in any test case to get an IRB prompt at that point [Jeremy Kemper]:
def test_complex_stuff
@david.projects << @new_project
@ -257,11 +426,11 @@
You need to install dev-utils yourself for this to work ("gem install dev-util").
* Added shared generator behavior so future upgrades should be possible without manually copying over files [bitsweat]
* Added shared generator behavior so future upgrades should be possible without manually copying over files [Jeremy Kemper]
* Added the new helper style to both controller and helper templates [bitsweat]
* Added the new helper style to both controller and helper templates [Jeremy Kemper]
* Added new_crud generator for creating a model and controller at the same time with explicit scaffolding [bitsweat]
* Added new_crud generator for creating a model and controller at the same time with explicit scaffolding [Jeremy Kemper]
* Added configuration of Test::Unit::TestCase.fixture_path to test_helper to concide with the new AR fixtures style

View file

@ -16,7 +16,7 @@ task :environment do
end
end
desc "Generate API documentatio, show coding stats"
desc "Generate API documentation, show coding stats"
task :doc => [ :appdoc, :stats ]
@ -65,39 +65,53 @@ Rake::RDocTask.new("appdoc") { |rdoc|
rdoc.title = "Tracks Documentation"
rdoc.options << '--line-numbers --inline-source'
rdoc.rdoc_files.include('doc/README_FOR_APP')
rdoc.rdoc_files.include('doc/CHANGELOG')
rdoc.rdoc_files.include('app/**/*.rb')
}
desc "Generate documentation for the Rails framework"
Rake::RDocTask.new("apidoc") { |rdoc|
rdoc.rdoc_dir = 'doc/api'
rdoc.template = "#{ENV['template']}.rb" if ENV['template']
rdoc.title = "Rails Framework Documentation"
rdoc.options << '--line-numbers --inline-source'
rdoc.rdoc_files.include('README')
rdoc.rdoc_files.include('CHANGELOG')
rdoc.rdoc_files.include('vendor/railties/lib/breakpoint.rb')
rdoc.rdoc_files.include('vendor/railties/CHANGELOG')
rdoc.rdoc_files.include('vendor/railties/MIT-LICENSE')
rdoc.rdoc_files.include('vendor/activerecord/README')
rdoc.rdoc_files.include('vendor/activerecord/CHANGELOG')
rdoc.rdoc_files.include('vendor/activerecord/lib/active_record/**/*.rb')
rdoc.rdoc_files.exclude('vendor/activerecord/lib/active_record/vendor/*')
rdoc.rdoc_files.include('vendor/actionpack/README')
rdoc.rdoc_files.include('vendor/actionpack/CHANGELOG')
rdoc.rdoc_files.include('vendor/actionpack/lib/action_controller/**/*.rb')
rdoc.rdoc_files.include('vendor/actionpack/lib/action_view/**/*.rb')
rdoc.rdoc_files.include('vendor/actionmailer/README')
rdoc.rdoc_files.include('vendor/actionmailer/CHANGELOG')
rdoc.rdoc_files.include('vendor/actionmailer/lib/action_mailer/base.rb')
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 "Report code statistics (KLOCs, etc) from the application"
task :stats do
task :stats => [ :environment ] do
require 'code_statistics'
CodeStatistics.new(
["Helpers", "app/helpers"],
["Controllers", "app/controllers"],
["APIs", "app/apis"],
["Components", "components"],
["Functionals", "test/functional"],
["Models", "app/models"],
["Units", "test/unit"]
@ -114,10 +128,15 @@ task :clone_structure_to_test => [ :db_structure_dump, :purge_test_database ] do
IO.readlines("db/#{RAILS_ENV}_structure.sql").join.split("\n\n").each do |table|
ActiveRecord::Base.connection.execute(table)
end
when "postgresql"
`psql -U #{abcs["test"]["username"]} -f db/#{RAILS_ENV}_structure.sql #{abcs["test"]["database"]}`
when "postgresql"
ENV['PGHOST'] = abcs["test"]["host"] if abcs["test"]["host"]
ENV['PGPORT'] = abcs["test"]["port"].to_s if abcs["test"]["port"]
ENV['PGPASSWORD'] = abcs["test"]["password"].to_s if abcs["test"]["password"]
`psql -U "#{abcs["test"]["username"]}" -f db/#{RAILS_ENV}_structure.sql #{abcs["test"]["database"]}`
when "sqlite", "sqlite3"
`#{abcs[RAILS_ENV]["adapter"]} #{abcs["test"]["dbfile"]} < db/#{RAILS_ENV}_structure.sql`
when "sqlserver"
`osql -E -S #{abcs["test"]["host"]} -d #{abcs["test"]["database"]} -i db\\#{RAILS_ENV}_structure.sql`
else
raise "Unknown database adapter '#{abcs["test"]["adapter"]}'"
end
@ -130,10 +149,16 @@ task :db_structure_dump => :environment do
when "mysql"
ActiveRecord::Base.establish_connection(abcs[RAILS_ENV])
File.open("db/#{RAILS_ENV}_structure.sql", "w+") { |f| f << ActiveRecord::Base.connection.structure_dump }
when "postgresql"
`pg_dump -U #{abcs[RAILS_ENV]["username"]} -s -f db/#{RAILS_ENV}_structure.sql #{abcs[RAILS_ENV]["database"]}`
when "postgresql"
ENV['PGHOST'] = abcs[RAILS_ENV]["host"] if abcs[RAILS_ENV]["host"]
ENV['PGPORT'] = abcs[RAILS_ENV]["port"].to_s if abcs[RAILS_ENV]["port"]
ENV['PGPASSWORD'] = abcs[RAILS_ENV]["password"].to_s if abcs[RAILS_ENV]["password"]
`pg_dump -U "#{abcs[RAILS_ENV]["username"]}" -s -x -O -f db/#{RAILS_ENV}_structure.sql #{abcs[RAILS_ENV]["database"]}`
when "sqlite", "sqlite3"
`#{abcs[RAILS_ENV]["adapter"]} #{abcs[RAILS_ENV]["dbfile"]} .schema > db/#{RAILS_ENV}_structure.sql`
when "sqlserver"
`scptxfr /s #{abcs[RAILS_ENV]["host"]} /d #{abcs[RAILS_ENV]["database"]} /I /f db\\#{RAILS_ENV}_structure.sql /q /A /r`
`scptxfr /s #{abcs[RAILS_ENV]["host"]} /d #{abcs[RAILS_ENV]["database"]} /I /F db\ /q /A /r`
else
raise "Unknown database adapter '#{abcs["test"]["adapter"]}'"
end
@ -144,14 +169,34 @@ task :purge_test_database => :environment do
abcs = ActiveRecord::Base.configurations
case abcs["test"]["adapter"]
when "mysql"
ActiveRecord::Base.establish_connection(abcs[RAILS_ENV])
ActiveRecord::Base.establish_connection(:test)
ActiveRecord::Base.connection.recreate_database(abcs["test"]["database"])
when "postgresql"
`dropdb -U #{abcs["test"]["username"]} #{abcs["test"]["database"]}`
`createdb -U #{abcs["test"]["username"]} #{abcs["test"]["database"]}`
ENV['PGHOST'] = abcs["test"]["host"] if abcs["test"]["host"]
ENV['PGPORT'] = abcs["test"]["port"].to_s if abcs["test"]["port"]
ENV['PGPASSWORD'] = abcs["test"]["password"].to_s if abcs["test"]["password"]
`dropdb -U "#{abcs["test"]["username"]}" #{abcs["test"]["database"]}`
`createdb -T template0 -U "#{abcs["test"]["username"]}" #{abcs["test"]["database"]}`
when "sqlite","sqlite3"
File.delete(abcs["test"]["dbfile"]) if File.exist?(abcs["test"]["dbfile"])
when "sqlserver"
dropfkscript = "#{abcs["test"]["host"]}.#{abcs["test"]["database"]}.DP1".gsub(/\\/,'-')
`osql -E -S #{abcs["test"]["host"]} -d #{abcs["test"]["database"]} -i db\\#{dropfkscript}`
`osql -E -S #{abcs["test"]["host"]} -d #{abcs["test"]["database"]} -i db\\#{RAILS_ENV}_structure.sql`
else
raise "Unknown database adapter '#{abcs["test"]["adapter"]}'"
end
end
desc "Clears all *.log files in log/"
task :clear_logs => :environment do
FileList["log/*.log"].each do |log_file|
f = File.open(log_file, "w")
f.close
end
end
desc "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"
task :migrate => :environment do
ActiveRecord::Migrator.migrate(File.dirname(__FILE__) + '/db/migrate/', ENV["VERSION"] ? ENV["VERSION"].to_i : nil)
end

View file

@ -6,17 +6,19 @@ require_dependency "login_system"
require 'date'
class ApplicationController < ActionController::Base
helper :application
include LoginSystem
before_filter :set_session_expiration
# Contstants from settings.yml
#
DATE_FORMAT = app_configurations["formats"]["date"]
WEEK_STARTS_ON = app_configurations["formats"]["week_starts"]
NO_OF_ACTIONS = app_configurations["formats"]["hp_completed"]
STALENESS_STARTS = app_configurations["formats"]["staleness_starts"]
# Count the number of uncompleted actions, excluding those in hidden contexts
#
def count_shown_items(hidden)
@ -33,11 +35,29 @@ class ApplicationController < ActionController::Base
def errors_for( obj )
error_messages_for( obj ) unless instance_eval("@#{obj}").nil?
end
# Reverses the urlize() method by substituting underscores for spaces
#
def deurlize(name)
name.to_s.gsub(/_/, " ")
end
end
def set_session_expiration
# http://wiki.rubyonrails.com/rails/show/HowtoChangeSessionOptions
return if @controller_name == 'feed'
# If no session we don't care
if @session
# Get expiry time (allow ten seconds window for the case where we have none)
expiry_time = @session['expiry_time'] || Time.now + 10
if expiry_time < Time.now
# Too late, matey... bang goes your session!
reset_session
else
# Okay, you get another hour
@session['expiry_time'] = Time.now + (60*60)
end
end
end
end

View file

@ -1,9 +1,9 @@
class ContextController < ApplicationController
helper :context
model :project
model :todo
before_filter :login_required
layout "standard"
@ -16,160 +16,124 @@ class ContextController < ApplicationController
# Set page title, and collect existing contexts in @contexts
#
def list
self.init
@page_title = "TRACKS::List Contexts"
@contexts = Context.find(:all, :conditions => nil, :order => "position ASC", :limit => nil )
end
# Filter the projects to show just the one passed in the URL
# e.g. <home>/project/show/<project_name> shows just <project_name>.
#
def show
@context = Context.find_by_name(deurlize(@params["name"]))
@places = Context.find(:all, :order => "position ASC")
@projects = Project.find( :all, :conditions => "done=0", :order => "position ASC" )
self.init
self.init_todos
self.check_user_set_context
@page_title = "TRACKS::Context: #{@context.name}"
@not_done = Todo.find(:all, :conditions => "done=0 AND context_id=#{@context.id}",
:order => "due IS NULL, due ASC, created ASC")
@done = Todo.find(:all, :conditions => "done=1 AND context_id=#{@context.id}",
:order => "completed DESC")
@count = Todo.count( "context_id=#{@context.id} AND done=0" )
end
end
# Creates a new context via Ajax helpers
#
def new_context
@context = Context.new(@params['context'])
if @context.save
render_partial( 'context_listing', @context )
context = @session['user'].contexts.build
context.attributes = @params['context']
context.name = deurlize(context.name)
if context.save
render :partial => 'context_listing', :locals => { :context_listing => context }
else
flash["warning"] = "Couldn't add new context"
render_text "#{flash["warning"]}"
render :text => "#{flash["warning"]}"
end
end
# Edit the details of the context
#
#
def update
context = Context.find(params[:id])
context.attributes = @params["context"]
if context.save
render_partial 'context_listing', context
check_user_set_context
@context.attributes = @params["context"]
@context.name = deurlize(@context.name)
if @context.save
render_partial 'context_listing', @context
else
flash["warning"] = "Couldn't update new context"
render_text ""
render :text => ""
end
end
# Edit the details of the action in this context
#
def update_action
@places = Context.find(:all, :order => "position ASC")
@projects = Project.find( :all, :conditions => "done=0", :order => "position ASC" )
action = Todo.find(params[:id])
action.attributes = @params["item"]
if action.due?
action.due = Date.strptime(@params["item"]["due"], DATE_FORMAT)
else
action.due = ""
end
if action.save
render_partial 'show_items', action
else
flash["warning"] = "Couldn't update the action"
render_text ""
end
end
# Called by a form button
# Parameters from form fields are passed to create new action
#
def add_item
@projects = Project.find( :all, :conditions => "done=0", :order => "position ASC" )
@places = Context.find( :all, :order => "position ASC" )
item = Todo.new
item.attributes = @params["new_item"]
if item.due?
item.due = Date.strptime(@params["new_item"]["due"], DATE_FORMAT)
else
item.due = ""
end
if item.save
render_partial 'show_items', item
else
flash["warning"] = "Couldn't add next action \"#{item.description}\""
render_text ""
end
end
# Fairly self-explanatory; deletes the context
# If the context contains actions, you'll get a warning dialogue.
# If you choose to go ahead, any actions in the context will also be deleted.
def destroy
this_context = Context.find(params[:id])
if this_context.destroy
def destroy
check_user_set_context
if @context.destroy
render_text ""
else
flash["warning"] = "Couldn't delete context \"#{context.name}\""
flash["warning"] = "Couldn't delete context \"#{@context.name}\""
redirect_to( :controller => "context", :action => "list" )
end
end
# Delete a next action in a context
#
def destroy_action
item = Todo.find(params[:id])
if item.destroy
render_text ""
else
flash["warning"] = "Couldn't delete next action \"#{item.description}\""
redirect_to :action => "list"
end
end
# Toggles the 'done' status of the action
#
def toggle_check
@places = Context.find(:all, :order => "position ASC")
@projects = Project.find( :all, :conditions => "done=0", :order => "position ASC" )
item = Todo.find(params[:id])
item.toggle!('done')
render_partial 'show_items', item
end
# Methods for changing the sort order of the contexts in the list
#
def move_up
line = Context.find(params[:id])
line.move_higher
line.save
check_user_set_context
@context.move_higher
@context.save
redirect_to(:controller => "context", :action => "list")
end
def move_down
line = Context.find(params[:id])
line.move_lower
line.save
check_user_set_context
@context.move_lower
@context.save
redirect_to(:controller => "context", :action => "list")
end
def move_top
line = Context.find(params[:id])
line.move_to_top
line.save
check_user_set_context
@context.move_to_top
@context.save
redirect_to(:controller => "context", :action => "list")
end
def move_bottom
line = Context.find(params[:id])
line.move_to_bottom
line.save
check_user_set_context
@context.move_to_bottom
@context.save
redirect_to(:controller => "context", :action => "list" )
end
protected
def check_user_set_context
@user = @session['user']
if @params["name"]
@context = Context.find_by_name_and_user_id(deurlize(@params["name"]), @user.id)
elsif @params['id']
@context = Context.find_by_id_and_user_id(@params["id"], @user.id)
else
redirect_to(:controller => "context", :action => "list" )
end
if @user == @context.user
return @context
else
@context = nil # Should be nil anyway.
flash["warning"] = "Item and session user mis-match: #{@context.user_id} and #{@session['user'].id}!"
render_text ""
end
end
def init
@user = @session['user']
@projects = @user.projects.collect { |x| x.done? ? nil:x }.compact
@contexts = @user.contexts
end
def init_todos
check_user_set_context
@done = @context.find_done_todos
@not_done = @context.find_not_done_todos
@count = @not_done.size
end
end

View file

@ -1,31 +1,24 @@
# Produces an feeds of the next actions, both RSS and plain text
#
class FeedController < ApplicationController
helper :feed
model :todo, :context, :project
before_filter :check_token_against_user_word
def index
end
# Builds an RSS feed for the latest 15 items
# This is fairly basic: it lists the action description as the title
# and the item context as the description
#
def na_feed
# Check whether the token in the URL matches the word in the User's table
# Render the RSS feed if it is, or show an error message if not
@token = @params['token']
@user_name = @params['name']
@current_user = User.find_by_login(@user_name)
if (@token == @current_user.word && @user_name == @current_user.login)
@not_done = Todo.find_all( "done=0", "created DESC" )
@headers["Content-Type"] = "text/xml; charset=utf-8"
else
render_text "Sorry, you don't have permission to view this page."
end
@not_done = @user.todos.collect { |x| x.done? ? nil:x }.compact.sort! {|x,y| y.created_at <=> x.created_at }
@headers["Content-Type"] = "text/xml; charset=utf-8"
end
# Builds a plain text page listing all the next actions,
# sorted by context. Showing notes doesn't make much sense here
# so they are omitted. You can use this with GeekTool to get your next actions
@ -33,19 +26,20 @@ class FeedController < ApplicationController
# curl [url from "TXT" link on todo/list]
#
def na_text
# Check whether the token in the URL matches the word in the User's table
# Render the text file if it is, or show an error message if not
@token = @params['token']
@user_name = @params['name']
@current_user = User.find_by_login(@user_name)
if (@token == @current_user.word && @user_name == @current_user.login)
@places = Context.find_all
@projects = Project.find_all
@not_done = Todo.find_all( "done=0", "context_id ASC" )
@headers["Content-Type"] = "text/plain; charset=utf-8"
else
render_text "Sorry, you don't have permission to view this page."
@contexts = @user.contexts
@not_done = @user.todos.collect { |x| x.done? ? nil:x }.compact.sort! {|x,y| x.context_id <=> y.context_id }
@headers["Content-Type"] = "text/plain; charset=utf-8"
end
protected
# Check whether the token in the URL matches the word in the User's table
def check_token_against_user_word
@user = User.find_by_login( @params['name'] )
unless ( @params['token'] == @user.word)
render :text => "Sorry, you don't have permission to view this page."
return false
end
end
end

View file

@ -3,10 +3,10 @@ class LoginController < ApplicationController
layout 'scaffold'
def login
@page_title = "Login"
case @request.method
when :post
if @session['user'] = User.authenticate(@params['user_login'], @params['user_password'])
flash['notice'] = "Login successful"
redirect_back_or_default :controller => "todo", :action => "list"
else
@ -15,35 +15,55 @@ class LoginController < ApplicationController
end
end
end
def signup
case @request.method
when :post
@user = User.new(@params['user'])
if @user.save
@session['user'] = User.authenticate(@user.login, @params['user']['password'])
flash['notice'] = "Signup successful"
redirect_back_or_default :controller => "todo", :action => "list"
end
when :get
@user = User.new
end
end
unless (User.find_all.empty? || ( @session['user'] && @session['user']['is_admin'] ) )
@page_title = "No signups"
render :action => "nosignup"
return
end
@signupname = User.find_all.empty? ? "as the admin":"a new"
@page_title = "Sign up #{@signupname} user"
if @session['new_user']
@user = @session['new_user']
@session['new_user'] = nil
else
@user = User.new
end
end
def create
user = User.new(@params['user'])
unless user.valid?
@session['new_user'] = user
redirect_to :controller => 'login', :action => 'signup'
return
end
user.is_admin = 1 if User.find_all.empty?
if user.save
@session['user'] = User.authenticate(user.login, @params['user']['password'])
flash['notice'] = "Signup successful"
redirect_back_or_default :controller => "todo", :action => "list"
end
end
def delete
if @params['id']
if @params['id'] and ( @params['id'] = @session['user'].id or @session['user'].is_admin )
@user = User.find(@params['id'])
# TODO: Maybe it would be better to mark deleted. That way user deletes can be reversed.
@user.destroy
end
redirect_back_or_default :controller => "todo", :action => "list"
end
end
def logout
@session['user'] = nil
reset_session
end
def welcome
end
end

View file

@ -1,22 +1,26 @@
class NoteController < ApplicationController
model :user
before_filter :login_required
layout "standard"
def index
@all_notes = Note.list_all
@user = @session['user']
@all_notes = @user.notes
@page_title = "TRACKS::All notes"
end
def show
@note = Note.find(@params[:id])
@note = check_user_return_note
@page_title = "TRACKS::Note " + @note.id.to_s
end
# Add a new note to this project
#
def add_note
note = Note.new
def add
@user = @session['user']
note = @user.notes.build
note.attributes = @params["new_note"]
if note.save
@ -25,9 +29,9 @@ class NoteController < ApplicationController
render_text ""
end
end
def destroy_note
note = Note.find_by_id(@params[:id])
def delete
note = check_user_return_note
if note.destroy
render_text ""
else
@ -35,9 +39,9 @@ class NoteController < ApplicationController
render_text ""
end
end
def update_note
note = Note.find_by_id(@params[:id])
def update
note = check_user_return_note
note.attributes = @params["note"]
if note.save
render_partial 'notes', note
@ -46,5 +50,15 @@ class NoteController < ApplicationController
render_text ""
end
end
protected
def check_user_return_note
note = Note.find_by_id( @params['id'] )
if @session['user'] == note.user
return note
else
render_text ""
end
end
end

View file

@ -1,13 +1,13 @@
class ProjectController < ApplicationController
helper :project
model :context
model :todo
before_filter :login_required
layout "standard"
def index
def index
list
render_action "list"
end
@ -16,161 +16,121 @@ class ProjectController < ApplicationController
# Set page title, and collect existing projects in @projects
#
def list
init
@page_title = "TRACKS::List Projects"
@projects = Project.find( :all, :order => "position ASC" )
end
# Filter the projects to show just the one passed in the URL
# e.g. <home>/project/show/<project_name> shows just <project_name>.
#
def show
@project = Project.find_by_name(deurlize(@params["name"]))
@places = Context.find(:all, :order => "position ASC")
@projects = Project.find( :all, :conditions => "done=0", :order => "position ASC" )
init
init_todos
@notes = @project.notes
@page_title = "TRACKS::Project: #{@project.name}"
@not_done = Todo.find(:all, :conditions => "done=0 AND project_id=#{@project.id}",
:order => "due IS NULL, due ASC, created ASC")
@done = Todo.find(:all, :conditions => "done=1 AND project_id=#{@project.id}",
:order => "completed DESC")
@count = @not_done.length
end
def new_project
@project = Project.new(@params['project'])
if @project.save
render_partial( 'project_listing', @project )
else
flash["warning"] = "Couldn't update new project"
render_text ""
end
end
# Edit the details of the project
#
def update
project = Project.find(params[:id])
project.attributes = @params["project"]
project = @session['user'].projects.build
project.attributes = @params['project']
project.name = deurlize(project.name)
if project.save
render_partial 'project_listing', project
render :partial => 'project_listing', :locals => { :project_listing => project }
else
flash["warning"] = "Couldn't update new project"
render :text => ""
end
end
# Edit the details of the project
#
def update
check_user_set_project
@project.attributes = @params["project"]
@project.name = deurlize(@project.name)
if @project.save
render_partial 'project_listing', @project
else
flash["warning"] = "Couldn't update project"
render_text ""
end
end
# Edit the details of the action in this project
#
def update_action
@places = Context.find(:all, :order => "position ASC")
@projects = Project.find( :all, :conditions => "done=0", :order => "position ASC" )
action = Todo.find(params[:id])
action.attributes = @params["item"]
if action.due?
action.due = Date.strptime(@params["item"]["due"], DATE_FORMAT)
else
action.due = ""
end
if action.save
render_partial 'show_items', action
else
flash["warning"] = "Couldn't update the action"
render_text ""
end
end
# Called by a form button
# Parameters from form fields are passed to create new action
#
def add_item
@projects = Project.find( :all, :conditions => "done=0", :order => "position ASC" )
@places = Context.find( :all, :order => "position ASC" )
item = Todo.new
item.attributes = @params["new_item"]
if item.due?
item.due = Date.strptime(@params["new_item"]["due"], DATE_FORMAT)
else
item.due = ""
end
if item.save
render_partial 'show_items', item
else
flash["warning"] = "Couldn't add next action \"#{item.description}\""
render_text ""
end
end
# Delete a project
#
def destroy
this_project = Project.find( @params['id'] )
if this_project.destroy
check_user_set_project
if @project.destroy
render_text ""
else
flash["warning"] = "Couldn't delete project \"#{project.name}\""
flash["warning"] = "Couldn't delete project \"#{@project.name}\""
redirect_to( :controller => "project", :action => "list" )
end
end
# Delete a next action in a project
#
def destroy_action
item = Todo.find(@params['id'])
if item.destroy
#flash["confirmation"] = "Next action \"#{item.description}\" was successfully deleted"
render_text ""
else
flash["warning"] = "Couldn't delete next action \"#{item.description}\""
redirect_to :action => "list"
end
end
# Toggles the 'done' status of the action
#
def toggle_check
@places = Context.find(:all, :order => "position ASC")
@projects = Project.find( :all, :conditions => "done=0", :order => "position ASC" )
item = Todo.find(@params['id'])
item.toggle!('done')
render_partial 'show_items', item
end
# Methods for changing the sort order of the projects in the list
#
def move_up
line = Project.find(params[:id])
line.move_higher
line.save
check_user_set_project
@project.move_higher
@project.save
redirect_to(:controller => "project", :action => "list")
end
def move_down
line = Project.find(params[:id])
line.move_lower
line.save
check_user_set_project
@project.move_lower
@project.save
redirect_to(:controller => "project", :action => "list")
end
def move_top
line = Project.find(params[:id])
line.move_to_top
line.save
check_user_set_project
@project.move_to_top
@project.save
redirect_to(:controller => "project", :action => "list")
end
def move_bottom
line = Project.find(params[:id])
line.move_to_bottom
line.save
check_user_set_project
@project.move_to_bottom
@project.save
redirect_to(:controller => "project", :action => "list" )
end
protected
def check_user_set_project
@user = @session['user']
if @params["name"]
@project = Project.find_by_name_and_user_id(deurlize(@params["name"]), @user.id)
elsif @params['id']
@project = Project.find_by_id_and_user_id(@params["id"], @user.id)
else
redirect_to(:controller => "project", :action => "list" )
end
if @user == @project.user
return @project
else
@project = nil # Should be nil anyway
flash["warning"] = "Project and session user mis-match: #{@project.user_id} and #{@session['user'].id}!"
render_text ""
end
end
def init
@user = @session['user']
@projects = @user.projects
@contexts = @user.contexts
end
def init_todos
check_user_set_project
@done = @project.find_done_todos
@not_done = @project.find_not_done_todos
@count = @not_done.size
end
end

View file

@ -1,11 +1,12 @@
class TodoController < ApplicationController
helper :todo
model :context, :project
model :context, :project, :user
before_filter :login_required
layout "standard"
def index
list
render_action "list"
@ -16,106 +17,93 @@ class TodoController < ApplicationController
# Number of completed actions to show is determined by a setting in settings.yml
#
def list
self.init
@page_title = "TRACKS::List tasks"
@projects = Project.find( :all, :conditions => "done=0", :order => "position ASC" )
@places = Context.find( :all, :order => "position ASC" )
@shown_places = Context.find( :all, :conditions => "hide=0", :order => "position ASC" )
@hidden_places = Context.find( :all, :conditions => "hide=1", :order => "position ASC" )
@done = Todo.find( :all, :conditions => "done=1", :order => "completed DESC",
:limit => NO_OF_ACTIONS )
# Set count badge to number of not-done, not hidden context items
@count = count_shown_items( @hidden_places )
end
@done = @done[0..(NO_OF_ACTIONS-1)]
# Set count badge to number of not-done, not hidden context items
@count = @todos.collect { |x| ( !x.done? and !x.context.hidden? ) ? x:nil }.compact.size
end
# List the completed tasks, sorted by completion date
#
# Use days declaration? 1.day.ago?
def completed
self.init
@page_title = "TRACKS::Completed tasks"
today_date = Date::today() - 1
today_query = today_date.strftime("'%Y-%m-%d'") + " <= completed"
week_begin = Date::today() - 2
week_end = Date::today() - 7
day = (60 * 60 * 24)
today = Time.now
week_query = week_begin.strftime("'%Y-%m-%d'") + " >= completed
AND " + week_end.strftime("'%Y-%m-%d'") + " <= completed"
today_date = today - 1 * day
week_begin = today - 2 * day
week_end = today - 7* day
month_begin = today - 8 * day
month_end = today - 31 * day
month_begin = Date::today() - 8
month_end = Date::today() - 31
@done_today = @done.collect { |x| today_date <= x.completed ? x:nil }.compact
@done_this_week = @done.collect { |x| week_begin >= x.completed && week_end <= x.completed ? x:nil }.compact
@done_this_month = @done.collect { |x| month_begin >= x.completed && month_end <= x.completed ? x:nil }.compact
month_query = month_begin.strftime("'%Y-%m-%d'") + " >= completed
AND " + month_end.strftime("'%Y-%m-%d'") + " <= completed"
@done_today = Todo.find_by_sql( "SELECT * FROM todos WHERE done = 1 AND #{today_query}
ORDER BY completed DESC;" )
@done_this_week = Todo.find_by_sql( "SELECT * FROM todos WHERE done = 1 AND #{week_query}
ORDER BY completed DESC;" )
@done_this_month = Todo.find_by_sql( "SELECT * FROM todos WHERE done = 1 AND #{month_query}
ORDER BY completed DESC;" )
end
# Archived completed items, older than 31 days
#
def completed_archive
self.init
@page_title = "TRACKS::Archived completed tasks"
archive_date = Date::today() - 32
archive_query = archive_date.strftime("'%Y-%m-%d'") + " >= completed"
@done_archive = Todo.find_by_sql( "SELECT * FROM todos WHERE done = 1 AND #{archive_query}
ORDER BY completed DESC;" )
archive_date = Time.now - 32 * (60 * 60 * 24)
@done_archive = @done.collect { |x| archive_date >= x.completed ? x:nil }.compact
end
# Called by a form button
# Parameters from form fields are passed to create new action
# in the selected context.
def add_item
@projects = Project.find( :all, :conditions => "done=0", :order => "position ASC" )
@places = Context.find( :all, :order => "position ASC" )
item = Todo.new
self.init
item = @user.todos.build
item.attributes = @params["new_item"]
if item.due?
item.due = Date.strptime(@params["new_item"]["due"], DATE_FORMAT)
else
item.due = ""
end
if item.save
render_partial 'show_items', item
else
flash["warning"] = "Couldn't add next action \"#{item.description}\""
render_text ""
end
end
# Edit the details of an action
#
def update_action
@places = Context.find(:all, :order => "position ASC")
@projects = Project.find( :all, :conditions => "done=0", :order => "position ASC" )
action = Todo.find(params[:id])
action.attributes = @params["item"]
if action.due?
action.due = Date.strptime(@params["item"]["due"], DATE_FORMAT)
if item.save
render :partial => 'item', :object => item
else
action.due = ""
flash["warning"] = "Couldn't add next action \"#{item.description}\""
render_text ""
end
if action.save
render_partial 'show_items', action
end
# Edit the details of an action
#
def update_action
self.init
item = check_user_return_item
item.attributes = @params["item"]
if item.due?
item.due = Date.strptime(@params["item"]["due"], DATE_FORMAT)
else
item.due = ""
end
if item.save
render :partial => 'item', :object => item
else
flash["warning"] = "Couldn't update the action"
render_text ""
end
end
# Delete a next action in a context
#
def destroy_action
item = Todo.find(@params['id'])
item = check_user_return_item
if item.destroy
render_text ""
else
@ -123,17 +111,34 @@ class TodoController < ApplicationController
render_text ""
end
end
# Toggles the 'done' status of the action
#
def toggle_check
@projects = Project.find( :all, :conditions => "done=0", :order => "position ASC" )
@places = Context.find(:all, :order => "position ASC")
item = Todo.find(@params['id'])
def toggle_check
self.init
item = check_user_return_item
item.toggle!('done')
render_partial 'show_items', item
end
render :partial => 'item', :object => item
end
protected
def check_user_return_item
item = Todo.find( @params['id'] )
if @session['user'] == item.user
return item
else
flash["warning"] = "Item and session user mis-match: #{item.user.name} and #{@session['user'].name}!"
render_text ""
end
end
def init
@user = @session['user']
@projects = @user.projects
@contexts = @user.contexts
@todos = @user.todos
@done = @todos.collect { |x| x.done? ? x:nil }.compact.sort! {|x,y| y.completed <=> x.completed }
end
end

View file

@ -1,6 +1,6 @@
# The methods added to this helper will be available to all templates in the application.
module ApplicationHelper
# Convert a date object to the format specified
# in config/settings.yml
#
@ -19,8 +19,7 @@ module ApplicationHelper
def markdown(text)
RedCloth.new(text).to_html
end
# Wraps object in HTML tags, tag
#
def tag_object(object, tag)
@ -32,8 +31,8 @@ module ApplicationHelper
def urlize(name)
name.to_s.gsub(/ /, "_")
end
# Check due date in comparison to today's date
# Flag up date appropriately with a 'traffic light' colour code
#
@ -41,7 +40,7 @@ module ApplicationHelper
if due == nil
return ""
end
@now = Date.today
@days = due-@now
case @days
@ -56,7 +55,7 @@ module ApplicationHelper
"<span class=\"green\">" + format_date(due) + "</span> "
end
end
# Uses the 'staleness_starts' value from settings.yml (in days) to colour
# the background of the action appropriately according to the age
# of the creation date:
@ -64,16 +63,22 @@ module ApplicationHelper
# * l2: created more than 2 x staleness_starts, but < 3 x staleness_starts
# * l3: created more than 3 x staleness_starts
#
def staleness(created)
if created < (ApplicationController::STALENESS_STARTS*3).days.ago
def staleness(item)
if item.created_at < (ApplicationController::STALENESS_STARTS*3).days.ago
return "<div class=\"stale_l3\">"
elsif created < (ApplicationController::STALENESS_STARTS*2).days.ago
elsif item.created_at < (ApplicationController::STALENESS_STARTS*2).days.ago
return "<div class=\"stale_l2\">"
elsif created < (ApplicationController::STALENESS_STARTS).days.ago
elsif item.created_at < (ApplicationController::STALENESS_STARTS).days.ago
return "<div class=\"stale_l1\">"
else
return "<div class=\"description\">"
end
end
def calendar_setup( input_field )
str = "Calendar.setup({ ifFormat:\"#{ApplicationController::DATE_FORMAT}\""
str << ",firstDay:#{ApplicationController::WEEK_STARTS_ON},showOthers:true,range:[2004, 2010]"
str << ",step:1,inputField:\"" + input_field + "\",cache:true,align:\"TR\" })"
javascript_tag str
end
end

View file

@ -1,31 +1,31 @@
module FeedHelper
# Build a nicely formatted text string for display
# Context forms the heading, then the items are
# indented underneath. If there is a due date
# and the item is in a project, these are also displayed
#
def build_text_page(list,contexts,projects)
def build_text_page(list,contexts)
result_string = ""
for @place in @places
result_string << "\n" + @place.name.upcase + ":\n"
for context in contexts
result_string << "\n" + context.name.upcase + ":\n"
list.each do |@item|
if @item.context_id == @place.id
if @item.due
result_string << " [" + format_date(@item.due) + "] "
result_string << @item.description + " "
list.each do |item|
if item.context_id == context.id
if item.due
result_string << " [" + format_date(item.due) + "] "
result_string << item.description + " "
else
result_string << " " + @item.description + " "
result_string << " " + item.description + " "
end
if @item.project_id
result_string << "(" + @item.project['name'] + ")"
if item.project_id
result_string << "(" + item.project.name + ")"
end
result_string << "\n"
end
end
end
return result_string

View file

@ -1,9 +1,69 @@
module TodoHelper
# Counts the number of uncompleted items in the selected context
#
def count_items(context)
count = Todo.find_all("done=0 AND context_id=#{context.id}").length
end
def form_remote_tag_todo_notdone( item )
form_remote_tag( :url => url_for( :controller => "todo", :action => "toggle_check", :id => item.id ),
:html => { :id=> "checkbox-notdone-#{item.id}", :class => "inline-form" },
:update => "completed",
:position => "top",
:loading => "Form.disable('checkbox-notdone-#{item.id}');",
:complete => "new Effect2.Fade('item-#{item.id}-container', true);"
)
end
def form_remote_tag_todo_done( item )
form_remote_tag( :url => url_for( :controller => "todo", :action => "toggle_check", :id => item.id ),
:html => { :id=> "checkbox-done-#{item.id}", :class => "inline-form" },
:update => "new_actions",
:position => "bottom",
:loading => "Form.disable('checkbox-done-#{item.id}');",
:complete => "Element.toggle('new_actions');new Effect2.Fade('done-item-#{item.id}-container', true);"
)
end
def form_remote_tag_edit_todo( item )
form_remote_tag( :url => { :controller => 'todo', :action => 'update_action', :id => item.id },
:html => { :id => "form-action-#{item.id}", :class => "inline-form" },
:update => "item-#{item.id}-container",
:complete => "new Effect.Appear('item-#{item.id}-container');"
)
end
def link_to_remote_todo_notdone( item )
str = "Element.toggle('item-#{item.id}','action-#{item.id}-edit-form');"
str << " new Effect.Appear('action-#{item.id}-edit-form');"
str << " Form.focusFirstElement('form-action-#{item.id}')"
link_to_remote( image_tag("blank", :title =>"Delete action", :class=>"delete_item"),
:update => "item-#{item.id}-container",
:loading => "new Effect2.Fade('item-#{item.id}-container', true)",
:url => { :controller => "todo", :action => "destroy_action", :id => item.id },
:confirm => "Are you sure that you want to delete the action, \'#{item.description}\'?") + " " +
link_to_function(image_tag( "blank", :title => "Edit action", :class => "edit_item"),
str ) + " "
end
def link_to_remote_todo_done( item )
link_to_remote( image_tag("blank", :title =>"Delete action", :class=>"delete_item"),
:update => "done-item-#{item.id}-container",
:loading => "new Effect2.Fade('done-item-#{item.id}-container', true)",
:url => { :controller => "todo", :action => "destroy_action", :id => item.id },
:confirm => "Are you sure that you want to delete the action \'#{item.description}\'?" ) +
image_tag("blank") + " "
end
def toggle_show_notes( item )
str = "<a href=\"javascript:Element.toggle('"
str << item.id.to_s
str << "')\" class=\"show_notes\" title=\"Show notes\">"
str << image_tag( "blank", :width=>"16", :height=>"16", :border=>"0" ) + "</a>"
m_notes = markdown( item.notes )
str << "<div class=\"notes\" id=\"" + item.id.to_s + "\" style=\"display:none\">"
str << m_notes + "</div>"
str
end
end

View file

@ -1,31 +1,47 @@
class Context < ActiveRecord::Base
acts_as_list
has_many :todo, :dependent => true
# Context name must not be empty
# and must be less than 255 bytes
validates_presence_of :name, :message => "context must have a name"
validates_length_of :name, :maximum => 255, :message => "context name must be less than %d"
validates_uniqueness_of :name, :message => "already exists"
def self.list_of(hidden=0)
find(:all, :conditions => [ "hide = ?" , hidden ], :order => "position ASC")
has_many :todos, :order => "completed DESC"
belongs_to :user
acts_as_list :scope => :user
attr_protected :user
# Context name must not be empty
# and must be less than 255 bytes
validates_presence_of :name, :message => "context must have a name"
validates_length_of :name, :maximum => 255, :message => "context name must be less than %d"
validates_uniqueness_of :name, :message => "already exists", :scope => "user_id"
def self.list_of(hidden=0)
find(:all, :conditions => [ "hide = ?" , hidden ], :order => "position ASC")
end
def find_not_done_todos
todos = Todo.find :all, :conditions => "context_id = #{id} AND done = 0",
:order => "due IS NULL, due ASC, created_at ASC"
end
def find_done_todos
todos = Todo.find :all, :conditions => "context_id = #{id} AND done = 1",
:order => "due IS NULL, due ASC, created_at ASC"
end
# Returns a count of next actions in the given context
# The result is count and a string descriptor, correctly pluralised if there are no
# actions or multiple actions
#
def count_undone_todos(string="actions")
count = self.find_not_done_todos.size
if count == 1
word = string.singularize
else
word = string.pluralize
end
# Returns a count of next actions in the given context
# The result is count and a string descriptor, correctly pluralised if there are no
# actions or multiple actions
#
def count_undone_todos(string="actions")
count = Todo.count( "context_id=#{self.id} AND done=0" )
if count == 1
word = string.singularize
else
word = string.pluralize
end
return count.to_s + " " + word
end
return count.to_s + " " + word
end
def hidden?
self.hide == 1
end
end

View file

@ -1,13 +1,15 @@
class Note < ActiveRecord::Base
belongs_to :user
belongs_to :project
attr_protected :user
def self.list_all
find(:all, :order => "created_at DESC")
end
def self.list_of(project_id)
find(:all, :conditions => [ "project_id = ?" , project_id ])
end
end

View file

@ -1,32 +1,44 @@
class Project < ActiveRecord::Base
has_many :todo, :dependent => true
has_many :note
acts_as_list
# Project name must not be empty
# and must be less than 255 bytes
validates_presence_of :name, :message => "project must have a name"
validates_length_of :name, :maximum => 255, :message => "project name must be less than %d"
validates_uniqueness_of :name, :message => "already exists"
def self.list_of(isdone=0)
find(:all, :conditions => [ "done = ?" , isdone ], :order => "position ASC")
has_many :todos, :dependent => true
has_many :notes, :order => "created_at DESC"
belongs_to :user
acts_as_list :scope => :user
attr_protected :user
# Project name must not be empty
# and must be less than 255 bytes
validates_presence_of :name, :message => "project must have a name"
validates_length_of :name, :maximum => 255, :message => "project name must be less than %d"
validates_uniqueness_of :name, :message => "already exists", :scope =>"user_id"
def self.list_of(isdone=0)
find(:all, :conditions => [ "done = ?" , isdone ], :order => "position ASC")
end
def find_not_done_todos
todos = Todo.find :all, :conditions => "project_id = #{id} AND done = 0",
:order => "due IS NULL, due ASC, created_at ASC"
end
def find_done_todos
todos = Todo.find :all, :conditions => "project_id = #{id} AND done = 1",
:order => "due IS NULL, due ASC, created_at ASC"
end
# Returns a count of next actions in the given project
# The result is count and a string descriptor, correctly pluralised if there are no
# actions or multiple actions
#
def count_undone_todos(string="actions")
count = find_not_done_todos.size
if count == 1
word = string.singularize
else
word = string.pluralize
end
# Returns a count of next actions in the given project
# The result is count and a string descriptor, correctly pluralised if there are no
# actions or multiple actions
#
def count_undone_todos(string="actions")
count = Todo.count( "project_id=#{self.id} AND done=0" )
if count == 1
word = string.singularize
else
word = string.pluralize
end
return count.to_s + " " + word
end
return count.to_s + " " + word
end
end

View file

@ -1,26 +1,30 @@
class Todo < ActiveRecord::Base
belongs_to :context, :order => 'name'
belongs_to :project
belongs_to :user
attr_protected :user
# Description field can't be empty, and must be < 100 bytes
# Notes must be < 60,000 bytes (65,000 actually, but I'm being cautious)
validates_presence_of :description
validates_length_of :description, :maximum => 100
validates_length_of :notes, :maximum => 60000
# Add a creation date (Ruby object format) to item before it's saved
# if there is no existing creation date (this prevents creation date
# being reset to completion date when item is completed)
#
def before_save
if self.created == nil
self.created = Time.now()
end
if self.done == 1
self.completed = Time.now()
end
end
def self.not_done( id=id )
self.find(:all, :conditions =>[ "done = 0 AND context_id = ?", id], \
:order =>"due IS NULL, due ASC, created_at ASC")
end
end

View file

@ -2,32 +2,37 @@ require 'digest/sha1'
# this model expects a certain database layout and its based on the name/login pattern.
class User < ActiveRecord::Base
has_many :contexts, :order => "position ASC"
has_many :projects, :order => "position ASC"
has_many :todos, :order => "completed DESC"
has_many :notes, :order => "created_at DESC"
attr_protected :is_admin
def self.authenticate(login, pass)
find_first(["login = ? AND password = ?", login, sha1(pass)])
end
end
def change_password(pass)
update_attribute "password", self.class.sha1(pass)
end
protected
protected
def self.sha1(pass)
Digest::SHA1.hexdigest("change-me--#{pass}--")
Digest::SHA1.hexdigest("#{app_configurations["admin"]["loginhash"]}--#{pass}--")
end
before_create :crypt_password
def crypt_password
write_attribute("password", self.class.sha1(password)) if password == @password_confirmation
write_attribute("word", self.class.sha1(word))
write_attribute("word", self.class.sha1(login + word))
end
validates_length_of :password, :within => 5..40
validates_length_of :login, :within => 3..80
validates_presence_of :password, :login, :word
validates_uniqueness_of :login, :on => :create
validates_uniqueness_of :word, :on => :create
validates_confirmation_of :password, :on => :create
validates_confirmation_of :password, :on => :create
end

View file

@ -1,7 +1,7 @@
<% item = show_items %>
<% if !item.done? %>
<div id="item-<%= item.id %>-container">
<%= form_remote_tag( :url => url_for( :controller => "context", :action => "toggle_check", :id => item.id ),
<%= form_remote_tag( :url => url_for( :controller => "todo", :action => "toggle_check", :id => item.id ),
:html => { :id=> "checkbox-notdone-#{item.id}", :class => "inline-form" },
:update => "completed",
:position => "top",
@ -15,7 +15,7 @@
link_to_remote( image_tag("blank", :title =>"Delete this action", :class=>"delete_item"),
:update => "item-#{item.id}-container",
:loading => "new Effect2.Fade('item-#{item.id}-container', true)",
:url => { :controller => "context", :action => "destroy_action", :id => item.id }, :confirm => "Are you sure that you want to delete the action \'#{item.description}\'?" ) + " " +
:url => { :controller => "todo", :action => "destroy_action", :id => item.id }, :confirm => "Are you sure that you want to delete the action \'#{item.description}\'?" ) + " " +
link_to_function(image_tag( "blank", :title => "Edit item", :class=>"edit_item"), "Element.toggle('item-#{item.id}','action-#{item.id}-edit-form'); new Effect.Appear('action-#{item.id}-edit-form'); Form.focusFirstElement('form-action-#{item.id}');" ) + " "
%>
<!-- begin div.checkbox -->
@ -28,7 +28,7 @@
<% if item.due %>
<div class="description">
<% else %>
<%= staleness(item.created) %>
<%= staleness( item ) %>
<% end %>
<%= due_date( item.due ) %>
<%= item.description %>
@ -45,7 +45,7 @@
<%= end_form_tag %>
<div id="action-<%= item.id %>-edit-form" class="edit-form" style="display:none;">
<%= form_remote_tag :url => { :controller => 'context', :action => 'update_action', :id => item.id },
<%= form_remote_tag :url => { :controller => 'todo', :action => 'update_action', :id => item.id },
:html => { :id => "form-action-#{item.id}", :class => "inline-form" },
:update => "item-#{item.id}-container",
:complete => "new Effect.Appear('item-#{item.id}-container');" %>
@ -56,7 +56,7 @@
</div><!-- [end:item-item.id-container] -->
<% else %>
<div id="done-item-<%= item.id %>-container">
<%= form_remote_tag( :url => url_for( :controller => "context", :action => "toggle_check", :id => item.id ),
<%= form_remote_tag( :url => url_for( :controller => "todo", :action => "toggle_check", :id => item.id ),
:html => { :id=> "checkbox-done-#{item.id}", :class => "inline-form" },
:update => "next_actions",
:position => "bottom",
@ -70,7 +70,7 @@
link_to_remote( image_tag("blank", :title =>"Delete this action", :class=>"delete_item"),
:update => "done-item-#{item.id}-container",
:loading => "new Effect2.Fade('done-item-#{item.id}-container', true)",
:url => { :controller => "context", :action => "destroy_action", :id => item.id }, :confirm => "Are you sure that you want to delete the action \'#{item.description}\'?" ) + " "
:url => { :controller => "todo", :action => "destroy_action", :id => item.id }, :confirm => "Are you sure that you want to delete the action \'#{item.description}\'?" ) + " "
%>
<%= image_tag("blank") %>
</div><!-- [end:big-box] -->
@ -95,4 +95,4 @@
<%= end_form_tag %>
</div><!-- [end:item-item.id-container] -->
<% end %>
<% item = nil %>
<% item = nil %>

View file

@ -36,7 +36,7 @@
<div id="context_new_action" class="context_new" style="display:none">
<!--[form:context]-->
<%= form_remote_tag :url => { :action => "add_item" },
<%= form_remote_tag :url => { :controller => "todo", :action => "add_item" },
:update=> "next_actions",
:position=> "bottom",
:loading => "context.reset()",
@ -57,44 +57,11 @@
<br /><br />
<input type="submit" value="Add item" tabindex="5">
<%= end_form_tag %><!--[eoform:context]-->
<script type="text/javascript">
Calendar.setup({ ifFormat:"<%= ApplicationController::DATE_FORMAT %>",firstDay:<%= ApplicationController::WEEK_STARTS_ON %>,showOthers:true,range:[2004, 2010],step:1,inputField:"new_item_due",cache:true,align:"TR" })
</script>
<%= calendar_setup( "new_item_due" ) %>
</div><!-- [end:context-new-action] -->
<h3>Active Projects:</h3>
<ul>
<% for project in Project.list_of -%>
<li><%= link_to( project.name, { :controller => "project", :action => "show",
:name => urlize(project.name) } ) + " (" + project.count_undone_todos("actions") + ")" %></li>
<% end -%>
</ul>
<h3>Completed Projects:</h3>
<ul>
<% for project in Project.list_of( isdone=1 ) -%>
<li><%= link_to( project.name, { :controller => "project", :action => "show",
:name => urlize(project.name) } ) + " (" + project.count_undone_todos("actions") + ")" %></li>
<% end -%>
</ul>
<h3>Active Contexts:</h3>
<ul>
<% for context in Context.list_of -%>
<li><%= link_to( context.name, { :controller => "context", :action => "show",
:name => urlize(context.name) } ) + " (" + context.count_undone_todos("actions") + ")" %></li>
<% end -%>
</ul>
<h3>Hidden Contexts:</h3>
<ul>
<% for context in Context.list_of( hidden=1 ) -%>
<li><%= link_to( context.name, { :controller => "context", :action => "show",
:name => urlize(context.name) } ) + " (" + context.count_undone_todos("actions") + ")" %></li>
<% end -%>
</ul>
<%= render "shared/sidebar" %>
</div><!-- End of input box -->
<% if @flash["confirmation"] %><div class="confirmation"><%= @flash["confirmation"] %></div><% end %>

View file

@ -1 +1 @@
<%= build_text_page( @not_done, @places, @projects ) %>
<%= build_text_page( @not_done, @contexts ) %>

View file

@ -0,0 +1,22 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<%= stylesheet_link_tag "scaffold" %>
<title><%= @page_title -%></title>
</head>
<body>
<div id="navcontainer">
<ul id="navlist">
<li><%= link_to "Home", :controller => "todo", :action=>"list"%></li>
</ul>
</div>
<%= @content_for_layout %>
<div id="footer">
<p>Send feedback: <a href="http://dev.rousette.org.uk/report/6">Trac</a> | <a href="http://www.rousette.org.uk/projects/wiki/">Wiki</a> | <a href="mailto:butshesagirl@rousette.org.uk?subject=Tracks feedback">Email</a> | <a href="http://www.rousette.org.uk/projects/">Website</a></p>
</div>
</body>
</html>

View file

@ -11,7 +11,8 @@
<%= javascript_include_tag 'calendar', 'calendar-en', 'calendar-setup' %>
<link rel="shortcut icon" href="<%= url_for(:controller => 'favicon.ico') %>" />
<%= auto_discovery_link_tag(:rss,{:controller => "feed", :action => "na_feed", :name => "#{@session['user']['login']}", :token => "#{@session['user']['word']}"}, {:title => "RSS feed of next actions"}) %>
<title><%= @page_title %></title>
</head>

View file

@ -0,0 +1,5 @@
<div title="No signups" id="signupform" class="form">
<h3>No Signups</h3>
<p>You don't have permission to sign up for a new account.</p>
<p>Please contact the site administrator <%= mail_to "#{app_configurations["admin"]["email"]}", "by email", :encode => "hex" %> to get permission.</p>
</div>

View file

@ -1,37 +1,25 @@
<%= start_form_tag :action=> "signup" %>
<div title="Account signup" id="signupform" class="form">
<% if User.find_all.empty? %>
<%= hidden_field "user", "is_admin", "value" => 1 %>
<h3>Sign up as the admin user</h3>
<%= render_errors @user %><br/>
<label for="user_login">Desired login:</label><br/>
<%= text_field "user", "login", :size => 20 %><br/>
<label for="user_password">Choose password:</label><br/>
<%= password_field "user", "password", :size => 20 %><br/>
<label for="user_password_confirmation">Confirm password:</label><br/>
<%= password_field "user", "password_confirmation", :size => 20 %><br/>
<label for="user_word">Secret word (different to password):</label><br />
<%= password_field "user", "word", :size => 20 %><br />
<input type="submit" value="Signup &#187;" class="primary" />
<% elsif (@session['user'] && @session['user']['is_admin'] == 1) %>
<%= hidden_field "user", "is_admin", "value" => 0 %>
<h3>Sign up a new user</h3>
<%= render_errors @user %><br/>
<label for="user_login">Desired login:</label><br/>
<%= text_field "user", "login", :size => 20 %><br/>
<label for="user_password">Choose password:</label><br/>
<%= password_field "user", "password", :size => 20 %><br/>
<label for="user_password_confirmation">Confirm password:</label><br/>
<%= password_field "user", "password_confirmation", :size => 20 %><br/>
<label for="user_word">Secret word (different to password):</label><br />
<%= password_field "user", "word", :size => 20 %><br />
<input type="submit" value="Signup &#187;" class="primary" />
<% else %>
<h3>Signup</h3>
<p>You don't have permission to sign up for a new account.</p>
<p>Please contact the site administrator <%= mail_to "#{app_configurations["admin"]["email"]}", "by email", :encode => "hex" %> to get permission.</p>
<% end %>
<%= start_form_tag :action=> "create" %>
<%= error_messages_for 'user' %><br/>
<h3><%= @page_title -%></h3>
<label for="user_login">Desired login:</label><br/>
<%= text_field "user", "login", :size => 20 %><br/>
<label for="user_password">Choose password:</label><br/>
<%= password_field "user", "password", :size => 20 %><br/>
<label for="user_password_confirmation">Confirm password:</label><br/>
<%= password_field "user", "password_confirmation", :size => 20 %><br/>
<label for="user_word">Secret word (different to password):</label><br />
<%= password_field "user", "word", :size => 20 %><br />
<input type="submit" value="Signup &#187;" class="primary" />
<%= end_form_tag %>
</div>

View file

@ -10,7 +10,8 @@
:update => "note-#{note.id}",
:loading => "new Effect2.Fade('note-#{note.id}-container', true)",
:complete => "Element.remove('note-#{note.id}-wrapper');",
:url => { :action => "destroy_note", :id => note.id }, :confirm => "Are you sure that you want to delete the note \'#{note.id.to_s}\'?" ) + "&nbsp;"%><%= link_to_function(image_tag( "blank", :title => "Edit item", :class=>"edit_item"),
:url => { :controller => "note", :action => "delete", :id => note.id },
:confirm => "Are you sure that you want to delete the note \'#{note.id.to_s}\'?" ) + "&nbsp;"%><%= link_to_function(image_tag( "blank", :title => "Edit item", :class=>"edit_item"),
"Element.toggle('note-#{note.id}','note-#{note.id}-edit-form'); new Effect.Appear('note-#{note.id}-edit-form'); Form.focusFirstElement('form-note-#{note.id}');" ) + " | " %>
<%= link_to("In: " + note.project.name, {:controller => "project", :action => "show", :name => urlize(note.project.name)}, :class=>"footer_link" ) %>&nbsp;|&nbsp;
Created: <%= format_date(note.created_at) %>
@ -21,7 +22,7 @@
</div><!-- [end.note.id] -->
<div id="note-<%= note.id %>-edit-form" class="edit-form" style="display:none;">
<%= form_remote_tag :url => { :action => 'update_note', :id => note.id },
<%= form_remote_tag :url => { :action => 'update', :id => note.id },
:html => { :id => "form-note-#{note.id}", :class => "inline-form" },
:update => "note-#{note.id}-container",
:complete => "new Effect.appear('note-#{note.id}-container');" %>
@ -30,4 +31,4 @@
</div><!-- [end:action-item.id-edit-form] -->
</div><!-- [end.note-note.id-container] -->
<% note = nil -%>
<% note = nil -%>

View file

@ -1,6 +1,7 @@
<% note = notes_summary -%>
<div class="note_wrapper">
<%= link_to(image_tag("blank"), {:controller => "note", :action => "show", :id => note.id}, :title => "Show note", :class => "show_notes" ) %>&nbsp;
<%= link_to( image_tag("blank"), { :controller => "note", :action => "show",
:id => note.id}, :title => "Show note", :class => "show_notes" ) %>&nbsp;
<%= textilize(truncate(note.body, 50, "...")) %>
</div>
<% note = nil -%>
<% note = nil -%>

View file

@ -1,7 +1,7 @@
<% item = show_items %>
<% if !item.done? %>
<div id="item-<%= item.id %>-container">
<%= form_remote_tag( :url => url_for( :controller => "project", :action => "toggle_check", :id => item.id ),
<%= form_remote_tag( :url => url_for( :controller => "todo", :action => "toggle_check", :id => item.id ),
:html => { :id=> "checkbox-notdone-#{item.id}", :class => "inline-form" },
:update => "completed",
:position => "top",
@ -15,7 +15,7 @@
link_to_remote( image_tag("blank", :title =>"Delete action", :class=>"delete_item"),
:update => "item-#{item.id}-container",
:loading => "new Effect2.Fade('item-#{item.id}-container', true)",
:url => { :controller => "project", :action => "destroy_action", :id => item.id }, :confirm => "Are you sure that you want to delete the action \'#{item.description}\'?" ) + " " +
:url => { :controller => "todo", :action => "destroy_action", :id => item.id }, :confirm => "Are you sure that you want to delete the action \'#{item.description}\'?" ) + " " +
link_to_function(image_tag( "blank", :title => "Edit action", :class=>"edit_item"), "Element.toggle('item-#{item.id}','action-#{item.id}-edit-form'); new Effect.Appear('action-#{item.id}-edit-form'); Form.focusFirstElement('form-action-#{item.id}');" ) + " "
%>
</div>
@ -28,7 +28,7 @@
<% if item.due %>
<div class="description">
<% else %>
<%= staleness(item.created) %>
<%= staleness( item ) %>
<% end %>
<%= due_date( item.due ) %>
@ -46,7 +46,7 @@
<%= end_form_tag %>
<div id="action-<%= item.id %>-edit-form" class="edit-form" style="display:none;">
<%= form_remote_tag :url => { :controller => 'project', :action => 'update_action', :id => item.id },
<%= form_remote_tag :url => { :controller => 'todo', :action => 'update_action', :id => item.id },
:html => { :id => "form-action-#{item.id}", :class => "inline-form" },
:update => "item-#{item.id}-container",
:complete => "new Effect.Appear('item-#{item.id}-container', true);" %>
@ -57,7 +57,7 @@
</div><!-- [end:item-item.id-container] -->
<% else %>
<div id="done-item-<%= item.id %>-container">
<%= form_remote_tag( :url => url_for( :controller => "project", :action => "toggle_check", :id => item.id ),
<%= form_remote_tag( :url => url_for( :controller => "todo", :action => "toggle_check", :id => item.id ),
:html => { :id=> "checkbox-done-#{item.id}", :class => "inline-form" },
:update => "next_actions",
:position => "bottom",
@ -71,7 +71,7 @@
link_to_remote( image_tag("blank", :title =>"Delete action", :class=>"delete_item"),
:update => "done-item-#{item.id}-container",
:loading => "new Effect2.Fade('done-item-#{item.id}-container', true)",
:url => { :controller => "project", :action => "destroy_action", :id => item.id }, :confirm => "Are you sure that you want to delete the action \'#{item.description}\'?" ) + " "
:url => { :controller => "todo", :action => "destroy_action", :id => item.id }, :confirm => "Are you sure that you want to delete the action \'#{item.description}\'?" ) + " "
%>
</div><!-- [end:big-box] -->
<!-- begin div.checkbox -->
@ -95,4 +95,4 @@
<%= end_form_tag %>
</div><!-- [end:item-item.id-container] -->
<% end %>
<% item = nil %>
<% item = nil %>

View file

@ -12,48 +12,40 @@
<% elsif @not_done.empty? -%>
<p>There are no next actions yet in this project</p>
<% else -%>
<% for item in @not_done -%>
<%= render_partial "show_items", item %>
<% end -%>
<%= render :partial => "show_items", :collection => @not_done %>
<% end -%>
</div><!-- [end:next_actions] -->
</div><!-- [end:contexts] -->
<div class="contexts">
<h2>Completed actions in this project</h2>
<div id="completed">
<% if @done.empty? %>
<p>There are no completed next actions yet in this project</p>
<% else %>
<% for done_item in @done %>
<%= render_partial "show_items", done_item %>
<% end %>
<%= render :partial => "show_items", :collection => @done %>
<% end %>
</div>
</div><!-- [end:contexts] -->
<!-- begin div.contexts -->
<% @notes = Note.list_of(@project.id) -%>
<div class="contexts">
<% if @notes.empty? -%>
<p>There are no notes yet for this project.</p>
<% end -%>
<div id="notes">
<h2>Notes</h2>
<% for note in @notes -%>
<%= render_partial "note/notes_summary", note %>
<div id="notes">
<h2>Notes</h2>
<% if @notes.empty? -%>
<p>There are no notes yet for this project.</p>
<% else -%>
<%= render :partial => "note/notes_summary", :collection => @notes %>
<% end -%>
</div>
</div>
</div>
<!-- end div.contexts -->
<%= link_to_function( "Add a note",
"Element.toggle('new-note'); Form.focusFirstElement('form-new-note');") %>
<%= link_to_function( "Add a note", "Element.toggle('new-note'); Form.focusFirstElement('form-new-note');") %>
<div id="new-note" style="display:none;">
<%= form_remote_tag :url => { :controller => "note", :action => "add_note" },
<%= form_remote_tag :url => { :controller => "note", :action => "add" },
:update => "notes",
:position => "bottom",
:complete => "new Effect.Highlight('notes');",
@ -73,7 +65,7 @@
<div id="project_new_action" class="project_new" style="display:none">
<!--[form:project]-->
<%= form_remote_tag :url => { :action => "add_item" },
<%= form_remote_tag :url => { :controller => "todo", :action => "add_item" },
:update=> "next_actions",
:position=> "bottom",
:loading => "project.reset()",
@ -86,50 +78,18 @@
<%= text_area( "new_item", "notes", "cols" => 25, "rows" => 10, "tabindex" => 2 ) %><br />
<label for="new_item_context_id">Context</label><br />
<select name="new_item[context_id]" id="new_item_context_id" tabindex="3">
<%= options_from_collection_for_select(@places, "id", "name" ) %>
<%= options_from_collection_for_select(@contexts, "id", "name" ) %>
</select><br />
<label for="item_due">Due</label><br />
<%= text_field("new_item", "due", "size" => 10, "class" => "Date", "onFocus" => "Calendar.setup", "tabindex" => 4) %>
<br /><br />
<input type="submit" value="Add item" tabindex="5">
<%= end_form_tag %><!--[eoform:project]-->
<script type="text/javascript">
Calendar.setup({ ifFormat:"<%= ApplicationController::DATE_FORMAT %>",firstDay:<%= ApplicationController::WEEK_STARTS_ON %>,showOthers:true,range:[2004, 2010],step:1,inputField:"new_item_due",cache:true,align:"TR" })
</script>
<%= calendar_setup( "new_item_due" ) %>
</div><!-- [end:project-new-action] -->
<h3>Active Projects:</h3>
<ul>
<% for project in Project.list_of -%>
<li><%= link_to( project.name, { :controller => "project", :action => "show",
:name => urlize(project.name) } ) + " (" + project.count_undone_todos("actions") + ")" %></li>
<% end -%>
</ul>
<h3>Completed Projects:</h3>
<ul>
<% for project in Project.list_of( isdone=1 ) -%>
<li><%= link_to( project.name, { :controller => "project", :action => "show",
:name => urlize(project.name) } ) + " (" + project.count_undone_todos("actions") + ")" %></li>
<% end -%>
</ul>
<h3>Active Contexts:</h3>
<ul>
<% for context in Context.list_of -%>
<li><%= link_to( context.name, { :controller => "context", :action => "show",
:name => urlize(context.name) } ) + " (" + context.count_undone_todos("actions") + ")" %></li>
<% end -%>
</ul>
<h3>Hidden Contexts:</h3>
<ul>
<% for context in Context.list_of( hidden=1 ) -%>
<li><%= link_to( context.name, { :controller => "context", :action => "show",
:name => urlize(context.name) } ) + " (" + context.count_undone_todos("actions") + ")" %></li>
<% end -%>
</ul>
<%= render "shared/sidebar" %>
</div><!-- End of input box -->
<% if @flash["confirmation"] %><div class="confirmation"><%= @flash["confirmation"] %></div><% end %>

View file

@ -0,0 +1,46 @@
<%
case controller.controller_name
when "context"
add_string = "Add a next action in this context &#187;"
when "project"
add_string = "Add a next action in this project &#187;"
else
add_string = "Add a next action &#187;"
end
%>
<%= link_to_function(
add_string,
"Element.toggle('todo_new_action');Form.focusFirstElement('todo-form-new-action');Element.toggle('new_actions');",
{:title => "Add the next action [Alt+n]", :accesskey => "n"}) %>
<div id="todo_new_action" class="context_new" style="display:none">
<!--[form:todo]-->
<%= form_remote_tag(
:url => { :controller => "todo", :action => "add_item" },
:update => "new_actions",
:position=> "bottom",
:complete => "Form.focusFirstElement('todo-form-new-action');Form.reset('todo-form-new-action')",
:html=> { :id=>'todo-form-new-action', :name=>'todo', :class => 'inline-form' }) %>
<label for="new_item_description">Description</label><br />
<%= text_field( "new_item", "description", "size" => 25, "tabindex" => 1 ) %><br />
<label for="new_item_notes">Notes</label><br />
<%= text_area( "new_item", "notes", "cols" => 25, "rows" => 10, "tabindex" => 2 ) %><br />
<label for="new_item_context_id">Context</label><br />
<%= collection_select( "new_item", "context_id", @contexts, "id", "name",
{}, {"tabindex" => 3}) %><br />
<label for="new_item_project_id">Project</label><br />
<%= collection_select( "new_item", "project_id", @projects, "id", "name",
{ :include_blank => true }, {"tabindex" => 4}) %><br />
<label for="item_due">Due</label><br />
<%= text_field("new_item", "due", "size" => 10, "class" => "Date", "onFocus" => "Calendar.setup", "tabindex" => 5) %>
<br /><br />
<input type="submit" value="Add item" tabindex="6">
<%= end_form_tag %><!--[eoform:todo]-->
<%= calendar_setup( "new_item_due" ) %>
</div><!-- [end:todo-new-action] -->

View file

@ -0,0 +1,31 @@
<h3>Active Projects:</h3>
<ul>
<% for project in @projects.collect { |x| x.done? ? nil:x }.compact -%>
<li><%= link_to( project.name, { :controller => "project", :action => "show",
:name => urlize(project.name) } ) + " (" + project.count_undone_todos("actions") + ")" %></li>
<% end -%>
</ul>
<h3>Completed Projects:</h3>
<ul>
<% for project in @projects.collect { |x| x.done? ? x:nil }.compact -%>
<li><%= link_to( project.name, { :controller => "project", :action => "show",
:name => urlize(project.name) } ) + " (" + project.count_undone_todos("actions") + ")" %></li>
<% end -%>
</ul>
<h3>Active Contexts:</h3>
<ul>
<% for context in @contexts.collect { |x| x.hidden? ? nil:x }.compact -%>
<li><%= link_to( context.name, { :controller => "context", :action => "show",
:name => urlize(context.name) } ) + " (" + context.count_undone_todos("actions") + ")" %></li>
<% end -%>
</ul>
<h3>Hidden Contexts:</h3>
<ul>
<% for context in @contexts.collect { |x| x.hidden? ? x:nil }.compact -%>
<li><%= link_to( context.name, { :controller => "context", :action => "show",
:name => urlize(context.name) } ) + " (" + context.count_undone_todos("actions") + ")" %></li>
<% end -%>
</ul>

View file

@ -16,7 +16,7 @@
<tr>
<td class="label"><label for="item_context_id">Context</label></td>
<td><select name="item[context_id]" id="item_context_id" tabindex="3">
<% for @place in @places %>
<% for @place in @contexts %>
<% if @item %>
<% if @place.id == @item.context_id %>
<option value="<%= @place.id %>" selected="selected"><%= @place.name %></option>
@ -52,8 +52,5 @@
<a href="javascript:void(0);" onclick="Element.toggle('item-<%= @item.id %>','action-<%= @item.id %>-edit-form');Form.reset('form-action-<%= @item.id %>');">Cancel</a></td>
</tr>
</table>
<script type="text/javascript">
Calendar.setup({ ifFormat:"<%= ApplicationController::DATE_FORMAT %>",firstDay:<%= ApplicationController::WEEK_STARTS_ON %>,showOthers:true,range:[2004, 2010],step:1,inputField:"due_<%= @item.id %>",cache:true,align:"TR" })
</script>
<% @item = nil %>
<%= calendar_setup( "due_#{@item.id}" ) %>
<% @item = nil %>

View file

@ -1,25 +1,25 @@
<% @done_item = done %>
<tr>
<% if @done_item.completed %>
<% if done.completed %>
<td valign="top"><%= image_tag( "done", :width=>"16", :height=>"16", :border=>"0") %></td>
<td valign="top"><span class="grey"><%= format_date( @done_item.completed ) %></span></td>
<td valign="top"><%= " " + @done_item.description + " "%>
<td valign="top"><span class="grey"><%= format_date( done.completed ) %></span></td>
<td valign="top"><%= " " + done.description + " "%>
<% if @done_item.project_id %>
<%= "(" + @done_item.context['name'] + ", " + @done_item.project['name'] + ")" %>
<% if done.project_id %>
<%= "(" + done.context['name'] + ", " + done.project['name'] + ")" %>
<% else %>
<%= "(" + @done_item.context['name'] + ")" %>
<%= "(" + done.context['name'] + ")" %>
<% end %>
<% if @done_item.due %>
<%= " - was due on " + format_date( @done_item.due ) %>
<% if done.due %>
<%= " - was due on " + format_date( done.due ) %>
<% end %>
<% if @done_item.notes? %>
<%= "<a href=\"javascript:Element.toggle('" + @done_item.id.to_s + "')\" title=\"Show notes\">" + image_tag( "notes", :width=>"10", :height=>"10", :border=>"0") + "</a>" %>
<% m_notes = markdown( @done_item.notes ) %>
<%= "<div class=\"notes\" id=\"" + @done_item.id.to_s + "\" style=\"display:none\">" + m_notes + "</div>" %>
<% if done.notes? %>
<%= "<a href=\"javascript:Element.toggle('" + done.id.to_s + "')\" title=\"Show notes\">" +
image_tag( "notes", :width=>"10", :height=>"10", :border=>"0") + "</a>" %>
<% m_notes = markdown( done.notes ) %>
<%= "<div class=\"notes\" id=\"" + done.id.to_s + "\" style=\"display:none\">" + m_notes + "</div>" %>
<% end %>
</td>
<% end %>
</tr>
</tr>

View file

@ -0,0 +1,59 @@
<% if !item.done? %>
<div id="item-<%= item.id %>-container">
<%= form_remote_tag_todo_notdone( item ) %>
<div id="item-<%= item.id %>">
<div class="big-box">
<%= link_to_remote_todo_notdone( item ) %>
</div>
<div class="checkbox">
<input type="checkbox" name="item_id" value="<%= item.id %>" onclick="document.forms['checkbox-notdone-<%= item.id %>'].onsubmit();" />
</div>
<!-- start of div which has a class 'description' or 'stale_7d', 'stale_14d' etc -->
<% if item.due %>
<div class="description">
<% else %>
<%= staleness( item ) %>
<% end %>
<%= due_date( item.due ) %>
<%= item.description %>
<% if item.project_id %>
<%= link_to( "[P]", { :controller => "project", :action => "show", :name => urlize(item.project.name) }, :title => "View project: #{item.project.name}" ) %>
<% end %>
<% if item.notes? %>
<%= toggle_show_notes( item ) %>
<% end %>
</div>
</div><!-- [end:item-item.id] -->
<%= end_form_tag %>
<div id="action-<%= item.id %>-edit-form" class="edit-form" style="display:none;">
<%= form_remote_tag_edit_todo( item ) %>
<%= render :partial => 'action_edit_form', :object => item %>
<%= end_form_tag %>
</div><!-- [end:action-item.id-edit-form] -->
</div><!-- [end:item-item.id-container] -->
<% else %>
<div id="done-item-<%= item.id %>-container">
<%= form_remote_tag_todo_done( item ) %>
<div id="done-item-<%= item.id %>">
<div class="big-box"> <%= link_to_remote_todo_done( item ) %></div><!-- [end:big-box] -->
<!-- begin div.checkbox -->
<div class="checkbox">
<input type="checkbox" name="item_id" value="<%= item.id %>" checked="checked" onclick="document.forms['checkbox-done-<%= item.id %>'].onsubmit();" />
</div>
<!-- end div.checkbox -->
<div class="description">
<span class="grey"><%= format_date( item.completed ) %></span>
<%= item.description %>
<% if item.project_id %>
<%= link_to( "[P]", { :controller => "project", :action => "show", :name => urlize(item.project.name) }, :title => "View project: #{item.project.name}" ) %>
<% end %>
<% if item.notes? %>
<%= toggle_show_notes( item ) %>
<% end %>
</div><!-- [end:description] -->
</div><!-- [end:item-item.id] -->
<%= end_form_tag %>
</div><!-- [end:item-item.id-container] -->
<% end %>

View file

@ -1,98 +0,0 @@
<% item = show_items %>
<% if !item.done? %>
<div id="item-<%= item.id %>-container">
<%= form_remote_tag( :url => url_for( :controller => "todo", :action => "toggle_check", :id => item.id ),
:html => { :id=> "checkbox-notdone-#{item.id}", :class => "inline-form" },
:update => "completed",
:position => "top",
:loading => "Form.disable('checkbox-notdone-#{item.id}');",
:complete => "new Effect2.Fade('item-#{item.id}-container', true);"
) %>
<div id="item-<%= item.id %>">
<div class="big-box">
<%=
link_to_remote( image_tag("blank", :title =>"Delete action", :class=>"delete_item"),
:update => "item-#{item.id}-container",
:loading => "new Effect2.Fade('item-#{item.id}-container', true)",
:url => { :controller => "todo", :action => "destroy_action", :id => item.id },
:confirm => "Are you sure that you want to delete the action, \'#{item.description}\'?") + " " +
link_to_function(image_tag( "blank", :title => "Edit action", :class => "edit_item"),
"Element.toggle('item-#{item.id}','action-#{item.id}-edit-form'); new Effect.Appear('action-#{item.id}-edit-form'); Form.focusFirstElement('form-action-#{item.id}')" ) + " "
%>
</div>
<div class="checkbox">
<input type="checkbox" name="item_id" value="<%= item.id %>" onclick="document.forms['checkbox-notdone-<%= item.id %>'].onsubmit();" />
</div>
<!-- start of div which has a class 'description' or 'stale_7d', 'stale_14d' etc -->
<% if item.due %>
<div class="description">
<% else %>
<%= staleness(item.created) %>
<% end %>
<%= due_date( item.due ) %>
<%= item.description %>
<% if item.project_id %>
<%= link_to( "[P]", { :controller => "project", :action => "show", :name => urlize(item.project.name) }, :title => "View project: #{item.project.name}" ) %>
<% end %>
<% if item.notes? %>
<%= "<a href=\"javascript:Element.toggle('" + item.id.to_s + "')\" class=\"show_notes\" title=\"Show notes\">" + image_tag( "blank", :width=>"16", :height=>"16", :border=>"0" ) + "</a>" %>
<% m_notes = markdown( item.notes ) %>
<%= "<div class=\"notes\" id=\"" + item.id.to_s + "\" style=\"display:none\">" + m_notes + "</div>" %>
<% end %>
</div>
</div><!-- [end:item-item.id] -->
<%= end_form_tag %>
<div id="action-<%= item.id %>-edit-form" class="edit-form" style="display:none;">
<%= form_remote_tag :url => { :controller => 'todo', :action => 'update_action', :id => item.id },
:html => { :id => "form-action-#{item.id}", :class => "inline-form" },
:update => "item-#{item.id}-container",
:complete => "new Effect.Appear('item-#{item.id}-container');" %>
<%= render_partial 'action_edit_form', item %>
<%= end_form_tag %>
</div><!-- [end:action-item.id-edit-form] -->
</div><!-- [end:item-item.id-container] -->
<% else %>
<div id="done-item-<%= item.id %>-container">
<%= form_remote_tag( :url => url_for( :controller => "todo", :action => "toggle_check", :id => item.id ),
:html => { :id=> "checkbox-done-#{item.id}", :class => "inline-form" },
:update => "new_actions",
:position => "bottom",
:loading => "Form.disable('checkbox-done-#{item.id}');",
:complete => "Element.toggle('new_actions');new Effect2.Fade('done-item-#{item.id}-container', true);"
) %>
<div id="done-item-<%= item.id %>">
<div class="big-box">
<%=
link_to_remote( image_tag("blank", :title =>"Delete action", :class=>"delete_item"),
:update => "done-item-#{item.id}-container",
:loading => "new Effect2.Fade('done-item-#{item.id}-container', true)",
:url => { :controller => "todo", :action => "destroy_action", :id => item.id }, :confirm => "Are you sure that you want to delete the action \'#{item.description}\'?" ) + " "
%>
<%= image_tag("blank") %>
</div><!-- [end:big-box] -->
<!-- begin div.checkbox -->
<div class="checkbox">
<input type="checkbox" name="item_id" value="<%= item.id %>" checked="checked" onclick="document.forms['checkbox-done-<%= item.id %>'].onsubmit();" />
</div>
<!-- end div.checkbox -->
<div class="description">
<span class="grey"><%= format_date( item.completed ) %></span>
<%= item.description %>
<% if item.project_id %>
<%= link_to( "[P]", { :controller => "project", :action => "show", :name => urlize(item.project.name) }, :title => "View project: #{item.project.name}" ) %>
<% end %>
<% if item.notes? %>
<%= "<a href=\"javascript:Element.toggle('" + item.id.to_s + "')\" class=\"show_notes\" title=\"Show notes\">" + image_tag( "blank", :width=>"16", :height=>"16", :border=>"0" ) + "</a>" %>
<% m_notes = markdown( item.notes ) %>
<%= "<div class=\"notes\" id=\"" + item.id.to_s + "\" style=\"display:none\">" + m_notes + "</div>" %>
<% end %>
</div><!-- [end:description] -->
</div><!-- [end:item-item.id] -->
<%= end_form_tag %>
</div><!-- [end:item-item.id-container] -->
<% end %>
<% item = nil %>

View file

@ -1,24 +1,23 @@
<div id="display_box_projects">
<p>You have completed <%= @done_today.length %> actions so far today.</p>
<div class="contexts">
<h2>Completed today</h2>
<table class="next_actions" cellspacing="5" cellpadding="0" border="0">
<%= render_collection_of_partials "done", @done_today %>
<%= render :partial => "done", :collection => @done_today %>
</table>
</div>
<div class="contexts">
<h2>Completed in last 7 days</h2>
<table class="next_actions" cellspacing="5" cellpadding="0" border="0">
<%= render_collection_of_partials "done", @done_this_week %>
<%= render :partial => "done", :collection => @done_this_week %>
</table>
</div>
<div class="contexts"
<h2>Completed in the last 31 days</h2>
<table class="next_actions" cellspacing="5" cellpadding="0" border="0">
<%= render_collection_of_partials "done", @done_this_month %>
<%= render :partial => "done", :collection => @done_this_month %>
</table>
</div>

View file

@ -4,7 +4,7 @@
<div class="contexts">
<h2>Completed more than 31 days ago</h2>
<table class="next_actions" cellspacing="5" cellpadding="0" border="0">
<%= render_collection_of_partials "done", @done_archive %>
<%= render :partial => "done", :collection => @done_archive %>
</table>
</div>

View file

@ -1,29 +1,23 @@
<div id="display_box">
<!-- begin div.new_actions -->
<div id="new_actions" class="new_actions" style="display:none">
<h2>Fresh actions (hit <a href="javascript:window.location.reload()">refresh</a> to sort)</h2>
</div>
</div>
<!-- end div.new_actions -->
<% for @shown_place in @shown_places -%>
<% @not_done = Todo.find_all("done=0 AND context_id=#{@shown_place.id}", "due IS NULL, due ASC, created ASC") -%>
<% if !@not_done.empty? -%>
<%
for @context in @contexts
next if @context.hidden?
@not_done = @context.find_not_done_todos
next if @not_done.empty?
-%>
<div class="contexts">
<h2><a href="javascript:toggleSingle('c<%=@shown_place.id%>');javascript:toggleImage('toggle_context_<%=@shown_place.id%>')" class="refresh"><%= image_tag("collapse.png", :name=>"toggle_context_#{@shown_place.id}", :border=>"0") %></a>
<%= link_to( "#{@shown_place.name}", :controller => "context", :action => "show", :name => urlize(@shown_place.name) ) %></h2>
<h2><a href="javascript:toggleSingle('c<%=@context.id%>');javascript:toggleImage('toggle_context_<%=@context.id%>')" class="refresh"><%= image_tag("collapse.png", :name=>"toggle_context_#{@context.id}", :border=>"0") %></a>
<%= link_to( "#{@context.name}", :controller => "context", :action => "show", :name => urlize(@context.name) ) %></h2>
<div id="c<%= @shown_place.id %>" class="next_actions">
<% if @not_done.empty? -%>
<p>There are no next actions yet in this context</p>
<% else -%>
<% for item in @not_done -%>
<%= render_partial "show_items", item %>
<% end -%>
<% end -%>
<div id="c<%= @context.id %>" class="next_actions">
<%= render :partial => "item", :collection => @not_done %>
</div><!-- [end:next_actions] -->
</div><!-- [end:contexts] -->
<% end -%>
<% end -%>
<div class="contexts">
@ -31,85 +25,22 @@
<div id="completed">
<% if @done.empty? -%>
<p>There are no completed next actions yet in this context</p>
<p>There are no completed next actions</p>
<% else -%>
<% for done_item in @done -%>
<%= render_partial "show_items", done_item %>
<% end -%>
<%= render :partial => "item", :collection => @done %>
<% end -%>
</div>
</div><!-- [end:contexts] -->
</div><!-- End of display_box -->
<div id="input_box">
<%= render "shared/add_new_item_form" %>
<%= render "shared/sidebar" %>
</div><!-- End of input box -->
<% if @flash["confirmation"] -%>
<div class="confirmation"><%= @flash["confirmation"] %></div>
<% end -%>
<% if @flash["warning"] -%>
<div class="warning"><%= @flash["warning"] %></div>
<% end -%>
<%= link_to_function( "Add a next action &#187;", "Element.toggle('todo_new_action');Element.toggle('new_actions');Form.focusFirstElement('todo-form-new-action');", {:title => "Add the next action [Alt+n]", :accesskey => "n"}) %>
<div id="todo_new_action" class="context_new" style="display:none">
<!--[form:todo]-->
<%= form_remote_tag :url => { :action => "add_item" },
:update => "new_actions",
:position=> "bottom",
:loading => "context.reset()",
:complete => "Form.focusFirstElement('todo-form-new-action');new Effect.Highlight('new_actions');",
:html=> { :id=>'todo-form-new-action', :name=>'todo', :class => 'inline-form' } %>
<label for="new_item_description">Description</label><br />
<%= text_field( "new_item", "description", "size" => 25, "tabindex" => 1 ) %><br />
<label for="new_item_notes">Notes</label><br />
<%= text_area( "new_item", "notes", "cols" => 25, "rows" => 10, "tabindex" => 2 ) %><br />
<label for="new_item_context_id">Context</label><br />
<%= collection_select( "new_item", "context_id", @places, "id", "name", {}, {"tabindex" => 3}) %><br />
<label for="new_item_project_id">Project</label><br />
<select name="new_item[project_id]" id="new_item_project_id" tabindex="4">
<option selected="selected"></option>
<%= options_from_collection_for_select(@projects, "id", "name") %>
</select><br />
<label for="item_due">Due</label><br />
<%= text_field("new_item", "due", "size" => 10, "class" => "Date", "onFocus" => "Calendar.setup", "tabindex" => 5) %>
<br /><br />
<input type="submit" value="Add item" tabindex="6">
<%= end_form_tag %><!--[eoform:todo]-->
<script type="text/javascript">
Calendar.setup({ ifFormat:"<%= ApplicationController::DATE_FORMAT %>",firstDay:<%= ApplicationController::WEEK_STARTS_ON %>,showOthers:true,range:[2004, 2010],step:1,inputField:"new_item_due",cache:true,align:"TR" })
</script>
</div><!-- [end:todo-new-action] -->
<h3>Active Projects:</h3>
<ul>
<% for project in Project.list_of -%>
<li><%= link_to( project.name, { :controller => "project", :action => "show",
:name => urlize(project.name) } ) + " (" + project.count_undone_todos("actions") + ")" %></li>
<% end -%>
</ul>
<h3>Completed Projects:</h3>
<ul>
<% for project in Project.list_of( isdone=1 ) -%>
<li><%= link_to( project.name, { :controller => "project", :action => "show",
:name => urlize(project.name) } ) + " (" + project.count_undone_todos("actions") + ")" %></li>
<% end -%>
</ul>
<h3>Active Contexts:</h3>
<ul>
<% for context in Context.list_of -%>
<li><%= link_to( context.name, { :controller => "context", :action => "show",
:name => urlize(context.name) } ) + " (" + context.count_undone_todos("actions") + ")" %></li>
<% end -%>
</ul>
<h3>Hidden Contexts:</h3>
<ul>
<% for context in Context.list_of( hidden=1 ) -%>
<li><%= link_to( context.name, { :controller => "context", :action => "show",
:name => urlize(context.name) } ) + " (" + context.count_undone_todos("actions") + ")" %></li>
<% end -%>
</ul>
</div><!-- End of input box -->

View file

@ -65,4 +65,4 @@ Controllers = Dependencies::LoadingModule.root(
# Include your app's configuration here:
def app_configurations
YAML::load(File.open("#{RAILS_ROOT}/config/settings.yml"))
end
end

View file

@ -1,7 +1,19 @@
# 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.
# Log error messages when you accidentally call methods on nil.
require 'active_support/whiny_nil'
# Reload code; show full error reports; disable caching.
Dependencies.mechanism = :load
ActionController::Base.consider_all_requests_local = true
ActionController::Base.perform_caching = false
# The breakpoint server port that script/breakpointer connects to.
BREAKPOINT_SERVER_PORT = 42531
# Use Memory Store if you are using FCGI, otherwise use file store
ActionController::Base.fragment_cache_store = ActionController::Caching::Fragments::MemoryStore.new
#ActionController::Base.fragment_cache_store = ActionController::Caching::Fragments::FileStore.new("/path/to/cache/directory")
# Unique cookies
ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS[:session_key] = "TrackDev"
ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS[:tmpdir] = "#{RAILS_ROOT}/tmp"

View file

@ -1,6 +1,8 @@
# The production environment is meant for finished, "live" apps.
# Code is not reloaded between requests, full error reports are disabled,
# and caching is turned on.
# Don't reload code; don't show full error reports; enable caching.
Dependencies.mechanism = :require
ActionController::Base.consider_all_requests_local = false
ActionController::Base.perform_caching = true
# Use Memory Store if you are using FCGI, otherwise use file store
ActionController::Base.fragment_cache_store = ActionController::Caching::Fragments::MemoryStore.new
#ActionController::Base.fragment_cache_store = ActionController::Caching::Fragments::FileStore.new("/path/to/cache/directory")

View file

@ -1,4 +1,17 @@
# 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 "scratch space" for the test suite and is wiped
# and recreated between test runs. Don't rely on the data there!
# Log error messages when you accidentally call methods on nil.
require 'active_support/whiny_nil'
# Don't reload code; show full error reports; disable caching.
Dependencies.mechanism = :require
ActionController::Base.consider_all_requests_local = true
ActionController::Base.perform_caching = false
ActionMailer::Base.delivery_method = :test
# Tell ActionMailer not to deliver emails to the real world.
# The :test delivery method accumulates sent emails in the
# ActionMailer::Base.deliveries array.
ActionMailer::Base.delivery_method = :test

View file

@ -36,19 +36,19 @@ ActionController::Routing::Routes.draw do |map|
map.connect 'projects', :controller => 'project', :action => 'list'
map.connect 'project/:name', :controller => 'project', :action => 'show'
map.connect 'project/:id', :controller => 'project', :action => 'show'
# Notes Routes
map.connect 'note/add_note', :controller => 'note', :action => 'add_note'
map.connect 'note/update_note/:id', :controller => 'note', :action => 'update_note', :id => 'id'
map.connect 'note/add', :controller => 'note', :action => 'add'
map.connect 'note/update/:id', :controller => 'note', :action => 'update', :id => 'id'
map.connect 'note/:id', :controller => 'note', :action => 'show', :id => 'id'
map.connect 'notes', :controller => 'note', :action => 'index'
# Feed Routes
map.connect 'feed/:action/:name/:user', :controller => 'feed'
map.connect 'add_item', :controller => 'todo', :action => 'add_item'
# Install the default route as the lowest priority.
map.connect ':controller/:action'
map.connect ':controller/:action/:id'

View file

@ -11,3 +11,4 @@ formats:
staleness_starts: 7
admin:
email: butshesagirl@rousette.org.uk
loginhash: change-me

View file

@ -0,0 +1,41 @@
# Verision 1.0.3 database
class CreateTracksDb < ActiveRecord::Migration
def self.up
create_table :contexts do |t|
t.column :name, :string, :null => false
t.column :position, :integer, :null => false
t.column :hide, :boolean, :default => 0
end
create_table :projects do |t|
t.column :name, :string, :null => false
t.column :position, :integer, :null => false
t.column :done, :boolean, :default => 0
end
create_table :todos do |t|
t.column :context_id, :integer, :null => false
t.column :project_id, :integer
t.column :description, :string, :null => false
t.column :notes, :text
t.column :done, :boolean, :default => 0, :null => false
t.column :created, :datetime, :default => '0000-00-00 00:00:00'
t.column :due, :date
t.column :completed, :datetime
end
create_table :users do |t|
t.column :login, :string, :limit => 80, :null => false
t.column :password, :string, :limit => 40, :null => false
t.column :word, :string
t.column :is_admin, :boolean, :default => 0, :null => false
end
end
def self.down
drop_table :contexts
drop_table :projects
drop_table :todos
drop_table :users
end
end

View file

@ -0,0 +1,16 @@
class AddUserId < ActiveRecord::Migration
def self.up
add_column :contexts, :user_id, :integer, :null => false
add_column :projects, :user_id, :integer, :null => false
add_column :todos, :user_id, :integer, :null => false
execute "UPDATE `contexts` SET `user_id` = 1;"
execute "UPDATE `projects` SET `user_id` = 1;"
execute "UPDATE `todos` SET `user_id` = 1;"
end
def self.down
remove_column :contexts, :user_id
remove_column :projects, :user_id
remove_column :todos, :user_id
end
end

View file

@ -0,0 +1,9 @@
class CreatedAt < ActiveRecord::Migration
def self.up
rename_column :todos, :created, :created_at
end
def self.down
rename_column :todos, :created_at, :created
end
end

View file

@ -0,0 +1,15 @@
class Notes < ActiveRecord::Migration
def self.up
create_table :notes do |t|
t.column :user_id, :integer, :null => false
t.column :project_id, :integer, :null => false
t.column :body, :text
t.column :created_at, :datetime, :default => '0000-00-00 00:00:00'
t.column :updated_at, :datetime, :default => '0000-00-00 00:00:00'
end
end
def self.down
drop_table :notes
end
end

View file

@ -2,7 +2,7 @@
* Homepage: http://www.rousette.org.uk/projects/
* Author: bsag (http://www.rousette.org.uk/)
* Contributors: Nicholas Lee, Lolindrath, Jim Ray, Arnaud Limbourg
* Contributors: Nicholas Lee, Lolindrath, Jim Ray, Arnaud Limbourg, Timothy Martens
* Version: 1.03
* Copyright: (cc) 2004-2005 rousette.org.uk
* License: GNU GPL
@ -18,6 +18,11 @@ Wiki (deprecated - please use Trac): http://www.rousette.org.uk/projects/wiki/
1. Tidied up the interface a bit, fixing mistakes in the wording.
2. The number of actions reported is now correctly pluralized depending on the number of actions (e.g. 1 action, 2 actions).
3. Added a Note model and notes database table. Notes have their own interface (/notes lists them all, and /note/1 shows note id 1), but they are displayed on the /project/show/[name] page. For now, notes belong to a particular project, and are added from the /project/show/[name] pages. You can delete and edit notes either from the /notes page or from the /note/[id] page. Notes use Textile/Markdown formatting.
4. Can now host multiple users on a single tracks install. User creation only possible by admin user.
5. Major improves to the render-flow, code reuse and MVC rationalisation.
6. Rails based db creation and migration via rake.
7. Added loginhash to settings.yml.
8. Modify signup to prevent is_admin being set by malicious user. Begin work on standardising layout for login controller.
== Version 1.03
@ -80,4 +85,4 @@ A small increment to fix a few bugs and typographical errors in the README:
8. You can now add a new item to a particular project when you are viewing it at <tt>/project/show/[id]</tt>. This makes it easy to add a block of next actions to a project. Edit and delete buttons work on this page, but redirect you to <tt>/todo/list</tt>. I need to fix this.
9. Added a login system by using the login_generator: http://wiki.rubyonrails.com/rails/show/LoginGenerator. The first time you access the system, you need to visit <tt>[tracks_url]/login/signup</tt> to enter a username and password. Then when you visit, you will be greeted with a login page. This stores a temporary cookie for your session. The whole app is protected by the login.
10. You can now add a new item to a particular context when you are viewing it at <tt>/context/show/[id]</tt>. This makes it easy to add a block of next actions to a project. Edit and delete buttons work on this page, but redirect you to <tt>/todo/list</tt>. I need to fix this.
11. Feeds! There's an RSS feed available at <tt>[tracks_url]/feed/na_feed</tt> which shows the last 15 next actions, and a text feed at <tt>[tracks_url]/feed/na_text</tt> which gives you a plain text list of all your next actions, sorted by context. You can use the latter to display your next actions on the desktop with GeekTool. Simply set up a shell command containing the following: <tt>curl http://[tracks_url]/feed/na_text</tt>. Obviously, you need to replace [tracks_url] with the actual URL to the Tracks application.
11. Feeds! There's an RSS feed available at <tt>[tracks_url]/feed/na_feed</tt> which shows the last 15 next actions, and a text feed at <tt>[tracks_url]/feed/na_text</tt> which gives you a plain text list of all your next actions, sorted by context. You can use the latter to display your next actions on the desktop with GeekTool. Simply set up a shell command containing the following: <tt>curl http://[tracks_url]/feed/na_text</tt>. Obviously, you need to replace [tracks_url] with the actual URL to the Tracks application.

View file

@ -1,3 +1,5 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<body>
<h1>File not found</h1>

View file

@ -1,3 +1,5 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<body>
<h1>Application error (Apache)</h1>

View file

@ -1,4 +1,4 @@
#!/usr/bin/ruby
#!/usr/bin/ruby1.8
require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT)

View file

@ -1,7 +1,24 @@
#!/usr/bin/ruby
#!/usr/bin/ruby1.8
#
# You may specify the path to the FastCGI crash log (a log of unhandled
# exceptions which forced the FastCGI instance to exit, great for debugging)
# and the number of requests to process before running garbage collection.
#
# By default, the FastCGI crash log is RAILS_ROOT/log/fastcgi.crash.log
# and the GC period is nil (turned off). A reasonable number of requests
# could range from 10-100 depending on the memory footprint of your app.
#
# Example:
# # Default log path, normal GC behavior.
# RailsFCGIHandler.process!
#
# # Default log path, 50 requests between GC.
# RailsFCGIHandler.process! nil, 50
#
# # Custom log path, normal GC behavior.
# RailsFCGIHandler.process! '/var/log/myapp_fcgi_crash.log'
#
require File.dirname(__FILE__) + "/../config/environment"
require 'dispatcher'
require 'fcgi'
require 'fcgi_handler'
FCGI.each_cgi { |cgi| Dispatcher.dispatch(cgi) }
RailsFCGIHandler.process!

View file

@ -1,4 +1,4 @@
#!/usr/bin/ruby
#!/usr/bin/ruby1.8
require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT)

446
tracks/public/javascripts/controls.js vendored Normal file
View file

@ -0,0 +1,446 @@
// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
// (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Element.collectTextNodesIgnoreClass = function(element, ignoreclass) {
var children = $(element).childNodes;
var text = "";
var classtest = new RegExp("^([^ ]+ )*" + ignoreclass+ "( [^ ]+)*$","i");
for (var i = 0; i < children.length; i++) {
if(children[i].nodeType==3) {
text+=children[i].nodeValue;
} else {
if((!children[i].className.match(classtest)) && children[i].hasChildNodes())
text += Element.collectTextNodesIgnoreClass(children[i], ignoreclass);
}
}
return text;
}
// Autocompleter.Base handles all the autocompletion functionality
// that's independent of the data source for autocompletion. This
// includes drawing the autocompletion menu, observing keyboard
// and mouse events, and similar.
//
// Specific autocompleters need to provide, at the very least,
// a getUpdatedChoices function that will be invoked every time
// the text inside the monitored textbox changes. This method
// should get the text for which to provide autocompletion by
// invoking this.getEntry(), NOT by directly accessing
// this.element.value. This is to allow incremental tokenized
// autocompletion. Specific auto-completion logic (AJAX, etc)
// belongs in getUpdatedChoices.
//
// Tokenized incremental autocompletion is enabled automatically
// when an autocompleter is instantiated with the 'tokens' option
// in the options parameter, e.g.:
// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
// will incrementally autocomplete with a comma as the token.
// Additionally, ',' in the above example can be replaced with
// a token array, e.g. { tokens: new Array (',', '\n') } which
// enables autocompletion on multiple tokens. This is most
// useful when one of the tokens is \n (a newline), as it
// allows smart autocompletion after linebreaks.
var Autocompleter = {}
Autocompleter.Base = function() {};
Autocompleter.Base.prototype = {
base_initialize: function(element, update, options) {
this.element = $(element);
this.update = $(update);
this.has_focus = false;
this.changed = false;
this.active = false;
this.index = 0;
this.entry_count = 0;
if (this.setOptions)
this.setOptions(options);
else
this.options = {}
this.options.tokens = this.options.tokens || new Array();
this.options.frequency = this.options.frequency || 0.4;
this.options.min_chars = this.options.min_chars || 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';
}
new 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;
Element.hide(this.update);
Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this));
Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this));
},
show: function() {
if(this.update.style.display=='none') this.options.onShow(this.element, this.update);
if(!this.iefix && (navigator.appVersion.indexOf('MSIE')>0) && this.update.style.position=='absolute') {
new Insertion.After(this.update,
'<iframe id="' + this.update.id + '_iefix" '+
'style="display:none;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
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);
}
},
hide: function() {
if(this.update.style.display=='') this.options.onHide(this.element, this.update);
if(this.iefix) Element.hide(this.iefix);
},
startIndicator: function() {
if(this.indicator) Element.show(this.indicator);
},
stopIndicator: function() {
if(this.indicator) Element.hide(this.indicator);
},
onKeyPress: function(event) {
if(this.active)
switch(event.keyCode) {
case Event.KEY_TAB:
case Event.KEY_RETURN:
this.select_entry();
Event.stop(event);
case Event.KEY_ESC:
this.hide();
this.active = false;
return;
case Event.KEY_LEFT:
case Event.KEY_RIGHT:
return;
case Event.KEY_UP:
this.mark_previous();
this.render();
if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
return;
case Event.KEY_DOWN:
this.mark_next();
this.render();
if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
return;
}
else
if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN)
return;
this.changed = true;
this.has_focus = true;
if(this.observer) clearTimeout(this.observer);
this.observer =
setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
},
onHover: function(event) {
var element = Event.findElement(event, 'LI');
if(this.index != element.autocompleteIndex)
{
this.index = element.autocompleteIndex;
this.render();
}
Event.stop(event);
},
onClick: function(event) {
var element = Event.findElement(event, 'LI');
this.index = element.autocompleteIndex;
this.select_entry();
Event.stop(event);
},
onBlur: function(event) {
// needed to make click events working
setTimeout(this.hide.bind(this), 250);
this.has_focus = false;
this.active = false;
},
render: function() {
if(this.entry_count > 0) {
for (var i = 0; i < this.entry_count; i++)
this.index==i ?
Element.addClassName(this.get_entry(i),"selected") :
Element.removeClassName(this.get_entry(i),"selected");
if(this.has_focus) {
if(this.get_current_entry().scrollIntoView)
this.get_current_entry().scrollIntoView(false);
this.show();
this.active = true;
}
} else this.hide();
},
mark_previous: function() {
if(this.index > 0) this.index--
else this.index = this.entry_count-1;
},
mark_next: function() {
if(this.index < this.entry_count-1) this.index++
else this.index = 0;
},
get_entry: function(index) {
return this.update.firstChild.childNodes[index];
},
get_current_entry: function() {
return this.get_entry(this.index);
},
select_entry: function() {
this.active = false;
value = Element.collectTextNodesIgnoreClass(this.get_current_entry(), 'informal').unescapeHTML();
this.updateElement(value);
this.element.focus();
},
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+/);
if (whitespace)
new_value += whitespace[0];
this.element.value = new_value + value;
} else {
this.element.value = value;
}
},
updateChoices: function(choices) {
if(!this.changed && this.has_focus) {
this.update.innerHTML = choices;
Element.cleanWhitespace(this.update);
Element.cleanWhitespace(this.update.firstChild);
if(this.update.firstChild && this.update.firstChild.childNodes) {
this.entry_count =
this.update.firstChild.childNodes.length;
for (var i = 0; i < this.entry_count; i++) {
entry = this.get_entry(i);
entry.autocompleteIndex = i;
this.addObservers(entry);
}
} else {
this.entry_count = 0;
}
this.stopIndicator();
this.index = 0;
this.render();
}
},
addObservers: function(element) {
Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
Event.observe(element, "click", this.onClick.bindAsEventListener(this));
},
onObserverEvent: function() {
this.changed = false;
if(this.getEntry().length>=this.options.min_chars) {
this.startIndicator();
this.getUpdatedChoices();
} else {
this.active = false;
this.hide();
}
},
getEntry: function() {
var token_pos = this.findLastToken();
if (token_pos != -1)
var ret = this.element.value.substr(token_pos + 1).replace(/^\s+/,'').replace(/\s+$/,'');
else
var ret = this.element.value;
return /\n/.test(ret) ? '' : ret;
},
findLastToken: function() {
var last_token_pos = -1;
for (var i=0; i<this.options.tokens.length; i++) {
var this_token_pos = this.element.value.lastIndexOf(this.options.tokens[i]);
if (this_token_pos > last_token_pos)
last_token_pos = this_token_pos;
}
return last_token_pos;
}
}
Ajax.Autocompleter = Class.create();
Ajax.Autocompleter.prototype = Object.extend(new Autocompleter.Base(),
Object.extend(new Ajax.Base(), {
initialize: function(element, update, url, options) {
this.base_initialize(element, update, options);
this.options.asynchronous = true;
this.options.onComplete = this.onComplete.bind(this)
this.options.method = 'post';
this.options.defaultParams = this.options.parameters || null;
this.url = url;
},
getUpdatedChoices: function() {
entry = encodeURIComponent(this.element.name) + '=' +
encodeURIComponent(this.getEntry());
this.options.parameters = this.options.callback ?
this.options.callback(this.element, entry) : entry;
if(this.options.defaultParams)
this.options.parameters += '&' + this.options.defaultParams;
new Ajax.Request(this.url, this.options);
},
onComplete: function(request) {
this.updateChoices(request.responseText);
}
}));
// The local array autocompleter. Used when you'd prefer to
// inject an array of autocompletion options into the page, rather
// than sending out Ajax queries, which can be quite slow sometimes.
//
// The constructor takes four parameters. The first two are, as usual,
// the id of the monitored textbox, and id of the autocompletion menu.
// The third is the array you want to autocomplete from, and the fourth
// is the options block.
//
// Extra local autocompletion options:
// - choices - How many autocompletion choices to offer
//
// - partial_search - 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).
//
// - full_search - Search anywhere in autocomplete array strings.
//
// - partial_chars - How many characters to enter before triggering
// a partial match (unlike min_chars, which defines
// how many characters are required to do any match
// at all). Defaults to 2.
//
// - ignore_case - Whether to ignore case when autocompleting.
// Defaults to true.
//
// It's possible to pass in a custom function as the 'selector'
// option, if you prefer to write your own autocompletion logic.
// In that case, the other options above will not apply unless
// you support them.
Autocompleter.Local = Class.create();
Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
initialize: function(element, update, array, options) {
this.base_initialize(element, update, options);
this.options.array = array;
},
getUpdatedChoices: function() {
this.updateChoices(this.options.selector(this));
},
setOptions: function(options) {
this.options = Object.extend({
choices: 10,
partial_search: true,
partial_chars: 2,
ignore_case: true,
full_search: false,
selector: function(instance) {
var ret = new Array(); // Beginning matches
var partial = new Array(); // Inside matches
var entry = instance.getEntry();
var count = 0;
for (var i = 0; i < instance.options.array.length &&
ret.length < instance.options.choices ; i++) {
var elem = instance.options.array[i];
var found_pos = instance.options.ignore_case ?
elem.toLowerCase().indexOf(entry.toLowerCase()) :
elem.indexOf(entry);
while (found_pos != -1) {
if (found_pos == 0 && elem.length != entry.length) {
ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
elem.substr(entry.length) + "</li>");
break;
} else if (entry.length >= instance.options.partial_chars &&
instance.options.partial_search && found_pos != -1) {
if (instance.options.full_search || /\s/.test(elem.substr(found_pos-1,1))) {
partial.push("<li>" + elem.substr(0, found_pos) + "<strong>" +
elem.substr(found_pos, entry.length) + "</strong>" + elem.substr(
found_pos + entry.length) + "</li>");
break;
}
}
found_pos = instance.options.ignore_case ?
elem.toLowerCase().indexOf(entry.toLowerCase(), found_pos + 1) :
elem.indexOf(entry, found_pos + 1);
}
}
if (partial.length)
ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
return "<ul>" + ret.join('') + "</ul>";
}
}, options || {});
}
});

537
tracks/public/javascripts/dragdrop.js vendored Normal file
View file

@ -0,0 +1,537 @@
// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
//
// 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
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
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 < arguments.length; i++) {
regEx = new RegExp("^" + arguments[i] + "\\b\\s*|\\s*\\b" + arguments[i] + "\\b", 'g');
element.className = element.className.replace(regEx, '')
}
},
add: function(element) {
element = $(element);
for(var i = 1; i < arguments.length; i++) {
Element.Class.remove(element, arguments[i]);
element.className += (element.className.length > 0 ? ' ' : '') + arguments[i];
}
},
// returns true if all given classes exist in said element
has: function(element) {
element = $(element);
if(!element || !element.className) return false;
var regEx;
for(var i = 1; i < arguments.length; i++) {
regEx = new RegExp("\\b" + arguments[i] + "\\b");
if(!regEx.test(element.className)) return false;
}
return true;
},
// expects arrays of strings and/or strings as optional paramters
// Element.Class.has_any(element, ['classA','classB','classC'], 'classD')
has_any: function(element) {
element = $(element);
if(!element || !element.className) return false;
var regEx;
for(var i = 1; i < arguments.length; i++) {
if((typeof arguments[i] == 'object') &&
(arguments[i].constructor == Array)) {
for(var j = 0; j < arguments[i].length; j++) {
regEx = new RegExp("\\b" + arguments[i][j] + "\\b");
if(regEx.test(element.className)) return true;
}
} else {
regEx = new RegExp("\\b" + arguments[i] + "\\b");
if(regEx.test(element.className)) return true;
}
}
return false;
},
childrenWith: function(element, className) {
var children = $(element).getElementsByTagName('*');
var elements = new Array();
for (var i = 0; i < children.length; i++) {
if (Element.Class.has(children[i], className)) {
elements.push(children[i]);
break;
}
}
return elements;
}
}
/*--------------------------------------------------------------------------*/
var Droppables = {
drops: false,
remove: function(element) {
for(var i = 0; i < this.drops.length; i++)
if(this.drops[i].element == element)
this.drops.splice(i,1);
},
add: function(element) {
var element = $(element);
var options = Object.extend({
greedy: true,
hoverclass: null
}, arguments[1] || {});
// cache containers
if(options.containment) {
options._containers = new Array();
var containment = options.containment;
if((typeof containment == 'object') &&
(containment.constructor == Array)) {
for(var i=0; i<containment.length; i++)
options._containers.push($(containment[i]));
} 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;
var parentNode = element.parentNode;
var i = drop._containers_length;
do { if(parentNode==containers[i]) return true; } while (i--);
return false;
},
is_affected: function(pX, pY, element, drop) {
return (
(drop.element!=element) &&
((!drop._containers) ||
this.is_contained(element, drop)) &&
((!drop.accept) ||
(Element.Class.has_any(element, drop.accept))) &&
Position.within(drop.element, pX, pY) );
},
deactivate: function(drop) {
Element.Class.remove(drop.element, drop.hoverclass);
this.last_active = null;
},
activate: function(drop) {
if(this.last_active) this.deactivate(this.last_active);
if(drop.hoverclass) {
Element.Class.add(drop.element, drop.hoverclass);
this.last_active = drop;
}
},
show: function(event, element) {
if(!this.drops) 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(drop.onHover)
drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
if(drop.greedy) {
this.activate(drop);
return;
}
}
} while (i--);
},
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.last_active.onDrop)
this.last_active.onDrop(element, this.last_active);
},
reset: function() {
if(this.last_active)
this.deactivate(this.last_active);
}
}
Draggables = {
observers: new Array(),
addObserver: function(observer) {
this.observers.push(observer);
},
removeObserver: function(element) { // element instead of obsever fixes mem leaks
for(var i = 0; i < this.observers.length; i++)
if(this.observers[i].element && (this.observers[i].element == element))
this.observers.splice(i,1);
},
notify: function(eventName, draggable) { // 'onStart', 'onEnd'
for(var i = 0; i < this.observers.length; i++)
this.observers[i][eventName](draggable);
}
}
/*--------------------------------------------------------------------------*/
Draggable = Class.create();
Draggable.prototype = {
initialize: function(element) {
var options = Object.extend({
handle: false,
starteffect: function(element) {
new Effect.Opacity(element, {duration:0.2, from:1.0, to:0.7});
},
reverteffect: function(element, top_offset, left_offset) {
new Effect.MoveBy(element, -top_offset, -left_offset, {duration:0.4});
},
endeffect: function(element) {
new Effect.Opacity(element, {duration:0.2, from:0.7, to:1.0});
},
zindex: 1000,
revert: false
}, arguments[1] || {});
this.element = $(element);
this.handle = options.handle ? $(options.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 || "0");
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, "mousedown", this.eventMouseDown);
Event.observe(document, "mouseup", this.eventMouseUp);
Event.observe(document, "mousemove", this.eventMouseMove);
Event.observe(document, "keypress", this.eventKeypress);
},
destroy: function() {
Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
Event.stopObserving(document, "mouseup", this.eventMouseUp);
Event.stopObserving(document, "mousemove", this.eventMouseMove);
Event.stopObserving(document, "keypress", this.eventKeypress);
},
currentLeft: function() {
return parseInt(this.element.style.left || '0');
},
currentTop: function() {
return parseInt(this.element.style.top || '0')
},
startDrag: function(event) {
if(Event.isLeftClick(event)) {
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;
Event.stop(event);
}
},
finishDrag: function(event, success) {
this.active = false;
this.dragging = false;
if(success) Droppables.fire(event, this.element);
Draggables.notify('onEnd', this);
var revert = this.options.revert;
if(revert && typeof revert == 'function') revert = revert(this.element);
if(revert && this.options.reverteffect) {
this.options.reverteffect(this.element,
this.currentTop()-this.originalTop,
this.currentLeft()-this.originalLeft);
} else {
this.originalLeft = this.currentLeft();
this.originalTop = this.currentTop();
}
this.element.style.zIndex = this.originalZ;
if(this.options.endeffect)
this.options.endeffect(this.element);
Droppables.reset();
},
keyPress: function(event) {
if(this.active) {
if(event.keyCode==Event.KEY_ESC) {
this.finishDrag(event, false);
Event.stop(event);
}
}
},
endDrag: function(event) {
if(this.active && this.dragging) {
this.finishDrag(event, true);
Event.stop(event);
}
this.active = false;
this.dragging = false;
},
draw: function(event) {
var 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) + "px";
if((!this.options.constraint) || (this.options.constraint=='vertical'))
style.top = ((event.clientY - this.originalY) - this.offsetY) + "px";
if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
},
update: function(event) {
if(this.active) {
if(!this.dragging) {
var style = this.element.style;
this.dragging = true;
if(style.position=="") style.position = "relative";
style.zIndex = this.options.zindex;
Draggables.notify('onStart', this);
if(this.options.starteffect) this.options.starteffect(this.element);
}
Droppables.show(event, this.element);
this.draw(event);
if(this.options.change) this.options.change(this);
// fix AppleWebKit rendering
if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
Event.stop(event);
}
}
}
/*--------------------------------------------------------------------------*/
SortableObserver = Class.create();
SortableObserver.prototype = {
initialize: function(element, observer) {
this.element = $(element);
this.observer = observer;
this.lastValue = Sortable.serialize(this.element);
},
onStart: function() {
this.lastValue = Sortable.serialize(this.element);
},
onEnd: function() {
if(this.lastValue != Sortable.serialize(this.element))
this.observer(this.element)
}
}
Sortable = {
sortables: new Array(),
options: function(element){
var element = $(element);
for(var i=0;i<this.sortables.length;i++)
if(this.sortables[i].element == element)
return this.sortables[i];
return null;
},
destroy: function(element){
var element = $(element);
for(var i=0;i<this.sortables.length;i++) {
if(this.sortables[i].element == element) {
var s = this.sortables[i];
Draggables.removeObserver(s.element);
for(var j=0;j<s.droppables.length;j++)
Droppables.remove(s.droppables[j]);
for(var j=0;j<s.draggables.length;j++)
s.draggables[j].destroy();
this.sortables.splice(i,1);
}
}
},
create: function(element) {
var element = $(element);
var options = Object.extend({
element: element,
tag: 'li', // assumes li children, override with tag: 'tagname'
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,
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,
constraint: options.constraint,
handle: handle };
if(options.starteffect)
options_for_draggable.starteffect = options.starteffect;
if(options.reverteffect)
options_for_draggable.reverteffect = options.reverteffect;
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>0.5) {
if(dropon.previousSibling != element) {
var oldParentNode = element.parentNode;
element.style.visibility = "hidden"; // fix gecko rendering
dropon.parentNode.insertBefore(element, dropon);
if(dropon.parentNode!=oldParentNode && 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 = "hidden"; // fix gecko rendering
dropon.parentNode.insertBefore(element, nextElement);
if(dropon.parentNode!=oldParentNode && oldParentNode.sortable)
oldParentNode.sortable.onChange(element);
if(dropon.parentNode.sortable)
dropon.parentNode.sortable.onChange(element);
}
}
}
}
// fix for gecko engine
Element.cleanWhitespace(element);
options.draggables = [];
options.droppables = [];
// make it so
var elements = element.childNodes;
for (var i = 0; i < elements.length; i++)
if(elements[i].tagName && elements[i].tagName==options.tag.toUpperCase() &&
(!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]);
}
// keep reference
this.sortables.push(options);
// for onupdate
Draggables.addObserver(new SortableObserver(element, options.onUpdate));
},
serialize: function(element) {
var element = $(element);
var sortableOptions = this.options(element);
var options = Object.extend({
tag: sortableOptions.tag,
only: sortableOptions.only,
name: element.id
}, arguments[1] || {});
var items = $(element).childNodes;
var queryComponents = new Array();
for(var i=0; i<items.length; i++)
if(items[i].tagName && items[i].tagName==options.tag.toUpperCase() &&
(!options.only || (Element.Class.has(items[i], options.only))))
queryComponents.push(
encodeURIComponent(options.name) + "[]=" +
encodeURIComponent(items[i].id.split("_")[1]));
return queryComponents.join("&");
}
}

612
tracks/public/javascripts/effects.js vendored Normal file
View file

@ -0,0 +1,612 @@
// 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/)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Effect = {}
Effect2 = Effect; // deprecated
/* ------------- transitions ------------- */
Effect.Transitions = {}
Effect.Transitions.linear = function(pos) {
return pos;
}
Effect.Transitions.sinoidal = function(pos) {
return (-Math.cos(pos*Math.PI)/2) + 0.5;
}
Effect.Transitions.reverse = function(pos) {
return 1-pos;
}
Effect.Transitions.flicker = function(pos) {
return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random(0.25);
}
Effect.Transitions.wobble = function(pos) {
return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
}
Effect.Transitions.pulse = function(pos) {
return (Math.floor(pos*10) % 2 == 0 ?
(pos*10-Math.floor(pos*10)) : 1-(pos*10-Math.floor(pos*10)));
}
Effect.Transitions.none = function(pos) {
return 0;
}
Effect.Transitions.full = function(pos) {
return 1;
}
/* ------------- element ext -------------- */
Element.makePositioned = function(element) {
element = $(element);
if(element.style.position == "")
element.style.position = "relative";
}
Element.makeClipping = function(element) {
element = $(element);
element._overflow = element.style.overflow || 'visible';
if(element._overflow!='hidden') element.style.overflow = 'hidden';
}
Element.undoClipping = function(element) {
element = $(element);
if(element._overflow!='hidden') element.style.overflow = element._overflow;
}
/* ------------- core effects ------------- */
Effect.Base = function() {};
Effect.Base.prototype = {
setOptions: function(options) {
this.options = Object.extend({
transition: Effect.Transitions.sinoidal,
duration: 1.0, // seconds
fps: 25.0, // max. 100fps
sync: false, // true for combining
from: 0.0,
to: 1.0
}, options || {});
},
start: function(options) {
this.setOptions(options || {});
this.currentFrame = 0;
this.startOn = new Date().getTime();
this.finishOn = this.startOn + (this.options.duration*1000);
if(this.options.beforeStart) this.options.beforeStart(this);
if(!this.options.sync) this.loop();
},
loop: function() {
var timePos = new Date().getTime();
if(timePos >= 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 > this.currentFrame) {
this.render(pos);
this.currentFrame = frame;
}
this.timeout = setTimeout(this.loop.bind(this), 10);
},
render: function(pos) {
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);
if(this.update) this.update(pos);
if(this.options.afterUpdate) this.options.afterUpdate(this);
},
cancel: function() {
if(this.timeout) clearTimeout(this.timeout);
}
}
Effect.Parallel = Class.create();
Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), {
initialize: function(effects) {
this.effects = effects || [];
this.start(arguments[1]);
},
update: function(position) {
for (var i = 0; i < this.effects.length; i++)
this.effects[i].render(position);
},
finish: function(position) {
for (var i = 0; i < this.effects.length; i++)
if(this.effects[i].finish) this.effects[i].finish(position);
}
});
// 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,
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 = "alpha(opacity:"+opacity*100+")";
}
});
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]);
},
update: function(position) {
topd = this.toTop * position + this.originalTop;
leftd = this.toLeft * position + this.originalLeft;
this.setPosition(topd, leftd);
},
setPosition: function(topd, leftd) {
this.element.style.top = topd + "px";
this.element.style.left = leftd + "px";
}
});
Effect.Scale = Class.create();
Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), {
initialize: function(element, percent) {
this.element = $(element)
options = Object.extend({
scaleX: true,
scaleY: true,
scaleContent: true,
scaleFromCenter: false,
scaleMode: 'box', // 'box' or 'contents' or {} with provided values
scaleFrom: 100.0
}, arguments[2] || {});
this.originalTop = this.element.offsetTop;
this.originalLeft = this.element.offsetLeft;
if(this.element.style.fontSize=="") this.sizeEm = 1.0;
if(this.element.style.fontSize && this.element.style.fontSize.indexOf("em")>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);
},
update: function(position) {
currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
if(this.options.scaleContent && this.sizeEm)
this.element.style.fontSize = this.sizeEm*currentScale + "em";
this.setDimensions(
this.originalWidth * currentScale,
this.originalHeight * 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';
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 + "px";
if(this.options.scaleX) this.element.style.left = this.originalLeft-leftd + "px";
} else {
if(this.options.scaleY) this.element.style.top = -topd + "px";
if(this.options.scaleX) this.element.style.left = -leftd + "px";
}
}
}
});
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: "rgb(255, 255, 255)", convert to "#ffffff" format
var endcolor = "#ffffff";
var current = this.element.style.backgroundColor;
if(current && current.slice(0,4) == "rgb(") {
endcolor = "#";
var cols = current.slice(4,current.length-1).split(',');
var i=0; do { endcolor += parseInt(cols[i]).toColorPart() } while (++i<3); }
var options = Object.extend({
startcolor: "#ffff99",
endcolor: endcolor,
restorecolor: current
}, arguments[1] || {});
// 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) ];
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);
},
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)) ];
this.element.style.backgroundColor = "#" +
colors[0].toColorPart() + colors[1].toColorPart() + colors[2].toColorPart();
},
finish: function() {
this.element.style.backgroundColor = this.options.restorecolor;
}
});
Effect.ScrollTo = Class.create();
Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), {
initialize: function(element) {
this.element = $(element);
Position.prepare();
var offsets = Position.cumulativeOffset(this.element);
var max = window.innerHeight ?
window.height - window.innerHeight :
document.body.scrollHeight -
(document.documentElement.clientHeight ?
document.documentElement.clientHeight : document.body.clientHeight);
this.scrollStart = Position.deltaY;
this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart;
this.start(arguments[1] || {});
},
update: function(position) {
Position.prepare();
window.scrollTo(Position.deltaX,
this.scrollStart + (position*this.delta));
}
});
/* ------------- prepackaged effects ------------- */
Effect.Fade = function(element) {
options = Object.extend({
from: 1.0,
to: 0.0,
afterFinish: function(effect)
{ Element.hide(effect.element);
effect.setOpacity(1); }
}, arguments[1] || {});
new Effect.Opacity(element,options);
}
Effect.Appear = function(element) {
options = Object.extend({
from: 0.0,
to: 1.0,
beforeStart: function(effect)
{ effect.setOpacity(0);
Element.show(effect.element); },
afterUpdate: function(effect)
{ Element.show(effect.element); }
}, arguments[1] || {});
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)
{ effect.effects[0].element.style.position = 'absolute'; },
afterFinish: function(effect)
{ Element.hide(effect.effects[0].element); }
}
);
}
Effect.BlindUp = function(element) {
Element.makeClipping(element);
new Effect.Scale(element, 0,
Object.extend({ scaleContent: false,
scaleX: false,
afterFinish: function(effect)
{
Element.hide(effect.element);
Element.undoClipping(effect.element);
}
}, arguments[1] || {})
);
}
Effect.BlindDown = function(element) {
$(element).style.height = '0px';
Element.makeClipping(element);
Element.show(element);
new Effect.Scale(element, 100,
Object.extend({ scaleContent: false,
scaleX: false,
scaleMode: 'contents',
scaleFrom: 0,
afterFinish: function(effect) {
Element.undoClipping(effect.element);
}
}, 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=="")
effect.element.style.position = 'relative'; },
afterFinish: function(effect) { Element.hide(effect.element); }
} )
}
} );
}
Effect.DropOut = function(element) {
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); }
});
}
Effect.Shake = function(element) {
new Effect.MoveBy(element, 0, 20,
{ duration: 0.05, afterFinish: function(effect) {
new Effect.MoveBy(effect.element, 0, -40,
{ duration: 0.1, afterFinish: function(effect) {
new Effect.MoveBy(effect.element, 0, 40,
{ duration: 0.1, afterFinish: function(effect) {
new Effect.MoveBy(effect.element, 0, -40,
{ duration: 0.1, afterFinish: function(effect) {
new Effect.MoveBy(effect.element, 0, 40,
{ duration: 0.1, afterFinish: function(effect) {
new Effect.MoveBy(effect.element, 0, -20,
{ duration: 0.05, afterFinish: function(effect) {
}}) }}) }}) }}) }}) }});
}
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,
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); }
}, 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,
Object.extend({ scaleContent: false,
scaleX: false,
afterUpdate: function(effect)
{ effect.element.firstChild.style.bottom =
(effect.originalHeight - effect.element.clientHeight) + 'px'; },
afterFinish: function(effect)
{
Element.hide(effect.element);
Element.undoClipping(effect.element);
}
}, arguments[1] || {})
);
}
Effect.Squish = function(element) {
new Effect.Scale(element, 0,
{ afterFinish: function(effect) { Element.hide(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 direction = options.direction || 'center';
var moveTransition = options.moveTransition || Effect.Transitions.sinoidal;
var scaleTransition = options.scaleTransition || Effect.Transitions.sinoidal;
var opacityTransition = options.opacityTransition || Effect.Transitions.full;
var initialMoveX, initialMoveY;
var moveX, moveY;
switch (direction) {
case 'top-left':
initialMoveX = initialMoveY = moveX = moveY = 0;
break;
case 'top-right':
initialMoveX = originalWidth;
initialMoveY = moveY = 0;
moveX = -originalWidth;
break;
case 'bottom-left':
initialMoveX = moveX = 0;
initialMoveY = originalHeight;
moveY = -originalHeight;
break;
case 'bottom-right':
initialMoveX = originalWidth;
initialMoveY = originalHeight;
moveX = -originalWidth;
moveY = -originalHeight;
break;
case 'center':
initialMoveX = originalWidth / 2;
initialMoveY = originalHeight / 2;
moveX = -originalWidth / 2;
moveY = -originalHeight / 2;
break;
}
new Effect.MoveBy(element, initialMoveY, initialMoveX, {
duration: 0.01,
beforeUpdate: function(effect) { $(element).style.height = '0px'; },
afterFinish: 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, {
scaleMode: { originalHeight: originalHeight, originalWidth: originalWidth },
sync: true, scaleFrom: 0, scaleTo: 100, transition: scaleTransition })],
options); }
});
}
Effect.Shrink = function(element) {
element = $(element);
var options = arguments[1] || {};
var originalWidth = element.clientWidth;
var originalHeight = element.clientHeight;
element.style.overflow = 'hidden';
Element.show(element);
var direction = options.direction || 'center';
var moveTransition = options.moveTransition || Effect.Transitions.sinoidal;
var scaleTransition = options.scaleTransition || Effect.Transitions.sinoidal;
var opacityTransition = options.opacityTransition || Effect.Transitions.none;
var moveX, moveY;
switch (direction) {
case 'top-left':
moveX = moveY = 0;
break;
case 'top-right':
moveX = originalWidth;
moveY = 0;
break;
case 'bottom-left':
moveX = 0;
moveY = originalHeight;
break;
case 'bottom-right':
moveX = originalWidth;
moveY = originalHeight;
break;
case 'center':
moveX = originalWidth / 2;
moveY = originalHeight / 2;
break;
}
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);
}
Effect.Pulsate = function(element) {
var options = arguments[1] || {};
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); }
}, 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) + "em";
if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
}

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,4 @@
#!/usr/bin/ruby
#!/usr/bin/ruby1.8
if ARGV.empty?
puts "Usage: benchmarker times 'Person.expensive_way' 'Person.another_expensive_way' ..."

View file

@ -1,4 +1,4 @@
#!/usr/bin/ruby
#!/usr/bin/ruby1.8
require 'rubygems'
require_gem 'rails'
require 'breakpoint_client'

View file

@ -1,16 +1,17 @@
#!/usr/bin/ruby
#!/usr/bin/ruby1.8
irb = RUBY_PLATFORM =~ /mswin32/ ? 'irb.bat' : 'irb'
require 'optparse'
options = {}
options = { :sandbox => false, :irb => irb }
OptionParser.new do |opt|
opt.on('-s', '--sandbox', 'Rollback database modifications on exit.') { |options[:sandbox]| }
opt.on("--irb=[#{irb}]", 'Invoke a different irb.') { |options[:irb]| }
opt.parse!(ARGV)
end
libs = " -r #{File.dirname(__FILE__)}/../config/environment"
libs << " -r #{File.dirname(__FILE__)}/console_sandbox" if options[:sandbox]
libs << " -r irb/completion"
libs = " -r irb/completion"
libs << " -r #{File.dirname(__FILE__)}/../config/environment"
libs << " -r console_sandbox" if options[:sandbox]
ENV['RAILS_ENV'] = ARGV.first || 'development'
if options[:sandbox]
@ -19,4 +20,4 @@ if options[:sandbox]
else
puts "Loading #{ENV['RAILS_ENV']} environment."
end
exec "#{irb} #{libs}"
exec "#{options[:irb]} #{libs} --prompt-mode simple"

View file

@ -1,5 +1,7 @@
#!/usr/bin/ruby
#!/usr/bin/ruby1.8
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)

View file

@ -1,5 +1,7 @@
#!/usr/bin/ruby
#!/usr/bin/ruby1.8
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)

View file

@ -1,17 +1,34 @@
#!/usr/bin/ruby
#!/usr/bin/ruby1.8
if ARGV.empty?
puts "Usage: profiler 'Person.expensive_method(10)' [times]"
exit
$stderr.puts "Usage: profiler 'Person.expensive_method(10)' [times]"
exit(1)
end
# Keep the expensive require out of the profile.
$stderr.puts 'Loading Rails...'
require File.dirname(__FILE__) + '/../config/environment'
require "profiler"
# Don't include compilation in the profile
eval(ARGV.first)
# Define a method to profile.
if ARGV[1] and ARGV[1].to_i > 1
eval "def profile_me() #{ARGV[1]}.times { #{ARGV[0]} } end"
else
eval "def profile_me() #{ARGV[0]} end"
end
Profiler__::start_profile
(ARGV[1] || 1).to_i.times { eval(ARGV.first) }
Profiler__::stop_profile
Profiler__::print_profile($stdout)
# Use the ruby-prof extension if available. Fall back to stdlib profiler.
begin
require 'prof'
$stderr.puts 'Using the ruby-prof extension.'
Prof.clock_mode = Prof::GETTIMEOFDAY
Prof.start
profile_me
results = Prof.stop
require 'rubyprof_ext'
Prof.print_profile(results, $stderr)
rescue LoadError
$stderr.puts 'Using the standard Ruby profiler.'
Profiler__.start_profile
profile_me
Profiler__.stop_profile
Profiler__.print_profile($stderr)
end

View file

@ -1,4 +1,29 @@
#!/usr/bin/ruby
#!/usr/bin/ruby1.8
require 'optparse'
options = { :environment => "development" }
ARGV.options do |opts|
script_name = File.basename($0)
opts.banner = "Usage: runner 'puts Person.find(1).name' [options]"
opts.separator ""
opts.on("-e", "--environment=name", String,
"Specifies the environment for the runner to operate under (test/development/production).",
"Default: development") { |options[:environment]| }
opts.separator ""
opts.on("-h", "--help",
"Show this help message.") { puts opts; exit }
opts.parse!
end
ENV["RAILS_ENV"] = options[:environment]
#!/usr/local/bin/ruby
require File.dirname(__FILE__) + '/../config/environment'
eval(ARGV.first)

View file

@ -1,4 +1,4 @@
#!/usr/bin/ruby
#!/usr/bin/ruby1.8
require 'webrick'
require 'optparse'
@ -22,10 +22,7 @@ ARGV.options do |opts|
"Default: 3000") { |OPTIONS[:port]| }
opts.on("-b", "--binding=ip", String,
"Binds Rails to the specified ip.",
"Default: 127.0.0.1") { |OPTIONS[:ip]| }
opts.on("-i", "--index=controller", String,
"Specifies an index controller that requests for root will go to (instead of congratulations screen)."
) { |OPTIONS[:index_controller]| }
"Default: 0.0.0.0") { |OPTIONS[:ip]| }
opts.on("-e", "--environment=name", String,
"Specifies the environment to run this server under (test/development/production).",
"Default: development") { |OPTIONS[:environment]| }
@ -48,4 +45,5 @@ require 'webrick_server'
OPTIONS['working_directory'] = File.expand_path(RAILS_ROOT)
puts "=> Rails application started on http://#{OPTIONS[:ip]}:#{OPTIONS[:port]}"
puts "=> Ctrl-C to shutdown server; call with --help for options" if OPTIONS[:server_type] == WEBrick::SimpleServer
DispatchServlet.dispatch(OPTIONS)

View file

@ -1 +1,46 @@
# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
agenda:
id: 1
name: agenda
user_id: 1
call:
id: 2
name: call
user_id: 1
email:
id: 3
name: email
user_id: 1
errand:
id: 4
name: errand
user_id: 1
lab:
id: 5
name: lab
user_id: 1
library:
id: 6
name: library
user_id: 1
freetime:
id: 7
name: freetime
user_id: 1
office:
id: 8
name: office
user_id: 1
waitingfor:
id: 9
name: waiting for
user_id: 1

View file

@ -1,5 +1,7 @@
# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
first_notes:
id: 1
user_id: 1
another_notes:
id: 2
user_id: 1

View file

@ -1 +1,16 @@
# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
timemachine:
id: 1
name: Build a working time machine
user_id: 1
moremoney:
id: 2
name: Make more money than Billy Gates
user_id: 1
gardenclean:
id: 3
name: Evict dinosaurs from the garden
user_id: 1

View file

@ -1 +1,169 @@
# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
1:
id: 1
context_id: 1
project_id: 2
description: Call Bill Gates to find out how much he makes per day
notes: ~
done: 0
created_at: 2004-11-28 16:01:00
due: 2004-10-30
completed: ~
user_id: 1
2:
id: 2
context_id: 2
project_id: 3
description: Call dinosaur exterminator
notes: Ask him if I need to hire a skip for the corpses.
done: 0
created_at: 2004-11-28 16:06:08
due: 2004-11-30
completed: ~
user_id: 1
3:
id: 3
context_id: 4
project_id: ~
description: Buy milk
notes: ~
done: 1
created_at: 2004-11-28 16:06:31
due: ~
completed: 2004-11-28
user_id: 1
4:
id: 4
context_id: 4
project_id: ~
description: Buy bread
notes: ~
done: 1
created_at: 2004-11-28 16:06:58
due: ~
completed: 2004-11-28
user_id: 1
5:
id: 5
context_id: 5
project_id: 1
description: Construct time dilation device
notes: ~
done: 0
created_at: 2004-11-28 16:07:33
due: ~
completed: ~
user_id: 1
6:
id: 6
context_id: 2
project_id: 1
description: Phone Grandfather to ask about the paradox
notes: Added some _notes_.
done: 0
created_at: 2004-11-28 16:08:33
due: 2004-12-30
completed: ~
user_id: 1
7:
id: 7
context_id: 6
project_id: 3
description: Get a book out of the library
notes: 'Dinosaurs''R'
done: 0
created_at: 2004-12-22 14:07:06
due: ~
completed: ~
user_id: 1
8:
id: 8
context_id: 4
project_id: ~
description: Upgrade to Rails 0.9.1
notes: ~
done: 1
created_at: 2004-12-20 17:02:52
due: 2004-12-21
completed: 2004-12-20
user_id: 1
9:
id: 9
context_id: 1
project_id: ~
description: This should be due today
notes: ~
done: 0
created_at: 2004-12-31 17:23:06
due: 2004-12-31
completed: ~
user_id: 1
10:
id: 10
context_id: 1
project_id: ~
description: foo
notes: ~
done: 1
created_at: 2004-12-31 18:38:34
due: 2005-01-05
completed: 2005-01-02 12:27:10
user_id: 1
11:
id: 11
context_id: 1
project_id: 2
description: Buy shares
notes: ~
done: 0
created_at: 2005-01-01 12:40:26
due: 2005-02-01
completed: ~
user_id: 1
12:
id: 12
context_id: 1
project_id: 3
description: Buy stegosaurus bait
notes: ~
done: 1
created_at: 2005-01-01 12:53:12
due: 2005-01-02
completed: 2005-01-01 15:44:19
user_id: 1
13:
id: 13
context_id: 1
project_id: 3
description: New action in context
notes: Some notes
done: 1
created_at: 2005-01-02 14:52:49
due: 2005-03-01
completed: 2005-01-02 15:44:19
user_id: 1
14:
id: 14
context_id: 2
project_id: 2
description: Call stock broker
notes: 'tel: 12345'
done: 0
created_at: 2005-01-03 11:38:25
due: ~
completed: ~
user_id: 1

View file

@ -1 +1,14 @@
# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
admin_user:
id: 1
login: admin
password: <%= Digest::SHA1.hexdigest("change-me--abracadabra--") %>
word: <%= Digest::SHA1.hexdigest("change-me--badger--") %>
is_admin: 1
other_user:
id: 2
login: jane
password: <%= Digest::SHA1.hexdigest("change-me--sesame--") %>
word: <%= Digest::SHA1.hexdigest("change-me--mouse--") %>
is_admin: 0

View file

@ -1,17 +1,66 @@
require File.dirname(__FILE__) + '/../test_helper'
require 'login_controller'
require_dependency "login_system"
# Re-raise errors caught by the controller.
class LoginController; def rescue_action(e) raise e end; end
class LoginControllerTest < Test::Unit::TestCase
fixtures :users
def setup
@controller = LoginController.new
@request, @response = ActionController::TestRequest.new, ActionController::TestResponse.new
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
end
# Replace this with your real tests.
def test_truth
assert true
def test_login_with_invalid_user
post :login, {:user_login => 'cracker', :user_password => 'secret'}
assert_response :success
assert_template 'login/login'
assert_nil(session['user'])
end
def test_login_with_valid_admin_user
user = login('admin','abracadabra')
assert_equal "Login successful", flash['notice']
assert_redirected_to :controller => 'todo', :action => 'list'
assert_equal 'admin', user.login
assert_equal 1, user.is_admin
assert_equal "#{Digest::SHA1.hexdigest("change-me--badger--")}", user.word
end
def test_login_with_valid_standard_user
user = login('jane','sesame')
assert_equal "Login successful", flash['notice']
assert_redirected_to :controller => 'todo', :action => 'list'
assert_equal 'jane', user.login
assert_equal 0, user.is_admin
assert_equal "#{Digest::SHA1.hexdigest("change-me--mouse--")}", user.word
end
def test_logout
user = login('admin','abracadabra')
get :logout
assert_nil(session['user'])
assert_template 'logout'
end
# TODO: Not sure how to test whether the user is blocked if the admin user is
# not logged in. I tried setting the session[:user] cookie to nil first,
# but that generated an error.
#
def test_create
post :create, :user => {:login => 'newbie',
:password => 'newbiepass',
:password_confirmation => 'newbiepass',
:word => 'turkey'}
assert_equal "Signup successful", flash['notice']
assert_redirected_to :controller => 'todo', :action => 'list'
assert_not_nil(session['user'])
user = User.find(session['user'].id)
assert_equal 'newbie', user.login
assert_equal 0, user.is_admin
end
end

View file

@ -1,14 +1,47 @@
ENV["RAILS_ENV"] = "test"
require File.dirname(__FILE__) + "/../config/environment"
# 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__) + "/../config/environment")
require 'application'
require 'test/unit'
require 'active_record/fixtures'
require 'action_controller/test_process'
require 'action_web_service/test_invoke'
require 'breakpoint'
def create_fixtures(*table_names)
Fixtures.create_fixtures(File.dirname(__FILE__) + "/fixtures", table_names)
end
Test::Unit::TestCase.fixture_path = File.dirname(__FILE__) + "/fixtures/"
Test::Unit::TestCase.fixture_path = File.dirname(__FILE__) + "/fixtures/"
class Test::Unit::TestCase
# Turn these on to use transactional fixtures with table_name(:fixture_name) instantiation of fixtures
# self.use_transactional_fixtures = true
# self.use_instantiated_fixtures = false
def create_fixtures(*table_names)
Fixtures.create_fixtures(File.dirname(__FILE__) + "/fixtures", table_names)
end
# Logs in a user and returns the user object found in the session object
#
def login(login,password)
post :login, {:user_login => login, :user_password => password}
assert_not_nil(session['user'])
return User.find(session['user'].id)
end
# Generates a random string of ascii characters (a-z, "1 0")
# of a given length for testing assignment to fields
# for validation purposes
#
def generate_random_string(length)
string = ""
characters = %w(a b c d e f g h i j k l m n o p q r s t u v w z y z 1\ 0)
length.times do
pick = characters[rand(26)]
string << pick
end
return string
end
end

View file

@ -4,11 +4,11 @@ class NotesTest < Test::Unit::TestCase
fixtures :notes
def setup
@notes = Notes.find(1)
@notes = Note.find(1)
end
# Replace this with your real tests.
def test_truth
assert_kind_of Notes, @notes
assert_kind_of Note, @notes
end
end

View file

@ -1,10 +1,59 @@
require File.dirname(__FILE__) + '/../test_helper'
require 'date'
class TodoTest < Test::Unit::TestCase
fixtures :todos
# Replace this with your real tests.
def test_truth
assert true
def setup
@not_completed1 = Todo.find(1)
@not_completed2 = Todo.find(2)
@completed = Todo.find(8)
end
# Test loading a todo item
def test_load
assert_kind_of Todo, @not_completed1
assert_equal 1, @not_completed1.id
assert_equal 1, @not_completed1.context_id
assert_equal 2, @not_completed1.project_id
assert_equal "Call Bill Gates to find out how much he makes per day", @not_completed1.description
assert_nil @not_completed1.notes
assert_equal 0, @not_completed1.done
assert_equal "2004-11-28 16:01:00", @not_completed1.created_at.strftime("%Y-%m-%d %H:%M:%S")
assert_equal "2004-10-30", @not_completed1.due.strftime("%Y-%m-%d")
assert_nil @not_completed1.completed
assert_equal 1, @not_completed1.user_id
end
def test_completed
assert_kind_of Todo, @completed
assert_equal 1, @completed.done
assert_not_nil @completed.completed
end
# Validation tests
#
def test_validate_presence_of_description
assert_equal "Call dinosaur exterminator", @not_completed2.description
@not_completed2.description = ""
assert !@not_completed2.save
assert_equal 1, @not_completed2.errors.count
assert_equal "can't be blank", @not_completed2.errors.on(:description)
end
def test_validate_length_of_description
assert_equal "Call dinosaur exterminator", @not_completed2.description
@not_completed2.description = generate_random_string(101)
assert !@not_completed2.save
assert_equal 1, @not_completed2.errors.count
assert_equal "is too long (max is 100 characters)", @not_completed2.errors.on(:description)
end
def test_validate_length_of_notes
assert_equal "Ask him if I need to hire a skip for the corpses.", @not_completed2.notes
@not_completed2.notes = generate_random_string(60001)
assert !@not_completed2.save
assert_equal 1, @not_completed2.errors.count
assert_equal "is too long (max is 60000 characters)", @not_completed2.errors.on(:notes)
end
end

View file

@ -3,8 +3,60 @@ require File.dirname(__FILE__) + '/../test_helper'
class UserTest < Test::Unit::TestCase
fixtures :users
# Replace this with your real tests.
def test_truth
assert true
def setup
@admin_user = User.find(1)
@other_user = User.find(2)
end
# Test an admin user model
def test_admin
assert_kind_of User, @admin_user
assert_equal 1, @admin_user.id
assert_equal "admin", @admin_user.login
assert_equal "#{Digest::SHA1.hexdigest("change-me--abracadabra--")}", @admin_user.password
assert_equal "#{Digest::SHA1.hexdigest("change-me--badger--")}", @admin_user.word
assert_equal 1, @admin_user.is_admin
end
# Test a non-admin user model
def test_non_admin
assert_kind_of User, @other_user
assert_equal 2, @other_user.id
assert_equal "jane", @other_user.login
assert_equal "#{Digest::SHA1.hexdigest("change-me--sesame--")}", @other_user.password
assert_equal "#{Digest::SHA1.hexdigest("change-me--mouse--")}", @other_user.word
assert_equal 0, @other_user.is_admin
end
def test_validate_short_password
assert_equal "#{Digest::SHA1.hexdigest("change-me--sesame--")}", @other_user.password
@other_user.password = "four"
assert !@other_user.save
assert_equal 1, @other_user.errors.count
assert_equal "is too short (min is 5 characters)", @other_user.errors.on(:password)
end
# Test a password longer than 40 characters
def test_validate_long_password
assert_equal "#{Digest::SHA1.hexdigest("change-me--sesame--")}", @other_user.password
@other_user.password = generate_random_string(41)
assert !@other_user.save
assert_equal 1, @other_user.errors.count
assert_equal "is too long (max is 40 characters)", @other_user.errors.on(:password)
end
def test_validate_correct_length_password
assert_equal "#{Digest::SHA1.hexdigest("change-me--sesame--")}", @other_user.password
@other_user.password = generate_random_string(6)
assert @other_user.save
end
# Test an invalid user with no password
def test_validate_missing_password
assert_equal 2, @other_user.id
@other_user.password = ""
assert !@other_user.save
assert_equal 2, @other_user.errors.count
assert_equal ["can't be blank", "is too short (min is 5 characters)"], @other_user.errors.on(:password)
end
end