mirror of
https://github.com/TracksApp/tracks.git
synced 2026-01-30 20:55:17 +01:00
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:
parent
2d2f9fcca8
commit
91641500a7
75 changed files with 4054 additions and 1375 deletions
189
tracks/CHANGELOG
189
tracks/CHANGELOG
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 %>
|
||||
|
|
|
|||
|
|
@ -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 %>
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
<%= build_text_page( @not_done, @places, @projects ) %>
|
||||
<%= build_text_page( @not_done, @contexts ) %>
|
||||
|
|
|
|||
22
tracks/app/views/layouts/login.rhtml
Normal file
22
tracks/app/views/layouts/login.rhtml
Normal 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>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
5
tracks/app/views/login/nosignup.rhtml
Normal file
5
tracks/app/views/login/nosignup.rhtml
Normal 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>
|
||||
|
|
@ -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 »" 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 »" 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 »" class="primary" />
|
||||
|
||||
<%= end_form_tag %>
|
||||
</div>
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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}\'?" ) + " "%><%= 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}\'?" ) + " "%><%= 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" ) %> |
|
||||
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 -%>
|
||||
|
|
|
|||
|
|
@ -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" ) %>
|
||||
<%= link_to( image_tag("blank"), { :controller => "note", :action => "show",
|
||||
:id => note.id}, :title => "Show note", :class => "show_notes" ) %>
|
||||
<%= textilize(truncate(note.body, 50, "...")) %>
|
||||
</div>
|
||||
<% note = nil -%>
|
||||
<% note = nil -%>
|
||||
|
|
|
|||
|
|
@ -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 %>
|
||||
|
|
|
|||
|
|
@ -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 %>
|
||||
|
|
|
|||
46
tracks/app/views/shared/add_new_item_form.rhtml
Normal file
46
tracks/app/views/shared/add_new_item_form.rhtml
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
<%
|
||||
case controller.controller_name
|
||||
when "context"
|
||||
add_string = "Add a next action in this context »"
|
||||
when "project"
|
||||
add_string = "Add a next action in this project »"
|
||||
else
|
||||
add_string = "Add a next action »"
|
||||
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] -->
|
||||
31
tracks/app/views/shared/sidebar.rhtml
Normal file
31
tracks/app/views/shared/sidebar.rhtml
Normal 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>
|
||||
|
|
@ -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 %>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
59
tracks/app/views/todo/_item.rhtml
Normal file
59
tracks/app/views/todo/_item.rhtml
Normal 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 %>
|
||||
|
|
@ -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 %>
|
||||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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 »", "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 -->
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -11,3 +11,4 @@ formats:
|
|||
staleness_starts: 7
|
||||
admin:
|
||||
email: butshesagirl@rousette.org.uk
|
||||
loginhash: change-me
|
||||
|
|
|
|||
41
tracks/db/migrate/1_create_tracks_db.rb
Normal file
41
tracks/db/migrate/1_create_tracks_db.rb
Normal 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
|
||||
16
tracks/db/migrate/2_add_user_id.rb
Normal file
16
tracks/db/migrate/2_add_user_id.rb
Normal 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
|
||||
9
tracks/db/migrate/3_created_at.rb
Normal file
9
tracks/db/migrate/3_created_at.rb
Normal 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
|
||||
15
tracks/db/migrate/4_notes.rb
Normal file
15
tracks/db/migrate/4_notes.rb
Normal 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
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/ruby
|
||||
#!/usr/bin/ruby1.8
|
||||
|
||||
require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT)
|
||||
|
||||
|
|
|
|||
|
|
@ -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!
|
||||
|
|
|
|||
|
|
@ -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
446
tracks/public/javascripts/controls.js
vendored
Normal 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
537
tracks/public/javascripts/dragdrop.js
vendored
Normal 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
612
tracks/public/javascripts/effects.js
vendored
Normal 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);
|
||||
}
|
||||
1143
tracks/public/javascripts/prototype.js
vendored
1143
tracks/public/javascripts/prototype.js
vendored
File diff suppressed because it is too large
Load diff
|
|
@ -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' ..."
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/ruby
|
||||
#!/usr/bin/ruby1.8
|
||||
require 'rubygems'
|
||||
require_gem 'rails'
|
||||
require 'breakpoint_client'
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
45
tracks/test/fixtures/contexts.yml
vendored
45
tracks/test/fixtures/contexts.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
2
tracks/test/fixtures/notes.yml
vendored
2
tracks/test/fixtures/notes.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
15
tracks/test/fixtures/projects.yml
vendored
15
tracks/test/fixtures/projects.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
168
tracks/test/fixtures/todos.yml
vendored
168
tracks/test/fixtures/todos.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
13
tracks/test/fixtures/users.yml
vendored
13
tracks/test/fixtures/users.yml
vendored
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue