diff --git a/app/controllers/login_controller.rb b/app/controllers/login_controller.rb index 0c1037ed..5106686f 100644 --- a/app/controllers/login_controller.rb +++ b/app/controllers/login_controller.rb @@ -54,7 +54,7 @@ class LoginController < ApplicationController end when :get if User.no_users_yet? - redirect_to :controller => 'users', :action => 'new' + redirect_to signup_path return end end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 2364ea97..374c874c 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -26,7 +26,7 @@ class ProjectsController < ApplicationController format.rss &render_rss_feed format.atom &render_atom_feed format.text &render_text_feed - format.autocomplete { render :text => for_autocomplete(@projects, params[:q]) } + format.autocomplete { render :text => for_autocomplete(@projects.reject(&:completed?), params[:q]) } end end end @@ -105,15 +105,16 @@ class ProjectsController < ApplicationController if params['project']['state'] @new_state = params['project']['state'] @state_changed = @project.state != @new_state - logger.info "@state_changed: #{@project.state} == #{params['project']['state']} != #{@state_changed}" params['project'].delete('state') end success_text = if params['field'] == 'name' && params['value'] params['project']['id'] = params['id'] params['project']['name'] = params['value'] end + @project.attributes = params['project'] - if @project.save + @saved = @project.save + if @saved @project.transition_to(@new_state) if @state_changed if boolean_param('wants_render') if (@project.hidden?) @@ -149,8 +150,8 @@ class ProjectsController < ApplicationController return end else - notify :warning, "Couldn't update project" - render :text => '' + init_data_for_sidebar + render :template => 'projects/update.js.rjs' return end render :template => 'projects/update.js.rjs' @@ -283,7 +284,7 @@ class ProjectsController < ApplicationController p.delete('default_context_name') unless default_context_name.blank? - default_context = Context.find_or_create_by_name(default_context_name) + default_context = current_user.contexts.find_or_create_by_name(default_context_name) p['default_context_id'] = default_context.id end end diff --git a/app/controllers/stats_controller.rb b/app/controllers/stats_controller.rb index 3847c47a..004b335c 100755 --- a/app/controllers/stats_controller.rb +++ b/app/controllers/stats_controller.rb @@ -319,6 +319,7 @@ class StatsController < ApplicationController # - actions not part of a hidden project # - actions not part of a hidden context # - actions not deferred (show_from must be null) + # - actions not pending/blocked @actions_running_time = @actions.find_by_sql([ "SELECT t.created_at "+ @@ -326,7 +327,7 @@ class StatsController < ApplicationController "WHERE t.user_id=? "+ "AND t.completed_at IS NULL " + "AND t.show_from IS NULL " + - "AND NOT (p.state='hidden' OR c.hide=?) " + + "AND NOT (p.state='hidden' OR p.state='pending' OR c.hide=?) " + "ORDER BY t.created_at ASC", @user.id, true] ) diff --git a/app/controllers/todos_controller.rb b/app/controllers/todos_controller.rb index 4ad8446e..06a7d181 100644 --- a/app/controllers/todos_controller.rb +++ b/app/controllers/todos_controller.rb @@ -4,14 +4,14 @@ class TodosController < ApplicationController skip_before_filter :login_required, :only => [:index, :calendar] prepend_before_filter :login_or_feed_token_required, :only => [:index, :calendar] + append_before_filter :find_and_activate_ready, :only => [:index, :list_deferred] append_before_filter :init, :except => [ :destroy, :completed, :completed_archive, :check_deferred, :toggle_check, :toggle_star, :edit, :update, :create, :calendar, :auto_complete_for_predecessor, :remove_predecessor, :add_predecessor] - append_before_filter :get_todo_from_params, :only => [ :edit, :toggle_check, :toggle_star, :show, :update, :destroy, :remove_predecessor] + append_before_filter :get_todo_from_params, :only => [ :edit, :toggle_check, :toggle_star, :show, :update, :destroy, :remove_predecessor, :show_notes] protect_from_forgery :except => [:auto_complete_for_predecessor] def index - current_user.deferred_todos.find_and_activate_ready @projects = current_user.projects.find(:all, :include => [:default_context]) @contexts = current_user.contexts.find(:all) @@ -48,68 +48,142 @@ class TodosController < ApplicationController def create @source_view = params['_source_view'] || 'todo' - @tag_name = params['_tag_name'] - p = TodoCreateParamsHelper.new(params, prefs) - p.parse_dates() unless mobile? - tag_list = p.tag_list - predecessor_list = p.predecessor_list - - @todo = current_user.todos.build(p.attributes) - - if p.project_specified_by_name? - project = current_user.projects.find_or_create_by_name(p.project_name) - @new_project_created = project.new_record_before_save? - @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? - @not_done_todos = [@todo] if @new_context_created - @todo.context_id = context.id - end + @tag_name = params['_tag_name'] - @todo.add_predecessor_list(predecessor_list) - @todo.update_state_from_project - @saved = @todo.save - unless (@saved == false) || tag_list.blank? - @todo.tag_with(tag_list) - @todo.tags.reload - end + is_multiple = params[:todo] && params[:todo][:multiple_todos] && !params[:todo][:multiple_todos].nil? + if is_multiple + create_multiple + else + p = TodoCreateParamsHelper.new(params, prefs) + p.parse_dates() unless mobile? + tag_list = p.tag_list + predecessor_list = p.predecessor_list + + @todo = current_user.todos.build(p.attributes) - unless (@saved == false) - unless @todo.uncompleted_predecessors.empty? || @todo.state == 'project_hidden' - @todo.state = 'pending' + if p.project_specified_by_name? + project = current_user.projects.find_or_create_by_name(p.project_name) + @new_project_created = project.new_record_before_save? + @todo.project_id = project.id end - @todo.save + + 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? + @not_done_todos = [@todo] if @new_context_created + @todo.context_id = context.id + end + + @todo.add_predecessor_list(predecessor_list) + + # Fix for #977 because AASM overrides @state on creation + specified_state = @todo.state + + @todo.update_state_from_project + @saved = @todo.save + + # Fix for #977 because AASM overrides @state on creation + @todo.update_attribute('state', specified_state) unless specified_state == "immediate" + + unless (@saved == false) || tag_list.blank? + @todo.tag_with(tag_list) + @todo.tags.reload + end + + unless (@saved == false) + unless @todo.uncompleted_predecessors.empty? || @todo.state == 'project_hidden' + @todo.state = 'pending' + end + @todo.save + end + + respond_to do |format| + format.html { redirect_to :action => "index" } + format.m do + @return_path=cookies[:mobile_url] ? cookies[:mobile_url] : mobile_path + if @saved + redirect_to @return_path + else + @projects = current_user.projects.find(:all) + @contexts = current_user.contexts.find(:all) + render :action => "new" + end + end + format.js do + determine_down_count if @saved + @contexts = current_user.contexts.find(:all) if @new_context_created + @projects = current_user.projects.find(:all) if @new_project_created + @initial_context_name = params['default_context_name'] + @initial_project_name = params['default_project_name'] + @default_tags = @todo.project.default_tags unless @todo.project.nil? + render :action => 'create' + end + format.xml do + if @saved + head :created, :location => todo_url(@todo) + else + render :xml => @todo.errors.to_xml, :status => 422 + end + end + end + end + end + + def create_multiple + if project_specified_by_name(params[:project_name]) + project = current_user.projects.find_or_create_by_name(params[:project_name]) + @new_project_created = project.new_record_before_save? + @project_id = project.id + end + + if context_specified_by_name(params[:context_name]) + context = current_user.contexts.find_or_create_by_name(params[:context_name]) + @new_context_created = context.new_record_before_save? + @not_done_todos = [] if @new_context_created + @context_id = context.id + end + + tag_list = params[:tag_list] + + @todos = [] + params[:todo][:multiple_todos].split("\n").map do |line| + @todo = current_user.todos.build( + :description => line) + @todo.project_id = @project_id + @todo.context_id = @context_id + puts "TODO: #{@todo.description}, #{@todo.project_id}, #{@todo.context_id}" + @saved = @todo.save + puts "NOT SAVED" unless @saved + unless (@saved == false) || tag_list.blank? + @todo.tag_with(tag_list) + @todo.tags.reload + end + @todos << @todo + @not_done_todos << @todo if @new_context_created end respond_to do |format| format.html { redirect_to :action => "index" } - format.m do - @return_path=cookies[:mobile_url] ? cookies[:mobile_url] : mobile_path - if @saved - redirect_to @return_path - else - @projects = current_user.projects.find(:all) - @contexts = current_user.contexts.find(:all) - render :action => "new" - end - end format.js do determine_down_count if @saved @contexts = current_user.contexts.find(:all) if @new_context_created @projects = current_user.projects.find(:all) if @new_project_created @initial_context_name = params['default_context_name'] @initial_project_name = params['default_project_name'] - @default_tags = @todo.project.default_tags unless @todo.project.nil? - render :action => 'create' + if @todos.size > 0 + @default_tags = @todos[0].project.default_tags unless @todos[0].project.nil? + else + @multiple_error = "You need to submit at least one next action" + @saved = false; + @default_tags = current_user.projects.find_by_name(@initial_project_name).default_tags unless @initial_project_name.blank? + end + render :action => 'create_multiple' end format.xml do if @saved - head :created, :location => todo_url(@todo) + head :created, :location => context_url(@todos[0].context) else - render :xml => @todo.errors.to_xml, :status => 422 + render :xml => @todos[0].errors.to_xml, :status => 422 end end end @@ -176,12 +250,12 @@ class TodosController < ApplicationController if @todo.completed? @pending_to_activate = @todo.pending_to_activate @pending_to_activate.each do |t| - t.activate! + t.activate! end else @active_to_block = @todo.active_to_block @active_to_block.each do |t| - t.block! + t.block! end end @@ -190,7 +264,7 @@ class TodosController < ApplicationController if @saved determine_remaining_in_context_count(@todo.context_id) determine_down_count - determine_completed_count + determine_completed_count determine_deferred_tag_count(params['_tag_name']) if @source_view == 'tag' if source_view_is :calendar @original_item_due_id = get_due_id_for_calendar(@original_item_due) @@ -222,6 +296,23 @@ class TodosController < ApplicationController end end + def change_context + @todo = Todo.find(params[:todo][:id]) + @original_item_context_id = @todo.context_id + @context = Context.find(params[:todo][:context_id]) + @todo.context = @context + @saved = @todo.save + + @context_changed = true + @message = "Context changed to #{@context.name}" + determine_remaining_in_context_count(@original_item_context_id) + + respond_to do |format| + format.js {render :action => :update } + format.xml { render :xml => @todo.to_xml( :except => :user_id ) } + end + end + def update @source_view = params['_source_view'] || 'todo' init_data_for_sidebar unless mobile? @@ -276,7 +367,7 @@ class TodosController < ApplicationController if params['done'] == '1' && !@todo.completed? @todo.complete! @todo.pending_to_activate.each do |t| - t.activate! + t.activate! end end # strange. if checkbox is not checked, there is no 'done' in params. @@ -284,7 +375,7 @@ class TodosController < ApplicationController if !(params['done'] == '1') && @todo.completed? @todo.activate! @todo.active_to_block.each do |t| - t.block! + t.block! end end @@ -395,7 +486,7 @@ class TodosController < ApplicationController notify :error, "Failed to delete the action", 2.0 redirect_to :action => 'index' end - end + end format.js do if @saved @@ -403,7 +494,7 @@ class TodosController < ApplicationController if source_view_is_one_of(:todo, :deferred) determine_remaining_in_context_count(@context_id) elsif source_view_is :calendar - @original_item_due_id = get_due_id_for_calendar(@original_item_due) + @original_item_due_id = get_due_id_for_calendar(@original_item_due) @old_due_empty = is_old_due_empty(@original_item_due_id) end end @@ -435,13 +526,10 @@ class TodosController < ApplicationController @source_view = 'deferred' @page_title = "TRACKS::Tickler" - @projects = current_user.projects.find(:all, :include => [ :todos, :default_context ]) - @contexts_to_show = @contexts = current_user.contexts.find(:all, :include => [ :todos ]) + @contexts_to_show = @contexts = current_user.contexts.find(:all) - current_user.deferred_todos.find_and_activate_ready - @not_done_todos = current_user.deferred_todos + current_user.pending_todos - @count = @not_done_todos.size - @down_count = @count + @not_done_todos = current_user.deferred_todos(:include => [:tags, :taggings, :projects]) + current_user.pending_todos(:include => [:tags, :taggings, :projects]) + @down_count = @count = @not_done_todos.size respond_to do |format| format.html @@ -482,25 +570,25 @@ class TodosController < ApplicationController @tag = Tag.new(:name => @tag_name) if @tag.nil? tag_collection = @tag.todos - @not_done_todos = tag_collection.find(:all, + @not_done_todos = tag_collection.find(:all, :conditions => ['todos.user_id = ? and state = ?', current_user.id, 'active'], :order => 'todos.due IS NULL, todos.due ASC, todos.created_at ASC') - @hidden_todos = current_user.todos.find(:all, - :include => [:taggings, :tags, :context], + @hidden_todos = current_user.todos.find(:all, + :include => [:taggings, :tags, :context], :conditions => ['tags.name = ? AND (todos.state = ? OR (contexts.hide = ? AND todos.state = ?))', @tag_name, 'project_hidden', true, 'active'], :order => 'todos.completed_at DESC, todos.created_at DESC') - @deferred = tag_collection.find(:all, + @deferred = tag_collection.find(:all, :conditions => ['todos.user_id = ? and state = ?', current_user.id, 'deferred'], :order => 'show_from ASC, todos.created_at DESC') - @pending = tag_collection.find(:all, + @pending = tag_collection.find(:all, :conditions => ['todos.user_id = ? and state = ?', current_user.id, 'pending'], :order => 'show_from ASC, todos.created_at DESC') # If you've set no_completed to zero, the completed items box isn't shown on # the tag page max_completed = current_user.prefs.show_number_completed - @done = tag_collection.find(:all, - :limit => max_completed, + @done = tag_collection.find(:all, + :limit => max_completed, :conditions => ['todos.user_id = ? and state = ?', current_user.id, 'completed'], :order => 'todos.completed_at DESC') @@ -516,13 +604,13 @@ class TodosController < ApplicationController # Set count badge to number of items with this tag @not_done_todos.empty? ? @count = 0 : @count = @not_done_todos.size - @down_count = @count + @down_count = @count respond_to do |format| format.html - format.m { + format.m { cookies[:mobile_url]= {:value => request.request_uri, :secure => SITE_CONFIG['secure_cookies']} - render :action => "mobile_tag" + render :action => "mobile_tag" } end end @@ -567,23 +655,23 @@ class TodosController < ApplicationController due_this_month_date = Time.zone.now.end_of_month @due_today = current_user.todos.not_completed.find(:all, - :include => [:taggings, :tags], + :include => [:taggings, :tags], :conditions => ['todos.due <= ?', due_today_date], :order => "due") @due_this_week = current_user.todos.not_completed.find(:all, - :include => [:taggings, :tags], + :include => [:taggings, :tags], :conditions => ['todos.due > ? AND todos.due <= ?', due_today_date, due_this_week_date], :order => "due") @due_next_week = current_user.todos.not_completed.find(:all, - :include => [:taggings, :tags], + :include => [:taggings, :tags], :conditions => ['todos.due > ? AND todos.due <= ?', due_this_week_date, due_next_week_date], :order => "due") @due_this_month = current_user.todos.not_completed.find(:all, - :include => [:taggings, :tags], + :include => [:taggings, :tags], :conditions => ['todos.due > ? AND todos.due <= ?', due_next_week_date, due_this_month_date], :order => "due") @due_after_this_month = current_user.todos.not_completed.find(:all, - :include => [:taggings, :tags], + :include => [:taggings, :tags], :conditions => ['todos.due > ?', due_this_month_date], :order => "due") @@ -602,35 +690,35 @@ class TodosController < ApplicationController unless params['id'].nil? get_todo_from_params # Begin matching todos in current project - @items = current_user.todos.find(:all, + @items = current_user.todos.find(:all, :select => 'description, project_id, context_id, created_at', :conditions => [ '(todos.state = ? OR todos.state = ? OR todos.state = ?) AND ' + - 'NOT (id = ?) AND lower(description) LIKE ? AND project_id = ?', - 'active', 'pending', 'deferred', - @todo.id, - '%' + params[:predecessor_list].downcase + '%', - @todo.project_id ], + 'NOT (id = ?) AND lower(description) LIKE ? AND project_id = ?', + 'active', 'pending', 'deferred', + @todo.id, + '%' + params[:predecessor_list].downcase + '%', + @todo.project_id ], :order => 'description ASC', :limit => 10 ) if @items.empty? # Match todos in other projects - @items = current_user.todos.find(:all, - :select => 'description, project_id, context_id, created_at', + @items = current_user.todos.find(:all, + :select => 'description, project_id, context_id, created_at', :conditions => [ '(todos.state = ? OR todos.state = ? OR todos.state = ?) AND ' + - 'NOT (id = ?) AND lower(description) LIKE ?', - 'active', 'pending', 'deferred', - params[:id], '%' + params[:q].downcase + '%' ], + 'NOT (id = ?) AND lower(description) LIKE ?', + 'active', 'pending', 'deferred', + params[:id], '%' + params[:q].downcase + '%' ], :order => 'description ASC', :limit => 10 ) end else # New todo - TODO: Filter on project - @items = current_user.todos.find(:all, + @items = current_user.todos.find(:all, :select => 'description, project_id, context_id, created_at', - :conditions => [ '(todos.state = ? OR todos.state = ? OR todos.state = ?) AND lower(description) LIKE ?', - 'active', 'pending', 'deferred', - '%' + params[:q].downcase + '%' ], + :conditions => [ '(todos.state = ? OR todos.state = ? OR todos.state = ?) AND lower(description) LIKE ?', + 'active', 'pending', 'deferred', + '%' + params[:q].downcase + '%' ], :order => 'description ASC', :limit => 10 ) @@ -641,22 +729,38 @@ class TodosController < ApplicationController def convert_to_project @todo = current_user.todos.find(params[:id]) @project = current_user.projects.new(:name => @todo.description, :description => @todo.notes, - :default_context => @todo.context) + :default_context => @todo.context) @todo.destroy @project.save! redirect_to project_url(@project) end - + + def show_notes + @return_path=cookies[:mobile_url] ? cookies[:mobile_url] : mobile_path + respond_to do |format| + format.html { + redirect_to home_path, "Viewing note of todo is not implemented" + } + format.m { + render:action => "mobile_show_notes" + } + end + end + private def get_todo_from_params @todo = current_user.todos.find(params['id']) end + + def find_and_activate_ready + current_user.deferred_todos.find_and_activate_ready + end def init @source_view = params['_source_view'] || 'todo' init_data_for_sidebar unless mobile? - init_todos + init_todos end def with_feed_query_scope(&block) @@ -725,7 +829,7 @@ class TodosController < ApplicationController end else yield - end + end end def with_limit_scope(&block) @@ -751,7 +855,7 @@ class TodosController < ApplicationController with_limit_scope do if mobile? - init_todos_for_mobile_view + init_todos_for_mobile_view else # Note: these next two finds were previously using @@ -761,10 +865,10 @@ class TodosController < ApplicationController @todos = Todo.find(:all, :conditions => ['todos.user_id = ?', current_user.id], :include => [ :project, :context, :tags ]) # Exclude hidden projects from the home page - @not_done_todos = Todo.find(:all, - :conditions => ['todos.user_id = ? AND contexts.hide = ? AND (projects.state = ? OR todos.project_id IS NULL)', - current_user.id, false, 'active'], - :order => "todos.due IS NULL, todos.due ASC, todos.created_at ASC", + @not_done_todos = Todo.find(:all, + :conditions => ['todos.user_id = ? AND contexts.hide = ? AND (projects.state = ? OR todos.project_id IS NULL)', + current_user.id, false, 'active'], + :order => "todos.due IS NULL, todos.due ASC, todos.created_at ASC", :include => [ :project, :context, :tags ]) end @@ -778,10 +882,10 @@ class TodosController < ApplicationController # but that broke with_scope for :limit # Exclude hidden projects from the home page - @not_done_todos = Todo.find(:all, - :conditions => ['todos.user_id = ? AND todos.state = ? AND contexts.hide = ? AND (projects.state = ? OR todos.project_id IS NULL)', - current_user.id, 'active', false, 'active'], - :order => "todos.due IS NULL, todos.due ASC, todos.created_at ASC", + @not_done_todos = Todo.find(:all, + :conditions => ['todos.user_id = ? AND todos.state = ? AND contexts.hide = ? AND (projects.state = ? OR todos.project_id IS NULL)', + current_user.id, 'active', false, 'active'], + :order => "todos.due IS NULL, todos.due ASC, todos.created_at ASC", :include => [ :project, :context, :tags ]) end @@ -789,8 +893,8 @@ class TodosController < ApplicationController source_view do |from| from.todo do @down_count = Todo.count( - :all, - :conditions => ['todos.user_id = ? and todos.state = ? and contexts.hide = ? AND (projects.state = ? OR todos.project_id IS NULL)', current_user.id, 'active', false, 'active'], + :all, + :conditions => ['todos.user_id = ? and todos.state = ? and contexts.hide = ? AND (projects.state = ? OR todos.project_id IS NULL)', current_user.id, 'active', false, 'active'], :include => [ :project, :context ]) # #@down_count = Todo.count_by_sql(['SELECT COUNT(*) FROM todos, # contexts WHERE todos.context_id = contexts.id and todos.user_id = ? @@ -821,12 +925,12 @@ class TodosController < ApplicationController @not_done_todos.empty? ? @down_count = 0 : @down_count = @not_done_todos.size end end - end + end def determine_remaining_in_context_count(context_id = @todo.context_id) source_view do |from| from.deferred { @remaining_in_context = current_user.contexts.find(context_id).deferred_todo_count } - from.tag { + from.tag { tag = Tag.find_by_name(params['_tag_name']) if tag.nil? tag = Tag.new(:name => params['tag']) @@ -835,7 +939,7 @@ class TodosController < ApplicationController } end @remaining_in_context = current_user.contexts.find(context_id).not_done_todo_count if @remaining_in_context.nil? - end + end def determine_completed_count source_view do |from| @@ -994,7 +1098,7 @@ class TodosController < ApplicationController end end end - return new_recurring_todo + return new_recurring_todo end def get_due_id_for_calendar(due) @@ -1035,11 +1139,11 @@ class TodosController < ApplicationController when "due_this_month" return 0 == current_user.todos.not_completed.count(:all, :conditions => ['todos.due > ? AND todos.due <= ?', due_next_week_date, due_this_month_date]) - when "due_after_this_month" + when "due_after_this_month" return 0 == current_user.todos.not_completed.count(:all, :conditions => ['todos.due > ?', due_this_month_date]) else - raise Exception.new, "unknown due id for calendar: '#{id}'" + raise Exception.new, "unknown due id for calendar: '#{id}'" end end @@ -1116,4 +1220,20 @@ class TodosController < ApplicationController end end + + private + + def project_specified_by_name(project_name) + return false unless params['project_id'].blank? + return false if project_name.blank? + return false if project_name == 'None' + true + end + + def context_specified_by_name(context_name) + return false unless params['context_id'].blank? + return false if context_name.blank? + true + end + end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 387c7689..1198837f 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -90,7 +90,7 @@ class UsersController < ApplicationController unless user.valid? session['new_user'] = user - redirect_to :action => 'new' + redirect_to signup_path return end diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb index 994fb305..fd155d03 100644 --- a/app/helpers/todos_helper.rb +++ b/app/helpers/todos_helper.rb @@ -119,10 +119,8 @@ module TodosHelper def grip_span unless @todo.completed? image_tag('grip.png', :width => '7', :height => '16', :border => '0', - :title => 'Drag onto another action to make it depend on that action', - :class => 'grip') + - image_tag('blank.png', :width => 16, :height => 16, :border => 0, - :title => "Drop an action to make it depend on this action", :class => 'successor_target drop_target') + :title => 'Drag onto another action to make it depend on that action', + :class => 'grip') end end @@ -227,14 +225,6 @@ module TodosHelper end end - def calendar_setup( input_field ) - # TODO:jQuery - #str = "Calendar.setup({ ifFormat:\"#{prefs.date_format}\"" - #str << ",firstDay:#{prefs.week_starts},showOthers:true,range:[2004, 2010]" - #str << ",step:1,inputField:\"" + input_field + "\",cache:true,align:\"TR\" })\n" - #javascript_tag str - end - def item_container_id (todo) if todo.deferred? or todo.pending? return "tickleritems" @@ -253,11 +243,14 @@ module TodosHelper return false if source_view_is(:context) && (@todo.project.hidden? || @todo.project.completed?) end + return false if (source_view_is(:tag) && !@todo.tags.include?(@tag_name)) + return true if source_view_is(:deferred) && @todo.deferred? return true if source_view_is(:project) && @todo.project.hidden? && @todo.project_hidden? return true if source_view_is(:project) && @todo.deferred? return true if !source_view_is(:deferred) && @todo.active? return true if source_view_is(:project) && @todo.pending? + return true if source_view_is(:tag) && @todo.pending? return false end diff --git a/app/models/recurring_todo.rb b/app/models/recurring_todo.rb index 86c549f1..64a95889 100644 --- a/app/models/recurring_todo.rb +++ b/app/models/recurring_todo.rb @@ -29,13 +29,16 @@ class RecurringTodo < ActiveRecord::Base validates_presence_of :description validates_presence_of :recurring_period + validates_presence_of :target + validates_presence_of :ends_on + validates_presence_of :context + validates_length_of :description, :maximum => 100 validates_length_of :notes, :maximum => 60000, :allow_nil => true - validates_presence_of :context - validate :period_specific_validations validate :starts_and_ends_on_validations + validate :set_recurrence_on_validations def period_specific_validations periods = %W[daily weekly monthly yearly] @@ -57,9 +60,8 @@ class RecurringTodo < ActiveRecord::Base errors.add_to_base("Every other nth week may not be empty for recurrence setting") end something_set = false - %w{sunday monday tuesday wednesday thursday friday}.each do |day| + %w{sunday monday tuesday wednesday thursday friday saturday}.each do |day| something_set ||= self.send("on_#{day}") - end errors.add_to_base("You must specify at least one day on which the todo recurs") if !something_set end @@ -104,6 +106,21 @@ class RecurringTodo < ActiveRecord::Base errors.add_to_base("The end of the recurrence is not selected") unless ends_on == "no_end_date" end end + + def set_recurrence_on_validations + # show always or x days before due date. x not null + case self.target + when 'show_from_date' + # no validations + when 'due_date' + errors.add_to_base("Please select when to show the action") if show_always.nil? + unless show_always + errors.add_to_base("Please fill in the number of days to show the todo before the due date") if show_from_delta.nil? || show_from_delta.blank? + end + else + raise Exception.new, "unexpected value of recurrence target selector '#{self.recurrence_target}'" + end + end # the following recurrence patterns can be stored: # @@ -388,9 +405,9 @@ class RecurringTodo < ActiveRecord::Base end def recurrence_pattern + return "invalid repeat pattern" if every_other1.nil? case recurring_period when 'daily' - return "invalid repeat pattern" if every_other1.nil? if only_work_days return "on work days" else @@ -401,21 +418,19 @@ class RecurringTodo < ActiveRecord::Base end end when 'weekly' - return "invalid repeat pattern" if every_other1.nil? if every_other1 > 1 return "every #{every_other1} weeks" else return 'weekly' end when 'monthly' - return "invalid repeat pattern" if every_other1.nil? || every_other2.nil? + return "invalid repeat pattern" if every_other2.nil? if self.recurrence_selector == 0 return "every #{self.every_other2} month#{self.every_other2>1?'s':''} on day #{self.every_other1}" else return "every #{self.xth} #{self.day_of_week} of every #{self.every_other2} month#{self.every_other2>1?'s':''}" end when 'yearly' - return "invalid repeat pattern" if every_other1.nil? if self.recurrence_selector == 0 return "every year on #{self.month_of_year} #{self.every_other1}" else @@ -496,30 +511,12 @@ class RecurringTodo < ActiveRecord::Base # # assumes self.recurring_period == 'daily' - # determine start - if previous.nil? - start = self.start_from.nil? ? Time.zone.now : self.start_from - else - # use the next day - start = previous + 1.day - - 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 - end + start = determine_start(previous, 1.day) if self.only_work_days - if start.wday() >= 1 && start.wday() <= 5 # 1=monday; 5=friday - return start - else - if start.wday() == 0 # sunday - return start + 1.day - else # saturday - return start + 2.day - end - end + return start + 2.day if start.wday() == 6 # saturday + return start + 1.day if start.wday() == 0 # sunday + return start else # every nth day; n = every_other1 # if there was no previous todo, do not add n: the first todo starts on # today or on start_from @@ -535,8 +532,8 @@ class RecurringTodo < ActiveRecord::Base start = previous + 1.day if start.wday() == 0 # we went to a new week , go to the nth next week and find first match - # that week - start += self.every_other1.week + # that week. Note that we already went into the next week, so -1 + start += (self.every_other1-1).week end unless self.start_from.nil? # check if the start_from date is later than previous. If so, use @@ -726,16 +723,16 @@ class RecurringTodo < ActiveRecord::Base end protected - - def validate - errors.add("", "At least one day must be selected in the weekly pattern") if self.every_day == ' ' - end - - def determine_start(previous) + + 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 + start = Time.zone.now if Time.zone.now > start else - start = previous + start = previous + offset unless self.start_from.nil? # check if the start_from date is later than previous. If so, use diff --git a/app/models/todo.rb b/app/models/todo.rb index 7fd64628..13ae14b5 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -18,12 +18,19 @@ class Todo < ActiveRecord::Base named_scope :active, :conditions => { :state => 'active' } named_scope :not_completed, :conditions => ['NOT (todos.state = ? )', 'completed'] + named_scope :completed, :conditions => ["NOT completed_at IS NULL"] named_scope :are_due, :conditions => ['NOT (todos.due IS NULL)'] + named_scope :deferred, :conditions => ["completed_at IS NULL AND NOT show_from IS NULL"] + named_scope :blocked, :conditions => ['todos.state = ?', 'pending'] STARRED_TAG_NAME = "starred" + + # regular expressions for dependencies 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 acts_as_state_machine :initial => :active, :column => 'state' @@ -94,27 +101,36 @@ class Todo < ActiveRecord::Base def todo_from_specification(specification) # Split specification into parts: description - re_parts = /"(#{RE_TODO})"\s<"(#{RE_CONTEXT})";\s"(#{RE_PROJECT})">/ - parts = specification.scan(re_parts) + parts = specification.scan(RE_PARTS) return nil unless parts.length == 1 return nil unless parts[0].length == 3 todo_description = parts[0][0] context_name = parts[0][1] + project_name = parts[0][2] + + # find the project + project_id = nil; + unless project_name == "(none)" + project = Project.first(:conditions => { + :user_id => self.user.id, + :name => project_name + }) + project_id = project.id unless project.nil? + end + todos = Todo.all( :joins => :context, :conditions => { :description => todo_description, - :contexts => {:name => context_name} + :user_id => self.user.id, + :contexts => {:name => context_name}, + :project_id => project_id } ) return nil if todos.empty? - # todos now contains all todos with matching description and context - # TODO: Is this possible to do with a single query? - todos.each do |todo| - project_name = todo.project.is_a?(NullProject) ? "(none)" : todo.project.name - return todo if project_name == parts[0][2] - end - return nil + + # TODO: what todo if there are more than one todo that fit the specification + return todos[0] end def validate @@ -267,9 +283,8 @@ class Todo < ActiveRecord::Base def add_predecessor_list(predecessor_list) return unless predecessor_list.kind_of? String - # Split into list - re_specification = /"#{RE_TODO}"\s<"#{RE_CONTEXT}";\s"#{RE_PROJECT}">/ - @predecessor_array = predecessor_list.scan(re_specification) + @predecessor_array = predecessor_list.scan(RE_SPEC) + return @predecessor_array end def add_predecessor(t) @@ -287,10 +302,6 @@ class Todo < ActiveRecord::Base return successors.find_all {|t| t.active? or t.deferred?} end - def notes=(value) - super(value.try(:gsub, //, '>')) - end - def raw_notes=(value) self[:notes] = value end diff --git a/app/views/contexts/_context.rhtml b/app/views/contexts/_context.rhtml index 7b33ee9a..105b66bb 100644 --- a/app/views/contexts/_context.rhtml +++ b/app/views/contexts/_context.rhtml @@ -10,11 +10,11 @@ <%= link_to_context( context ) %> <% end %> +

Currently there are no incomplete actions in this context

<%= render :partial => "todos/todo", :collection => @not_done, :locals => { :parent_container_type => "context" } %>
-
diff --git a/app/views/contexts/_context_listing.rhtml b/app/views/contexts/_context_listing.rhtml index df90a138..f4c8ce5c 100644 --- a/app/views/contexts/_context_listing.rhtml +++ b/app/views/contexts/_context_listing.rhtml @@ -26,7 +26,7 @@ :with => "'_source_view=#{@source_view}'", :before => "$('#{dom_id(context)}').block({message:null});", :complete => "$('#{dom_id(context)}').unblock();", - :confirm => "Are you sure that you want to delete the context '#{context.name}'?", + :confirm => "Are you sure that you want to delete the context '#{context.name}'? Be aware that this will also delete all actions in this context!", :html => { :id => dom_id(context, 'delete') } ) %> <%= link_to_remote( diff --git a/app/views/contexts/_mobile_context.rhtml b/app/views/contexts/_mobile_context.rhtml index 52236e1f..46d45db9 100644 --- a/app/views/contexts/_mobile_context.rhtml +++ b/app/views/contexts/_mobile_context.rhtml @@ -5,7 +5,7 @@ if not @not_done.empty? # only show a context when there are actions in it -%> -

<%=mobile_context.name%>

+

<%= link_to mobile_context.name, context_path(mobile_context, :format => 'm') %>