Merge branch 'master' of git://github.com/TracksApp/tracks into upstream

This commit is contained in:
Hans de Graaff 2012-05-28 09:27:43 +02:00
commit 5bef623ed7
623 changed files with 15313 additions and 10300 deletions

25
.gitignore.rails2 Normal file
View file

@ -0,0 +1,25 @@
*~
*.tmproj
.dotest
/.emacs-project
/.redcar
config/database.yml
config/site.yml
config/deploy.rb
db/*.sqlite3
db/data.yml
db/schema.rb
log
nbproject
public/javascripts/cache
public/stylesheets/cache
tmp
vendor/plugins/query_trace/
rerun.txt
public/javascripts/jquery-cached.js
public/javascripts/tracks-cached.js
public/stylesheets/tracks-cached.css
.idea
.rvmrc
.yardoc
tags

101
Gemfile
View file

@ -1,34 +1,55 @@
source :gemcutter
source "http://gems.github.com/"
source 'https://rubygems.org'
gem "rake", "~>0.8.7"
gem "rails", "~>2.3.12"
gem "highline", "~>1.5.0"
gem "RedCloth", "4.2.8"
gem "sanitize", "~>1.2.1"
gem "rack", "1.1.0"
gem "will_paginate", "~> 2.3.15"
gem "has_many_polymorphs", "~> 2.13"
gem "acts_as_list", "~>0.1.4"
gem "aasm", "~>2.2.0"
gem "rubyjedi-actionwebservice", :require => "actionwebservice"
gem "rubycas-client", "~>2.2.1"
gem "ruby-openid", :require => "openid"
gem 'rails', '3.2.3'
# Bundle edge Rails instead:
# gem 'rails', :git => 'git://github.com/rails/rails.git'
# you may comment out the database driver you will not be using.
# This will prevent a native build of the driver. Building native drivers is not always possible on all hosters
gem "sqlite3"
gem "mysql"
gem 'bcrypt-ruby', '~> 2.1.4'
gem 'htmlentities', '~> 4.3.0'
gem "mail"
gem "mysql2"
if RUBY_VERSION.to_f >= 1.9
gem "soap4r-ruby1.9"
else
gem "soap4r", "~>1.5.8"
# gem "highline", "~>1.5.0"
gem "RedCloth"
gem "formatize"
gem "sanitize"
gem "will_paginate"
gem "acts_as_list"
gem "aasm"
gem "htmlentities"
gem "swf_fu"
gem "rails_autolink"
# Gems used only for assets and not required
# in production environments by default.
group :assets do
gem 'sass-rails', '~> 3.2.3'
gem 'coffee-rails', '~> 3.2.1'
# See https://github.com/sstephenson/execjs#readme for more supported runtimes
# gem 'therubyracer', :platform => :ruby
gem 'uglifier', '>= 1.0.3'
end
gem 'jquery-rails'
# To use ActiveModel has_secure_password
gem 'bcrypt-ruby', '~> 3.0.0'
# To use Jbuilder templates for JSON
# gem 'jbuilder'
# Use unicorn as the app server
# gem 'unicorn'
# Deploy with Capistrano
# gem 'capistrano'
group :development do
if RUBY_VERSION.to_f >= 1.9
gem "ruby-debug19"
# gem "ruby-debug19", :require => 'ruby-debug'
gem "mongrel", "1.2.0.pre2"
else
gem "ruby-debug"
@ -38,16 +59,24 @@ group :development do
end
group :test do
gem "test-unit", "1.2.3"
gem "flexmock"
gem "ZenTest", ">=4.0.0"
gem "hpricot"
gem "hoe"
gem "rspec-rails", "~>1.3.3"
gem "thoughtbot-factory_girl"
gem 'memory_test_fix', '~>0.1.3'
gem "selenium-client"
gem "webrat", ">=0.7.0"
gem "database_cleaner", ">=0.5.0"
gem "cucumber-rails", "~>0.3.0"
# gem "test-unit", "1.2.3"
# gem "flexmock"
# gem "ZenTest", ">=4.0.0"
# gem "hpricot"
# gem "hoe"
# gem "rspec-rails", "~>1.3.3"
# gem 'memory_test_fix', '~>0.1.3'
gem "factory_girl_rails"
gem "capybara"
gem "selenium-webdriver" # Note that > 2.14 has problems: https://code.google.com/p/selenium/issues/detail?id=3075
gem "database_cleaner"
gem "cucumber-rails"
gem "aruba"
# uncomment to use the webkit option. This depends on Qt to be installed
#gem "capybara-webkit"
# uncomment to be able to make screenshots from scenarios
#gem "capybara-screenshot"
#gem "launchy"
end

View file

@ -1,150 +1,210 @@
GEM
remote: http://rubygems.org/
remote: http://gems.github.com/
remote: https://rubygems.org/
specs:
RedCloth (4.2.8)
ZenTest (4.6.1)
aasm (2.2.0)
actionmailer (2.3.14)
actionpack (= 2.3.14)
actionpack (2.3.14)
activesupport (= 2.3.14)
rack (~> 1.1.0)
activerecord (2.3.14)
activesupport (= 2.3.14)
activeresource (2.3.14)
activesupport (= 2.3.14)
activesupport (2.3.14)
acts_as_list (0.1.4)
bcrypt-ruby (2.1.4)
builder (3.0.0)
cgi_multipart_eof_fix (2.5.0)
columnize (0.3.4)
cucumber (1.0.2)
builder (>= 2.1.2)
diff-lcs (>= 1.1.2)
gherkin (~> 2.4.5)
json (>= 1.4.6)
term-ansicolor (>= 1.0.5)
cucumber-rails (0.3.2)
cucumber (>= 0.8.0)
daemons (1.1.4)
database_cleaner (0.6.7)
diff-lcs (1.1.2)
fastthread (1.0.7)
flexmock (0.9.0)
gem_plugin (0.2.3)
gherkin (2.4.11)
json (>= 1.4.6)
has_many_polymorphs (2.13)
RedCloth (4.2.9)
aasm (3.0.6)
activerecord
highline (1.5.2)
hoe (2.12.0)
rake (~> 0.8)
hpricot (0.8.4)
htmlentities (4.3.0)
httpclient (2.2.1)
actionmailer (3.2.3)
actionpack (= 3.2.3)
mail (~> 2.4.4)
actionpack (3.2.3)
activemodel (= 3.2.3)
activesupport (= 3.2.3)
builder (~> 3.0.0)
erubis (~> 2.7.0)
journey (~> 1.0.1)
rack (~> 1.4.0)
rack-cache (~> 1.2)
rack-test (~> 0.6.1)
sprockets (~> 2.1.2)
activemodel (3.2.3)
activesupport (= 3.2.3)
builder (~> 3.0.0)
activerecord (3.2.3)
activemodel (= 3.2.3)
activesupport (= 3.2.3)
arel (~> 3.0.2)
tzinfo (~> 0.3.29)
activeresource (3.2.3)
activemodel (= 3.2.3)
activesupport (= 3.2.3)
activesupport (3.2.3)
i18n (~> 0.6)
multi_json (~> 1.0)
acts_as_list (0.1.6)
addressable (2.2.8)
arel (3.0.2)
aruba (0.4.11)
childprocess (>= 0.2.3)
cucumber (>= 1.1.1)
ffi (>= 1.0.11)
rspec (>= 2.7.0)
bcrypt-ruby (3.0.1)
bluecloth (2.2.0)
builder (3.0.0)
capybara (1.1.2)
mime-types (>= 1.16)
nokogiri (>= 1.3.3)
rack (>= 1.0.0)
rack-test (>= 0.5.4)
selenium-webdriver (~> 2.0)
xpath (~> 0.1.4)
childprocess (0.3.2)
ffi (~> 1.0.6)
coffee-rails (3.2.2)
coffee-script (>= 2.2.0)
railties (~> 3.2.0)
coffee-script (2.2.0)
coffee-script-source
execjs
coffee-script-source (1.3.3)
cucumber (1.2.0)
builder (>= 2.1.2)
diff-lcs (>= 1.1.3)
gherkin (~> 2.10.0)
json (>= 1.4.6)
cucumber-rails (1.3.0)
capybara (>= 1.1.2)
cucumber (>= 1.1.8)
nokogiri (>= 1.5.0)
daemons (1.0.10)
database_cleaner (0.7.2)
diff-lcs (1.1.3)
erubis (2.7.0)
execjs (1.3.2)
multi_json (~> 1.0)
factory_girl (3.3.0)
activesupport (>= 3.0.0)
factory_girl_rails (3.3.0)
factory_girl (~> 3.3.0)
railties (>= 3.0.0)
ffi (1.0.11)
formatize (1.1.0)
RedCloth (~> 4.2)
actionpack (~> 3.0)
bluecloth (~> 2.2)
gem_plugin (0.2.3)
gherkin (2.10.0)
json (>= 1.4.6)
hike (1.2.1)
htmlentities (4.3.1)
i18n (0.6.0)
json (1.5.3)
linecache (0.46)
rbx-require-relative (> 0.0.4)
mail (2.3.0)
journey (1.0.3)
jquery-rails (2.0.2)
railties (>= 3.2.0, < 5.0)
thor (~> 0.14)
json (1.7.3)
libwebsocket (0.1.3)
addressable
mail (2.4.4)
i18n (>= 0.4.0)
mime-types (~> 1.16)
treetop (~> 1.4.8)
memory_test_fix (0.1.3)
mime-types (1.16)
mongrel (1.1.5)
cgi_multipart_eof_fix (>= 2.4)
daemons (>= 1.0.3)
fastthread (>= 1.0.1)
gem_plugin (>= 0.2.3)
mysql (2.8.1)
nokogiri (1.4.7)
polyglot (0.3.2)
rack (1.1.0)
mime-types (1.18)
mongrel (1.2.0.pre2)
daemons (~> 1.0.10)
gem_plugin (~> 0.2.3)
multi_json (1.3.5)
mysql2 (0.3.11)
nokogiri (1.5.2)
polyglot (0.3.3)
rack (1.4.1)
rack-cache (1.2)
rack (>= 0.4)
rack-ssl (1.3.2)
rack
rack-test (0.6.1)
rack (>= 1.0)
rails (2.3.14)
actionmailer (= 2.3.14)
actionpack (= 2.3.14)
activerecord (= 2.3.14)
activeresource (= 2.3.14)
activesupport (= 2.3.14)
rake (>= 0.8.3)
rake (0.8.7)
rbx-require-relative (0.0.5)
rspec (1.3.2)
rspec-rails (1.3.4)
rack (>= 1.0.0)
rspec (~> 1.3.1)
ruby-debug (0.10.4)
columnize (>= 0.1)
ruby-debug-base (~> 0.10.4.0)
ruby-debug-base (0.10.4)
linecache (>= 0.3)
ruby-openid (2.1.8)
rubycas-client (2.2.1)
activesupport
rubyjedi-actionwebservice (2.3.5.20100714122544)
actionpack (~> 2.3.0)
activerecord (~> 2.3.0)
activesupport (~> 2.3.0)
sanitize (1.2.1)
nokogiri (~> 1.4.1)
selenium-client (1.2.18)
soap4r (1.5.8)
httpclient (>= 2.1.1)
sqlite3 (1.3.4)
term-ansicolor (1.0.6)
test-unit (1.2.3)
hoe (>= 1.5.1)
thoughtbot-factory_girl (1.2.2)
rails (3.2.3)
actionmailer (= 3.2.3)
actionpack (= 3.2.3)
activerecord (= 3.2.3)
activeresource (= 3.2.3)
activesupport (= 3.2.3)
bundler (~> 1.0)
railties (= 3.2.3)
rails_autolink (1.0.7)
rails (~> 3.1)
railties (3.2.3)
actionpack (= 3.2.3)
activesupport (= 3.2.3)
rack-ssl (~> 1.3.2)
rake (>= 0.8.7)
rdoc (~> 3.4)
thor (~> 0.14.6)
rake (0.9.2.2)
rdoc (3.12)
json (~> 1.4)
rspec (2.10.0)
rspec-core (~> 2.10.0)
rspec-expectations (~> 2.10.0)
rspec-mocks (~> 2.10.0)
rspec-core (2.10.0)
rspec-expectations (2.10.0)
diff-lcs (~> 1.1.3)
rspec-mocks (2.10.1)
rubyzip (0.9.8)
sanitize (2.0.3)
nokogiri (>= 1.4.4, < 1.6)
sass (3.1.18)
sass-rails (3.2.5)
railties (~> 3.2.0)
sass (>= 3.1.10)
tilt (~> 1.3)
selenium-webdriver (2.21.2)
childprocess (>= 0.2.5)
ffi (~> 1.0)
libwebsocket (~> 0.1.3)
multi_json (~> 1.0)
rubyzip
sprockets (2.1.3)
hike (~> 1.2)
rack (~> 1.0)
tilt (~> 1.1, != 1.3.0)
sqlite3 (1.3.6)
swf_fu (2.0.2)
coffee-script
rails (>= 3.1)
thor (0.14.6)
tilt (1.3.3)
treetop (1.4.10)
polyglot
polyglot (>= 0.3.1)
webrat (0.7.3)
nokogiri (>= 1.2.0)
rack (>= 1.0)
rack-test (>= 0.5.3)
will_paginate (2.3.16)
yard (0.7.3)
tzinfo (0.3.33)
uglifier (1.2.4)
execjs (>= 0.3.0)
multi_json (>= 1.0.2)
will_paginate (3.0.3)
xpath (0.1.4)
nokogiri (~> 1.3)
yard (0.8.1)
PLATFORMS
ruby
DEPENDENCIES
RedCloth (= 4.2.8)
ZenTest (>= 4.0.0)
aasm (~> 2.2.0)
acts_as_list (~> 0.1.4)
bcrypt-ruby (~> 2.1.4)
cucumber-rails (~> 0.3.0)
database_cleaner (>= 0.5.0)
flexmock
has_many_polymorphs (~> 2.13)
highline (~> 1.5.0)
hoe
hpricot
htmlentities (~> 4.3.0)
mail
memory_test_fix (~> 0.1.3)
mongrel
mysql
rack (= 1.1.0)
rails (~> 2.3.12)
rake (~> 0.8.7)
rspec-rails (~> 1.3.3)
ruby-debug
ruby-openid
rubycas-client (~> 2.2.1)
rubyjedi-actionwebservice
sanitize (~> 1.2.1)
selenium-client
soap4r (~> 1.5.8)
RedCloth
aasm
acts_as_list
aruba
bcrypt-ruby (~> 3.0.0)
capybara
coffee-rails (~> 3.2.1)
cucumber-rails
database_cleaner
factory_girl_rails
formatize
htmlentities
jquery-rails
mongrel (= 1.2.0.pre2)
mysql2
rails (= 3.2.3)
rails_autolink
sanitize
sass-rails (~> 3.2.3)
selenium-webdriver
sqlite3
test-unit (= 1.2.3)
thoughtbot-factory_girl
webrat (>= 0.7.0)
will_paginate (~> 2.3.15)
swf_fu
uglifier (>= 1.0.3)
will_paginate
yard

26
README
View file

@ -1,28 +1,21 @@
# Tracks: a GTD(TM) web application, built with Ruby on Rails
**IMPORTANT: Tracks is moving to a GitHub Organization to make it easier to continue administering the project. Development will soon cease at bsag/tracks and move to TracksApp/tracks. If you are currently pulling from bsag/tracks, please pull from TracksApp instead.**
`git clone git://github.com/TracksApp/tracks.git`
**The new home for Tracks is https://github.com/TracksApp/tracks**
* Project homepage: http://getontracks.org/
* Manual: http://TracksApp.github.com/tracks/
* Manual: http://getontracks.org/manual/
* Source at GitHub: http://github.com/TracksApp/tracks/
* Assembla space (for bug reports and feature requests): http://www.assembla.com/spaces/tracks-tickets/tickets
* Wiki (community contributed information): https://github.com/TracksApp/tracks/wiki
* Forum: http://getontracks.org/forums/
* Forum (read-only): http://getontracks.org/forums/
* Mailing list: http://lists.rousette.org.uk/mailman/listinfo/tracks-discuss
* Original developer: bsag (http://www.rousette.org.uk/)
* Contributors: https://github.com/TracksApp/tracks/wiki/Contributors
* Version: 2.1devel
* Copyright: (cc) 2004-2011 rousette.org.uk.
* Version: 2.2devel
* Copyright: (cc) 2004-2012 rousette.org.uk.
* License: GNU GPL
All the documentation for Tracks can be found within the /doc directory and at
http://bsag.github.com/tracks/
More documentation for Tracks can be found within the /doc directory
The latter includes full instructions for both new installations and upgrades
The manual includes full instructions for both new installations and upgrades
from older installations of Tracks.
The instructions might appear long and intimidatingly complex, but that is
@ -37,12 +30,11 @@ you cannot find a solution to your problem.
The wiki has a lot of user contributed installation HOWTOs for various webhosts, specific OS's and more.
If you are thinking about contributing towards the development of Tracks,
please read /doc/README_DEVELOPERS for general information, or
/doc/tracks_api_wrapper.rb for information on Tracks' API. Also you can find
some information on development on the wiki.
please read /doc/README_DEVELOPERS for general information. Also you can find
some information on development, testing and contributing on the wiki.
While fully usable for everyday use, Tracks is still a work in progress. Make
sure that you take sensible precautions and back up all your data frequently,
taking particular care when you are upgrading.
Enjoy being productive!
Enjoy being productive!

View file

@ -1,16 +1,7 @@
#!/usr/bin/env rake
# 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 File.expand_path('../config/application', __FILE__)
require 'rake'
require 'rake/testtask'
require 'rake/rdoctask'
require 'tasks/rails'
begin
require 'test/rails/rake_tasks'
rescue LoadError => e
#It's ok if you don't have ZenTest installed if you're not a developer
end
Tracksapp::Application.load_tasks

View file

Before

Width:  |  Height:  |  Size: 781 B

After

Width:  |  Height:  |  Size: 781 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 596 B

After

Width:  |  Height:  |  Size: 596 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2 KiB

After

Width:  |  Height:  |  Size: 2 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 244 B

After

Width:  |  Height:  |  Size: 244 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 156 B

After

Width:  |  Height:  |  Size: 156 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 587 B

After

Width:  |  Height:  |  Size: 587 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 109 B

After

Width:  |  Height:  |  Size: 109 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 138 B

After

Width:  |  Height:  |  Size: 138 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 3 KiB

After

Width:  |  Height:  |  Size: 3 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 496 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 425 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 501 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 441 B

View file

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 905 B

After

Width:  |  Height:  |  Size: 905 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 216 B

After

Width:  |  Height:  |  Size: 216 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 764 B

After

Width:  |  Height:  |  Size: 764 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 192 B

After

Width:  |  Height:  |  Size: 192 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 618 B

After

Width:  |  Height:  |  Size: 618 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 69 B

After

Width:  |  Height:  |  Size: 69 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 337 B

After

Width:  |  Height:  |  Size: 337 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 271 B

After

Width:  |  Height:  |  Size: 271 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 161 B

After

Width:  |  Height:  |  Size: 161 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 237 B

After

Width:  |  Height:  |  Size: 237 B

Before After
Before After

BIN
app/assets/images/rails.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

View file

Before

Width:  |  Height:  |  Size: 410 B

After

Width:  |  Height:  |  Size: 410 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 598 B

After

Width:  |  Height:  |  Size: 598 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 475 B

After

Width:  |  Height:  |  Size: 475 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 814 B

After

Width:  |  Height:  |  Size: 814 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 642 B

After

Width:  |  Height:  |  Size: 642 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 84 B

After

Width:  |  Height:  |  Size: 84 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 935 B

After

Width:  |  Height:  |  Size: 935 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 328 B

After

Width:  |  Height:  |  Size: 328 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 515 B

After

Width:  |  Height:  |  Size: 515 B

Before After
Before After

View file

@ -0,0 +1,30 @@
// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// the compiled file.
//
// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
// GO AFTER THE REQUIRES BELOW.
//
//= require jquery
//= require jquery_ujs
// Stuff in app/assets
//= require tracks.js
// Stuff in vendor/assets
//= require jquery-ui-1.8.17.custom.min
//= require jquery.blockUI
//= require jquery.cookie
//= require jquery.form
//= require jquery.jeditable.mini
//= require jquery.simulate.drag-sortable
//= require jquery.truncator
//= require niftycube
//= require superfish
//= require supersubs
//= require swfobject

View file

@ -434,7 +434,7 @@ var TodoItems = {
return confirm(i18n['contexts.new_context_pre'] + givenContextName + i18n['contexts.new_context_post']);
},
generate_predecessor: function(todo_id, todo_spec) {
var img = "<img id=\"delete_dep_"+todo_id+"\" class=\"icon_delete_dep\" src=\""+ relative_to_root('images/blank.png') + "\">";
var img = "<img id=\"delete_dep_"+todo_id+"\" class=\"icon_delete_dep\" src=\""+ relative_to_root('assets/blank.png') + "\">";
var anchor = "<a class=\"icon_delete_dep\" id=\""+todo_id+"\" href=\"#\">" + img + "</a>";
var li = "<li style=\"display:none\" id=\"pred_"+todo_id+"\">"+ anchor +" "+ todo_spec + "</li>";
return li;
@ -534,12 +534,12 @@ var TodoItems = {
});
$('.item-show').droppable({
drop: TodoItems.drop_todo,
tolerance: 'pointer',
tolerance: 'intersect', /* warning: selenium fails on drag_and_drop when this is 'pointer' */
hoverClass: 'hover'
});
$('.context_target').droppable({
drop: TodoItems.drop_todo_on_context,
tolerance: 'pointer',
tolerance: 'intersect', /* warning: selenium fails on drag_and_drop when this is 'pointer' */
hoverClass: 'hover'
});
},
@ -610,7 +610,7 @@ var TodoItems = {
return false;
});
/* delete button to delete a project from the list */
/* delete button to delete a dependency from the list */
$('.item-container a.delete_dependency_button').live('click', function(evt){
var predecessor_id=$(this).attr("x_predecessors_id");
var ajax_options = default_ajax_options_for_scripts('DELETE', this.href, $(this).parents('.item-container'));
@ -726,9 +726,10 @@ var ProjectListPage = {
var highlight = function(){
$('h2#project_name').effect('highlight', {}, 500);
};
$.post(relative_to_root('projects/update/'+project_id), {
$.post(relative_to_root('projects/'+project_id), {
'project[name]': value,
'update_project_name': 'true'
'update_project_name': 'true',
'_method': 'put'
}, highlight, 'script');
return(value);
},
@ -770,8 +771,9 @@ var ProjectListPage = {
/* set behavior for edit project settings link in both projects list page and project page */
$("a.project_edit_settings").live('click', function (evt) {
get_with_ajax_and_block_element(this.href, $(this).parent().parent());
return false;
$(this).parent().parent().addClass('project-edit-current'); /* mark project in list */
get_with_ajax_and_block_element(this.href, $(this).parent().parent());
return false;
});
/* submit project form after edit */
@ -785,6 +787,7 @@ var ProjectListPage = {
$('form.edit-project-form a.negative').live('click', function(){
$('div#project_name').editable('enable');
$(this).parents('.edit-form').fadeOut(200, function () {
$(this).parents('.project-edit-current').removeClass('project-edit-current');
$(this).parents('.list').find('.project').fadeIn(500);
$(this).parents('.container').find('.item-show').fadeIn(500);
})
@ -806,7 +809,7 @@ var ProjectListPage = {
/* make the three lists of project sortable */
$(['active', 'hidden', 'completed']).each(function() {
$("#list-"+this+"-projects").sortable({
handle: '.handle',
handle: '.grip',
update: update_order
});
});
@ -841,8 +844,9 @@ var ContextListPage = {
var highlight = function(){
$('div.context span#context_name').effect('highlight', {}, 500);
};
$.post(relative_to_root('contexts/update/'+context_id), {
'context[name]': value
$.post(relative_to_root('contexts/'+context_id), {
'context[name]': value,
'_method': 'put'
}, highlight);
return value;
},
@ -891,7 +895,7 @@ var ContextListPage = {
/* make the two state lists of context sortable */
$(['active', 'hidden']).each(function() {
$("#list-contexts-"+this).sortable({
handle: '.handle',
handle: '.grip',
update: update_order
})
});
@ -1014,8 +1018,14 @@ var RecurringTodosPage = {
width: 750,
modal: true,
buttons: {
"Create": function() { submit_with_ajax_and_block_element('form.#recurring-todo-form-new-action', $(this).parents(".ui-dialog")); },
Cancel: function() { $( this ).dialog( "close" ); }
create: {
text: i18n['common.create'],
click: function() { submit_with_ajax_and_block_element('form.#recurring-todo-form-new-action', $(this).parents(".ui-dialog")); },
},
cancel: {
text: i18n['common.cancel'],
click: function() { $( this ).dialog( "close" ); }
}
},
show: "fade",
hide: "fade",
@ -1041,11 +1051,14 @@ var RecurringTodosPage = {
modal: true,
buttons: {
"Update": {
text: "Update",
text: i18n['common.update'],
id: 'recurring_todo_edit_update_button',
click: function() { submit_with_ajax_and_block_element('form#recurring-todo-form-edit-action', $(this).parents(".ui-dialog")); }
},
Cancel: function() { $( this ).dialog( "close" ); }
cancel: {
text: i18n['common.cancel'],
click: function() { $( this ).dialog( "close" ); }
}
},
show: "fade",
hide: "fade",
@ -1147,7 +1160,7 @@ $.fn.clearDeps = function() {
/**************************************/
function generic_get_script_for_list(element, getter, param){
$(element).load(relative_to_root(getter+'?'+param));
$(element).load(relative_to_root(getter+'.js?'+param));
}
function default_ajax_options_for_submit(ajax_type, element_to_block) {

View file

@ -0,0 +1,14 @@
/*
* This is a manifest file that'll be compiled into application.css, which will include all the files
* listed below.
*
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
* or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
*
* You're free to add application-wide styles to this file and they'll appear at the top of the
* compiled file, but it's generally better to create a new file per style scope.
*
*= require_self
*= require tracks
*= require_tree ../../../vendor/assets/stylesheets
*/

View file

@ -1,22 +1,20 @@
body {
font-family: Arial,Helvetica,sans-serif;
font-size: smaller;
}
#content {
margin-top: 50px;
margin-top: 4em;
}
div.footer {
font-size: XX-small;
font-size: small;
color: #999999;
text-align: center;
}
}
a, a:link, a:active, a:visited {
color: #CC3334;
padding-left: 1px;
padding-right: 1px;
padding: 0.25em;
text-decoration: none;
}
@ -25,27 +23,27 @@ a:hover {
color: #FFFFFF;
}
div.footer a {
div.footer a {
text-decoration: underline;
color: #999999;
}
h1 {
color: #fff;
font-size: small;
padding-top: 0.2em;
padding-bottom: 0.2em;
padding-left:8px;
margin-top:0;
margin-bottom:0;
font-size:medium;
}
h2 {
background-color: #aaaaaa;
font-size : small;
margin: .3em 0;
padding: .3em 0 .1em .3em;
border-top: 1px solid #777777;
font-size:medium;
}
h2 a, h2 a:link, h2 a:active, h2 a:visited {
@ -60,27 +58,27 @@ h2 a:hover {
}
h4.alert {
border: 1px solid #666666;
border: 1px solid #666666;
text-align: center;
}
h4.warning {
border: 1px solid #ED2E38;
border: 1px solid #ED2E38;
background-color: #F6979C;
color: #000;
}
h4.error {
color:#fff;
color:#fff;
background:#c00;
}
h4.notice {
border: 1px solid #007E00;
border: 1px solid #007E00;
background-color: #c2ffc2;
color: #007E00;
}
span.tag {
font-size: x-small;
font-size: small;
background-color: #CCE7FF;
color: #000;
padding: 1px;
@ -88,12 +86,12 @@ span.tag {
}
span.r {
font-size: XX-small;
font-size: small;
color: #777777;
}
span.prj, span.ctx{
font-size: X-small;
font-size: small;
}
#ctx, #pjr {
@ -104,7 +102,7 @@ span.prj, span.ctx{
padding: 0.1em 0;
}
/* Draw attention to some text
/* Draw attention to some text
Same format as traffic lights */
.red {
color: #fff;
@ -162,7 +160,7 @@ ul.c {
}
ul.c li {
padding: 0;
padding: 0.25em 0;
margin: 0;
}
@ -178,9 +176,7 @@ span.r {
background-color: #000000;
clear: both;
color: #EEEEEE;
height: 45px;
left: 0;
margin-bottom: 5px;
position: fixed;
top: 0;
width: 100%;
@ -190,19 +186,16 @@ span.r {
.nav {
color: #fff;
background: #000;
padding-top: 0.2em;
padding-bottom: 0.2em;
}
#topbar .nav {
padding-left:8px;
margin-bottom:0.3em;
padding:0;
overflow:auto;
list-style:none;
margin:0;
}
.nav a, .nav a:link, .nav a:active, .nav a:visited {
background: #666;
color: #fff;
padding-top: 1.0em;
padding-bottom: 0.5em;
padding: 0.5em;
}
.nav a:focus, .nav a:hover, .nav a:active {
@ -214,6 +207,17 @@ span.r {
color: #CCCCCC;
}
.nav li.link {
width:20%;
float:left;
outline:black solid 1px;
}
.nav .link a {
font-size:small;
text-align:center;
display:block;
}
#database_auth_form table td {
width:7em;
}
@ -221,10 +225,54 @@ span.r {
table.c {
margin-left: 5px;
}
.mobile-done {
display:inline;
}
input#todo_description, input#tag_list, textarea#todo_notes, select#todo_project_id, select#todo_context_id {
input#todo_description, input#todo_tag_list, textarea#todo_notes, select#todo_project_id, select#todo_context_id {
width: 95%;
}
.next-prev-project {
overflow:auto;
padding:0;
margin:0;
list-style:none;
}
.prev,
.next {
float:left;
width:50%;
}
.next {
float:right;
}
.prev a,
.next a {
display:block;
height:1em;
overflow:hidden;
}
.prev a {
background: url(assets/previous.png) left center no-repeat;
padding-left: 20px;
}
.prev a:hover {
background: #cc3334 url(assets/previous.png) left center no-repeat;
}
.next a {
text-align:right;
background: url(assets/next.png) right center no-repeat;
padding-right: 20px;
}
.next a:hover {
background: #cc3334 url(assets/next.png) right center no-repeat;
}

View file

@ -71,23 +71,23 @@ h4.alert {
}
h4.warning {
border: 1px solid #ED2E38;
border: 1px solid #ED2E38;
background-color: #F6979C;
color: #000000;
}
h4.error {
color:#fff;
color:#fff;
background:#c00;
}
h4.notice {
border: 1px solid #007E00;
border: 1px solid #007E00;
background-color: #c2ffc2;
color: #007E00;
}
/*#notice {
padding: 2px;
border: 1px solid #007E00;
border: 1px solid #007E00;
background-color: #c2ffc2;
color: #007E00;
margin-bottom: 15px;
@ -147,8 +147,8 @@ input.login_text {
width:200px;
}
input.open_id {
background: url(../images/open-id-login-bg.gif) no-repeat;
background-color: #fff;
background: url(/assets/open-id-login-bg.gif) no-repeat;
background-color: #fff;
background-position: 0 50%;
color: #000;
padding-left: 18px;

View file

@ -6,6 +6,10 @@
float:right;
}
div.depends_on {
clear:both;
}
div.depends_on label {
float: left;
}
@ -102,35 +106,35 @@ h3 {
/* Rules for the icon links */
img.edit_item {background-image: url(../images/edit_off.png); background-repeat: no-repeat; border: none;}
a:hover img.edit_item {background-image: url(../images/edit_on.png); background-color: transparent; background-repeat: no-repeat; border: none;}
img.edit_item {background-image: url(/assets/edit_off.png); background-repeat: no-repeat; border: none;}
a:hover img.edit_item {background-image: url(/assets/edit_on.png); background-color: transparent; background-repeat: no-repeat; border: none;}
img.delete_item {background-image: url(../images/delete_off.png); background-repeat: no-repeat; border: none;}
a:hover img.delete_item {background-image: url(../images/delete_on.png);background-color: transparent;background-repeat: no-repeat; border: none;}
img.delete_item {background-image: url(/assets/delete_off.png); background-repeat: no-repeat; border: none;}
a:hover img.delete_item {background-image: url(/assets/delete_on.png);background-color: transparent;background-repeat: no-repeat; border: none;}
a.undecorated_link {background-color:transparent;color:transparent;}
img.todo_star {background-image: url(../images/staricons.png); background-repeat: no-repeat; border:none; background-position: -32px 0px;}
img.todo_star {background-image: url(/assets/staricons.png); background-repeat: no-repeat; border:none; background-position: -32px 0px;}
img.todo_star.starred{ background-position: 0px 0px; }
a:hover img.todo_star { background-position: -48px 0px;}
a:hover img.todo_star.starred { background-position: -16px 0px; }
a.to_top {background: transparent url(../images/top_off.png) no-repeat;}
a.to_top:hover {background: transparent url(../images/top_on.png) no-repeat;}
a.to_top {background: transparent url(/assets/top_off.png) no-repeat;}
a.to_top:hover {background: transparent url(/assets/top_on.png) no-repeat;}
a.up {background: transparent url(../images/up_off.png) no-repeat;}
a.up:hover {background: transparent url(../images/up_on.png) no-repeat;}
a.up {background: transparent url(/assets/up_off.png) no-repeat;}
a.up:hover {background: transparent url(/assets/up_on.png) no-repeat;}
a.down {background: transparent url(../images/down_off.png) no-repeat;}
a.down:hover {background: transparent url(../images/down_on.png) no-repeat;}
a.down {background: transparent url(/assets/down_off.png) no-repeat;}
a.down:hover {background: transparent url(/assets/down_on.png) no-repeat;}
a.to_bottom {background: transparent url(../images/bottom_off.png) no-repeat;}
a.to_bottom:hover {background: transparent url(../images/bottom_on.png) no-repeat;}
a.to_bottom {background: transparent url(/assets/bottom_off.png) no-repeat;}
a.to_bottom:hover {background: transparent url(/assets/bottom_on.png) no-repeat;}
a.show_notes, a.link_to_notes {background-image: url(../images/notes_off.png); background-repeat: no-repeat; padding: 1px; background-color: transparent;}
a.show_notes:hover, a.link_to_notes:hover {background-image: url(../images/notes_on.png); background-repeat: no-repeat; padding: 1px; background-color: transparent;}
a.show_notes, a.link_to_notes {background-image: url(/assets/notes_off.png); background-repeat: no-repeat; padding: 1px; background-color: transparent;}
a.show_notes:hover, a.link_to_notes:hover {background-image: url(/assets/notes_on.png); background-repeat: no-repeat; padding: 1px; background-color: transparent;}
a.show_successors, a.link_to_successors {background-image: url(../images/successor_off.png); background-repeat: no-repeat; padding: 1px; background-color: transparent;}
a.show_successors:hover, a.link_to_successors:hover {background-image: url(../images/successor_on.png); background-repeat: no-repeat; padding: 1px; background-color: transparent;}
a.show_successors, a.link_to_successors {background-image: url(/assets/successor_off.png); background-repeat: no-repeat; padding: 1px; background-color: transparent;}
a.show_successors:hover, a.link_to_successors:hover {background-image: url(/assets/successor_on.png); background-repeat: no-repeat; padding: 1px; background-color: transparent;}
/* Structural divs */
@ -251,7 +255,7 @@ a.show_successors:hover, a.link_to_successors:hover {background-image: url(../im
#develop-notify-bar {
line-height:0.5;
background-image: url(/images/construction.gif);
background-image: url(/assets/construction.gif);
background-repeat: repeat-x;
}
@ -361,7 +365,7 @@ div#input_box {
}
.selected_predecessors {
clear:right;
clear:both;
}
form.new_todo_form ul.predecessor_list,
@ -380,8 +384,8 @@ ul.predecessor_list li {
}
/* deleting dependency from new form of a todo */
img.icon_delete_dep {width: 10px; background-image: url(../images/icon_delete.png); background-repeat: no-repeat; background-position: -9px 0; border: none; color:black;}
a:hover img.icon_delete_dep {width: 10px; background-image: url(../images/icon_delete.png); background-repeat: no-repeat; background-position: 0 0; border: none; color:black; background-color: black;}
img.icon_delete_dep {width: 10px; background-image: url(/assets/icon_delete.png); background-repeat: no-repeat; background-position: -9px 0; border: none; color:black;}
a:hover img.icon_delete_dep {width: 10px; background-image: url(/assets/icon_delete.png); background-repeat: no-repeat; background-position: 0 0; border: none; color:black; background-color: black;}
a.icon_delete_dep:hover {width: 10px; background-color: black;}
/* deleting dependency from edit form of a todo */
@ -426,13 +430,13 @@ input.item-checkbox {
vertical-align: middle;
}
.rec_description, .description {
.rec_description, .description, .project_description, .context_description {
margin-left: 80px;
position:relative;
}
.rec_description {
margin-left: 80px;
.project_description, .context_description {
margin-left: 50px;
}
.stale_l1 {
@ -546,6 +550,10 @@ div.note_wrapper p {
display: inline;
}
div.note_note {
margin-left:24px;
}
div.note_footer {
border-top: 1px solid #999;
padding-top: 3px;
@ -857,6 +865,7 @@ form {
padding: 10px;
margin: 0px;
}
.inline-form {
border: none;
padding: 3px;
@ -1152,7 +1161,7 @@ ul#prefs {list-style-type: disc; margin-left: 15px;}
}
input.open_id {
background: url(../images/open-id-login-bg.gif) no-repeat;
background: url(/assets/open-id-login-bg.gif) no-repeat;
background-color: #fff;
background-position: 0 50%;
color: #000;
@ -1304,7 +1313,7 @@ button.positive, .widgets a.positive{
}
.blockUI.blockOverlay {
background-image:url('../images/waiting.gif');
background-image:url('/assets/waiting.gif');
background-repeat:no-repeat;
background-position:center center;
background-color:white;
@ -1312,21 +1321,21 @@ button.positive, .widgets a.positive{
}
.bigWaiting {
background-image:url('../images/bigWaiting.gif');
background-image:url('/assets/bigWaiting.gif');
background-repeat:no-repeat;
background-position:center 20%;
background-color:white;
}
.blackWaiting {
background-image:url('../images/blackWaiting.gif');
background-image:url('/assets/blackWaiting.gif');
background-repeat:no-repeat;
background-position:center center;
background-color:black;
}
.bigBlackWaiting {
background-image:url('../images/bigBlackWaiting.gif');
background-image:url('/assets/bigBlackWaiting.gif');
background-repeat:no-repeat;
background-position:center center;
background-color:black;
@ -1415,9 +1424,9 @@ div.auto_complete ul strong.highlight {
}
.ui-datepicker {
z-index: 1000;
z-index: 1000 !important;
}
.ui-autocomplete-loading {
background: white url('/images/ui-anim_basic_16x16.gif') right center no-repeat;
background: white url('/assets/ui-anim_basic_16x16.gif') right center no-repeat;
}

View file

@ -9,12 +9,11 @@ class ApplicationController < ActionController::Base
protect_from_forgery
helper :application
include LoginSystem
helper_method :current_user, :prefs, :format_date, :markdown
layout proc{ |controller| controller.mobile? ? "mobile" : "standard" }
exempt_from_layout /\.js\.erb$/
# exempt_from_layout /\.js\.erb$/
before_filter :check_for_deprecated_password_hash
before_filter :set_session_expiration
@ -73,32 +72,19 @@ class ApplicationController < ActionController::Base
render :text => message, :status => status
end
# def rescue_action(exception)
# log_error(exception) if logger
# respond_to do |format|
# format.html do
# notify :warning, "An error occurred on the server."
# render :action => "index"
# end
# format.js { render :action => 'error' }
# format.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")
def count_undone_todos_phrase(todos_parent)
count = count_undone_todos(todos_parent)
deferred_count = count_deferred_todos(todos_parent)
if count == 0 && deferred_count > 0
word = deferred_count == 1 ? string.singularize : string.pluralize
word = "deferred&nbsp;" + word
deferred_count.to_s + "&nbsp;" + word
word = "#{I18n.t('common.deferred')}&nbsp;#{I18n.t('common.actions_midsentence', :count => deferred_count)}"
return "#{deferred_count.to_s}&nbsp;#{word}".html_safe
else
word = count == 1 ? string.singularize : string.pluralize
count.to_s + "&nbsp;" + word
word = I18n.t('common.actions_midsentence', :count => count)
return "#{count}&nbsp;#{word}".html_safe
end
end
@ -143,14 +129,6 @@ class ApplicationController < ActionController::Base
return json_elems
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
# Here's the concept behind this "mobile content negotiation" hack: In
# addition to the main, AJAXy Web UI, Tracks has a lightweight low-feature
# 'mobile' version designed to be suitablef or use from a phone or PDA. It
@ -211,6 +189,10 @@ class ApplicationController < ActionController::Base
super # handle xml http auth via our own login code
end
end
def sanitize(arg)
ActionController::Base.helpers.sanitize(arg)
end
protected
@ -223,7 +205,7 @@ class ApplicationController < ActionController::Base
def redirect_back_or_home
respond_to do |format|
format.html { redirect_back_or_default home_url }
format.html { redirect_back_or_default root_url }
format.m { redirect_back_or_default mobile_url }
end
end
@ -260,24 +242,26 @@ class ApplicationController < ActionController::Base
self.class.prefered_auth?
end
# all completed todos [today@00:00, today@now]
def get_done_today(completed_todos, includes = {:include => Todo::DEFAULT_INCLUDES})
start_of_this_day = Time.zone.now.beginning_of_day
completed_todos.completed_after(start_of_this_day).all(includes)
end
# all completed todos [begin_of_week, start_of_today]
def get_done_this_week(completed_todos, includes = {:include => Todo::DEFAULT_INCLUDES})
start_of_this_week = Time.zone.now.beginning_of_week
start_of_this_day = Time.zone.now.beginning_of_day
completed_todos.completed_after(start_of_this_week).completed_before(start_of_this_day).all(includes)
completed_todos.completed_before(start_of_this_day).completed_after(start_of_this_week).all(includes)
end
# all completed todos [begin_of_month, begin_of_week]
def get_done_this_month(completed_todos, includes = {:include => Todo::DEFAULT_INCLUDES})
start_of_this_month = Time.zone.now.beginning_of_month
start_of_this_week = Time.zone.now.beginning_of_week
completed_todos.completed_after(start_of_this_month).completed_before(start_of_this_week).all(includes)
completed_todos.completed_before(start_of_this_week).completed_after(start_of_this_month).all(includes)
end
private
def parse_date_per_user_prefs( s )

View file

@ -0,0 +1,324 @@
# 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 "tracks/source_view"
class ApplicationController < ActionController::Base
protect_from_forgery
helper :application
include LoginSystem
helper_method :current_user, :prefs, :format_date
layout proc{ |controller| controller.mobile? ? "mobile" : "standard" }
exempt_from_layout /\.js\.erb$/
before_filter :check_for_deprecated_password_hash
before_filter :set_session_expiration
before_filter :set_time_zone
before_filter :set_zindex_counter
before_filter :set_locale
prepend_before_filter :login_required
prepend_before_filter :enable_mobile_content_negotiation
after_filter :set_charset
# 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_locale
locale = params[:locale] # specifying a locale in the request takes precedence
locale = locale || prefs.locale unless current_user.nil? # otherwise, the locale of the currently logged in user takes over
locale = locale || request.env['HTTP_ACCEPT_LANGUAGE'].scan(/^[a-z]{2}/).first if request.env['HTTP_ACCEPT_LANGUAGE']
I18n.locale = locale.nil? ? I18n.default_locale : (I18n::available_locales.include?(locale.to_sym) ? locale : I18n.default_locale)
logger.debug("Selected '#{I18n.locale}' as locale")
end
def set_session_expiration
# http://wiki.rubyonrails.com/rails/show/HowtoChangeSessionOptions
unless session == nil
return if self.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
# Redirects to change_password_user_path if the current user uses a
# deprecated password hashing algorithm.
def check_for_deprecated_password_hash
if current_user and current_user.uses_deprecated_password?
notify :warning, t('users.you_have_to_reset_your_password')
redirect_to change_password_user_path current_user
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 |format|
# format.html do
# notify :warning, "An error occurred on the server."
# render :action => "index"
# end
# format.js { render :action => 'error' }
# format.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)
count = count_undone_todos(todos_parent)
deferred_count = count_deferred_todos(todos_parent)
if count == 0 && deferred_count > 0
word = I18n.t('common.actions_midsentence', :count => deferred_count)
word = I18n.t('common.deferred') + "&nbsp;" + word
return deferred_count.to_s + "&nbsp;" + word
else
word = I18n.t('common.actions_midsentence', :count => count)
return count.to_s + "&nbsp;" + word
end
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
def count_deferred_todos(todos_parent)
if todos_parent.nil?
count = 0
else
count = todos_parent.todos.deferred.count
end
end
# Convert a date object to the format specified in the user's preferences in
# config/settings.yml
#
def format_date(date)
return date ? date.in_time_zone(prefs.time_zone).strftime("#{prefs.date_format}") : ''
end
def for_autocomplete(coll, substr)
if substr # protect agains empty request
filtered = coll.find_all{|item| item.name.downcase.include? substr.downcase}
json_elems = Array[*filtered.map{ |e| {:id => e.id.to_s, :value => e.name} }].to_json
return json_elems
else
return ""
end
end
def format_dependencies_as_json_for_auto_complete(entries)
json_elems = Array[*entries.map{ |e| {:value => e.id.to_s, :label => e.specification} }].to_json
return json_elems
end
# Here's the concept behind this "mobile content negotiation" hack: In
# addition to the main, AJAXy Web UI, Tracks has a lightweight low-feature
# 'mobile' version designed to be suitablef or use from a phone or PDA. It
# makes some sense that tne pages of that mobile version are simply alternate
# representations of the same Todo resources. The implementation goal was to
# treat mobile as another format and be able to use respond_to to render both
# versions. Unfortunately, I ran into a lot of trouble simply registering a
# new mime type 'text/html' with format :m because :html already is linked to
# that mime type and the new registration was forcing all html requests to be
# rendered in the mobile view. The before_filter and after_filter hackery
# below accomplishs that implementation goal by using a 'fake' mime type
# during the processing and then setting it to 'text/html' in an
# 'after_filter' -LKM 2007-04-01
def mobile?
return params[:format] == 'm'
end
def enable_mobile_content_negotiation
if mobile?
request.format = :m
end
end
def create_todo_from_recurring_todo(rt, date=nil)
# create todo and initialize with data from recurring_todo rt
todo = current_user.todos.build( { :description => rt.description, :notes => rt.notes, :project_id => rt.project_id, :context_id => rt.context_id})
todo.recurring_todo_id = rt.id
# set dates
todo.due = rt.get_due_date(date)
show_from_date = rt.get_show_from_date(date)
if show_from_date.nil?
todo.show_from=nil
else
# make sure that show_from is not in the past
todo.show_from = show_from_date < Time.zone.now ? nil : show_from_date
end
saved = todo.save
if saved
todo.tag_with(rt.tag_list)
todo.tags.reload
end
# increate number of occurences created from recurring todo
rt.inc_occurences
# mark recurring todo complete if there are no next actions left
checkdate = todo.due.nil? ? todo.show_from : todo.due
rt.toggle_completion! unless rt.has_next_todo(checkdate)
return saved ? todo : nil
end
def handle_unverified_request
unless request.format=="application/xml"
super # handle xml http auth via our own login code
end
end
protected
def admin_login_required
unless User.find_by_id_and_is_admin(session['user_id'], true)
render :text => t('errors.user_unauthorized'), :status => 401
return false
end
end
def redirect_back_or_home
respond_to do |format|
format.html { redirect_back_or_default home_url }
format.m { redirect_back_or_default mobile_url }
end
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
def self.openid_enabled?
Tracks::Config.openid_enabled?
end
def openid_enabled?
self.class.openid_enabled?
end
def self.cas_enabled?
Tracks::Config.cas_enabled?
end
def cas_enabled?
self.class.cas_enabled?
end
def self.prefered_auth?
Tracks::Config.prefered_auth?
end
def prefered_auth?
self.class.prefered_auth?
end
# all completed todos [today@00:00, today@now]
def get_done_today(completed_todos, includes = {:include => Todo::DEFAULT_INCLUDES})
start_of_this_day = Time.zone.now.beginning_of_day
completed_todos.completed_after(start_of_this_day).all(includes)
end
# all completed todos [begin_of_week, start_of_today]
def get_done_this_week(completed_todos, includes = {:include => Todo::DEFAULT_INCLUDES})
start_of_this_week = Time.zone.now.beginning_of_week
start_of_this_day = Time.zone.now.beginning_of_day
completed_todos.completed_before(start_of_this_day).completed_after(start_of_this_week).all(includes)
end
# all completed todos [begin_of_month, begin_of_week]
def get_done_this_month(completed_todos, includes = {:include => Todo::DEFAULT_INCLUDES})
start_of_this_month = Time.zone.now.beginning_of_month
start_of_this_week = Time.zone.now.beginning_of_week
completed_todos.completed_before(start_of_this_week).completed_after(start_of_this_month).all(includes)
end
private
def parse_date_per_user_prefs( s )
prefs.parse_date(s)
end
def init_data_for_sidebar
@completed_projects = current_user.projects.completed
@hidden_projects = current_user.projects.hidden
@active_projects = current_user.projects.active
@active_contexts = current_user.contexts.active
@hidden_contexts = current_user.contexts.hidden
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 = @#{parent}_not_done_counts || current_user.todos.active.count(:group => :#{parent}_id)")
end
end
def init_project_hidden_todo_counts(parents = ['project','context'])
parents.each do |parent|
eval("@#{parent}_project_hidden_todo_counts = @#{parent}_project_hidden_todo_counts || current_user.todos.count(:conditions => ['state = ? or state = ?', 'project_hidden', 'active'], :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
def set_time_zone
Time.zone = current_user.prefs.time_zone if logged_in?
end
def set_zindex_counter
# this counter can be used to handle the IE z-index bug
@z_index_counter = 500
end
end

View file

@ -9,32 +9,36 @@ class ContextsController < ApplicationController
prepend_before_filter :login_or_feed_token_required, :only => [:index]
def index
# #true is passed here to force an immediate load so that size and empty?
# checks later don't result in separate SQL queries
@active_contexts = current_user.contexts.active(true)
@hidden_contexts = current_user.contexts.hidden(true)
@active_contexts = current_user.contexts.active
@hidden_contexts = current_user.contexts.hidden
@new_context = current_user.contexts.build
init_not_done_counts(['context'])
# save all contexts here as @new_context will add an empty one to current_user.contexts
@all_contexts = @active_contexts + @hidden_contexts
@count = @all_contexts.size
init_not_done_counts(['context'])
respond_to do |format|
format.html &render_contexts_html
format.m &render_contexts_mobile
format.xml { render :xml => @all_contexts.to_xml( :except => :user_id ) }
format.rss &render_contexts_rss_feed
format.atom &render_contexts_atom_feed
format.rss do
@feed_title = 'Tracks Contexts'
@feed_description = "Lists all the contexts for #{current_user.display_name}"
end
format.atom do
@feed_title = 'Tracks Contexts'
@feed_description = "Lists all the contexts for #{current_user.display_name}"
end
format.text do
@all_contexts = current_user.contexts.all
# somehow passing Mime::TEXT using content_type to render does not work
headers['Content-Type']=Mime::TEXT.to_s
render :action => 'index', :layout => false, :content_type => Mime::TEXT
end
format.autocomplete { render :text => for_autocomplete(@active_contexts + @hidden_contexts, params[:term])}
format.autocomplete &render_autocomplete
end
end
def show
@contexts = current_user.contexts(true)
if @context.nil?
@ -51,24 +55,13 @@ class ContextsController < ApplicationController
end
end
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>.", 400
render_failure "Expected post format is valid xml like so: <context><name>context name</name></context>.", 400
return
end
@context = current_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
@context = current_user.contexts.build(params['context'])
@saved = @context.save
@context_not_done_counts = { @context.id => 0 }
respond_to do |format|
@ -76,10 +69,8 @@ class ContextsController < ApplicationController
@down_count = current_user.contexts.size
end
format.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>.", 400
elsif @context.new_record?
render_failure @context.errors.to_xml, 409
if @context.new_record?
render_failure @context.errors.to_xml.html_safe, 409
else
head :created, :location => context_url(@context)
end
@ -218,8 +209,8 @@ class ContextsController < ApplicationController
@active_contexts = current_user.contexts.active
@hidden_contexts = current_user.contexts.hidden
@down_count = @active_contexts.size + @hidden_contexts.size
cookies[:mobile_url]= {:value => request.request_uri, :secure => SITE_CONFIG['secure_cookies']}
render :action => 'index_mobile'
cookies[:mobile_url]= {:value => request.fullpath, :secure => SITE_CONFIG['secure_cookies']}
render
end
end
@ -228,24 +219,18 @@ class ContextsController < ApplicationController
@page_title = "TRACKS::List actions in "+@context.name
@not_done = @not_done_todos.select {|t| t.context_id == @context.id }
@down_count = @not_done.size
cookies[:mobile_url]= {:value => request.request_uri, :secure => SITE_CONFIG['secure_cookies']}
cookies[:mobile_url]= {:value => request.fullpath, :secure => SITE_CONFIG['secure_cookies']}
@mobile_from_context = @context.id
render :action => 'mobile_show_context'
render
end
end
def render_contexts_rss_feed
def render_autocomplete
lambda do
render_rss_feed_for current_user.contexts.all, :feed => feed_options,
:item => { :description => lambda { |c| @template.summary(c, count_undone_todos_phrase(c)) } }
end
end
def render_contexts_atom_feed
lambda do
render_atom_feed_for current_user.contexts.all, :feed => feed_options,
:item => { :description => lambda { |c| @template.summary(c, count_undone_todos_phrase(c)) },
:author => lambda { |c| nil } }
# first get active contexts with todos then those without
filled_contexts = @active_contexts.reject { |ctx| ctx.todos.count == 0 } + @hidden_contexts.reject { |ctx| ctx.todos.count == 0 }
empty_contexts = @active_contexts.find_all { |ctx| ctx.todos.count == 0 } + @hidden_contexts.find_all { |ctx| ctx.todos.count == 0 }
render :text => for_autocomplete(filled_contexts + empty_contexts, params[:term])
end
end
@ -254,7 +239,7 @@ class ContextsController < ApplicationController
end
def set_context_from_params
@context = current_user.contexts.find_by_params(params)
@context = current_user.contexts.find(params[:id])
rescue
@context = nil
end
@ -267,11 +252,8 @@ class ContextsController < ApplicationController
def init_todos
set_context_from_params
unless @context.nil?
@context.todos.send :with_scope, :find => { :include => Todo::DEFAULT_INCLUDES } do
@done = @context.done_todos
end
@max_completed = current_user.prefs.show_number_completed
@done = @context.todos.completed.all(:limit => @max_completed)
# @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
@ -281,9 +263,12 @@ class ContextsController < ApplicationController
:order => "todos.due IS NULL, todos.due ASC, todos.created_at ASC",
:include => Todo::DEFAULT_INCLUDES)
@deferred = @context.todos.deferred(:include => Todo::DEFAULT_INCLUDES)
@pending = @context.todos.pending(:include => Todo::DEFAULT_INCLUDES)
@projects = current_user.projects
@count = @not_done_todos.size
@count = @not_done_todos.count + @deferred.count + @pending.count
end
end

View file

@ -18,9 +18,9 @@ class DataController < ApplicationController
def yaml_export
all_tables = {}
all_tables['todos'] = current_user.todos.find(:all, :include => [:tags])
all_tables['contexts'] = current_user.contexts.find(:all)
all_tables['projects'] = current_user.projects.find(:all)
all_tables['todos'] = current_user.todos.includes(:tags).all
all_tables['contexts'] = current_user.contexts.all
all_tables['projects'] = current_user.projects.all
todo_tag_ids = Tag.find_by_sql([
"SELECT DISTINCT tags.id "+
@ -34,13 +34,13 @@ class DataController < ApplicationController
"WHERE recurring_todos.user_id=? "+
"AND tags.id = taggings.tag_id " +
"AND taggings.taggable_id = recurring_todos.id ", current_user.id])
tags = Tag.find(:all, :conditions => ["id IN (?) OR id IN (?)", todo_tag_ids, rec_todo_tag_ids])
taggings = Tagging.find(:all, :conditions => ["tag_id IN (?) OR tag_id IN(?)", todo_tag_ids, rec_todo_tag_ids])
tags = Tag.where("id IN (?) OR id IN (?)", todo_tag_ids, rec_todo_tag_ids)
taggings = Tagging.where("tag_id IN (?) OR tag_id IN(?)", todo_tag_ids, rec_todo_tag_ids)
all_tables['tags'] = tags
all_tables['taggings'] = taggings
all_tables['notes'] = current_user.notes.find(:all)
all_tables['recurring_todos'] = current_user.recurring_todos.find(:all)
all_tables['tags'] = tags.all
all_tables['taggings'] = taggings.all
all_tables['notes'] = current_user.notes.all
all_tables['recurring_todos'] = current_user.recurring_todos.all
result = all_tables.to_yaml
result.gsub!(/\n/, "\r\n") # TODO: general functionality for line endings
@ -53,7 +53,7 @@ class DataController < ApplicationController
csv << ["id", "Context", "Project", "Description", "Notes", "Tags",
"Created at", "Due", "Completed at", "User ID", "Show from",
"state"]
current_user.todos.find(:all, :include => [:context, :project]).each do |todo|
current_user.todos.include(:context, :project).all.each do |todo|
csv << [todo.id, todo.context.name,
todo.project_id.nil? ? "" : todo.project.name,
todo.description,
@ -72,19 +72,19 @@ class DataController < ApplicationController
def csv_notes
content_type = 'text/csv'
CSV::Writer.generate(result = "") do |csv|
CSV.generate(result = "") do |csv|
csv << ["id", "User ID", "Project", "Note",
"Created at", "Updated at"]
# had to remove project include because it's association order is leaking
# through and causing an ambiguous column ref even with_exclusive_scope
# didn't seem to help -JamesKebinger
current_user.notes.find(:all,:order=>"notes.created_at").each do |note|
current_user.notes.reorder("notes.created_at").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,
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)]
note.updated_at.to_formatted_s(:db)]
end
end
send_data(result, :filename => "notes.csv", :type => content_type)
@ -103,17 +103,17 @@ class DataController < ApplicationController
"WHERE recurring_todos.user_id=? "+
"AND tags.id = taggings.tag_id " +
"AND taggings.taggable_id = recurring_todos.id ", current_user.id])
tags = Tag.find(:all, :conditions => ["id IN (?) OR id IN (?)", todo_tag_ids, rec_todo_tag_ids])
taggings = Tagging.find(:all, :conditions => ["tag_id IN (?) OR tag_id IN(?)", todo_tag_ids, rec_todo_tag_ids])
tags = Tag.where("id IN (?) OR id IN (?)", todo_tag_ids, rec_todo_tag_ids)
taggings = Tagging.where("tag_id IN (?) OR tag_id IN(?)", todo_tag_ids, rec_todo_tag_ids)
result = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?><tracks_data>"
result << current_user.todos.find(:all).to_xml(:skip_instruct => true)
result << current_user.contexts.find(:all).to_xml(:skip_instruct => true)
result << current_user.projects.find(:all).to_xml(:skip_instruct => true)
result << current_user.todos.to_xml(:skip_instruct => true)
result << current_user.contexts.to_xml(:skip_instruct => true)
result << current_user.projects.to_xml(:skip_instruct => true)
result << tags.to_xml(:skip_instruct => true)
result << taggings.to_xml(:skip_instruct => true)
result << current_user.notes.find(:all).to_xml(:skip_instruct => true)
result << current_user.recurring_todos.find(:all).to_xml(:skip_instruct => true)
result << current_user.notes.to_xml(:skip_instruct => true)
result << current_user.recurring_todos.to_xml(:skip_instruct => true)
result << "</tracks_data>"
send_data(result, :filename => "tracks_data.xml", :type => 'text/xml')
end
@ -126,7 +126,7 @@ class DataController < ApplicationController
def adjust_time(timestring)
if (timestring=='') or ( timestring == nil)
return nil
else
else
return Time.parse(timestring + 'UTC')
end
end
@ -186,7 +186,7 @@ class DataController < ApplicationController
case item.ivars['attributes']['state']
when 'active' then newitem.activate!
when 'project_hidden' then newitem.hide!
when 'completed'
when 'completed'
newitem.complete!
newitem.completed_at = adjust_time(item.ivars['attributes']['completed_at'])
when 'deferred' then newitem.defer!

View file

@ -6,7 +6,7 @@ class FeedlistController < ApplicationController
@page_title = 'TRACKS::Feeds'
unless mobile?
init_data_for_sidebar
init_data_for_sidebar
else
@projects = current_user.projects
@contexts = current_user.contexts
@ -21,7 +21,7 @@ class FeedlistController < ApplicationController
respond_to do |format|
format.html { render :layout => 'standard' }
format.m { render :action => 'mobile_index' }
format.m
end
end

View file

@ -27,7 +27,8 @@ class IntegrationsController < ApplicationController
end
def search_plugin
@icon_data = [File.open(RAILS_ROOT + '/public/images/done.png').read].
# TODO: ASSET PATH!!
@icon_data = [File.open(Rails.root + '/app/assets/images/done.png').read].
pack('m').gsub(/\n/, '')
render :layout => false
@ -52,7 +53,7 @@ class IntegrationsController < ApplicationController
message = Mail.new(params[:message])
# find user
user = User.find(:first, :include => [:preference], :conditions => ["preferences.sms_email = ?", message.from])
user = User.where("preferences.sms_email = ?", message.from).includes(:preference).first
if user.nil?
render :text => "No user found", :status => 404
return false

View file

@ -1,7 +1,6 @@
class LoginController < ApplicationController
layout 'login'
filter_parameter_logging :user_password
skip_before_filter :set_session_expiration
skip_before_filter :login_required
before_filter :login_optional
@ -9,62 +8,38 @@ class LoginController < ApplicationController
protect_from_forgery :except => [:check_expiry, :login]
if ( SITE_CONFIG['authentication_schemes'].include? 'cas')
# This will allow the user to view the index page without authentication
# but will process CAS authentication data if the user already
# has an SSO session open.
if defined? CASClient
# Only require sub-library if gem is installed and loaded
require 'casclient/frameworks/rails/filter'
before_filter CASClient::Frameworks::Rails::GatewayFilter, :only => :login_cas
# This requires the user to be authenticated for viewing all other pages.
before_filter CASClient::Frameworks::Rails::Filter, :only => [:login_cas ]
end
end
def login
if cas_enabled?
@username = session[:cas_user]
@login_url = CASClient::Frameworks::Rails::Filter.login_url(self)
@page_title = "TRACKS::Login"
cookies[:preferred_auth] = prefered_auth? unless cookies[:preferred_auth]
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 and we remember this user for future browser sessions
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, :secure => SITE_CONFIG['secure_cookies'] }
unless should_expire_sessions?
@user.remember_me
cookies[:auth_token] = { :value => @user.remember_token , :expires => @user.remember_token_expires_at, :secure => SITE_CONFIG['secure_cookies'] }
end
redirect_back_or_home
return
else
@login = params['user_login']
notify :warning, t('login.unsuccessful')
end
when 'GET'
if User.no_users_yet?
redirect_to signup_path
return
end
end
if openid_enabled? && using_open_id?
login_openid
elsif cas_enabled? && session[:cas_user]
login_cas
else
@page_title = "TRACKS::Login"
cookies[:preferred_auth] = prefered_auth? unless cookies[:preferred_auth]
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 and we remember this user for future browser sessions
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, :secure => SITE_CONFIG['secure_cookies'] }
unless should_expire_sessions?
@user.remember_me
cookies[:auth_token] = { :value => @user.remember_token , :expires => @user.remember_token_expires_at, :secure => SITE_CONFIG['secure_cookies'] }
end
redirect_back_or_home
return
else
@login = params['user_login']
notify :warning, t('login.unsuccessful')
end
when :get
if User.no_users_yet?
redirect_to signup_path
return
end
end
respond_to do |format|
format.html
format.m { render :action => 'login_mobile.html.erb', :layout => 'mobile' }
end
respond_to do |format|
format.html
format.m { render :action => 'login', :layout => 'mobile' }
end
end
@ -111,32 +86,6 @@ class LoginController < ApplicationController
format.js
end
end
def login_cas
# If checkbox on login page checked, we don't expire the session after 1 hour
# of inactivity and we remember this user for future browser sessions
session['noexpiry'] ||= params['user_noexpiry']
if session[:cas_user]
if @user = User.find_by_login(session[:cas_user])
session['user_id'] = @user.id
msg = (should_expire_sessions?) ? t('login.session_will_expire', :hours => 1) : t('login.session_will_not_expire')
notify :notice, (t('login.successful_with_session_info') + msg)
cookies[:tracks_login] = { :value => @user.login, :expires => Time.now + 1.year, :secure => SITE_CONFIG['secure_cookies'] }
unless should_expire_sessions?
@user.remember_me
cookies[:auth_token] = { :value => @user.remember_token, :expires => @user.remember_token_expires_at, :secure => SITE_CONFIG['secure_cookies'] }
end
else
notify :warning, t('login.cas_username_not_found', :username => session[:cas_user])
redirect_to signup_url ; return
end
else
notify :warning, result.message
end
redirect_back_or_home
end
private
@ -144,32 +93,4 @@ class LoginController < ApplicationController
session['noexpiry'] != "on"
end
protected
def login_openid
# If checkbox on login page checked, we don't expire the session after 1 hour
# of inactivity and we remember this user for future browser sessions
session['noexpiry'] ||= params['user_noexpiry']
authenticate_with_open_id do |result, identity_url|
if result.successful?
if @user = User.find_by_open_id_url(identity_url)
session['user_id'] = @user.id
msg = (should_expire_sessions?) ? t('login.session_will_expire', :hours => 1) : t('login.session_will_not_expire')
notify :notice, (t('login.successful_with_session_info') + msg)
cookies[:tracks_login] = { :value => @user.login, :expires => Time.now + 1.year, :secure => SITE_CONFIG['secure_cookies'] }
unless should_expire_sessions?
@user.remember_me
cookies[:auth_token] = { :value => @user.remember_token , :expires => @user.remember_token_expires_at, :secure => SITE_CONFIG['secure_cookies'] }
end
redirect_back_or_home
else
notify :warning, t('login.openid_identity_url_not_found', :identity_url => identity_url)
end
else
notify :warning, result.message
end
end
end
end

View file

@ -18,7 +18,7 @@ class NotesController < ApplicationController
@page_title = "TRACKS::Note " + @note.id.to_s
respond_to do |format|
format.html
format.m { render :action => 'note_mobile' }
format.m
end
end
@ -29,7 +29,7 @@ class NotesController < ApplicationController
@saved = @note.save
respond_to do |format|
format.js
format.js
format.xml do
if @saved
head :created, :location => note_url(@note), :text => "new note with id #{@note.id}"

View file

@ -9,11 +9,10 @@ class ProjectsController < ApplicationController
def index
@source_view = params['_source_view'] || 'project_list'
@new_project = current_user.projects.build
if params[:projects_and_actions]
projects_and_actions
else
@contexts = current_user.contexts.all
@contexts = current_user.contexts
init_not_done_counts(['project'])
init_project_hidden_todo_counts(['project'])
if params[:only_active_with_no_next_actions]
@ -21,19 +20,47 @@ class ProjectsController < ApplicationController
else
@projects = current_user.projects.all
end
@new_project = current_user.projects.build
@active_projects = current_user.projects.active
@hidden_projects = current_user.projects.hidden
respond_to do |format|
format.html &render_projects_html
format.m &render_projects_mobile
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
format.autocomplete { render :text => for_autocomplete(current_user.projects.uncompleted, params[:term]) }
format.html do
@page_title = t('projects.list_projects')
@count = current_user.projects.count
@completed_projects = current_user.projects.completed.limit(10)
@completed_count = current_user.projects.completed.count
@no_projects = current_user.projects.empty?
current_user.projects.cache_note_counts
@new_project = current_user.projects.build
end
format.m do
@completed_projects = current_user.projects.completed
@down_count = @active_projects.size + @hidden_projects.size + @completed_projects.size
cookies[:mobile_url]= {:value => request.fullpath, :secure => SITE_CONFIG['secure_cookies']}
end
format.xml { render :xml => @projects.all.to_xml( :except => :user_id ) }
format.rss do
@feed_title = I18n.t('models.project.feed_title')
@feed_description = I18n.t('models.project.feed_description', :username => current_user.display_name)
end
format.atom do
@feed_title = I18n.t('models.project.feed_title')
@feed_description = I18n.t('models.project.feed_description', :username => current_user.display_name)
end
format.text do
# somehow passing Mime::TEXT using content_type to render does not work
headers['Content-Type']=Mime::TEXT.to_s
end
format.autocomplete do
projects = current_user.projects.active + current_user.projects.hidden
render :text => for_autocomplete(projects, params[:term])
end
end
end
end
def review
@source_view = params['_source_view'] || 'review'
@page_title = t('projects.list_reviews')
@projects = current_user.projects.all
@contexts = current_user.contexts.all
@ -73,13 +100,25 @@ class ProjectsController < ApplicationController
def set_reviewed
@project.last_reviewed = Time.zone.now
@project.save
redirect_to :action => 'show'
case @source_view
when "project"
redirect_to :action => 'show'
when "project_list"
redirect_to :action => 'index'
when "review"
redirect_to :action => 'review'
else
redirect_to :action => 'index'
end
end
def projects_and_actions
@projects = current_user.projects.active
respond_to do |format|
format.text {
# somehow passing Mime::TEXT using content_type to render does not work
headers['Content-Type']=Mime::TEXT.to_s
render :action => 'index_text_projects_and_actions', :layout => false, :content_type => Mime::TEXT
}
end
@ -90,11 +129,15 @@ class ProjectsController < ApplicationController
init_data_for_sidebar unless mobile?
@page_title = t('projects.page_title', :project => @project.name)
@not_done = @project.todos.active_or_hidden(:include => Todo::DEFAULT_INCLUDES)
@deferred = @project.todos.deferred(:include => Todo::DEFAULT_INCLUDES)
@pending = @project.todos.pending(:include => Todo::DEFAULT_INCLUDES)
@done = @project.todos.find_in_state(:all, :completed,
:order => "todos.completed_at DESC", :limit => current_user.prefs.show_number_completed, :include => Todo::DEFAULT_INCLUDES)
@not_done = @project.todos.active_or_hidden.includes(Todo::DEFAULT_INCLUDES)
@deferred = @project.todos.deferred.includes(Todo::DEFAULT_INCLUDES)
@pending = @project.todos.pending.includes(Todo::DEFAULT_INCLUDES)
@done = {}
@done = @project.todos.completed.
reorder("todos.completed_at DESC").
limit(current_user.prefs.show_number_completed).
includes(Todo::DEFAULT_INCLUDES) unless current_user.prefs.show_number_completed == 0
@count = @not_done.size
@down_count = @count + @deferred.size + @pending.size
@ -106,39 +149,34 @@ class ProjectsController < ApplicationController
@contexts = current_user.contexts
respond_to do |format|
format.html
format.m &render_project_mobile
format.xml {
format.m do
if @project.default_context.nil?
@project_default_context = t('projects.no_default_context')
else
@project_default_context = t('projects.default_context', :context => @project.default_context.name)
end
cookies[:mobile_url]= {:value => request.fullpath, :secure => SITE_CONFIG['secure_cookies']}
@mobile_from_project = @project.id
end
format.xml do
render :xml => @project.to_xml(:except => :user_id) { |xml|
xml.not_done { @not_done.each { |child| child.to_xml(:builder => xml, :skip_instruct => true) } }
xml.deferred { @deferred.each { |child| child.to_xml(:builder => xml, :skip_instruct => true) } }
xml.pending { @pending.each { |child| child.to_xml(:builder => xml, :skip_instruct => true) } }
xml.done { @done.each { |child| child.to_xml(:builder => xml, :skip_instruct => true) } }
}
}
end
end
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>."
render_failure "Expected post format is valid xml like so: <project><name>project name</name></project>.", 400
return
end
@project = current_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
@project = current_user.projects.build(params['project'])
@go_to_project = params['go_to_project']
@saved = @project.save
@project_not_done_counts = { @project.id => 0 }
@active_projects_count = current_user.projects.active.count
@contexts = current_user.contexts
@ -146,10 +184,8 @@ class ProjectsController < ApplicationController
respond_to do |format|
format.js { @down_count = current_user.projects.size }
format.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(', ')
if @project.new_record?
render_failure @project.errors.to_xml.html_safe, 409
else
head :created, :location => project_url(@project), :text => @project.id
end
@ -179,38 +215,30 @@ class ProjectsController < ApplicationController
if @saved
@project.transition_to(@new_state) if @state_changed
if boolean_param('wants_render')
if (@project.hidden?)
@project_project_hidden_todo_counts = Hash.new
@project_project_hidden_todo_counts[@project.id] = @project.reload().todos.active_or_hidden.count
else
@project_not_done_counts = Hash.new
@project_not_done_counts[@project.id] = @project.reload().todos.active_or_hidden.count
end
@contexts = current_user.contexts
update_state_counts
init_data_for_sidebar
template = 'projects/update.js.erb'
# TODO: are these params ever set? or is this dead code?
template = 'projects/update'
# TODO: are these params ever set? or is this dead code?
elsif boolean_param('update_status')
template = 'projects/update_status.js.rjs'
template = 'projects/update_status'
elsif boolean_param('update_default_context')
@initial_context_name = @project.default_context.name
template = 'projects/update_default_context.js.rjs'
template = 'projects/update_default_context'
elsif boolean_param('update_default_tags')
template = 'projects/update_default_tags.js.rjs'
template = 'projects/update_default_tags'
elsif boolean_param('update_project_name')
@projects = current_user.projects
template = 'projects/update_project_name.js.rjs'
template = 'projects/update_project_name'
else
render :text => success_text || 'Success'
return
end
else
init_data_for_sidebar
template = 'projects/update.js.erb'
template = 'projects/update'
end
respond_to do |format|
@ -304,73 +332,7 @@ class ProjectsController < ApplicationController
@show_hidden_projects = @hidden_projects_count > 0
@show_completed_projects = @completed_projects_count > 0
end
def render_projects_html
lambda do
@page_title = t('projects.list_projects')
@count = current_user.projects.count
@active_projects = current_user.projects.active
@hidden_projects = current_user.projects.hidden
@completed_projects = current_user.projects.completed.find(:all, :limit => 10)
@completed_count = current_user.projects.completed.count
@no_projects = current_user.projects.empty?
current_user.projects.cache_note_counts
@new_project = current_user.projects.build
render
end
end
def render_projects_mobile
lambda do
@active_projects = current_user.projects.active
@hidden_projects = current_user.projects.hidden
@completed_projects = current_user.projects.completed
@down_count = @active_projects.size + @hidden_projects.size + @completed_projects.size
cookies[:mobile_url]= {:value => request.request_uri, :secure => SITE_CONFIG['secure_cookies']}
render :action => 'index_mobile'
end
end
def render_project_mobile
lambda do
if @project.default_context.nil?
@project_default_context = t('projects.no_default_context')
else
@project_default_context = t('projects.default_context', :context => @project.default_context.name)
end
cookies[:mobile_url]= {:value => request.request_uri, :secure => SITE_CONFIG['secure_cookies']}
@mobile_from_project = @project.id
render :action => 'project_mobile'
end
end
def render_rss_feed
lambda do
render_rss_feed_for @projects, :feed => feed_options,
:title => :name,
:item => { :description => lambda { |p| @template.summary(p) } }
end
end
def render_atom_feed
lambda do
render_atom_feed_for @projects, :feed => feed_options,
:item => { :description => lambda { |p| @template.summary(p) },
:title => :name,
:author => lambda { |p| nil } }
end
end
def feed_options
Project.feed_options(current_user)
end
def render_text_feed
lambda do
render :action => 'index', :layout => false, :content_type => Mime::TEXT
end
end
def set_project_from_params
@project = current_user.projects.find_by_params(params)
end

View file

@ -9,12 +9,12 @@ class RecurringTodosController < ApplicationController
@page_title = t('todos.recurring_actions_title')
@source_view = params['_source_view'] || 'recurring_todo'
find_and_inactivate
@recurring_todos = current_user.recurring_todos.active.find(:all, :include => [:tags, :taggings])
@completed_recurring_todos = current_user.recurring_todos.completed.find(:all, :limit => 10, :include => [:tags, :taggings])
@recurring_todos = current_user.recurring_todos.active.includes(:tags, :taggings)
@completed_recurring_todos = current_user.recurring_todos.completed.limit(10).includes(:tags, :taggings)
@no_recurring_todos = @recurring_todos.size == 0
@no_completed_recurring_todos = @completed_recurring_todos.size == 0
@count = @recurring_todos.size
@no_recurring_todos = @recurring_todos.count == 0
@no_completed_recurring_todos = @completed_recurring_todos.count == 0
@count = @recurring_todos.count
@new_recurring_todo = RecurringTodo.new
end
@ -161,10 +161,10 @@ class RecurringTodosController < ApplicationController
format.html do
if @saved
notify :notice, t('todos.recurring_deleted_success'), 2.0
notify :notice, t('todos.recurring_deleted_success')
redirect_to :action => 'index'
else
notify :error, t('todos.error_deleting_recurring', :description => @recurring_todo.description), 2.0
notify :error, t('todos.error_deleting_recurring', :description => @recurring_todo.description)
redirect_to :action => 'index'
end
end
@ -271,8 +271,8 @@ class RecurringTodosController < ApplicationController
end
@xth_day = [[t('common.first'),1],[t('common.second'),2],[t('common.third'),3],[t('common.fourth'),4],[t('common.last'),5]]
@projects = current_user.projects.find(:all, :include => [:default_context])
@contexts = current_user.contexts.find(:all)
@projects = current_user.projects.includes(:default_context)
@contexts = current_user.contexts
end
def get_recurring_todo_from_param
@ -282,10 +282,11 @@ class RecurringTodosController < ApplicationController
def find_and_inactivate
# find active recurring todos without active todos and inactivate them
current_user.recurring_todos.active.all(
:select => "recurring_todos.id, recurring_todos.state",
:joins => "LEFT JOIN todos fai_todos ON (recurring_todos.id = fai_todos.recurring_todo_id) AND (NOT fai_todos.state='completed')",
:conditions => "fai_todos.id IS NULL").each { |rt| current_user.recurring_todos.find(rt.id).toggle_completion! }
current_user.recurring_todos.active.
select("recurring_todos.id, recurring_todos.state").
joins("LEFT JOIN todos fai_todos ON (recurring_todos.id = fai_todos.recurring_todo_id) AND (NOT fai_todos.state='completed')").
where("fai_todos.id IS NULL").
each { |rt| current_user.recurring_todos.find(rt.id).toggle_completion! }
end
end

View file

@ -1,25 +1,30 @@
class SearchController < ApplicationController
helper :todos, :application, :notes, :projects
helper :todos, :application, :notes, :projects, :contexts
def results
@source_view = params['_source_view'] || 'search'
@page_title = "TRACKS::Search Results for #{params[:search]}"
terms = '%' + params[:search] + '%'
terms = "%#{params[:search]}%"
@found_not_complete_todos = current_user.todos.find(:all,
:conditions => ["(todos.description LIKE ? OR todos.notes LIKE ?) AND todos.completed_at IS NULL", terms, terms],
:include => Todo::DEFAULT_INCLUDES,
:order => "todos.due IS NULL, todos.due ASC, todos.created_at ASC")
@found_complete_todos = current_user.todos.find(:all,
:conditions => ["(todos.description LIKE ? OR todos.notes LIKE ?) AND NOT (todos.completed_at IS NULL)", terms, terms],
:include => Todo::DEFAULT_INCLUDES,
:order => "todos.completed_at DESC")
@found_not_complete_todos = current_user.todos.
where("(todos.description LIKE ? OR todos.notes LIKE ?) AND todos.completed_at IS NULL", terms, terms).
includes(Todo::DEFAULT_INCLUDES).
reorder("todos.due IS NULL, todos.due ASC, todos.created_at ASC").
all
@found_complete_todos = current_user.todos.
where("(todos.description LIKE ? OR todos.notes LIKE ?) AND NOT (todos.completed_at IS NULL)", terms, terms).
includes(Todo::DEFAULT_INCLUDES).
reorder("todos.completed_at DESC").
all
@found_todos = @found_not_complete_todos + @found_complete_todos
@found_projects = current_user.projects.find(:all, :conditions => ["name LIKE ? OR description LIKE ?", terms, terms])
@found_notes = current_user.notes.find(:all, :conditions => ["body LIKE ?", terms])
@found_contexts = current_user.contexts.find(:all, :conditions => ["name LIKE ?", terms])
@found_projects = current_user.projects.where("name LIKE ? OR description LIKE ?", terms, terms).all
@found_notes = current_user.notes.where("body LIKE ?", terms).all
@found_contexts = current_user.contexts.where("name LIKE ?", terms).all
# TODO: limit search to tags on todos
@found_tags = Tagging.find_by_sql([
"SELECT DISTINCT tags.name as name "+

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,5 @@
class UsersController < ApplicationController
before_filter :admin_login_required, :only => [ :index, :show, :destroy ]
skip_before_filter :login_required, :only => [ :new, :create ]
skip_before_filter :check_for_deprecated_password_hash,
@ -17,7 +18,7 @@ class UsersController < ApplicationController
store_location
end
format.xml do
@users = User.find(:all, :order => 'login')
@users = User.order('login').all
render :xml => @users.to_xml(:except => [ :password ])
end
end
@ -25,7 +26,7 @@ class UsersController < ApplicationController
# GET /users/id GET /users/id.xml
def show
@user = User.find_by_id(params[:id])
@user = User.find(params[:id])
render :xml => @user.to_xml(:except => [ :password ])
end
@ -64,7 +65,7 @@ class UsersController < ApplicationController
# 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>."
render_failure "Expected post format is valid xml like so: <user><login>username</login><password>abc123</password></user>."
return
end
respond_to do |format|
@ -82,7 +83,7 @@ class UsersController < ApplicationController
user.auth_type == 'ldap' &&
!SimpleLdapAuthenticator.valid?(user.login, params['user']['password'])
notify :warning, "Incorrect password"
redirect_to :action => 'new'
redirect_to signup_path
return
end
@ -111,23 +112,21 @@ class UsersController < ApplicationController
return
end
format.xml do
unless User.find_by_id_and_is_admin(session['user_id'], true)
unless current_user && current_user.is_admin
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>."
render_failure "Expected post format is valid xml like so: <user><login>username</login><password>abc123</password></user>.", 400
return
end
user = User.new(params[:request])
if Tracks::Config.auth_schemes.include?('cas') && session[:cas_user]
user.auth_type = "cas" #if they area cas user
end
user.password_confirmation = params[:request][:password]
if user.save
user = User.new(params[:user])
user.password_confirmation = params[:user][:password]
saved = user.save
unless user.new_record?
render :text => t('users.user_created'), :status => 200
else
render_failure user.errors.to_xml
render_failure user.errors.to_xml, 409
end
return
end
@ -136,16 +135,16 @@ class UsersController < ApplicationController
# DELETE /users/id DELETE /users/id.xml
def destroy
@deleted_user = User.find_by_id(params[:id])
@deleted_user = User.find(params[:id])
@saved = @deleted_user.destroy
@total_users = User.find(:all).size
@total_users = User.all.size
respond_to do |format|
format.html do
if @saved
notify :notice, t('users.successfully_deleted_user', :username => @deleted_user.login), 2.0
notify :notice, t('users.successfully_deleted_user', :username => @deleted_user.login)
else
notify :error, t('users.failed_to_delete_user', :username => @deleted_user.login), 2.0
notify :error, t('users.failed_to_delete_user', :username => @deleted_user.login)
end
redirect_to users_url
end
@ -154,19 +153,18 @@ class UsersController < ApplicationController
end
end
def change_password
@page_title = t('users.change_password_title')
end
def update_password
# is used for focing password change after sha->bcrypt upgrade
@user.change_password(params[:user][:password], params[:user][:password_confirmation])
current_user.change_password(params[:user][:password], params[:user][:password_confirmation])
notify :notice, t('users.password_updated')
redirect_to preferences_path
rescue Exception => error
notify :error, error.message
redirect_to :action => 'change_password'
redirect_to change_password_user_path(current_user)
end
def change_auth_type
@ -174,40 +172,19 @@ class UsersController < ApplicationController
end
def update_auth_type
if (params[:open_id_complete] || (params[:user][:auth_type] == 'open_id')) && openid_enabled?
authenticate_with_open_id do |result, identity_url|
if result.successful?
# 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 = identity_url
if @user.save
notify :notice, t('users.openid_url_verified', :url => identity_url)
else
debugger
notify :warning, t('users.openid_ok_pref_failed', :url => identity_url)
end
redirect_to preferences_path
else
notify :warning, result.message
redirect_to :action => 'change_auth_type'
end
end
return
end
@user.auth_type = params[:user][:auth_type]
if @user.save
current_user.auth_type = params[:user][:auth_type]
if current_user.save
notify :notice, t('users.auth_type_updated')
redirect_to preferences_path
else
notify :warning, t('users.auth_type_update_error', :error_messages => @user.errors.full_messages.join(', '))
redirect_to :action => 'change_auth_type'
notify :warning, t('users.auth_type_update_error', :error_messages => current_user.errors.full_messages.join(', '))
redirect_to change_auth_type_user_path(current_user)
end
end
def refresh_token
@user.generate_token
@user.save!
current_user.generate_token
current_user.save!
notify :notice, t('users.new_token_generated')
redirect_to preferences_path
end
@ -225,11 +202,11 @@ class UsersController < ApplicationController
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 false unless params.has_key?(:user)
return false unless params[:user].has_key?(:login)
return false if params[:user][:login].empty?
return false unless params[:user].has_key?(:password)
return false if params[:user][:password].empty?
return true
end

View file

@ -6,21 +6,23 @@ module ApplicationHelper
# 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>"
link_to name, options, html_options
# TODO: check if this needs to be converted
# 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.in_time_zone.to_date - current_user.time.to_date
Integer (date.in_time_zone.to_date - current_user.time.to_date)
end
# Check due date in comparison to today's date Flag up date appropriately with
@ -53,7 +55,7 @@ module ApplicationHelper
else
# overdue or due very soon! sound the alarm!
if days == -1
t('todos.next_actions_due_date.overdue_by', :days => days * -1)
t('todos.next_actions_due_date.overdue_by', :days => days * -1)
elsif days < -1
t('todos.next_actions_due_date.overdue_by_plural', :days => days * -1)
else
@ -98,18 +100,18 @@ module ApplicationHelper
# 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)
def count_undone_todos_phrase(todos_parent)
controller.count_undone_todos_phrase(todos_parent).html_safe
end
def count_undone_todos_phrase_text(todos_parent, string="actions")
count_undone_todos_phrase(todos_parent, string).gsub("&nbsp;"," ")
def count_undone_todos_phrase_text(todos_parent)
count_undone_todos_phrase(todos_parent).gsub("&nbsp;"," ").html_safe
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
def count_undone_todos_and_notes_phrase(project)
s = count_undone_todos_phrase(project)
s += ", #{t('common.note', :count => project.note_count)}" unless project.note_count == 0
s.html_safe
end
def link_to_context(context, descriptor = sanitize(context.name))
@ -120,18 +122,6 @@ module ApplicationHelper
link_to( descriptor, project, :title => "View project: #{project.name}" )
end
def link_to_edit_project (project, descriptor = sanitize(project.name))
link_to(descriptor,
url_for({:controller => 'projects', :action => 'edit', :id => project.id}),
{:id => "link_edit_#{dom_id(project)}", :class => "project_edit_settings"})
end
def link_to_edit_context (context, descriptor = sanitize(context.name))
link_to(descriptor,
url_for({:controller => 'contexts', :action => 'edit', :id => context.id}),
{:id => "link_edit_#{dom_id(context)}", :class => "context_edit_settings"})
end
def link_to_edit_note (note, descriptor = sanitize(note.id.to_s))
link_to(descriptor,
url_for({:controller => 'notes', :action => 'edit', :id => note.id}),
@ -155,7 +145,7 @@ module ApplicationHelper
end
def render_flash
render :partial => 'shared/flash', :object => flash
render :partial => 'shared/flash', :object => flash
end
def recurrence_time_span(rt)
@ -201,50 +191,18 @@ module ApplicationHelper
end
end
AUTO_LINK_MESSAGE_RE = %r{message://<[^>]+>} unless const_defined?(:AUTO_LINK_MESSAGE_RE)
# Converts message:// links to href. This URL scheme is used on Mac OS X
# to link to a mail message in Mail.app.
def auto_link_message(text)
text.gsub(AUTO_LINK_MESSAGE_RE) do
href = $&
left, right = $`, $'
# detect already linked URLs and URLs in the middle of a tag
if left =~ /<[^>]+$/ && right =~ /^[^>]*>/
# do not change string; URL is alreay linked
href
else
content = content_tag(:a, h(href), :href => h(href))
end
end
end
def format_note(note)
note = auto_link_message(note)
note = markdown(note)
note = auto_link(note, :link => :urls)
# add onenote and message protocols
Sanitize::Config::RELAXED[:protocols]['a']['href'] << 'onenote'
Sanitize::Config::RELAXED[:protocols]['a']['href'] << 'message'
note = Sanitize.clean(note, Sanitize::Config::RELAXED)
return note
end
def sidebar_html_for_titled_list (list, title)
return content_tag(:h3, title+" (#{list.length})") +
content_tag(:ul, sidebar_html_for_list(list))
return content_tag(:h3, title+" (#{list.size})") + content_tag(:ul, sidebar_html_for_list(list))
end
def sidebar_html_for_list(list)
if list.empty?
return content_tag(:li, t('sidebar.list_empty'))
return content_tag(:li, t('sidebar.list_empty')).html_safe
else
return list.inject("") do |html, item|
link = (item.class == "Project") ? link_to_project( item ) : link_to_context(item)
html << content_tag(:li, link + " (" + count_undone_todos_phrase(item,"actions")+")")
end
html << content_tag(:li, link + " (" + count_undone_todos_phrase(item)+")")
end.html_safe
end
end
@ -261,6 +219,7 @@ module ApplicationHelper
contexts.show_form contexts.show_form_title
contexts.new_context_pre contexts.new_context_post
common.cancel common.ok
common.update common.create
common.ajaxError todos.unresolved_dependency
}.each do |s|
js << "i18n['#{s}'] = '#{ t(s).gsub(/'/, "\\\\'") }';\n"
@ -277,7 +236,7 @@ module ApplicationHelper
end
def determine_done_path
case @controller.controller_name
case controller.controller_name
when "contexts"
done_todos_context_path(@context)
when "projects"
@ -287,14 +246,14 @@ module ApplicationHelper
done_tag_path(@tag_name)
else
done_todos_path
end
end
else
done_todos_path
end
end
def determine_all_done_path
case @controller.controller_name
case controller.controller_name
when "contexts"
all_done_todos_context_path(@context)
when "projects"
@ -309,5 +268,13 @@ module ApplicationHelper
all_done_todos_path
end
end
def get_list_of_error_messages_for(model)
error_messages = ""
if model.errors.any?
list_of_messages = model.errors.full_messages.inject("") { |all, msg| all += content_tag(:li, msg) }
error_messages = content_tag(:ul, list_of_messages)
end
end
end

View file

@ -1,30 +1,36 @@
module ContextsHelper
def get_listing_sortable_options
{
:tag => 'div',
:handle => 'handle',
:complete => visual_effect(:highlight, 'list-contexts'),
:url => order_contexts_path
}
end
def link_to_delete_context(context, descriptor = sanitize(context.name))
link_to(descriptor,
context_path(context, :format => 'js'),
{
:id => "delete_context_#{context.id}",
:class => "delete_context_button icon",
:x_confirm_message => t('contexts.delete_context_confirmation', :name => context.name),
:title => t('contexts.delete_context_title')
})
end
def get_listing_sortable_options
{
:tag => 'div',
:handle => 'handle',
:complete => visual_effect(:highlight, 'list-contexts'),
:url => order_contexts_path
}
end
def link_to_delete_context(context, descriptor = sanitize(context.name))
link_to(
descriptor,
context_path(context, :format => 'js'),
{
:id => "delete_context_#{context.id}",
:class => "delete_context_button",
:x_confirm_message => t('contexts.delete_context_confirmation', :name => context.name),
:title => t('contexts.delete_context_title')
}
)
end
def summary(context, undone_todo_count)
content_tag(:p, "#{undone_todo_count}. Context is #{context.hidden? ? 'Hidden' : 'Active'}.")
end
def link_to_edit_context (context, descriptor = sanitize(context.name))
link_to(descriptor,
url_for({:controller => 'contexts', :action => 'edit', :id => context.id}),
{
:id => "link_edit_#{dom_id(context)}",
:class => "context_edit_settings icon"
})
end
def context_summary(context, undone_todo_count)
content_tag(:p, "#{undone_todo_count}. Context is #{context.hidden? ? 'Hidden' : 'Active'}.".html_safe)
end
end

View file

@ -8,12 +8,12 @@ module FeedlistHelper
def text_formatted_link(options = {})
linkoptions = merge_hashes( {:format => 'txt'}, user_token_hash, options)
link_to('<span class="feed">TXT</span>', linkoptions, :title => "Plain text feed" )
link_to(content_tag(:span, 'TXT', {:class => 'feed', :title => "Plain text feed"}), linkoptions)
end
def ical_formatted_link(options = {})
linkoptions = merge_hashes( {:format => 'ics'}, user_token_hash, options)
link_to('<span class="feed">iCal</span>', linkoptions, :title => "iCal feed" )
link_to(content_tag(:span, 'iCal', {:class=>"feed", :title => "iCal feed"}), linkoptions)
end
def feed_links(feeds, link_options, title)
@ -23,7 +23,7 @@ module FeedlistHelper
html << text_formatted_link(link_options)+space if feeds.include?(:txt)
html << ical_formatted_link(link_options)+space if feeds.include?(:ical)
html << title
return html
return html.html_safe
end
def all_feed_links_for_project(project)

View file

@ -1,9 +1,10 @@
module PreferencesHelper
def pref(model, pref_name, &block)
s = "<label for #{model+pref_name}>#{Preference.human_attribute_name(pref_name)}:</label><br/>"
s = content_tag(:label, Preference.human_attribute_name(pref_name), :for => model+pref_name)
s << "<br/>".html_safe
s << yield
s << "<br/><br/>"
s << "<br/><br/>".html_safe
s
end

View file

@ -18,31 +18,30 @@ module ProjectsHelper
end
def project_next_prev
html = ''
unless @previous_project.nil?
html = ""
if @previous_project
project_name = truncate(@previous_project.name, :length => 40, :omission => "...")
html << link_to_project(@previous_project, "&laquo; #{project_name}")
html << link_to_project(@previous_project, "&laquo; #{project_name}".html_safe)
end
html << ' | ' if @previous_project && @next_project
unless @next_project.nil?
html << " | " if @previous_project && @next_project
if @next_project
project_name = truncate(@next_project.name, :length => 40, :omission => "...")
html << link_to_project(@next_project, "#{project_name} &raquo;")
html << link_to_project(@next_project, "#{project_name} &raquo;".html_safe)
end
html
html.html_safe
end
def project_next_prev_mobile
html = ''
prev_project,next_project= "", ""
unless @previous_project.nil?
project_name = truncate(@previous_project.name, :length => 40, :omission => "...")
html << link_to_project_mobile(@previous_project, "5", "&laquo; 5-#{project_name}")
prev_project = content_tag(:li, link_to_project_mobile(@previous_project, "5", project_name), :class=>"prev")
end
html << ' | ' if @previous_project && @next_project
unless @next_project.nil?
project_name = truncate(@next_project.name, :length => 40, :omission => "...")
html << link_to_project_mobile(@next_project, "6", "6-#{project_name} &raquo;")
next_project = content_tag(:li, link_to_project_mobile(@next_project, "6", project_name), :class=>"next")
end
html
return content_tag(:ul, "#{prev_project}#{next_project}".html_safe, :class=>"next-prev-project").html_safe
end
def link_to_delete_project(project, descriptor = sanitize(project.name))
@ -51,20 +50,29 @@ module ProjectsHelper
project_path(project, :format => 'js'),
{
:id => "delete_project_#{project.id}",
:class => "delete_project_button",
:class => "delete_project_button icon",
:x_confirm_message => t('projects.delete_project_confirmation', :name => project.name),
:title => t('projects.delete_project_title')
}
)
end
def summary(project)
def link_to_edit_project (project, descriptor = sanitize(project.name))
link_to(descriptor,
url_for({:controller => 'projects', :action => 'edit', :id => project.id}),
{
:id => "link_edit_#{dom_id(project)}",
:class => "project_edit_settings icon"
})
end
def project_summary(project)
project_description = ''
project_description += sanitize(markdown( project.description )) unless project.description.blank?
project_description += Tracks::Utils.render_text( project.description ) unless project.description.blank?
project_description += content_tag(:p,
"#{count_undone_todos_phrase(p)}. " + t('projects.project_state', :state => project.state)
"#{count_undone_todos_phrase(p)}. #{t('projects.project_state', :state => project.state)}".html_safe
)
project_description
raw project_description
end
def needsreview_class(item)

View file

@ -2,11 +2,10 @@ module RecurringTodosHelper
def recurring_todo_tag_list
tags_except_starred = @recurring_todo.tags.reject{|t| t.name == Todo::STARRED_TAG_NAME}
tag_list = tags_except_starred.collect{|t| "<span class=\"tag #{t.name.gsub(' ','-')}\">" +
link_to(t.name, :controller => "todos", :action => "tag", :id =>
t.name) + #TODO: tag view for recurring_todos (yet?)
"</span>"}.join('')
"<span class='tags'>#{tag_list}</span>"
tag_list = tags_except_starred.
collect{|t| content_tag(:span,link_to(t.name, tag_path(t.name)), :class => "tag #{t.name.gsub(' ','-')}")}.
join('')
return content_tag :span, tag_list.html_safe, :class => "tags"
end
def recurring_todo_remote_delete_icon
@ -27,7 +26,7 @@ module RecurringTodosHelper
edit_recurring_todo_path(@recurring_todo),
:class => "icon edit_icon", :id => "link_edit_recurring_todo_#{@recurring_todo.id}")
else
str = '<a class="icon">' + image_tag("blank.png") + "</a> "
str = content_tag(:a, image_tag("blank.png"), :class => "icon")
end
str
end

View file

@ -63,8 +63,14 @@ module TodosHelper
end
def collapsed_notes_image(todo)
link = link_to(image_tag( 'blank.png', :width=>'16', :height=>'16', :border=>'0' ), "#", {:class => 'show_notes', :title => 'Show notes'})
notes = content_tag(:div, {:class => "todo_notes", :id => dom_id(todo, 'notes'), :style => "display:none"}) { format_note(todo.notes) }
link = link_to(
image_tag( 'blank.png', :width=>'16', :height=>'16', :border=>'0' ),
"#",
{:class => 'show_notes', :title => 'Show notes'})
notes = content_tag(:div, {
:class => "todo_notes",
:id => dom_id(todo, 'notes'),
:style => "display:none"}) { raw todo.rendered_notes }
return link+notes
end
@ -80,7 +86,7 @@ module TodosHelper
def image_tag_for_recurring_todo(todo)
return link_to(
image_tag("recurring16x16.png"),
{:controller => "recurring_todos", :action => "index"},
recurring_todos_path,
:class => "recurring_icon", :title => recurrence_pattern_as_text(todo.recurring_todo))
end
@ -90,9 +96,9 @@ module TodosHelper
end
def remote_mobile_checkbox(todo=@todo)
form_tag mobile_done_todo_path(@todo, :format => 'm'), :method => :put, :class => "mobile-done", :name => "mobile_complete_#{@todo.id}" do
form_tag toggle_check_todo_path(@todo, :format => 'm'), :method => :put, :class => "mobile-done", :name => "mobile_complete_#{@todo.id}" do
check_box_tag('_source_view', 'todo', @todo && @todo.completed?, "onClick" => "document.mobile_complete_#{@todo.id}.submit()")
end
end
end
def date_span(todo=@todo)
@ -110,7 +116,7 @@ module TodosHelper
def successors_span(todo=@todo)
unless todo.pending_successors.empty?
pending_count = todo.pending_successors.length
pending_count = todo.pending_successors.count
title = "#{t('todos.has_x_pending', :count => pending_count)}: #{todo.pending_successors.map(&:description).join(', ')}"
image_tag( 'successor_off.png', :width=>'10', :height=>'16', :border=>'0', :title => title )
end
@ -129,25 +135,19 @@ module TodosHelper
end
def tag_span (tag, mobile=false)
content_tag(:span, :class => "tag #{tag.name.gsub(' ','-')}") { link_to(tag.name, (mobile ? mobile_tag_path(tag.name) : tag_path(tag.name))) }
content_tag(:span, :class => "tag #{tag.name.gsub(' ','-')}") { link_to(tag.name, tag_path(tag.name, :format => mobile ? :m : :html)) }
end
def tag_list(todo=@todo, mobile=false)
content_tag(:span, :class => 'tags') { todo.tags.all_except_starred.collect{|tag| tag_span(tag, mobile)}.join('') }
content_tag(:span, :class => 'tags') { todo.tags.all_except_starred.collect{|tag| tag_span(tag, mobile)}.join('').html_safe }
end
def tag_list_mobile(todo=@todo)
unless todo.tags.all_except_starred.empty?
return tag_list(todo, true)
else
return ""
end
todo.tags.all_except_starred.empty? ? "" : tag_list(todo, true)
end
def deferred_due_date(todo=@todo)
if todo.deferred? && todo.due
t('todos.action_due_on', :date => format_date(todo.due))
end
t('todos.action_due_on', :date => format_date(todo.due)) if todo.deferred? && todo.due
end
def project_and_context_links(todo, parent_container_type, opts = {})
@ -166,7 +166,7 @@ module TodosHelper
str << item_link_to_project( todo )
end
end
return str
return str.html_safe
end
# Uses the 'staleness_starts' value from settings.yml (in days) to colour the
@ -272,12 +272,12 @@ module TodosHelper
end
def default_contexts_for_autocomplete
projects = current_user.uncompleted.projects.find(:all, :include => [:context], :conditions => ['default_context_id is not null'])
projects = current_user.projects.uncompleted.includes(:default_context).where('NOT(default_context_id IS NULL)')
Hash[*projects.map{ |p| [escape_javascript(p.name), escape_javascript(p.default_context.name)] }.flatten].to_json
end
def default_tags_for_autocomplete
projects = current_user.projects.uncompleted.find(:all, :conditions => ["default_tags != ''"])
projects = current_user.projects.uncompleted.where("NOT(default_tags = '')")
Hash[*projects.map{ |p| [escape_javascript(p.name), p.default_tags] }.flatten].to_json
end
@ -311,7 +311,7 @@ module TodosHelper
def update_needs_to_remove_todo_from_container
source_view do |page|
page.context { return @context_changed || @todo.deferred? || @todo.pending? || @todo_should_be_hidden }
page.context { return @context_changed || @todo_deferred_state_changed || @todo_pending_state_changed || @todo_should_be_hidden }
page.project { return @todo_deferred_state_changed || @todo_pending_state_changed || @project_changed}
page.deferred { return @context_changed || !(@todo.deferred? || @todo.pending?) }
page.calendar { return @due_date_changed || !@todo.due }
@ -339,7 +339,7 @@ module TodosHelper
def append_updated_todo
source_view do |page|
page.context { return false }
page.context { return @todo_deferred_state_changed || @todo_pending_state_changed }
page.project { return @todo_deferred_state_changed || @todo_pending_state_changed }
page.deferred { return @context_changed && (@todo.deferred? || @todo.pending?) }
page.calendar { return @due_date_changed && @todo.due }
@ -370,7 +370,8 @@ module TodosHelper
@todo_was_blocked_from_active_state ||
@todo_was_destroyed_from_deferred_state ||
@todo_was_created_deferred ||
@todo_was_blocked_from_completed_state
@todo_was_blocked_from_completed_state ||
@todo_was_created_blocked
return "p#{todo.project_id}empty-nd"
}
page.tag {
@ -379,13 +380,31 @@ module TodosHelper
@todo_was_blocked_from_active_state ||
@todo_was_destroyed_from_deferred_state ||
@todo_was_created_deferred ||
@todo_was_blocked_from_completed_state
@todo_was_blocked_from_completed_state ||
@todo_was_created_blocked
return "hidden-empty-nd" if @todo.hidden?
return "c#{todo.context_id}empty-nd"
}
page.calendar {
return "tickler-empty-nd" if
@todo_was_deferred_from_active_state ||
@todo_was_blocked_from_active_state ||
@todo_was_destroyed_from_deferred_state ||
@todo_was_created_deferred ||
@todo_was_blocked_from_completed_state ||
@todo_was_created_blocked
return "empty_#{@new_due_id}"
}
page.context {
return "tickler-empty-nd" if
@todo_was_deferred_from_active_state ||
@todo_was_blocked_from_active_state ||
@todo_was_destroyed_from_deferred_state ||
@todo_was_created_deferred ||
@todo_was_blocked_from_completed_state ||
@todo_was_created_blocked
return "c#{todo.context_id}empty-nd"
}
end
return "c#{todo.context_id}empty-nd"
@ -417,11 +436,12 @@ module TodosHelper
}
page.context {
container_id = "c#{@original_item_context_id}empty-nd" if @remaining_in_context == 0
container_id = "tickler-empty-nd" if todo_was_removed_from_deferred_or_blocked_container && @remaining_deferred_or_pending_count == 0
container_id = "empty-d" if @completed_count && @completed_count == 0 && !@todo.completed?
}
page.todo { container_id = "c#{@original_item_context_id}empty-nd" if @remaining_in_context == 0 }
end
return container_id.blank? ? "" : "$(\"##{container_id}\").slideDown(100);"
return container_id.blank? ? "" : "$(\"##{container_id}\").slideDown(100);".html_safe
end
def render_animation(animation)
@ -447,6 +467,17 @@ module TodosHelper
return $tracks_tab_index
end
def feed_content_for_todo(todo)
item_notes = todo.notes ? todo.rendered_notes : ''
due = todo.due ? content_tag(:div, t('todos.feeds.due', :date => format_date(todo.due))) : ''
done = todo.completed? ? content_tag(:div, t('todos.feeds.completed', :date => format_date(todo.completed_at))) : ''
context_link = link_to(context_url(todo.context), todo.context.name)
project_link = todo.project.is_a?(NullProject) ? content_tag(:em, t('common.none')) : link_to(project_url(todo.project), todo.project.name)
return "#{done} #{due} #{item_notes}\n" +
content_tag(:div, "#{t('common.project')}: #{project_link}") + "\n" +
content_tag(:div, "#{t('common.context')}: #{context_link}")
end
private
def image_tag_for_star(todo)

0
app/mailers/.gitkeep Normal file
View file

Some files were not shown because too many files have changed in this diff Show more