diff --git a/tracks/app/controllers/search_controller.rb b/tracks/app/controllers/search_controller.rb new file mode 100644 index 00000000..baaa25dd --- /dev/null +++ b/tracks/app/controllers/search_controller.rb @@ -0,0 +1,27 @@ +class SearchController < ApplicationController + + helper :todos, :application, :notes, :projects + + def results + @source_view = params['_source_view'] || 'search' + @page_title = "TRACKS::Search Results for #{params[:search]}" + terms = '%' + params[:search] + '%' + @found_todos = current_user.todos.find(:all, :conditions => ["todos.description LIKE ? OR todos.notes LIKE ?", terms, terms], :include => [:tags, :project, :context]) + @found_projects = current_user.projects.find(:all, :conditions => ["name LIKE ? or description LIKE ?", terms, terms]) + @found_notes = current_user.notes.find(:all, :conditions => ["body LIKE ?", terms]) + + @count = @found_todos.size + @found_projects.size + @found_notes.size + + init_not_done_counts(['project']) + init_project_hidden_todo_counts(['project']) + end + + def index + @page_title = "TRACKS::Search" + end + + def init + @source_view = params['_source_view'] || 'search' + end + +end diff --git a/tracks/app/controllers/todos_controller.rb b/tracks/app/controllers/todos_controller.rb index ffb03d5d..a334e189 100644 --- a/tracks/app/controllers/todos_controller.rb +++ b/tracks/app/controllers/todos_controller.rb @@ -67,6 +67,9 @@ class TodosController < ApplicationController respond_to do |format| format.html { redirect_to :action => "index" } format.m do + @return_path=cookies[:mobile_url] + # todo: use function for this fixed path + @return_path='/mobile' if @return_path.nil? if @saved redirect_to mobile_abbrev_url else diff --git a/tracks/app/helpers/search_helper.rb b/tracks/app/helpers/search_helper.rb new file mode 100644 index 00000000..56867b18 --- /dev/null +++ b/tracks/app/helpers/search_helper.rb @@ -0,0 +1,3 @@ +module SearchHelper + +end diff --git a/tracks/app/helpers/todos_helper.rb b/tracks/app/helpers/todos_helper.rb index d171f746..7b9ca745 100644 --- a/tracks/app/helpers/todos_helper.rb +++ b/tracks/app/helpers/todos_helper.rb @@ -1,268 +1,268 @@ -module TodosHelper - - # #require 'users_controller' Counts the number of incomplete items in the - # specified context - # - def count_items(context) - count = Todo.find_all("done=0 AND context_id=#{context.id}").length - end - - def form_remote_tag_edit_todo( &block ) - form_tag( - todo_path(@todo), { - :method => :put, - :id => dom_id(@todo, 'form'), - :class => dom_id(@todo, 'form') + " inline-form edit_todo_form" }, - &block ) - apply_behavior 'form.edit_todo_form', make_remote_form( - :method => :put, - :before => "this.down('button.positive').startWaiting()", - :loaded => "this.down('button.positive').stopWaiting()", - :condition => "!(this.down('button.positive').isWaiting())"), - :prevent_default => true - end - - def remote_delete_icon - parameters = "_source_view=#{@source_view}" - parameters += "&_tag_name=#{@tag_name}" if @source_view == 'tag' - str = link_to( image_tag_for_delete, - todo_path(@todo), :id => "delete_icon_"+@todo.id.to_s, - :class => "icon delete_icon", :title => "delete the action '#{@todo.description}'") - apply_behavior '.item-container a.delete_icon:click', :prevent_default => true do |page| - page.confirming "'Are you sure that you want to ' + this.title + '?'" do - page << "itemContainer = this.up('.item-container'); itemContainer.startWaiting();" - page << remote_to_href(:method => 'delete', :with => "'#{parameters}'", :complete => "itemContainer.stopWaiting();") - end - end - str - end - - def remote_star_icon - str = link_to( image_tag_for_star(@todo), - toggle_star_todo_path(@todo), - :class => "icon star_item", :title => "star the action '#{@todo.description}'") - apply_behavior '.item-container a.star_item:click', - remote_to_href(:method => 'put', :with => "{ _source_view : '#{@source_view}' }"), - :prevent_default => true - str - end - - def remote_edit_icon - parameters = "_source_view=#{@source_view}" - parameters += "&_tag_name=#{@tag_name}" if @source_view == 'tag' - if !@todo.completed? - str = link_to( image_tag_for_edit, - edit_todo_path(@todo), - :class => "icon edit_icon") - apply_behavior '.item-container a.edit_icon:click', :prevent_default => true do |page| - page << "Effect.Pulsate(this);" - page << remote_to_href(:method => 'get', :with => "'#{parameters}'") - end - else - str = '' + image_tag("blank.png") + " " - end - str - end - - def remote_toggle_checkbox - str = check_box_tag('item_id', toggle_check_todo_path(@todo), @todo.completed?, :class => 'item-checkbox') - parameters = "_source_view=#{@source_view}" - parameters += "&_tag_name=#{@tag_name}" if @source_view == 'tag' - apply_behavior '.item-container input.item-checkbox:click', - remote_function(:url => javascript_variable('this.value'), :method => 'put', - :with => "'#{parameters}'") - str - end - - def date_span - if @todo.completed? - "#{format_date( @todo.completed_at )}" - elsif @todo.deferred? - show_date( @todo.show_from ) - else - due_date( @todo.due ) - end - end - - def tag_list_text - @todo.tags.collect{|t| t.name}.join(', ') - end - - def tag_list - tags_except_starred = @todo.tags.reject{|t| t.name == Todo::STARRED_TAG_NAME} - tag_list = tags_except_starred.collect{|t| "" + link_to(t.name, :controller => "todos", :action => "tag", :id => t.name) + ""}.join('') - "" - end - - def tag_list_mobile - tags_except_starred = @todo.tags.reject{|t| t.name == Todo::STARRED_TAG_NAME} - # removed the link. TODO: add link to mobile view of tagged actions - tag_list = tags_except_starred.collect{|t| - "" + - link_to(t.name, {:action => "tag", :controller => "todos", :id => t.name+".m"}) + - # link_to(t.name, formatted_tag_path(t, :m)) + - ""}.join('') - "" - end - - def deferred_due_date - if @todo.deferred? && @todo.due - "(action due on #{format_date(@todo.due)})" - end - end - - def project_and_context_links(parent_container_type, opts = {}) - str = '' - if @todo.completed? - str += @todo.context.name unless opts[:suppress_context] - should_suppress_project = opts[:suppress_project] || @todo.project.nil? - str += ", " unless str.blank? || should_suppress_project - str += @todo.project.name unless should_suppress_project - str = "(#{str})" unless str.blank? - else - if (['project', 'tag', 'stats'].include?(parent_container_type)) - str << item_link_to_context( @todo ) - end - if (['context', 'tickler', 'tag', 'stats'].include?(parent_container_type)) && @todo.project_id - str << item_link_to_project( @todo ) - end - end - return str - end - - # Uses the 'staleness_starts' value from settings.yml (in days) to colour the - # background of the action appropriately according to the age of the creation - # date: - # * l1: created more than 1 x staleness_starts, but < 2 x staleness_starts - # * l2: created more than 2 x staleness_starts, but < 3 x staleness_starts - # * l3: created more than 3 x staleness_starts - # - def staleness_class(item) - if item.due || item.completed? - return "" - elsif item.created_at < user_time - (prefs.staleness_starts * 3).days - return " stale_l3" - elsif item.created_at < user_time - (prefs.staleness_starts * 2).days - return " stale_l2" - elsif item.created_at < user_time - (prefs.staleness_starts).days - return " stale_l1" - else - return "" - end - end - - # Check show_from date in comparison to today's date Flag up date - # appropriately with a 'traffic light' colour code - # - def show_date(d) - if d == nil - return "" - end - - days = days_from_today(d) - - case days - # overdue or due very soon! sound the alarm! - when -1000..-1 - "Scheduled to show " + (days * -1).to_s + " days ago " - when 0 - "Show Today " - when 1 - "Show Tomorrow " - # due 2-7 days away - when 2..7 - if prefs.due_style == Preference.due_styles[:due_on] - "Show on " + d.strftime("%A") + " " - else - "Show in " + days.to_s + " days " - end - # more than a week away - relax - else - "Show in " + days.to_s + " days " - end - end - - def calendar_setup( input_field ) - 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 - if source_view_is :project - return "p#{@todo.project_id}" if @todo.active? - return "tickler" if @todo.deferred? - end - return "c#{@todo.context_id}" - end - - def should_show_new_item - - if @todo.project.nil? == false - # do not show new actions that were added to hidden or completed projects - # on home page and context page - return false if source_view_is(:todo) && (@todo.project.hidden? || @todo.project.completed?) - return false if source_view_is(:context) && (@todo.project.hidden? || @todo.project.completed?) - end - - return true if source_view_is(:deferred) && @todo.deferred? - return true if source_view_is(:project) && @todo.project.hidden? && @todo.project_hidden? - return true if source_view_is(:project) && @todo.deferred? - return true if !source_view_is(:deferred) && @todo.active? - return false - end - - def parent_container_type - return 'tickler' if source_view_is :deferred - return 'project' if source_view_is :project - return 'stats' if source_view_is :stats - return 'context' - end - - def empty_container_msg_div_id - return "tickler-empty-nd" if source_view_is(:project) && @todo.deferred? - return "p#{@todo.project_id}empty-nd" if source_view_is :project - return "c#{@todo.context_id}empty-nd" - end - - def project_names_for_autocomplete - array_or_string_for_javascript( ['None'] + @projects.select{ |p| p.active? }.collect{|p| escape_javascript(p.name) } ) - end - - def context_names_for_autocomplete - # #return array_or_string_for_javascript(['Create a new context']) if - # @contexts.empty? - array_or_string_for_javascript( @contexts.collect{|c| escape_javascript(c.name) } ) - end - - def format_ical_notes(notes) - split_notes = notes.split(/\n/) - joined_notes = split_notes.join("\\n") - end - - def formatted_pagination(total) - s = will_paginate(@todos) - (s.gsub /(<\/[^<]+>)/, '\1 ').chomp(' ') - end - - def date_field_tag(name, id, value = nil, options = {}) - text_field_tag name, value, {"size" => 12, "id" => id, "class" => "Date", "onfocus" => "Calendar.setup", "autocomplete" => "off"}.update(options.stringify_keys) - end - - private - - def image_tag_for_delete - image_tag("blank.png", :title =>"Delete action", :class=>"delete_item") - end - - def image_tag_for_edit - image_tag("blank.png", :title =>"Edit action", :class=>"edit_item", :id=> dom_id(@todo, 'edit_icon')) - end - - def image_tag_for_star(todo) - class_str = todo.starred? ? "starred_todo" : "unstarred_todo" - image_tag("blank.png", :title =>"Star action", :class => class_str) - end - -end +module TodosHelper + + # #require 'users_controller' Counts the number of incomplete items in the + # specified context + # + def count_items(context) + count = Todo.find_all("done=0 AND context_id=#{context.id}").length + end + + def form_remote_tag_edit_todo( &block ) + form_tag( + todo_path(@todo), { + :method => :put, + :id => dom_id(@todo, 'form'), + :class => dom_id(@todo, 'form') + " inline-form edit_todo_form" }, + &block ) + apply_behavior 'form.edit_todo_form', make_remote_form( + :method => :put, + :before => "this.down('button.positive').startWaiting()", + :loaded => "this.down('button.positive').stopWaiting()", + :condition => "!(this.down('button.positive').isWaiting())"), + :prevent_default => true + end + + def remote_delete_icon + parameters = "_source_view=#{@source_view}" + parameters += "&_tag_name=#{@tag_name}" if @source_view == 'tag' + str = link_to( image_tag_for_delete, + todo_path(@todo), :id => "delete_icon_"+@todo.id.to_s, + :class => "icon delete_icon", :title => "delete the action '#{@todo.description}'") + apply_behavior '.item-container a.delete_icon:click', :prevent_default => true do |page| + page.confirming "'Are you sure that you want to ' + this.title + '?'" do + page << "itemContainer = this.up('.item-container'); itemContainer.startWaiting();" + page << remote_to_href(:method => 'delete', :with => "'#{parameters}'", :complete => "itemContainer.stopWaiting();") + end + end + str + end + + def remote_star_icon + str = link_to( image_tag_for_star(@todo), + toggle_star_todo_path(@todo), + :class => "icon star_item", :title => "star the action '#{@todo.description}'") + apply_behavior '.item-container a.star_item:click', + remote_to_href(:method => 'put', :with => "{ _source_view : '#{@source_view}' }"), + :prevent_default => true + str + end + + def remote_edit_icon + parameters = "_source_view=#{@source_view}" + parameters += "&_tag_name=#{@tag_name}" if @source_view == 'tag' + if !@todo.completed? + str = link_to( image_tag_for_edit, + edit_todo_path(@todo), + :class => "icon edit_icon") + apply_behavior '.item-container a.edit_icon:click', :prevent_default => true do |page| + page << "Effect.Pulsate(this);" + page << remote_to_href(:method => 'get', :with => "'#{parameters}'") + end + else + str = '' + image_tag("blank.png") + " " + end + str + end + + def remote_toggle_checkbox + str = check_box_tag('item_id', toggle_check_todo_path(@todo), @todo.completed?, :class => 'item-checkbox') + parameters = "_source_view=#{@source_view}" + parameters += "&_tag_name=#{@tag_name}" if @source_view == 'tag' + apply_behavior '.item-container input.item-checkbox:click', + remote_function(:url => javascript_variable('this.value'), :method => 'put', + :with => "'#{parameters}'") + str + end + + def date_span + if @todo.completed? + "#{format_date( @todo.completed_at )}" + elsif @todo.deferred? + show_date( @todo.show_from ) + else + due_date( @todo.due ) + end + end + + def tag_list_text + @todo.tags.collect{|t| t.name}.join(', ') + end + + def tag_list + tags_except_starred = @todo.tags.reject{|t| t.name == Todo::STARRED_TAG_NAME} + tag_list = tags_except_starred.collect{|t| "" + link_to(t.name, :controller => "todos", :action => "tag", :id => t.name) + ""}.join('') + "" + end + + def tag_list_mobile + tags_except_starred = @todo.tags.reject{|t| t.name == Todo::STARRED_TAG_NAME} + # removed the link. TODO: add link to mobile view of tagged actions + tag_list = tags_except_starred.collect{|t| + "" + + link_to(t.name, {:action => "tag", :controller => "todos", :id => t.name+".m"}) + + # link_to(t.name, formatted_tag_path(t, :m)) + + ""}.join('') + "" + end + + def deferred_due_date + if @todo.deferred? && @todo.due + "(action due on #{format_date(@todo.due)})" + end + end + + def project_and_context_links(parent_container_type, opts = {}) + str = '' + if @todo.completed? + str += @todo.context.name unless opts[:suppress_context] + should_suppress_project = opts[:suppress_project] || @todo.project.nil? + str += ", " unless str.blank? || should_suppress_project + str += @todo.project.name unless should_suppress_project + str = "(#{str})" unless str.blank? + else + if (['project', 'tag', 'stats', 'search'].include?(parent_container_type)) + str << item_link_to_context( @todo ) + end + if (['context', 'tickler', 'tag', 'stats', 'search'].include?(parent_container_type)) && @todo.project_id + str << item_link_to_project( @todo ) + end + end + return str + end + + # Uses the 'staleness_starts' value from settings.yml (in days) to colour the + # background of the action appropriately according to the age of the creation + # date: + # * l1: created more than 1 x staleness_starts, but < 2 x staleness_starts + # * l2: created more than 2 x staleness_starts, but < 3 x staleness_starts + # * l3: created more than 3 x staleness_starts + # + def staleness_class(item) + if item.due || item.completed? + return "" + elsif item.created_at < user_time - (prefs.staleness_starts * 3).days + return " stale_l3" + elsif item.created_at < user_time - (prefs.staleness_starts * 2).days + return " stale_l2" + elsif item.created_at < user_time - (prefs.staleness_starts).days + return " stale_l1" + else + return "" + end + end + + # Check show_from date in comparison to today's date Flag up date + # appropriately with a 'traffic light' colour code + # + def show_date(d) + if d == nil + return "" + end + + days = days_from_today(d) + + case days + # overdue or due very soon! sound the alarm! + when -1000..-1 + "Scheduled to show " + (days * -1).to_s + " days ago " + when 0 + "Show Today " + when 1 + "Show Tomorrow " + # due 2-7 days away + when 2..7 + if prefs.due_style == Preference.due_styles[:due_on] + "Show on " + d.strftime("%A") + " " + else + "Show in " + days.to_s + " days " + end + # more than a week away - relax + else + "Show in " + days.to_s + " days " + end + end + + def calendar_setup( input_field ) + 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 + if source_view_is :project + return "p#{@todo.project_id}" if @todo.active? + return "tickler" if @todo.deferred? + end + return "c#{@todo.context_id}" + end + + def should_show_new_item + + if @todo.project.nil? == false + # do not show new actions that were added to hidden or completed projects + # on home page and context page + return false if source_view_is(:todo) && (@todo.project.hidden? || @todo.project.completed?) + return false if source_view_is(:context) && (@todo.project.hidden? || @todo.project.completed?) + end + + return true if source_view_is(:deferred) && @todo.deferred? + return true if source_view_is(:project) && @todo.project.hidden? && @todo.project_hidden? + return true if source_view_is(:project) && @todo.deferred? + return true if !source_view_is(:deferred) && @todo.active? + return false + end + + def parent_container_type + return 'tickler' if source_view_is :deferred + return 'project' if source_view_is :project + return 'stats' if source_view_is :stats + return 'context' + end + + def empty_container_msg_div_id + return "tickler-empty-nd" if source_view_is(:project) && @todo.deferred? + return "p#{@todo.project_id}empty-nd" if source_view_is :project + return "c#{@todo.context_id}empty-nd" + end + + def project_names_for_autocomplete + array_or_string_for_javascript( ['None'] + @projects.select{ |p| p.active? }.collect{|p| escape_javascript(p.name) } ) + end + + def context_names_for_autocomplete + # #return array_or_string_for_javascript(['Create a new context']) if + # @contexts.empty? + array_or_string_for_javascript( @contexts.collect{|c| escape_javascript(c.name) } ) + end + + def format_ical_notes(notes) + split_notes = notes.split(/\n/) + joined_notes = split_notes.join("\\n") + end + + def formatted_pagination(total) + s = will_paginate(@todos) + (s.gsub /(<\/[^<]+>)/, '\1 ').chomp(' ') + end + + def date_field_tag(name, id, value = nil, options = {}) + text_field_tag name, value, {"size" => 12, "id" => id, "class" => "Date", "onfocus" => "Calendar.setup", "autocomplete" => "off"}.update(options.stringify_keys) + end + + private + + def image_tag_for_delete + image_tag("blank.png", :title =>"Delete action", :class=>"delete_item") + end + + def image_tag_for_edit + image_tag("blank.png", :title =>"Edit action", :class=>"edit_item", :id=> dom_id(@todo, 'edit_icon')) + end + + def image_tag_for_star(todo) + class_str = todo.starred? ? "starred_todo" : "unstarred_todo" + image_tag("blank.png", :title =>"Star action", :class => class_str) + end + +end diff --git a/tracks/app/models/todo.rb b/tracks/app/models/todo.rb index c3754e53..c7a3c38d 100644 --- a/tracks/app/models/todo.rb +++ b/tracks/app/models/todo.rb @@ -8,8 +8,8 @@ class Todo < ActiveRecord::Base acts_as_state_machine :initial => :active, :column => 'state' - # when entering active state, also remove completed_at date. - # Looks like :exit of state completed is not run, see #679 + # when entering active state, also remove completed_at date. Looks like :exit + # of state completed is not run, see #679 state :active, :enter => Proc.new { |t| t[:show_from], t.completed_at = nil, nil } state :project_hidden state :completed, :enter => Proc.new { |t| t.completed_at = Time.now.utc }, :exit => Proc.new { |t| t.completed_at = nil } @@ -38,8 +38,8 @@ class Todo < ActiveRecord::Base attr_protected :user - # Description field can't be empty, and must be < 100 bytes - # Notes must be < 60,000 bytes (65,000 actually, but I'm being cautious) + # Description field can't be empty, and must be < 100 bytes Notes must be < + # 60,000 bytes (65,000 actually, but I'm being cautious) validates_presence_of :description validates_length_of :description, :maximum => 100 validates_length_of :notes, :maximum => 60000, :allow_nil => true @@ -91,7 +91,8 @@ class Todo < ActiveRecord::Base alias_method :original_run_initial_state_actions, :run_initial_state_actions def run_initial_state_actions - #only run the initial state actions if the standard initial state hasn't been changed + # only run the initial state actions if the standard initial state hasn't + # been changed if self.class.initial_state.to_sym == current_state original_run_initial_state_actions end diff --git a/tracks/app/views/layouts/standard.html.erb b/tracks/app/views/layouts/standard.html.erb index d0f0e14e..b1535484 100644 --- a/tracks/app/views/layouts/standard.html.erb +++ b/tracks/app/views/layouts/standard.html.erb @@ -59,6 +59,7 @@ window.onload=function(){
Project has been marked as completed
- <% elsif @project.completed? -%> + <% elsif project.completed? -%>Project has been marked as hidden
<% end -%>