mirror of
https://github.com/TracksApp/tracks.git
synced 2025-09-21 21:40:48 +02:00
And here's the copy step.
git-svn-id: http://www.rousette.org.uk/svn/tracks-repos/trunk@501 a4c988fc-2ded-0310-b66e-134b36920a42
This commit is contained in:
parent
c34d9faa38
commit
06f14dd3b9
1721 changed files with 200174 additions and 0 deletions
709
tracks/CHANGELOG
Normal file
709
tracks/CHANGELOG
Normal file
|
@ -0,0 +1,709 @@
|
|||
*0.14.1* (October 19th, 2005)
|
||||
|
||||
* Don't clean RAILS_ROOT on windows
|
||||
|
||||
* Remove trailing '/' from RAILS_ROOT [Nicholas Seckar]
|
||||
|
||||
* Upgraded to Active Record 1.12.1 and Action Pack 1.10.1
|
||||
|
||||
|
||||
*0.14.0* (October 16th, 2005)
|
||||
|
||||
* Moved generator folder from RAILS_ROOT/generators to RAILS_ROOT/lib/generators [Tobias Luetke]
|
||||
|
||||
* Fix rake dev and related commands [Nicholas Seckar]
|
||||
|
||||
* The rails command tries to deduce your MySQL socket by running `mysql_config
|
||||
--socket`. If it fails, default to /path/to/your/mysql.sock
|
||||
|
||||
* Made the rails command use the application name for database names in the tailored database.yml file. Example: "rails ~/projects/blog" will use "blog_development" instead of "rails_development". [Florian Weber]
|
||||
|
||||
* Added Rails framework freezing tasks: freeze_gems (freeze to current gems), freeze_edge (freeze to Rails SVN trunk), unfreeze_rails (float with newest gems on system)
|
||||
|
||||
* Added update_javascripts task which will fetch all the latest js files from your current rails install. Use after updating rails. [Tobias Luetke]
|
||||
|
||||
* Added cleaning of RAILS_ROOT to useless elements such as '../non-dot-dot/'. Provides cleaner backtraces and error messages. [Nicholas Seckar]
|
||||
|
||||
* Made the instantiated/transactional fixtures settings be controlled through Rails::Initializer. Transactional and non-instantiated fixtures are default from now on. [Florian Weber]
|
||||
|
||||
* Support using different database adapters for development and test with ActiveRecord::Base.schema_format = :ruby [Sam Stephenson]
|
||||
|
||||
* Make webrick work with session(:off)
|
||||
|
||||
* Add --version, -v option to the Rails command. Closes #1840. [stancell]
|
||||
|
||||
* Update Prototype to V1.4.0_pre11, script.aculo.us to V1.5_rc3 [2504] and fix the rails generator to include the new .js files [Thomas Fuchs]
|
||||
|
||||
* Make the generator skip a file if it already exists and is identical to the new file.
|
||||
|
||||
* Add experimental plugin support #2335
|
||||
|
||||
* Made Rakefile aware of new .js files in script.aculo.us [Thomas Fuchs]
|
||||
|
||||
* Make table_name and controller_name in generators honor AR::Base.pluralize_table_names. #1216 #2213 [kazuhiko@fdiary.net]
|
||||
|
||||
* Clearly label functional and unit tests in rake stats output. #2297 [lasse.koskela@gmail.com]
|
||||
|
||||
* Make the migration generator only check files ending in *.rb when calculating the next file name #2317 [Chad Fowler]
|
||||
|
||||
* Added prevention of duplicate migrations from the generator #2240 [fbeausoleil@ftml.net]
|
||||
|
||||
* Add db_schema_dump and db_schema_import rake tasks to work with the new ActiveRecord::SchemaDumper (for dumping a schema to and reading a schema from a ruby file).
|
||||
|
||||
* Reformed all the config/environments/* files to conform to the new Rails::Configuration approach. Fully backwards compatible.
|
||||
|
||||
* Added create_sessions_table, drop_sessions_table, and purge_sessions_table as rake tasks for databases that supports migrations (MySQL, PostgreSQL, SQLite) to get a table for use with CGI::Session::ActiveRecordStore
|
||||
|
||||
* Added dump of schema version to the db_structure_dump task for databases that support migrations #1835 [Rick Olson]
|
||||
|
||||
* Fixed script/profiler for Ruby 1.8.2 #1863 [Rick Olson]
|
||||
|
||||
* Fixed clone_structure_to_test task for SQLite #1864 [jon@burningbush.us]
|
||||
|
||||
* Added -m/--mime-types option to the WEBrick server, so you can specify a Apache-style mime.types file to load #2059 [ask@develooper.com]
|
||||
|
||||
* Added -c/--svn option to the generator that'll add new files and remove destroyed files using svn add/revert/remove as appropriate #2064 [kevin.clark@gmail.com]
|
||||
|
||||
* Added -c/--charset option to WEBrick server, so you can specify a default charset (which without changes is UTF-8) #2084 [wejn@box.cz]
|
||||
|
||||
* Make the default stats task extendable by modifying the STATS_DIRECTORIES constant
|
||||
|
||||
* Allow the selected environment to define RAILS_DEFAULT_LOGGER, and have Rails::Initializer use it if it exists.
|
||||
|
||||
* Moved all the shared tasks from Rakefile into Rails, so that the Rakefile is empty and doesn't require updating.
|
||||
|
||||
* Added Rails::Initializer and Rails::Configuration to abstract all of the common setup out of config/environment.rb (uses config/boot.rb to bootstrap the initializer and paths)
|
||||
|
||||
* Fixed the scaffold generator to fail right away if the database isn't accessible instead of in mid-air #1169 [Chad Fowler]
|
||||
|
||||
* Corrected project-local generator location in scripts.rb #2010 [Michael Schuerig]
|
||||
|
||||
* Don't require the environment just to clear the logs #2093 [Scott Barron]
|
||||
|
||||
* Make the default rakefile read *.rake files from config/tasks (for easy extension of the rakefile by e.g. generators)
|
||||
|
||||
* Only load breakpoint in development mode and when BREAKPOINT_SERVER_PORT is defined.
|
||||
|
||||
* Allow the --toggle-spin switch on process/reaper to be negated
|
||||
|
||||
* Replace render_partial with render :partial in scaffold generator [Nicholas Seckar]
|
||||
|
||||
* Added -w flag to ps in process/reaper #1934 [Scott Barron]
|
||||
|
||||
* Allow ERb in the database.yml file (just like with fixtures), so you can pull out the database configuration in environment variables #1822 [Duane Johnson]
|
||||
|
||||
* Added convenience controls for FCGI processes (especially when managed remotely): spinner, spawner, and reaper. They reside in script/process. More details can be had by calling them with -h/--help.
|
||||
|
||||
* Added load_fixtures task to the Rakefile, which will load all the fixtures into the database for the current environment #1791 [Marcel Molina]
|
||||
|
||||
* Added an empty robots.txt to public/, so that web servers asking for it won't trigger a dynamic call, like favicon.ico #1738 [michael@schubert]
|
||||
|
||||
* Dropped the 'immediate close-down' of FCGI processes since it didn't work consistently and produced bad responses when it didn't. So now a TERM ensures exit after the next request (just as if the process is handling a request when it receives the signal). This means that you'll have to 'nudge' all FCGI processes with a request in order to ensure that they have all reloaded. This can be done by something like ./script/process/repear --nudge 'http://www.myapp.com' --instances 10, which will load the myapp site 10 times (and thus hit all of the 10 FCGI processes once, enough to shut down).
|
||||
|
||||
|
||||
*0.13.1* (11 July, 2005)
|
||||
|
||||
* Look for app-specific generators in RAILS_ROOT/generators rather than the clunky old RAILS_ROOT/script/generators. Nobody really uses this feature except for the unit tests, so it's a negligible-impact change. If you want to work with third-party generators, drop them in ~/.rails/generators or simply install gems.
|
||||
|
||||
* 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]
|
||||
|
||||
* Fixed that script/server -d was broken so daemon mode couldn't be used #687 [Nicholas Seckar]
|
||||
|
||||
* Upgraded to breakpoint 92 which fixes:
|
||||
|
||||
* overload IRB.parse_opts(), fixes #443
|
||||
=> breakpoints in tests work even when running them via rake
|
||||
* untaint handlers, might fix an issue discussed on the Rails ML
|
||||
* added verbose mode to breakpoint_client
|
||||
* less noise caused by breakpoint_client by default
|
||||
* ignored TerminateLineInput exception in signal handler
|
||||
=> quiet exit on Ctrl-C
|
||||
|
||||
* Added support for independent components residing in /components. Example:
|
||||
|
||||
Controller: components/list/items_controller.rb
|
||||
(holds a List::ItemsController class with uses_component_template_root called)
|
||||
|
||||
Model : components/list/item.rb
|
||||
(namespace is still shared, so an Item model in app/models will take precedence)
|
||||
|
||||
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 [Jeremy Kemper]
|
||||
|
||||
* 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 [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 [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 [Jeremy Kemper]
|
||||
|
||||
* Added Action Web Service as a new add-on framework for Action Pack [Leon Bredt]
|
||||
|
||||
* Added Active Support as an independent utility and standard library extension bundle
|
||||
|
||||
* Upgraded to Active Record 1.7.0, Action Pack 1.5.0, Action Mailer 0.7.0
|
||||
|
||||
|
||||
*0.9.5* (January 25th, 2005)
|
||||
|
||||
* Fixed dependency reloading by switching to a remove_const approach where all Active Records, Active Record Observers, and Action Controllers are reloading by undefining their classes. This enables you to remove methods in all three types and see the change reflected immediately and it fixes #539. This also means that only those three types of classes will benefit from the const_missing and reloading approach. If you want other classes (like some in lib/) to reload, you must use require_dependency to do it.
|
||||
|
||||
* Added Florian Gross' latest version of Breakpointer and friends that fixes a variaty of bugs #441 [Florian Gross]
|
||||
|
||||
* Fixed skeleton Rakefile to work with sqlite3 out of the box #521 [rasputnik]
|
||||
|
||||
* Fixed that script/breakpointer didn't get the Ruby path rewritten as the other scripts #523 [brandt@kurowski.net]
|
||||
|
||||
* Fixed handling of syntax errors in models that had already been succesfully required once in the current interpreter
|
||||
|
||||
* Fixed that models that weren't referenced in associations weren't being reloaded in the development mode by reinstating the reload
|
||||
|
||||
* Fixed that generate scaffold would produce bad functional tests
|
||||
|
||||
* Fixed that FCGI can also display SyntaxErrors
|
||||
|
||||
* Upgraded to Active Record 1.6.0, Action Pack 1.4.0
|
||||
|
||||
|
||||
*0.9.4.1* (January 18th, 2005)
|
||||
|
||||
* Added 5-second timeout to WordNet alternatives on creating reserved-word models #501 [Marcel Molina]
|
||||
|
||||
* Fixed binding of caller #496 [Alexey]
|
||||
|
||||
* Upgraded to Active Record 1.5.1, Action Pack 1.3.1, Action Mailer 0.6.1
|
||||
|
||||
|
||||
*0.9.4* (January 17th, 2005)
|
||||
|
||||
* Added that ApplicationController will catch a ControllerNotFound exception if someone attempts to access a url pointing to an unexisting controller [Tobias Luetke]
|
||||
|
||||
* Flipped code-to-test ratio around to be more readable #468 [Scott Baron]
|
||||
|
||||
* Fixed log file permissions to be 666 instead of 777 (so they're not executable) #471 [Lucas Carlson]
|
||||
|
||||
* Fixed that auto reloading would some times not work or would reload the models twice #475 [Tobias Luetke]
|
||||
|
||||
* Added rewrite rules to deal with caching to public/.htaccess
|
||||
|
||||
* Added the option to specify a controller name to "generate scaffold" and made the default controller name the plural form of the model.
|
||||
|
||||
* Added that rake clone_structure_to_test, db_structure_dump, and purge_test_database tasks now pick up the source database to use from
|
||||
RAILS_ENV instead of just forcing development #424 [Tobias Luetke]
|
||||
|
||||
* Fixed script/console to work with Windows (that requires the use of irb.bat) #418 [octopod]
|
||||
|
||||
* Fixed WEBrick servlet slowdown over time by restricting the load path reloading to mod_ruby
|
||||
|
||||
* Removed Fancy Indexing as a default option on the WEBrick servlet as it made it harder to use various caching schemes
|
||||
|
||||
* Upgraded to Active Record 1.5, Action Pack 1.3, Action Mailer 0.6
|
||||
|
||||
|
||||
*0.9.3* (January 4th, 2005)
|
||||
|
||||
* Added support for SQLite in the auto-dumping/importing of schemas for development -> test #416
|
||||
|
||||
* Added automated rewriting of the shebang lines on installs through the gem rails command #379 [Manfred Stienstra]
|
||||
|
||||
* Added ActionMailer::Base.deliver_method = :test to the test environment so that mail objects are available in ActionMailer::Base.deliveries
|
||||
for functional testing.
|
||||
|
||||
* Added protection for creating a model through the generators with a name of an existing class, like Thread or Date.
|
||||
It'll even offer you a synonym using wordnet.princeton.edu as a look-up. No, I'm not kidding :) [Florian Gross]
|
||||
|
||||
* Fixed dependency management to happen in a unified fashion for Active Record and Action Pack using the new Dependencies module. This means that
|
||||
the environment options needs to change from:
|
||||
|
||||
Before in development.rb:
|
||||
ActionController::Base.reload_dependencies = true
|
||||
ActiveRecord::Base.reload_associations = true
|
||||
|
||||
Now in development.rb:
|
||||
Dependencies.mechanism = :load
|
||||
|
||||
Before in production.rb and test.rb:
|
||||
ActionController::Base.reload_dependencies = false
|
||||
ActiveRecord::Base.reload_associations = false
|
||||
|
||||
Now in production.rb and test.rb:
|
||||
Dependencies.mechanism = :require
|
||||
|
||||
* Fixed problems with dependency caching and controller hierarchies on Ruby 1.8.2 in development mode #351
|
||||
|
||||
* Fixed that generated action_mailers doesnt need to require the action_mailer since thats already done in the environment #382 [Lucas Carlson]
|
||||
|
||||
* Upgraded to Action Pack 1.2.0 and Active Record 1.4.0
|
||||
|
||||
|
||||
*0.9.2*
|
||||
|
||||
* Fixed CTRL-C exists from the Breakpointer to be a clean affair without error dumping [Kent Sibilev]
|
||||
|
||||
* Fixed "rake stats" to work with sub-directories in models and controllers and to report the code to test ration [Scott Baron]
|
||||
|
||||
* Added that Active Record associations are now reloaded instead of cleared to work with the new const_missing hook in Active Record.
|
||||
|
||||
* Added graceful handling of an inaccessible log file by redirecting output to STDERR with a warning #330 [rainmkr]
|
||||
|
||||
* Added support for a -h/--help parameter in the generator #331 [Ulysses]
|
||||
|
||||
* Fixed that File.expand_path in config/environment.rb would fail when dealing with symlinked public directories [mjobin]
|
||||
|
||||
* Upgraded to Action Pack 1.1.0 and Active Record 1.3.0
|
||||
|
||||
|
||||
*0.9.1*
|
||||
|
||||
* Upgraded to Action Pack 1.0.1 for important bug fix
|
||||
|
||||
* Updated gem dependencies
|
||||
|
||||
|
||||
*0.9.0*
|
||||
|
||||
* Renamed public/dispatch.servlet to script/server -- it wasn't really dispatching anyway as its delegating calls to public/dispatch.rb
|
||||
|
||||
* Renamed AbstractApplicationController and abstract_application.rb to ApplicationController and application.rb, so that it will be possible
|
||||
for the framework to automatically pick up on app/views/layouts/application.rhtml and app/helpers/application.rb
|
||||
|
||||
* Added script/console that makes it even easier to start an IRB session for interacting with the domain model. Run with no-args to
|
||||
see help.
|
||||
|
||||
* Added breakpoint support through the script/breakpointer client. This means that you can break out of execution at any point in
|
||||
the code, investigate and change the model, AND then resume execution! Example:
|
||||
|
||||
class WeblogController < ActionController::Base
|
||||
def index
|
||||
@posts = Post.find_all
|
||||
breakpoint "Breaking out from the list"
|
||||
end
|
||||
end
|
||||
|
||||
So the controller will accept the action, run the first line, then present you with a IRB prompt in the breakpointer window.
|
||||
Here you can do things like:
|
||||
|
||||
Executing breakpoint "Breaking out from the list" at .../webrick_server.rb:16 in 'breakpoint'
|
||||
|
||||
>> @posts.inspect
|
||||
=> "[#<Post:0x14a6be8 @attributes={\"title\"=>nil, \"body\"=>nil, \"id\"=>\"1\"}>,
|
||||
#<Post:0x14a6620 @attributes={\"title\"=>\"Rails you know!\", \"body\"=>\"Only ten..\", \"id\"=>\"2\"}>]"
|
||||
>> @posts.first.title = "hello from a breakpoint"
|
||||
=> "hello from a breakpoint"
|
||||
|
||||
...and even better is that you can examine how your runtime objects actually work:
|
||||
|
||||
>> f = @posts.first
|
||||
=> #<Post:0x13630c4 @attributes={"title"=>nil, "body"=>nil, "id"=>"1"}>
|
||||
>> f.
|
||||
Display all 152 possibilities? (y or n)
|
||||
|
||||
Finally, when you're ready to resume execution, you press CTRL-D
|
||||
|
||||
* Changed environments to be configurable through an environment variable. By default, the environment is "development", but you
|
||||
can change that and set your own by configuring the Apache vhost with a string like (mod_env must be available on the server):
|
||||
|
||||
SetEnv RAILS_ENV production
|
||||
|
||||
...if you're using WEBrick, you can pick the environment to use with the command-line parameters -e/--environment, like this:
|
||||
|
||||
ruby public/dispatcher.servlet -e production
|
||||
|
||||
* Added a new default environment called "development", which leaves the production environment to be tuned exclusively for that.
|
||||
|
||||
* Added a start_server in the root of the Rails application to make it even easier to get started
|
||||
|
||||
* Fixed public/.htaccess to use RewriteBase and share the same rewrite rules for all the dispatch methods
|
||||
|
||||
* Fixed webrick_server to handle requests in a serialized manner (the Rails reloading infrastructure is not thread-safe)
|
||||
|
||||
* Added support for controllers in directories. So you can have:
|
||||
|
||||
app/controllers/account_controller.rb # URL: /account/
|
||||
app/controllers/admin/account_controller.rb # URL: /admin/account/
|
||||
|
||||
NOTE: You need to update your public/.htaccess with the new rules to pick it up
|
||||
|
||||
* Added reloading for associations and dependencies under cached environments like FastCGI and mod_ruby. This makes it possible to use
|
||||
those environments for development. This is turned on by default, but can be turned off with
|
||||
ActiveRecord::Base.reload_associations = false and ActionController::Base.reload_dependencies = false in production environments.
|
||||
|
||||
* Added support for sub-directories in app/models. So now you can have something like Basecamp with:
|
||||
|
||||
app/models/accounting
|
||||
app/models/project
|
||||
app/models/participants
|
||||
app/models/settings
|
||||
|
||||
It's poor man's namespacing, but only for file-system organization. You still require files just like before.
|
||||
Nothing changes inside the files themselves.
|
||||
|
||||
|
||||
* Fixed a few references in the tests generated by new_mailer [Jeremy Kemper]
|
||||
|
||||
* Added support for mocks in testing with test/mocks
|
||||
|
||||
* Cleaned up the environments a bit and added global constant RAILS_ROOT
|
||||
|
||||
|
||||
*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 [Jeremy Kemper]:
|
||||
|
||||
def test_complex_stuff
|
||||
@david.projects << @new_project
|
||||
breakpoint "Let's have a closer look at @david"
|
||||
end
|
||||
|
||||
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 [Jeremy Kemper]
|
||||
|
||||
* 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 [Jeremy Kemper]
|
||||
|
||||
* Added configuration of Test::Unit::TestCase.fixture_path to test_helper to concide with the new AR fixtures style
|
||||
|
||||
* Fixed that new_model was generating singular table/fixture names
|
||||
|
||||
* Upgraded to Action Mailer 0.4.0
|
||||
|
||||
* Upgraded to Action Pack 0.9.5
|
||||
|
||||
* Upgraded to Active Record 1.1.0
|
||||
|
||||
|
||||
*0.8.0 (15)*
|
||||
|
||||
* Removed custom_table_name option for new_model now that the Inflector is as powerful as it is
|
||||
|
||||
* Changed the default rake action to just do testing and separate API generation and coding statistics into a "doc" task.
|
||||
|
||||
* Fixed WEBrick dispatcher to handle missing slashes in the URLs gracefully [alexey]
|
||||
|
||||
* Added user option for all postgresql tool calls in the rakefile [elvstone]
|
||||
|
||||
* Fixed problem with running "ruby public/dispatch.servlet" instead of "cd public; ruby dispatch.servlet" [alexey]
|
||||
|
||||
* Fixed WEBrick server so that it no longer hardcodes the ruby interpreter used to "ruby" but will get the one used based
|
||||
on the Ruby runtime configuration. [Marcel Molina Jr.]
|
||||
|
||||
* Fixed Dispatcher so it'll route requests to magic_beans to MagicBeansController/magic_beans_controller.rb [Caio Chassot]
|
||||
|
||||
* "new_controller MagicBeans" and "new_model SubscriptionPayments" will now both behave properly as they use the new Inflector.
|
||||
|
||||
* Fixed problem with MySQL foreign key constraint checks in Rake :clone_production_structure_to_test target [Andreas Schwarz]
|
||||
|
||||
* Changed WEBrick server to by default be auto-reloading, which is slower but makes source changes instant.
|
||||
Class compilation cache can be turned on with "-c" or "--cache-classes".
|
||||
|
||||
* Added "-b/--binding" option to WEBrick dispatcher to bind the server to a specific IP address (default: 127.0.0.1) [Kevin Temp]
|
||||
|
||||
* dispatch.fcgi now DOESN'T set FCGI_PURE_RUBY as it was slowing things down for now reason [Andreas Schwarz]
|
||||
|
||||
* Added new_mailer generator to work with Action Mailer
|
||||
|
||||
* Included new framework: Action Mailer 0.3
|
||||
|
||||
* Upgraded to Action Pack 0.9.0
|
||||
|
||||
* Upgraded to Active Record 1.0.0
|
||||
|
||||
|
||||
*0.7.0*
|
||||
|
||||
* Added an optional second argument to the new_model script that allows the programmer to specify the table name,
|
||||
which will used to generate a custom table_name method in the model and will also be used in the creation of fixtures.
|
||||
[Kevin Radloff]
|
||||
|
||||
* script/new_model now turns AccountHolder into account_holder instead of accountholder [Kevin Radloff]
|
||||
|
||||
* Fixed the faulty handleing of static files with WEBrick [Andreas Schwarz]
|
||||
|
||||
* Unified function_test_helper and unit_test_helper into test_helper
|
||||
|
||||
* Fixed bug with the automated production => test database dropping on PostgreSQL [dhawkins]
|
||||
|
||||
* create_fixtures in both the functional and unit test helper now turns off the log during fixture generation
|
||||
and can generate more than one fixture at a time. Which makes it possible for assignments like:
|
||||
|
||||
@people, @projects, @project_access, @companies, @accounts =
|
||||
create_fixtures "people", "projects", "project_access", "companies", "accounts"
|
||||
|
||||
* Upgraded to Action Pack 0.8.5 (locally-scoped variables, partials, advanced send_file)
|
||||
|
||||
* Upgraded to Active Record 0.9.5 (better table_name guessing, cloning, find_all_in_collection)
|
||||
|
||||
|
||||
*0.6.5*
|
||||
|
||||
* No longer specifies a template for rdoc, so it'll use whatever is default (you can change it in the rakefile)
|
||||
|
||||
* The new_model generator will now use the same rules for plural wordings as Active Record
|
||||
(so Category will give categories, not categorys) [Kevin Radloff]
|
||||
|
||||
* dispatch.fcgi now sets FCGI_PURE_RUBY to true to ensure that it's the Ruby version that's loaded [danp]
|
||||
|
||||
* Made the GEM work with Windows
|
||||
|
||||
* Fixed bug where mod_ruby would "forget" the load paths added when switching between controllers
|
||||
|
||||
* PostgreSQL are now supported for the automated production => test database dropping [Kevin Radloff]
|
||||
|
||||
* Errors thrown by the dispatcher are now properly handled in FCGI.
|
||||
|
||||
* Upgraded to Action Pack 0.8.0 (lots and lots and lots of fixes)
|
||||
|
||||
* Upgraded to Active Record 0.9.4 (a bunch of fixes)
|
||||
|
||||
|
||||
*0.6.0*
|
||||
|
||||
* Added AbstractionApplicationController as a superclass for all controllers generated. This class can be used
|
||||
to carry filters and methods that are to be shared by all. It has an accompanying ApplicationHelper that all
|
||||
controllers will also automatically have available.
|
||||
|
||||
* Added environments that can be included from any script to get the full Active Record and Action Controller
|
||||
context running. This can be used by maintenance scripts or to interact with the model through IRB. Example:
|
||||
|
||||
require 'config/environments/production'
|
||||
|
||||
for account in Account.find_all
|
||||
account.recalculate_interests
|
||||
end
|
||||
|
||||
A short migration script for an account model that had it's interest calculation strategy changed.
|
||||
|
||||
* Accessing the index of a controller with "/weblog" will now redirect to "/weblog/" (only on Apache, not WEBrick)
|
||||
|
||||
* Simplified the default Apache config so even remote requests are served off CGI as a default.
|
||||
You'll now have to do something specific to activate mod_ruby and FCGI (like using the force urls).
|
||||
This should make it easier for new comers that start on an external server.
|
||||
|
||||
* Added more of the necessary Apache options to .htaccess to make it easier to setup
|
||||
|
||||
* Upgraded to Action Pack 0.7.9 (lots of fixes)
|
||||
|
||||
* Upgraded to Active Record 0.9.3 (lots of fixes)
|
||||
|
||||
|
||||
*0.5.7*
|
||||
|
||||
* Fixed bug in the WEBrick dispatcher that prevented it from getting parameters from the URL
|
||||
(through GET requests or otherwise)
|
||||
|
||||
* Added lib in root as a place to store app specific libraries
|
||||
|
||||
* Added lib and vendor to load_path, so anything store within can be loaded directly.
|
||||
Hence lib/redcloth.rb can be loaded with require "redcloth"
|
||||
|
||||
* Upgraded to Action Pack 0.7.8 (lots of fixes)
|
||||
|
||||
* Upgraded to Active Record 0.9.2 (minor upgrade)
|
||||
|
||||
|
||||
*0.5.6*
|
||||
|
||||
* Upgraded to Action Pack 0.7.7 (multipart form fix)
|
||||
|
||||
* Updated the generated template stubs to valid XHTML files
|
||||
|
||||
* Ensure that controllers generated are capitalized, so "new_controller TodoLists"
|
||||
gives the same as "new_controller Todolists" and "new_controller todolists".
|
||||
|
||||
|
||||
*0.5.5*
|
||||
|
||||
* Works on Windows out of the box! (Dropped symlinks)
|
||||
|
||||
* Added webrick dispatcher: Try "ruby public/dispatch.servlet --help" [Florian Gross]
|
||||
|
||||
* Report errors about initialization to browser (instead of attempting to use uninitialized logger)
|
||||
|
||||
* Upgraded to Action Pack 0.7.6
|
||||
|
||||
* Upgraded to Active Record 0.9.1
|
||||
|
||||
* Added distinct 500.html instead of reusing 404.html
|
||||
|
||||
* Added MIT license
|
||||
|
||||
|
||||
*0.5.0*
|
||||
|
||||
* First public release
|
11
tracks/README_DEVELOPERS
Normal file
11
tracks/README_DEVELOPERS
Normal file
|
@ -0,0 +1,11 @@
|
|||
To run selenium tests, start Tracks in test mode using
|
||||
|
||||
script/server -e test
|
||||
|
||||
Then open a browser to
|
||||
|
||||
http://localhost:3000/selenium/
|
||||
|
||||
and interact with the test runner.
|
||||
|
||||
For more information about Selenium on Rails, see vendor/plugins/selenium-on-rails/README
|
190
tracks/README_RAILS
Normal file
190
tracks/README_RAILS
Normal file
|
@ -0,0 +1,190 @@
|
|||
== Welcome to Rails
|
||||
|
||||
Rails is a web-application and persistance framework that includes everything
|
||||
needed to create database-backed web-applications according to the
|
||||
Model-View-Control pattern of separation. This pattern splits the view (also
|
||||
called the presentation) into "dumb" templates that are primarily responsible
|
||||
for inserting pre-build data in between HTML tags. The model contains the
|
||||
"smart" domain objects (such as Account, Product, Person, Post) that holds all
|
||||
the business logic and knows how to persist themselves to a database. The
|
||||
controller handles the incoming requests (such as Save New Account, Update
|
||||
Product, Show Post) by manipulating the model and directing data to the view.
|
||||
|
||||
In Rails, the model is handled by what's called a object-relational mapping
|
||||
layer entitled Active Record. This layer allows you to present the data from
|
||||
database rows as objects and embellish these data objects with business logic
|
||||
methods. You can read more about Active Record in
|
||||
link:files/vendor/rails/activerecord/README.html.
|
||||
|
||||
The controller and view is handled by the Action Pack, which handles both
|
||||
layers by its two parts: Action View and Action Controller. These two layers
|
||||
are bundled in a single package due to their heavy interdependence. This is
|
||||
unlike the relationship between the Active Record and Action Pack that is much
|
||||
more separate. Each of these packages can be used independently outside of
|
||||
Rails. You can read more about Action Pack in
|
||||
link:files/vendor/rails/actionpack/README.html.
|
||||
|
||||
|
||||
== Requirements
|
||||
|
||||
* Database and driver (MySQL, PostgreSQL, or SQLite)
|
||||
* Rake[http://rake.rubyforge.org] for running tests and the generating documentation
|
||||
|
||||
== Optionals
|
||||
|
||||
* Apache 1.3.x or 2.x or lighttpd 1.3.11+ (or any FastCGI-capable webserver with a
|
||||
mod_rewrite-like module)
|
||||
* FastCGI (or mod_ruby) for better performance on Apache
|
||||
|
||||
== Getting started
|
||||
|
||||
1. Run the WEBrick servlet: <tt>ruby script/server</tt>
|
||||
(run with --help for options)
|
||||
2. Go to http://localhost:3000/ and get "Congratulations, you've put Ruby on Rails!"
|
||||
3. Follow the guidelines on the "Congratulations, you've put Ruby on Rails!" screen
|
||||
|
||||
|
||||
== Example for Apache conf
|
||||
|
||||
<VirtualHost *:80>
|
||||
ServerName rails
|
||||
DocumentRoot /path/application/public/
|
||||
ErrorLog /path/application/log/server.log
|
||||
|
||||
<Directory /path/application/public/>
|
||||
Options ExecCGI FollowSymLinks
|
||||
AllowOverride all
|
||||
Allow from all
|
||||
Order allow,deny
|
||||
</Directory>
|
||||
</VirtualHost>
|
||||
|
||||
NOTE: Be sure that CGIs can be executed in that directory as well. So ExecCGI
|
||||
should be on and ".cgi" should respond. All requests from 127.0.0.1 goes
|
||||
through CGI, so no Apache restart is necessary for changes. All other requests
|
||||
goes through FCGI (or mod_ruby) that requires restart to show changes.
|
||||
|
||||
|
||||
== Example for lighttpd conf (with FastCGI)
|
||||
|
||||
server.port = 8080
|
||||
server.bind = "127.0.0.1"
|
||||
# server.event-handler = "freebsd-kqueue" # needed on OS X
|
||||
|
||||
server.modules = ( "mod_rewrite", "mod_fastcgi" )
|
||||
|
||||
url.rewrite = ( "^/$" => "index.html", "^([^.]+)$" => "$1.html" )
|
||||
server.error-handler-404 = "/dispatch.fcgi"
|
||||
|
||||
server.document-root = "/path/application/public"
|
||||
server.errorlog = "/path/application/log/server.log"
|
||||
|
||||
fastcgi.server = ( ".fcgi" =>
|
||||
( "localhost" =>
|
||||
(
|
||||
"min-procs" => 1,
|
||||
"max-procs" => 5,
|
||||
"socket" => "/tmp/application.fcgi.socket",
|
||||
"bin-path" => "/path/application/public/dispatch.fcgi",
|
||||
"bin-environment" => ( "RAILS_ENV" => "development" )
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
== Debugging Rails
|
||||
|
||||
Have "tail -f" commands running on both the server.log, production.log, and
|
||||
test.log files. Rails will automatically display debugging and runtime
|
||||
information to these files. Debugging info will also be shown in the browser
|
||||
on requests from 127.0.0.1.
|
||||
|
||||
|
||||
== Breakpoints
|
||||
|
||||
Breakpoint support is available through the script/breakpointer client. This
|
||||
means that you can break out of execution at any point in the code, investigate
|
||||
and change the model, AND then resume execution! Example:
|
||||
|
||||
class WeblogController < ActionController::Base
|
||||
def index
|
||||
@posts = Post.find_all
|
||||
breakpoint "Breaking out from the list"
|
||||
end
|
||||
end
|
||||
|
||||
So the controller will accept the action, run the first line, then present you
|
||||
with a IRB prompt in the breakpointer window. Here you can do things like:
|
||||
|
||||
Executing breakpoint "Breaking out from the list" at .../webrick_server.rb:16 in 'breakpoint'
|
||||
|
||||
>> @posts.inspect
|
||||
=> "[#<Post:0x14a6be8 @attributes={\"title\"=>nil, \"body\"=>nil, \"id\"=>\"1\"}>,
|
||||
#<Post:0x14a6620 @attributes={\"title\"=>\"Rails you know!\", \"body\"=>\"Only ten..\", \"id\"=>\"2\"}>]"
|
||||
>> @posts.first.title = "hello from a breakpoint"
|
||||
=> "hello from a breakpoint"
|
||||
|
||||
...and even better is that you can examine how your runtime objects actually work:
|
||||
|
||||
>> f = @posts.first
|
||||
=> #<Post:0x13630c4 @attributes={"title"=>nil, "body"=>nil, "id"=>"1"}>
|
||||
>> f.
|
||||
Display all 152 possibilities? (y or n)
|
||||
|
||||
Finally, when you're ready to resume execution, you press CTRL-D
|
||||
|
||||
|
||||
== Console
|
||||
|
||||
You can interact with the domain model by starting the console through script/console.
|
||||
Here you'll have all parts of the application configured, just like it is when the
|
||||
application is running. You can inspect domain models, change values, and save to the
|
||||
database. Start the script without arguments will launch it in the development environment.
|
||||
Passing an argument will specify a different environment, like <tt>console production</tt>.
|
||||
|
||||
|
||||
== Description of contents
|
||||
|
||||
app
|
||||
Holds all the code that's specific to this particular application.
|
||||
|
||||
app/controllers
|
||||
Holds controllers that should be named like weblog_controller.rb for
|
||||
automated URL mapping. All controllers should descend from
|
||||
ActionController::Base.
|
||||
|
||||
app/models
|
||||
Holds models that should be named like post.rb.
|
||||
Most models will descent from ActiveRecord::Base.
|
||||
|
||||
app/views
|
||||
Holds the template files for the view that should be named like
|
||||
weblog/index.rhtml for the WeblogController#index action. All views uses eRuby
|
||||
syntax. This directory can also be used to keep stylesheets, images, and so on
|
||||
that can be symlinked to public.
|
||||
|
||||
app/helpers
|
||||
Holds view helpers that should be named like weblog_helper.rb.
|
||||
|
||||
config
|
||||
Configuration files for the Rails environment, the routing map, the database, and other dependencies.
|
||||
|
||||
components
|
||||
Self-contained mini-applications that can bundle controllers, models, and views together.
|
||||
|
||||
lib
|
||||
Application specific libraries. Basically, any kind of custom code that doesn't
|
||||
belong controllers, models, or helpers. This directory is in the load path.
|
||||
|
||||
public
|
||||
The directory available for the web server. Contains sub-directories for images, stylesheets,
|
||||
and javascripts. Also contains the dispatchers and the default HTML files.
|
||||
|
||||
script
|
||||
Helper scripts for automation and generation.
|
||||
|
||||
test
|
||||
Unit and functional tests along with fixtures.
|
||||
|
||||
vendor
|
||||
External libraries that the application depend on. This directory is in the load path.
|
10
tracks/Rakefile
Normal file
10
tracks/Rakefile
Normal file
|
@ -0,0 +1,10 @@
|
|||
# Add your own tasks in files placed in lib/tasks ending in .rake,
|
||||
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
|
||||
|
||||
require(File.join(File.dirname(__FILE__), 'config', 'boot'))
|
||||
|
||||
require 'rake'
|
||||
require 'rake/testtask'
|
||||
require 'rake/rdoctask'
|
||||
|
||||
require 'tasks/rails'
|
18
tracks/app/apis/todo_api.rb
Normal file
18
tracks/app/apis/todo_api.rb
Normal file
|
@ -0,0 +1,18 @@
|
|||
class TodoApi < ActionWebService::API::Base
|
||||
api_method :new_todo,
|
||||
:expects => [{:username => :string}, {:token => :string}, {:context_id => :int}, {:description => :string}],
|
||||
:returns => [:int]
|
||||
|
||||
api_method :new_rich_todo,
|
||||
:expects => [{:username => :string}, {:token => :string}, {:default_context_id => :int}, {:description => :string}],
|
||||
:returns => [:int]
|
||||
|
||||
api_method :list_contexts,
|
||||
:expects => [{:username => :string}, {:token => :string}],
|
||||
:returns => [[Context]]
|
||||
|
||||
api_method :list_projects,
|
||||
:expects => [{:username => :string}, {:token => :string}],
|
||||
:returns => [[Project]]
|
||||
|
||||
end
|
176
tracks/app/controllers/application.rb
Normal file
176
tracks/app/controllers/application.rb
Normal file
|
@ -0,0 +1,176 @@
|
|||
# The filters added to this controller will be run for all controllers in the application.
|
||||
# Likewise will all the methods added be available for all controllers.
|
||||
|
||||
require_dependency "login_system"
|
||||
require_dependency "source_view"
|
||||
require "redcloth"
|
||||
|
||||
require 'date'
|
||||
require 'time'
|
||||
Tag # We need this in development mode, or you get 'method missing' errors
|
||||
|
||||
class ApplicationController < ActionController::Base
|
||||
|
||||
helper :application
|
||||
include LoginSystem
|
||||
|
||||
layout 'standard'
|
||||
|
||||
before_filter :set_session_expiration
|
||||
prepend_before_filter :login_required
|
||||
|
||||
after_filter :set_charset
|
||||
|
||||
include ActionView::Helpers::TextHelper
|
||||
helper_method :format_date, :markdown
|
||||
|
||||
# By default, sets the charset to UTF-8 if it isn't already set
|
||||
def set_charset
|
||||
headers["Content-Type"] ||= "text/html; charset=UTF-8"
|
||||
end
|
||||
|
||||
def set_session_expiration
|
||||
# http://wiki.rubyonrails.com/rails/show/HowtoChangeSessionOptions
|
||||
unless session == nil
|
||||
return if @controller_name == 'feed' or session['noexpiry'] == "on"
|
||||
# If the method is called by the feed controller (which we don't have under session control)
|
||||
# or if we checked the box to keep logged in on login
|
||||
# don't set the session expiry time.
|
||||
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
|
||||
|
||||
def render_failure message, status = 404
|
||||
render :text => message, :status => status
|
||||
end
|
||||
|
||||
def rescue_action(exception)
|
||||
log_error(exception) if logger
|
||||
respond_to do |wants|
|
||||
wants.html do
|
||||
notify :warning, "An error occurred on the server."
|
||||
render :action => "index"
|
||||
end
|
||||
wants.js { render :action => 'error' }
|
||||
wants.xml { render :text => 'An error occurred on the server.' + $! }
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a count of next actions in the given context or project
|
||||
# The result is count and a string descriptor, correctly pluralised if there are no
|
||||
# actions or multiple actions
|
||||
#
|
||||
def count_undone_todos_phrase(todos_parent, string="actions")
|
||||
count = count_undone_todos(todos_parent)
|
||||
if count == 1
|
||||
word = string.singularize
|
||||
else
|
||||
word = string.pluralize
|
||||
end
|
||||
return count.to_s + " " + word
|
||||
end
|
||||
|
||||
def count_undone_todos(todos_parent)
|
||||
if todos_parent.nil?
|
||||
count = 0
|
||||
elsif (todos_parent.is_a?(Project) && todos_parent.hidden?)
|
||||
count = eval "@project_project_hidden_todo_counts[#{todos_parent.id}]"
|
||||
else
|
||||
count = eval "@#{todos_parent.class.to_s.downcase}_not_done_counts[#{todos_parent.id}]"
|
||||
end
|
||||
count || 0
|
||||
end
|
||||
|
||||
# Convert a date object to the format specified
|
||||
# in config/settings.yml
|
||||
#
|
||||
def format_date(date)
|
||||
if date
|
||||
date_format = @user.prefs.date_format
|
||||
formatted_date = date.strftime("#{date_format}")
|
||||
else
|
||||
formatted_date = ''
|
||||
end
|
||||
formatted_date
|
||||
end
|
||||
|
||||
# Uses RedCloth to transform text using either Textile or Markdown
|
||||
# Need to require redcloth above
|
||||
# RedCloth 3.0 or greater is needed to use Markdown, otherwise it only handles Textile
|
||||
#
|
||||
def markdown(text)
|
||||
RedCloth.new(text).to_html
|
||||
end
|
||||
|
||||
def build_default_project_context_name_map(projects)
|
||||
Hash[*projects.reject{ |p| p.default_context.nil? }.map{ |p| [p.name, p.default_context.name] }.flatten].to_json
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def admin_login_required
|
||||
unless User.find_by_id_and_is_admin(session['user_id'], true)
|
||||
render :text => "401 Unauthorized: Only admin users are allowed access to this function.", :status => 401
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
def redirect_back_or_home
|
||||
redirect_back_or_default home_url
|
||||
end
|
||||
|
||||
def boolean_param(param_name)
|
||||
return false if param_name.blank?
|
||||
s = params[param_name]
|
||||
return false if s.blank? || s == false || s =~ /^false$/i
|
||||
return true if s == true || s =~ /^true$/i
|
||||
raise ArgumentError.new("invalid value for Boolean: \"#{s}\"")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parse_date_per_user_prefs( s )
|
||||
return nil if s.blank?
|
||||
Date.strptime(s, @user.prefs.date_format)
|
||||
end
|
||||
|
||||
def init_data_for_sidebar
|
||||
@projects = @user.projects
|
||||
@contexts = @user.contexts
|
||||
init_not_done_counts
|
||||
if @prefs.show_hidden_projects_in_sidebar
|
||||
init_project_hidden_todo_counts(['project'])
|
||||
end
|
||||
end
|
||||
|
||||
def init_not_done_counts(parents = ['project','context'])
|
||||
parents.each do |parent|
|
||||
eval("@#{parent}_not_done_counts = Todo.count(:conditions => ['user_id = ? and state = ?', @user.id, 'active'], :group => :#{parent}_id)")
|
||||
end
|
||||
end
|
||||
|
||||
def init_project_hidden_todo_counts(parents = ['project','context'])
|
||||
parents.each do |parent|
|
||||
eval("@#{parent}_project_hidden_todo_counts = Todo.count(:conditions => ['user_id = ? and state = ?', @user.id, 'project_hidden'], :group => :#{parent}_id)")
|
||||
end
|
||||
end
|
||||
|
||||
# Set the contents of the flash message from a controller
|
||||
# Usage: notify :warning, "This is the message"
|
||||
# Sets the flash of type 'warning' to "This is the message"
|
||||
def notify(type, message)
|
||||
flash[type] = message
|
||||
logger.error("ERROR: #{message}") if type == :error
|
||||
end
|
||||
|
||||
end
|
96
tracks/app/controllers/backend_controller.rb
Normal file
96
tracks/app/controllers/backend_controller.rb
Normal file
|
@ -0,0 +1,96 @@
|
|||
class BackendController < ApplicationController
|
||||
wsdl_service_name 'Backend'
|
||||
web_service_api TodoApi
|
||||
web_service_scaffold :invoke
|
||||
skip_before_filter :login_required
|
||||
|
||||
|
||||
def new_todo(username, token, context_id, description)
|
||||
check_token_against_user_word(username, token)
|
||||
check_context_belongs_to_user(context_id)
|
||||
item = create_todo(description, context_id)
|
||||
item.id
|
||||
end
|
||||
|
||||
def new_rich_todo(username, token, default_context_id, description)
|
||||
check_token_against_user_word(username,token)
|
||||
description,context = split_by_char('@',description)
|
||||
description,project = split_by_char('>',description)
|
||||
if(!context.nil? && project.nil?)
|
||||
context,project = split_by_char('>',context)
|
||||
end
|
||||
|
||||
context_id = default_context_id
|
||||
unless(context.nil?)
|
||||
found_context = @user.contexts.find_by_namepart(context)
|
||||
context_id = found_context.id unless found_context.nil?
|
||||
end
|
||||
check_context_belongs_to_user(context_id)
|
||||
|
||||
project_id = nil
|
||||
unless(project.blank?)
|
||||
if(project[0..3].downcase == "new:")
|
||||
found_project = @user.projects.build
|
||||
found_project.name = project[4..255+4].strip
|
||||
found_project.save!
|
||||
else
|
||||
found_project = @user.projects.find_by_namepart(project)
|
||||
end
|
||||
project_id = found_project.id unless found_project.nil?
|
||||
end
|
||||
|
||||
todo = create_todo(description, context_id, project_id)
|
||||
todo.id
|
||||
end
|
||||
|
||||
def list_contexts(username, token)
|
||||
check_token_against_user_word(username, token)
|
||||
|
||||
@user.contexts
|
||||
end
|
||||
|
||||
def list_projects(username, token)
|
||||
check_token_against_user_word(username, token)
|
||||
|
||||
@user.projects
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Check whether the token in the URL matches the word in the User's table
|
||||
def check_token_against_user_word(username, token)
|
||||
@user = User.find_by_login( username )
|
||||
unless (token == @user.word)
|
||||
raise(InvalidToken, "Sorry, you don't have permission to perform this action.")
|
||||
end
|
||||
end
|
||||
|
||||
def check_context_belongs_to_user(context_id)
|
||||
unless @user.contexts.exists? context_id
|
||||
raise(CannotAccessContext, "Cannot access a context that does not belong to this user.")
|
||||
end
|
||||
end
|
||||
|
||||
def create_todo(description, context_id, project_id = nil)
|
||||
item = @user.todos.build
|
||||
item.description = description
|
||||
item.context_id = context_id
|
||||
item.project_id = project_id unless project_id.nil?
|
||||
item.save
|
||||
raise item.errors.full_messages.to_s if item.new_record?
|
||||
item
|
||||
end
|
||||
|
||||
def split_by_char(separator,string)
|
||||
parts = string.split(separator)
|
||||
return safe_strip(parts[0]), safe_strip(parts[1])
|
||||
end
|
||||
|
||||
def safe_strip(s)
|
||||
s.strip! unless s.nil?
|
||||
s
|
||||
end
|
||||
end
|
||||
|
||||
class InvalidToken < RuntimeError; end
|
||||
class CannotAccessContext < RuntimeError; end
|
150
tracks/app/controllers/contexts_controller.rb
Normal file
150
tracks/app/controllers/contexts_controller.rb
Normal file
|
@ -0,0 +1,150 @@
|
|||
class ContextsController < ApplicationController
|
||||
|
||||
helper :todos
|
||||
|
||||
before_filter :init, :except => [:create, :destroy, :order]
|
||||
before_filter :init_todos, :only => :show
|
||||
before_filter :set_context_from_params, :only => [:update, :destroy]
|
||||
skip_before_filter :login_required, :only => [:index]
|
||||
prepend_before_filter :login_or_feed_token_required, :only => [:index]
|
||||
session :off, :only => :index, :if => Proc.new { |req| ['rss','atom','txt'].include?(req.parameters[:format]) }
|
||||
|
||||
def index
|
||||
respond_to do |format|
|
||||
format.html &render_contexts_html
|
||||
format.xml { render :xml => @contexts.to_xml( :except => :user_id ) }
|
||||
format.rss &render_contexts_rss_feed
|
||||
format.atom &render_contexts_atom_feed
|
||||
format.text { render :action => 'index_text', :layout => false, :content_type => Mime::TEXT }
|
||||
end
|
||||
end
|
||||
|
||||
def show
|
||||
@page_title = "TRACKS::Context: #{@context.name}"
|
||||
end
|
||||
|
||||
# Example XML usage: curl -H 'Accept: application/xml' -H 'Content-Type: application/xml'
|
||||
# -u username:password
|
||||
# -d '<request><context><name>new context_name</name></context></request>'
|
||||
# http://our.tracks.host/contexts
|
||||
#
|
||||
def create
|
||||
if params[:format] == 'application/xml' && params['exception']
|
||||
render_failure "Expected post format is valid xml like so: <request><context><name>context name</name></context></request>."
|
||||
return
|
||||
end
|
||||
@context = @user.contexts.build
|
||||
params_are_invalid = true
|
||||
if (params['context'] || (params['request'] && params['request']['context']))
|
||||
@context.attributes = params['context'] || params['request']['context']
|
||||
params_are_invalid = false
|
||||
end
|
||||
@saved = @context.save
|
||||
@context_not_done_counts = { @context.id => 0 }
|
||||
respond_to do |wants|
|
||||
wants.js
|
||||
wants.xml do
|
||||
if @context.new_record? && params_are_invalid
|
||||
render_failure "Expected post format is valid xml like so: <request><context><name>context name</name></context></request>."
|
||||
elsif @context.new_record?
|
||||
render_failure @context.errors.to_xml
|
||||
else
|
||||
render :xml => @context.to_xml( :except => :user_id )
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Edit the details of the context
|
||||
#
|
||||
def update
|
||||
params['context'] ||= {}
|
||||
success_text = if params['field'] == 'name' && params['value']
|
||||
params['context']['id'] = params['id']
|
||||
params['context']['name'] = params['value']
|
||||
end
|
||||
@context.attributes = params["context"]
|
||||
if @context.save
|
||||
if params['wants_render']
|
||||
render
|
||||
else
|
||||
render :text => success_text || 'Success'
|
||||
end
|
||||
else
|
||||
notify :warning, "Couldn't update new context"
|
||||
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
|
||||
@context.destroy
|
||||
respond_to do |format|
|
||||
format.js
|
||||
format.xml { render :text => "Deleted context #{@context.name}" }
|
||||
end
|
||||
end
|
||||
|
||||
# Methods for changing the sort order of the contexts in the list
|
||||
#
|
||||
def order
|
||||
params["list-contexts"].each_with_index do |id, position|
|
||||
@user.contexts.update(id, :position => position + 1)
|
||||
end
|
||||
render :nothing => true
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def render_contexts_html
|
||||
lambda do
|
||||
@page_title = "TRACKS::List Contexts"
|
||||
@no_contexts = @contexts.empty?
|
||||
render
|
||||
end
|
||||
end
|
||||
|
||||
def render_contexts_rss_feed
|
||||
lambda do
|
||||
render_rss_feed_for @contexts, :feed => Context.feed_options(@user),
|
||||
:item => { :description => lambda { |c| c.summary(count_undone_todos_phrase(c)) } }
|
||||
end
|
||||
end
|
||||
|
||||
def render_contexts_atom_feed
|
||||
lambda do
|
||||
render_atom_feed_for @contexts, :feed => Context.feed_options(@user),
|
||||
:item => { :description => lambda { |c| c.summary(count_undone_todos_phrase(c)) },
|
||||
:author => lambda { |c| nil } }
|
||||
end
|
||||
end
|
||||
|
||||
def set_context_from_params
|
||||
@context = @user.contexts.find_by_params(params)
|
||||
end
|
||||
|
||||
def init
|
||||
@source_view = params['_source_view'] || 'context'
|
||||
# If we exclude completed projects, then we can't display them in the sidebar
|
||||
# if the user sets the preference for them to be shown
|
||||
# @projects = @user.projects.reject { |x| x.completed? }
|
||||
init_data_for_sidebar
|
||||
@todos = @user.todos
|
||||
@done = @user.todos.find_in_state(:all, :completed, :order => "todos.completed_at DESC")
|
||||
end
|
||||
|
||||
def init_todos
|
||||
set_context_from_params
|
||||
@done = @context.done_todos
|
||||
# @not_done_todos = @context.not_done_todos
|
||||
# TODO: Temporarily doing this search manually until I can work out a way
|
||||
# to do the same thing using not_done_todos acts_as_todo_container method
|
||||
# Hides actions in hidden projects from context.
|
||||
@not_done_todos = @context.todos.find(:all, :conditions => ['todos.state = ?', 'active'], :order => "todos.due IS NULL, todos.due ASC, todos.created_at ASC", :include => :project)
|
||||
@count = @not_done_todos.size
|
||||
@default_project_context_name_map = build_default_project_context_name_map(@projects).to_json
|
||||
end
|
||||
|
||||
end
|
87
tracks/app/controllers/data_controller.rb
Normal file
87
tracks/app/controllers/data_controller.rb
Normal file
|
@ -0,0 +1,87 @@
|
|||
class DataController < ApplicationController
|
||||
|
||||
require 'csv'
|
||||
|
||||
def index
|
||||
end
|
||||
|
||||
def import
|
||||
end
|
||||
|
||||
def export
|
||||
# Show list of formats for export
|
||||
end
|
||||
|
||||
# Thanks to a tip by Gleb Arshinov
|
||||
# <http://lists.rubyonrails.org/pipermail/rails/2004-November/000199.html>
|
||||
def yaml_export
|
||||
all_tables = {}
|
||||
|
||||
all_tables['todos'] = @user.todos.find(:all)
|
||||
all_tables['contexts'] = @user.contexts.find(:all)
|
||||
all_tables['projects'] = @user.projects.find(:all)
|
||||
all_tables['notes'] = @user.notes.find(:all)
|
||||
|
||||
result = all_tables.to_yaml
|
||||
result.gsub!(/\n/, "\r\n") # TODO: general functionality for line endings
|
||||
send_data(result, :filename => "tracks_backup.yml", :type => 'text/plain')
|
||||
end
|
||||
|
||||
def csv_actions
|
||||
content_type = 'text/csv'
|
||||
CSV::Writer.generate(result = "") do |csv|
|
||||
csv << ["ID", "Context", "Project", "Description", "Notes",
|
||||
"Created at", "Due", "Completed at", "User ID", "Show from",
|
||||
"state"]
|
||||
@user.todos.find(:all, :include => [:context, :project]).each do |todo|
|
||||
# Format dates in ISO format for easy sorting in spreadsheet
|
||||
# Print context and project names for easy viewing
|
||||
csv << [todo.id, todo.context.name,
|
||||
todo.project_id = todo.project_id.nil? ? "" : todo.project.name,
|
||||
todo.description,
|
||||
todo.notes, todo.created_at.to_formatted_s(:db),
|
||||
todo.due = todo.due? ? todo.due.to_formatted_s(:db) : "",
|
||||
todo.completed_at = todo.completed_at? ? todo.completed_at.to_formatted_s(:db) : "",
|
||||
todo.user_id,
|
||||
todo.show_from = todo.show_from? ? todo.show_from.to_formatted_s(:db) : "",
|
||||
todo.state]
|
||||
end
|
||||
end
|
||||
send_data(result, :filename => "todos.csv", :type => content_type)
|
||||
end
|
||||
|
||||
def csv_notes
|
||||
content_type = 'text/csv'
|
||||
CSV::Writer.generate(result = "") do |csv|
|
||||
csv << ["ID", "User ID", "Project", "Note",
|
||||
"Created at", "Updated at"]
|
||||
@user.notes.find(:all, :include => [:project]).each do |note|
|
||||
# Format dates in ISO format for easy sorting in spreadsheet
|
||||
# Print context and project names for easy viewing
|
||||
csv << [note.id, note.user_id,
|
||||
note.project_id = note.project_id.nil? ? "" : note.project.name,
|
||||
note.body, note.created_at.to_formatted_s(:db),
|
||||
note.updated_at.to_formatted_s(:db)]
|
||||
end
|
||||
end
|
||||
send_data(result, :filename => "notes.csv", :type => content_type)
|
||||
end
|
||||
|
||||
def xml_export
|
||||
result = ""
|
||||
result << @user.todos.find(:all).to_xml
|
||||
result << @user.contexts.find(:all).to_xml(:skip_instruct => true)
|
||||
result << @user.projects.find(:all).to_xml(:skip_instruct => true)
|
||||
result << @user.notes.find(:all).to_xml(:skip_instruct => true)
|
||||
send_data(result, :filename => "tracks_backup.xml", :type => 'text/xml')
|
||||
end
|
||||
|
||||
def yaml_form
|
||||
# Draw the form to input the YAML text data
|
||||
end
|
||||
|
||||
def yaml_import
|
||||
# Logic to load the YAML text file and create new records from data
|
||||
end
|
||||
|
||||
end
|
11
tracks/app/controllers/feedlist_controller.rb
Normal file
11
tracks/app/controllers/feedlist_controller.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
class FeedlistController < ApplicationController
|
||||
|
||||
helper :feedlist
|
||||
|
||||
def index
|
||||
@page_title = 'TRACKS::Feeds'
|
||||
init_data_for_sidebar
|
||||
render :layout => 'standard'
|
||||
end
|
||||
|
||||
end
|
129
tracks/app/controllers/login_controller.rb
Normal file
129
tracks/app/controllers/login_controller.rb
Normal file
|
@ -0,0 +1,129 @@
|
|||
class LoginController < ApplicationController
|
||||
|
||||
layout 'login'
|
||||
skip_before_filter :set_session_expiration
|
||||
skip_before_filter :login_required
|
||||
before_filter :get_current_user
|
||||
open_id_consumer if Tracks::Config.openid_enabled?
|
||||
|
||||
def login
|
||||
@page_title = "TRACKS::Login"
|
||||
@openid_url = cookies[:openid_url] if Tracks::Config.openid_enabled?
|
||||
case request.method
|
||||
when :post
|
||||
if @user = User.authenticate(params['user_login'], params['user_password'])
|
||||
session['user_id'] = @user.id
|
||||
# If checkbox on login page checked, we don't expire the session after 1 hour
|
||||
# of inactivity
|
||||
session['noexpiry'] = params['user_noexpiry']
|
||||
msg = (should_expire_sessions?) ? "will expire after 1 hour of inactivity." : "will not expire."
|
||||
notify :notice, "Login successful: session #{msg}"
|
||||
cookies[:tracks_login] = { :value => @user.login, :expires => Time.now + 1.year }
|
||||
redirect_back_or_home
|
||||
else
|
||||
@login = params['user_login']
|
||||
notify :warning, "Login unsuccessful"
|
||||
end
|
||||
when :get
|
||||
if User.no_users_yet?
|
||||
redirect_to :controller => 'users', :action => 'new'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def begin
|
||||
# If the URL was unusable (either because of network conditions,
|
||||
# a server error, or that the response returned was not an OpenID
|
||||
# identity page), the library will return HTTP_FAILURE or PARSE_ERROR.
|
||||
# Let the user know that the URL is unusable.
|
||||
case open_id_response.status
|
||||
when OpenID::SUCCESS
|
||||
session['openid_url'] = params[:openid_url]
|
||||
session['user_noexpiry'] = params[:user_noexpiry]
|
||||
# The URL was a valid identity URL. Now we just need to send a redirect
|
||||
# to the server using the redirect_url the library created for us.
|
||||
|
||||
# redirect to the server
|
||||
redirect_to open_id_response.redirect_url((request.protocol + request.host_with_port + "/"), url_for(:action => 'complete'))
|
||||
else
|
||||
notify :warning, "Unable to find openid server for <q>#{openid_url}</q>"
|
||||
redirect_to :action => 'login'
|
||||
end
|
||||
end
|
||||
|
||||
def complete
|
||||
openid_url = session['openid_url']
|
||||
if openid_url.blank?
|
||||
notify :error, "expected an openid_url"
|
||||
end
|
||||
|
||||
case open_id_response.status
|
||||
when OpenID::FAILURE
|
||||
# In the case of failure, if info is non-nil, it is the
|
||||
# URL that we were verifying. We include it in the error
|
||||
# message to help the user figure out what happened.
|
||||
if open_id_response.identity_url
|
||||
msg = "Verification of #{openid_url}(#{open_id_response.identity_url}) failed. "
|
||||
else
|
||||
msg = "Verification failed. "
|
||||
end
|
||||
notify :error, open_id_response.msg.to_s + msg
|
||||
|
||||
when OpenID::SUCCESS
|
||||
# Success means that the transaction completed without
|
||||
# error. If info is nil, it means that the user cancelled
|
||||
# the verification.
|
||||
@user = User.find_by_open_id_url(openid_url)
|
||||
unless (@user.nil?)
|
||||
session['user_id'] = @user.id
|
||||
session['noexpiry'] = session['user_noexpiry']
|
||||
msg = (should_expire_sessions?) ? "will expire after 1 hour of inactivity." : "will not expire."
|
||||
notify :notice, "You have successfully verified #{openid_url} as your identity. Login successful: session #{msg}"
|
||||
cookies[:tracks_login] = { :value => @user.login, :expires => Time.now + 1.year }
|
||||
cookies[:openid_url] = { :value => openid_url, :expires => Time.now + 1.year }
|
||||
redirect_back_or_home
|
||||
else
|
||||
notify :warning, "You have successfully verified #{openid_url} as your identity, but you do not have a Tracks account. Please ask your administrator to sign you up."
|
||||
end
|
||||
|
||||
when OpenID::CANCEL
|
||||
notify :warning, "Verification cancelled."
|
||||
|
||||
else
|
||||
notify :warning, "Unknown response status: #{open_id_response.status}"
|
||||
end
|
||||
redirect_to :action => 'login' unless performed?
|
||||
end
|
||||
|
||||
def logout
|
||||
session['user_id'] = nil
|
||||
reset_session
|
||||
notify :notice, "You have been logged out of Tracks."
|
||||
redirect_to :action => "login"
|
||||
end
|
||||
|
||||
def check_expiry
|
||||
# Gets called by periodically_call_remote to check whether
|
||||
# the session has timed out yet
|
||||
unless session == nil
|
||||
if session
|
||||
return unless should_expire_sessions?
|
||||
# Get expiry time (allow ten seconds window for the case where we have none)
|
||||
expiry_time = session['expiry_time'] || Time.now + 10
|
||||
@time_left = expiry_time - Time.now
|
||||
if @time_left < (10*60) # Session will time out before the next check
|
||||
@msg = "Session has timed out. Please "
|
||||
else
|
||||
@msg = ""
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def should_expire_sessions?
|
||||
session['noexpiry'] != "on"
|
||||
end
|
||||
|
||||
end
|
89
tracks/app/controllers/mobile_controller.rb
Normal file
89
tracks/app/controllers/mobile_controller.rb
Normal file
|
@ -0,0 +1,89 @@
|
|||
class MobileController < ApplicationController
|
||||
|
||||
layout 'mobile'
|
||||
|
||||
before_filter :init, :except => :update
|
||||
|
||||
# Plain list of all next actions, paginated 6 per page
|
||||
# Sorted by due date, then creation date
|
||||
#
|
||||
def index
|
||||
@page_title = @desc = "All actions"
|
||||
@todos_pages, @todos = paginate( :todos, :order => 'due IS NULL, due ASC, created_at ASC',
|
||||
:conditions => ['user_id = ? and state = ?', @user.id, "active"],
|
||||
:per_page => 6 )
|
||||
@count = @all_todos.reject { |x| !x.active? || x.context.hide? }.size
|
||||
end
|
||||
|
||||
def detail
|
||||
@item = check_user_return_item
|
||||
@place = @item.context.id
|
||||
end
|
||||
|
||||
def update
|
||||
if params[:id]
|
||||
@item = check_user_return_item
|
||||
@item.update_attributes params[:todo]
|
||||
if params[:todo][:state] == "1"
|
||||
@item.state = "completed"
|
||||
else
|
||||
@item.state = "active"
|
||||
end
|
||||
else
|
||||
params[:todo][:user_id] = @user.id
|
||||
@item = Todo.new(params[:todo]) if params[:todo]
|
||||
end
|
||||
|
||||
if @item.save
|
||||
redirect_to :action => 'index'
|
||||
else
|
||||
self.init
|
||||
if params[:id]
|
||||
render :partial => 'mobile_edit'
|
||||
else
|
||||
render :action => 'show_add_form'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def show_add_form
|
||||
# Just render the view
|
||||
end
|
||||
|
||||
def filter
|
||||
@type = params[:type]
|
||||
case params[:type]
|
||||
when 'context'
|
||||
@context = Context.find( params[:context][:id] )
|
||||
@page_title = @desc = "#{@context.name}"
|
||||
@todos = Todo.find( :all, :order => 'due IS NULL, due ASC, created_at ASC',
|
||||
:conditions => ['user_id = ? and state = ? and context_id = ?', @user.id, "active", @context.id] )
|
||||
@count = @all_todos.reject { |x| x.completed? || x.context_id != @context.id }.size
|
||||
when 'project'
|
||||
@project = Project.find( params[:project][:id] )
|
||||
@page_title = @desc = "#{@project.name}"
|
||||
@todos = Todo.find( :all, :order => 'due IS NULL, due ASC, created_at ASC',
|
||||
:conditions => ['user_id = ? and state = ? and project_id = ?', @user.id, "active", @project.id] )
|
||||
@count = @all_todos.reject { |x| x.completed? || x.project_id != @project.id }.size
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def check_user_return_item
|
||||
item = Todo.find( params['id'] )
|
||||
if @user == item.user
|
||||
return item
|
||||
else
|
||||
notify :warning, "Item and session user mis-match: #{item.user.name} and #{@user.name}!"
|
||||
render_text ""
|
||||
end
|
||||
end
|
||||
|
||||
def init
|
||||
@contexts = @user.contexts.find(:all, :order => 'position ASC')
|
||||
@projects = @user.projects.find_in_state(:all, :active, :order => 'position ASC')
|
||||
@all_todos = @user.todos.find(:all, :conditions => ['state = ? or state = ?', "active", "completed"])
|
||||
end
|
||||
|
||||
end
|
61
tracks/app/controllers/notes_controller.rb
Normal file
61
tracks/app/controllers/notes_controller.rb
Normal file
|
@ -0,0 +1,61 @@
|
|||
class NotesController < ApplicationController
|
||||
|
||||
def index
|
||||
@all_notes = @user.notes
|
||||
@page_title = "TRACKS::All notes"
|
||||
respond_to do |wants|
|
||||
wants.html
|
||||
wants.xml { render :xml => @all_notes.to_xml( :except => :user_id ) }
|
||||
end
|
||||
end
|
||||
|
||||
def show
|
||||
@note = check_user_return_note
|
||||
@page_title = "TRACKS::Note " + @note.id.to_s
|
||||
end
|
||||
|
||||
# Add a new note to this project
|
||||
#
|
||||
def create
|
||||
note = @user.notes.build
|
||||
note.attributes = params["new_note"]
|
||||
|
||||
if note.save
|
||||
render :partial => 'notes_summary', :object => note
|
||||
else
|
||||
render :text => ''
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
note = check_user_return_note
|
||||
if note.destroy
|
||||
render :text => ''
|
||||
else
|
||||
notify :warning, "Couldn't delete note \"#{note.id.to_s}\""
|
||||
render :text => ''
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
note = check_user_return_note
|
||||
note.attributes = params["note"]
|
||||
if note.save
|
||||
render :partial => 'notes', :object => note
|
||||
else
|
||||
notify :warning, "Couldn't update note \"#{note.id.to_s}\""
|
||||
render :text => ''
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def check_user_return_note
|
||||
note = Note.find_by_id( params['id'] )
|
||||
if @user == note.user
|
||||
return note
|
||||
else
|
||||
render :text => ''
|
||||
end
|
||||
end
|
||||
end
|
25
tracks/app/controllers/preferences_controller.rb
Normal file
25
tracks/app/controllers/preferences_controller.rb
Normal file
|
@ -0,0 +1,25 @@
|
|||
class PreferencesController < ApplicationController
|
||||
|
||||
def index
|
||||
@page_title = "TRACKS::Preferences"
|
||||
@prefs = @user.preference
|
||||
end
|
||||
|
||||
def edit
|
||||
@page_title = "TRACKS::Edit Preferences"
|
||||
@prefs = @user.preference
|
||||
|
||||
render :object => @prefs
|
||||
end
|
||||
|
||||
def update
|
||||
user_success = @user.update_attributes(params['user'])
|
||||
prefs_success = @user.preference.update_attributes(params['prefs'])
|
||||
if user_success && prefs_success
|
||||
redirect_to :action => 'index'
|
||||
else
|
||||
render :action => 'edit'
|
||||
end
|
||||
end
|
||||
|
||||
end
|
204
tracks/app/controllers/projects_controller.rb
Normal file
204
tracks/app/controllers/projects_controller.rb
Normal file
|
@ -0,0 +1,204 @@
|
|||
class ProjectsController < ApplicationController
|
||||
|
||||
helper :application, :todos, :notes
|
||||
before_filter :init, :except => [:create, :destroy, :order]
|
||||
before_filter :set_project_from_params, :only => [:update, :destroy, :show]
|
||||
before_filter :default_context_filter, :only => [:create,:update]
|
||||
skip_before_filter :login_required, :only => [:index]
|
||||
prepend_before_filter :login_or_feed_token_required, :only => [:index]
|
||||
session :off, :only => :index, :if => Proc.new { |req| ['rss','atom','txt'].include?(req.parameters[:format]) }
|
||||
|
||||
def index
|
||||
if params[:only_active_with_no_next_actions]
|
||||
@projects = @projects.select { |p| p.active? && count_undone_todos(p) == 0 }
|
||||
end
|
||||
respond_to do |format|
|
||||
format.html &render_projects_html
|
||||
format.xml { render :xml => @projects.to_xml( :except => :user_id ) }
|
||||
format.rss &render_rss_feed
|
||||
format.atom &render_atom_feed
|
||||
format.text &render_text_feed
|
||||
end
|
||||
end
|
||||
|
||||
def show
|
||||
@page_title = "TRACKS::Project: #{@project.name}"
|
||||
@not_done = @project.not_done_todos(:include_project_hidden_todos => true)
|
||||
@deferred = @project.deferred_todos
|
||||
@done = @project.done_todos
|
||||
@count = @not_done.size
|
||||
@next_project = @user.projects.next_from(@project)
|
||||
@previous_project = @user.projects.previous_from(@project)
|
||||
@default_project_context_name_map = build_default_project_context_name_map(@projects).to_json
|
||||
end
|
||||
|
||||
# Example XML usage: curl -H 'Accept: application/xml' -H 'Content-Type: application/xml'
|
||||
# -u username:password
|
||||
# -d '<request><project><name>new project_name</name></project></request>'
|
||||
# http://our.tracks.host/projects
|
||||
#
|
||||
def create
|
||||
if params[:format] == 'application/xml' && params['exception']
|
||||
render_failure "Expected post format is valid xml like so: <request><project><name>project name</name></project></request>."
|
||||
return
|
||||
end
|
||||
@project = @user.projects.build
|
||||
params_are_invalid = true
|
||||
if (params['project'] || (params['request'] && params['request']['project']))
|
||||
@project.attributes = params['project'] || params['request']['project']
|
||||
params_are_invalid = false
|
||||
end
|
||||
@go_to_project = params['go_to_project']
|
||||
@saved = @project.save
|
||||
@project_not_done_counts = { @project.id => 0 }
|
||||
@active_projects_count = @user.projects.count(:conditions => "state = 'active'")
|
||||
@contexts = @user.contexts
|
||||
respond_to do |wants|
|
||||
wants.js
|
||||
wants.xml do
|
||||
if @project.new_record? && params_are_invalid
|
||||
render_failure "Expected post format is valid xml like so: <request><project><name>project name</name></project></request>."
|
||||
elsif @project.new_record?
|
||||
render_failure @project.errors.full_messages.join(', ')
|
||||
else
|
||||
render :xml => @project.to_xml( :except => :user_id )
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Edit the details of the project
|
||||
#
|
||||
def update
|
||||
params['project'] ||= {}
|
||||
if params['project']['state']
|
||||
@state_changed = @project.state != params['project']['state']
|
||||
logger.info "@state_changed: #{@project.state} == #{params['project']['state']} != #{@state_changed}"
|
||||
@project.transition_to(params['project']['state'])
|
||||
params['project'].delete('state')
|
||||
end
|
||||
success_text = if params['field'] == 'name' && params['value']
|
||||
params['project']['id'] = params['id']
|
||||
params['project']['name'] = params['value']
|
||||
end
|
||||
@project.attributes = params['project']
|
||||
if @project.save
|
||||
if boolean_param('wants_render')
|
||||
if (@project.hidden?)
|
||||
@project_project_hidden_todo_counts = Hash.new
|
||||
@project_project_hidden_todo_counts[@project.id] = @project.reload().not_done_todo_count(:include_project_hidden_todos => true)
|
||||
else
|
||||
@project_not_done_counts[@project.id] = @project.reload().not_done_todo_count(:include_project_hidden_todos => true)
|
||||
end
|
||||
@active_projects_count = @user.projects.count(:conditions => "state = 'active'")
|
||||
@hidden_projects_count = @user.projects.count(:conditions => "state = 'hidden'")
|
||||
@completed_projects_count = @user.projects.count(:conditions => "state = 'completed'")
|
||||
render
|
||||
elsif boolean_param('update_status')
|
||||
render :action => 'update_status'
|
||||
elsif boolean_param('update_default_context')
|
||||
render :action => 'update_default_context'
|
||||
else
|
||||
render :text => success_text || 'Success'
|
||||
end
|
||||
else
|
||||
notify :warning, "Couldn't update project"
|
||||
render :text => ''
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@project.destroy
|
||||
@active_projects_count = @user.projects.count(:conditions => "state = 'active'")
|
||||
@hidden_projects_count = @user.projects.count(:conditions => "state = 'hidden'")
|
||||
@completed_projects_count = @user.projects.count(:conditions => "state = 'completed'")
|
||||
respond_to do |format|
|
||||
format.js
|
||||
format.xml { render :text => "Deleted project #{@project.name}" }
|
||||
end
|
||||
end
|
||||
|
||||
def order
|
||||
project_ids = params["list-active-projects"] || params["list-hidden-projects"] || params["list-completed-projects"]
|
||||
projects = @user.projects.update_positions( project_ids )
|
||||
render :nothing => true
|
||||
rescue
|
||||
notify :error, $!
|
||||
redirect_to :action => 'index'
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def render_projects_html
|
||||
lambda do
|
||||
init_project_hidden_todo_counts
|
||||
@page_title = "TRACKS::List Projects"
|
||||
@active_projects = @projects.select{ |p| p.active? }
|
||||
@hidden_projects = @projects.select{ |p| p.hidden? }
|
||||
@completed_projects = @projects.select{ |p| p.completed? }
|
||||
@no_projects = @projects.empty?
|
||||
@projects.cache_note_counts
|
||||
@new_project = @user.projects.build
|
||||
render
|
||||
end
|
||||
end
|
||||
|
||||
def render_rss_feed
|
||||
lambda do
|
||||
render_rss_feed_for @projects, :feed => Project.feed_options(@user),
|
||||
:item => { :title => :name, :description => lambda { |p| summary(p) } }
|
||||
end
|
||||
end
|
||||
|
||||
def render_atom_feed
|
||||
lambda do
|
||||
render_atom_feed_for @projects, :feed => Project.feed_options(@user),
|
||||
:item => { :description => lambda { |p| summary(p) },
|
||||
:title => :name,
|
||||
:author => lambda { |p| nil } }
|
||||
end
|
||||
end
|
||||
|
||||
def render_text_feed
|
||||
lambda do
|
||||
init_project_hidden_todo_counts(['project'])
|
||||
render :action => 'index_text', :layout => false, :content_type => Mime::TEXT
|
||||
end
|
||||
end
|
||||
|
||||
def set_project_from_params
|
||||
@project = @user.projects.find_by_params(params)
|
||||
end
|
||||
|
||||
def init
|
||||
@source_view = params['_source_view'] || 'project'
|
||||
@projects = @user.projects
|
||||
@contexts = @user.contexts
|
||||
@todos = @user.todos
|
||||
@done = @user.todos.find_in_state(:all, :completed, :order => "completed_at DESC")
|
||||
init_data_for_sidebar
|
||||
end
|
||||
|
||||
def default_context_filter
|
||||
p = params['project']
|
||||
p = params['request']['project'] if p.nil? && params['request']
|
||||
p = {} if p.nil?
|
||||
default_context_name = p['default_context_name']
|
||||
p.delete('default_context_name')
|
||||
|
||||
unless default_context_name.blank?
|
||||
default_context = Context.find_or_create_by_name(default_context_name)
|
||||
p['default_context_id'] = default_context.id
|
||||
end
|
||||
end
|
||||
|
||||
def summary(project)
|
||||
project_description = ''
|
||||
project_description += sanitize(markdown( project.description )) unless project.description.blank?
|
||||
project_description += "<p>#{count_undone_todos_phrase(p)}. "
|
||||
project_description += "Project is #{project.state}."
|
||||
project_description += "</p>"
|
||||
project_description
|
||||
end
|
||||
|
||||
end
|
490
tracks/app/controllers/todos_controller.rb
Normal file
490
tracks/app/controllers/todos_controller.rb
Normal file
|
@ -0,0 +1,490 @@
|
|||
class TodosController < ApplicationController
|
||||
|
||||
helper :todos
|
||||
|
||||
append_before_filter :init, :except => [ :destroy, :completed, :completed_archive, :check_deferred ]
|
||||
append_before_filter :get_todo_from_params, :only => [ :edit, :toggle_check, :show, :update, :destroy ]
|
||||
skip_before_filter :login_required, :only => [:index]
|
||||
prepend_before_filter :login_or_feed_token_required, :only => [:index]
|
||||
session :off, :only => :index, :if => Proc.new { |req| is_feed_request(req) }
|
||||
|
||||
layout 'standard'
|
||||
|
||||
def index
|
||||
@projects = @user.projects.find(:all, :include => [ :todos ])
|
||||
@contexts = @user.contexts.find(:all, :include => [ :todos ])
|
||||
|
||||
@contexts_to_show = @contexts.reject {|x| x.hide? }
|
||||
|
||||
respond_to do |format|
|
||||
format.html &render_todos_html
|
||||
format.xml { render :action => 'list.rxml', :layout => false }
|
||||
format.rss &render_rss_feed
|
||||
format.atom &render_atom_feed
|
||||
format.text &render_text_feed
|
||||
format.ics &render_ical_feed
|
||||
end
|
||||
end
|
||||
|
||||
def create
|
||||
@todo = @user.todos.build
|
||||
p = params['request'] || params
|
||||
|
||||
if p['todo']['show_from']
|
||||
p['todo']['show_from'] = parse_date_per_user_prefs(p['todo']['show_from'])
|
||||
end
|
||||
|
||||
@todo.attributes = p['todo']
|
||||
|
||||
if p['todo']['project_id'].blank? && !p['project_name'].blank? && p['project_name'] != 'None'
|
||||
project = @user.projects.find_by_name(p['project_name'].strip)
|
||||
unless project
|
||||
project = @user.projects.build
|
||||
project.name = p['project_name'].strip
|
||||
project.save
|
||||
@new_project_created = true
|
||||
end
|
||||
@todo.project_id = project.id
|
||||
end
|
||||
|
||||
if p['todo']['context_id'].blank? && !p['context_name'].blank?
|
||||
context = @user.contexts.find_by_name(p['context_name'].strip)
|
||||
unless context
|
||||
context = @user.contexts.build
|
||||
context.name = p['context_name'].strip
|
||||
context.save
|
||||
@new_context_created = true
|
||||
@not_done_todos = [@todo]
|
||||
end
|
||||
@todo.context_id = context.id
|
||||
end
|
||||
|
||||
if @todo.due?
|
||||
@todo.due = parse_date_per_user_prefs(p['todo']['due'])
|
||||
else
|
||||
@todo.due = ""
|
||||
end
|
||||
|
||||
@saved = @todo.save
|
||||
if @saved
|
||||
@todo.tag_with(params[:tag_list],@user)
|
||||
@todo.reload
|
||||
end
|
||||
|
||||
respond_to do |wants|
|
||||
wants.html { redirect_to :action => "index" }
|
||||
wants.js do
|
||||
if @saved
|
||||
determine_down_count
|
||||
end
|
||||
render :action => 'create'
|
||||
end
|
||||
wants.xml { render :xml => @todo.to_xml( :root => 'todo', :except => :user_id ) }
|
||||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
end
|
||||
|
||||
def show
|
||||
respond_to do |format|
|
||||
format.xml { render :xml => @todo.to_xml( :root => 'todo', :except => :user_id ) }
|
||||
end
|
||||
end
|
||||
|
||||
# Toggles the 'done' status of the action
|
||||
#
|
||||
def toggle_check
|
||||
@todo.toggle_completion!
|
||||
@saved = @todo.save
|
||||
respond_to do |format|
|
||||
format.js do
|
||||
if @saved
|
||||
@remaining_undone_in_context = @user.contexts.find(@todo.context_id).not_done_todo_count
|
||||
determine_down_count
|
||||
determine_completed_count
|
||||
end
|
||||
render
|
||||
end
|
||||
format.html do
|
||||
if @saved
|
||||
# TODO: I think this will work, but can't figure out how to test it
|
||||
notify :notice, "The action <strong>'#{@todo.description}'</strong> was marked as <strong>#{@todo.completed? ? 'complete' : 'incomplete' }</strong>"
|
||||
redirect_to :action => "index"
|
||||
else
|
||||
notify :notice, "The action <strong>'#{@todo.description}'</strong> was NOT marked as <strong>#{@todo.completed? ? 'complete' : 'incomplete' } due to an error on the server.</strong>", "index"
|
||||
redirect_to :action => "index"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
@todo.tag_with(params[:tag_list],@user)
|
||||
@original_item_context_id = @todo.context_id
|
||||
@original_item_project_id = @todo.project_id
|
||||
@original_item_was_deferred = @todo.deferred?
|
||||
if params['todo']['project_id'].blank? && !params['project_name'].nil?
|
||||
if params['project_name'] == 'None'
|
||||
project = Project.null_object
|
||||
else
|
||||
project = @user.projects.find_by_name(params['project_name'].strip)
|
||||
unless project
|
||||
project = @user.projects.build
|
||||
project.name = params['project_name'].strip
|
||||
project.save
|
||||
@new_project_created = true
|
||||
end
|
||||
end
|
||||
params["todo"]["project_id"] = project.id
|
||||
end
|
||||
|
||||
if params['todo']['context_id'].blank? && !params['context_name'].blank?
|
||||
context = @user.contexts.find_by_name(params['context_name'].strip)
|
||||
unless context
|
||||
context = @user.contexts.build
|
||||
context.name = params['context_name'].strip
|
||||
context.save
|
||||
@new_context_created = true
|
||||
end
|
||||
params["todo"]["context_id"] = context.id
|
||||
end
|
||||
|
||||
if params["todo"].has_key?("due")
|
||||
params["todo"]["due"] = parse_date_per_user_prefs(params["todo"]["due"])
|
||||
else
|
||||
params["todo"]["due"] = ""
|
||||
end
|
||||
|
||||
if params['todo']['show_from']
|
||||
params['todo']['show_from'] = parse_date_per_user_prefs(params['todo']['show_from'])
|
||||
end
|
||||
|
||||
@saved = @todo.update_attributes params["todo"]
|
||||
@context_changed = @original_item_context_id != @todo.context_id
|
||||
@todo_was_activated_from_deferred_state = @original_item_was_deferred && @todo.active?
|
||||
if @context_changed then @remaining_undone_in_context = @user.contexts.find(@original_item_context_id).not_done_todo_count; end
|
||||
@project_changed = @original_item_project_id != @todo.project_id
|
||||
if (@project_changed && !@original_item_project_id.nil?) then @remaining_undone_in_project = @user.projects.find(@original_item_project_id).not_done_todo_count; end
|
||||
determine_down_count
|
||||
end
|
||||
|
||||
def destroy
|
||||
@todo = get_todo_from_params
|
||||
@context_id = @todo.context_id
|
||||
@project_id = @todo.project_id
|
||||
@saved = @todo.destroy
|
||||
|
||||
respond_to do |wants|
|
||||
|
||||
wants.html do
|
||||
if @saved
|
||||
notify :notice, "Successfully deleted next action", 2.0
|
||||
redirect_to :action => 'index'
|
||||
else
|
||||
notify :error, "Failed to delete the action", 2.0
|
||||
redirect_to :action => 'index'
|
||||
end
|
||||
end
|
||||
|
||||
wants.js do
|
||||
if @saved
|
||||
determine_down_count
|
||||
source_view do |from|
|
||||
from.todo do
|
||||
@remaining_undone_in_context = @user.contexts.find(@context_id).not_done_todo_count
|
||||
end
|
||||
end
|
||||
end
|
||||
render
|
||||
end
|
||||
|
||||
wants.xml { render :text => '200 OK. Action deleted.', :status => 200 }
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
def completed
|
||||
@page_title = "TRACKS::Completed tasks"
|
||||
@done = @user.completed_todos
|
||||
@done_today = @done.completed_within @user.time - 1.day
|
||||
@done_this_week = @done.completed_within @user.time - 1.week
|
||||
@done_this_month = @done.completed_within @user.time - 4.week
|
||||
end
|
||||
|
||||
def completed_archive
|
||||
@page_title = "TRACKS::Archived completed tasks"
|
||||
@done = @user.completed_todos
|
||||
@done_archive = @done.completed_more_than @user.time - 28.days
|
||||
end
|
||||
|
||||
def list_deferred
|
||||
@source_view = 'deferred'
|
||||
@page_title = "TRACKS::Tickler"
|
||||
@tickles = @user.deferred_todos
|
||||
@count = @tickles.size
|
||||
@default_project_context_name_map = build_default_project_context_name_map(@projects).to_json
|
||||
end
|
||||
|
||||
# Check for any due tickler items, activate them
|
||||
# Called by periodically_call_remote
|
||||
def check_deferred
|
||||
@due_tickles = @user.deferred_todos.find_and_activate_ready
|
||||
respond_to do |format|
|
||||
format.html { redirect_to home_path }
|
||||
format.js
|
||||
end
|
||||
end
|
||||
|
||||
# /todos/tag/[tag_name] shows all the actions tagged with tag_name
|
||||
#
|
||||
def tag
|
||||
|
||||
@tag = tag_name = params[:name]
|
||||
|
||||
if Tag.find_by_name(tag_name).nil?
|
||||
# TODO: This doesn't work - you get kicked back to the index
|
||||
# with a generic "Error occured on the server error"
|
||||
notify :error, "Tag \'#{@tag}\' does not exist", 2.0
|
||||
@not_done_todos = []
|
||||
else
|
||||
tag_collection = Tag.find_by_name(tag_name).todos
|
||||
@not_done_todos = tag_collection.find(:all, :conditions => ['taggings.user_id = ? and state = ?', @user.id, 'active'])
|
||||
end
|
||||
|
||||
@contexts = @user.contexts.find(:all, :include => [ :todos ])
|
||||
@contexts_to_show = @contexts.reject {|x| x.hide? }
|
||||
|
||||
@page_title = "TRACKS::Tagged with \'#{@tag}\'"
|
||||
# If you've set no_completed to zero, the completed items box
|
||||
# isn't shown on the home page
|
||||
max_completed = @user.prefs.show_number_completed
|
||||
@done = @user.completed_todos.find(:all, :limit => max_completed, :include => [ :context, :project, :tags ]) unless max_completed == 0
|
||||
# Set count badge to number of items with this tag
|
||||
@not_done_todos.empty? ? @count = 0 : @count = @not_done_todos.size
|
||||
@default_project_context_name_map = build_default_project_context_name_map(@projects).to_json
|
||||
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def get_todo_from_params
|
||||
@todo = @user.todos.find(params['id'])
|
||||
end
|
||||
|
||||
def init
|
||||
@source_view = params['_source_view'] || 'todo'
|
||||
init_data_for_sidebar
|
||||
init_todos
|
||||
end
|
||||
|
||||
def with_feed_query_scope(&block)
|
||||
unless TodosController.is_feed_request(request)
|
||||
yield
|
||||
return
|
||||
end
|
||||
|
||||
condition_builder = FindConditionBuilder.new
|
||||
options = Hash.new
|
||||
|
||||
if params.key?('done')
|
||||
condition_builder.add 'todos.state = ?', 'completed'
|
||||
else
|
||||
condition_builder.add 'todos.state = ?', 'active'
|
||||
end
|
||||
|
||||
@title = "Tracks - Next Actions"
|
||||
@description = "Filter: "
|
||||
|
||||
if params.key?('due')
|
||||
due_within = params['due'].to_i
|
||||
due_within_when = @user.time + due_within.days
|
||||
condition_builder.add('todos.due <= ?', due_within_when)
|
||||
due_within_date_s = due_within_when.strftime("%Y-%m-%d")
|
||||
@title << " due today" if (due_within == 0)
|
||||
@title << " due within a week" if (due_within == 6)
|
||||
@description << " with a due date #{due_within_date_s} or earlier"
|
||||
end
|
||||
|
||||
if params.key?('done')
|
||||
done_in_last = params['done'].to_i
|
||||
condition_builder.add('todos.completed_at >= ?', @user.time - done_in_last.days)
|
||||
@title << " actions completed"
|
||||
@description << " in the last #{done_in_last.to_s} days"
|
||||
end
|
||||
|
||||
Todo.with_scope :find => {:conditions => condition_builder.to_conditions} do
|
||||
yield
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def with_parent_resource_scope(&block)
|
||||
if (params[:context_id])
|
||||
context = @user.contexts.find_by_params(params)
|
||||
Todo.with_scope :find => {:conditions => ['todos.context_id = ?', context.id]} do
|
||||
yield
|
||||
end
|
||||
elsif (params[:project_id])
|
||||
project = @user.projects.find_by_params(params)
|
||||
Todo.with_scope :find => {:conditions => ['todos.project_id = ?', project.id]} do
|
||||
yield
|
||||
end
|
||||
else
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
||||
def with_limit_scope(&block)
|
||||
if params.key?('limit')
|
||||
Todo.with_scope :find => {:limit => params['limit']} do
|
||||
yield
|
||||
end
|
||||
#@description = limit ? "Lists the last #{limit} incomplete next actions" : "Lists incomplete next actions"
|
||||
else
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
||||
def init_todos
|
||||
with_feed_query_scope do
|
||||
with_parent_resource_scope do
|
||||
with_limit_scope do
|
||||
|
||||
# Exclude hidden projects from count on home page
|
||||
@todos = @user.todos.find(:all, :conditions => ['todos.state = ? or todos.state = ?', 'active', 'complete'], :include => [ :project, :context, :tags ])
|
||||
|
||||
# Exclude hidden projects from the home page
|
||||
@not_done_todos = @user.todos.find(:all, :conditions => ['todos.state = ?', 'active'], :order => "todos.due IS NULL, todos.due ASC, todos.created_at ASC", :include => [ :project, :context, :tags ])
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def determine_down_count
|
||||
source_view do |from|
|
||||
from.todo do
|
||||
@down_count = Todo.count_by_sql(['SELECT COUNT(*) FROM todos, contexts WHERE todos.context_id = contexts.id and todos.user_id = ? and todos.state = ? and contexts.hide = ?', @user.id, 'active', false])
|
||||
end
|
||||
from.context do
|
||||
@down_count = @user.contexts.find(@todo.context_id).not_done_todo_count
|
||||
end
|
||||
from.project do
|
||||
unless @todo.project_id == nil
|
||||
@down_count = @user.projects.find(@todo.project_id).not_done_todo_count
|
||||
@deferred_count = @user.projects.find(@todo.project_id).deferred_todo_count
|
||||
end
|
||||
end
|
||||
from.deferred do
|
||||
@down_count = @user.todos.count_in_state(:deferred)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def determine_completed_count
|
||||
source_view do |from|
|
||||
from.todo do
|
||||
@completed_count = Todo.count_by_sql(['SELECT COUNT(*) FROM todos, contexts WHERE todos.context_id = contexts.id and todos.user_id = ? and todos.state = ? and contexts.hide = ?', @user.id, 'completed', false])
|
||||
end
|
||||
from.context do
|
||||
@completed_count = @user.contexts.find(@todo.context_id).done_todo_count
|
||||
end
|
||||
from.project do
|
||||
unless @todo.project_id == nil
|
||||
@completed_count = @user.projects.find(@todo.project_id).done_todo_count
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def render_todos_html
|
||||
lambda do
|
||||
@page_title = "TRACKS::List tasks"
|
||||
|
||||
# If you've set no_completed to zero, the completed items box
|
||||
# isn't shown on the home page
|
||||
max_completed = @user.prefs.show_number_completed
|
||||
@done = @user.completed_todos.find(:all, :limit => max_completed, :include => [ :context, :project, :tags ]) unless max_completed == 0
|
||||
|
||||
# Set count badge to number of not-done, not hidden context items
|
||||
@count = @todos.reject { |x| !x.active? || x.context.hide? }.size
|
||||
|
||||
@default_project_context_name_map = build_default_project_context_name_map(@projects).to_json
|
||||
|
||||
render
|
||||
end
|
||||
end
|
||||
|
||||
def render_rss_feed
|
||||
lambda do
|
||||
render_rss_feed_for @todos, :feed => Todo.feed_options(@user),
|
||||
:item => {
|
||||
:title => :description,
|
||||
:link => lambda { |t| context_url(t.context) },
|
||||
:description => todo_feed_content
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def todo_feed_content
|
||||
lambda do |i|
|
||||
item_notes = sanitize(markdown( i.notes )) if i.notes?
|
||||
due = "<div>Due: #{format_date(i.due)}</div>\n" if i.due?
|
||||
done = "<div>Completed: #{format_date(i.completed_at)}</div>\n" if i.completed?
|
||||
context_link = "<a href=\"#{ context_url(i.context) }\">#{ i.context.name }</a>"
|
||||
if i.project_id?
|
||||
project_link = "<a href=\"#{ project_url(i.project) }\">#{ i.project.name }</a>"
|
||||
else
|
||||
project_link = "<em>none</em>"
|
||||
end
|
||||
"#{done||''}#{due||''}#{item_notes||''}\n<div>Project: #{project_link}</div>\n<div>Context: #{context_link}</div>"
|
||||
end
|
||||
end
|
||||
|
||||
def render_atom_feed
|
||||
lambda do
|
||||
render_atom_feed_for @todos, :feed => Todo.feed_options(@user),
|
||||
:item => {
|
||||
:title => :description,
|
||||
:link => lambda { |t| context_url(t.context) },
|
||||
:description => todo_feed_content,
|
||||
:author => lambda { |p| nil }
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def render_text_feed
|
||||
lambda do
|
||||
render :action => 'index_text', :layout => false, :content_type => Mime::TEXT
|
||||
end
|
||||
end
|
||||
|
||||
def render_ical_feed
|
||||
lambda do
|
||||
render :action => 'index_ical', :layout => false, :content_type => Mime::ICS
|
||||
end
|
||||
end
|
||||
|
||||
def self.is_feed_request(req)
|
||||
['rss','atom','txt','ics'].include?(req.parameters[:format])
|
||||
end
|
||||
|
||||
class FindConditionBuilder
|
||||
|
||||
def initialize
|
||||
@queries = Array.new
|
||||
@params = Array.new
|
||||
end
|
||||
|
||||
def add(query, param)
|
||||
@queries << query
|
||||
@params << param
|
||||
end
|
||||
|
||||
def to_conditions
|
||||
[@queries.join(' AND ')] + @params
|
||||
end
|
||||
end
|
||||
|
||||
end
|
251
tracks/app/controllers/users_controller.rb
Normal file
251
tracks/app/controllers/users_controller.rb
Normal file
|
@ -0,0 +1,251 @@
|
|||
class UsersController < ApplicationController
|
||||
|
||||
if Tracks::Config.openid_enabled?
|
||||
open_id_consumer
|
||||
before_filter :begin_open_id_auth, :only => :update_auth_type
|
||||
end
|
||||
|
||||
before_filter :admin_login_required, :only => [ :index, :show, :destroy ]
|
||||
skip_before_filter :login_required, :only => [ :new, :create ]
|
||||
prepend_before_filter :login_optional, :only => [ :new, :create ]
|
||||
|
||||
# GET /users
|
||||
# GET /users.xml
|
||||
def index
|
||||
@users = User.find(:all, :order => 'login')
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
@page_title = "TRACKS::Manage Users"
|
||||
@user_pages, @users = paginate :users, :order => 'login ASC', :per_page => 10
|
||||
@total_users = User.count
|
||||
# When we call users/signup from the admin page
|
||||
# we store the URL so that we get returned here when signup is successful
|
||||
store_location
|
||||
end
|
||||
format.xml { render :xml => @users.to_xml(:except => [ :password ]) }
|
||||
end
|
||||
end
|
||||
|
||||
# GET /users/somelogin
|
||||
# GET /users/somelogin.xml
|
||||
def show
|
||||
@user = User.find_by_login(params[:id])
|
||||
render :xml => @user.to_xml(:except => [ :password ])
|
||||
end
|
||||
|
||||
# GET /users/new
|
||||
def new
|
||||
if User.no_users_yet?
|
||||
@page_title = "TRACKS::Sign up as the admin user"
|
||||
@heading = "Welcome to TRACKS. To get started, please create an admin account:"
|
||||
@user = get_new_user
|
||||
elsif @user && @user.is_admin?
|
||||
@page_title = "TRACKS::Sign up a new user"
|
||||
@heading = "Sign up a new user:"
|
||||
@user = get_new_user
|
||||
else # all other situations (i.e. a non-admin is logged in, or no one is logged in, but we have some users)
|
||||
@page_title = "TRACKS::No signups"
|
||||
@admin_email = User.find_admin.preference.admin_email
|
||||
render :action => "nosignup", :layout => "login"
|
||||
return
|
||||
end
|
||||
render :layout => "login"
|
||||
end
|
||||
|
||||
# Example usage: curl -H 'Accept: application/xml' -H 'Content-Type: application/xml'
|
||||
# -u admin:up2n0g00d
|
||||
# -d '<request><login>username</login><password>abc123</password></request>'
|
||||
# http://our.tracks.host/users
|
||||
#
|
||||
# POST /users
|
||||
# POST /users.xml
|
||||
def create
|
||||
if params['exception']
|
||||
render_failure "Expected post format is valid xml like so: <request><login>username</login><password>abc123</password></request>."
|
||||
return
|
||||
end
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
unless User.no_users_yet? || (@user && @user.is_admin?)
|
||||
@page_title = "No signups"
|
||||
@admin_email = User.find_admin.preference.admin_email
|
||||
render :action => "nosignup", :layout => "login"
|
||||
return
|
||||
end
|
||||
|
||||
user = User.new(params['user'])
|
||||
unless user.valid?
|
||||
session['new_user'] = user
|
||||
redirect_to :action => 'new'
|
||||
return
|
||||
end
|
||||
|
||||
first_user_signing_up = User.no_users_yet?
|
||||
user.is_admin = true if first_user_signing_up
|
||||
if user.save
|
||||
@user = User.authenticate(user.login, params['user']['password'])
|
||||
@user.create_preference
|
||||
@user.save
|
||||
session['user_id'] = @user.id if first_user_signing_up
|
||||
notify :notice, "Signup successful for user #{@user.login}."
|
||||
redirect_back_or_home
|
||||
end
|
||||
return
|
||||
end
|
||||
format.xml do
|
||||
unless User.find_by_id_and_is_admin(session['user_id'], true)
|
||||
render :text => "401 Unauthorized: Only admin users are allowed access to this function.", :status => 401
|
||||
return
|
||||
end
|
||||
unless check_create_user_params
|
||||
render_failure "Expected post format is valid xml like so: <request><login>username</login><password>abc123</password></request>."
|
||||
return
|
||||
end
|
||||
user = User.new(params[:request])
|
||||
user.password_confirmation = params[:request][:password]
|
||||
if user.save
|
||||
render :text => "User created.", :status => 200
|
||||
else
|
||||
render_failure user.errors.to_xml
|
||||
end
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# DELETE /users/somelogin
|
||||
# DELETE /users/somelogin.xml
|
||||
def destroy
|
||||
@deleted_user = User.find_by_id(params[:id])
|
||||
@saved = @deleted_user.destroy
|
||||
@total_users = User.find(:all).size
|
||||
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
if @saved
|
||||
notify :notice, "Successfully deleted user #{@deleted_user.login}", 2.0
|
||||
else
|
||||
notify :error, "Failed to delete user #{@deleted_user.login}", 2.0
|
||||
end
|
||||
redirect_to users_url
|
||||
end
|
||||
format.js
|
||||
format.xml { head :ok }
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def change_password
|
||||
@page_title = "TRACKS::Change password"
|
||||
end
|
||||
|
||||
def update_password
|
||||
@user.change_password(params[:updateuser][:password], params[:updateuser][:password_confirmation])
|
||||
notify :notice, "Password updated."
|
||||
redirect_to :controller => 'preferences'
|
||||
rescue Exception => error
|
||||
notify :error, error.message
|
||||
redirect_to :action => 'change_password'
|
||||
end
|
||||
|
||||
def change_auth_type
|
||||
@page_title = "TRACKS::Change authentication type"
|
||||
end
|
||||
|
||||
def update_auth_type
|
||||
if (params[:user][:auth_type] == 'open_id') && Tracks::Config.openid_enabled?
|
||||
case open_id_response.status
|
||||
when OpenID::SUCCESS
|
||||
# The URL was a valid identity URL. Now we just need to send a redirect
|
||||
# to the server using the redirect_url the library created for us.
|
||||
session['openid_url'] = params[:openid_url]
|
||||
|
||||
# redirect to the server
|
||||
redirect_to open_id_response.redirect_url((request.protocol + request.host_with_port + "/"), url_for(:action => 'complete'))
|
||||
else
|
||||
notify :warning, "Unable to find openid server for <q>#{openid_url}</q>"
|
||||
redirect_to :action => 'change_auth_type'
|
||||
end
|
||||
return
|
||||
end
|
||||
@user.auth_type = params[:user][:auth_type]
|
||||
if @user.save
|
||||
notify :notice, "Authentication type updated."
|
||||
redirect_to :controller => 'preferences'
|
||||
else
|
||||
notify :warning, "There was a problem updating your authentication type: #{ @user.errors.full_messages.join(', ')}"
|
||||
redirect_to :action => 'change_auth_type'
|
||||
end
|
||||
end
|
||||
|
||||
def complete
|
||||
return unless Tracks::Config.openid_enabled?
|
||||
openid_url = session['openid_url']
|
||||
if openid_url.blank?
|
||||
notify :error, "expected an openid_url"
|
||||
end
|
||||
case open_id_response.status
|
||||
when OpenID::FAILURE
|
||||
# In the case of failure, if info is non-nil, it is the
|
||||
# URL that we were verifying. We include it in the error
|
||||
# message to help the user figure out what happened.
|
||||
if open_id_response.identity_url
|
||||
msg = "Verification of #{openid_url}(#{open_id_response.identity_url}) failed. "
|
||||
else
|
||||
msg = "Verification failed. "
|
||||
end
|
||||
notify :error, open_id_response.msg.to_s + msg
|
||||
|
||||
when OpenID::SUCCESS
|
||||
# Success means that the transaction completed without
|
||||
# error. If info is nil, it means that the user cancelled
|
||||
# the verification.
|
||||
@user.auth_type = 'open_id'
|
||||
@user.open_id_url = openid_url
|
||||
if @user.save
|
||||
notify :notice, "You have successfully verified #{openid_url} as your identity and set your authentication type to Open ID."
|
||||
else
|
||||
notify :warning, "You have successfully verified #{openid_url} as your identity but there was a problem saving your authentication preferences."
|
||||
end
|
||||
redirect_to :controller => 'preferences', :action => 'index'
|
||||
|
||||
when OpenID::CANCEL
|
||||
notify :warning, "Verification cancelled."
|
||||
|
||||
else
|
||||
notify :warning, "Unknown response status: #{open_id_response.status}"
|
||||
end
|
||||
redirect_to :action => 'change_auth_type' unless performed?
|
||||
end
|
||||
|
||||
|
||||
def refresh_token
|
||||
@user.crypt_word
|
||||
@user.save
|
||||
notify :notice, "New token successfully generated"
|
||||
redirect_to :controller => 'preferences', :action => 'index'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def get_new_user
|
||||
if session['new_user']
|
||||
user = session['new_user']
|
||||
session['new_user'] = nil
|
||||
else
|
||||
user = User.new
|
||||
end
|
||||
user
|
||||
end
|
||||
|
||||
def check_create_user_params
|
||||
return false unless params.has_key?(:request)
|
||||
return false unless params[:request].has_key?(:login)
|
||||
return false if params[:request][:login].empty?
|
||||
return false unless params[:request].has_key?(:password)
|
||||
return false if params[:request][:password].empty?
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
end
|
140
tracks/app/helpers/application_helper.rb
Normal file
140
tracks/app/helpers/application_helper.rb
Normal file
|
@ -0,0 +1,140 @@
|
|||
# The methods added to this helper will be available to all templates in the application.
|
||||
module ApplicationHelper
|
||||
|
||||
def user_time
|
||||
@user.time
|
||||
end
|
||||
|
||||
# Replicates the link_to method but also checks request.request_uri to find
|
||||
# current page. If that matches the url, the link is marked
|
||||
# id = "current"
|
||||
#
|
||||
def navigation_link(name, options = {}, html_options = nil, *parameters_for_method_reference)
|
||||
if html_options
|
||||
html_options = html_options.stringify_keys
|
||||
convert_options_to_javascript!(html_options)
|
||||
tag_options = tag_options(html_options)
|
||||
else
|
||||
tag_options = nil
|
||||
end
|
||||
url = options.is_a?(String) ? options : self.url_for(options, *parameters_for_method_reference)
|
||||
id_tag = (request.request_uri == url) ? " id=\"current\"" : ""
|
||||
|
||||
"<a href=\"#{url}\"#{tag_options}#{id_tag}>#{name || url}</a>"
|
||||
end
|
||||
|
||||
def days_from_today(date)
|
||||
date.to_date - user_time.to_date
|
||||
end
|
||||
|
||||
# Check due date in comparison to today's date
|
||||
# Flag up date appropriately with a 'traffic light' colour code
|
||||
#
|
||||
def due_date(due)
|
||||
if due == nil
|
||||
return ""
|
||||
end
|
||||
|
||||
days = days_from_today(due)
|
||||
|
||||
case days
|
||||
# overdue or due very soon! sound the alarm!
|
||||
when -1000..-1
|
||||
"<a title='#{format_date(due)}'><span class=\"red\">Overdue by #{pluralize(days * -1, 'day')}</span></a> "
|
||||
when 0
|
||||
"<a title='#{format_date(due)}'><span class=\"amber\">Due Today</span></a> "
|
||||
when 1
|
||||
"<a title='#{format_date(due)}'><span class=\"amber\">Due Tomorrow</span></a> "
|
||||
# due 2-7 days away
|
||||
when 2..7
|
||||
if @user.prefs.due_style == "1"
|
||||
"<a title='#{format_date(due)}'><span class=\"orange\">Due on #{due.strftime("%A")}</span></a> "
|
||||
else
|
||||
"<a title='#{format_date(due)}'><span class=\"orange\">Due in #{pluralize(days, 'day')}</span></a> "
|
||||
end
|
||||
# more than a week away - relax
|
||||
else
|
||||
"<a title='#{format_date(due)}'><span class=\"green\">Due in #{pluralize(days, 'day')}</span></a> "
|
||||
end
|
||||
end
|
||||
|
||||
# Check due date in comparison to today's date
|
||||
# Flag up date appropriately with a 'traffic light' colour code
|
||||
# Modified method for mobile screen
|
||||
#
|
||||
def due_date_mobile(due)
|
||||
if due == nil
|
||||
return ""
|
||||
end
|
||||
|
||||
days = days_from_today(due)
|
||||
|
||||
case days
|
||||
# overdue or due very soon! sound the alarm!
|
||||
when -1000..-1
|
||||
"<span class=\"red\">" + format_date(due) +"</span>"
|
||||
when 0
|
||||
"<span class=\"amber\">"+ format_date(due) + "</span>"
|
||||
when 1
|
||||
"<span class=\"amber\">" + format_date(due) + "</span>"
|
||||
# due 2-7 days away
|
||||
when 2..7
|
||||
"<span class=\"orange\">" + format_date(due) + "</span>"
|
||||
# more than a week away - relax
|
||||
else
|
||||
"<span class=\"green\">" + format_date(due) + "</span>"
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a count of next actions in the given context or project
|
||||
# The result is count and a string descriptor, correctly pluralised if there are no
|
||||
# actions or multiple actions
|
||||
#
|
||||
def count_undone_todos_phrase(todos_parent, string="actions")
|
||||
@controller.count_undone_todos_phrase(todos_parent, string)
|
||||
end
|
||||
|
||||
def count_undone_todos_phrase_text(todos_parent, string="actions")
|
||||
count_undone_todos_phrase(todos_parent, string).gsub(" "," ")
|
||||
end
|
||||
|
||||
def count_undone_todos_and_notes_phrase(project, string="actions")
|
||||
s = count_undone_todos_phrase(project, string)
|
||||
s += ", #{pluralize(project.note_count, 'note')}" unless project.note_count == 0
|
||||
s
|
||||
end
|
||||
|
||||
def link_to_context(context, descriptor = sanitize(context.name))
|
||||
link_to( descriptor, context_path(context), :title => "View context: #{context.name}" )
|
||||
end
|
||||
|
||||
def link_to_project(project, descriptor = sanitize(project.name))
|
||||
link_to( descriptor, project_path(project), :title => "View project: #{project.name}" )
|
||||
end
|
||||
|
||||
def item_link_to_context(item)
|
||||
descriptor = "[C]"
|
||||
descriptor = "[#{item.context.name}]" if (@user.prefs.verbose_action_descriptors)
|
||||
link_to_context( item.context, descriptor )
|
||||
end
|
||||
|
||||
def item_link_to_project(item)
|
||||
descriptor = "[P]"
|
||||
descriptor = "[#{item.project.name}]" if (@user.prefs.verbose_action_descriptors)
|
||||
link_to_project( item.project, descriptor )
|
||||
end
|
||||
|
||||
def render_flash
|
||||
render :partial => 'shared/flash', :locals => { :flash => flash }
|
||||
end
|
||||
|
||||
# Display a flash message in RJS templates
|
||||
# Usage: page.notify :warning, "This is the message", 5.0
|
||||
# Puts the message into a flash of type 'warning', fades over 5 secs
|
||||
def notify(type, message, fade_duration)
|
||||
type = type.to_s # symbol to string
|
||||
page.replace 'flash', "<h4 id='flash' class='alert #{type}'>#{message}</h4>"
|
||||
page.visual_effect :fade, 'flash', :duration => fade_duration
|
||||
end
|
||||
|
||||
end
|
2
tracks/app/helpers/backend_helper.rb
Normal file
2
tracks/app/helpers/backend_helper.rb
Normal file
|
@ -0,0 +1,2 @@
|
|||
module BackendHelper
|
||||
end
|
12
tracks/app/helpers/contexts_helper.rb
Normal file
12
tracks/app/helpers/contexts_helper.rb
Normal file
|
@ -0,0 +1,12 @@
|
|||
module ContextsHelper
|
||||
|
||||
def get_listing_sortable_options
|
||||
{
|
||||
:tag => 'div',
|
||||
:handle => 'handle',
|
||||
:complete => visual_effect(:highlight, 'list-contexts'),
|
||||
:url => order_contexts_path
|
||||
}
|
||||
end
|
||||
|
||||
end
|
2
tracks/app/helpers/data_helper.rb
Normal file
2
tracks/app/helpers/data_helper.rb
Normal file
|
@ -0,0 +1,2 @@
|
|||
module DataHelper
|
||||
end
|
22
tracks/app/helpers/feedlist_helper.rb
Normal file
22
tracks/app/helpers/feedlist_helper.rb
Normal file
|
@ -0,0 +1,22 @@
|
|||
module FeedlistHelper
|
||||
|
||||
def rss_formatted_link(options = {})
|
||||
image_tag = image_tag("feed-icon.png", :size => "16X16", :border => 0, :class => "rss-icon")
|
||||
linkoptions = { :token => @user.word, :format => 'rss' }
|
||||
linkoptions.merge!(options)
|
||||
link_to(image_tag, linkoptions, :title => "RSS feed")
|
||||
end
|
||||
|
||||
def text_formatted_link(options = {})
|
||||
linkoptions = { :token => @user.word, :format => 'txt' }
|
||||
linkoptions.merge!(options)
|
||||
link_to('<span class="feed">TXT</span>', linkoptions, :title => "Plain text feed" )
|
||||
end
|
||||
|
||||
def ical_formatted_link(options = {})
|
||||
linkoptions = { :token => @user.word, :format => 'ics' }
|
||||
linkoptions.merge!(options)
|
||||
link_to('<span class="feed">iCal</span>', linkoptions, :title => "iCal feed" )
|
||||
end
|
||||
|
||||
end
|
3
tracks/app/helpers/login_helper.rb
Normal file
3
tracks/app/helpers/login_helper.rb
Normal file
|
@ -0,0 +1,3 @@
|
|||
module LoginHelper
|
||||
|
||||
end
|
2
tracks/app/helpers/mobile_helper.rb
Normal file
2
tracks/app/helpers/mobile_helper.rb
Normal file
|
@ -0,0 +1,2 @@
|
|||
module MobileHelper
|
||||
end
|
5
tracks/app/helpers/notes_helper.rb
Normal file
5
tracks/app/helpers/notes_helper.rb
Normal file
|
@ -0,0 +1,5 @@
|
|||
module NotesHelper
|
||||
def truncated_note(note, characters = 50)
|
||||
sanitize(textilize(truncate(note.body, characters, "...")))
|
||||
end
|
||||
end
|
2
tracks/app/helpers/preferences_helper.rb
Normal file
2
tracks/app/helpers/preferences_helper.rb
Normal file
|
@ -0,0 +1,2 @@
|
|||
module PreferencesHelper
|
||||
end
|
34
tracks/app/helpers/projects_helper.rb
Normal file
34
tracks/app/helpers/projects_helper.rb
Normal file
|
@ -0,0 +1,34 @@
|
|||
module ProjectsHelper
|
||||
|
||||
def get_listing_sortable_options(list_container_id)
|
||||
{
|
||||
:tag => 'div',
|
||||
:handle => 'handle',
|
||||
:complete => visual_effect(:highlight, list_container_id),
|
||||
:url => order_projects_path
|
||||
}
|
||||
end
|
||||
|
||||
def set_element_visible(id,test)
|
||||
if (test)
|
||||
page.show id
|
||||
else
|
||||
page.hide id
|
||||
end
|
||||
end
|
||||
|
||||
def project_next_prev
|
||||
html = ''
|
||||
unless @previous_project.nil?
|
||||
project_name = truncate(@previous_project.name, 40, "...")
|
||||
html << link_to_project(@previous_project, "« #{project_name}")
|
||||
end
|
||||
html << ' | ' if @previous_project && @next_project
|
||||
unless @next_project.nil?
|
||||
project_name = truncate(@next_project.name, 40, "...")
|
||||
html << link_to_project(@next_project, "#{project_name} »")
|
||||
end
|
||||
html
|
||||
end
|
||||
|
||||
end
|
199
tracks/app/helpers/todos_helper.rb
Normal file
199
tracks/app/helpers/todos_helper.rb
Normal file
|
@ -0,0 +1,199 @@
|
|||
module TodosHelper
|
||||
|
||||
require 'users_controller'
|
||||
# Counts the number of incomplete items in the specified context
|
||||
#
|
||||
def count_items(context)
|
||||
count = Todo.find_all("done=0 AND context_id=#{context.id}").length
|
||||
end
|
||||
|
||||
def form_remote_tag_edit_todo( &block )
|
||||
form_tag( todo_path(@todo), {:method => :put, :id => dom_id(@todo, 'form'), :class => "edit_todo_form inline-form" }, &block )
|
||||
apply_behavior 'form.edit_todo_form', make_remote_form(:method => :put), :prevent_default => true
|
||||
end
|
||||
|
||||
def remote_delete_icon
|
||||
str = link_to( image_tag_for_delete,
|
||||
todo_path(@todo),
|
||||
:class => "icon delete_icon", :title => "delete the action '#{@todo.description}'")
|
||||
apply_behavior '.item-container a.delete_icon:click', :prevent_default => true do |page|
|
||||
page << "if (confirm('Are you sure that you want to ' + this.title + '?')) {"
|
||||
page << " new Ajax.Request(this.href, { asynchronous : true, evalScripts : true, method : 'delete', parameters : { '_source_view' : '#{@source_view}' }})"
|
||||
page << "}"
|
||||
end
|
||||
str
|
||||
end
|
||||
|
||||
def remote_edit_icon
|
||||
if !@todo.completed?
|
||||
str = link_to( image_tag_for_edit,
|
||||
edit_todo_path(@todo),
|
||||
:class => "icon edit_icon")
|
||||
apply_behavior '.item-container a.edit_icon:click', :prevent_default => true do |page|
|
||||
page << "new Ajax.Request(this.href, { asynchronous : true, evalScripts : true, method : 'get', parameters : { '_source_view' : '#{@source_view}' }, onLoading: function(request){ Effect.Pulsate(this)}});"
|
||||
end
|
||||
else
|
||||
str = '<a class="icon">' + image_tag("blank.png") + "</a> "
|
||||
end
|
||||
str
|
||||
end
|
||||
|
||||
def remote_toggle_checkbox
|
||||
str = check_box_tag('item_id', toggle_check_todo_path(@todo), @todo.completed?, :class => 'item-checkbox')
|
||||
apply_behavior '.item-container input.item-checkbox:click',
|
||||
remote_function(:url => javascript_variable('this.value'),
|
||||
:with => "{ method : 'post', _source_view : '#{@source_view}' }")
|
||||
str
|
||||
end
|
||||
|
||||
def date_span
|
||||
if @todo.completed?
|
||||
"<span class=\"grey\">#{format_date( @todo.completed_at )}</span>"
|
||||
elsif @todo.deferred?
|
||||
show_date( @todo.show_from )
|
||||
else
|
||||
due_date( @todo.due )
|
||||
end
|
||||
end
|
||||
|
||||
def tag_list
|
||||
@todo.tags.collect{|t| "<span class=\"tag\">" + link_to(t.name, :action => "tag", :id => t.name) + "</span>"}.join('')
|
||||
end
|
||||
|
||||
def deferred_due_date
|
||||
if @todo.deferred? && @todo.due
|
||||
"(action due on #{format_date(@todo.due)})"
|
||||
end
|
||||
end
|
||||
|
||||
def project_and_context_links(parent_container_type)
|
||||
if @todo.completed?
|
||||
"(#{@todo.context.name}#{", " + @todo.project.name unless @todo.project.nil?})"
|
||||
else
|
||||
str = ''
|
||||
if (['project', 'tickler', 'tag'].include?(parent_container_type))
|
||||
str << item_link_to_context( @todo )
|
||||
end
|
||||
if (['context', 'tickler', 'tag'].include?(parent_container_type)) && @todo.project_id
|
||||
str << item_link_to_project( @todo )
|
||||
end
|
||||
str
|
||||
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:
|
||||
# * l1: created more than 1 x staleness_starts, but < 2 x staleness_starts
|
||||
# * l2: created more than 2 x staleness_starts, but < 3 x staleness_starts
|
||||
# * l3: created more than 3 x staleness_starts
|
||||
#
|
||||
def staleness_class(item)
|
||||
if item.due || item.completed?
|
||||
return ""
|
||||
elsif item.created_at < user_time - (@user.prefs.staleness_starts * 3).days
|
||||
return " stale_l3"
|
||||
elsif item.created_at < user_time - (@user.prefs.staleness_starts * 2).days
|
||||
return " stale_l2"
|
||||
elsif item.created_at < user_time - (@user.prefs.staleness_starts).days
|
||||
return " stale_l1"
|
||||
else
|
||||
return ""
|
||||
end
|
||||
end
|
||||
|
||||
# Check show_from date in comparison to today's date
|
||||
# Flag up date appropriately with a 'traffic light' colour code
|
||||
#
|
||||
def show_date(due)
|
||||
if due == nil
|
||||
return ""
|
||||
end
|
||||
|
||||
days = days_from_today(due)
|
||||
|
||||
case days
|
||||
# overdue or due very soon! sound the alarm!
|
||||
when -1000..-1
|
||||
"<a title='" + format_date(due) + "'><span class=\"red\">Shown on " + (days * -1).to_s + " days</span></a> "
|
||||
when 0
|
||||
"<a title='" + format_date(due) + "'><span class=\"amber\">Show Today</span></a> "
|
||||
when 1
|
||||
"<a title='" + format_date(due) + "'><span class=\"amber\">Show Tomorrow</span></a> "
|
||||
# due 2-7 days away
|
||||
when 2..7
|
||||
if @user.prefs.due_style == 1
|
||||
"<a title='" + format_date(due) + "'><span class=\"orange\">Show on " + due.strftime("%A") + "</span></a> "
|
||||
else
|
||||
"<a title='" + format_date(due) + "'><span class=\"orange\">Show in " + days.to_s + " days</span></a> "
|
||||
end
|
||||
# more than a week away - relax
|
||||
else
|
||||
"<a title='" + format_date(due) + "'><span class=\"green\">Show in " + days.to_s + " days</span></a> "
|
||||
end
|
||||
end
|
||||
|
||||
def calendar_setup( input_field )
|
||||
date_format = @user.prefs.date_format
|
||||
week_starts = @user.prefs.week_starts
|
||||
str = "Calendar.setup({ ifFormat:\"#{date_format}\""
|
||||
str << ",firstDay:#{week_starts},showOthers:true,range:[2004, 2010]"
|
||||
str << ",step:1,inputField:\"" + input_field + "\",cache:true,align:\"TR\" })\n"
|
||||
javascript_tag str
|
||||
end
|
||||
|
||||
def item_container_id
|
||||
return "tickler-items" if source_view_is :deferred
|
||||
if source_view_is :project
|
||||
return "p#{@todo.project_id}" if @todo.active?
|
||||
return "tickler" if @todo.deferred?
|
||||
end
|
||||
return "c#{@todo.context_id}"
|
||||
end
|
||||
|
||||
def should_show_new_item
|
||||
return true if source_view_is(:deferred) && @todo.deferred?
|
||||
return true if source_view_is(:project) && @todo.project.hidden? && @todo.project_hidden?
|
||||
return true if source_view_is(:project) && @todo.deferred?
|
||||
return true if !source_view_is(:deferred) && @todo.active?
|
||||
return false
|
||||
end
|
||||
|
||||
def parent_container_type
|
||||
return 'tickler' if source_view_is :deferred
|
||||
return 'project' if source_view_is :project
|
||||
return 'context'
|
||||
end
|
||||
|
||||
def empty_container_msg_div_id
|
||||
return "tickler-empty-nd" if source_view_is(:project) && @todo.deferred?
|
||||
return "p#{@todo.project_id}empty-nd" if source_view_is :project
|
||||
return "tickler-empty-nd" if source_view_is :deferred
|
||||
return "c#{@todo.context_id}empty-nd"
|
||||
end
|
||||
|
||||
def project_names_for_autocomplete
|
||||
array_or_string_for_javascript( ['None'] + @projects.select{ |p| p.active? }.collect{|p| escape_javascript(p.name) } )
|
||||
end
|
||||
|
||||
def context_names_for_autocomplete
|
||||
return array_or_string_for_javascript(['Create a new context']) if @contexts.empty?
|
||||
array_or_string_for_javascript( @contexts.collect{|c| escape_javascript(c.name) } )
|
||||
end
|
||||
|
||||
def format_ical_notes(notes)
|
||||
split_notes = notes.split(/\n/)
|
||||
joined_notes = split_notes.join("\\n")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def image_tag_for_delete
|
||||
image_tag("blank.png", :title =>"Delete action", :class=>"delete_item")
|
||||
end
|
||||
|
||||
def image_tag_for_edit
|
||||
image_tag("blank.png", :title =>"Edit action", :class=>"edit_item", :id=> dom_id(@todo, 'edit_icon'))
|
||||
end
|
||||
|
||||
end
|
2
tracks/app/helpers/users_helper.rb
Normal file
2
tracks/app/helpers/users_helper.rb
Normal file
|
@ -0,0 +1,2 @@
|
|||
module UsersHelper
|
||||
end
|
65
tracks/app/models/context.rb
Normal file
65
tracks/app/models/context.rb
Normal file
|
@ -0,0 +1,65 @@
|
|||
class Context < ActiveRecord::Base
|
||||
|
||||
has_many :todos, :dependent => :delete_all, :include => :project, :order => "completed_at DESC"
|
||||
belongs_to :user
|
||||
|
||||
acts_as_list :scope => :user
|
||||
extend NamePartFinder
|
||||
include Tracks::TodoList
|
||||
include UrlFriendlyName
|
||||
|
||||
attr_protected :user
|
||||
|
||||
validates_presence_of :name, :message => "context must have a name"
|
||||
validates_length_of :name, :maximum => 255, :message => "context name must be less than 256 characters"
|
||||
validates_uniqueness_of :name, :message => "already exists", :scope => "user_id"
|
||||
validates_does_not_contain :name, :string => '/', :message => "cannot contain the slash ('/') character"
|
||||
validates_does_not_contain :name, :string => ',', :message => "cannot contain the comma (',') character"
|
||||
|
||||
def self.feed_options(user)
|
||||
{
|
||||
:title => 'Tracks Contexts',
|
||||
:description => "Lists all the contexts for #{user.display_name}"
|
||||
}
|
||||
end
|
||||
|
||||
def self.null_object
|
||||
NullContext.new
|
||||
end
|
||||
|
||||
def hidden?
|
||||
self.hide == true || self.hide == 1
|
||||
end
|
||||
|
||||
def to_param
|
||||
url_friendly_name
|
||||
end
|
||||
|
||||
def title
|
||||
name
|
||||
end
|
||||
|
||||
def summary(undone_todo_count)
|
||||
s = "<p>#{undone_todo_count}. "
|
||||
s += "Context is #{hidden? ? 'Hidden' : 'Active'}."
|
||||
s += "</p>"
|
||||
s
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class NullContext
|
||||
|
||||
def nil?
|
||||
true
|
||||
end
|
||||
|
||||
def id
|
||||
nil
|
||||
end
|
||||
|
||||
def name
|
||||
''
|
||||
end
|
||||
|
||||
end
|
7
tracks/app/models/note.rb
Normal file
7
tracks/app/models/note.rb
Normal file
|
@ -0,0 +1,7 @@
|
|||
class Note < ActiveRecord::Base
|
||||
belongs_to :user
|
||||
belongs_to :project
|
||||
|
||||
attr_protected :user
|
||||
|
||||
end
|
21
tracks/app/models/preference.rb
Normal file
21
tracks/app/models/preference.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
class Preference < ActiveRecord::Base
|
||||
belongs_to :user
|
||||
composed_of :tz,
|
||||
:class_name => 'TimeZone',
|
||||
:mapping => %w(time_zone name)
|
||||
|
||||
def self.day_number_to_name_map
|
||||
{ 0 => "Sunday",
|
||||
1 => "Monday",
|
||||
2 => "Tuesday",
|
||||
3 => "Wednesday",
|
||||
4 => "Thursday",
|
||||
5 => "Friday",
|
||||
6 => "Saturday"}
|
||||
end
|
||||
|
||||
def hide_completed_actions?
|
||||
return show_number_completed == 0
|
||||
end
|
||||
|
||||
end
|
112
tracks/app/models/project.rb
Normal file
112
tracks/app/models/project.rb
Normal file
|
@ -0,0 +1,112 @@
|
|||
class Project < ActiveRecord::Base
|
||||
has_many :todos, :dependent => :delete_all, :include => :context
|
||||
has_many :notes, :dependent => :delete_all, :order => "created_at DESC"
|
||||
belongs_to :default_context, :dependent => :nullify, :class_name => "Context", :foreign_key => "default_context_id"
|
||||
belongs_to :user
|
||||
|
||||
validates_presence_of :name, :message => "project must have a name"
|
||||
validates_length_of :name, :maximum => 255, :message => "project name must be less than 256 characters"
|
||||
validates_uniqueness_of :name, :message => "already exists", :scope =>"user_id"
|
||||
validates_does_not_contain :name, :string => '/', :message => "cannot contain the slash ('/') character"
|
||||
validates_does_not_contain :name, :string => ',', :message => "cannot contain the comma (',') character"
|
||||
|
||||
acts_as_list :scope => 'user_id = #{user_id} AND state = \'#{state}\''
|
||||
acts_as_state_machine :initial => :active, :column => 'state'
|
||||
extend NamePartFinder
|
||||
include Tracks::TodoList
|
||||
include UrlFriendlyName
|
||||
|
||||
state :active
|
||||
state :hidden, :enter => :hide_todos, :exit => :unhide_todos
|
||||
state :completed
|
||||
|
||||
event :activate do
|
||||
transitions :to => :active, :from => [:hidden, :completed]
|
||||
end
|
||||
|
||||
event :hide do
|
||||
transitions :to => :hidden, :from => [:active, :completed]
|
||||
end
|
||||
|
||||
event :complete do
|
||||
transitions :to => :completed, :from => [:active, :hidden]
|
||||
end
|
||||
|
||||
attr_protected :user
|
||||
attr_accessor :cached_note_count
|
||||
|
||||
def self.null_object
|
||||
NullProject.new
|
||||
end
|
||||
|
||||
def self.feed_options(user)
|
||||
{
|
||||
:title => 'Tracks Projects',
|
||||
:description => "Lists all the projects for #{user.display_name}"
|
||||
}
|
||||
end
|
||||
|
||||
def to_param
|
||||
url_friendly_name
|
||||
end
|
||||
|
||||
def hide_todos
|
||||
todos.each do |t|
|
||||
unless t.completed? || t.deferred?
|
||||
t.hide!
|
||||
t.save
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def unhide_todos
|
||||
todos.each do |t|
|
||||
if t.project_hidden?
|
||||
t.unhide!
|
||||
t.save
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def note_count
|
||||
cached_note_count || notes.count
|
||||
end
|
||||
|
||||
alias_method :original_default_context, :default_context
|
||||
|
||||
def default_context
|
||||
original_default_context.nil? ? Context.null_object : original_default_context
|
||||
end
|
||||
|
||||
# would prefer to call this method state=(), but that causes an endless loop
|
||||
# as a result of acts_as_state_machine calling state=() to update the attribute
|
||||
def transition_to(candidate_state)
|
||||
case candidate_state.to_sym
|
||||
when current_state
|
||||
return
|
||||
when :hidden
|
||||
hide!
|
||||
when :active
|
||||
activate!
|
||||
when :completed
|
||||
complete!
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class NullProject
|
||||
|
||||
def hidden?
|
||||
false
|
||||
end
|
||||
|
||||
def nil?
|
||||
true
|
||||
end
|
||||
|
||||
def id
|
||||
nil
|
||||
end
|
||||
|
||||
end
|
11
tracks/app/models/tag.rb
Normal file
11
tracks/app/models/tag.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
class Tag < ActiveRecord::Base
|
||||
has_many_polymorphs :taggables,
|
||||
:from => [:todos],
|
||||
:through => :taggings,
|
||||
:dependent => :destroy
|
||||
|
||||
def on(taggable, user)
|
||||
tagging = taggings.create :taggable => taggable, :user => user
|
||||
end
|
||||
|
||||
end
|
11
tracks/app/models/tagging.rb
Normal file
11
tracks/app/models/tagging.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
class Tagging < ActiveRecord::Base
|
||||
belongs_to :tag
|
||||
belongs_to :taggable, :polymorphic => true
|
||||
belongs_to :user
|
||||
|
||||
# def before_destroy
|
||||
# # disallow orphaned tags
|
||||
# # TODO: this doesn't seem to be working
|
||||
# tag.destroy if tag.taggings.count < 2
|
||||
# end
|
||||
end
|
94
tracks/app/models/todo.rb
Normal file
94
tracks/app/models/todo.rb
Normal file
|
@ -0,0 +1,94 @@
|
|||
class Todo < ActiveRecord::Base
|
||||
|
||||
belongs_to :context, :order => 'name'
|
||||
belongs_to :project
|
||||
belongs_to :user
|
||||
|
||||
acts_as_state_machine :initial => :active, :column => 'state'
|
||||
|
||||
state :active, :enter => Proc.new { |t| t[:show_from] = nil }
|
||||
state :project_hidden
|
||||
state :completed, :enter => Proc.new { |t| t.completed_at = Time.now.utc }, :exit => Proc.new { |t| t.completed_at = nil }
|
||||
state :deferred
|
||||
|
||||
event :defer do
|
||||
transitions :to => :deferred, :from => [:active]
|
||||
end
|
||||
|
||||
event :complete do
|
||||
transitions :to => :completed, :from => [:active, :project_hidden, :deferred]
|
||||
end
|
||||
|
||||
event :activate do
|
||||
transitions :to => :active, :from => [:project_hidden, :completed, :deferred]
|
||||
end
|
||||
|
||||
event :hide do
|
||||
transitions :to => :project_hidden, :from => [:active, :deferred]
|
||||
end
|
||||
|
||||
event :unhide do
|
||||
transitions :to => :deferred, :from => [:project_hidden], :guard => Proc.new{|t| !t.show_from.blank? }
|
||||
transitions :to => :active, :from => [:project_hidden]
|
||||
end
|
||||
|
||||
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, :allow_nil => true
|
||||
# validates_chronic_date :due, :allow_nil => true
|
||||
validates_presence_of :show_from, :if => :deferred?
|
||||
validates_presence_of :context
|
||||
|
||||
def validate
|
||||
if !show_from.blank? && show_from < user.date
|
||||
errors.add("show_from", "must be a date in the future")
|
||||
end
|
||||
end
|
||||
|
||||
def toggle_completion!
|
||||
if completed?
|
||||
activate!
|
||||
else
|
||||
complete!
|
||||
end
|
||||
end
|
||||
|
||||
def activate_and_save!
|
||||
activate!
|
||||
save!
|
||||
end
|
||||
|
||||
def show_from=(date)
|
||||
activate! if deferred? && date.blank?
|
||||
defer! if active? && !date.blank? && date > user.date
|
||||
self[:show_from] = date
|
||||
end
|
||||
|
||||
alias_method :original_project, :project
|
||||
|
||||
def project
|
||||
original_project.nil? ? Project.null_object : original_project
|
||||
end
|
||||
|
||||
alias_method :original_set_initial_state, :set_initial_state
|
||||
|
||||
def set_initial_state
|
||||
if show_from && (show_from > user.date)
|
||||
write_attribute self.class.state_column, 'deferred'
|
||||
else
|
||||
original_set_initial_state
|
||||
end
|
||||
end
|
||||
|
||||
def self.feed_options(user)
|
||||
{
|
||||
:title => 'Tracks Actions',
|
||||
:description => "Actions for #{user.display_name}"
|
||||
}
|
||||
end
|
||||
|
||||
end
|
182
tracks/app/models/user.rb
Normal file
182
tracks/app/models/user.rb
Normal file
|
@ -0,0 +1,182 @@
|
|||
require 'digest/sha1'
|
||||
|
||||
class User < ActiveRecord::Base
|
||||
has_many :contexts,
|
||||
:order => 'position ASC',
|
||||
:dependent => :delete_all do
|
||||
def find_by_params(params)
|
||||
if params['url_friendly_name']
|
||||
find_by_url_friendly_name(params['url_friendly_name'])
|
||||
elsif params['id'] && params['id'] =~ /^\d+$/
|
||||
find(params['id'])
|
||||
elsif params['id']
|
||||
find_by_url_friendly_name(params['id'])
|
||||
elsif params['context']
|
||||
find_by_url_friendly_name(params['context'])
|
||||
elsif params['context_id']
|
||||
find_by_url_friendly_name(params['context_id'])
|
||||
end
|
||||
end
|
||||
end
|
||||
has_many :projects,
|
||||
:order => 'position ASC',
|
||||
:dependent => :delete_all do
|
||||
def find_by_params(params)
|
||||
if params['url_friendly_name']
|
||||
find_by_url_friendly_name(params['url_friendly_name'])
|
||||
elsif params['id'] && params['id'] =~ /^\d+$/
|
||||
find(params['id'])
|
||||
elsif params['id']
|
||||
find_by_url_friendly_name(params['id'])
|
||||
elsif params['project']
|
||||
find_by_url_friendly_name(params['project'])
|
||||
elsif params['project_id']
|
||||
find_by_url_friendly_name(params['project_id'])
|
||||
end
|
||||
end
|
||||
def update_positions(project_ids)
|
||||
project_ids.each_with_index do |id, position|
|
||||
project = self.detect { |p| p.id == id.to_i }
|
||||
raise "Project id #{id} not associated with user id #{@user.id}." if project.nil?
|
||||
project.update_attribute(:position, position + 1)
|
||||
end
|
||||
end
|
||||
def projects_in_state_by_position(state)
|
||||
self.sort{ |a,b| a.position <=> b.position }.select{ |p| p.state == state }
|
||||
end
|
||||
def next_from(project)
|
||||
self.offset_from(project, 1)
|
||||
end
|
||||
def previous_from(project)
|
||||
self.offset_from(project, -1)
|
||||
end
|
||||
def offset_from(project, offset)
|
||||
projects = self.projects_in_state_by_position(project.state)
|
||||
position = projects.index(project)
|
||||
return nil if position == 0 && offset < 0
|
||||
projects.at( position + offset)
|
||||
end
|
||||
def cache_note_counts
|
||||
project_note_counts = Note.count(:group => 'project_id')
|
||||
self.each do |project|
|
||||
project.cached_note_count = project_note_counts[project.id] || 0
|
||||
end
|
||||
end
|
||||
end
|
||||
has_many :todos,
|
||||
:order => 'completed_at DESC, todos.created_at DESC',
|
||||
:dependent => :delete_all
|
||||
has_many :deferred_todos,
|
||||
:class_name => 'Todo',
|
||||
:conditions => [ 'state = ?', 'deferred' ],
|
||||
:order => 'show_from ASC, todos.created_at DESC' do
|
||||
def find_and_activate_ready
|
||||
find(:all, :conditions => ['show_from <= ?', Time.now.utc.to_date.to_time ]).collect { |t| t.activate_and_save! }
|
||||
end
|
||||
end
|
||||
has_many :completed_todos,
|
||||
:class_name => 'Todo',
|
||||
:conditions => ['todos.state = ? and todos.completed_at is not null', 'completed'],
|
||||
:order => 'todos.completed_at DESC',
|
||||
:include => [ :project, :context ] do
|
||||
def completed_within( date )
|
||||
reject { |x| x.completed_at < date }
|
||||
end
|
||||
|
||||
def completed_more_than( date )
|
||||
reject { |x| x.completed_at > date }
|
||||
end
|
||||
end
|
||||
has_many :notes, :order => "created_at DESC", :dependent => :delete_all
|
||||
has_one :preference, :dependent => :destroy
|
||||
has_many :taggings
|
||||
has_many :tags, :through => :taggings, :select => "DISTINCT tags.*"
|
||||
|
||||
attr_protected :is_admin
|
||||
|
||||
validates_presence_of :login
|
||||
validates_presence_of :password, :if => :password_required?
|
||||
validates_length_of :password, :within => 5..40, :if => :password_required?
|
||||
validates_confirmation_of :password
|
||||
validates_length_of :login, :within => 3..80
|
||||
validates_uniqueness_of :login, :on => :create
|
||||
validates_presence_of :open_id_url, :if => Proc.new{|user| user.auth_type == 'open_id'}
|
||||
|
||||
def validate
|
||||
unless Tracks::Config.auth_schemes.include?(auth_type)
|
||||
errors.add("auth_type", "not a valid authentication type")
|
||||
end
|
||||
end
|
||||
|
||||
alias_method :prefs, :preference
|
||||
|
||||
def self.authenticate(login, pass)
|
||||
candidate = find(:first, :conditions => ["login = ?", login])
|
||||
return nil if candidate.nil?
|
||||
if candidate.auth_type == 'database'
|
||||
return candidate if candidate.password == sha1(pass)
|
||||
elsif candidate.auth_type == 'ldap' && Tracks::Config.auth_schemes.include?('ldap')
|
||||
return candidate if SimpleLdapAuthenticator.valid?(login, pass)
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
def self.no_users_yet?
|
||||
count == 0
|
||||
end
|
||||
|
||||
def self.find_admin
|
||||
find(:first, :conditions => [ "is_admin = ?", true ])
|
||||
end
|
||||
|
||||
def to_param
|
||||
login
|
||||
end
|
||||
|
||||
def display_name
|
||||
if first_name.blank? && last_name.blank?
|
||||
return login
|
||||
elsif first_name.blank?
|
||||
return last_name
|
||||
elsif last_name.blank?
|
||||
return first_name
|
||||
end
|
||||
"#{first_name} #{last_name}"
|
||||
end
|
||||
|
||||
def change_password(pass,pass_confirm)
|
||||
self.password = pass
|
||||
self.password_confirmation = pass_confirm
|
||||
save!
|
||||
end
|
||||
|
||||
def crypt_word
|
||||
write_attribute("word", self.class.sha1(login + Time.now.to_i.to_s + rand.to_s))
|
||||
end
|
||||
|
||||
def time
|
||||
prefs.tz.adjust(Time.now.utc)
|
||||
end
|
||||
|
||||
def date
|
||||
time.to_date
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def self.sha1(pass)
|
||||
Digest::SHA1.hexdigest("#{Tracks::Config.salt}--#{pass}--")
|
||||
end
|
||||
|
||||
before_create :crypt_password, :crypt_word
|
||||
before_update :crypt_password
|
||||
|
||||
def crypt_password
|
||||
write_attribute("password", self.class.sha1(password)) if password == @password_confirmation
|
||||
end
|
||||
|
||||
def password_required?
|
||||
auth_type == 'database'
|
||||
end
|
||||
|
||||
end
|
35
tracks/app/views/contexts/_context.rhtml
Normal file
35
tracks/app/views/contexts/_context.rhtml
Normal file
|
@ -0,0 +1,35 @@
|
|||
<% @not_done = @not_done_todos.select {|t| t.context_id == context.id } %>
|
||||
<div id="c<%= context.id %>" class="container context" <%= "style=\"display:none\"" if collapsible && @not_done.empty? %>>
|
||||
<h2>
|
||||
<% if collapsible -%>
|
||||
<a href="#" class="container_toggle" id="toggle_c<%= context.id %>"><%= image_tag("collapse.png") %></a>
|
||||
<% apply_behavior '.container_toggle:click', :prevent_default => true do |page|
|
||||
page << "containerElem = this.up('.container')
|
||||
toggleTarget = containerElem.down('.toggle_target')
|
||||
if (Element.visible(toggleTarget))
|
||||
{
|
||||
todoItems.collapseNextActionListing(this, toggleTarget);
|
||||
todoItems.contextCollapseCookieManager.setCookie(todoItems.buildCookieName(containerElem), true)
|
||||
}
|
||||
else
|
||||
{
|
||||
todoItems.expandNextActionListing(this, toggleTarget);
|
||||
todoItems.contextCollapseCookieManager.clearCookie(todoItems.buildCookieName(containerElem))
|
||||
}
|
||||
"
|
||||
end
|
||||
%>
|
||||
<% end -%>
|
||||
<% if source_view_is :context %>
|
||||
<%= in_place_editor_field :context, :name, {}, { :url => url_for(:controller => 'context', :action => 'update', :id => context.id, :field => 'name', :wants_render => false) } %>
|
||||
<% else %>
|
||||
<%= link_to_context( context ) %>
|
||||
<% end %>
|
||||
</h2>
|
||||
<div id="c<%= context.id %>items" class="items toggle_target">
|
||||
<div id="c<%= context.id %>empty-nd" style="display:<%= @not_done.empty? ? 'block' : 'none'%>;">
|
||||
<div class="message"><p>Currently there are no incomplete actions in this context</p></div>
|
||||
</div>
|
||||
<%= render :partial => "todos/todo", :collection => @not_done, :locals => { :parent_container_type => "context" } %>
|
||||
</div><!-- [end:items] -->
|
||||
</div><!-- [end:c<%= context.id %>] -->
|
13
tracks/app/views/contexts/_context_form.rhtml
Normal file
13
tracks/app/views/contexts/_context_form.rhtml
Normal file
|
@ -0,0 +1,13 @@
|
|||
<%
|
||||
@context = context_form
|
||||
%>
|
||||
<%= error_messages_for 'context' %>
|
||||
<tr>
|
||||
<td width="150"><label for="context_name">Name</label></td>
|
||||
<td width="300"><%= text_field 'context', 'name', :class => 'context-name' %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><label for="context_hide">Hidden?</label></td>
|
||||
<td width="300"><%= check_box 'context', 'hide', :class => 'context-hide' %></td>
|
||||
</tr>
|
||||
<% @context = nil %>
|
56
tracks/app/views/contexts/_context_listing.rhtml
Normal file
56
tracks/app/views/contexts/_context_listing.rhtml
Normal file
|
@ -0,0 +1,56 @@
|
|||
<% context = context_listing %>
|
||||
<div id="<%= dom_id(context, "container") %>" class="list">
|
||||
<div id="<%= dom_id(context) %>" class="context sortable_row" style="display:'';">
|
||||
<div class="position">
|
||||
<span class="handle">DRAG</span>
|
||||
</div>
|
||||
<div class="data">
|
||||
<%= link_to_context( context ) %> <%= " (" + count_undone_todos_phrase(context,"actions") + ")" %>
|
||||
</div>
|
||||
|
||||
<div class="buttons">
|
||||
<% if context.hide? %>
|
||||
<span class="grey">HIDDEN</span>
|
||||
<% else %>
|
||||
<span class="grey">VISIBLE</span>
|
||||
<% end %>
|
||||
<a class="delete_context_button" href="<%= formatted_context_path(context, :js) %>" title="delete the context '<%= context.name %>'"><%= image_tag( "blank.png", :title => "Delete context", :class=>"delete_item") %></a>
|
||||
<%= apply_behavior "a.delete_context_button:click", { :prevent_default => true, :external => true} do |page|
|
||||
page << "if (confirm('Are you sure that you want to ' + this.title + '?')) {"
|
||||
page << " new Ajax.Request(this.href, {asynchronous:true,"
|
||||
page << " evalScripts:true, method:'delete'}); };"
|
||||
end -%>
|
||||
<a class="edit_context_button" href="#"><%= image_tag( "blank.png", :title => "Edit context", :class=>"edit_item") %></a>
|
||||
<%= apply_behavior 'a.edit_context_button:click', :prevent_default => true do |page, element|
|
||||
element.up('.context').toggle
|
||||
editform = element.up('.list').down('.edit-form')
|
||||
editform.toggle
|
||||
editform.visual_effect(:appear)
|
||||
editform.down('input').focus
|
||||
end
|
||||
-%>
|
||||
</div>
|
||||
</div>
|
||||
<div id="<%= dom_id(context, 'edit') %>" class="edit-form" style="display:none;">
|
||||
<% form_tag context_path(context), { :id => dom_id(context, 'edit_form'), :class => "inline-form edit-context-form", :method => :put } do -%>
|
||||
<table style="table-layout: fixed;" width="450">
|
||||
<%= render :partial => 'context_form', :object => context %>
|
||||
<tr>
|
||||
<td width="150"> <input type="hidden" name="wants_render" value="true" /> </td>
|
||||
<td width="300"><input type="submit" value="Update" /> <a href="#" class="form_reset">Cancel</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
<% end -%>
|
||||
<%= apply_behavior ".edit-context-form", make_remote_form(:complete => "Effect.Appear($(this).up('.list'));" ), :external => true %>
|
||||
<%= apply_behavior "a.form_reset:click", :prevent_default => true do |page, element|
|
||||
element.up('.list').down('.context').toggle
|
||||
element.up('.edit-form').toggle
|
||||
element.up('form').reset
|
||||
end %>
|
||||
</div>
|
||||
</div>
|
||||
<% if controller.action_name == 'create' %>
|
||||
<script>
|
||||
new Effect.Appear('<%= dom_id(context) %>');
|
||||
</script>
|
||||
<% end %>
|
8
tracks/app/views/contexts/_text_context.rhtml
Normal file
8
tracks/app/views/contexts/_text_context.rhtml
Normal file
|
@ -0,0 +1,8 @@
|
|||
<%
|
||||
context = text_context
|
||||
todos_in_context = todos.select { |t| t.context_id == context.id }
|
||||
if todos_in_context.length > 0
|
||||
%>
|
||||
<%= context.name.upcase %>:
|
||||
<% end -%>
|
||||
<%= render :partial => "todos/text_todo", :collection => todos_in_context -%>
|
10
tracks/app/views/contexts/create.rjs
Normal file
10
tracks/app/views/contexts/create.rjs
Normal file
|
@ -0,0 +1,10 @@
|
|||
if @saved
|
||||
page.hide 'contexts-empty-nd'
|
||||
page.insert_html :bottom, "list-contexts", :partial => 'context_listing', :locals => { :context_listing => @context }
|
||||
page.sortable "list-contexts", get_listing_sortable_options
|
||||
page.call "Form.reset", "context-form"
|
||||
page.call "Form.focusFirstElement", "context-form"
|
||||
else
|
||||
page.show 'status'
|
||||
page.replace_html 'status', "#{error_messages_for('context')}"
|
||||
end
|
5
tracks/app/views/contexts/destroy.rjs
Normal file
5
tracks/app/views/contexts/destroy.rjs
Normal file
|
@ -0,0 +1,5 @@
|
|||
page.visual_effect :fade, dom_id(@context, "container"), :duration => 0.5
|
||||
page.delay(0.5) do
|
||||
page[dom_id(@context, "container")].remove
|
||||
end
|
||||
page.notify :notice, "Deleted context '#{@context.name}'", 5.0
|
1
tracks/app/views/contexts/error.rjs
Normal file
1
tracks/app/views/contexts/error.rjs
Normal file
|
@ -0,0 +1 @@
|
|||
page.notify :error, @error_message || "An error occurred on the server.", 8.0
|
35
tracks/app/views/contexts/index.rhtml
Normal file
35
tracks/app/views/contexts/index.rhtml
Normal file
|
@ -0,0 +1,35 @@
|
|||
<div id="display_box">
|
||||
<div id="list-contexts">
|
||||
<div id="contexts-empty-nd" style="display:<%= @no_contexts ? 'block' : 'none'%>;">
|
||||
<div class="message"><p>Currently there are no contexts</p></div>
|
||||
</div>
|
||||
<%= render :partial => 'context_listing', :collection => @contexts %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="input_box">
|
||||
<a id="toggle-new-context-form" href="#" accesskey="n" title="Create a new context">Create new context »</a>
|
||||
<div id="context_new" class="context_new" style="display:none">
|
||||
<% form_remote_tag :url => contexts_path, :method => :post, :html=> { :id => 'context-form', :name => 'context', :class => 'inline-form' } do -%>
|
||||
|
||||
<div id="status"><%= error_messages_for('context') %></div>
|
||||
|
||||
<label for="context_name">Context name</label><br />
|
||||
<%= text_field( "context", "name" ) %><br />
|
||||
|
||||
<label for="new_context_on_front">Hide from front page?</label>
|
||||
<%= check_box( "context", "hide" ) %><br />
|
||||
|
||||
<input type="submit" value="Add" />
|
||||
<% end -%>
|
||||
</div>
|
||||
</div>
|
||||
<%
|
||||
sortable_element 'list-contexts', get_listing_sortable_options
|
||||
|
||||
apply_behavior '#toggle-new-context-form:click', :prevent_default => true do |page|
|
||||
page['context_new'].toggle
|
||||
page['context-form'].down('input').focus
|
||||
#page << "Form.focusFirstElement('context-form');"
|
||||
end
|
||||
-%>
|
5
tracks/app/views/contexts/index_text.rhtml
Normal file
5
tracks/app/views/contexts/index_text.rhtml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<% @contexts.each do |c| -%>
|
||||
|
||||
<%= c.name.upcase %>
|
||||
<%= count_undone_todos_phrase_text(c)%>. Context is <%= c.hidden? ? "Hidden" : "Active" %>.
|
||||
<% end -%>
|
10
tracks/app/views/contexts/show.rhtml
Normal file
10
tracks/app/views/contexts/show.rhtml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<div id="display_box">
|
||||
<%= render :partial => "contexts/context", :locals => { :context => @context, :collapsible => false } %>
|
||||
<%= render :partial => "todos/completed", :locals => { :done => @done, :collapsible => false, :append_descriptor => "in this context (last #{@user.prefs.show_number_completed})" } %>
|
||||
|
||||
</div><!-- [end:display_box] -->
|
||||
|
||||
<div id="input_box">
|
||||
<%= render :partial => "shared/add_new_item_form" %>
|
||||
<%= render "sidebar/sidebar" %>
|
||||
</div><!-- End of input box -->
|
2
tracks/app/views/contexts/update.rjs
Normal file
2
tracks/app/views/contexts/update.rjs
Normal file
|
@ -0,0 +1,2 @@
|
|||
page.replace_html dom_id(@context, 'container'), :partial => 'context_listing', :object => @context
|
||||
page.sortable "list-contexts", get_listing_sortable_options
|
37
tracks/app/views/data/export.rhtml
Normal file
37
tracks/app/views/data/export.rhtml
Normal file
|
@ -0,0 +1,37 @@
|
|||
<div id="feeds">
|
||||
<div id="feedlegend">
|
||||
<h3>Exporting data</h3>
|
||||
<p>You can choose between the following formats:</p>
|
||||
<ul>
|
||||
<li><strong>YAML: </strong>Best for porting data between Tracks installations</li>
|
||||
<li><strong>CSV: </strong>Best for importing into spreadsheet or data analysis software</li>
|
||||
<li><strong>XML: </strong>Best for importing or repurposing the data</li>
|
||||
</ul
|
||||
</div>
|
||||
|
||||
<p>
|
||||
<table class="users_table">
|
||||
<tr>
|
||||
<th>Description</th>
|
||||
<th>Download link</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>YAML file containing all your actions, contexts, projects and notes</td>
|
||||
<td><%= link_to "YAML file", :controller => 'data', :action => 'yaml_export' %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>CSV file containing all of your actions, with named contexts and projects</td>
|
||||
<td><%= link_to "CSV file (actions, contexts and projects)", :controller => 'data', :action => 'csv_actions' %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>CSV file containing all your notes</td>
|
||||
<td><%= link_to "CSV file (notes only)", :controller => 'data', :action => 'csv_notes' %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>XML file containing all your actions, contexts, projects and notes</td>
|
||||
<td><%= link_to "XML file (actions only)", :controller => 'data', :action => 'xml_export' %></td>
|
||||
</tr>
|
||||
</table>
|
||||
</p>
|
||||
|
||||
</div><!-- End of feeds -->
|
18
tracks/app/views/data/import.rhtml
Normal file
18
tracks/app/views/data/import.rhtml
Normal file
|
@ -0,0 +1,18 @@
|
|||
<div id="display_box">
|
||||
<div id="feeds">
|
||||
<div id="feedlegend">
|
||||
<h3>Importing data</h3>
|
||||
<p>You can choose between the following formats:</p>
|
||||
<ul>
|
||||
<li><strong>YAML: </strong>Best for porting data between Tracks installations</li>
|
||||
<li><strong>CSV: </strong>Best for importing into spreadsheet or data analysis software</li>
|
||||
</ul
|
||||
</div>
|
||||
|
||||
<ul>
|
||||
<li>YAML</li>
|
||||
<li>CSV</li>
|
||||
</ul>
|
||||
|
||||
</div><!-- End of feeds -->
|
||||
</div><!-- End of display_box -->
|
15
tracks/app/views/data/index.rhtml
Normal file
15
tracks/app/views/data/index.rhtml
Normal file
|
@ -0,0 +1,15 @@
|
|||
<div id="display_box">
|
||||
<div id="feeds">
|
||||
<div id="feedlegend">
|
||||
<h3>Importing and exporting data into Tracks</h3>
|
||||
<p>You can export your data in a number of different forms. Note: you can only export/import your own data (i.e. the data available when you are logged in).</p>
|
||||
</div>
|
||||
|
||||
<ul>
|
||||
<li>Import</li>
|
||||
<li>
|
||||
<%= link_to "Export", :controller => 'data', :action => 'export' %>
|
||||
</li>
|
||||
</ul>
|
||||
</div><!-- End of feeds -->
|
||||
</div><!-- End of display_box -->
|
0
tracks/app/views/data/yaml_export.rhtml
Normal file
0
tracks/app/views/data/yaml_export.rhtml
Normal file
19
tracks/app/views/data/yaml_form.rhtml
Normal file
19
tracks/app/views/data/yaml_form.rhtml
Normal file
|
@ -0,0 +1,19 @@
|
|||
<div id="display_box">
|
||||
<div id="feeds">
|
||||
<div id="feedlegend">
|
||||
<p>Paste the contents of the YAML file you exported into the text box below:</p>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
<% form_for :import, @import, :url => {:controller => 'data', :action => 'yaml_import'} do |f| %>
|
||||
<%= f.text_area :yaml %><br />
|
||||
<input type="submit" value="Import data">
|
||||
<% end %>
|
||||
</p>
|
||||
|
||||
</div><!-- End of feeds -->
|
||||
</div><!-- End of display_box -->
|
||||
|
||||
<div id="input_box">
|
||||
|
||||
</div><!-- End of input box -->
|
1
tracks/app/views/data/yaml_import.rhtml
Normal file
1
tracks/app/views/data/yaml_import.rhtml
Normal file
|
@ -0,0 +1 @@
|
|||
<p>Import was successful</p>
|
87
tracks/app/views/feedlist/index.rhtml
Normal file
87
tracks/app/views/feedlist/index.rhtml
Normal file
|
@ -0,0 +1,87 @@
|
|||
<div id="display_box">
|
||||
<div id="feeds">
|
||||
<div id="feedlegend">
|
||||
<h3>Legend:</h3>
|
||||
<dl>
|
||||
<dt><%= image_tag("feed-icon.png", :size => "16X16", :border => 0)%></dt><dd>RSS Feed</dd>
|
||||
<dt><span class="feed">TXT</span></dt><dd>Plain Text Feed</dd>
|
||||
<dt><span class="feed">iCal</span></dt><dd>iCal feed</dd>
|
||||
</dl>
|
||||
<p>Note: All feeds show only actions that have not been marked as done.</p>
|
||||
</div>
|
||||
<ul>
|
||||
<li>
|
||||
<%= rss_formatted_link({ :controller => 'todos', :action => 'index', :limit => 15 }) %>
|
||||
<%= text_formatted_link({ :controller => 'todos', :action => 'index', :limit => 15 }) %>
|
||||
<%= ical_formatted_link({ :controller => 'todos', :action => 'index', :limit => 15 }) %>
|
||||
Last 15 actions
|
||||
</li>
|
||||
<li>
|
||||
<%= rss_formatted_link( { :controller => 'todos', :action => 'index' } ) %>
|
||||
<%= text_formatted_link( { :controller => 'todos', :action => 'index' } ) %>
|
||||
<%= ical_formatted_link( { :controller => 'todos', :action => 'index' } ) %>
|
||||
All actions
|
||||
</li>
|
||||
<li>
|
||||
<%= rss_formatted_link({ :controller => 'todos', :action => 'index', :due => 0 }) %>
|
||||
<%= text_formatted_link({ :controller => 'todos', :action => 'index', :due => 0 }) %>
|
||||
<%= ical_formatted_link({ :controller => 'todos', :action => 'index', :due => 0 }) %>
|
||||
Actions due today or earlier
|
||||
</li>
|
||||
<li>
|
||||
<%= rss_formatted_link({ :controller => 'todos', :action => 'index', :due => 6 }) %>
|
||||
<%= text_formatted_link({ :controller => 'todos', :action => 'index', :due => 6 }) %>
|
||||
<%= ical_formatted_link({ :controller => 'todos', :action => 'index', :due => 6 }) %>
|
||||
Actions due in 7 days or earlier
|
||||
</li>
|
||||
<li>
|
||||
<%= rss_formatted_link({ :controller => 'todos', :action => 'index', :done => 7 }) %>
|
||||
<%= text_formatted_link({ :controller => 'todos', :action => 'index', :done => 7 }) %>
|
||||
Actions completed in the last 7 days
|
||||
</li>
|
||||
<li>
|
||||
<%= rss_formatted_link({:controller => 'contexts', :action => 'index'}) %>
|
||||
<%= text_formatted_link({:controller => 'contexts', :action => 'index'}) %>
|
||||
All Contexts
|
||||
</li>
|
||||
<li>
|
||||
<%= rss_formatted_link({:controller => 'projects', :action => 'index'}) %>
|
||||
<%= text_formatted_link({:controller => 'projects', :action => 'index'}) %>
|
||||
All Projects
|
||||
</li>
|
||||
<li>
|
||||
<%= rss_formatted_link({:controller => 'projects', :action => 'index', :only_active_with_no_next_actions => true}) %>
|
||||
<%= text_formatted_link({:controller => 'projects', :action => 'index', :only_active_with_no_next_actions => true}) %>
|
||||
Active projects with no next actions
|
||||
</li>
|
||||
<li><h4>Feeds for incomplete actions in a specific context:</h4>
|
||||
<ul>
|
||||
<% for context in @contexts %>
|
||||
<li>
|
||||
<%= rss_formatted_link({ :controller=> 'todos', :action => 'index', :context_id => context.to_param }) %>
|
||||
<%= text_formatted_link({ :controller=> 'todos', :action => 'index', :context_id => context.to_param }) %>
|
||||
<%= ical_formatted_link({ :controller=> 'todos', :action => 'index', :context_id => context.to_param }) %>
|
||||
Next actions in <strong><%=h context.name %></strong>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</li>
|
||||
<li><h4>Feeds for incomplete actions in a specific project:</h4>
|
||||
<ul>
|
||||
<% for project in @projects %>
|
||||
<li>
|
||||
<%= rss_formatted_link({ :controller=> 'todos', :action => 'index', :project_id => project.to_param }) %>
|
||||
<%= text_formatted_link({ :controller=> 'todos', :action => 'index', :project_id => project.to_param }) %>
|
||||
<%= ical_formatted_link({ :controller=> 'todos', :action => 'index', :project_id => project.to_param }) %>
|
||||
Next actions for <strong><%=h project.name %></strong>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div><!-- End of display_box -->
|
||||
|
||||
<div id="input_box">
|
||||
<%= render "sidebar/sidebar" %>
|
||||
</div><!-- End of input box -->
|
24
tracks/app/views/layouts/login.rhtml
Normal file
24
tracks/app/views/layouts/login.rhtml
Normal file
|
@ -0,0 +1,24 @@
|
|||
<!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" %>
|
||||
<%= javascript_include_tag :defaults %>
|
||||
<script type="text/javascript">
|
||||
function setfocus() {
|
||||
var f = $('user_login');
|
||||
if (!f) { f = $('openid_url'); }
|
||||
if (f) { f.focus() };
|
||||
}
|
||||
Event.observe(window, 'load', setfocus);
|
||||
</script>
|
||||
<title><%= @page_title -%></title>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<%= yield %>
|
||||
|
||||
<%= render :partial => "shared/footer" %>
|
||||
</body>
|
||||
</html>
|
15
tracks/app/views/layouts/mobile.rhtml
Normal file
15
tracks/app/views/layouts/mobile.rhtml
Normal file
|
@ -0,0 +1,15 @@
|
|||
<!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 "mobile" %>
|
||||
|
||||
<title><%= @page_title %></title>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<%= yield %>
|
||||
|
||||
</body>
|
||||
</html>
|
10
tracks/app/views/layouts/scaffold.rhtml
Normal file
10
tracks/app/views/layouts/scaffold.rhtml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||
<head>
|
||||
<title>Login</title>
|
||||
<%= stylesheet_link_tag "scaffold" %>
|
||||
</head>
|
||||
<body>
|
||||
<%= yield %>
|
||||
</body>
|
||||
</html>
|
75
tracks/app/views/layouts/standard.rhtml
Normal file
75
tracks/app/views/layouts/standard.rhtml
Normal file
|
@ -0,0 +1,75 @@
|
|||
<!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" />
|
||||
<% if @prefs.refresh != 0 -%>
|
||||
<meta http-equiv="Refresh" content="<%= @prefs["refresh"].to_i*60 %>;url=<%= request.request_uri %>">
|
||||
<% end -%>
|
||||
<%= stylesheet_link_tag "standard" %>
|
||||
<%= stylesheet_link_tag "print", :media => "print" %>
|
||||
<%= javascript_include_tag :defaults, :unobtrusive %>
|
||||
<%= javascript_include_tag "selector-addon-v1" %>
|
||||
<%= stylesheet_link_tag 'calendar-system.css' %>
|
||||
<%= javascript_include_tag 'calendar', 'calendar-en', 'calendar-setup' %>
|
||||
<%= javascript_include_tag "accesskey-hints" %>
|
||||
<%= javascript_include_tag "todo-items" %>
|
||||
|
||||
<link rel="shortcut icon" href="<%= url_for(:controller => 'favicon.ico') %>" />
|
||||
<%= auto_discovery_link_tag(:rss,{:controller => "feed", :action => "na_feed", :name => "#{@user.login}", :token => "#{@user.word}"}, {:title => "RSS feed of next actions"}) %>
|
||||
|
||||
<title><%= @page_title %></title>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div id="topbar">
|
||||
<div id="date">
|
||||
<h1>
|
||||
<% if @count %>
|
||||
<span id="badge_count" class="badge"><%= @count %></span>
|
||||
<% end %>
|
||||
<%= user_time.strftime(@prefs.title_date_format) %> <%= image_tag("spinner.gif", :size => "16X16", :border => 0, :id => 'busy', :style => 'display:none' ) %>
|
||||
</h1>
|
||||
</div>
|
||||
<div id="minilinks">
|
||||
<%= link_to_function("Toggle notes", nil, {:accesskey => "S", :title => "Toggle all notes"}) do |page|
|
||||
page.select('.notes').each { |e| e.toggle }
|
||||
end
|
||||
-%> |
|
||||
<%= link_to "Logout (#{@user.display_name}) »", :controller => "login", :action=>"logout"%>
|
||||
</div>
|
||||
|
||||
<div id="navcontainer">
|
||||
<ul id="navlist">
|
||||
<li><%= navigation_link("Home", home_path, {:accesskey => "t", :title => "Home"} ) %></li>
|
||||
<li><%= navigation_link( "Contexts", contexts_path, {:accesskey=>"c", :title=>"Contexts"} ) %></li>
|
||||
<li><%= navigation_link( "Projects", projects_path, {:accesskey=>"p", :title=>"Projects"} ) %></li>
|
||||
<li><%= navigation_link( "Tickler", tickler_path, :title => "Tickler" ) %></li>
|
||||
<li><%= navigation_link( "Done", done_path, {:accesskey=>"d", :title=>"Completed"} ) %></li>
|
||||
<li><%= navigation_link( "Notes", notes_path, {:accesskey => "o", :title => "Show all notes"} ) %></li>
|
||||
<li><%= navigation_link( "Preferences", {:controller => "preferences", :action => "index"}, {:accesskey => "u", :title => "Show my preferences"} ) %></li>
|
||||
<li><%= navigation_link( "Import/Export", {:controller => "data", :action => "index"}, {:accesskey => "i", :title => "Import and export data"} ) %></li>
|
||||
<% if @user.is_admin? -%>
|
||||
<li><%= navigation_link("Admin", users_path, {:accesskey => "a", :title => "Add or delete users"} ) %></li>
|
||||
<% end -%>
|
||||
<li><%= navigation_link(image_tag("feed-icon.png", :size => "16X16", :border => 0), {:controller => "feedlist", :action => "index"}, :title => "See a list of available feeds" ) %></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<%= render_flash %>
|
||||
</div>
|
||||
|
||||
<div id="content">
|
||||
|
||||
<% unless @controller_name == 'feed' or session['noexpiry'] == "on" -%>
|
||||
<%= periodically_call_remote( :url => {:controller => "login", :action => "check_expiry"},
|
||||
:frequency => (5*60)) %>
|
||||
<% end -%>
|
||||
<%= periodically_call_remote( :url => formatted_check_deferred_todos_path(:js),
|
||||
:method => :post,
|
||||
:frequency => (10*60)) %>
|
||||
<%= yield %>
|
||||
</div>
|
||||
|
||||
<%= render :partial => "shared/footer" %>
|
||||
</body>
|
||||
</html>
|
1
tracks/app/views/login/_redirect_to_login.rjs
Normal file
1
tracks/app/views/login/_redirect_to_login.rjs
Normal file
|
@ -0,0 +1 @@
|
|||
page.redirect_to :controller => 'login', :action => 'login'
|
3
tracks/app/views/login/check_expiry.rjs
Normal file
3
tracks/app/views/login/check_expiry.rjs
Normal file
|
@ -0,0 +1,3 @@
|
|||
unless @msg == ""
|
||||
page.replace_html "info", content_tag("div", @msg + link_to("log in again.", :controller => "login", :action => "login"), "class" => "warning")
|
||||
end
|
52
tracks/app/views/login/login.rhtml
Normal file
52
tracks/app/views/login/login.rhtml
Normal file
|
@ -0,0 +1,52 @@
|
|||
<% auth_schemes = Tracks::Config.auth_schemes -%>
|
||||
<div title="Account login" id="loginform" class="form">
|
||||
|
||||
<%= render_flash %>
|
||||
|
||||
<h3>Please log in to use Tracks:</h3>
|
||||
|
||||
<% if auth_schemes.include?('database') || auth_schemes.include?('open_id') %>
|
||||
<% form_tag :action=> 'login' do %>
|
||||
<table>
|
||||
<tr>
|
||||
<td width="100px"><label for="user_login">Login:</label></td>
|
||||
<td width="100px"><input type="text" name="user_login" id="user_login" value="" class="login_text" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="100px"><label for="user_password">Password:</label></td>
|
||||
<td width="100px"><input type="password" name="user_password" id="user_password" class="login_text" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="100px"><label for="user_noexpiry">Stay logged in:</label></td>
|
||||
<td width="100px"><input type="checkbox" name="user_noexpiry" id="user_noexpiry" checked /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="100px"></td>
|
||||
<td><input type="submit" name="login" value="Login »" class="primary" /></td>
|
||||
</tr>
|
||||
</table>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<% if auth_schemes.include?('open_id') %>
|
||||
<% form_tag :action=> 'login', :action => 'begin' do %>
|
||||
<table>
|
||||
<tr>
|
||||
<td width="100px"><label for="openid_url">Identity URL:</label></td>
|
||||
<td width="100px"><input type="text" name="openid_url" id="openid_url" value="<%= @openid_url %>" class="login_text open_id" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="100px"><label for="user_noexpiry">Stay logged in:</label></td>
|
||||
<td width="100px"><input type="checkbox" name="user_noexpiry" id="user_noexpiry" checked /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="100px"></td>
|
||||
<td><input type="submit" name="login" value="Verify »" class="primary" /></td>
|
||||
</tr>
|
||||
</table>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
</div>
|
||||
|
||||
|
35
tracks/app/views/mobile/_mobile_actions.rhtml
Normal file
35
tracks/app/views/mobile/_mobile_actions.rhtml
Normal file
|
@ -0,0 +1,35 @@
|
|||
<%= render_flash %>
|
||||
<% if @todos.length == 0 -%>
|
||||
<p>There are no incomplete actions in this <%= @type %></p>
|
||||
<% else -%>
|
||||
<ul>
|
||||
<% for todo in @todos -%>
|
||||
<li>
|
||||
<%= link_to "»", :controller => 'mobile', :action => 'detail', :id => todo.id %>
|
||||
<% if todo.due? -%>
|
||||
<%= due_date_mobile(todo.due) %>
|
||||
<% end -%>
|
||||
<%= todo.description %>
|
||||
(<em><%= todo.context.name %></em>)
|
||||
<% end -%>
|
||||
</li>
|
||||
<ul>
|
||||
<% if !@todos_pages.nil? -%>
|
||||
<% if @todos_pages.length > 1 -%>
|
||||
<hr />
|
||||
Pages: <%= pagination_links( @todos_pages, :always_show_anchors => true ) %>
|
||||
<% end -%>
|
||||
<% end -%>
|
||||
<% end -%>
|
||||
<hr />
|
||||
<% form_tag( { :action => "filter", :type => "context" } ) do -%>
|
||||
<%= collection_select( "context", "id", @contexts, "id", "name",
|
||||
{ :include_blank => true } ) %>
|
||||
<%= submit_tag( value = "Go" ) %>
|
||||
<% end -%>
|
||||
|
||||
<% form_tag( {:action => "filter", :type => "project" }) do -%>
|
||||
<%= collection_select( "project", "id", @projects, "id", "name",
|
||||
{ :include_blank => true } ) %>
|
||||
<%= submit_tag( value = "Go" ) %>
|
||||
<% end -%>
|
21
tracks/app/views/mobile/_mobile_edit.rhtml
Normal file
21
tracks/app/views/mobile/_mobile_edit.rhtml
Normal file
|
@ -0,0 +1,21 @@
|
|||
<span class="errors">
|
||||
<%= error_messages_for("todo") %>
|
||||
</span>
|
||||
<% this_year = user_time.to_date.strftime("%Y").to_i -%>
|
||||
<p><label for="todo_state">Done?</label></p>
|
||||
<p><%= check_box( "todo", "state", "tabindex" => 1) %></p>
|
||||
<p><label for="todo_description">Next action</label></p>
|
||||
<p><%= text_field( "todo", "description", "tabindex" => 2) %></p>
|
||||
<p><label for="todo_notes">Notes</label></p>
|
||||
<p><%= text_area( "todo", "notes", "cols" => 30, "rows" => 5, "tabindex" => 3) %></p>
|
||||
<p><label for="todo_context_id">Context</label></p>
|
||||
<p><%= collection_select( "todo", "context_id", @contexts, "id", "name", {"tabindex" => 4} ) %></p> <p><label for="todo_project_id">Project</label></p>
|
||||
<p><%= collection_select( "todo", "project_id", @projects, "id", "name",
|
||||
{:include_blank => true}, {"tabindex" => 5} ) %></p>
|
||||
<p><label for="todo_due">Due</label></p>
|
||||
<p><%= date_select("todo", "due", :order => [:day, :month, :year],
|
||||
:start_year => this_year, :include_blank => true) %></p>
|
||||
<p><label for="todo_show_from">Show from</label></p>
|
||||
<p><%= date_select("todo", "show_from", :order => [:day, :month, :year],
|
||||
:start_year => this_year, :include_blank => true) %></p>
|
||||
<p><input type="submit" value="Update" tabindex="6" /></p>
|
4
tracks/app/views/mobile/detail.rhtml
Normal file
4
tracks/app/views/mobile/detail.rhtml
Normal file
|
@ -0,0 +1,4 @@
|
|||
<% form_tag :action => 'update', :id => @item.id do -%>
|
||||
<%= render :partial => 'mobile_edit' %>
|
||||
<% end -%>
|
||||
<%= button_to "Back", :controller => 'mobile', :action => 'index' %>
|
6
tracks/app/views/mobile/filter.rhtml
Normal file
6
tracks/app/views/mobile/filter.rhtml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<h1><span class="count"><%= @count.to_s %></span> <%= @desc %>
|
||||
<%= link_to "+", :controller => 'mobile', :action => 'add_action' %></h1>
|
||||
<hr />
|
||||
<%= render :partial => 'mobile_actions' %>
|
||||
|
||||
<%= link_to "View All", :controller => 'mobile', :action => 'index' %>
|
5
tracks/app/views/mobile/index.rhtml
Normal file
5
tracks/app/views/mobile/index.rhtml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<h1><span class="count"><%= @count.to_s %></span> <%= @desc %>
|
||||
<%= link_to "+", :controller => 'mobile', :action => 'add_action' %></h1>
|
||||
<hr />
|
||||
<%= render :partial => 'mobile_actions' %>
|
||||
<%= link_to "Logout", :controller => 'login', :action => 'logout' %>
|
4
tracks/app/views/mobile/show_add_form.rhtml
Normal file
4
tracks/app/views/mobile/show_add_form.rhtml
Normal file
|
@ -0,0 +1,4 @@
|
|||
<% form_tag :action => 'update' do %>
|
||||
<%= render :partial => 'mobile_edit' %>
|
||||
<% end -%>
|
||||
<%= button_to "Back", :controller => 'mobile', :action => 'index' %>
|
6
tracks/app/views/notes/_note_edit_form.rhtml
Normal file
6
tracks/app/views/notes/_note_edit_form.rhtml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<% @note = note_edit_form %>
|
||||
<%= hidden_field( "note", "project_id" ) %>
|
||||
<%= text_area( "note", "body", "cols" => 70, "rows" => 15, "tabindex" => 1 ) %>
|
||||
<br /><br />
|
||||
<input type="submit" value="Update" tabindex="2" />
|
||||
<% @note = nil %>
|
35
tracks/app/views/notes/_notes.rhtml
Normal file
35
tracks/app/views/notes/_notes.rhtml
Normal file
|
@ -0,0 +1,35 @@
|
|||
<% note = notes -%>
|
||||
<div id="<%= dom_id(note, 'container') %>">
|
||||
<h2><%= link_to("Note #{note.id}", note_path(note), :title => "Show note #{note.id}" ) %></h2>
|
||||
<div id="<%= dom_id(note) %>">
|
||||
<%= sanitize(textilize(note.body)) %>
|
||||
|
||||
<div class="note_footer">
|
||||
<%= link_to_remote( image_tag("blank.png", :title =>"Delete this note", :class=>"delete_item"),
|
||||
:update => dom_id(note),
|
||||
:loading => visual_effect(:fade, dom_id(note, 'container')),
|
||||
:complete => "Element.remove('#{dom_id(note, 'container')}');",
|
||||
:url => note_path(note),
|
||||
:method => :delete,
|
||||
:confirm => "Are you sure that you want to delete the note \'#{note.id.to_s}\'?" ) + " " -%>
|
||||
<%= link_to_function(image_tag( "blank.png", :title => "Edit item", :class=>"edit_item"),
|
||||
"Element.toggle('#{dom_id(note)}'); Element.toggle('#{dom_id(note, 'edit')}'); Effect.Appear('#{dom_id(note, 'edit')}'); Form.focusFirstElement('#{dom_id(note, 'edit_form')}');" ) + " | " %>
|
||||
<%= link_to("In: " + note.project.name, project_path(note.project), :class=>"footer_link" ) %> |
|
||||
Created: <%= format_date(note.created_at) %>
|
||||
<% if note.updated_at? -%>
|
||||
| Modified: <%= format_date(note.updated_at) %>
|
||||
<% end -%>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="<%= dom_id(note, 'edit') %>" class="edit-form" style="display:none;">
|
||||
<% form_remote_tag :url => note_path(note),
|
||||
:method => :put,
|
||||
:html => { :id => dom_id(note, 'edit_form'), :class => "inline-form" },
|
||||
:update => dom_id(note, 'container'),
|
||||
:complete => visual_effect(:appear, dom_id(note, 'container')) do -%>
|
||||
<%= render :partial => "note_edit_form", :object => note %>
|
||||
<% end -%>
|
||||
</div>
|
||||
</div>
|
||||
<% note = nil -%>
|
6
tracks/app/views/notes/_notes_summary.rhtml
Normal file
6
tracks/app/views/notes/_notes_summary.rhtml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<% note = notes_summary -%>
|
||||
<div class="note_wrapper" id="<%= dom_id(note) %>">
|
||||
<%= link_to( image_tag("blank.png", :border => 0), note_path(note), :title => "Show note", :class => "link_to_notes icon") %>
|
||||
<%= truncated_note(note) %>
|
||||
</div>
|
||||
<% note = nil -%>
|
11
tracks/app/views/notes/index.rhtml
Normal file
11
tracks/app/views/notes/index.rhtml
Normal file
|
@ -0,0 +1,11 @@
|
|||
<div id="display_box_projects">
|
||||
<% if @all_notes.empty? -%>
|
||||
<div class="message"><p>Currently there are no notes: add notes to projects from individual project pages.</p></div>
|
||||
<% else -%>
|
||||
<% for notes in @all_notes -%>
|
||||
<div class="container" id="note-<%= notes.id %>-wrapper">
|
||||
<%= render :partial => 'notes', :object => notes %>
|
||||
</div>
|
||||
<% end -%>
|
||||
<% end -%>
|
||||
</div>
|
5
tracks/app/views/notes/show.rhtml
Normal file
5
tracks/app/views/notes/show.rhtml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<div id="display_box_projects">
|
||||
<div class="container" id="note-<%= @note.id %>-wrapper">
|
||||
<%= render :partial => 'notes', :object => @note %>
|
||||
</div>
|
||||
</div>
|
74
tracks/app/views/preferences/edit.rhtml
Normal file
74
tracks/app/views/preferences/edit.rhtml
Normal file
|
@ -0,0 +1,74 @@
|
|||
<div id="display_box" class="container context">
|
||||
<h2>Help on preferences</h2>
|
||||
<p>The preference settings should mostly be self-explanatory, but some hints are included below: </p>
|
||||
<ul>
|
||||
<li><strong>first name and last name:</strong> Used for display purposes if set</li>
|
||||
<li><strong>date format:</strong> the format in which you'd like dates to be shown. For example, for the date 31st January 2006, %d/%m/%Y will show 31/01/2006, %b-%e-%y will show Jan-31-06. See the <a href="http://uk2.php.net/strftime" title="PHP strftime manual">strftime manual</a> for more formatting options for the date.</li>
|
||||
<li><strong>title date format:</strong> same as above, but for the big date at the top of each page.</li>
|
||||
<li><strong>time zone:</strong> your local time zone</li>
|
||||
<li><strong>week starts:</strong> day of the week shown as the start of the week on the popup calendar.</li>
|
||||
<li><strong>due style:</strong> style in which due dates are shown, e.g. "Due in 3 days", "Due on Wednesday"</li>
|
||||
<li><strong>show completed projects in sidebar:</strong> whether or not projects marked as complete are shown in the sidebar on the home page and elsewhere</li>
|
||||
<li><strong>show hidden contexts in sidebar:</strong> whether or not contexts marked as hidden are shown in the sidebar on the home page and elsewhere</li>
|
||||
<li><strong>show project on todo done:</strong> whether or not to redirect to the project page when an action associated with a project is marked complete</li>
|
||||
<% if @user.is_admin? %>
|
||||
<li><strong>admin email:</strong> email address for the admin user of Tracks (displayed on the signup page for users to contact to obtain an account)</li>
|
||||
<% end %>
|
||||
<li><strong>staleness starts:</strong> the number of days before items with no due date get marked as stale (with a yellow highlight)</li>
|
||||
<li><strong>show number completed:</strong> number of completed actions to show on the home page. If you set this to zero, the completed actions box will not be shown on the home page or on the individual context or project pages. You can still see all your completed items by clicking the 'Done' link in the navigation bar at the top of each page.</li>
|
||||
<li><strong>refresh:</strong> automatic refresh interval for each of the pages (in minutes)</li>
|
||||
<li><strong>verbose action descriptor:</strong> when true, show project/context name in action listing; when false show [P]/[C] with tool tips</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div id="input_box" class="container context">
|
||||
<% form_tag :action => 'update' do %>
|
||||
<table>
|
||||
<tr>
|
||||
<td><label>first name:</label></td>
|
||||
<td><%= text_field 'user', 'first_name' %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label>last name:</label></td>
|
||||
<td><%= text_field 'user', 'last_name' %></td>
|
||||
</tr>
|
||||
<%
|
||||
def table_row(pref_name, nowrap_label = false, &block)
|
||||
nowrap_attribute = nowrap_label ? ' nowrap="nowrap"' : ''
|
||||
s = %Q|<tr>\n<td#{nowrap_attribute}><label>#{pref_name.gsub(/_/,' ')}:</label></td>\n<td>\n|
|
||||
s << yield
|
||||
s << "\n</td></tr>"
|
||||
s
|
||||
end
|
||||
|
||||
def row_with_select_field(pref_name, collection = [true,false], nowrap_label = false)
|
||||
table_row(pref_name, nowrap_label) { select('prefs', pref_name, collection) }
|
||||
end
|
||||
|
||||
def row_with_text_field(pref_name, nowrap_label = false)
|
||||
table_row(pref_name, nowrap_label) { text_field('prefs', pref_name) }
|
||||
end
|
||||
%>
|
||||
<%= row_with_text_field('date_format') %>
|
||||
<%= row_with_text_field('title_date_format') %>
|
||||
<%= table_row('time_zone', false) { time_zone_select('prefs','time_zone') } %>
|
||||
|
||||
<%= row_with_select_field("week_starts", Preference.day_number_to_name_map.invert.sort{|a,b| a[1]<=>b[1]})%>
|
||||
<%= row_with_select_field("due_style", [['Due in ___ days',0],['Due on _______',1]]) %>
|
||||
<%= row_with_select_field("show_completed_projects_in_sidebar") %>
|
||||
<%= row_with_select_field("show_hidden_projects_in_sidebar") %>
|
||||
<%= row_with_select_field("show_hidden_contexts_in_sidebar") %>
|
||||
<%= row_with_select_field("show_project_on_todo_done") %>
|
||||
|
||||
<% if @user.is_admin? %> <%= row_with_text_field('admin_email') %> <% end %>
|
||||
<%= row_with_text_field('staleness_starts', true) %>
|
||||
<%= row_with_text_field('show_number_completed') %>
|
||||
<%= row_with_text_field('refresh') %>
|
||||
<%= row_with_select_field("verbose_action_descriptors") %>
|
||||
|
||||
<tr><td><%= submit_tag "Update" %></td>
|
||||
<td><%= link_to "Cancel", :action => 'index' %></td>
|
||||
</tr>
|
||||
</table>
|
||||
<% end %>
|
||||
</div>
|
64
tracks/app/views/preferences/index.rhtml
Normal file
64
tracks/app/views/preferences/index.rhtml
Normal file
|
@ -0,0 +1,64 @@
|
|||
<div id="single_box" class="container context prefscontainer">
|
||||
|
||||
<h2>Your preferences</h2>
|
||||
|
||||
<ul id="prefs">
|
||||
<li>First name: <span class="highlight"><%= @user.first_name %></span></li>
|
||||
<li>Last name: <span class="highlight"><%= @user.last_name %></span></li>
|
||||
<li>Date format: <span class="highlight"><%= @prefs.date_format %></span> Your current date: <%= format_date(user_time) %></li>
|
||||
<li>Title date format: <span class="highlight"><%= @prefs.title_date_format %></span> Your current title date: <%= user_time.strftime(@prefs.title_date_format) %></li>
|
||||
<li>Time zone: <span class="highlight"><%= @prefs.tz %></span> Your current time: <%= user_time.strftime('%I:%M %p') %></li>
|
||||
<li>Week starts on: <span class="highlight"><%= Preference.day_number_to_name_map[@prefs.week_starts] %></span></li>
|
||||
<li>Show the last <span class="highlight"><%= @prefs.show_number_completed %></span> completed items on the home page</li>
|
||||
<li>Show completed projects in sidebar: <span class="highlight"><%= @prefs.show_completed_projects_in_sidebar %></span></li>
|
||||
<li>Show hidden projects in sidebar: <span class="highlight"><%= @prefs.show_hidden_projects_in_sidebar %></span></li>
|
||||
<li>Show hidden contexts in sidebar: <span class="highlight"><%= @prefs.show_hidden_contexts_in_sidebar %></span></li>
|
||||
<li>Go to project page on todo complete: <span class="highlight"><%= @prefs.show_project_on_todo_done %></span></li>
|
||||
<li>Staleness starts after <span class="highlight"><%= @prefs.staleness_starts %></span> days</li>
|
||||
<li>Due style: <span class="highlight">
|
||||
<% if @prefs.due_style == "0" %>
|
||||
Due in ___ days
|
||||
<% else %>
|
||||
Due on ________
|
||||
<% end %>
|
||||
</span></li>
|
||||
<% if @user.is_admin? %>
|
||||
<li>Admin email: <span class="highlight"><%= @prefs.admin_email %></span></li>
|
||||
<% end %>
|
||||
<li>Refresh interval (in minutes): <span class="highlight"><%= @prefs.refresh %></span></li>
|
||||
<li>Verbose action descriptors: <span class="highlight"><%= @prefs.verbose_action_descriptors %></span></li>
|
||||
</ul>
|
||||
<div class="actions">
|
||||
<%= link_to "Edit preferences »", { :controller => 'preferences', :action => 'edit'}, :class => 'edit_link' %>
|
||||
</div>
|
||||
|
||||
<h2>Your token</h2>
|
||||
<div id="token_area">
|
||||
<div class="description">Token (for feeds and API use):</div>
|
||||
<div id="token><span class="highlight"><%= @user.word %></span></div>
|
||||
<div class="token_regenerate">
|
||||
<%= button_to "Generate a new token", refresh_token_user_path(@user),
|
||||
:confirm => "Are you sure? Generating a new token will replace the existing one and break any external usages of this token." %>
|
||||
</div>
|
||||
</div>
|
||||
<h2>Your authentication</h2>
|
||||
<div id="authentication_area">
|
||||
<% if Tracks::Config.auth_schemes.length > 1 %>
|
||||
<p>Your authentication type is <span class="highlight"><%= @user.auth_type %></span>.
|
||||
<div class="actions">
|
||||
<%= link_to "Change your authentication type »", change_auth_type_user_path(@user), :class => 'edit_link' %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if @user.auth_type == 'database' %>
|
||||
<div class="actions">
|
||||
<%= link_to 'Change your password »', change_password_user_path(@user) %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if @user.auth_type == 'open_id' %>
|
||||
<p>Your Open ID URL is <span class="highlight"><%= @user.open_id_url %></span>.
|
||||
<div class="actions">
|
||||
<%= link_to 'Change Your Identity URL »', change_auth_type_user_path(@user) %></p>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,6 @@
|
|||
<div class="page_name_auto_complete" id="default_context_list" style="display:none;z-index:9999"></div>
|
||||
<script type="text/javascript">
|
||||
defaultContextAutoCompleter = new Autocompleter.Local('project[default_context_name]', 'default_context_list', <%= context_names_for_autocomplete %>, {choices:100,autoSelect:false});
|
||||
Event.observe($('project[default_context_name]'), "focus", defaultContextAutoCompleter.activate.bind(defaultContextAutoCompleter));
|
||||
Event.observe($('project[default_context_name]'), "click", defaultContextAutoCompleter.activate.bind(defaultContextAutoCompleter));
|
||||
</script>
|
5
tracks/app/views/projects/_edit_project.rhtml
Normal file
5
tracks/app/views/projects/_edit_project.rhtml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<%= hidden_field( "project", "id" ) %>
|
||||
<label for="project_name">Project name</label><br />
|
||||
<%= text_field( "project", "name" ) %>
|
||||
<br />
|
||||
<br />
|
25
tracks/app/views/projects/_project.rhtml
Normal file
25
tracks/app/views/projects/_project.rhtml
Normal file
|
@ -0,0 +1,25 @@
|
|||
<% @not_done = project.not_done_todos -%>
|
||||
|
||||
<div id="p<%= project.id %>" class="container project">
|
||||
<h2>
|
||||
<% if collapsible %>
|
||||
<a href="#" class="container_toggle" id="toggle_p<%= project.id %>"><%= image_tag("collapse.png") %></a>
|
||||
<% end %>
|
||||
<%= in_place_editor_field :project, :name, {}, { :url => url_for(:controller => 'project', :action => 'update', :id => project.id, :field => 'name', :wants_render => false) } %>
|
||||
</h2>
|
||||
<% if @project.description -%>
|
||||
<div class="project_description"><%= sanitize(@project.description) %></div>
|
||||
<% end -%>
|
||||
|
||||
<% if @project.completed? -%>
|
||||
<p class="project_completed">Project has been marked as completed</p>
|
||||
<% elsif @project.completed? -%>
|
||||
<p class="project_completed">Project has been marked as hidden</p>
|
||||
<% end -%>
|
||||
<div class="items toggle_target">
|
||||
<div id="p<%= project.id %>empty-nd" style="display:<%= @not_done.empty? ? 'block' : 'none'%>;">
|
||||
<div class="message"><p>Currently there are no incomplete actions in this project</p></div>
|
||||
</div>
|
||||
<%= render :partial => "todos/todo", :collection => @not_done, :locals => { :parent_container_type => "project" } %>
|
||||
</div><!-- [end:items] -->
|
||||
</div><!-- [end:p<%= project.id %>] -->
|
26
tracks/app/views/projects/_project_form.rhtml
Normal file
26
tracks/app/views/projects/_project_form.rhtml
Normal file
|
@ -0,0 +1,26 @@
|
|||
<%
|
||||
@project = project_form
|
||||
%>
|
||||
<tr>
|
||||
<td width="150"><label for="project_name">Name:</label></td>
|
||||
<td width="300"><%= text_field :project, 'name', :class => 'project-name' %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><label for="project_description">Description (optional):</label></td>
|
||||
<td width="300"><%= text_area :project, 'description', "cols" => 30, "rows" => 4, :class => 'project-description' %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><label for="project_done">Project status:</label></td>
|
||||
<td width="300">
|
||||
<% ['active', 'hidden', 'completed'].each do | state | %>
|
||||
<%= radio_button(:project, 'state', state) %> <%= state.titlecase %>
|
||||
<% end %>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><label for="project[default_context_name]">Default Context</label></td>
|
||||
<td width="300">
|
||||
<%= text_field_tag("project[default_context_name]", @project.default_context.name, {:tabindex=>1,:size=> 25}) %>
|
||||
<%= render :partial => 'default_context_autocomplete' %>
|
||||
</td>
|
||||
</tr>
|
54
tracks/app/views/projects/_project_listing.rhtml
Normal file
54
tracks/app/views/projects/_project_listing.rhtml
Normal file
|
@ -0,0 +1,54 @@
|
|||
<% project = project_listing
|
||||
@project_listing_zindex = @project_listing_zindex.nil? ? 200 : @project_listing_zindex - 1
|
||||
-%>
|
||||
<div id="<%= dom_id(project, "container") %>" class="list" style="z-index:<%= @project_listing_zindex %>">
|
||||
<div id="<%= dom_id(project) %>" class="project sortable_row" style="display:''">
|
||||
<div class="position">
|
||||
<span class="handle">DRAG</span>
|
||||
</div>
|
||||
<div class="data">
|
||||
<%= link_to_project( project ) %><%= " (" + count_undone_todos_and_notes_phrase(project,"actions") + ")" %>
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<span class="grey"><%= project.current_state.to_s.upcase %></span>
|
||||
<a class="delete_project_button" href="<%= formatted_project_path(project, :js) %>" title="delete the project '<%= project.name %>'"><%= image_tag( "blank.png", :title => "Delete project", :class=>"delete_item") %></a>
|
||||
<%= apply_behavior "a.delete_project_button:click", { :prevent_default => true, :external => true} do |page|
|
||||
page << "if (confirm('Are you sure that you want to ' + this.title + '?')) {"
|
||||
page << " new Ajax.Request(this.href, {asynchronous:true,"
|
||||
page << " evalScripts:true, method:'delete'}); };"
|
||||
end -%>
|
||||
<a class="edit_project_button" href="#"><%= image_tag( "blank.png", :title => "Edit project", :class=>"edit_item") %></a>
|
||||
<%= apply_behavior 'a.edit_project_button:click', {:prevent_default => true, :external => true} do |page, element|
|
||||
element.up('.project').toggle
|
||||
editform = element.up('.list').down('.edit-form')
|
||||
editform.toggle
|
||||
editform.visual_effect(:appear)
|
||||
editform.down('input').focus
|
||||
end
|
||||
-%>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="<%= dom_id(project, 'edit') %>" class="edit-form" style="display:none;">
|
||||
<% form_tag project_path(project), { :id => dom_id(project, 'edit_form'), :class => "inline-form edit-project-form", :method => :put } do -%>
|
||||
<table style="table-layout: fixed;" width="450">
|
||||
<%= render :partial => 'project_form', :object => project %>
|
||||
<tr>
|
||||
<td width="150"> <input type="hidden" name="wants_render" value="true" /></td>
|
||||
<td width="300"><input type="submit" value="Update" /> <a href="#" class="form_reset">Cancel</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
<% end -%>
|
||||
<%= apply_behavior ".edit-project-form", make_remote_form(:complete => "Effect.Appear($(this).up('.list'));" ), :external => true %>
|
||||
<%= apply_behavior "a.form_reset:click", :prevent_default => true do |page, element|
|
||||
element.up('.list').down('.project').toggle
|
||||
element.up('.edit-form').toggle
|
||||
element.up('form').reset
|
||||
end %>
|
||||
</div>
|
||||
</div>
|
||||
<% if controller.action_name == 'create' %>
|
||||
<script>
|
||||
new Effect.Appear('<%= dom_id(project) %>');
|
||||
</script>
|
||||
<% end %>
|
7
tracks/app/views/projects/_project_state_group.rhtml
Normal file
7
tracks/app/views/projects/_project_state_group.rhtml
Normal file
|
@ -0,0 +1,7 @@
|
|||
<div class="project-state-group" id="list-<%= state %>-projects-container" <%= " style=\"display:none\"" if project_state_group.empty? %>>
|
||||
<h2><span id="<%= state %>-projects-count" class="badge"><%= project_state_group.length %></span><%= state.titlecase %> Projects</h2>
|
||||
<div id="list-<%= state %>-projects">
|
||||
<%= render :partial => 'project_listing', :collection => project_state_group %>
|
||||
</div>
|
||||
<%= sortable_element "list-#{state}-projects", get_listing_sortable_options("list-#{state}-projects") %>
|
||||
</div>
|
15
tracks/app/views/projects/create.rjs
Normal file
15
tracks/app/views/projects/create.rjs
Normal file
|
@ -0,0 +1,15 @@
|
|||
if @saved and @go_to_project
|
||||
page.redirect_to project_path(@project)
|
||||
elsif @saved
|
||||
page.hide 'projects-empty-nd'
|
||||
page.show 'list-active-projects-container'
|
||||
page.replace_html "active-projects-count", @active_projects_count
|
||||
page.insert_html :bottom, "list-active-projects", :partial => 'project_listing', :locals => { :project_listing => @project }
|
||||
page.sortable "list-active-projects", get_listing_sortable_options('list-active-projects')
|
||||
page.call "Form.reset", "project-form"
|
||||
page.call "Form.focusFirstElement", "project-form"
|
||||
else
|
||||
page.show 'status'
|
||||
page.replace_html 'status', "#{error_messages_for('project')}"
|
||||
end
|
||||
page.hide "busy"
|
12
tracks/app/views/projects/destroy.rjs
Normal file
12
tracks/app/views/projects/destroy.rjs
Normal file
|
@ -0,0 +1,12 @@
|
|||
page.visual_effect :fade, dom_id(@project, "container"), :duration => 0.5
|
||||
page.delay(0.5) do
|
||||
page[dom_id(@project, "container")].remove
|
||||
page.replace_html "active-projects-count", @active_projects_count
|
||||
page.replace_html "hidden-projects-count", @hidden_projects_count
|
||||
page.replace_html "completed-projects-count", @completed_projects_count
|
||||
page.set_element_visible ("list-hidden-projects-container", @hidden_projects_count > 0)
|
||||
page.set_element_visible ("list-active-projects-container", @active_projects_count > 0)
|
||||
page.set_element_visible ("list-completed-projects-container", @completed_projects_count > 0)
|
||||
end
|
||||
page.notify :notice, "Deleted project '#{@project.name}'", 5.0
|
||||
page.hide "busy"
|
1
tracks/app/views/projects/error.rjs
Normal file
1
tracks/app/views/projects/error.rjs
Normal file
|
@ -0,0 +1 @@
|
|||
page.notify :error, @error_message || "An error occurred on the server.", 8.0
|
39
tracks/app/views/projects/index.rhtml
Normal file
39
tracks/app/views/projects/index.rhtml
Normal file
|
@ -0,0 +1,39 @@
|
|||
<div id="display_box">
|
||||
<div id="projects-empty-nd" style="display:<%= @no_projects ? 'block' : 'none'%>;">
|
||||
<div class="message"><p>Currently there are no projects</p></div>
|
||||
</div>
|
||||
<%= render :partial => 'project_state_group', :object => @active_projects, :locals => { :state => 'active'} %>
|
||||
<%= render :partial => 'project_state_group', :object => @hidden_projects, :locals => { :state => 'hidden'} %>
|
||||
<%= render :partial => 'project_state_group', :object => @completed_projects, :locals => { :state => 'completed'} %>
|
||||
</div>
|
||||
|
||||
<% @project = @new_project -%>
|
||||
<div id="input_box">
|
||||
<%= link_to_function("Create a new project »",
|
||||
"Element.toggle('project_new');Form.focusFirstElement('project-form');",
|
||||
{:title => "Add a next action", :accesskey => "n"}) %>
|
||||
|
||||
<div id="project_new" class="project_new" style="display:none">
|
||||
<% form_remote_tag :url => projects_path, :method => :post,
|
||||
:html=> { :id=>'project-form', :name=>'project', :class => 'inline-form' } do -%>
|
||||
|
||||
<div id="status"><%= error_messages_for('project') %></div>
|
||||
|
||||
<label for="project_name">Name:</label><br />
|
||||
<%= text_field 'project', 'name', "tabindex" => 1 %><br />
|
||||
|
||||
<label for="project_description">Description (optional):</label><br />
|
||||
<%= text_area 'project', 'description', "cols" => 30, "rows" => 4, "tabindex" => 2 %><br />
|
||||
|
||||
<label for="default_context_name">Default Context (optional):</label><br />
|
||||
<%= text_field_tag("project[default_context_name]", @project.default_context.name, :tabindex => 3) %>
|
||||
<%= render :partial => 'default_context_autocomplete' %>
|
||||
<br />
|
||||
|
||||
<input type="submit" value="Add Project" tabindex="4" /><br />
|
||||
|
||||
<input id="go_to_project" type="checkbox" tabindex="5" name="go_to_project"/><label for="go_to_project"> Take me to the new project page</label><br />
|
||||
|
||||
<% end -%>
|
||||
</div>
|
||||
</div>
|
6
tracks/app/views/projects/index_text.rhtml
Normal file
6
tracks/app/views/projects/index_text.rhtml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<% @projects.each do |p| -%>
|
||||
|
||||
<%= p.name.upcase %>
|
||||
<%= p.description + "\n" unless p.description.blank? -%>
|
||||
<%= count_undone_todos_phrase_text(p)%>. Project is <%= p.state %>.
|
||||
<% end -%>
|
76
tracks/app/views/projects/show.rhtml
Normal file
76
tracks/app/views/projects/show.rhtml
Normal file
|
@ -0,0 +1,76 @@
|
|||
<div id="display_box">
|
||||
<div id="project-next-prev">
|
||||
<%= project_next_prev %>
|
||||
</div>
|
||||
|
||||
<%= render :partial => "projects/project", :locals => { :project => @project, :collapsible => false } %>
|
||||
<%= render :partial => "todos/deferred", :locals => { :deferred => @deferred, :collapsible => false, :append_descriptor => "in this project" } %>
|
||||
<%= render :partial => "todos/completed", :locals => { :done => @done, :collapsible => false, :append_descriptor => "in this project" } %>
|
||||
|
||||
<div class="container">
|
||||
<div id="notes">
|
||||
<div class="add_note_link"><%= link_to_function( "Add a note", "Element.toggle('new-note'); Form.focusFirstElement('form-new-note');") %></div>
|
||||
<h2>Notes</h2>
|
||||
<div id="empty-n" style="display:<%= @project.notes.empty? ? 'block' : 'none'%>;">
|
||||
<%= render :partial => "shared/empty",
|
||||
:locals => { :message => "Currently there are no notes attached to this project"} %>
|
||||
</div>
|
||||
<%= render :partial => "notes/notes_summary", :collection => @project.notes %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="new-note" style="display:none;">
|
||||
<% form_remote_tag :url => notes_path,
|
||||
:method => :post,
|
||||
:update => "notes",
|
||||
:position => "bottom",
|
||||
:complete => "new Effect.Highlight('notes');$('empty-n').hide();",
|
||||
:html => {:id=>'form-new-note', :class => 'inline-form'} do %>
|
||||
<%= hidden_field( "new_note", "project_id", "value" => "#{@project.id}" ) %>
|
||||
<%= text_area( "new_note", "body", "cols" => 50, "rows" => 3, "tabindex" => 1 ) %>
|
||||
<br /><br />
|
||||
<input type="submit" value="Add note" tabindex="2" />
|
||||
<% end -%>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div id="project_status">
|
||||
<h2>Status</h2>
|
||||
<div>
|
||||
<% ['active', 'hidden', 'completed'].each do | state | %>
|
||||
<% span_class = @project.current_state.to_s == state ? 'active_state' : 'inactive_state' %>
|
||||
<span class="<%= state %>"><%= radio_button(:project, 'state', state) %> <span class="<%= span_class %>"><%= state.titlecase %></span></span>
|
||||
<% end %>
|
||||
<% apply_behavior "#project_status input:click",
|
||||
remote_function(:url => project_path(@project), :method => :put,
|
||||
:with => "{wants_render: false, update_status: true, 'project[state]' : this.value}" )
|
||||
%>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div id="default_context">
|
||||
<h2>Default Context</h2>
|
||||
<div>
|
||||
<% form_remote_tag( :url => project_path(@project), :method => :put,
|
||||
:html=> { :id => 'set-default-context-action',
|
||||
:name => 'default_context',
|
||||
:class => 'inline-form' }) do -%>
|
||||
<%= hidden_field_tag("update_default_context", true) %>
|
||||
<%= text_field_tag("project[default_context_name]",
|
||||
@project.default_context.name,
|
||||
{ :tabindex => 9,:size => 25 }) %>
|
||||
<%= submit_tag "Set Default Context for this Project", { :tabindex => 10 } %>
|
||||
<%= render :partial => 'default_context_autocomplete' %>
|
||||
<% end -%>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- [end:display_box] -->
|
||||
|
||||
<div id="input_box">
|
||||
<%= render :partial => "shared/add_new_item_form" %>
|
||||
<%= render "sidebar/sidebar" %>
|
||||
</div><!-- End of input box -->
|
18
tracks/app/views/projects/update.rjs
Normal file
18
tracks/app/views/projects/update.rjs
Normal file
|
@ -0,0 +1,18 @@
|
|||
status_message = 'Project saved'
|
||||
page.notify :notice, status_message, 5.0
|
||||
if @state_changed
|
||||
page[dom_id(@project, 'container')].remove
|
||||
page.insert_html :bottom, "list-#{@project.state}-projects", :partial => 'project_listing', :object => @project
|
||||
else
|
||||
page.replace_html dom_id(@project, 'container'), :partial => 'project_listing', :object => @project
|
||||
end
|
||||
page.sortable "list-#{@project.state}-projects", get_listing_sortable_options("list-#{@project.state}-projects")
|
||||
page.replace_html "active-projects-count", @active_projects_count
|
||||
page.replace_html "hidden-projects-count", @hidden_projects_count
|
||||
page.replace_html "completed-projects-count", @completed_projects_count
|
||||
|
||||
page.set_element_visible ("list-hidden-projects-container", @hidden_projects_count > 0)
|
||||
page.set_element_visible ("list-active-projects-container", @active_projects_count > 0)
|
||||
page.set_element_visible ("list-completed-projects-container", @completed_projects_count > 0)
|
||||
|
||||
page.hide "busy"
|
10
tracks/app/views/projects/update_default_context.rjs
Normal file
10
tracks/app/views/projects/update_default_context.rjs
Normal file
|
@ -0,0 +1,10 @@
|
|||
if @project.default_context.nil?
|
||||
page.notify :notice, "Removed default context", 5.0
|
||||
else
|
||||
if source_view_is :project
|
||||
page['todo_context_name'].value = @project.default_context.name
|
||||
end
|
||||
page.notify :notice, "Set project's default context to #{@project.default_context.name}", 5.0
|
||||
end
|
||||
page.hide "busy"
|
||||
|
11
tracks/app/views/projects/update_status.rjs
Normal file
11
tracks/app/views/projects/update_status.rjs
Normal file
|
@ -0,0 +1,11 @@
|
|||
page.select('#project_status .active span').each do |element|
|
||||
element.className = @project.current_state == :active ? 'active_state' : 'inactive_state'
|
||||
end
|
||||
page.select('#project_status .hidden span').each do |element|
|
||||
element.className = @project.current_state == :hidden ? 'active_state' : 'inactive_state'
|
||||
end
|
||||
page.select('#project_status .completed span').each do |element|
|
||||
element.className = @project.current_state == :completed ? 'active_state' : 'inactive_state'
|
||||
end
|
||||
page.notify :notice, "Set project status to #{@project.current_state}", 5.0
|
||||
page.hide 'busy'
|
74
tracks/app/views/shared/_add_new_item_form.rhtml
Normal file
74
tracks/app/views/shared/_add_new_item_form.rhtml
Normal file
|
@ -0,0 +1,74 @@
|
|||
<%
|
||||
@todo = nil
|
||||
@initial_context_name = @context.name unless @context.nil?
|
||||
@initial_context_name ||= @project.default_context.name unless @project.nil? || @project.default_context.nil?
|
||||
@initial_context_name ||= @contexts[0].name unless @contexts[0].nil?
|
||||
@initial_project_name = @project.name unless @project.nil?
|
||||
%>
|
||||
|
||||
<%= link_to_function("Add a next action »",
|
||||
"Element.toggle('todo_new_action');Form.focusFirstElement('todo-form-new-action');",
|
||||
{:title => "Add a next action", :accesskey => "n"}) %>
|
||||
|
||||
<div id="todo_new_action" class="context_new" style="display:block">
|
||||
|
||||
<!--[form:todo]-->
|
||||
<% form_remote_tag(
|
||||
:url => todos_path, :method => :post,
|
||||
:html=> { :id=>'todo-form-new-action', :name=>'todo', :class => 'inline-form' }) do -%>
|
||||
|
||||
<div id="status"><%= error_messages_for("item") %></div>
|
||||
|
||||
<label for="todo_description">Description</label>
|
||||
<%= text_field( "todo", "description", "size" => 25, "tabindex" => 1) %>
|
||||
|
||||
<label for="todo_notes">Notes</label>
|
||||
<%= text_area( "todo", "notes", "cols" => 25, "rows" => 6, "tabindex" => 2) %>
|
||||
|
||||
<label for="todo_project_name">Project</label>
|
||||
<input id="todo_project_name" name="project_name" autocomplete="off" tabindex="3" size="25" type="text" value="<%= @initial_project_name %>" />
|
||||
<div class="page_name_auto_complete" id="project_list" style="display:none"></div>
|
||||
|
||||
<script type="text/javascript">
|
||||
projectAutoCompleter = new Autocompleter.Local('todo_project_name', 'project_list', <%= project_names_for_autocomplete %>, {choices:100,autoSelect:false});
|
||||
function selectDefaultContext() {
|
||||
todoContextNameElement = $('todo_context_name');
|
||||
defaultContextName = todoContextNameElement.projectDefaultContextsMap[this.value];
|
||||
if (defaultContextName && !todoContextNameElement.editedByTracksUser) {
|
||||
todoContextNameElement.value = defaultContextName;
|
||||
}
|
||||
}
|
||||
Event.observe($('todo_project_name'), "focus", projectAutoCompleter.activate.bind(projectAutoCompleter));
|
||||
Event.observe($('todo_project_name'), "click", projectAutoCompleter.activate.bind(projectAutoCompleter));
|
||||
Event.observe($('todo_project_name'), "blur", selectDefaultContext.bind($('todo_project_name')));
|
||||
</script>
|
||||
|
||||
<label for="todo_context_name">Context</label>
|
||||
<input id="todo_context_name" name="context_name" autocomplete="off" tabindex="4" size="25" type="text" value="<%= @initial_context_name %>" />
|
||||
<div class="page_name_auto_complete" id="context_list" style="display:none"></div>
|
||||
|
||||
<script type="text/javascript">
|
||||
contextAutoCompleter = new Autocompleter.Local('todo_context_name', 'context_list', <%= context_names_for_autocomplete %>, {choices:100,autoSelect:false});
|
||||
$('todo_context_name').projectDefaultContextsMap = <%= @default_project_context_name_map %>;
|
||||
Event.observe($('todo_context_name'), "focus", function(){ $('todo_context_name').editedByTracksUser = true; });
|
||||
Event.observe($('todo_context_name'), "focus", contextAutoCompleter.activate.bind(contextAutoCompleter));
|
||||
Event.observe($('todo_context_name'), "click", contextAutoCompleter.activate.bind(contextAutoCompleter));
|
||||
</script>
|
||||
|
||||
<label for="tag_list">Tags (separate with commas)</label>
|
||||
<%= text_field_tag "tag_list", nil, :size => 40, :tabindex => 5 %>
|
||||
|
||||
<label for="todo_due">Due</label>
|
||||
<%= text_field("todo", "due", "size" => 10, "class" => "Date", "onfocus" => "Calendar.setup", "tabindex" => 6, "autocomplete" => "off") %>
|
||||
|
||||
<label for="todo_show_from">Show from</label>
|
||||
<%= text_field("todo", "show_from", "size" => 10, "class" => "Date", "onfocus" => "Calendar.setup", "tabindex" => 7, "autocomplete" => "off") %>
|
||||
|
||||
<%= source_view_tag( @source_view ) %>
|
||||
<div class="submit_box"><input type="submit" value="Add item" tabindex="8" /></div>
|
||||
<% end -%><!--[eoform:todo]-->
|
||||
|
||||
|
||||
<%= calendar_setup( "todo_due" ) %>
|
||||
<%= calendar_setup( "todo_show_from" ) %>
|
||||
</div><!-- [end:todo-new-action] -->
|
3
tracks/app/views/shared/_empty.rhtml
Normal file
3
tracks/app/views/shared/_empty.rhtml
Normal file
|
@ -0,0 +1,3 @@
|
|||
<div class="message">
|
||||
<p><%= message %></p>
|
||||
</div>
|
11
tracks/app/views/shared/_flash.rhtml
Normal file
11
tracks/app/views/shared/_flash.rhtml
Normal file
|
@ -0,0 +1,11 @@
|
|||
<div id="message_holder">
|
||||
<% if flash.empty? %>
|
||||
<h4 id="flash" class="alert" style="display:none"></h4>
|
||||
<% else %>
|
||||
<% flash.each do |key,value| -%>
|
||||
<h4 id="flash" class='alert <%= key %>'>
|
||||
<%= value %>
|
||||
</h4>
|
||||
<% end -%>
|
||||
<% end %>
|
||||
</div>
|
3
tracks/app/views/shared/_footer.rhtml
Normal file
3
tracks/app/views/shared/_footer.rhtml
Normal file
|
@ -0,0 +1,3 @@
|
|||
<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/forums/">Forum</a> | <a href="http://www.rousette.org.uk/projects/wiki/index">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>
|
1
tracks/app/views/sidebar/_context.rhtml
Normal file
1
tracks/app/views/sidebar/_context.rhtml
Normal file
|
@ -0,0 +1 @@
|
|||
<li><%= link_to_context( context ) + " (" + count_undone_todos_phrase(context,"actions") + ")"%></li>
|
8
tracks/app/views/sidebar/_context_list.rhtml
Normal file
8
tracks/app/views/sidebar/_context_list.rhtml
Normal file
|
@ -0,0 +1,8 @@
|
|||
<h3><%= list_name %> (<%= contexts.length %>)</h3>
|
||||
<ul>
|
||||
<% if contexts.empty? -%>
|
||||
<li>None</li>
|
||||
<% else -%>
|
||||
<%= render :partial => "sidebar/context", :collection => contexts -%>
|
||||
<% end -%>
|
||||
</ul>
|
1
tracks/app/views/sidebar/_project.rhtml
Normal file
1
tracks/app/views/sidebar/_project.rhtml
Normal file
|
@ -0,0 +1 @@
|
|||
<li><%= link_to_project( project ) + " (" + count_undone_todos_phrase(project,"actions") + ")" %></li>
|
8
tracks/app/views/sidebar/_project_list.rhtml
Normal file
8
tracks/app/views/sidebar/_project_list.rhtml
Normal file
|
@ -0,0 +1,8 @@
|
|||
<h3><%= list_name %> (<%= projects.length %>)</h3>
|
||||
<ul>
|
||||
<% if projects.empty? %>
|
||||
<li>None</li>
|
||||
<% else %>
|
||||
<%= render :partial => "sidebar/project", :collection => projects %>
|
||||
<% end %>
|
||||
</ul>
|
29
tracks/app/views/sidebar/sidebar.rhtml
Normal file
29
tracks/app/views/sidebar/sidebar.rhtml
Normal file
|
@ -0,0 +1,29 @@
|
|||
<div id="sidebar">
|
||||
|
||||
<%= render :partial => "sidebar/project_list",
|
||||
:locals => { :list_name => 'Active Projects',
|
||||
:projects => @projects.select{|p| p.active? } } -%>
|
||||
|
||||
<% if @user.prefs.show_hidden_projects_in_sidebar -%>
|
||||
<%= render :partial => "sidebar/project_list",
|
||||
:locals => { :list_name => 'Hidden Projects',
|
||||
:projects => @projects.select{|p| p.hidden? } } -%>
|
||||
<% end -%>
|
||||
|
||||
<% if @user.prefs.show_completed_projects_in_sidebar -%>
|
||||
<%= render :partial => "sidebar/project_list",
|
||||
:locals => { :list_name => 'Completed Projects',
|
||||
:projects => @projects.select{|p| p.completed? } } -%>
|
||||
<% end -%>
|
||||
|
||||
<%= render :partial => "sidebar/context_list",
|
||||
:locals => { :list_name => 'Active Contexts',
|
||||
:contexts => @contexts.reject{|c| c.hide? } } -%>
|
||||
|
||||
<% if @user.prefs.show_hidden_contexts_in_sidebar -%>
|
||||
<%= render :partial => "sidebar/context_list",
|
||||
:locals => { :list_name => 'Hidden Contexts',
|
||||
:contexts => @contexts.select{|c| c.hide? } } -%>
|
||||
<% end -%>
|
||||
|
||||
</div>
|
17
tracks/app/views/todos/_completed.rhtml
Normal file
17
tracks/app/views/todos/_completed.rhtml
Normal file
|
@ -0,0 +1,17 @@
|
|||
<% suffix = append_descriptor ? append_descriptor : '' -%>
|
||||
<div class="container completed" id="completed_container">
|
||||
<h2>
|
||||
<% if collapsible %>
|
||||
<a href="#" class="container_toggle" id="toggle_completed"><%= image_tag("collapse.png") %></a>
|
||||
<% end %>
|
||||
Completed actions <%= append_descriptor ? append_descriptor : '' %>
|
||||
</h2>
|
||||
<div id="completed" class="items toggle_target">
|
||||
|
||||
<div id="empty-d" style="display:<%= @done.empty? ? 'block' : 'none' %>">
|
||||
<div class="message"><p>Currently there are no completed actions.</p></div>
|
||||
</div>
|
||||
|
||||
<%= render :partial => "todos/todo", :collection => done, :locals => { :parent_container_type => "completed" } %>
|
||||
</div>
|
||||
</div><!-- [end:next_actions] -->
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue