diff --git a/README b/README index fcc1d7a8..d2c82d58 100644 --- a/README +++ b/README @@ -9,19 +9,16 @@ * Mailing list: http://lists.rousette.org.uk/mailman/listinfo/tracks-discuss * Original developer: bsag (http://www.rousette.org.uk/) * Contributors: http://getontracks.org/wiki/Contributors -* Version: 2.0RC1 +* Version: 2.0devel * Copyright: (cc) 2004-2010 rousette.org.uk. * License: GNU GPL -All the documentation for Tracks can be found within the /doc directory. It contains a manual in HTML (manual.html) or PDF format (manual.pdf), and this 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. -If you checked out Tracks from the GitHub repository, the manual is not provided by default and is in its own git submodule. To checkout the manual's source files, type "git submodule init doc/manual & git submodule update doc/manual". From then on, you should be able to issue the command "git pull" in the doc/manual directory to update the manual with the latest changes. - -The latest version of the manual can be found at http://bsag.github.com/tracks/ - -For those upgrading, change notes are available in /doc/CHANGELOG. 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. +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. While fully usable for everyday use, Tracks is still a work in progress. Make sure that you take sensible precautions and back up all your data frequently, taking particular care when you are upgrading. -Enjoy being productive! - +Enjoy being productive! \ No newline at end of file diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 779f9031..f2e2b1ae 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -4,19 +4,6 @@ require_dependency "login_system" require_dependency "tracks/source_view" -require "redcloth" - -require 'date' -require 'time' - -# Commented the following line because of #744. It prevented rake db:migrate to -# run because this tag went looking for the taggings table that did not exist -# when you feshly create a new database Old comment: We need this in development -# mode, or you get 'method missing' errors -# -# Tag - -class CannotAccessContext < RuntimeError; end class ApplicationController < ActionController::Base @@ -24,26 +11,19 @@ class ApplicationController < ActionController::Base helper :application include LoginSystem - helper_method :current_user, :prefs + helper_method :current_user, :prefs, :format_date, :markdown layout proc{ |controller| controller.mobile? ? "mobile" : "standard" } exempt_from_layout /\.js\.erb$/ - - + before_filter :set_session_expiration before_filter :set_time_zone before_filter :set_zindex_counter before_filter :set_locale prepend_before_filter :login_required prepend_before_filter :enable_mobile_content_negotiation - # after_filter :set_locale after_filter :set_charset - include ActionView::Helpers::TextHelper - include ActionView::Helpers::SanitizeHelper - extend ActionView::Helpers::SanitizeHelper::ClassMethods - helper_method :format_date, :markdown - # By default, sets the charset to UTF-8 if it isn't already set def set_charset headers["Content-Type"] ||= "text/html; charset=UTF-8" @@ -60,7 +40,7 @@ class ApplicationController < ActionController::Base def set_session_expiration # http://wiki.rubyonrails.com/rails/show/HowtoChangeSessionOptions unless session == nil - return if @controller_name == 'feed' or session['noexpiry'] == "on" + return if self.controller_name == 'feed' or session['noexpiry'] == "on" # If the method is called by the feed controller (which we don't have # under session control) or if we checked the box to keep logged in on # login don't set the session expiry time. @@ -137,8 +117,8 @@ class ApplicationController < ActionController::Base end end - def auto_complete_result2(entries, phrase = nil) - json_elems = "[{" + entries.map {|item| "\"id\" : \"#{item.id}\", \"value\" : \"#{item.specification()}\""}.join("},{") + "}]" + def format_dependencies_as_json_for_auto_complete(entries) + json_elems = "[{" + entries.map {|item| "\"value\" : \"#{item.id}\", \"label\" : \"#{item.specification()}\""}.join("},{") + "}]" return json_elems == "[{}]" ? "" : json_elems end @@ -150,7 +130,7 @@ class ApplicationController < ActionController::Base def markdown(text) RedCloth.new(text).to_html end - + # Here's the concept behind this "mobile content negotiation" hack: In # addition to the main, AJAXy Web UI, Tracks has a lightweight low-feature # 'mobile' version designed to be suitablef or use from a phone or PDA. It diff --git a/app/controllers/backend_controller.rb b/app/controllers/backend_controller.rb index 0a96cb30..5fceed53 100644 --- a/app/controllers/backend_controller.rb +++ b/app/controllers/backend_controller.rb @@ -1,3 +1,5 @@ +class CannotAccessContext < RuntimeError; end + class BackendController < ApplicationController wsdl_service_name 'Backend' web_service_api TodoApi diff --git a/app/controllers/login_controller.rb b/app/controllers/login_controller.rb index 08ded1c9..c9644244 100644 --- a/app/controllers/login_controller.rb +++ b/app/controllers/login_controller.rb @@ -6,6 +6,9 @@ class LoginController < ApplicationController skip_before_filter :login_required before_filter :login_optional before_filter :get_current_user + + protect_from_forgery :except => :check_expiry + if ( SITE_CONFIG['authentication_schemes'].include? 'cas') # This will allow the user to view the index page without authentication # but will process CAS authentication data if the user already diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index fadf2db3..a1f8f6ba 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -47,10 +47,11 @@ class ProjectsController < ApplicationController init_data_for_sidebar unless mobile? @page_title = t('projects.page_title', :project => @project.name) - @not_done = @project.todos.active_or_hidden - @deferred = @project.deferred_todos - @pending = @project.pending_todos - @done = @project.todos.find_in_state(:all, :completed, :order => "todos.completed_at DESC", :limit => current_user.prefs.show_number_completed, :include => [:context]) + @not_done = @project.todos.active_or_hidden(:include => [:tags, :context, :predecessors]) + @deferred = @project.deferred_todos(:include => [:tags, :context, :predecessors]) + @pending = @project.pending_todos(:include => [:tags, :context, :predecessors]) + @done = @project.todos.find_in_state(:all, :completed, + :order => "todos.completed_at DESC", :limit => current_user.prefs.show_number_completed, :include => [:context, :tags, :predecessors]) @count = @not_done.size @down_count = @count + @deferred.size + @pending.size @@ -59,6 +60,7 @@ class ProjectsController < ApplicationController @default_tags = @project.default_tags @new_note = current_user.notes.new @new_note.project_id = @project.id + @contexts = current_user.contexts respond_to do |format| format.html format.m &render_project_mobile @@ -237,9 +239,9 @@ class ProjectsController < ApplicationController def render_projects_mobile lambda do - @active_projects = @projects.active - @hidden_projects = @projects.hidden - @completed_projects = @projects.completed + @active_projects = current_user.projects.active + @hidden_projects = current_user.projects.hidden + @completed_projects = current_user.projects.completed @down_count = @active_projects.size + @hidden_projects.size + @completed_projects.size cookies[:mobile_url]= {:value => request.request_uri, :secure => SITE_CONFIG['secure_cookies']} render :action => 'index_mobile' diff --git a/app/controllers/recurring_todos_controller.rb b/app/controllers/recurring_todos_controller.rb index 0d17e24b..126e7c4b 100644 --- a/app/controllers/recurring_todos_controller.rb +++ b/app/controllers/recurring_todos_controller.rb @@ -112,17 +112,17 @@ class RecurringTodosController < ApplicationController end if @saved - @status_message = "The recurring todo was saved" + @status_message = t('todos.recurring_action_saved') @todo_saved = create_todo_from_recurring_todo(@recurring_todo).nil? == false if @todo_saved - @status_message += " / created a new todo" + @status_message += " / " + t('todos.new_related_todo_created_short') else - @status_message += " / did not create todo" + @status_message += " / " + t('todos.new_related_todo_not_created_short') end @down_count = current_user.recurring_todos.active.count @new_recurring_todo = RecurringTodo.new else - @status_message = "Error saving recurring todo" + @status_message = t('todos.error_saving_recurring') end respond_to do |format| @@ -151,10 +151,10 @@ class RecurringTodosController < ApplicationController format.html do if @saved - notify :notice, "Successfully deleted recurring action", 2.0 + notify :notice, t('todos.recurring_deleted_success'), 2.0 redirect_to :action => 'index' else - notify :error, "Failed to delete the recurring action", 2.0 + notify :error, t('todos.error_deleting_recurring', :description => @recurring_todo.description), 2.0 redirect_to :action => 'index' end end @@ -251,11 +251,17 @@ class RecurringTodosController < ApplicationController private def init - @days_of_week = [ ['Sunday',0], ['Monday',1], ['Tuesday', 2], ['Wednesday',3], ['Thursday',4], ['Friday',5], ['Saturday',6]] - @months_of_year = [ - ['January',1], ['Februari',2], ['March', 3], ['April',4], ['May',5], ['June',6], - ['July',7], ['August',8], ['September',9], ['October', 10], ['November', 11], ['December',12]] - @xth_day = [['first',1],['second',2],['third',3],['fourth',4],['last',5]] + @days_of_week = [] + 0.upto 6 do |i| + @days_of_week << [t('date.day_names')[i], i] + end + + @months_of_year = [] + 1.upto 12 do |i| + @months_of_year << [t('date.month_names')[i], i] + end + + @xth_day = [[t('common.first'),1],[t('common.second'),2],[t('common.third'),3],[t('common.fourth'),4],[t('common.last'),5]] @projects = current_user.projects.find(:all, :include => [:default_context]) @contexts = current_user.contexts.find(:all) end diff --git a/app/controllers/todos_controller.rb b/app/controllers/todos_controller.rb index 880c0de7..61892c55 100644 --- a/app/controllers/todos_controller.rb +++ b/app/controllers/todos_controller.rb @@ -11,6 +11,12 @@ class TodosController < ApplicationController :completed_archive, :check_deferred, :toggle_check, :toggle_star, :edit, :update, :defer, :create, :calendar, :auto_complete_for_predecessor, :remove_predecessor, :add_predecessor] + protect_from_forgery :except => :check_deferred + + # these are needed for todo_feed_content. TODO: remove this view stuff from controller + include ActionView::Helpers::SanitizeHelper + extend ActionView::Helpers::SanitizeHelper::ClassMethods + def index @projects = current_user.projects.find(:all, :include => [:default_context]) @contexts = current_user.contexts.find(:all) @@ -49,7 +55,7 @@ class TodosController < ApplicationController def create @source_view = params['_source_view'] || 'todo' @default_context = current_user.contexts.find_by_name(params['default_context_name']) - @default_project = current_user.projects.find_by_name(params['default_project_name']) + @default_project = current_user.projects.find_by_name(params['default_project_name']) unless params['default_project_name'].blank? @tag_name = params['_tag_name'] @@ -248,6 +254,7 @@ class TodosController < ApplicationController end def remove_predecessor + puts "@@@ start remove_predecessor" @source_view = params['_source_view'] || 'todo' @todo = current_user.todos.find(params['id']) @predecessor = current_user.todos.find(params['predecessor']) @@ -440,7 +447,7 @@ class TodosController < ApplicationController format.js do if @saved determine_down_count - if source_view_is_one_of(:todo, :deferred) + if source_view_is_one_of(:todo, :deferred, :project) determine_remaining_in_context_count(@context_id) elsif source_view_is :calendar @original_item_due_id = get_due_id_for_calendar(@original_item_due) @@ -510,7 +517,7 @@ class TodosController < ApplicationController def tag init_data_for_sidebar unless mobile? @source_view = params['_source_view'] || 'tag' - @tag_name = params[:name] + @tag_name = sanitize(params[:name]) # sanitize to prevent XSS vunerability! @page_title = t('todos.tagged_page_title', :tag_name => @tag_name) # mobile tags are routed with :name ending on .m. So we need to chomp it @@ -648,7 +655,7 @@ class TodosController < ApplicationController get_todo_from_params # Begin matching todos in current project @items = current_user.todos.find(:all, - :select => 'description, project_id, context_id, created_at', + :include => [:context, :project], :conditions => [ '(todos.state = ? OR todos.state = ? OR todos.state = ?) AND ' + 'NOT (id = ?) AND lower(description) LIKE ? AND project_id = ?', 'active', 'pending', 'deferred', @@ -660,7 +667,7 @@ class TodosController < ApplicationController ) if @items.empty? # Match todos in other projects @items = current_user.todos.find(:all, - :select => 'description, project_id, context_id, created_at', + :include => [:context, :project], :conditions => [ '(todos.state = ? OR todos.state = ? OR todos.state = ?) AND ' + 'NOT (id = ?) AND lower(description) LIKE ?', 'active', 'pending', 'deferred', @@ -672,7 +679,7 @@ class TodosController < ApplicationController else # New todo - TODO: Filter on project @items = current_user.todos.find(:all, - :select => 'description, project_id, context_id, created_at', + :include => [:context, :project], :conditions => [ '(todos.state = ? OR todos.state = ? OR todos.state = ?) AND lower(description) LIKE ?', 'active', 'pending', 'deferred', '%' + params[:term].downcase + '%' ], @@ -680,7 +687,7 @@ class TodosController < ApplicationController :limit => 10 ) end - render :inline => auto_complete_result2(@items) + render :inline => format_dependencies_as_json_for_auto_complete(@items) end def convert_to_project @@ -822,13 +829,13 @@ class TodosController < ApplicationController # current_users.todos.find but that broke with_scope for :limit # Exclude hidden projects from count on home page - @todos = current_user.todos.find(:all, :include => [ :project, :context, :tags ]) + @todos = current_user.todos.find(:all, :include => [ :project, :context, :tags, :pending_successors, :recurring_todo ]) # Exclude hidden projects from the home page @not_done_todos = current_user.todos.find(:all, :conditions => ['contexts.hide = ? AND (projects.state = ? OR todos.project_id IS NULL)', false, 'active'], :order => "todos.due IS NULL, todos.due ASC, todos.created_at ASC", - :include => [ :project, :context, :tags ]) + :include => [ :project, :context, :tags, :pending_successors, :recurring_todo ]) end end @@ -891,12 +898,13 @@ class TodosController < ApplicationController @remaining_hidden_count = current_user.todos.hidden.with_tag(tag).count } from.project { - @remaining_deferred_or_pending_count = current_user.projects.find(@todo.project_id).todos.deferred_or_blocked.count - @remaining_in_context = current_user.projects.find(@todo.project_id).todos.active.count - @target_context_count = current_user.projects.find(@todo.project_id).todos.active.count + project_id = @project_changed ? @original_item_project_id : @todo.project_id + @remaining_deferred_or_pending_count = current_user.projects.find(project_id).todos.deferred_or_blocked.count + @remaining_in_context = current_user.projects.find(project_id).todos.active.count + @target_context_count = current_user.projects.find(project_id).todos.active.count } from.calendar { - @target_context_count = count_old_due_empty(@new_due_id) + @target_context_count = @new_due_id.blank? ? 0 : count_old_due_empty(@new_due_id) } end @remaining_in_context = current_user.contexts.find(context_id).todos(true).active.not_hidden.count if !@remaining_in_context @@ -978,6 +986,7 @@ class TodosController < ApplicationController end def todo_feed_content + # TODO: move view stuff into view, also the includes at the top lambda do |i| item_notes = sanitize(markdown( i.notes )) if i.notes? due = "
#{t('todos.feeds.due', :date => format_date(i.due))}
\n" if i.due? @@ -1325,4 +1334,4 @@ class TodosController < ApplicationController end -end \ No newline at end of file +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 0f6fa52c..0af710a0 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -240,7 +240,7 @@ module ApplicationHelper end def generate_i18n_strings - js = "" + js = "i18n_locale='#{I18n.locale}';\n" js << "i18n = new Array();\n" %w{ shared.toggle_multi shared.toggle_multi_title @@ -259,4 +259,12 @@ module ApplicationHelper return js end + def javascript_tag_for_i18n_datepicker + locale = I18n.locale + # do not include en as locale since this the available by default + if locale and locale != :en + javascript_include_tag("i18n/jquery.ui.datepicker-#{locale}.js") + end + end + end diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb index 7f7bbc4d..1f347fb4 100644 --- a/app/helpers/todos_helper.rb +++ b/app/helpers/todos_helper.rb @@ -11,6 +11,7 @@ module TodosHelper image_tag("blank.png", :alt => t('todos.edit'), :align => "absmiddle", :id => 'edit_icon_todo_'+todo.id.to_s, :class => 'edit_item'), {:controller => 'todos', :action => 'edit', :id => todo.id}, :class => "icon edit_item", + :id => "icon_edit_todo_#{todo.id}", :title => t('todos.edit_action_with_description', :description => todo.description)) end @@ -29,11 +30,13 @@ module TodosHelper :_source_view => (@source_view.underscore.gsub(/\s+/,'_') rescue "")} url[:_tag_name] = @tag_name if @source_view == 'tag' - futuredate = (todo.show_from || todo.user.date) + days.days options = {:x_defer_alert => false, :class => "icon_defer_item" } - if todo.due && futuredate > todo.due - options[:x_defer_alert] = true - options[:x_defer_date_after_due_date] = t('todos.defer_date_after_due_date') + if todo.due + futuredate = (todo.show_from || todo.user.date) + days.days + if futuredate > todo.due + options[:x_defer_alert] = true + options[:x_defer_date_after_due_date] = t('todos.defer_date_after_due_date') + end end return link_to(image_tag_for_defer(days), url, options) @@ -87,7 +90,7 @@ module TodosHelper end def remote_toggle_checkbox(todo=@todo) - check_box_tag('item_id', toggle_check_todo_path(todo), todo.completed?, :class => 'item-checkbox', + check_box_tag("mark_complete_#{todo.id}", toggle_check_todo_path(todo), todo.completed?, :class => 'item-checkbox', :title => todo.pending? ? t('todos.blocked_by', :predecessors => todo.uncompleted_predecessors.map(&:description).join(', ')) : "", :readonly => todo.pending?) end @@ -139,10 +142,6 @@ module TodosHelper if tag_list.empty? then "" else "#{tag_list}" end end - def predecessor_list_text(todo=@todo) - todo.predecessors.map{|t| t.specification}.join(', ') - end - def deferred_due_date(todo=@todo) if todo.deferred? && todo.due t('todos.action_due_on', :date => format_date(todo.due)) @@ -225,7 +224,6 @@ module TodosHelper page.todo { return !@todo.hidden? } page.deferred { return @todo.deferred? || @todo.pending? } page.context { - logger.debug "ci=#{@todo.context_id} dci=#{@default_context.id} th=#{@todo.hidden?} tch=#{@todo.context.hidden?}" return @todo.context_id==@default_context.id && ( (@todo.hidden? && @todo.context.hidden?) || (!@todo.hidden?) ) } page.tag { @@ -266,7 +264,7 @@ module TodosHelper end def default_contexts_for_autocomplete - projects = current_user.projects.find(:all, :conditions => ['default_context_id is not null']) + projects = current_user.projects.find(:all, :include => [:context], :conditions => ['default_context_id is not null']) Hash[*projects.map{ |p| [p.name, p.default_context.name] }.flatten].to_json end @@ -289,7 +287,7 @@ module TodosHelper 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) + text_field_tag name, value, {"size" => 12, "id" => id, "class" => "Date", "autocomplete" => "off"}.update(options.stringify_keys) end def update_needs_to_hide_context @@ -297,7 +295,7 @@ module TodosHelper (@remaining_in_context == 0 && @todo_was_deferred_from_active_state) || (@remaining_in_context == 0 && @todo.completed? && !(@original_item_was_deferred || @original_item_was_hidden)) if source_view_is(:tag) - return false if source_view_is(:project) + return false if source_view_is_one_of(:project, :calendar) return (@remaining_in_context == 0) && !source_view_is(:context) end @@ -305,7 +303,7 @@ module TodosHelper def update_needs_to_remove_todo_from_container source_view do |page| page.context { return @context_changed || @todo.deferred? || @todo.pending? } - page.project { return @todo_deferred_state_changed || @todo_pending_state_changed } + page.project { return @todo_deferred_state_changed || @todo_pending_state_changed || @project_changed} page.deferred { return @context_changed || !(@todo.deferred? || @todo.pending?) } page.calendar { return @due_date_changed || !@todo.due } page.stats { return @todo.completed? } @@ -379,8 +377,8 @@ module TodosHelper container_id = "p#{@original_item_project_id}empty-nd" if @remaining_in_context == 0 container_id = "tickler-empty-nd" if ( ( (@todo_was_activated_from_deferred_state || @todo_was_activated_from_pending_state) && @remaining_deferred_or_pending_count == 0) || - (@original_item_was_deferred && @remaining_deferred_or_pending_count == 0 && @todo.completed?) ) - container_id = "empty-d" if @completed_count && @completed_count == 0 && !@todo.completed? + (@original_item_was_deferred && @remaining_deferred_or_pending_count == 0 && @todo.completed?) ) + container_id = "empty-d" if @completed_count && @completed_count == 0 && !@todo.completed? } page.deferred { container_id = "c#{@original_item_context_id}empty-nd" if @remaining_in_context == 0 } page.calendar { container_id = "empty_#{@original_item_due_id}" if @old_due_empty } @@ -405,7 +403,7 @@ module TodosHelper end end html += "}}) " * animation.count - return html + return html + ";" end private diff --git a/app/models/todo.rb b/app/models/todo.rb index 3456f55c..72ac6c0f 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -108,84 +108,41 @@ class Todo < ActiveRecord::Base # Returns a string with description def specification - project_name = project.is_a?(NullProject) ? "(none)" : project.name - return "\'#{description}\' <\'#{context.title}\'; \'#{project_name}\'>" + project_name = self.project.is_a?(NullProject) ? "(none)" : self.project.name + return "\'#{self.description}\' <\'#{self.context.title}\'; \'#{project_name}\'>" end - - def todo_from_specification(specification) - # Split specification into parts: description - 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, - :user_id => self.user.id, - :contexts => {:name => context_name}, - :project_id => project_id - } - ) - - return nil if todos.empty? - - # TODO: what todo if there are more than one todo that fit the specification - return todos[0] - end - + def validate if !show_from.blank? && show_from < user.date errors.add("show_from", I18n.t('models.todo.error_date_must_be_future')) end - errors.add(:description, "may not contain \" characters") if /\"/.match(description) + errors.add(:description, "may not contain \" characters") if /\"/.match(self.description) unless @predecessor_array.nil? # Only validate predecessors if they changed - @predecessor_array.each do |specification| - t = todo_from_specification(specification) - if t.nil? - errors.add("Depends on:", "Could not find action '#{h(specification)}'") - else - errors.add("Depends on:", "Adding '#{h(specification)}' would create a circular dependency") if is_successor?(t) - end + @predecessor_array.each do |todo| + errors.add("Depends on:", "Adding '#{h(todo.specification)}' would create a circular dependency") if is_successor?(todo) end end end def save_predecessors unless @predecessor_array.nil? # Only save predecessors if they changed - current_array = predecessors.map{|p| p.specification} + current_array = self.predecessors remove_array = current_array - @predecessor_array add_array = @predecessor_array - current_array @removed_predecessors = [] - # This is probably a bit naive code... - remove_array.each do |specification| - t = todo_from_specification(specification) - unless t.nil? - @removed_predecessors << t - self.predecessors.delete(t) + remove_array.each do |todo| + unless todo.nil? + @removed_predecessors << todo + self.predecessors.delete(todo) end end - # ... as is this? - add_array.each do |specification| - t = todo_from_specification(specification) - unless t.nil? - self.predecessors << t unless self.predecessors.include?(t) + + add_array.each do |todo| + unless todo.nil? + self.predecessors << todo unless self.predecessors.include?(todo) else - logger.error "Could not find #{specification}" # Unexpected since validation passed + logger.error "Could not find #{todo.description}" # Unexpected since validation passed end end end @@ -196,20 +153,22 @@ class Todo < ActiveRecord::Base end def remove_predecessor(predecessor) + puts "@@@ before delete" # remove predecessor and activate myself - predecessors.delete(predecessor) + self.predecessors.delete(predecessor) + puts "@@@ before activate" self.activate! end # Returns true if t is equal to self or a successor of self - def is_successor?(t) - if self == t + def is_successor?(todo) + if self == todo return true elsif self.successors.empty? return false else self.successors.each do |item| - if item.is_successor?(t) + if item.is_successor?(todo) return true end end @@ -314,13 +273,21 @@ class Todo < ActiveRecord::Base def add_predecessor_list(predecessor_list) return unless predecessor_list.kind_of? String - @predecessor_array = predecessor_list.scan(RE_SPEC) + + @predecessor_array=[] + + predecessor_ids_array = predecessor_list.split(",") + predecessor_ids_array.each do |todo_id| + predecessor = self.user.todos.find_by_id( todo_id.to_i ) unless todo_id.blank? + @predecessor_array << predecessor unless predecessor.nil? + end + return @predecessor_array end def add_predecessor(t) - @predecessor_array = predecessors.map{|p| p.specification} - @predecessor_array << t.specification + @predecessor_array = predecessors + @predecessor_array << t end # Return todos that should be activated if the current todo is completed diff --git a/app/views/integrations/index.de.html.erb b/app/views/integrations/index.de.html.erb index 2eb83f5a..52f812e9 100644 --- a/app/views/integrations/index.de.html.erb +++ b/app/views/integrations/index.de.html.erb @@ -100,8 +100,12 @@ If Tracks is running on the same server as your mail server, you can use the integrated mail handler built into tracks. Steps to set it up:

You can also use the Rich Todo API to send in tasks like "do laundry @ Home" diff --git a/app/views/integrations/index.en.html.erb b/app/views/integrations/index.en.html.erb index aa6c88a1..2e2160c3 100644 --- a/app/views/integrations/index.en.html.erb +++ b/app/views/integrations/index.en.html.erb @@ -100,8 +100,12 @@ If Tracks is running on the same server as your mail server, you can use the integrated mail handler built into tracks. Steps to set it up:

You can also use the Rich Todo API to send in tasks like "do laundry @ Home" diff --git a/app/views/integrations/index.nl.html.erb b/app/views/integrations/index.nl.html.erb new file mode 100644 index 00000000..390a2040 --- /dev/null +++ b/app/views/integrations/index.nl.html.erb @@ -0,0 +1,134 @@ +<% has_contexts = !current_user.contexts.empty? -%> +

Integratie

+

Tracks kan met een aantal tools worden geïntegreerd... + Alles om je te helpen om dingen gedaan te krijgen! + Deze pagina heeft informatie over het tot stand brengen van sommige integratievormen. + Deze voorbeelden zijn niet altijd voor alle platformen van toepassing en + sommige voorbeelden vragen meer technische kennis dan anderen + Zie ook <%= link_to "de documentatie voor ontwikkelaars met Tracks' REST API", url_for(:action => 'rest_api') %>.

+

Inhoud:

+
+

Heb je een tip om hier toe te voegen? + Vertel ons er over in onze + Tips and Tricks forum en misschien voegen we jouw tip toe op deze pagina in een toekomstige versie van Tracks. +

+ + +

Voeg een actie toe met Applescript

+

Dit is een eenvoudig script die een dialog box toont om jou om een beschrijving te vragen en vervolgens die op te sturen naar Tracks + met een hard-coded context.

+ +<% if has_contexts -%> +
    +
  1. Kies de context waar de je acties aan toe wilt laten voegen: +
  2. +
  3. Kopieer de volgende Applescript naar het klembord.
    + +
  4. +
  5. Open de Script Editor en plak het script in een nieuw document.
  6. +
  7. Compileer en bewaar het script. Voert het uit wanneer nodig.
  8. +
+<% else %> +

Je hebt nog geen context(en). Het script komt beschikbaar als je het eerste context hebt toegevoegd.

+<% end %> + + +

Voeg een acties toe met Applescript op basis van de huidig geselecteerde e-mail in Mail.app

+

Dit script neemt de verstuurder en het onderwerp van de geselecteerde email(s) + van Mail over en maakt een nieuwe acties voor elke email met de beschrijving + "Email [sender] about [subject]". De beschrijving wordt, als nodig, na 100 karakters afgebroken + (dit is de limiet voor een beschrijving). Het heeft ook Growl notificaties mocht je Growl geïnstalleerd hebben.

+ +<% if has_contexts -%> +
    +
  1. Kies de context waar de je acties aan toe wilt laten voegen: +
  2. +
  3. Kopieer de volgende Applescript naar het klembord.
    + +
  4. +
  5. Open de Script Editor en plak het script in een nieuw document.
  6. +
  7. Compileer en bewaar het script in de directory ~/Library/Scripts/Mail Scripts.
  8. +
  9. Voor meer informatie over het gebruiken van AppleScript met Mail.app, zie + dit overzicht.
  10. +
+<% else %> +

Je hebt nog geen context(en). Het script komt beschikbaar als je het eerste context hebt toegevoegd.

+<% end %> + + +

Voeg acties toe met Quicksilver en Applescript

+ +

Dit integratievoorbeeld laat je acties toevoegen aan Tracks via Quicksilver.

+ +<% if has_contexts -%> +
    +
  1. Kies de context waar de je acties aan toe wilt laten voegen: +
  2. +
  3. Kopieer de volgende Applescript naar het klembord.
    + +
  4. +
  5. Open de Script Editor en plak het script in een nieuw document.
  6. +
  7. Compileer en bewaar het script als "Add to Tracks.scpt" in ~/Library/Application Support/Quicksilver/Actions/ + (mogelijk moet je eerst de Actions directory aanmaken)
  8. +
  9. Herstart Quicksilver
  10. +
  11. Activeer Quicksilver (Standaard via Ctrl+Space)
  12. +
  13. Toets "." om quicksilver in text mode te brengen
  14. +
  15. Voer de gewenste beschrijving van de actie in.
  16. +
  17. Toets tab om naar de action pane te gaan.
  18. +
  19. Via typen of bladeren, kies de "Add to Tracks" actie.
  20. +
+<% else %> +

Je hebt nog geen context(en). Het script komt beschikbaar als je het eerste context hebt toegevoegd.

+<% end %> + + +

Email jezelf automatisch de acties met een aflopende deadline

+ +

Als je de volgende regel toevoegd aan jouw crontab, dat ontvang je een e-mail op elke dag rond 05:00 met een lijst met acties waarvan de deadline afloopt binnen de komende 7 dagen.

+ + + +

Uiteraard kan je ook een andere <%= link_to 'text feed gebruiken die Tracks biedt', feeds_path %> -- bijvoorbeeld een email met een lijst van acties voor een specifiek project naar een groep collega's die werken aan dat project?

+ + +

Integreer Tracks met een email server om een actie via email naar Tracks te sturen

+

+ Als Tracks draait op dezelfde server als jouw mailserver, dan kan je de geïntegreerde mail handler gebruiken van Tracks. Om dit in te stellen: +

+ +

Je kan ook de Rich Todo API gebruiken om acties te maken zoals "do laundry @ Home" + of "Call Bill > project X". Het onderwerp van het bericht zal de bijschrijving van de actie vullen, + de context, en het project, terwijl de body van het bericht de notities van de actie zal vullen. +

+ + +

Voeg tracks toe als een Google Gmail gadget

+

+ Je kan nu ook jouw projects/actions beheren in Gmail met de Tracks Gmail Gadget. + Voeg Tracks Gmail gadget toe aan de sidebar van Gmail en volg jouw acties + of voeg een nieuwe actie toe zonder apart een nieuw browser tab/scherm te openen + voor Tracks. Om dit in te stellen: +

+ \ No newline at end of file diff --git a/app/views/layouts/login.html.erb b/app/views/layouts/login.html.erb index 9e4da310..36ebb2fd 100644 --- a/app/views/layouts/login.html.erb +++ b/app/views/layouts/login.html.erb @@ -3,7 +3,7 @@ <%= stylesheet_link_tag "scaffold" %> - <%= javascript_include_tag 'jquery-1.5.min', 'jquery.cookie' %> + <%= javascript_include_tag 'jquery-1.5.1.min', 'jquery.cookie' %> <%= @page_title -%> diff --git a/app/views/layouts/mobile.m.erb b/app/views/layouts/mobile.m.erb index 42c1c81e..ab8ff179 100644 --- a/app/views/layouts/mobile.m.erb +++ b/app/views/layouts/mobile.m.erb @@ -12,8 +12,8 @@ <%= @page_title %> <% if !(@new_mobile || @edit_mobile) - if !current_user.prefs.nil? -%> -

<%= @down_count %> <%= + if current_user && !current_user.prefs.nil? -%> +

<%= @down_count %> <%= l(Date.today, :format => current_user.prefs.title_date_format) -%>

<%= yield -%> -
<% if !current_user.prefs.nil? -%> +
<% if current_user && !current_user.prefs.nil? -%>