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

This commit is contained in:
Hans de Graaff 2012-01-08 19:55:46 +01:00
commit 87b25f77d2
50 changed files with 3411 additions and 1978 deletions

1
.gitignore vendored
View file

@ -22,3 +22,4 @@ public/stylesheets/tracks-cached.css
.idea
.rvmrc
.yardoc
tags

11
Gemfile
View file

@ -15,6 +15,7 @@ gem "rubyjedi-actionwebservice", :require => "actionwebservice"
gem "rubycas-client", "~>2.2.1"
gem "ruby-openid", :require => "openid"
gem "sqlite3"
gem "mysql"
gem 'bcrypt-ruby', '~> 2.1.4'
gem 'htmlentities', '~> 4.3.0'
gem "mail"
@ -25,10 +26,6 @@ else
gem "soap4r", "~>1.5.8"
end
gem "webrat", ">=0.7.0", :groups => [:cucumber, :test]
gem "database_cleaner", ">=0.5.0", :groups => [:cucumber, :selenium]
gem "cucumber-rails", "~>0.3.0", :groups => :cucumber
group :development do
if RUBY_VERSION.to_f >= 1.9
gem "ruby-debug19"
@ -49,8 +46,8 @@ group :test do
gem "rspec-rails", "~>1.3.3"
gem "thoughtbot-factory_girl"
gem 'memory_test_fix', '~>0.1.3'
end
group :selenium do
gem "selenium-client"
gem "webrat", ">=0.7.0"
gem "database_cleaner", ">=0.5.0"
gem "cucumber-rails", "~>0.3.0"
end

View file

@ -59,6 +59,7 @@ GEM
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)
@ -129,6 +130,7 @@ DEPENDENCIES
mail
memory_test_fix (~> 0.1.3)
mongrel
mysql
rack (= 1.1.0)
rails (~> 2.3.12)
rake (~> 0.8.7)

27
README
View file

@ -19,13 +19,30 @@
* Copyright: (cc) 2004-2011 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/
The latter 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 mostly because of the number of different platforms supported, and the different configurations which can be used (e.g. running Tracks on your local computer or on a remote server). If you choose the appropriate section for your situation (installation vs. upgrade), and use the easiest (recommended) method, you should find the instructions easy to follow. If you encounter problems, try searching the wiki, forum or mailing list (URLs above), and ask a question if you cannot find a solution to your problem.
All the documentation for Tracks can be found within the /doc directory and at
http://bsag.github.com/tracks/
The latter 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
mostly because of the number of different platforms supported, and the
different configurations which can be used (e.g. running Tracks on your local
computer or on a remote server). If you choose the appropriate section for your
situation (installation vs. upgrade), and use the easiest (recommended) method,
you should find the instructions easy to follow. If you encounter problems, try
searching the wiki, forum or mailing list (URLs above), and ask a question if
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.
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.
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.
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!

View file

@ -177,9 +177,9 @@ class ApplicationController < ActionController::Base
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.recurring_todo_id = rt.id
todo.due = rt.get_due_date(date)
show_from_date = rt.get_show_from_date(date)

View file

@ -9,8 +9,8 @@ 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
@completed_recurring_todos = current_user.recurring_todos.completed.find(:all, :limit => 10)
@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])
@no_recurring_todos = @recurring_todos.size == 0
@no_completed_recurring_todos = @completed_recurring_todos.size == 0
@ -21,15 +21,15 @@ class RecurringTodosController < ApplicationController
def new
end
def show
end
def done
@page_title = t('todos.completed_recurring_actions_title')
@source_view = params['_source_view'] || 'recurring_todo'
items_per_page = 20
page = params[:page] || 1
page = params[:page] || 1
@completed_recurring_todos = current_user.recurring_todos.completed.paginate :page => params[:page], :per_page => items_per_page
@total = @count = current_user.recurring_todos.completed.count
@range_low = (page.to_i-1) * items_per_page + 1
@ -54,7 +54,7 @@ class RecurringTodosController < ApplicationController
params['recurring_todo']['recurring_period']=params['recurring_edit_todo']['recurring_period']
params['recurring_todo']['end_date']=parse_date_per_user_prefs(params['recurring_todo_edit_end_date'])
params['recurring_todo']['start_from']=parse_date_per_user_prefs(params['recurring_todo_edit_start_from'])
# update project
if params['recurring_todo']['project_id'].blank? && !params['project_name'].nil?
if params['project_name'] == 'None'
@ -70,7 +70,7 @@ class RecurringTodosController < ApplicationController
end
params["recurring_todo"]["project_id"] = project.id
end
# update context
if params['recurring_todo']['context_id'].blank? && !params['context_name'].blank?
context = current_user.contexts.find_by_name(params['context_name'].strip)
@ -88,14 +88,14 @@ class RecurringTodosController < ApplicationController
%w{monday tuesday wednesday thursday friday saturday sunday}.each do |day|
params["recurring_todo"]["weekly_return_"+day]=' ' if params["recurring_todo"]["weekly_return_"+day].nil?
end
@saved = @recurring_todo.update_attributes params["recurring_todo"]
respond_to do |format|
format.js
end
end
def create
p = RecurringTodoCreateParamsHelper.new(params)
p.attributes['end_date']=parse_date_per_user_prefs(p.attributes['end_date'])
@ -109,7 +109,7 @@ class RecurringTodosController < ApplicationController
@new_project_created = project.new_record_before_save?
@recurring_todo.project_id = project.id
end
if p.context_specified_by_name?
context = current_user.contexts.find_or_create_by_name(p.context_name)
@new_context_created = context.new_record_before_save?
@ -134,13 +134,13 @@ class RecurringTodosController < ApplicationController
@new_recurring_todo = RecurringTodo.new
else
@status_message = t('todos.error_saving_recurring')
end
end
respond_to do |format|
format.js
format.js
end
end
def destroy
# remove all references to this recurring todo
@todos = @recurring_todo.todos
@ -149,16 +149,16 @@ class RecurringTodosController < ApplicationController
t.recurring_todo_id = nil
t.save
end
# delete the recurring todo
@saved = @recurring_todo.destroy
# count remaining recurring todos
@active_remaining = current_user.recurring_todos.active.count
@completed_remaining = current_user.recurring_todos.completed.count
respond_to do |format|
format.html do
if @saved
notify :notice, t('todos.recurring_deleted_success'), 2.0
@ -168,10 +168,10 @@ class RecurringTodosController < ApplicationController
redirect_to :action => 'index'
end
end
format.js do
render
end
end
end
end
@ -184,19 +184,19 @@ class RecurringTodosController < ApplicationController
if @recurring_todo.active?
@completed_remaining = current_user.recurring_todos.completed.count
# from completed back to active -> check if there is an active todo
@active_todos = @recurring_todo.todos.active.count
# create todo if there is no active todo belonging to the activated
# recurring_todo
@new_recurring_todo = create_todo_from_recurring_todo(@recurring_todo) if @active_todos == 0
end
respond_to do |format|
format.js
format.js
end
end
def toggle_star
@recurring_todo.toggle_star!
@saved = @recurring_todo.save!
@ -204,13 +204,13 @@ class RecurringTodosController < ApplicationController
format.js
end
end
class RecurringTodoCreateParamsHelper
def initialize(params)
@params = params['request'] || params
@attributes = params['request'] && params['request']['recurring_todo'] || params['recurring_todo']
# make sure all selectors (recurring_period, recurrence_selector,
# daily_selector, monthly_selector and yearly_selector) are first in hash
# so that they are processed first by the model
@ -221,47 +221,47 @@ class RecurringTodosController < ApplicationController
'yearly_selector' => @attributes['yearly_selector']
}
end
def attributes
@attributes
end
def selector_attributes
return @selector_attributes
end
def project_name
@params['project_name'].strip unless @params['project_name'].nil?
end
def context_name
@params['context_name'].strip unless @params['context_name'].nil?
end
def tag_list
@params['tag_list']
end
def project_specified_by_name?
return false unless @attributes['project_id'].blank?
return false if project_name.blank?
return false if project_name == 'None'
true
end
def context_specified_by_name?
return false unless @attributes['context_id'].blank?
return false if context_name.blank?
true
end
end
private
def init
@days_of_week = []
0.upto 6 do |i|
0.upto 6 do |i|
@days_of_week << [t('date.day_names')[i], i]
end
@ -274,15 +274,18 @@ class RecurringTodosController < ApplicationController
@projects = current_user.projects.find(:all, :include => [:default_context])
@contexts = current_user.contexts.find(:all)
end
def get_recurring_todo_from_param
@recurring_todo = current_user.recurring_todos.find(params[:id])
end
def find_and_inactivate
# find active recurring todos without active todos and inactivate them
recurring_todos = current_user.recurring_todos.active
recurring_todos.each { |rt| rt.toggle_completion! if rt.todos.not_completed.count == 0}
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! }
end
end

View file

@ -90,7 +90,7 @@ class TodosController < ApplicationController
end
if @todo.errors.empty?
@todo.starred= (params[:new_todo_starred]||"").include? "true"
@todo.starred= (params[:new_todo_starred]||"").include? "true" if params[:new_todo_starred]
@todo.add_predecessor_list(predecessor_list)
@ -361,6 +361,49 @@ class TodosController < ApplicationController
end
end
def mobile_done
# copied from toggle_check, left out other formats as they shouldn't come here
# ultimately would like to just use toggle_check
@todo = current_user.todos.find(params['id'])
@source_view = params['_source_view'] || 'todo'
@original_item_due = @todo.due
@original_item_was_deferred = @todo.deferred?
@original_item_was_pending = @todo.pending?
@original_item_was_hidden = @todo.hidden?
@original_item_context_id = @todo.context_id
@original_item_project_id = @todo.project_id
@todo_was_completed_from_deferred_or_blocked_state = @original_item_was_deferred || @original_item_was_pending
@saved = @todo.toggle_completion!
@todo_was_blocked_from_completed_state = @todo.pending? # since we toggled_completion the previous state was completed
# check if this todo has a related recurring_todo. If so, create next todo
@new_recurring_todo = check_for_next_todo(@todo) if @saved
@predecessors = @todo.uncompleted_predecessors
if @saved
if @todo.completed?
@pending_to_activate = @todo.activate_pending_todos
else
@active_to_block = @todo.block_successors
end
end
if @saved
if cookies[:mobile_url]
old_path = cookies[:mobile_url]
cookies[:mobile_url] = {:value => nil, :secure => SITE_CONFIG['secure_cookies']}
notify(:notice, t("todos.action_marked_complete", :description => @todo.description, :completed => @todo.completed? ? 'complete' : 'incomplete'))
redirect_to old_path
else
notify(:notice, t("todos.action_marked_complete", :description => @todo.description, :completed => @todo.completed? ? 'complete' : 'incomplete'))
redirect_to todos_path(:format => 'm')
end
else
render :action => "edit", :format => :m
end
end
def toggle_star
@todo = current_user.todos.find(params['id'])
@todo.toggle_star!
@ -1103,7 +1146,7 @@ class TodosController < ApplicationController
if @todo_was_completed_from_deferred_or_blocked_state
@remaining_in_context = @remaining_deferred_or_pending_count
else
@remaining_in_context = current_user.projects.find(project_id).todos.active.count
@remaining_in_context = current_user.projects.find(project_id).todos.active_or_hidden.count
end
@target_context_count = current_user.projects.find(project_id).todos.active.count
@ -1176,6 +1219,10 @@ class TodosController < ApplicationController
lambda do
@page_title = t('todos.mobile_todos_page_title')
@home = true
max_completed = current_user.prefs.show_number_completed
@done = current_user.todos.completed.find(:all, :limit => max_completed, :include => Todo::DEFAULT_INCLUDES) unless max_completed == 0
cookies[:mobile_url]= { :value => request.request_uri, :secure => SITE_CONFIG['secure_cookies']}
determine_down_count
@ -1516,6 +1563,15 @@ class TodosController < ApplicationController
@params = params['request'] || params
@prefs = prefs
@attributes = params['request'] && params['request']['todo'] || params['todo']
if @attributes && @attributes[:tags]
# for single tags, @attributed[:tags] returns a hash. For multiple tags,
# it with return an array of hashes. Make sure it is always an array of hashes
@attributes[:tags][:tag] = [@attributes[:tags][:tag]] unless @attributes[:tags][:tag].class == Array
# the REST api may use <tags> which will collide with tags association, so rename tags to add_tags
@attributes[:add_tags] = @attributes[:tags]
@attributes.delete :tags
end
end
def attributes

View file

@ -89,6 +89,12 @@ module TodosHelper
:title => todo.pending? ? t('todos.blocked_by', :predecessors => todo.uncompleted_predecessors.map(&:description).join(', ')) : "", :readonly => todo.pending?)
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
check_box_tag('_source_view', 'todo', @todo && @todo.completed?, "onClick" => "document.mobile_complete_#{@todo.id}.submit()")
end
end
def date_span(todo=@todo)
if todo.completed?
content_tag(:span, {:class => :grey}) { format_date( todo.completed_at ) }

View file

@ -120,8 +120,8 @@ class Project < ActiveRecord::Base
end
def stalled?
# stalled is active/hidden project with no active todos
return false if self.completed?
# Stalled projects are active projects with no active next actions
return false if self.completed? || self.hidden?
return self.todos.deferred_or_blocked.empty? && self.todos.not_deferred_or_blocked.empty?
end

View file

@ -1,5 +1,5 @@
class RecurringTodo < ActiveRecord::Base
belongs_to :context
belongs_to :project
belongs_to :user
@ -10,11 +10,11 @@ class RecurringTodo < ActiveRecord::Base
named_scope :completed, :conditions => { :state => 'completed'}
attr_protected :user
include AASM
aasm_column :state
aasm_initial_state :active
aasm_state :active, :enter => Proc.new { |t| t.occurences_count = 0 }
aasm_state :completed, :enter => Proc.new { |t| t.completed_at = Time.zone.now }, :exit => Proc.new { |t| t.completed_at = nil }
@ -25,7 +25,7 @@ class RecurringTodo < ActiveRecord::Base
aasm_event :activate do
transitions :to => :active, :from => [:completed]
end
validates_presence_of :description
validates_presence_of :recurring_period
validates_presence_of :target
@ -33,12 +33,12 @@ class RecurringTodo < ActiveRecord::Base
validates_presence_of :context
validates_length_of :description, :maximum => 100
validates_length_of :notes, :maximum => 60000, :allow_nil => true
validates_length_of :notes, :maximum => 60000, :allow_nil => true
validate :period_specific_validations
validate :starts_and_ends_on_validations
validate :set_recurrence_on_validations
def period_specific_validations
if %W[daily weekly monthly yearly].include?(recurring_period)
self.send("validate_#{recurring_period}")
@ -118,7 +118,7 @@ class RecurringTodo < ActiveRecord::Base
raise Exception.new, "unexpected value of recurrence target selector '#{self.recurrence_target}'"
end
end
# the following recurrence patterns can be stored:
#
# daily todos - recurrence_period = 'daily'
@ -140,9 +140,9 @@ class RecurringTodo < ActiveRecord::Base
# x is stored in every_other3, y is stored in every_count, z is stored in every_other2
# choosing between both options is done on recurrence_selector where 0 is
# for first type and 1 for second type
# DAILY
def daily_selector=(selector)
case selector
when 'daily_every_x_day'
@ -153,34 +153,34 @@ class RecurringTodo < ActiveRecord::Base
raise Exception.new, "unknown daily recurrence pattern: '#{selector}'"
end
end
def daily_every_x_days=(x)
if recurring_period=='daily'
self.every_other1 = x
end
end
def daily_every_x_days
return self.every_other1
end
# WEEKLY
def weekly_every_x_week=(x)
self.every_other1 = x if recurring_period=='weekly'
end
def weekly_every_x_week
return self.every_other1
end
def switch_week_day (day, position)
if self.every_day.nil?
self.every_day=' '
end
self.every_day = self.every_day[0,position] + day + self.every_day[position+1,self.every_day.length]
end
def weekly_return_monday=(selector)
switch_week_day(selector,1) if recurring_period=='weekly'
end
@ -188,15 +188,15 @@ class RecurringTodo < ActiveRecord::Base
def weekly_return_tuesday=(selector)
switch_week_day(selector,2) if recurring_period=='weekly'
end
def weekly_return_wednesday=(selector)
switch_week_day(selector,3) if recurring_period=='weekly'
end
def weekly_return_thursday=(selector)
switch_week_day(selector,4) if recurring_period=='weekly'
end
def weekly_return_friday=(selector)
switch_week_day(selector,5) if recurring_period=='weekly'
end
@ -204,11 +204,11 @@ class RecurringTodo < ActiveRecord::Base
def weekly_return_saturday=(selector)
switch_week_day(selector,6) if recurring_period=='weekly'
end
def weekly_return_sunday=(selector)
switch_week_day(selector,0) if recurring_period=='weekly'
end
def on_xday(n)
unless self.every_day.nil?
return self.every_day[n,1] == ' ' ? false : true
@ -216,51 +216,51 @@ class RecurringTodo < ActiveRecord::Base
return false
end
end
def on_monday
return on_xday(1)
end
def on_tuesday
return on_xday(2)
end
def on_wednesday
return on_xday(3)
end
def on_thursday
return on_xday(4)
end
def on_friday
return on_xday(5)
end
def on_saturday
return on_xday(6)
end
def on_sunday
return on_xday(0)
end
# MONTHLY
def monthly_selector=(selector)
if recurring_period=='monthly'
self.recurrence_selector= (selector=='monthly_every_x_day')? 0 : 1
end
end
def monthly_every_x_day=(x)
self.every_other1 = x if recurring_period=='monthly'
end
def monthly_every_x_day
return self.every_other1
end
def is_monthly_every_x_day
return self.recurrence_selector == 0 if recurring_period == 'monthly'
return false
@ -270,11 +270,11 @@ class RecurringTodo < ActiveRecord::Base
return self.recurrence_selector == 1 if recurring_period == 'monthly'
return false
end
def monthly_every_x_month=(x)
self.every_other2 = x if recurring_period=='monthly' && recurrence_selector == 0
end
def monthly_every_x_month
# in case monthly pattern is every day x, return every_other2 otherwise
# return a default value
@ -298,36 +298,36 @@ class RecurringTodo < ActiveRecord::Base
return 1
end
end
def monthly_every_xth_day=(x)
self.every_other3 = x if recurring_period=='monthly'
end
def monthly_every_xth_day(default=nil)
return self.every_other3 unless self.every_other3.nil?
return default
end
def monthly_day_of_week=(dow)
self.every_count = dow if recurring_period=='monthly'
end
def monthly_day_of_week
return self.every_count
end
# YEARLY
def yearly_selector=(selector)
if recurring_period=='yearly'
self.recurrence_selector = (selector=='yearly_every_x_day') ? 0 : 1
end
end
def yearly_month_of_year=(moy)
self.every_other2 = moy if self.recurring_period=='yearly' && self.recurrence_selector == 0
end
def yearly_month_of_year
# if recurrence pattern is every x day in a month, return month otherwise
# return a default value
@ -341,7 +341,7 @@ class RecurringTodo < ActiveRecord::Base
def yearly_month_of_year2=(moy)
self.every_other2 = moy if self.recurring_period=='yearly' && self.recurrence_selector == 1
end
def yearly_month_of_year2
# if recurrence pattern is every xth day in a month, return month otherwise
# return a default value
@ -351,33 +351,33 @@ class RecurringTodo < ActiveRecord::Base
return Time.zone.now.month
end
end
def yearly_every_x_day=(x)
self.every_other1 = x if recurring_period=='yearly'
end
def yearly_every_x_day
return self.every_other1
end
def yearly_every_xth_day=(x)
self.every_other3 = x if recurring_period=='yearly'
end
def yearly_every_xth_day
return self.every_other3
end
def yearly_day_of_week=(dow)
self.every_count=dow if recurring_period=='yearly'
end
def yearly_day_of_week
return self.every_count
end
# target
def recurring_target=(t)
self.target = t
end
@ -392,11 +392,11 @@ class RecurringTodo < ActiveRecord::Base
raise Exception.new, "unexpected value of recurrence target '#{self.target}'"
end
end
def recurring_show_days_before=(days)
self.show_from_delta=days
end
def recurring_show_always=(value)
self.show_always=value
end
@ -435,41 +435,45 @@ class RecurringTodo < ActiveRecord::Base
else
n_months = I18n.t('common.month')
end
return I18n.t('todos.recurrence.pattern.every_xth_day_of_every_n_months',
return I18n.t('todos.recurrence.pattern.every_xth_day_of_every_n_months',
:x => self.xth, :day => self.day_of_week, :n_months => n_months)
end
when 'yearly'
if self.recurrence_selector == 0
return I18n.t("todos.recurrence.pattern.every_year_on",
return I18n.t("todos.recurrence.pattern.every_year_on",
:date => I18n.l(DateTime.new(Time.zone.now.year, self.every_other2, self.every_other1), :format => :month_day))
else
return I18n.t("todos.recurrence.pattern.every_year_on",
return I18n.t("todos.recurrence.pattern.every_year_on",
:date => I18n.t("todos.recurrence.pattern.the_xth_day_of_month", :x => self.xth, :day => self.day_of_week, :month => self.month_of_year))
end
else
return 'unknown recurrence pattern: period unknown'
end
end
def xth
xth_day = [
I18n.t('todos.recurrence.pattern.first'),I18n.t('todos.recurrence.pattern.second'),I18n.t('todos.recurrence.pattern.third'),
I18n.t('todos.recurrence.pattern.fourth'),I18n.t('todos.recurrence.pattern.last')]
return self.every_other3.nil? ? '??' : xth_day[self.every_other3-1]
end
def day_of_week
return (self.every_count.nil? ? '??' : I18n.t('todos.recurrence.pattern.day_names')[self.every_count])
end
def month_of_year
return self.every_other2.nil? ? '??' : I18n.t('todos.recurrence.pattern.month_names')[self.every_other2]
end
def starred?
tags.any? {|tag| tag.name == Todo::STARRED_TAG_NAME }
return has_tag?(Todo::STARRED_TAG_NAME)
end
def has_tag?(tag_name)
return self.tags.any? {|tag| tag.name == tag_name}
end
def get_due_date(previous)
case self.target
when 'due_date'
@ -481,7 +485,7 @@ class RecurringTodo < ActiveRecord::Base
raise Exception.new, "unexpected value of recurrence target '#{self.target}'"
end
end
def get_show_from_date(previous)
case self.target
when 'due_date'
@ -498,7 +502,7 @@ class RecurringTodo < ActiveRecord::Base
raise Exception.new, "unexpected value of recurrence target '#{self.target}'"
end
end
def get_next_date(previous)
case self.recurring_period
when 'daily'
@ -513,14 +517,14 @@ class RecurringTodo < ActiveRecord::Base
raise Exception.new, "unknown recurrence pattern: '#{self.recurring_period}'"
end
end
def get_daily_date(previous)
# previous is the due date of the previous todo or it is the completed_at
# date when the completed_at date is after due_date (i.e. you did not make
# the due date in time)
#
# assumes self.recurring_period == 'daily'
start = determine_start(previous, 1.day)
if self.only_work_days
@ -533,7 +537,7 @@ class RecurringTodo < ActiveRecord::Base
return previous == nil ? start : start+every_other1.day-1.day
end
end
def get_weekly_date(previous)
# determine start
if previous == nil
@ -551,12 +555,12 @@ class RecurringTodo < ActiveRecord::Base
start = self.start_from if self.start_from > previous
end
end
# check if there are any days left this week for the next todo
start.wday().upto 6 do |i|
return start + (i-start.wday()).days unless self.every_day[i,1] == ' '
end
# we did not find anything this week, so check the nth next, starting from
# sunday
start = start + self.every_other1.week - (start.wday()).days
@ -568,9 +572,9 @@ class RecurringTodo < ActiveRecord::Base
raise Exception.new, "unable to find next weekly date (#{self.every_day})"
end
def get_monthly_date(previous)
start = determine_start(previous)
day = self.every_other1
n = self.every_other2
@ -594,7 +598,7 @@ class RecurringTodo < ActiveRecord::Base
# go back to day
end
return Time.zone.local(start.year, start.month, day)
when 1 # relative weekday of a month
the_next = get_xth_day_of_month(self.every_other3, self.every_count, start.month, start.year)
if the_next.nil? || the_next <= start
@ -618,7 +622,7 @@ class RecurringTodo < ActiveRecord::Base
end
return nil
end
def get_xth_day_of_month(x, weekday, month, year)
if x == 5
# last -> count backwards. use UTC to avoid strange timezone oddities
@ -646,12 +650,12 @@ class RecurringTodo < ActiveRecord::Base
return Time.zone.local(start.year, start.month, start.day)
end
end
def get_yearly_date(previous)
start = determine_start(previous)
day = self.every_other1
month = self.every_other2
case self.recurrence_selector
when 0 # specific day of a specific month
if start.month > month || (start.month == month && start.day >= day)
@ -663,25 +667,25 @@ class RecurringTodo < ActiveRecord::Base
start = Time.zone.local(start.year, month, 1)
end
return Time.zone.local(start.year, month, day)
when 1 # relative weekday of a specific month
# if there is no next month n in this year, search in next year
the_next = start.month > month ? Time.zone.local(start.year+1, month, 1) : start
# get the xth day of the month
the_next = get_xth_day_of_month(self.every_other3, self.every_count, month, the_next.year)
# if the_next is before previous, we went back into the past, so try next
# year
the_next = get_xth_day_of_month(self.every_other3, self.every_count, month, start.year+1) if the_next <= start
return the_next
else
raise Exception.new, "unknown monthly recurrence selection (#{self.recurrence_selector})"
end
return nil
end
def has_next_todo(previous)
unless self.number_of_occurences.nil?
return self.occurences_count < self.number_of_occurences
@ -700,11 +704,11 @@ class RecurringTodo < ActiveRecord::Base
end
end
end
def toggle_completion!
return completed? ? activate! : complete!
end
def toggle_star!
if starred?
_remove_tags Todo::STARRED_TAG_NAME
@ -715,7 +719,7 @@ class RecurringTodo < ActiveRecord::Base
end
starred?
end
def remove_from_project!
self.project = nil
self.save
@ -729,17 +733,18 @@ class RecurringTodo < ActiveRecord::Base
end
end
end
def inc_occurences
self.occurences_count += 1
self.save
end
protected
# Determine start date to calculate next date for recurring todo
# offset needs to be 1.day for daily patterns
def determine_start(previous, offset=0.day)
# offset needs to be 1.day for daily patterns
if previous.nil?
start = self.start_from.nil? ? Time.zone.now : self.start_from
# skip to present
@ -747,13 +752,12 @@ class RecurringTodo < ActiveRecord::Base
else
start = previous + offset
unless self.start_from.nil?
# check if the start_from date is later than previous. If so, use
# start_from as start to search for next date
start = self.start_from if self.start_from > previous
end
# check if the start_from date is later than previous. If so, use
# start_from as start to search for next date
start = self.start_from if ( self.start_from && self.start_from > previous )
end
return start
end
end

View file

@ -47,13 +47,6 @@ class Todo < ActiveRecord::Base
STARRED_TAG_NAME = "starred"
DEFAULT_INCLUDES = [ :project, :context, :tags, :taggings, :pending_successors, :uncompleted_predecessors, :recurring_todo ]
# regular expressions for dependencies. TODO: are these still used?
RE_TODO = /[^']+/
RE_CONTEXT = /[^']+/
RE_PROJECT = /[^']+/
RE_PARTS = /'(#{RE_TODO})'\s<'(#{RE_CONTEXT})';\s'(#{RE_PROJECT})'>/ # results in array
RE_SPEC = /'#{RE_TODO}'\s<'#{RE_CONTEXT}';\s'#{RE_PROJECT}'>/ # results in string
# state machine
include AASM
aasm_column :state
@ -113,12 +106,11 @@ class Todo < ActiveRecord::Base
def no_uncompleted_predecessors_or_deferral?
no_deferral = show_from.blank? or Time.zone.now > show_from
no_uncompleted_predecessors = uncompleted_predecessors.all(true).empty?
return (no_deferral && no_uncompleted_predecessors)
return (no_deferral && no_uncompleted_predecessors?)
end
def no_uncompleted_predecessors?
return uncompleted_predecessors.all(true).empty?
return !uncompleted_predecessors?
end
def uncompleted_predecessors?
@ -197,8 +189,8 @@ class Todo < ActiveRecord::Base
return !pending_successors.empty?
end
def has_tag?(tag)
return self.tags.select{|t| t.name==tag }.size > 0
def has_tag?(tag_name)
return self.tags.any? {|tag| tag.name == tag_name}
end
def hidden?
@ -239,12 +231,6 @@ class Todo < ActiveRecord::Base
defer! if active? && !date.blank? && date > user.date
end
alias_method :original_project, :project
def project
original_project.nil? ? Project.null_object : original_project
end
def self.feed_options(user)
{
:title => 'Tracks Actions',
@ -253,7 +239,7 @@ class Todo < ActiveRecord::Base
end
def starred?
tags.any? {|tag| tag.name == STARRED_TAG_NAME}
return has_tag?(STARRED_TAG_NAME)
end
def toggle_star!
@ -276,18 +262,18 @@ class Todo < ActiveRecord::Base
def add_predecessor_list(predecessor_list)
return unless predecessor_list.kind_of? String
@predecessor_array=[]
predecessor_ids_array = predecessor_list.split(",")
predecessor_ids_array.each do |todo_id|
@predecessor_array=predecessor_list.split(",").inject([]) do |list, todo_id|
predecessor = self.user.todos.find_by_id( todo_id.to_i ) unless todo_id.blank?
@predecessor_array << predecessor unless predecessor.nil?
list << predecessor unless predecessor.nil?
list
end
return @predecessor_array
end
def add_predecessor(t)
return if t.nil?
@predecessor_array = predecessors
@predecessor_array << t
end
@ -310,8 +296,53 @@ class Todo < ActiveRecord::Base
self[:notes] = value
end
# Rich Todo API
# XML API fixups
def predecessor_dependencies=(params)
value = params[:predecessor]
return if value.nil?
# for multiple dependencies, value will be an array of id's, but for a single dependency,
# value will be a string. In that case convert to array
value = [value] unless value.class == Array
value.each { |ele| add_predecessor(self.user.todos.find_by_id(ele.to_i)) unless ele.blank? }
end
alias_method :original_context=, :context=
def context=(value)
if value.is_a? Context
self.original_context=(value)
else
c = Context.find_by_name(value[:name])
c = Context.create(value) if c.nil?
self.original_context=(c)
end
end
alias_method :original_project, :project
def project
original_project.nil? ? Project.null_object : original_project
end
alias_method :original_project=, :project=
def project=(value)
if value.is_a? Project
self.original_project=(value)
elsif !(value.nil? || value.is_a?(NullProject))
p = Project.find_by_name(value[:name])
p = Project.create(value) if p.nil?
self.original_project=(p)
else
self.original_project=value
end
end
# used by the REST API. <tags> will also work, this is renamed to add_tags in TodosController::TodoCreateParamsHelper::initialize
def add_tags=(params)
tag_with params[:tag].inject([]) { |list, value| list << value[:name] } unless params[:tag].nil?
end
# Rich Todo API
def self.from_rich_message(user, default_context_id, description, notes)
fields = description.match(/([^>@]*)@?([^>]*)>?(.*)/)
description = fields[1].strip

View file

@ -7,10 +7,8 @@ if not @not_done.empty?
%>
<h2><%=@context.name%></h2>
<ul class="c">
<table cellpadding="0" cellspacing="0" border="0" class="c">
<%= render :partial => "todos/mobile_todo",
:collection => @not_done,
:locals => { :parent_container_type => "context" }-%>
</table>
</ul>
<% end -%>

View file

@ -7,13 +7,12 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="initial-scale = 1.0" />
<meta name="viewport" content="width=device-width, user-scalable=no">
<%= stylesheet_link_tag "mobile", :media => 'handheld,all' %>
<title><%= @page_title %></title>
</head><body>
<% if !(@new_mobile || @edit_mobile)
if current_user && !current_user.prefs.nil? -%>
<h1><span class="count" id="badge_count"><%= @down_count %></span> <%=
<% if current_user && !current_user.prefs.nil? -%>
<div id="topbar"><h1><% if @down_count -%><span class="count" id="badge_count"><%= @down_count %></span><% end -%> <%=
l(Date.today, :format => current_user.prefs.title_date_format) -%></h1>
<div class="nav">
<%= (link_to(t('layouts.mobile_navigation.new_action'), new_todo_path(new_todo_params))+" | ") unless @new_mobile -%>
@ -21,10 +20,9 @@
<%= (link_to(t('layouts.mobile_navigation.contexts'), contexts_path(:format => 'm'))+" | ") -%>
<%= (link_to(t('layouts.mobile_navigation.projects'), projects_path(:format => 'm'))+" | ") -%>
<%= (link_to(t('layouts.mobile_navigation.starred'), {:action => "tag", :controller => "todos", :id => "starred.m"})) -%>
<% end
end -%><%= render_flash -%>
</div>
<%= yield -%>
<% end -%>
</div></div>
<div id="content"><%= render_flash -%><%= yield -%></div>
<hr/><% if current_user && !current_user.prefs.nil? -%>
<div class="nav">
<%= (link_to(t('layouts.mobile_navigation.logout'), logout_path(:format => 'm')) +" | ") -%>

View file

@ -1,4 +1,17 @@
replace_project_with_edit_form();
function html_for_edit_form() {
return "<%= escape_javascript(render(:partial => 'project_form', :object => @project)) %>";
}
function show_edit_form() {
$('div#<%=dom_id(@project, 'edit')%>').html(html_for_edit_form());
$('div#<%=dom_id(@project, 'edit')%>').fadeIn(500);
$('div#project_name').editable('disable');
}
function set_focus() {
$('input.project-name').focus();
}
function replace_project_with_edit_form() {
$('div#<%=dom_id(@project)%>').fadeOut(250, function() {
@ -8,15 +21,5 @@ function replace_project_with_edit_form() {
});
}
function show_edit_form() {
$('div#<%=dom_id(@project, 'edit')%>').html(html_for_edit_form());
$('div#<%=dom_id(@project, 'edit')%>').fadeIn(500);
}
function set_focus() {
$('input.project-name').focus();
}
function html_for_edit_form() {
return "<%= escape_javascript(render(:partial => 'project_form', :object => @project)) %>"
}
replace_project_with_edit_form();

View file

@ -4,24 +4,34 @@
<% unless @project.description.blank? -%>
<div class="project_description"><%= sanitize(@project.description) %></div>
<% end -%>
<%= render :partial => "todos/mobile_todo", :collection => @not_done, :locals => { :parent_container_type => "project" }%>
<ul class="c">
<%= render :partial => "todos/mobile_todo",
:collection => @not_done,
:locals => { :parent_container_type => "project" }%>
</ul>
<h2><%= t('projects.deferred_actions')%></h2>
<% if @deferred.empty? -%>
<%= t('projects.deferred_actions_empty') %>
<% else -%>
<%= render :partial => "todos/mobile_todo", :collection => @deferred, :locals => { :parent_container_type => "project" }%>
<% end
<ul class="c">
<%= render :partial => "todos/mobile_todo",
:collection => @deferred,
:locals => { :parent_container_type => "project" }%>
</ul><% end
-%>
<h2><%= t('projects.completed_actions')%></h2>
<% if @done.empty? -%>
<%= t('projects.completed_actions_empty') %>
<% else -%>
<%= render :partial => "todos/mobile_todo", :collection => @done, :locals => { :parent_container_type => "project" }%>
<% end %>
<ul class="c">
<%= render :partial => "todos/mobile_todo",
:collection => @done,
:locals => { :parent_container_type => "project" }%>
</ul><% end %>
<h2><%= t('projects.notes') %></h2>
<% if @project.notes.empty? -%>
<%= t('projects.notes_empty') %>
<% else -%><%= render :partial => "notes/mobile_notes_summary", :collection => @project.notes %>
<% end -%>
<h2><%= t('projects.settings') %></h2>
<%= t('projects.state', :state => project.aasm_current_state.to_s) %>. <%= @project_default_context %>
<%= t('projects.state', :state => project.aasm_current_state.to_s) %>. <%= @project_default_context %>

View file

@ -1,4 +1,3 @@
<label>Edit Recurring Todo</label><br/>
<div class="recurring_container">
<% #form_remote_tag(
#url => recurring_todo_path(@recurring_todo), :method => :put,
@ -99,17 +98,5 @@
<%= radio_button_tag('recurring_todo[recurring_target]', 'show_from_date', @recurring_todo.target == 'show_from_date', {:tabindex => next_tab_index})%> <%= t('todos.recurrence.from_tickler') %><br/>
<br/>
</div>
<div class="recurring_submit_box">
<div class="widgets">
<button type="submit" class="positive" id="recurring_todo_edit_action_submit" tabindex="<%=next_tab_index%>">
<%=image_tag("accept.png", :alt => "") %>
<%= t('common.update') %>
</button>
<button type="button" class="positive" id="recurring_todo_edit_action_cancel" tabindex="<%=next_tab_index%>">
<%=image_tag("cancel.png", :alt => "") %>
<%= t('common.cancel') %>
</button>
</div>
</div>
<% end %>
</div>

View file

@ -1,8 +1,9 @@
<% @recurring_todo = recurring_todo -%>
<div id="<%= dom_id(@recurring_todo)%>" class="recurring_todo item-container">
<%= recurring_todo_remote_delete_icon %> <%= recurring_todo_remote_edit_icon -%>
<%= recurring_todo_remote_star_icon %> <%= recurring_todo_remote_toggle_checkbox -%>
<%= recurring_todo_remote_delete_icon %>
<%= recurring_todo_remote_edit_icon -%>
<%= recurring_todo_remote_star_icon %>
<%= recurring_todo_remote_toggle_checkbox -%>
<div class="rec_description">
<span class="todo.descr"><%= sanitize(recurring_todo.description) %></span> <%= recurring_todo_tag_list %>
<span class='recurrence_pattern'>

View file

@ -1,10 +1,10 @@
<%- reset_tab_index %>
<div class="recurring_container">
<%
<%
form_for(@new_recurring_todo, :html=> { :id=>'recurring-todo-form-new-action', :name=>'recurring_todo', :class => 'inline-form' }) do
-%>
<div id="error_status"><%= error_messages_for("item", :object_name => 'action') %></div>
<div id="recurring_todo_form_container">
<div id="recurring_todo">
<label for="recurring_todo_description"><%= Todo.human_attribute_name('description') %></label><%=
@ -14,12 +14,12 @@
<label for="recurring_todo_project_name"><%= Todo.human_attribute_name('project') %></label>
<input id="recurring_todo_project_name" name="project_name" autocomplete="off" tabindex="<%=next_tab_index%>" size="30" type="text" value="" />
<div class="page_name_auto_complete" id="project_list" style="display:none"></div>
<label for="recurring_todo_context_name"><%= Todo.human_attribute_name('context') %></label>
<input id="recurring_todo_context_name" name="context_name" autocomplete="off" tabindex="<%=next_tab_index%>" size="30" type="text" value="<%= current_user.contexts.first.name unless current_user.contexts.first.nil?%>" />
<div class="page_name_auto_complete" id="context_list" style="display:none"></div>
<div class="page_name_auto_complete" id="context_list" style="display:none"></div>
<label for="tag_list"><%= "#{Todo.human_attribute_name('tags')} #{t('shared.separate_tags_with_commas')}"%></label>
<%= text_field_tag "tag_list", nil, :size => 30, :tabindex => next_tab_index -%>
<%= text_field_tag "tag_list", nil, :size => 30, :tabindex => next_tab_index -%>
</div>
</div>
<div id="recurring_period_id">
@ -88,17 +88,6 @@
<%= radio_button_tag('recurring_todo[recurring_target]', 'show_from_date', false, {:tabindex => next_tab_index})%> <%= t('todos.recurrence.from_tickler') %><br/>
<br/>
</div>
<div class="recurring_submit_box">
<div class="widgets">
<button type="submit" class="positive" id="recurring_todo_new_action_submit" tabindex="<%=next_tab_index%>">
<%=image_tag("accept.png", :alt => "") %>
<%= t('common.create') %>
</button>
<button type="button" class="positive" id="recurring_todo_new_action_cancel" tabindex="<%=next_tab_index%>">
<%=image_tag("cancel.png", :alt => "") %>
<%= t('common.cancel') %>
</button>
</div>
</div>
<% end %>
</div>

View file

@ -1,8 +1,8 @@
<% if @saved -%>
TracksPages.page_notify('notice', "<%=@status_message%>", 5);
RecurringTodosPage.toggle_overlay();
add_recurring_todo_to_active_container();
replace_form_with_empty_form();
$( "#new-recurring-todo" ).dialog( "close" );
TracksPages.set_page_badge(<%= @down_count %>);
<% else -%>
TracksPages.show_errors(html_for_error_messages());

View file

@ -1,9 +1,6 @@
$('#new-recurring-todo').hide();
$('#edit-recurring-todo').html(html_for_edit_form());
$('#edit-recurring-todo').show();
RecurringTodosPage.toggle_overlay();
enable_rich_interaction();
$('#edit-recurring-todo').dialog( "open" );
function html_for_edit_form() {
return "<%= escape_javascript(render(:partial => 'edit_form')) %>"
return "<%= escape_javascript(render(:partial => 'edit_form')) %>";
}

View file

@ -8,7 +8,7 @@
<%= render :partial => @recurring_todos %>
</div>
</div>
<div class="container" id="completed_recurring_todos_container">
<div class=add_note_link><%= link_to "Show all", done_recurring_todos_path%></div>
<h2><%= t('common.last') %> <%= t('todos.completed_recurring') %></h2>
@ -26,12 +26,9 @@
</div>
</div>
<div id="overlay">
<div id="new-recurring-todo" class="new-form">
<label><%= t('todos.add_new_recurring') %></label><br/>
<%= render :partial => "recurring_todo_form" %>
</div>
<div id="edit-recurring-todo" class="edit-form" style="display:none">
<div class='placeholder'>This should not be visible</div>
</div>
<div id="new-recurring-todo" class="new-form" title="<%= t('todos.add_new_recurring') %>">
<%= render :partial => "recurring_todo_form" %>
</div>
<div id="edit-recurring-todo" class="edit-form" style="display:none" title="<%= t('todos.edit_recurring_todo') %>">
<div class='placeholder'>This should not be visible</div>
</div>

View file

@ -1,5 +1,5 @@
<%- if @saved -%>
RecurringTodosPage.toggle_overlay();
$( "#edit-recurring-todo" ).dialog( "close" );
TracksPages.page_notify('notice', text_for_status_message(), 5);
replace_old_recurring_todo_with_updated();
<%- else -%>

View file

@ -4,37 +4,34 @@
<%= error_messages_for("todo") %>
</span>
<% this_year = current_user.time.to_date.strftime("%Y").to_i -%>
<% if parent_container_type == 'show_mobile' -%>
<p><label for="todo_done"><%= t('todos.done') %></label>&nbsp;<%= check_box_tag("done", 1, @todo && @todo.completed?, "tabindex" => 1, "onClick" => "document.mobileEdit.submit()") %></p>
<% end -%>
<h2><label for="todo_description"><%= t('common.description') %></label></h2>
<%= text_field( "todo", "description", "tabindex" => 2, "maxlength" => 100, "size" => 50) %>
<h2><label for="todo_notes"><%= t('common.notes') %></label></h2>
<%= text_area( "todo", "notes", "cols" => 40, "rows" => 3, "tabindex" => 3) %>
<%= text_field( "todo", "description", "tabindex" => 1, "maxlength" => 100, "size" => 50) %>
<h2><label for="tag_list"><%= t('todos.tags') %></label></h2>
<%= text_field_tag "tag_list", @tag_list_text, :size => 50, :tabindex => 2 %>
<h2><label for="todo_context_id"><%= t('common.context') %></label></h2>
<%= unless @mobile_from_context
collection_select( "todo", "context_id", @contexts, "id", "name", {}, {"tabindex" => 4} )
collection_select( "todo", "context_id", @contexts, "id", "name", {}, {"tabindex" => 3} )
else
select_tag("todo[context_id]", options_from_collection_for_select(
@contexts, "id", "name", @mobile_from_context.id),
{"id" => :todo_context_id, :tabindex => 4} )
{"id" => :todo_context_id, :tabindex => 3} )
end %>
<h2><label for="todo_project_id"><%= t('common.project') %></label></h2>
<%= unless @mobile_from_project
collection_select( "todo", "project_id", @projects, "id", "name",
{:include_blank => t('todos.no_project')}, {"tabindex" => 5} )
{:include_blank => t('todos.no_project')}, {"tabindex" => 4} )
else
# manually add blank option since :include_blank does not work
# with options_from_collection_for_select
select_tag("todo[project_id]", "<option value=\"\"></option>"+options_from_collection_for_select(
@projects, "id", "name", @mobile_from_project.id),
{"id" => :todo_project_id, :tabindex => 5} )
{"id" => :todo_project_id, :tabindex => 4} )
end %>
<h2><label for="tag_list"><%= t('todos.tags') %></label></h2>
<%= text_field_tag "tag_list", @tag_list_text, :size => 50, :tabindex => 6 %>
<h2><label for="todo_notes"><%= t('common.notes') %></label></h2>
<%= text_area( "todo", "notes", "cols" => 40, "rows" => 3, "tabindex" => 5) %>
<h2><label for="todo_due"><%= t('todos.due') %></label></h2>
<%= date_select("todo", "due", {:order => [:day, :month, :year],
:start_year => this_year, :include_blank => '--'}, :tabindex => 7) %>
:start_year => this_year, :include_blank => '--'}, :tabindex => 6) %>
<h2><label for="todo_show_from"><%= t('todos.show_from') %></label></h2>
<%= date_select("todo", "show_from", {:order => [:day, :month, :year],
:start_year => this_year, :include_blank => true}, :tabindex => 8) %>
:start_year => this_year, :include_blank => true}, :tabindex => 7) %>

View file

@ -2,4 +2,12 @@
<p><%= t('todos.no_incomplete_actions') %></p>
<% else -%>
<%= render :partial => "contexts/mobile_context", :collection => @contexts_to_show -%>
<% end -%>
<% end -%>
<% unless @done.nil? -%>
<div id="completed_container">
<h2><%= t('todos.completed_actions') %></h2>
<ul class="c">
<%= render :partial => "todos/mobile_todo", :collection => @done %>
</ul>
</div>
<% end %>

View file

@ -1,21 +1,13 @@
<% @todo = mobile_todo
if mobile_todo.starred?
bullet = "<span class=\"star\">"+image_tag("menustar_small.gif")+"</span>"
li_class = " class=\"star\""
else
bullet = "<span class=\"r\">&raquo;&nbsp;</span>"
li_class = ""
end -%>
<li id="<%= dom_id(mobile_todo) %>" <%= li_class %>><%= bullet %><%
if mobile_todo.completed?
-%><span class="m_t_d">
<% else
-%><span class="m_t">
<% end -%>
<% @todo = mobile_todo -%>
<li id="<%= dom_id(mobile_todo) %>" >
<% remote_mobile_checkbox(mobile_todo) %>
<%= date_span -%> <%= link_to mobile_todo.description, todo_path(mobile_todo, :format => 'm') -%>
<% unless mobile_todo.notes.blank? %>
<%= link_to(image_tag("mobile_notes.png", :border => "0"), mobile_todo_show_notes_path(mobile_todo, :format => 'm')) -%>
<% end %>
<% if mobile_todo.starred? %>
<%= image_tag("menustar_small.gif", :border => "0") -%>
<% end %>
<% if parent_container_type == 'context' or parent_container_type == 'tag' -%>
<%= "<span class=\"prj\"> (" +
link_to(mobile_todo.project.name, project_path(mobile_todo.project, :format => 'm')) +

View file

@ -2,4 +2,4 @@
<%= link_to @todo.description, todo_path(@todo, :format => 'm') -%>
<h2><%= t('todos.notes') %></h2>
<%= format_note(@todo.notes) %>
<%= link_to t('common.back'), @return_path %>
<%= link_to t('common.back'), @return_path %>

View file

@ -8,18 +8,18 @@
<%= render :partial => "contexts/mobile_context", :collection => @contexts_to_show -%>
<h2><%= t('todos.deferred_actions_with', :tag_name=> @tag_title) %></h2>
<% unless (@deferred.nil? or @deferred.size == 0) -%>
<table cellpadding="0" cellspacing="0" border="0">
<ul class="c">
<%= render :partial => "todos/mobile_todo", :collection => @deferred, :locals => { :parent_container_type => "tag" } -%>
</table>
</ul>
<% else -%>
<%= t('todos.no_deferred_actions_with', :tag_name => @tag_title) %>
<% end -%>
<h2><%= t('todos.completed_actions_with', :tag_name => @tag_title) %></h2>
<% unless (@done.nil? or @done.size == 0) -%>
<table cellpadding="0" cellspacing="0" border="0">
<ul class="c">
<%= render :partial => "todos/mobile_todo", :collection => @done, :locals => { :parent_container_type => "tag" } %>
</table>
</ul>
<% else -%>
<%= t('todos.no_completed_actions_with', :tag_name => @tag_title) %>
<% end -%>
</div>
</div>

945
config/locales/cz.yml Executable file
View file

@ -0,0 +1,945 @@
---
cz:
layouts:
toggle_notes: Zobrazit/skrýt poznámky
toggle_contexts: "Přepnout sbalené kontexty"
toggle_contexts_title: "Zobrazí/skryje sbalené kontexty"
next_actions_rss_feed: RSS feed aktuálních úkolů
toggle_notes_title: Zobrazí/skryje všechny poznámky
mobile_navigation:
new_action: 0-Nový úkol
logout: Odhlásit
feeds: Feedy
starred: 4-Hvězdičky
projects: 3-Projekty
tickler: Tickler
contexts: 2-Kontexty
home: 1-Domů
navigation:
manage_users_title: Přidat nebo smazat uživatele
recurring_todos: Opakující se úkoly
api_docs: REST API Dokumenty
feeds: Feedy
starred: S hvězdou
notes_title: Zobrazit všechny poznámky
review_title: Provést revizi
stats: Statistiky
tickler_title: Tickler
manage_users: Správa uživatelů
export_title: Import a export dat
preferences: Nastavení
integrations_: Integrovat Tracks
feeds_title: Seznam dostupných feedů
calendar_title: Kalendář datovaných úkolů
completed_tasks: Hotové
stats_title: Zobrazí statistiky úkolů
tickler: Tickler
home_title: Domů
starred_title: Zobrazí úkoly s hvězdičkou
recurring_todos_title: Správa opakovaných úkolů
view: Ukázat
organize: Správa
completed_tasks_title: Hotové úkoly
home: Domů
export: Export
contexts_title: Kontexty
calendar: Kalendář
projects_title: Projekty
search: Hledat
preferences_title: Zobrazí možnosti nastavení
integrations:
opensearch_description: Prohledat Tracks
applescript_next_action_prompt: "Popis úkolu:"
gmail_description: Gadget pro Tracks do Gmailu
applescript_success_after_id: vytvořen
applescript_success_before_id: Nový úkol s ID
number:
format:
separator: .
precision: 3
delimiter: ","
human:
format:
precision: 1
delimiter: ""
storage_units:
format: "%n %u"
units:
kb: KB
tb: TB
gb: GB
byte:
one: Byte
other: Bytů
mb: MB
percentage:
format:
delimiter: ""
currency:
format:
format: "%u%n"
unit: $
separator: .
precision: 2
delimiter: ","
precision:
format:
delimiter: ""
common:
back: Zpět
third: Třetí
recurring_todos: Opakované úkoly
actions: Úkoly
add: Přidat
previous: Předchozí
logout: Odhlásit
go_back: Zpět
optional: volitelné
week: týden
cancel: Zrušit
none: Žádný
second: Druhý
month: měsíc
server_error: Nastala chyba na serveru.
forum: Fórum
notes: Poznámky
review: Revize
last: Poslední
projects: Projekty
action: Úkol
project: Projekt
ok: Ok
contribute: Přispět
website: Webová stránka
first: První
numbered_step: Krok %{number}
sort:
by_task_count_title: Řadit podle počtu úkolů
by_task_count_title_confirm: Určitě chcete řadit tyto projekty podle počtu úkolů? Stávající pořadí bude ztraceno.
alphabetically: Abecedně
alphabetically_confirm: Určitě chcete řadit tyto projekty abecedně? Stávající pořadí bude ztraceno.
alphabetically_title: Seřadit projekty abecedně
sort: Řadit
by_task_count: Podle počtu úkolů
fourth: Čtvrtý
create: Vytvořit
months: měsíce
contexts: Kontexty
errors_with_fields: "Nastaly potíže s následujícími políčky:"
next: Další
todo: úkol
context: Kontext
drag_handle: CHYŤ MĚ
description: Popis
bugs: Chyby
update: Uložit
forth: Čtvrtý
weeks: týdny
wiki: Wiki
email: Email
search: Hledat
ajaxError: Chyba čtení ze serveru
data:
import_successful: Import byl úspěšný.
import_errors: Při importu došlo k chybám
models:
project:
feed_title: Projekty
feed_description: Všechny projekty uživatele %{username}
todo:
error_date_must_be_future: datum musí být v budoucnosti
user:
error_context_not_associated: Kontext %{context} nepatří uživateli %{user}.
error_project_not_associated: Projekt %{project} nepatří uživateli %{user}.
preference:
due_on: Plánováno na %{date}
due_in: Plánováno za %{days} dní
due_styles:
- Plánováno za ___ dní
- Plánováno na _______
activerecord:
attributes:
project:
name: Název
default_tags: Výchozí štítky
default_context_name: Výchozí kontext
description: Popis
todo:
predecessors: Závisí na
show_from: Zobrazovat od
notes: Poznámky
project: Projekt
context: Kontext
description: Popis
due: Plánováno na
user:
last_name: Jméno
first_name: Příjmení
preference:
show_hidden_projects_in_sidebar: Zobrazovat skryté projekty v sidebaru
date_format: Formát data
show_hidden_contexts_in_sidebar: Zobrazovat skryté kontexty v sidebaru
mobile_todos_per_page: Úkolů na stránku (mobilní zobrazení)
verbose_action_descriptors: Ukecané popisovače úkolů
staleness_starts: Jako prošlé označit projekty starší než
review_period: Interval revize projektů
sms_context: Výchozí emailový kontext
title_date_format: Formát data nadpisu
show_number_completed: Počet hotových úkolů k zobrazení
refresh: Interval obnovení stránky (v minutách)
week_starts: Začátek týdne
last_name: Příjmení
locale: Lokále
due_style: Zobrazení datovaných úkolů
time_zone: Časové pásmo
show_project_on_todo_done: Po splnění úkolu přejít na projekt
sms_email: SMS email
first_name: Jméno
show_completed_projects_in_sidebar: Zobrazovat hotové projekty v sidebaru
errors:
messages:
greater_than_or_equal_to: musí být větší nebo rovno %{count}
record_invalid: "Problém s daty: %{errors}"
confirmation: se neshoduje s ověřením
less_than_or_equal_to: musí být menší nebo rovno %{count}
blank: nesmí být prázdné
invalid: nesmí obsahovat čárku (',')
exclusion: je rezervované
odd: must be odd
even: must be even
too_short: je příliš krátké (minimum je %{count} znaků)
empty: nesmí být prázdné
wrong_length: nemá správnou délku (má mít %{count} znaků)
less_than: musí být menší než %{count}
greater_than: musí být větší než %{count}
equal_to: se musí rovnat %{count}
accepted: musí být akceptováno
too_long: je příliš dlouhé (maximum je %{count} znaků)
taken: už bylo zabráno
inclusion: není na seznamu
not_a_number: není číslo
models:
project:
attributes:
name:
blank: projekt musí mít název
too_long: název projektu musí být kratší než 256 znaků
taken: už existuje
full_messages:
format: "%{attribute} %{message}"
template:
body: "Nastaly potíže s následujícími políčky:"
header:
one: jedna chyba brání uložení tohoto objektu %{model}
other: "%{count} chyb brání uložení tohoto objektu %{model}"
stats:
tag_cloud_title: Mrak štítků pro všechny úkly
tag_cloud_description: Tento mrak zahrnuje štítky všech úkolů (hotových, nehotových, viditelných i skrytých)
tag_cloud_90days_title: Značky úkolů z posledních 90-ti dní
actions: Úkoly
totals_active_project_count: Znich %{count} je aktivních projeků
actions_last_year_legend:
number_of_actions: Počet úklolů
months_ago: měsíců zpět
totals_first_action: Od vašeho prvního úkolu %{date}
actions_avg_completion_time: Pro všechny vaše hotové úkoly je průměrný čas dokončení %{count} dní.
top10_longrunning: 10 nejdéle běžících projektů
actions_dow_30days_title: Dny v týdnu (posleních 30 dní)
legend:
actions: Úkoly
number_of_days: Před kolika dny
number_of_actions: Počet úkolů
day_of_week: Den v týdnu
percentage: Podíl
running_time: Čas k dokončení úkolu (týdny)
months_ago: měsíců zpět
current_running_time_of_incomplete_visible_actions: Aktuální čas běhu nedokončených viditelných úkolů
totals_deferred_actions: z nichž %{count} jsou odložené úkoly v Ticlkeru
running_time_legend:
actions: Úkoly
percentage: Podíl
weeks: Čas běhu úkolu (týdny). Klepněte na sloupec pro další info
totals_action_count: máte celkem %{count} úkolů
totals_incomplete_actions: Máte %{count} nehotových úkolů
totals_unique_tags: Z těchto štítků je %{count} unikátních.
actions_avg_completed_30days: a dokončeno průměrně %{count} úkolů za den.
top5_contexts: Top 5 kontextů
actions_lastyear_title: Úkoly za posledních 12 měsíců
totals_actions_completed: "%{count} z nich je hotových."
totals_context_count: Máte %{count} kontextů.
totals_visible_context_count: Z nich je %{count} viditelných kontextů
totals_blocked_actions: "%{count} je závislých na dokončení jiných akcí."
projects: Projekty
action_completion_time_title: Čas dokončení (všechny hotové úkoly)
actions_last_year: Úkoly v posledním roce
actions_min_max_completion_days: Maximum/minimum dní na dokončení je %{min}/%{max}.
tags: Štítky
actions_min_completion_time: Minimální čas k dokončení je %{time}.
no_tags_available: žádné štítky nejsou definovány
actions_day_of_week_title: Den v týdnu (všechny úkoly)
totals_project_count: Máte %{count} projektů.
running_time_all: Aktuální čas běhu všech nehotových úkolů
actions_30days_title: Úkoly za posledních 30 dní
time_of_day: Denní doba (všechny úkoly)
totals_hidden_project_count: "%{count} je skrytých"
tod30: Denní doba (posledních 30 dní)
tag_cloud_90days_description: Tento mrak zahrnuje štítky úkolů, které byly vytvořeny nebo dokončeny v posledních 90-ti dnech.
more_stats_will_appear: Další statistiky se zobrazí až přibyde více úkolů.
top5_visible_contexts_with_incomplete_actions: Top 5 viditelných kontextů s nehotovými úkoly
actions_further: " a dále"
totals_tag_count: Na akcích je umístěno %{count} štítků.
top10_projects_30days: Top 10 projektů za posledních 30 dní
spread_of_running_actions_for_visible_contexts: Distribuce běžících úkolů do viditelných kontextů
actions_selected_from_week: "Úkoly vybrané z týdne "
spread_of_actions_for_all_context: Distribuce všech úkolů do kontextů
click_to_show_actions_from_week: Klepněte %{link} pro zobrazení úkolů z týdne %{week} a dalších.
other_actions_label: (ostatní)
top10_projects: Top 10 projektů
totals_completed_project_count: a %{count} je hotových projektů.
actions_avg_created: Za posledních 12 měsíců bylo vytvořeno průměrně %{count} úkolů
click_to_return: Klepněte %{link} pro návrat ke statistikám.
actions_avg_completed: a uzavřeno průměrně %{count} úkolů za měsíc.
totals: Celkem
time_of_day_legend:
number_of_actions: Počet úkolů
time_of_day: Denní doba
contexts: Kontexty
click_to_return_link: zde
totals_hidden_context_count: a %{count} skrytých kontextů.
labels:
month_avg_completed: "%{months} měsíční průměr hotových"
completed: Completed
month_avg_created: "%{months} měsíční průměr vytvořených"
avg_created: průměrně vytvořeno
avg_completed: průměrně uzavřeno
created: Vytvořeno
running_time_all_legend:
actions: Úkoly
percentage: Podíl
running_time: Čas běhu úkolů (týdny). Klepněte na sloupec pro další info
click_to_update_actions: Klepněte na sloupec v grafu pro zobrazení detailů níže.
no_actions_selected: Nejsou vybrány žádné úkoly.
actions_actions_avg_created_30days: Za posledních 30 dní bylo vytvořeno průměrně %{count} úkolů
tod30_legend:
number_of_actions: Počet úkolů
time_of_day: Denní doba
action_selection_title: TRACKS::výběr úkolů
todos:
show_from: Zobrazovat od
error_starring_recurring: Nebylo možno ohvězdičkovat opakovaný úkol \'%{description}\'
recurring_action_deleted: Úkol byl smazán. Protože jde o opakovaný úkol, byl vložen nový úkol
completed_actions: Hotové úkoly
completed_recurring: Hotové opakované úkoly
added_new_next_action: Přidán nový úkol
completed_rest_of_previous_month: Uzavřeno ve zbytku minulého měsíce
blocked_by: Čeká na %{predecessors}
star_action: Oznařit hvězdičkou
completed_recurrence_completed: Bylo smazáno poslední opakování opakovaného úkolu. Opakování dokončeno
defer_date_after_due_date: Datum zobrazení je až po plánovaném datu úkolu. Upravte datum úkolu před dalším pokusem o odpložení zobrazení.
unable_to_add_dependency: Nepodařilo se přidat závislost
done: Hotovo?
star_action_with_description: ohvězdičkovat úkol '%{description}'
tagged_with: označeno štítkem &lsquo;%{tag_name}&rsquo;
completed: Hotovo
no_deferred_actions_with: Žádné odložené úkoly se štítkem '%{tag_name}'
edit_action_with_description: Upravit úkol '%{description}'
no_hidden_actions: Žádné skryté úkoly
action_due_on: (úkol plánován na %{date})
remove_dependency: Odstranit závislost (nesmaže úkol)
archived_tasks_title: TRACKS::Archiv hotových úkolů
list_incomplete_next_actions: Zabrazí nehotové úkoly
tags: Štítky (oddělené čárkami)
action_deleted_success: Úkol byl úspěšně smazán
new_related_todo_created: Byl vytvořen nový úkol patřící do tohoto opakovaného úkolu
context_changed: Kontext byl změněn na %{name}
add_another_dependency: Přidat další závislost
mobile_todos_page_title: Všechny úkoly
delete_recurring_action_title: Smazat opakovaný úkol
removed_predecessor: Byl odstraněn %{successor} jako závislost pro %{predecessor}.
recurring_actions_title: TRACKS::Opakované úkoly
next_action_needed: Je potřeba zadat aspoň jeden úkol
action_saved: Úkol uložen
scheduled_overdue: Naplánováno zobrazení před %{days} dny
action_deleted_error: Nepodařilo se smazat úkol
edit_action: Upravit úkol
added_new_context: Přidán nový kontext
next_actions_description: "Filtr:"
list_incomplete_next_actions_with_limit: Zobrazuje posledních %{count} nedokončených úkolů
set_to_pending: "%{task} nastaven jako čekající"
added_new_project: Přidán nový projekt
next_actions_title_additions:
completed: hotové úkoly
due_today: dnes
due_within_a_week: během týdne
older_completed_items: ""
append_in_this_project: v tomto projektu
error_deleting_item: Nepodařilo se smazat položku %{description}
task_list_title: TRACKS::Úkoly
no_actions_due_this_week: Žádné úkoly plánovány na tento týden
no_deferred_pending_actions: Žádné odložené ani čekající úkoly
no_recurring_todos: Žádné opakované úkoly
error_completing_todo: Nepodařilo se ukončit / aktivovat opakovaný úkol %{description}
recurring_pattern_removed: Vzor opakování byl odstraněn z %{count}
convert_to_project: Vytvořit projekt
delete_recurring_action_confirm: Opravdu chcete smazat opakovaný úkol '%{description}'?
completed_last_day: Ukončené v posledních 24 hodinách
completed_more_than_x_days_ago: ""
show_in_days: Zobrazit za %{days} dní
no_project: --Žádný projekt--
error_saving_recurring: Nepodařilo se uložit opakovaný úkol \'%{description}\'
new_related_todo_created_short: vytvořen nový úkol
all_completed: Všechny hotové úkoly
feed_title_in_context: v kontextu '%{context}'
older_than_days: ""
completed_tagged_page_title: TRACKS::Hotové úkoly se štítkem '%{tag_name}'
edit: Upravit
pending: Čekající
completed_actions_with: Hotové úkoly se štítkem '%{tag_name}'
deleted_success: Úkol byl úspěšně smazán.
completed_tasks_title: TRACKS::Hotové úkoly
feed_title_in_project: v projektu '%{project}'
clear_due_date: Smazat plánované datum úkolu
hidden_actions: Skryté úkoly
error_removing_dependency: Nepodařilo se odstranit závislost
was_due_on_date: bylo plánováno na %{date}
show_on_date: Ukázat %{date}
recurrence_period: Interval opakování
deferred_actions_with: Odložené úkoly se štítkem '%{tag_name}'
recurring_deleted_success: Opakovaný úkol byl úspěšně smazán.
confirm_delete: Opravdu chcete smazat úkol '%{description}'?
deferred_tasks_title: TRACKS::Tickler
next_actions_title: Tracks - Úkoly
next_action_description: Popis úkolu
no_completed_actions_with: Žádné hotové úkoly se štítkem '%{tag_name}'
clear_show_from_date: Odstranit datum zobrazení
calendar_page_title: TRACKS::Kalendář
unresolved_dependency: Hodnota v poli 'závisí na' neodpovídá žádnému existujícímu úkolu. Hodnota bude ignorována. Pokračovat?
in_hidden_state: (skrytý)
show_today: Zobrazit Dnes
no_actions_found_title: Nenalezeny žádné úkoly
next_actions_due_date:
overdue_by: Prošlé %{days} den
due_today: Dnes
due_in_x_days: Za %{days} dní
overdue_by_plural: Prošlé %{days} dní
due_tomorrow: Zítra
completed_last_x_days: Uzavřené za posledních %{count} dní
no_actions_with: Žádné úkoly se štítkem '%{tag_name}'
defer_x_days:
one: Ukázat zítra
other: Ukázat za %{count} dní
added_new_next_action_singular: Přidán nový úkol
no_completed_actions: Žádné hotové úkoly.
feeds:
completed: "Hotové: %{date}"
due: "Plánováno na: %{date}"
deferred_pending_actions: Odložené/čekající úkoly
has_x_pending:
one: Jeden čekající úkol
other: %{count} čekajících úkolů
delete_action: Smazat úkol
error_deleting_recurring: Nepodařilo se smazat opakovaný úkol \'%{description}\'
recurring_todos: Opakované úkoly
delete: Smazat
cannot_add_dependency_to_completed_todo: Nelze přidat úkol jako závislost k hotovému úkolu!
drag_action_title: Přetáhnout na jiný úkol pro vytvoření závislosti
no_last_completed_actions: Žádné hotové úkoly
depends_on: Závisí na
tickler_items_due:
one: Jeden úkol v Tickleru je plánován dnes - obnovte stránku pro zobrazení.
other: "%{count} položek v Tickleru je plánováno na dnes tickler items are now due - obnovte stránku pro zobrazení."
action_marked_complete: Úkol <strong>'%{description}'</strong> byl označen jako <strong>%{completed}</strong>
completed_today: Dokončené dnes
added_new_next_action_plural: Úkoly byly přidány
new_related_todo_not_created_short: úkol nebyl vytvořen
completed_rest_of_week: Dokončené ve zbytku týdne
error_starring: Nepodařilo se označit úkol hvězdičkou '%{description}'
calendar:
get_in_ical_format: Kalendář ve formátu iCal
due_next_week: Plánované příští týden
no_actions_due_next_week: Na příští týden nejsou plánované žádné úkoly
due_this_week: Plánované ve zbytku týdne
due_today: Plánované dnes
no_actions_due_today: Na dnešek nejsou plánované žádné úkoly
due_next_month_and_later: Plánováno na %{month} a dále
no_actions_due_after_this_month: Na příští měsíc a dále nejsou plánované žádné úkoly
due_this_month: Plánováno na %{month}
no_actions_due_this_month: Na zbytek tohoto měsíce nejsou žádné úkoly
show_tomorrow: Zobrazit zítra
tagged_page_title: TRACKS::Se štítkem '%{tag_name}'
action_deferred: Úkol '%{description}' byl oldožen
recurrence:
ends_on_number_times: Končí po %{number} opakováních
ends_on_date: Končí %{date}
every_work_day: Každý pracovní den
recurrence_on_due_date: datum na které je úkol plánován
weekly_options: Nastavení pro týdenní opakované úkoly
weekly: Týdně
monthly_options: Nastavení pro měsíční opakované úkoly
starts_on: Začíná
daily_options: Nastavení pro denní opakované úkoly
monthly: Měsíčně
pattern:
month_names:
-
- Leden
- Únor
- Březen
- Duben
- Květen
- Červen
- Červenec
- Srpen
- Září
- Říjen
- Listopad
- Prosinec
third: třetí
every_n: každé %{n}
on_day_n: %{n}. den
second: druhý
every_xth_day_of_every_n_months: každý %{x} %{day} každých %{n_months}
from: od
weekly: každý týden
last: poslední
every_day: každý den
the_xth_day_of_month: %{x} %{day} měsíce %{month}
times: (%{number} opakování)
on_work_days: v pracovní dny
first: první
every_year_on: každý rok %{date}
day_names:
- neděle
- pondělí
- úterý
- středa
- čtvrtek
- pátek
- sobota
show: ukázat
fourth: čtvrtý
due: plánováno na
until: do
every_month: každý měsíc
show_option_always: stále
daily: Denně
yearly_every_x_day: Každý %{month} %{day}
recurrence_on_options: Nastavit opakování na
daily_every_number_day: Každých %{number} dní
show_options: Úkázat úkol
weekly_every_number_week: Každých %{number} týdnů
ends_on: Končí
show_days_before: "%{days} dní před plánovaným datem"
from_tickler: datum kdy úkol vypadne z Tickleru (není nastaveno plánované datum)
no_end_date: Nikdy
day_x_on_every_x_month: %{day}. den každý %{month}. měsíc
yearly_options: Nastavení pro roční opakované úkoly
yearly_every_xth_day: %{day} %{day_of_week} měsíce %{month}
monthly_every_xth_day: %{day} %{day_of_week} každý %{month}. měsíc
yearly: Ročně
no_completed_recurring: Žádné hotové opakované úkoly
added_dependency: Přidáno %{dependency} jako závislost.
no_deferred_actions: Žádné odložené úkoly.
all_completed_tagged_page_title: TRACKS::Hotové úkoly se štítkem %{tag_name}
completed_rest_of_month: Ukončené ve zbytku tohoto měsíce
recurrence_completed: Poslední opakování úkolu bylo označeno jako hotové. Opakovaný úkol je dokončený
error_toggle_complete: Nepodařilo se označit úkol jako hotový
no_actions_found: Žádné běžící úkoly.
in_pending_state: ve stavu čekající
due: Plánováno na
action_marked_complete_error: Úkol <strong>'%{description}'</strong> NEBYL označen jako <strong>%{completed} kvůli chybě na serveru.</strong>
depends_on_separate_with_commas: Závisí na (odděleno čárkami)
action_saved_to_tickler: Úkol byl uložen do Tickleru
recurring_action_saved: Opakovaný úkol byl uložen
completed_in_archive:
one: V archivu je hotový úkol.
other: V archivu je %{count} hotových úkolů.
to_tickler: do Tickleru
next_actions_description_additions:
completed: v posledních %{count} dnech
due_date: plánovano na %{due_date} nebo dříve
overdue: Spožděné úkoly
add_new_recurring: Vytvořit opakovaný úkol
edit_recurring_todo: Upravit opakovaný úkol
no_incomplete_actions: Žádné nehotové úkoly
notes:
delete_confirmation: Opravdu chcete smazat poznámku '%{id}'?
delete_item_title: Smazat položku
delete_note_title: Smazat poznámku '%{id}'
note_link_title: Zobrazit poznámku %{id}
show_note_title: Zobrazit poznámku
deleted_note: Smazat poznámku '%{id}'
edit_item_title: Upravit položku
note_location_link: "V:"
no_notes_available: "Žádné poznámky: přidejte poznámky ze stránek jednotlivých projektů."
note_header: Poznámka %{id}
delete_note_confirm: Opravdu chcete smazat poznámku '%{id}'?
states:
hidden_plural: Skryté
completed: Hotový
completed_plural: Hotové
visible_plural: Viditelné
visible: Viditelný
active_plural: Aktivní
hidden: Skrytý
active: Aktivní
review_plural: Nerevidované
review: Nerevidovaný
stalled_plural: Opuštěné
stalled: Opuštěný
blocked_plural: Blokované
blocked: Blokovaný
current_plural: Aktuální
current: Aktuální
projects:
was_marked_hidden: byl označen jako skrytý
edit_project_title: Upravit projekt
default_tags_removed_notice: Výchozí štítky byly odstraněny
default_context_set: Výchozí kontext %{default_context} byl nastaven
no_actions_in_project: Žádné aktivní úkoly
deferred_actions: Odložené úkoly projektu
all_completed_tasks_title: TRACKS::Hotové úkoly projektu '%{project_name}'
hide_form: Skrýt formulář
page_title: "TRACKS::Projekt: %{project}"
show_form_title: Nový projekt
list_completed_projects: TRACKS::Hotové projekty
to_new_project_page: přejít k novému projektu
no_notes_attached: Žádné poznámky
deferred_actions_empty: Žádné odložené úkoly
this_project: Tento projekt
project_state: Projekt je %{state}.
todos_append: v tomto projektu
no_last_completed_projects: Žádné hotové projekty
notes: Poznámky
no_last_completed_recurring_todos: Žádné hotové opakované úkoly
notes_empty: Žádné poznámky
no_projects: Žádné projekty
hide_form_title: Schovat formulář založení projektu
with_no_default_context: bez výchozího kontextu
delete_project: Smazat projekt
completed_actions_empty: V tomto projektu nejsou žádné hotové úkoly
show_form: Nový projekt
actions_in_project_title: Úkoly v tomto projetku
delete_project_confirmation: Opravdu chcete smazat projekt '%{name}'?
with_default_context: s výchozím kontextem '%{context_name}'
set_default_tags_notice: Nastavit výchozí šítky úkolů v tomto projektu %{default_tags}
is_active: je aktivní
settings: Nastavení
completed_projects: Hotové projetky
with_default_tags: a s '%{tags}' jako výchozí štítky
list_projects: TRACKS::Projekty
list_reviews: TRACKS::Revize
project_saved_status: Projekt byl uložen
add_project: Přidat projekt
add_note: Nová poznámka
completed_tasks_title: TRACKS::Hotové úkoly projektu '%{project_name}'
delete_project_title: Smaže projekt
hidden_projects: Skryté projekty
add_note_submit: Nová poznámka
was_marked_complete: byl označen jako hotový
completed_actions: Hotové úkoly tohoto projektu
default_context_removed: Výchozí kontext byl odstraněn
default_context: Výchozí kontext pro tento projekt je %{context}
status_project_name_changed: Jméno projektu bylo změněno
active_projects: Aktivní projekty
no_default_context: Tento projekt nemá výchozí kontext
with_no_default_tags: a nemá žádné výchozí značky
edit_project_settings: Upravit vlastnosti projektu
state: Tento projekt je %{state}
time:
am: am
formats:
default: "%a, %d %b %Y %H:%M:%S %z"
time: ""
short: "%d %b %H:%M"
month_day: "%B %d"
long: "%B %d, %Y %H:%M"
pm: pm
preferences:
open_id_url: Vaše OpenID URL je
staleness_starts_after: Zastarání nastává po %{days} dnech
change_identity_url: Změna URL identity
change_password: Změna hesla
password_changed: Heslo bylo změněno. Prosím znovu se přihlašte.
updated: Nastavení bylo uloženo
page_title: TRACKS::Nastavení
title: Vaše nastavení
token_description: Pešek (feedy a použití API)
is_false: "ne"
show_number_completed: Zobrazit %{number} hotových položek
page_title_edit: TRACKS::Editace nastavení
is_true: "ano"
edit_preferences: Editace nastavení
sms_context_none: žádný
generate_new_token: Generovat nového peška
token_header: Váš pešek
authentication_header: Vaše autentizace
current_authentication_type: Používáte autentizaci %{auth_type}
change_authentication_type: Změna typu autentizace
tabs:
authentication: Autentizace
tracks_behavior: Chování Tracks
profile: Profil
date_and_time: Datum a čas
generate_new_token_confirm: Opravdu? Nový pešek nahradí ten původní a způsobí nefunkčnost ve všech aplikacích, kde jej používáte.
errors:
user_unauthorized: "401 Neautorizováno: Jen administrátoři smí používat tuto funkci."
date:
month_names:
-
- Leden
- Únor
- Březen
- Duben
- Květen
- Červen
- Červenec
- Srpen
- Září
- Říjen
- Listopad
- Prosinec
abbr_day_names:
- Ne
- Po
- Út
- St
- Čt
-
- So
order:
- :rok
- :měsíc
- :den
formats:
only_day: ""
default: "%Y-%m-%d"
short: "%b %d"
month_day: ""
long: "%B %d, %Y"
longer: "%A %B %d, %Y"
day_names:
- Neděle
- Ponělí
- Úterý
- Středa
- Čtvrtek
- Pátek
- Sobota
abbr_month_names:
-
- Led
- Úno
- Bře
- Dub
- Kvě
- Čer
- Čec
- Srp
- Zář
- Říj
- Lis
- Pro
support:
array:
words_connector: ", "
last_word_connector: ", a "
two_words_connector: " a "
select:
prompt: Prosím vyberte
footer:
send_feedback: Poslat zpětnou vazbu na %{version}
shared:
multiple_next_actions: Úkoly (jeden na každém řádku)
hide_form: Schovat formulář
toggle_single: Přidat úkol
add_action: Přidat úkol
add_actions: Přidat úkoly
tags_for_all_actions: Značky (oddělené čárkami)
toggle_single_title: Přidat jeden nový úkol
project_for_all_actions: Projekt (pro všechny)
context_for_all_actions: Kontext (pro všechny)
toggle_multi: Přidat více úkolů
separate_tags_with_commas: oddělené čárkami
toggle_multi_title: Přepnout formulář zakládání jedoho/více úkolů
hide_action_form_title: Skrýt formulář pro založení nového úkolu
make_actions_dependent: Akce budou vzájemně závislé
users:
successfully_deleted_user: Uživatel %{username} byl úspěšně smazán
failed_to_delete_user: Nepodařilo se smazat uživatele %{username}
total_contexts: Kontextů celkem
first_user_heading: "Vítejte v TRACKS. Nejdříve je nutné vytvořit administrátorský účet:"
openid_url_verified: Identitní url %{url} bylo úspěšně ověřeno a nastavena autentizace pomocí OpenID.
auth_type_update_error: "Nepodařilo se změnit typ autentizace: %{error_messages}"
destroy_successful: Uživatel %{login} byl úspěšně zničen
new_token_generated: Nový pešek byl úspěšně vygenerován
total_projects: Projektů celkem
signup_successful: Registrace uživatele %{username} byla úspěšná.
change_password_submit: Změnit heslo
no_signups_title: TRACKS::Registrace není povolena
user_created: Uživatel byl vytvořen.
manage_users: Správa uživatelů
account_signup: Registrace uživatele
password_updated: Heslo bylo změněno.
desired_login: Uživatelské jméno
signup: Registrace
confirm_password: Potvrzení hesla
new_user_heading: "Registrace nového uživatele:"
auth_type_updated: Typ autentizace byl změněn.
total_actions: Úkolů celkem
change_password_title: TRACKS::Změna hesla
change_auth_type_title: TRACKS::Změna zůsobu autentizace
change_password_prompt: Pro změnu hesla zadejte nové hestlo do polí níže a stiskněte 'Změna hesla'.
password_confirmation_label: Potvrzení hesla
destroy_error: Nepodařilo se smazat uživatele %{login}
choose_password: Zvolte heslo
register_with_cas: S vaším uživatelským jménem z CASu
label_auth_type: Způsob autentizace
new_password_label: Nové heslo
you_have_to_reset_your_password: "Je nutné změnit heslo"
new_user_title: TRACKS::Přihlášení jako administrátor
destroy_user: Zničit uživatele
total_users_count: Máte celkem %{count} uživatelů
destroy_confirmation: "Varování: tato akce smaže uživatele '%{login}', všechny jeho úkoly, kontexty, projekty i poznámky. Opravdu chcete pokračovat?"
signup_new_user: Registrace nového uživatele
openid_ok_pref_failed: Vaše identitní URL %{url} bylo úspěšně ověřeno, ale došlo k problému při ukládání nastavení.
identity_url: URL identity
auth_change_submit: Změnit způsob přihlašování
change_authentication_type: Změna způsobu přihlašování
total_notes: Poznámek celkem
select_authentication_type: Vyberte nový způsob autentizace a stiskněte 'Změnit způsob přihlašování'.
sidebar:
list_name_active_contexts: Aktivní kontexty
list_name_active_projects: Aktivní projekty
list_empty: Nic
list_name_completed_projects: Hotové projekty
list_name_hidden_projects: Skryté projekty
list_name_hidden_contexts: Skryté kontexty
feedlist:
choose_context: Vyberte kontext jehož feed si přejete
actions_due_today: Akce plánované na dnes
rss_feed: RSS Feed
ical_feed: iCal feed
all_contexts: Všechny kontexty
legend: "Legenda:"
all_projects: Všechny projekty
choose_project: Vyberte projekt jehož feed si přejete
select_feed_for_project: Vyberte feed pro tento projekt
active_projects_wo_next: Aktivni projekty bez úkolů
project_needed: Musíte nejdříve založit aspoň jeden projekt
active_starred_actions: Všechny aktivní úkoly s hvězdičkou
select_feed_for_context: Select the feed for this context
projects_and_actions: Aktivní projekty a jejich úkoly
context_needed: Musíte nejdříve založit aspoň jeden kontext
actions_due_next_week: Úkoly plánované na příštích sedm dní
notice_incomplete_only: "Poznámka: všechny feedy obsahují jen nehotové úkoly."
all_actions: Všechny úkoly
actions_completed_last_week: Úkoly dokončené v posledních sedmi dnech
context_centric_actions: Feedy s aktivními úkoly podle kontextů
plain_text_feed: Prostý text
last_fixed_number: Posledních %{number} úkolů
project_centric: Feedy s aktivními úkoly podle projektu
contexts:
delete_context_title: Smazat kontext
all_completed_tasks_title: TRACKS::Hotové úkoly v kontextu '%{context_name}'
hide_form: Schovat formulář
show_form_title: Nový kontext
delete_context_confirmation: Opravdu chcete smazat kontext '%{name}'? Dojde ke smazání všech (opakovaných) úkolů z daného kontextu!
delete_context: Smazat kontext
edit_context: Upravit kontext
hide_form_title: Schovat formulář
context_hide: Schovat z úvodní stránky?
hidden_contexts: Schovat kontexty
no_contexts_active: Žádné aktivní kontexty
show_form: Nový kontext
visible_contexts: Viditelné kontexty
save_status_message: Kontext uložen
add_context: Vytvořit kontext
context_name: Náev kontextu
update_status_message: Název kontextu byl změněn
completed_tasks_title: TRACKS::Hotové úkoly v kontextu '%{context_name}'
new_context_post: "' bude také vytvořen. Opravdu?"
status_active: Kontext je aktivní
no_actions: Žádné aktivní úkoly v tomto kontextu
last_completed_in_context: v tomto kontextu (posledních %{number})
context_deleted: Kontext byl odstraněn '%{name}'
no_contexts_hidden: Žádné skryté kontexty
new_context_pre: Nový kontext '
status_hidden: kontext je skrytý
login:
login_cas: přejít na CAS
sign_in: Přihlásit se
openid_identity_url_not_found: Je nám líto, neexistuje uživatel s touto identitou (%{identity_url})
user_no_expiry: Neodhlšovat
cas_no_user_found: Nazdar, %{username}! Nemáte účet na Tracks.
cas_login: Přihlášení přes CAS
successful_with_session_info: "Přihlášení bylo úspěšné:"
please_login: Pro pokračování se prosím přihlšte do Tracks
cas_logged_in_greeting: Zdravíčko, %{username}! Byl jste autorizován.
cas_username_not_found: Bohužel neexistuje uživatel v CASu se jménem (%{username})
mobile_use_openid: "\xE2\x80\xA6nebo přihlášení s OpenID"
cas_create_account: Pro vytvoření účtu v CASu prosím pokračujte sem %{signup_link}
account_login: Přihlášení uživatele
cas_signup_link: Vyžádat účet
session_will_not_expire: sezení bylo nastaveno jako trvalé.
successful: Přihlášení úspěšné. Vítejte zpět!
option_separator: nebo
session_time_out: Sezení vypršelo. Prosím %{link}
session_will_expire: sezen vyprší za %{hours} hodin neaktivity.
login_standard: vraťte se ke standardnímu přihlášení
logged_out: You have been logged out of Tracks.
login_with_openid: přihlašte se se svým OpenID
unsuccessful: Přihlášení bylo úspěšné.
log_in_again: přihlašte se znovu.
datetime:
prompts:
minute: Minuta
second: Sekunda
month: Měsíc
hour: Hodina
day: Den
year: Rok
distance_in_words:
less_than_x_minutes:
one: méně než minuta
other: méně než %{count} minut
zero: méně než minuta
x_days:
one: 1 den
other: "%{count} dní"
almost_x_years:
one: almost 1 rok
other: skoro %{count} let
x_seconds:
one: 1 sekunda
other: "%{count} sekund"
about_x_hours:
one: about 1 hodina
other: přibližně %{count} hodin
less_than_x_seconds:
one: méně než 1 sekunda
other: mén než %{count} sekund
zero: méně než 1 sekunda
x_months:
one: 1 měsíc
other: "%{count} měsíců"
x_minutes:
one: 1 minuta
other: "%{count} minut"
about_x_years:
one: about 1 rok
other: přibližně %{count} let
about_x_months:
one: about 1 měsíc
other: přibližně %{count} měsíců
over_x_years:
one: přes rok
other: přes %{count} let
half_a_minute: půl minuty
search:
contexts_matching_query: Nalezené kontexty
tags_matching_query: Nalezené štítky
notes_matching_query: Nalezené poznámky
no_results: Na váš dotaz nebylo nic nalezeno.
todos_matching_query: Nalezené úkoly
projects_matching_query: Nalezené projekty

File diff suppressed because it is too large Load diff

View file

@ -7,14 +7,14 @@ en:
next_actions_rss_feed: RSS feed of next actions
toggle_notes_title: Toggle all notes
mobile_navigation:
new_action: 0-New action
new_action: New
logout: Logout
feeds: Feeds
starred: 4-Starred
projects: 3-Projects
starred: Starred
projects: Projects
tickler: Tickler
contexts: 2-Contexts
home: 1-Home
contexts: Contexts
home: Home
navigation:
manage_users_title: Add or delete users
recurring_todos: Repeating todos
@ -560,6 +560,7 @@ en:
due_date: with a due date %{due_date} or earlier
overdue: Overdue
add_new_recurring: Add a new recurring action
edit_recurring_todo: Edit repeating action
no_incomplete_actions: There are no incomplete actions
notes:
delete_confirmation: Are you sure that you want to delete the note '%{id}'?

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -25,7 +25,7 @@ ActionController::Routing::Routes.draw do |map|
map.resources :notes
map.resources :todos,
:member => {:toggle_check => :put, :toggle_star => :put, :defer => :put},
:member => {:toggle_check => :put, :toggle_star => :put, :defer => :put, :mobile_done => :put},
:collection => {:check_deferred => :post, :filter_to_context => :post, :filter_to_project => :post, :done => :get, :all_done => :get
}

View file

@ -15,32 +15,32 @@
== Version 2.1devel
NOTE: to use this version you need to migrate your database. Not migrating will
NOTE: To use this version you need to migrate your database. Not migrating will
cause new actions not to appear!
This version of tracks has moved to a new place on github. Also the wiki moved
to github, see the changed URLs above.
This version of tracks has moved to a new place on GitHub. Also the wiki moved
to GitHub, see the changed URLs above.
New and changed features:
1. redesign of the completed todos: a new overview page. Also all context and
1. Redesign of the completed todos: a new overview page. Also all context and
project pages have a link to their completed actions
2. New locales (es and fr) and updated locales (de, nl)
3. You can star an action right from the form to add a new action
4. redesign of preferences page
4. Redesign of preferences page.
5. You can now mark an action complete from the tickler
6. project names can now contain comma (',') in it name
6. Project names can now contain comma (',') in it name
7. There are two example ruby scripts in /doc to use the REST API to add a todo
or a project template with todos from the command line
Under the hood:
1. Upgraded rails to 2.3.12, jquery to 1.6.2 and jquery-ui to 1.8.14
2. fixed several issues with the REST API
3. upgraded the act_as_statemachine plugin. This change requires a
migration, see note above!
4. migated to bundler for gem dependencies
2. Fixed several issues with the REST API
3. Upgraded the act_as_statemachine plugin. This change requires a
migration. See note above!
4. Migated to bundler for gem dependencies
See https://github.com/tracksapp/tracks/compare/v2.0...master for all
detailled changes
detailed changes
== Version 2.0
@ -50,23 +50,23 @@ New features:
to a new todo in that project if no tags are supplied
3. Tracks now includes support of dependencies. Making an action dependent on
another action will hide it until the dependency is completed
4. you can now drag an action from one context to another
4. You can now drag an action from one context to another
5. Support for entering multiple actions in one form
6. You can now promote an action to a project
7. It is easier to view notes on the mobile interface and other interface fixes
8. The project description supports markup
9. support for Mail.app (message://) and OneNote (onenote://) links in notes
10.The email receiver is now able to receive email from several email adresses.
In site.yml this could be set to the previous behavior (receive from one
address per user)
11.You can enable open signup (like in tracks.tra.in)
12.Cleanup of context page
13.Support for CAS for login
14.Support for adding Tracks as a GMail Widget with instructions on the
Integrations page
15.Tracks now support internationalization. First translations are German and
Dutch. See http://www.getontracks.org/wiki/Translating-Tracks it you like to
help translating Tracks to other languages
9. Support for Mail.app (message://) and OneNote (onenote://) links in notes
10. The email receiver is now able to receive email from several email adresses.
In site.yml this could be set to the previous behavior (receive from one
address per user)
11. You can enable open signup (like in tracks.tra.in)
12. Cleanup of context page
13. Support for CAS for login
14. Support for adding Tracks as a GMail Widget with instructions on the
Integrations page
15. Tracks now supports internationalization. First translations are German and
Dutch. See http://www.getontracks.org/wiki/Translating-Tracks if you'd like to
help translate Tracks to other languages
Under the hood
1. All js is migrated to jQuery and most ui-widgets are migrated to jQuery-UI
@ -93,9 +93,9 @@ Under the hood:
5. Bugfixes, including fixing OpenID
== Version 1.6
1. upgrade to rails 2.0.2
2. new mobile interface (with some iPhone compatibility fixes)
3. new search functionality to search on todos, projects, contexts and notes
1. Upgrade to rails 2.0.2
2. New mobile interface (with some iPhone compatibility fixes)
3. New search functionality to search on todos, projects, contexts and notes
4. Bugfixes
== Version 1.5
@ -173,11 +173,6 @@ Under the hood:
== Version 1.03
13. All the adding, updating, deleting and marking actions done is performed using Ajax, so happens without needing to refresh the page.
14. There's a new setting in settings.yml ('staleness_starts') which defines the number of days before which actions get marked as stale. Let's say you set it to 7 days. Actions created between 7 and 14 days ago get marked pale yellow, those created between 14 and 28 days ago (staleness_starts x 2) get marked darker yellow, and those created more than 28 days ago (staleness_starts x 3) are fluorescent yellow! This is only applied to items without a due date, so you can add items to be done some time in the future without getting yellow splashed all over the place (thanks to Nicholas for the patch to restrict to items with no due date).
15. Contexts and projects can now be sorted in any order you like. Arrow buttons on the <tt>/contexts</tt> and <tt>/projects</tt> pages let you move an item to the top, up, down or to the bottom. For contexts, this affects the order in which they sort on the home page. Position is also used to sort the listings of active/completed/hidden contexts and projects in the 'sidebar', and in the dropdown lists on forms.
16. You can mark projects as completed (by editing the project on <tt>/projects</tt>). In the 'sidebar' active and completed projects are shown separately, but you can still view the completed project.
17. New images (from eclipse.org) for the edit, delete, notes and up, down, top and bottom buttons. I've made a greyscale version for the default, then the coloured version gets loaded when the mouse is hovering over the button.
1. Added back border="0" to images which I had mistakenly taken out. This should fix the ugly red border around images that appears in Firefox (thanks, Adam Hughes).
2. Removed the section in <tt>config/environment.rb</tt> which requires Redcloth. This was causing errors because Rails now requires Redcloth itself. This means that you now need to have Redcloth installed as a gem (gem install redcloth) (thanks, Jim).
3. SQL dumps are now available for each of the available database formats (MySQL, PostgreSQL and SQLite), as well as a separate file containing some example contents, which should work for all of the formats. These are in the <tt>tracks/db</tt> directory (thanks, Jim)
@ -190,6 +185,11 @@ Under the hood:
10. <b>Patch by lolindrath</b>: Sorting by date is now much smarter on the home page. Actions are sorted by ascending due date then ascending creation date, but non-due dated items sort to the bottom. This means that the most urgent items float to the top of each context list.
11. You can now uncheck actions from the completed actions list, so that they dynamically appear back in the uncompleted actions area.
12. A tiny improvement: the toggling of the individual notes now uses Element.toggle from prototype.js, so it doesn't have to be an onLoad property of the body tag. This means that you don't get the notes flashing before they are hidden when you load or reload a page.
13. All the adding, updating, deleting and marking actions done is performed using Ajax, so happens without needing to refresh the page.
14. There's a new setting in settings.yml ('staleness_starts') which defines the number of days before which actions get marked as stale. Let's say you set it to 7 days. Actions created between 7 and 14 days ago get marked pale yellow, those created between 14 and 28 days ago (staleness_starts x 2) get marked darker yellow, and those created more than 28 days ago (staleness_starts x 3) are fluorescent yellow! This is only applied to items without a due date, so you can add items to be done some time in the future without getting yellow splashed all over the place (thanks to Nicholas for the patch to restrict to items with no due date).
15. Contexts and projects can now be sorted in any order you like. Arrow buttons on the <tt>/contexts</tt> and <tt>/projects</tt> pages let you move an item to the top, up, down or to the bottom. For contexts, this affects the order in which they sort on the home page. Position is also used to sort the listings of active/completed/hidden contexts and projects in the 'sidebar', and in the dropdown lists on forms.
16. You can mark projects as completed (by editing the project on <tt>/projects</tt>). In the 'sidebar' active and completed projects are shown separately, but you can still view the completed project.
17. New images (from eclipse.org) for the edit, delete, notes and up, down, top and bottom buttons. I've made a greyscale version for the default, then the coloured version gets loaded when the mouse is hovering over the button.
== Version 1.02

View file

@ -3,8 +3,7 @@
Tracks is using
* github to host the git repository.
* Assembla to manage bugs and enhancement request.
* the mailing list to discuss features and development
* the forum to discuss with users
* the mailing list to discuss features and development and interact with users
See README for links to the respective sites
@ -12,35 +11,39 @@ Also see the Development pages on the wiki for details on installing, upgrading,
2. Dependencies
The dependencies are maintained by Tracks. For development we try not to vendor them
Install them using
rake gems:install RAILS_ENV=development
rake gems:install RAILS_ENV=test
rake gems:install RAILS_ENV=selenium
The dependencies for Tracks are maintained using bundler. Before starting your
tracks instance, you'll need to run 'bundle install' to fetch all the
dependencies
3. Wiki
There are some pointers for setting up your Tracks copy for testing at https://github.com/TracksApp/tracks/wiki/Testing/
There are some pointers for setting up your Tracks copy for testing at
https://github.com/TracksApp/tracks/wiki/Testing/
4. SQLITE3 FOR TESTING
By default, tests are configured to run using sqlite3 in memory mode to increase speed. You will need the sqlite3-ruby gem for this.
By default, tests are configured to run using sqlite3 in memory mode to
increase speed. You will need the sqlite3-ruby gem for this.
To avoid showing the migrations as tests are run, add the following to your database.yml below 'database: ":memory:"':
To avoid showing the migrations as tests are run, add the following to your
database.yml below 'database: ":memory:"':
verbosity: quiet
If you want to run tests using another database, that's fine, too. Just change your database.yml accordingly.
If you want to run tests using another database, that's fine, too. Just change
your database.yml accordingly.
5. SELENIUM TESTS (Selenium on Rails)
5. Test::Unit tests
This testing style is deprecated and tests are being moved over to Selenium via Cucumber.
To run the Test::Unit tests run
See the wiki for more information to run the tests that are not yet migrated: https://github.com/TracksApp/tracks/wiki/Testing
rake test
6. RSPEC tests
Running the above command will run through the unit, functional, and
integration tests for Tracks. Use 'rake -T' to see a list of all rake tasks to
determine how to run each section of tests separately.
6. RSpec tests
To run the RSpec tests run
@ -56,4 +59,7 @@ and for those using javascript/ajax use
rake cucumber:selenium
See the wiki for more information on testing: https://github.com/TracksApp/tracks/wiki/Testing
In order to run the selenium tests, you'll need to have Firefox 3.x installed.
Newer versions won't work.
See the wiki for more information on testing: https://github.com/TracksApp/tracks/wiki/Testing

View file

@ -14,7 +14,7 @@ Feature: Add new next action from mobile page
Scenario Outline: The new action form is prefilled with context and project
Given I am on the <page>
When I follow "0-New action"
When I follow "New"
Then the selected project should be "<project>"
And the selected context should be "<context>"
@ -29,7 +29,7 @@ Feature: Add new next action from mobile page
Scenario: I can add a new todo using the mobile interface
Given I am on the home page
Then the badge should show 0
When I follow "0-New action"
When I follow "New"
And I fill in "Description" with "test me"
And I press "Create"
Then I should see "test me"

View file

@ -14,6 +14,7 @@ Feature: Edit a next action from the mobile view
| context | description |
| @mobile | test action |
@selenium
Scenario: I can edit an action on the mobile page
When I am on the home page
Then the badge should show 1
@ -27,10 +28,8 @@ Feature: Edit a next action from the mobile view
Then I should see "changed action"
And I should not see "test action"
When I follow "changed action"
And I press "Edit this action"
And I check "done"
And I press "Update"
Then I should not see "changed action"
And I press "Mark complete"
Then I should see "changed action" in the completed container
Scenario: Navigate from home page
move this to separate features when other scenarios are created for these features

View file

@ -33,6 +33,15 @@ Feature: Edit a project
Then I should see that a project named "release tracks 1.8" is not present
And I should see that a project named "release tracks 2.0" is present
@selenium
Scenario: I cannot edit the project name in two places at once
Given I have a project "release tracks 1.8" with 1 todos
When I go to the "release tracks 1.8" project
And I click to edit the project name in place
Then I should be able to change the project name in place
When I edit the project settings
Then I should not be able to change the project name in place
# Ticket #1041
@selenium
Scenario: I can change the name of the project using the Edit Project Settings form

View file

@ -196,6 +196,39 @@ When /^I edit the project name in place to be "([^"]*)"$/ do |new_project_name|
click_button "Ok"
end
When /^I click to edit the project name in place$/ do
selenium.click "css=div#project_name"
end
Then /^I should be able to change the project name in place$/ do
#Note that this is not changing the project name
selenium.wait_for_element "css=div#project_name>form>input"
selenium.click "css=div#project_name > form > button[type=cancel]"
end
When /^I edit the project settings$/ do
@project.should_not be_nil
click_link "link_edit_project_#{@project.id}"
selenium.wait_for_element("xpath=//div[@id='edit_project_#{@project.id}']/form//button[@id='submit_project_#{@project.id}']")
end
Then /^I should not be able to change the project name in place$/ do
When "I click to edit the project name in place"
found = selenium.element? "xpath=//div[@id='project_name']/form/input"
!found
end
When /^I close the project settings$/ do
@project.should_not be_nil
click_link "Cancel"
wait_for :wait_for => :effects , :javascript_framework => 'jquery' do
true
end
end
When /^I edit the project state of "([^"]*)" to "([^"]*)"$/ do |project_name, state_name|
project = @current_user.projects.find_by_name(project_name)
project.should_not be_nil

View file

@ -1,5 +1,6 @@
Given /^I have a repeat pattern called "([^"]*)"$/ do |pattern_name|
context = @current_user.contexts.first
@recurring_todo = @current_user.recurring_todos.create!(
:description => pattern_name,
:context_id => context.id,
@ -44,10 +45,10 @@ When /^I edit the name of the pattern "([^\"]*)" to "([^\"]*)"$/ do |pattern_nam
wait_for_ajax
fill_in "edit_recurring_todo_description", :with => new_name
selenium.click "recurring_todo_edit_action_submit"
selenium.click "recurring_todo_edit_update_button"
wait_for do
!selenium.is_visible("overlay")
!selenium.is_visible("edit-recurring-todo")
end
end

View file

@ -2,7 +2,7 @@ class ActiveRecord::Base #:nodoc:
# These extensions make models taggable. This file is automatically generated and required by your app if you run the tagging generator included with has_many_polymorphs.
module TaggingExtensions
# Add tags to <tt>self</tt>. Accepts a string of tagnames, an array of tagnames, an array of ids, or an array of Tags.
#
# We need to avoid name conflicts with the built-in ActiveRecord association methods, thus the underscores.
@ -21,8 +21,8 @@ class ActiveRecord::Base #:nodoc:
end
end
end
# Removes tags from <tt>self</tt>. Accepts a string of tagnames, an array of tagnames, an array of ids, or an array of Tags.
# Removes tags from <tt>self</tt>. Accepts a string of tagnames, an array of tagnames, an array of ids, or an array of Tags.
def _remove_tags outgoing
taggable?(true)
outgoing = tag_cast_to_string(outgoing)
@ -35,20 +35,20 @@ class ActiveRecord::Base #:nodoc:
def tag_list
# Redefined later to avoid an RDoc parse error.
end
# Replace the existing tags on <tt>self</tt>. Accepts a string of tagnames, an array of tagnames, an array of ids, or an array of Tags.
def tag_with list
def tag_with list
#:stopdoc:
taggable?(true)
list = tag_cast_to_string(list)
# Transactions may not be ideal for you here; be aware.
Tag.transaction do
Tag.transaction do
current = tags.map(&:name)
_add_tags(list - current)
_remove_tags(current - list)
end
self
#:startdoc:
end
@ -64,10 +64,10 @@ class ActiveRecord::Base #:nodoc:
def tag_list=(value)
tag_with(value)
end
end
private
private
def tag_cast_to_string obj #:nodoc:
case obj
when Array
@ -88,9 +88,9 @@ class ActiveRecord::Base #:nodoc:
else
raise "Invalid object of class #{obj.class} as tagging method parameter"
end.flatten.compact.map(&:downcase).uniq
end
# Check if a model is in the :taggables target list. The alternative to this check is to explicitly include a TaggingMethods module (which you would create) in each target model.
end
# Check if a model is in the :taggables target list. The alternative to this check is to explicitly include a TaggingMethods module (which you would create) in each target model.
def taggable?(should_raise = false) #:nodoc:
unless flag = respond_to?(:tags)
raise "#{self.class} is not a taggable model" if should_raise
@ -99,69 +99,69 @@ class ActiveRecord::Base #:nodoc:
end
end
module TaggingFinders
# Find all the objects tagged with the supplied list of tags
#
#
# Usage : Model.tagged_with("ruby")
# Model.tagged_with("hello", "world")
# Model.tagged_with("hello", "world", :limit => 10)
#
# XXX This query strategy is not performant, and needs to be rewritten as an inverted join or a series of unions
#
#
def tagged_with(*tag_list)
options = tag_list.last.is_a?(Hash) ? tag_list.pop : {}
tag_list = parse_tags(tag_list)
scope = scope(:find)
options[:select] ||= "#{table_name}.*"
options[:from] ||= "#{table_name}, tags, taggings"
sql = "SELECT #{(scope && scope[:select]) || options[:select]} "
sql << "FROM #{(scope && scope[:from]) || options[:from]} "
add_joins!(sql, options[:joins], scope)
sql << "WHERE #{table_name}.#{primary_key} = taggings.taggable_id "
sql << "AND taggings.taggable_type = '#{ActiveRecord::Base.send(:class_name_of_active_record_descendant, self).to_s}' "
sql << "AND taggings.tag_id = tags.id "
tag_list_condition = tag_list.map {|name| "'#{name}'"}.join(", ")
sql << "AND (tags.name IN (#{sanitize_sql(tag_list_condition)})) "
sql << "AND #{sanitize_sql(options[:conditions])} " if options[:conditions]
columns = column_names.map do |column|
columns = column_names.map do |column|
"#{table_name}.#{column}"
end.join(", ")
sql << "GROUP BY #{columns} "
sql << "HAVING COUNT(taggings.tag_id) = #{tag_list.size}"
add_order!(sql, options[:order], scope)
add_limit!(sql, options, scope)
add_lock!(sql, options, scope)
find_by_sql(sql)
end
def self.tagged_with_any(*tag_list)
options = tag_list.last.is_a?(Hash) ? tag_list.pop : {}
tag_list = parse_tags(tag_list)
scope = scope(:find)
options[:select] ||= "#{table_name}.*"
options[:from] ||= "#{table_name}, meta_tags, taggings"
sql = "SELECT #{(scope && scope[:select]) || options[:select]} "
sql << "FROM #{(scope && scope[:from]) || options[:from]} "
add_joins!(sql, options, scope)
sql << "WHERE #{table_name}.#{primary_key} = taggings.taggable_id "
sql << "AND taggings.taggable_type = '#{ActiveRecord::Base.send(:class_name_of_active_record_descendant, self).to_s}' "
sql << "AND taggings.meta_tag_id = meta_tags.id "
sql << "AND ("
or_options = []
tag_list.each do |name|
@ -169,30 +169,30 @@ class ActiveRecord::Base #:nodoc:
end
or_options_joined = or_options.join(" OR ")
sql << "#{or_options_joined}) "
sql << "AND #{sanitize_sql(options[:conditions])} " if options[:conditions]
columns = column_names.map do |column|
"#{table_name}.#{column}"
end.join(", ")
sql << "GROUP BY #{columns} "
add_order!(sql, options[:order], scope)
add_limit!(sql, options, scope)
add_lock!(sql, options, scope)
find_by_sql(sql)
end
def parse_tags(tags)
return [] if tags.blank?
tags = Array(tags).first
tags = tags.respond_to?(:flatten) ? tags.flatten : tags.split(Tag::DELIMITER)
tags.map { |tag| tag.strip.squeeze(" ") }.flatten.compact.map(&:downcase).uniq
end
end
include TaggingExtensions

View file

@ -737,7 +737,8 @@ var ProjectListPage = {
$('div#project_name').editable(ProjectListPage.save_project_name, {
style: 'padding: 0px; width=100%;',
submit: i18n['common.ok'],
cancel: i18n['common.cancel']
cancel: i18n['common.cancel'],
onblur: 'cancel'
});
/* alphabetize project list */
@ -775,12 +776,14 @@ var ProjectListPage = {
/* submit project form after edit */
$("form.edit-project-form button.positive").live('click', function (ev) {
$('div#project_name').editable('enable');
submit_with_ajax_and_block_element('form.edit-project-form', $(this));
return false;
});
/* cancel edit project form */
$('form.edit-project-form a.negative').live('click', function(){
$('div#project_name').editable('enable');
$(this).parents('.edit-form').fadeOut(200, function () {
$(this).parents('.list').find('.project').fadeIn(500);
$(this).parents('.container').find('.item-show').fadeIn(500);
@ -991,64 +994,89 @@ var RecurringTodosPage = {
$('#recurring_edit_'+this).hide();
});
},
reset_radio: function () {
$('input:radio[name="recurring_todo[recurring_period]"]')[0].checked = true;
},
toggle_overlay: function () {
var overlay_element = document.getElementById("overlay");
overlay_element.style.visibility = (overlay_element.style.visibility == "visible") ? "hidden" : "visible";
},
setup_behavior: function() {
/* cancel button on new recurring todo form */
$("#recurring_todo_new_action_cancel").live('click', function(){
$('#recurring-todo-form-new-action input:text:first').focus();
RecurringTodosPage.hide_all_recurring();
$('#recurring_daily').show();
RecurringTodosPage.toggle_overlay();
});
/* cancel button on edit recurring todo form */
$("#recurring_todo_edit_action_cancel").live('click', function(){
$('#recurring-todo-form-edit-action input:text:first').focus();
RecurringTodosPage.hide_all_recurring();
$('#recurring_daily').show();
RecurringTodosPage.toggle_overlay();
});
/* change recurring period radio input on edit form */
$("#recurring_edit_period input").live('click', function(){
RecurringTodosPage.hide_all_edit_recurring();
$('#recurring_edit_'+this.id.split('_')[5]).show();
});
/* change recurring period radio input on new form */
$("#recurring_period input").live('click', function(){
RecurringTodosPage.hide_all_recurring();
$('#recurring_'+this.id.split('_')[4]).show();
});
/* add new recurring todo plus-button in sidebar */
$("#add-new-recurring-todo").live('click', function(){
$('#new-recurring-todo').show();
$('#edit-recurring-todo').hide();
RecurringTodosPage.toggle_overlay();
});
/* submit form when editing a recurring todo */
$("#recurring_todo_edit_action_submit").live('click', function (ev) {
submit_with_ajax_and_block_element('form#recurring-todo-form-edit-action', $(this));
return false;
});
/* submit form for new recurring todo */
$("#recurring_todo_new_action_submit").live('click', function (ev) {
submit_with_ajax_and_block_element('form.#recurring-todo-form-new-action', $(this));
return false;
});
/* set behavior for edit recurring todo */
$(".item-container a.edit_icon").live('click', function (ev){
get_with_ajax_and_block_element(this.href, $(this).parents(".item-container"));
return false;
});
/* delete button to delete a todo from the list */
$('.item-container a.delete_icon').live('click', function(evt){
var confirm_message = $(this).attr("x_confirm_message")
if(confirm(confirm_message)){
delete_with_ajax_and_block_element(this.href, $(this).parents('.project'));
/* add new recurring todo plus-button in sidebar */
$("#add-new-recurring-todo").live('click', function(){
$( "#new-recurring-todo" ).dialog( "open" );
});
/* setup dialog for new repeating action */
$( "#new-recurring-todo" ).dialog({
autoOpen: false,
height: 690,
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" ); }
},
show: "fade",
hide: "fade",
close: function() {
$('#recurring-todo-form-new-action input:text:first').focus();
RecurringTodosPage.hide_all_recurring();
RecurringTodosPage.reset_radio();
$('#recurring_daily').show();
}
});
/* change recurring period radio input on new form */
$("#recurring_period input").live('click', function(){
RecurringTodosPage.hide_all_recurring();
$('#recurring_'+this.id.split('_')[4]).show();
});
/* setup dialog for new repeating action */
$( "#edit-recurring-todo" ).dialog({
autoOpen: false,
height: 690,
width: 750,
modal: true,
buttons: {
"Update": {
text: "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" ); }
},
show: "fade",
hide: "fade",
close: function() {
$('#recurring-todo-form-edit-action input:text:first').focus();
RecurringTodosPage.hide_all_recurring();
RecurringTodosPage.reset_radio();
$('#recurring_daily').show();
}
});
/* change recurring period radio input on edit form */
$("#recurring_edit_period input").live('click', function(){
RecurringTodosPage.hide_all_edit_recurring();
$('#recurring_edit_'+this.id.split('_')[5]).show();
});
/* set behavior for edit recurring todo */
$(".item-container a.edit_icon").live('click', function (ev){
get_with_ajax_and_block_element(this.href, $(this).parents(".item-container"));
return false;
});
/* delete button to delete a todo from the list */
$('.item-container a.delete_icon').live('click', function(evt){
var confirm_message = $(this).attr("x_confirm_message")
if(confirm(confirm_message)){
delete_with_ajax_and_block_element(this.href, $(this).parents('.project'));
}
return false;
});
return false;
});
}
}
@ -1290,7 +1318,7 @@ function enable_rich_interaction(){
$(document).ready(function() {
// fix for IE8. Without this checkboxes don't work AJAXy. See #1152
if($.browser.msie && ($.browser.version.substring(0, 2) == "8.")) {
if($.browser.msie && ( ($.browser.version.substring(0, 2) == "8.") || ($.browser.version.substring(0, 2) == "7.") ) ) {
$('body').bind('change', function() {
return true;
});

View file

@ -0,0 +1,23 @@
/* Czech initialisation for the jQuery UI date picker plugin. */
/* Written by Pavel Župa (pavel.zupa@gmail.com). */
jQuery(function($){
$.datepicker.regional['cz'] = {
closeText: 'zavřít',
prevText: 'předchozí',
nextText: 'další',
currentText: 'dnes',
monthNames: ['Leden','Únor','Březen','Duben','Květen','Červen',
'Červenec','Srpen','Září','Říjen','Listopad','Prosinec'],
monthNamesShort: ['Led','Úno','Bře','Dub','Kvě','Čer',
'Čec','Srp','Zář','Říj','Lis','Pro'],
dayNames: ['Neděle','Pondělí','Úterý','Středa','Čtvrtek','Pátek','Sobota'],
dayNamesShort: ['Ne','Po','Út','St','Čt','Pá','So'],
dayNamesMin: ['Ne','Po','Út','St','Čt','Pá','So'],
weekHeader: 'č.',
dateFormat: 'dd.mm.yy',
firstDay: 1,
isRTL: false,
showMonthAfterYear: false,
yearSuffix: ''};
$.datepicker.setDefaults($.datepicker.regional['cz']);
});

View file

@ -3,36 +3,41 @@ body {
font-size: smaller;
}
#content {
margin-top: 50px;
}
div.footer {
font-size: XX-small;
color: #999999;
text-align: center;
}
a, a:link, a:active, a:visited {
color: #CC3334;
padding-left: 1px;
padding-right: 1px;
text-decoration: none;
}
a:hover {
background-color: #CC3334;
color: #FFFFFF;
}
div.footer a {
text-decoration: underline;
color: #999999;
}
.m_t_d a {
text-decoration: none;
color: #000000;
}
.m_t_d a:hover {
text-decoration: underline;
color: #0000FF;
}
.m_t_d .red, .m_t_d .amber, .m_t_d .orange, .m_t_d .green{
background-color: #999999;
}
h1 {
color: #f00;
color: #fff;
font-size: small;
margin-top:.3em;
margin-bottom:.3em;
padding-top: 0.2em;
padding-bottom: 0.2em;
padding-left:8px;
margin-top:0;
margin-bottom:0;
}
h2 {
@ -43,6 +48,17 @@ h2 {
border-top: 1px solid #777777;
}
h2 a, h2 a:link, h2 a:active, h2 a:visited {
color: #666666;
text-decoration: none;
}
h2 a:hover {
background-color: transparent;
color: #CC3334;
text-decoration: none;
}
h4.alert {
border: 1px solid #666666;
text-align: center;
@ -79,6 +95,15 @@ span.r {
span.prj, span.ctx{
font-size: X-small;
}
#ctx, #pjr {
margin: 0.5em 0;
}
#ctx a, #pjr a {
padding: 0.1em 0;
}
/* Draw attention to some text
Same format as traffic lights */
.red {
@ -118,22 +143,22 @@ span.prj, span.ctx{
.count {
color: #fff;
background: #000;
font-size: medium;
background: #f00;
padding: 0.2em;
}
.errors {
background: #FFC2C2;
}
ul.c li.star {
list-style-type: circle;
ul.c li {
list-style-type: none;
}
ul.c {
padding: 0;
margin: 0;
padding-left: 1.1em;
padding-left: 0.1em;
}
ul.c li {
@ -149,8 +174,44 @@ span.r {
display:none;
}
#topbar {
background-color: #000000;
clear: both;
color: #EEEEEE;
height: 45px;
left: 0;
margin-bottom: 5px;
position: fixed;
top: 0;
width: 100%;
z-index: 501;
}
.nav {
font-size: x-small;
color: #fff;
background: #000;
padding-top: 0.2em;
padding-bottom: 0.2em;
}
#topbar .nav {
padding-left:8px;
margin-bottom:0.3em;
}
.nav a, .nav a:link, .nav a:active, .nav a:visited {
color: #fff;
padding-top: 1.0em;
padding-bottom: 0.5em;
}
.nav a:focus, .nav a:hover, .nav a:active {
background: transparent;
text-decoration: underline;
}
.nav li:hover, .nav a:focus, .nav a:hover, .nav a:active {
color: #CCCCCC;
}
#database_auth_form table td {
@ -160,3 +221,10 @@ 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 {
width: 95%;
}

View file

@ -203,26 +203,6 @@ a.show_successors:hover, a.link_to_successors:hover {background-image: url(../im
width: 100%;
}
#overlay {
visibility: hidden;
position: absolute;
left: 0px;
top: 0px;
width: 100%;
height: 100%;
z-index: 102;
text-align: center;
background-image:url("../images/trans70.png");
}
#overlay #new-recurring-todo, #overlay #edit-recurring-todo {
width:750px;
background-color: #fff;
border:1px solid #000;
padding: 15px;
margin: 70px auto;
}
.recurring_container {
padding: 0px 5px 0px 5px;
border: 1px solid #999;
@ -231,14 +211,6 @@ a.show_successors:hover, a.link_to_successors:hover {background-image: url(../im
text-align: left;
}
.recurring_submit_box {
height: 25px;
padding: 5px 0;
text-align: center;
clear: both;
border: none;
}
/* Navigation links at the top */
#navcontainer {
@ -278,8 +250,8 @@ a.show_successors:hover, a.link_to_successors:hover {background-image: url(../im
#navlist a:hover { color: #CCC; }
#develop-notify-bar {
line-height:0.5;
background-image: url(/images/construction.gif);
line-height:0.5;
background-image: url(/images/construction.gif);
background-repeat: repeat-x;
}
@ -321,6 +293,9 @@ a.show_successors:hover, a.link_to_successors:hover {background-image: url(../im
border: 1px solid #999;
margin: 0px 0px 15px 0px;
background: #fff;
border-radius: 5px;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
}
.completed {
@ -373,7 +348,7 @@ div#input_box {
#input_box ul#predecessor_ul {
list-style-type: none;
color: black;
color: red;
}
.show_from_input, .due_input, .project_input, .context_input,
@ -424,7 +399,7 @@ a.delete_dependency_button:hover {background-color: white;}
div.item-container {
padding: 2px 0px;
line-height:20px;
line-height: 20px;
clear: both;
}
@ -445,6 +420,7 @@ a.icon {
input.item-checkbox {
float: left;
margin-top: 2px;
margin-left: 3px;
margin-right: 3px;
vertical-align: middle;
@ -753,6 +729,10 @@ The colour of the background gets progressively yellower with age */
font-size: 12pt;
margin: 10px 10px 0px 0px;
height:26px;
-moz-border-radius: 2px;
-webkit-border-radius: 2px;
border-radius: 2px;
}
ul {

View file

@ -20,6 +20,7 @@ begin
t.sequence(:description) { |n| "testtodo#{n}" }
t.association :context
end
rescue FactoryGirl::DuplicateDefinitionError
# No problem, apparently this file was included already.
end

View file

@ -2,7 +2,7 @@ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
class RecurringTodosControllerTest < ActionController::TestCase
fixtures :users, :preferences, :projects, :contexts, :todos, :tags, :taggings, :recurring_todos
def setup
@controller = RecurringTodosController.new
@request, @response = ActionController::TestRequest.new, ActionController::TestResponse.new
@ -12,30 +12,30 @@ class RecurringTodosControllerTest < ActionController::TestCase
get :index
assert_redirected_to :controller => 'login', :action => 'login'
end
def test_destroy_recurring_todo
login_as(:admin_user)
xhr :post, :destroy, :id => 1, :_source_view => 'todo'
begin
begin
rc = RecurringTodo.find(1)
rescue
rc = nil
rc = nil
end
assert_nil rc
end
def test_new_recurring_todo
login_as(:admin_user)
orig_rt_count = RecurringTodo.count
orig_todo_count = Todo.count
put :create,
"context_name"=>"library",
"project_name"=>"Build a working time machine",
"recurring_todo" =>
put :create,
"context_name"=>"library",
"project_name"=>"Build a working time machine",
"recurring_todo" =>
{
"daily_every_x_days"=>"1",
"daily_selector"=>"daily_every_x_day",
"description"=>"new recurring pattern",
"daily_every_x_days"=>"1",
"daily_selector"=>"daily_every_x_day",
"description"=>"new recurring pattern",
"end_date" => "31/08/2010",
"ends_on" => "ends_on_end_date",
"monthly_day_of_week" => "1",
@ -59,15 +59,15 @@ class RecurringTodosControllerTest < ActionController::TestCase
"yearly_month_of_year2"=>"8",
"yearly_month_of_year"=>"6",
"yearly_selector"=>"yearly_every_x_day"
},
},
"tag_list"=>"one, two, three, four"
# check new recurring todo added
assert_equal orig_rt_count+1, RecurringTodo.count
assert_equal orig_rt_count+1, RecurringTodo.count
# check new todo added
assert_equal orig_todo_count+1, Todo.count
end
def test_recurring_todo_toggle_check
# the test fixtures did add recurring_todos but not the corresponding todos,
# so we check complete and uncheck to force creation of a todo from the
@ -78,22 +78,22 @@ class RecurringTodosControllerTest < ActionController::TestCase
xhr :post, :toggle_check, :id=>1, :_source_view=>""
recurring_todo_1 = RecurringTodo.find(1)
assert recurring_todo_1.completed?
# remove remaining todo
todo = Todo.find_by_recurring_todo_id(1)
todo.recurring_todo_id = 2
todo.save
todo_count = Todo.count
# mark as active
xhr :post, :toggle_check, :id=>1, :_source_view=>""
xhr :post, :toggle_check, :id=>1, :_source_view=>""
recurring_todo_1.reload
assert recurring_todo_1.active?
# by making active, a new todo should be created from the pattern
assert_equal todo_count+1, Todo.count
# find the new todo and check its description
new_todo = Todo.find_by_recurring_todo_id 1
assert_equal "Call Bill Gates every day", new_todo.description
@ -101,9 +101,9 @@ class RecurringTodosControllerTest < ActionController::TestCase
def test_creating_recurring_todo_with_show_from_in_past
login_as(:admin_user)
@yearly = RecurringTodo.find(5) # yearly on june 8th
# change due date in four days from now and show from 10 days before, i.e. 6
# days ago
target_date = Time.now.utc + 4.days
@ -114,37 +114,37 @@ class RecurringTodosControllerTest < ActionController::TestCase
# @yearly.errors.each {|obj, error| puts error}
# end
assert @yearly.save
# toggle twice to force generation of new todo
xhr :post, :toggle_check, :id=>5, :_source_view=>""
xhr :post, :toggle_check, :id=>5, :_source_view=>""
new_todo = Todo.find_by_recurring_todo_id 5
# due date should be the target_date
assert_equal users(:admin_user).at_midnight(Date.new(target_date.year, target_date.month, target_date.day)), new_todo.due
# show_from should be nil since now+4.days-10.days is in the past
assert_equal nil, new_todo.show_from
end
def test_last_sunday_of_march
# this test is a duplicate of the unit test. Only this test covers the
# codepath in the controllers
login_as(:admin_user)
orig_rt_count = RecurringTodo.count
orig_todo_count = Todo.count
put :create,
"context_name"=>"library",
"project_name"=>"Build a working time machine",
"recurring_todo" =>
put :create,
"context_name"=>"library",
"project_name"=>"Build a working time machine",
"recurring_todo" =>
{
"daily_every_x_days"=>"1",
"daily_selector"=>"daily_every_x_day",
"description"=>"new recurring pattern",
"daily_every_x_days"=>"1",
"daily_selector"=>"daily_every_x_day",
"description"=>"new recurring pattern",
"end_date" => "",
"ends_on" => "no_end_date",
"monthly_day_of_week" => "1",
@ -168,36 +168,36 @@ class RecurringTodosControllerTest < ActionController::TestCase
"yearly_month_of_year2"=>"3",
"yearly_month_of_year"=>"10",
"yearly_selector"=>"yearly_every_xth_day"
},
},
"tag_list"=>"one, two, three, four"
# check new recurring todo added
assert_equal orig_rt_count+1, RecurringTodo.count
assert_equal orig_rt_count+1, RecurringTodo.count
# check new todo added
assert_equal orig_todo_count+1, Todo.count
# find the newly created todo
new_todo = Todo.find_by_description("new recurring pattern")
assert !new_todo.nil?
# the date should be 31 march 2013
assert_equal Time.zone.local(2013,3,31), new_todo.due
end
def test_recurring_todo_with_due_date_and_show_always
login_as(:admin_user)
orig_rt_count = RecurringTodo.count
orig_todo_count = Todo.count
put :create,
"context_name"=>"library",
"project_name"=>"Build a working time machine",
"recurring_todo" =>
put :create,
"context_name"=>"library",
"project_name"=>"Build a working time machine",
"recurring_todo" =>
{
"daily_every_x_days"=>"1",
"daily_selector"=>"daily_every_x_day",
"description"=>"new recurring pattern",
"daily_every_x_days"=>"1",
"daily_selector"=>"daily_every_x_day",
"description"=>"new recurring pattern",
"end_date" => "",
"ends_on" => "no_end_date",
"monthly_day_of_week" => "1",
@ -221,20 +221,50 @@ class RecurringTodosControllerTest < ActionController::TestCase
"yearly_month_of_year2"=>"3",
"yearly_month_of_year"=>"10",
"yearly_selector"=>"yearly_every_xth_day"
},
},
"tag_list"=>"one, two, three, four"
# check new recurring todo added
assert_equal orig_rt_count+1, RecurringTodo.count
assert_equal orig_rt_count+1, RecurringTodo.count
# check new todo added
assert_equal orig_todo_count+1, Todo.count
# find the newly created recurring todo
recurring_todo = RecurringTodo.find_by_description("new recurring pattern")
assert !recurring_todo.nil?
assert_equal "due_date", recurring_todo.target
assert_equal true, recurring_todo.show_always?
end
def test_find_and_inactivate
login_as(:admin_user)
rt = RecurringTodo.find(recurring_todos(:call_bill_gates_every_day).id)
todo = Todo.find_by_recurring_todo_id(rt.id)
assert_not_nil todo
assert_equal "active", todo.state, "todo should be active"
assert_equal "active", rt.state, "repeat pattern should be active"
get :index # will call find_and_inactivate
rt.reload
assert_equal "active", rt.state, "repeat pattern should still be active"
# disconnect todo from pattern thus leaving the pattern without
# any active todos, but in active state
todo.reload
todo.recurring_todo_id=nil
todo.save!
todo.reload
rt.reload
assert_equal "active", rt.state, "repeat pattern should still be active and not changed"
get :index
rt.reload
assert_equal "completed", rt.state, "repeat pattern should be completed"
end
end

View file

@ -530,7 +530,9 @@ class TodosControllerTest < ActionController::TestCase
# check that the new_todo is in the tickler to show next month
assert !new_todo.show_from.nil?
next_month = today + 1.month
# do not use today here. It somehow gets messed up with the timezone calculation.
next_month = (Time.zone.now + 1.month).at_midnight
assert_equal next_month.utc.to_date.to_s(:db), new_todo.show_from.utc.to_date.to_s(:db)
end

View file

@ -8,8 +8,8 @@ class TodoXmlApiTest < ActionController::IntegrationTest
def setup
assert_test_environment_ok
@user = users(:other_user)
@password = 'sesame'
@user = users(:admin_user)
@password = 'abracadabra'
end
def test_get_tickler_succeeds
@ -25,24 +25,24 @@ class TodoXmlApiTest < ActionController::IntegrationTest
assert_response 401
end
def test_get_tickler_returns_all_deferred_todos
number = @user.todos.deferred.count
def test_get_tickler_returns_all_deferred_and_pending_todos
number = @user.todos.deferred.count + @user.todos.pending.count
authenticated_get_xml "/tickler", @user.login, @password, {}
assert_tag :tag => "todos", :children => { :count => number, :only => { :tag => "todo" } }
assert_tag :tag => "todos", :children => { :count => number }
end
def test_get_tickler_omits_user_id
authenticated_get_xml "/tickler", @user.login, @password, {}
assert_no_tag :tag => "user_id"
end
def test_create_todo_via_xml_show_from
def test_create_todo_with_show_from
old_count = @user.todos.count
authenticated_post_xml_to_todo_create "
<todo>
<description>Call Warren Buffet to find out how much he makes per day</description>
<project_id>#{projects(:attendrailsconf).id}</project_id>
<context_id>#{contexts(:office_otheruser).id}</context_id>
<context_id>#{contexts(:office).id}</context_id>
<project_id>#{projects(:timemachine).id}</project_id>
<show-from type=\"datetime\">#{1.week.from_now.xmlschema}</show-from>
</todo>"
@ -50,22 +50,169 @@ class TodoXmlApiTest < ActionController::IntegrationTest
assert_equal @user.todos.count, old_count + 1
end
def test_post_create_todo_with_multiple_dependencies
authenticated_post_xml_to_todo_create "
<todo>
<description>this will succeed 2.0</description>
<context_id>#{contexts(:office).id}</context_id>
<project_id>#{projects(:timemachine).id}</project_id>
<predecessor_dependencies>
<predecessor>5</predecessor>
<predecessor>6</predecessor>
</predecessor_dependencies>
</todo>"
assert_response :success
todo = @user.todos.find_by_description("this will succeed 2.0")
assert_not_nil todo
assert !todo.uncompleted_predecessors.empty?
end
def test_post_create_todo_with_single_dependency
authenticated_post_xml_to_todo_create "
<todo>
<description>this will succeed 2.1</description>
<context_id>#{contexts(:office).id}</context_id>
<project_id>#{projects(:timemachine).id}</project_id>
<predecessor_dependencies>
<predecessor>6</predecessor>
</predecessor_dependencies>
</todo>"
assert_response :success
todo = @user.todos.find_by_description("this will succeed 2.1")
assert_not_nil todo
assert !todo.uncompleted_predecessors.empty?
end
def test_post_create_todo_with_multiple_tags
authenticated_post_xml_to_todo_create "
<todo>
<description>this will succeed 3</description>
<context_id>#{contexts(:office).id}</context_id>
<project_id>#{projects(:timemachine).id}</project_id>
<tags>
<tag><name>starred</name></tag>
<tag><name>starred1</name></tag>
<tag><name>starred2</name></tag>
</tags>
</todo>"
assert_response :success
todo = @user.todos.find_by_description("this will succeed 3")
assert_not_nil todo
assert_equal "starred, starred1, starred2", todo.tag_list
assert todo.starred?
end
def test_post_create_todo_with_single_tag
authenticated_post_xml_to_todo_create "
<todo>
<description>this will succeed 3.1</description>
<context_id>#{contexts(:office).id}</context_id>
<project_id>#{projects(:timemachine).id}</project_id>
<tags>
<tag><name>tracks</name></tag>
</tags>
</todo>"
assert_response :success
todo = @user.todos.find_by_description("this will succeed 3.1")
assert_not_nil todo
assert_equal "tracks", todo.tag_list
end
def test_post_create_todo_with_new_context
authenticated_post_xml_to_todo_create "
<todo>
<description>this will succeed 4</description>
<project_id>#{projects(:timemachine).id}</project_id>
<context>
<name>@SomeNewContext</name>
</context>
</todo>"
assert_response :success
todo = @user.todos.find_by_description("this will succeed 4")
assert_not_nil todo
assert_not_nil todo.context
assert_equal todo.context.name, "@SomeNewContext"
end
def test_post_create_todo_with_name_of_existing_context
authenticated_post_xml_to_todo_create "
<todo>
<description>this will succeed 4</description>
<project_id>#{projects(:timemachine).id}</project_id>
<context>
<name>#{contexts(:office).name}</name>
</context>
</todo>"
assert_response :success
todo = @user.todos.find_by_description("this will succeed 4")
assert_not_nil todo
assert_not_nil todo.context
assert_equal contexts(:office).name, todo.context.name
end
def test_post_create_todo_with_new_project
authenticated_post_xml_to_todo_create "
<todo>
<description>this will succeed 5</description>
<context_id>#{contexts(:office).id}</context_id>
<project>
<name>Make even more money</name>
</project>
</todo>"
assert_response :success
todo = @user.todos.find_by_description("this will succeed 5")
assert_not_nil todo
assert_not_nil todo.project
assert_equal todo.project.name, "Make even more money"
end
def test_post_create_todo_with_name_of_existing_project
authenticated_post_xml_to_todo_create "
<todo>
<description>this will succeed 5</description>
<context_id>#{contexts(:office).id}</context_id>
<project>
<name>#{projects(:timemachine).name}</name>
</project>
</todo>"
assert_response :success
todo = @user.todos.find_by_description("this will succeed 5")
assert_not_nil todo
assert_not_nil todo.project
assert_equal projects(:timemachine).name, todo.project.name
assert 1, @user.projects.all(:conditions => ["projects.name = ?", projects(:timemachine).name]).count # no duplication of project
end
def test_post_create_todo_with_wrong_project_and_context_id
authenticated_post_xml_to_todo_create "<todo><description>this will fail</description><context_id type='integer'>-16</context_id><project_id type='integer'>-11</project_id></todo>"
authenticated_post_xml_to_todo_create "
<todo>
<description>this will fail</description>
<context_id type='integer'>-16</context_id>
<project_id type='integer'>-11</project_id>
</todo>"
assert_response 422
assert_xml_select 'errors' do
assert_select 'error', 2
end
end
def test_fails_with_401_if_not_authorized_user
def test_fails_with_401_if_not_authorized_user
authenticated_post_xml_to_todo_create '', 'nobody', 'nohow'
assert_response 401
end
private
def authenticated_post_xml_to_todo_create(postdata = @@valid_postdata, user = users(:other_user).login, password = 'sesame')
def authenticated_post_xml_to_todo_create(postdata = @@valid_postdata, user = @user.login, password = @password)
authenticated_post_xml "/todos", user, password, postdata
end