diff --git a/app/controllers/contexts_controller.rb b/app/controllers/contexts_controller.rb index bd14ef4b..4a9984b8 100644 --- a/app/controllers/contexts_controller.rb +++ b/app/controllers/contexts_controller.rb @@ -144,6 +144,7 @@ class ContextsController < ApplicationController @not_done = @not_done_todos.select {|t| t.context_id == @context.id } @down_count = @not_done.size cookies[:mobile_url]=request.request_uri + @mobile_from_context = @context.id render :action => 'mobile_show_context' end end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 668b9d45..3889b8bd 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -1,282 +1,283 @@ -class ProjectsController < ApplicationController - - helper :application, :todos, :notes - before_filter :set_source_view - before_filter :set_project_from_params, :only => [:update, :destroy, :show, :edit] - before_filter :default_context_filter, :only => [:create, :update] - skip_before_filter :login_required, :only => [:index] - prepend_before_filter :login_or_feed_token_required, :only => [:index] - session :off, :only => :index, :if => Proc.new { |req| ['rss','atom','txt'].include?(req.parameters[:format]) } - - def index - @projects = current_user.projects(true) - if params[:projects_and_actions] - projects_and_actions - else - @contexts = current_user.contexts(true) - init_not_done_counts(['project']) - if params[:only_active_with_no_next_actions] - @projects = @projects.select { |p| p.active? && count_undone_todos(p) == 0 } - end - init_project_hidden_todo_counts(['project']) - respond_to do |format| - format.html &render_projects_html - format.m &render_projects_mobile - format.xml { render :xml => @projects.to_xml( :except => :user_id ) } - format.rss &render_rss_feed - format.atom &render_atom_feed - format.text &render_text_feed - end - end - end - - def projects_and_actions - @projects = @projects.select { |p| p.active? } - respond_to do |format| - format.text { - render :action => 'index_text_projects_and_actions', :layout => false, :content_type => Mime::TEXT - } - end - end - - def show - init_data_for_sidebar unless mobile? - @projects = current_user.projects - @page_title = "TRACKS::Project: #{@project.name}" - @project.todos.send :with_scope, :find => { :include => [:context, :tags] } do - @not_done = @project.not_done_todos(:include_project_hidden_todos => true) - @deferred = @project.deferred_todos.sort_by { |todo| todo.show_from } - @done = @project.done_todos - end - - @max_completed = current_user.prefs.show_number_completed - - @count = @not_done.size - @down_count = @count + @deferred.size - @next_project = current_user.projects.next_from(@project) - @previous_project = current_user.projects.previous_from(@project) - @default_project_context_name_map = build_default_project_context_name_map(@projects).to_json - respond_to do |format| - format.html - format.m &render_project_mobile - format.xml { render :xml => @project.to_xml( :except => :user_id ) } - end - end - - # Example XML usage: curl -H 'Accept: application/xml' -H 'Content-Type: - # application/xml' - # -u username:password - # -d 'new project_name' - # http://our.tracks.host/projects - # - def create - if params[:format] == 'application/xml' && params['exception'] - render_failure "Expected post format is valid xml like so: project name." - return - end - @project = current_user.projects.build - params_are_invalid = true - if (params['project'] || (params['request'] && params['request']['project'])) - @project.attributes = params['project'] || params['request']['project'] - params_are_invalid = false - end - @go_to_project = params['go_to_project'] - @saved = @project.save - @project_not_done_counts = { @project.id => 0 } - @active_projects_count = current_user.projects.count(:conditions => "state = 'active'") - @contexts = current_user.contexts - respond_to do |format| - format.js { @down_count = current_user.projects.size } - format.xml do - if @project.new_record? && params_are_invalid - render_failure "Expected post format is valid xml like so: project name." - elsif @project.new_record? - render_failure @project.errors.full_messages.join(', ') - else - head :created, :location => project_url(@project) - end - end - end - end - - # Edit the details of the project - # - def update - params['project'] ||= {} - if params['project']['state'] - @state_changed = @project.state != params['project']['state'] - logger.info "@state_changed: #{@project.state} == #{params['project']['state']} != #{@state_changed}" - @project.transition_to(params['project']['state']) - params['project'].delete('state') - end - success_text = if params['field'] == 'name' && params['value'] - params['project']['id'] = params['id'] - params['project']['name'] = params['value'] - end - @project.attributes = params['project'] - if @project.save - if boolean_param('wants_render') - if (@project.hidden?) - @project_project_hidden_todo_counts = Hash.new - @project_project_hidden_todo_counts[@project.id] = @project.reload().not_done_todo_count(:include_project_hidden_todos => true) - else - @project_not_done_counts = Hash.new - @project_not_done_counts[@project.id] = @project.reload().not_done_todo_count(:include_project_hidden_todos => true) - end - @contexts = current_user.contexts - @active_projects_count = current_user.projects.count(:conditions => "state = 'active'") - @hidden_projects_count = current_user.projects.count(:conditions => "state = 'hidden'") - @completed_projects_count = current_user.projects.count(:conditions => "state = 'completed'") - render :template => 'projects/update.js.rjs' - return - elsif boolean_param('update_status') - render :template => 'projects/update_status.js.rjs' - return - elsif boolean_param('update_default_context') - @initial_context_name = @project.default_context.name - render :template => 'projects/update_default_context.js.rjs' - return - else - render :text => success_text || 'Success' - return - end - else - notify :warning, "Couldn't update project" - render :text => '' - return - end - render :template => 'projects/update.js.rjs' - end - - def edit - @contexts = current_user.contexts - respond_to do |format| - format.js - end - end - - def destroy - @project.destroy - @active_projects_count = current_user.projects.count(:conditions => "state = 'active'") - @hidden_projects_count = current_user.projects.count(:conditions => "state = 'hidden'") - @completed_projects_count = current_user.projects.count(:conditions => "state = 'completed'") - respond_to do |format| - format.js { @down_count = current_user.projects.size } - format.xml { render :text => "Deleted project #{@project.name}" } - end - end - - def order - project_ids = params["list-active-projects"] || params["list-hidden-projects"] || params["list-completed-projects"] - projects = current_user.projects.update_positions( project_ids ) - render :nothing => true - rescue - notify :error, $! - redirect_to :action => 'index' - end - - def alphabetize - @state = params['state'] - @projects = current_user.projects.alphabetize(:state => @state) if @state - @contexts = current_user.contexts - init_not_done_counts(['project']) - end - - protected - - def render_projects_html - lambda do - @page_title = "TRACKS::List Projects" - @count = current_user.projects.size - @active_projects = @projects.select{ |p| p.active? } - @hidden_projects = @projects.select{ |p| p.hidden? } - @completed_projects = @projects.select{ |p| p.completed? } - @no_projects = @projects.empty? - @projects.cache_note_counts - @new_project = current_user.projects.build - render - end - end - - def render_projects_mobile - lambda do - @active_projects = @projects.select{ |p| p.active? } - @hidden_projects = @projects.select{ |p| p.hidden? } - @completed_projects = @projects.select{ |p| p.completed? } - @down_count = @active_projects.size + @hidden_projects.size + @completed_projects.size - cookies[:mobile_url]=request.request_uri - render :action => 'index_mobile' - end - end - - def render_project_mobile - lambda do - if @project.default_context.nil? - @project_default_context = "This project does not have a default context" - else - @project_default_context = "The default context for this project is "+ - @project.default_context.name - end - cookies[:mobile_url]=request.request_uri - render :action => 'project_mobile' - end - end - - def render_rss_feed - lambda do - render_rss_feed_for @projects, :feed => feed_options, - :item => { :title => :name, :description => lambda { |p| summary(p) } } - end - end - - def render_atom_feed - lambda do - render_atom_feed_for @projects, :feed => feed_options, - :item => { :description => lambda { |p| summary(p) }, - :title => :name, - :author => lambda { |p| nil } } - end - end - - def feed_options - Project.feed_options(current_user) - end - - def render_text_feed - lambda do - init_project_hidden_todo_counts(['project']) - render :action => 'index', :layout => false, :content_type => Mime::TEXT - end - end - - def set_project_from_params - @project = current_user.projects.find_by_params(params) - end - - def set_source_view - @source_view = params['_source_view'] || 'project' - end - - def default_context_filter - p = params['project'] - p = params['request']['project'] if p.nil? && params['request'] - p = {} if p.nil? - default_context_name = p['default_context_name'] - p.delete('default_context_name') - - unless default_context_name.blank? - default_context = Context.find_or_create_by_name(default_context_name) - p['default_context_id'] = default_context.id - end - end - - def summary(project) - project_description = '' - project_description += sanitize(markdown( project.description )) unless project.description.blank? - project_description += "

#{count_undone_todos_phrase(p)}. " - project_description += "Project is #{project.state}." - project_description += "

" - project_description - end - -end +class ProjectsController < ApplicationController + + helper :application, :todos, :notes + before_filter :set_source_view + before_filter :set_project_from_params, :only => [:update, :destroy, :show, :edit] + before_filter :default_context_filter, :only => [:create, :update] + skip_before_filter :login_required, :only => [:index] + prepend_before_filter :login_or_feed_token_required, :only => [:index] + session :off, :only => :index, :if => Proc.new { |req| ['rss','atom','txt'].include?(req.parameters[:format]) } + + def index + @projects = current_user.projects(true) + if params[:projects_and_actions] + projects_and_actions + else + @contexts = current_user.contexts(true) + init_not_done_counts(['project']) + if params[:only_active_with_no_next_actions] + @projects = @projects.select { |p| p.active? && count_undone_todos(p) == 0 } + end + init_project_hidden_todo_counts(['project']) + respond_to do |format| + format.html &render_projects_html + format.m &render_projects_mobile + format.xml { render :xml => @projects.to_xml( :except => :user_id ) } + format.rss &render_rss_feed + format.atom &render_atom_feed + format.text &render_text_feed + end + end + end + + def projects_and_actions + @projects = @projects.select { |p| p.active? } + respond_to do |format| + format.text { + render :action => 'index_text_projects_and_actions', :layout => false, :content_type => Mime::TEXT + } + end + end + + def show + init_data_for_sidebar unless mobile? + @projects = current_user.projects + @page_title = "TRACKS::Project: #{@project.name}" + @project.todos.send :with_scope, :find => { :include => [:context, :tags] } do + @not_done = @project.not_done_todos(:include_project_hidden_todos => true) + @deferred = @project.deferred_todos.sort_by { |todo| todo.show_from } + @done = @project.done_todos + end + + @max_completed = current_user.prefs.show_number_completed + + @count = @not_done.size + @down_count = @count + @deferred.size + @next_project = current_user.projects.next_from(@project) + @previous_project = current_user.projects.previous_from(@project) + @default_project_context_name_map = build_default_project_context_name_map(@projects).to_json + respond_to do |format| + format.html + format.m &render_project_mobile + format.xml { render :xml => @project.to_xml( :except => :user_id ) } + end + end + + # Example XML usage: curl -H 'Accept: application/xml' -H 'Content-Type: + # application/xml' + # -u username:password + # -d 'new project_name' + # http://our.tracks.host/projects + # + def create + if params[:format] == 'application/xml' && params['exception'] + render_failure "Expected post format is valid xml like so: project name." + return + end + @project = current_user.projects.build + params_are_invalid = true + if (params['project'] || (params['request'] && params['request']['project'])) + @project.attributes = params['project'] || params['request']['project'] + params_are_invalid = false + end + @go_to_project = params['go_to_project'] + @saved = @project.save + @project_not_done_counts = { @project.id => 0 } + @active_projects_count = current_user.projects.count(:conditions => "state = 'active'") + @contexts = current_user.contexts + respond_to do |format| + format.js { @down_count = current_user.projects.size } + format.xml do + if @project.new_record? && params_are_invalid + render_failure "Expected post format is valid xml like so: project name." + elsif @project.new_record? + render_failure @project.errors.full_messages.join(', ') + else + head :created, :location => project_url(@project) + end + end + end + end + + # Edit the details of the project + # + def update + params['project'] ||= {} + if params['project']['state'] + @state_changed = @project.state != params['project']['state'] + logger.info "@state_changed: #{@project.state} == #{params['project']['state']} != #{@state_changed}" + @project.transition_to(params['project']['state']) + params['project'].delete('state') + end + success_text = if params['field'] == 'name' && params['value'] + params['project']['id'] = params['id'] + params['project']['name'] = params['value'] + end + @project.attributes = params['project'] + if @project.save + if boolean_param('wants_render') + if (@project.hidden?) + @project_project_hidden_todo_counts = Hash.new + @project_project_hidden_todo_counts[@project.id] = @project.reload().not_done_todo_count(:include_project_hidden_todos => true) + else + @project_not_done_counts = Hash.new + @project_not_done_counts[@project.id] = @project.reload().not_done_todo_count(:include_project_hidden_todos => true) + end + @contexts = current_user.contexts + @active_projects_count = current_user.projects.count(:conditions => "state = 'active'") + @hidden_projects_count = current_user.projects.count(:conditions => "state = 'hidden'") + @completed_projects_count = current_user.projects.count(:conditions => "state = 'completed'") + render :template => 'projects/update.js.rjs' + return + elsif boolean_param('update_status') + render :template => 'projects/update_status.js.rjs' + return + elsif boolean_param('update_default_context') + @initial_context_name = @project.default_context.name + render :template => 'projects/update_default_context.js.rjs' + return + else + render :text => success_text || 'Success' + return + end + else + notify :warning, "Couldn't update project" + render :text => '' + return + end + render :template => 'projects/update.js.rjs' + end + + def edit + @contexts = current_user.contexts + respond_to do |format| + format.js + end + end + + def destroy + @project.destroy + @active_projects_count = current_user.projects.count(:conditions => "state = 'active'") + @hidden_projects_count = current_user.projects.count(:conditions => "state = 'hidden'") + @completed_projects_count = current_user.projects.count(:conditions => "state = 'completed'") + respond_to do |format| + format.js { @down_count = current_user.projects.size } + format.xml { render :text => "Deleted project #{@project.name}" } + end + end + + def order + project_ids = params["list-active-projects"] || params["list-hidden-projects"] || params["list-completed-projects"] + projects = current_user.projects.update_positions( project_ids ) + render :nothing => true + rescue + notify :error, $! + redirect_to :action => 'index' + end + + def alphabetize + @state = params['state'] + @projects = current_user.projects.alphabetize(:state => @state) if @state + @contexts = current_user.contexts + init_not_done_counts(['project']) + end + + protected + + def render_projects_html + lambda do + @page_title = "TRACKS::List Projects" + @count = current_user.projects.size + @active_projects = @projects.select{ |p| p.active? } + @hidden_projects = @projects.select{ |p| p.hidden? } + @completed_projects = @projects.select{ |p| p.completed? } + @no_projects = @projects.empty? + @projects.cache_note_counts + @new_project = current_user.projects.build + render + end + end + + def render_projects_mobile + lambda do + @active_projects = @projects.select{ |p| p.active? } + @hidden_projects = @projects.select{ |p| p.hidden? } + @completed_projects = @projects.select{ |p| p.completed? } + @down_count = @active_projects.size + @hidden_projects.size + @completed_projects.size + cookies[:mobile_url]=request.request_uri + render :action => 'index_mobile' + end + end + + def render_project_mobile + lambda do + if @project.default_context.nil? + @project_default_context = "This project does not have a default context" + else + @project_default_context = "The default context for this project is "+ + @project.default_context.name + end + cookies[:mobile_url]=request.request_uri + @mobile_from_project = @project.id + render :action => 'project_mobile' + end + end + + def render_rss_feed + lambda do + render_rss_feed_for @projects, :feed => feed_options, + :item => { :title => :name, :description => lambda { |p| summary(p) } } + end + end + + def render_atom_feed + lambda do + render_atom_feed_for @projects, :feed => feed_options, + :item => { :description => lambda { |p| summary(p) }, + :title => :name, + :author => lambda { |p| nil } } + end + end + + def feed_options + Project.feed_options(current_user) + end + + def render_text_feed + lambda do + init_project_hidden_todo_counts(['project']) + render :action => 'index', :layout => false, :content_type => Mime::TEXT + end + end + + def set_project_from_params + @project = current_user.projects.find_by_params(params) + end + + def set_source_view + @source_view = params['_source_view'] || 'project' + end + + def default_context_filter + p = params['project'] + p = params['request']['project'] if p.nil? && params['request'] + p = {} if p.nil? + default_context_name = p['default_context_name'] + p.delete('default_context_name') + + unless default_context_name.blank? + default_context = Context.find_or_create_by_name(default_context_name) + p['default_context_id'] = default_context.id + end + end + + def summary(project) + project_description = '' + project_description += sanitize(markdown( project.description )) unless project.description.blank? + project_description += "

#{count_undone_todos_phrase(p)}. " + project_description += "Project is #{project.state}." + project_description += "

" + project_description + end + +end diff --git a/app/controllers/todos_controller.rb b/app/controllers/todos_controller.rb index 990d2ed1..49d9d0ca 100644 --- a/app/controllers/todos_controller.rb +++ b/app/controllers/todos_controller.rb @@ -33,6 +33,12 @@ class TodosController < ApplicationController format.m { @new_mobile = true @return_path=cookies[:mobile_url] + @mobile_from_context = current_user.contexts.find_by_id(params[:from_context]) if params[:from_context] + @mobile_from_project = current_user.projects.find_by_id(params[:from_project]) if params[:from_project] + if params[:from_project] && !params[:from_context] + # we have a project but not a context -> use the default context + @mobile_from_context = @mobile_from_project.default_context + end render :action => "new" } end @@ -71,7 +77,7 @@ class TodosController < ApplicationController # todo: use function for this fixed path @return_path='/mobile' if @return_path.nil? if @saved - redirect_to mobile_abbrev_url + redirect_to @return_path else @projects = current_user.projects.find(:all) @contexts = current_user.contexts.find(:all) diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb index 1e199962..ec70762e 100644 --- a/app/helpers/todos_helper.rb +++ b/app/helpers/todos_helper.rb @@ -114,11 +114,10 @@ module TodosHelper 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('') - "#{tag_list}" + "#{tag_list}" end def deferred_due_date diff --git a/app/views/layouts/mobile.m.erb b/app/views/layouts/mobile.m.erb index c775332c..c53ea4c3 100644 --- a/app/views/layouts/mobile.m.erb +++ b/app/views/layouts/mobile.m.erb @@ -1,4 +1,8 @@ - +<% + new_todo_params = {} + new_todo_params[:from_project] = @mobile_from_project if @mobile_from_project + new_todo_params[:from_context] = @mobile_from_context if @mobile_from_context +-%> @@ -10,7 +14,7 @@ if !@prefs.nil? -%>

<%= @down_count %> <%= user_time.strftime(@prefs.title_date_format) -%>

-<%= (link_to("0-Add new action", formatted_new_todo_path(:m))+" | ") unless @new_mobile -%> +<%= (link_to("0-Add new action", formatted_new_todo_path(:m, new_todo_params))+" | ") unless @new_mobile -%> <%= (link_to("1-Home", formatted_todos_path(:m))+" | ") unless @home -%> <%= (link_to("2-Contexts", formatted_contexts_path(:m))+" | ") -%> <%= (link_to("3-Projects", formatted_projects_path(:m))+" | ") -%> diff --git a/app/views/todos/_edit_mobile.rhtml b/app/views/todos/_edit_mobile.rhtml index b2c423e6..f1caa191 100644 --- a/app/views/todos/_edit_mobile.rhtml +++ b/app/views/todos/_edit_mobile.rhtml @@ -1,26 +1,40 @@ -<% @tag_list_text = "" - @tag_list_text = tag_list_text if @todo -%> - -<%= error_messages_for("todo") %> - -<% this_year = user_time.to_date.strftime("%Y").to_i -if parent_container_type == 'show_mobile' -%> -

 <%= check_box_tag("done", 1, @todo && @todo.completed?, "tabindex" => 1) %>

-<% end -%> -

-<%= text_field( "todo", "description", "tabindex" => 2) %> -

-<%= text_area( "todo", "notes", "cols" => 30, "rows" => 5, "tabindex" => 3) %> -

-<%= collection_select( "todo", "context_id", @contexts, "id", "name", {"tabindex" => 4} ) %> -

-<%= collection_select( "todo", "project_id", @projects, "id", "name", - {:include_blank => true}, {"tabindex" => 5} ) %> -

-<%= text_field_tag "tag_list", @tag_list_text, :size => 30, :tabindex => 6 %> -

-<%= date_select("todo", "due", :order => [:day, :month, :year], - :start_year => this_year, :include_blank => true) %> -

-<%= date_select("todo", "show_from", :order => [:day, :month, :year], - :start_year => this_year, :include_blank => true) %> +<% @tag_list_text = "" + @tag_list_text = tag_list_text if @todo -%> + +<%= error_messages_for("todo") %> + +<% this_year = user_time.to_date.strftime("%Y").to_i +if parent_container_type == 'show_mobile' -%> +

 <%= check_box_tag("done", 1, @todo && @todo.completed?, "tabindex" => 1) %>

+<% end -%> +

+<%= text_field( "todo", "description", "tabindex" => 2) %> +

+<%= text_area( "todo", "notes", "cols" => 30, "rows" => 5, "tabindex" => 3) %> +

+<%= unless @mobile_from_context + collection_select( "todo", "context_id", @contexts, "id", "name", {}, {"tabindex" => 4} ) +else + select_tag("todo[context_id]", options_from_collection_for_select( + @contexts, "id", "name", @mobile_from_context.id), + {"id" => :todo_context_id, :tabindex => 4} ) +end %> +

+<%= unless @mobile_from_project + collection_select( "todo", "project_id", @projects, "id", "name", + {:include_blank => true}, {"tabindex" => 5} ) +else + # manually add blank option since :include_blank does not work + # with options_from_collection_for_select + select_tag("todo[project_id]", ""+options_from_collection_for_select( + @projects, "id", "name", @mobile_from_project.id), + {"id" => :todo_project_id, :tabindex => 5} ) +end %> +

+<%= text_field_tag "tag_list", @tag_list_text, :size => 30, :tabindex => 6 %> +

+<%= date_select("todo", "due", :order => [:day, :month, :year], + :start_year => this_year, :include_blank => true) %> +

+<%= date_select("todo", "show_from", :order => [:day, :month, :year], + :start_year => this_year, :include_blank => true) %> diff --git a/app/views/todos/toggle_check.js.rjs b/app/views/todos/toggle_check.js.rjs index 8cfeac54..92684bc5 100644 --- a/app/views/todos/toggle_check.js.rjs +++ b/app/views/todos/toggle_check.js.rjs @@ -17,12 +17,12 @@ if @saved end # show new todo if the completed todo was recurring - unless @new_recurring_todo.nil? + unless @new_recurring_todo.nil? || @new_recurring_todo.deferred? page.call "todoItems.ensureVisibleWithEffectAppear", item_container_id(@new_recurring_todo) page.insert_html :bottom, item_container_id(@new_recurring_todo), :partial => 'todos/todo', :locals => { :todo => @new_recurring_todo, :parent_container_type => parent_container_type } page.visual_effect :highlight, dom_id(@new_recurring_todo, 'line'), {'startcolor' => "'#99ff99'"} else - page.notify :notice, "There is no next action after the recurring action you just finished. The recurrence is completed", 6.0 unless @recurring_todo.nil? + page.notify :notice, "There is no next action after the recurring action you just finished. The recurrence is completed", 6.0 unless @new_recurring_todo.deferred? end else