mirror of
https://github.com/TracksApp/tracks.git
synced 2025-12-16 23:30:12 +01:00
Merge branch 'master' of github.com:epall/tracks
This commit is contained in:
commit
3e83d19299
33 changed files with 579 additions and 38 deletions
|
|
@ -47,10 +47,11 @@ class ProjectsController < ApplicationController
|
|||
|
||||
@not_done = @project.not_done_todos_including_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])
|
||||
|
||||
@count = @not_done.size
|
||||
@down_count = @count + @deferred.size
|
||||
@down_count = @count + @deferred.size + @pending.size
|
||||
@next_project = current_user.projects.next_from(@project)
|
||||
@previous_project = current_user.projects.previous_from(@project)
|
||||
@default_tags = @project.default_tags
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@ class TodosController < ApplicationController
|
|||
prepend_before_filter :login_or_feed_token_required, :only => [:index, :calendar]
|
||||
append_before_filter :init, :except => [ :destroy, :completed,
|
||||
:completed_archive, :check_deferred, :toggle_check, :toggle_star,
|
||||
:edit, :update, :create, :calendar, :auto_complete_for_tag]
|
||||
append_before_filter :get_todo_from_params, :only => [ :edit, :toggle_check, :toggle_star, :show, :update, :destroy ]
|
||||
protect_from_forgery :except => [:auto_complete_for_tag]
|
||||
:edit, :update, :create, :calendar, :auto_complete_for_tag, :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]
|
||||
protect_from_forgery :except => [:auto_complete_for_tag, :auto_complete_for_predecessor]
|
||||
|
||||
session :off, :only => :index, :if => Proc.new { |req| is_feed_request(req) }
|
||||
|
||||
|
|
@ -54,6 +54,7 @@ class TodosController < ApplicationController
|
|||
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)
|
||||
|
||||
|
|
@ -70,13 +71,21 @@ class TodosController < ApplicationController
|
|||
@todo.context_id = context.id
|
||||
end
|
||||
|
||||
@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
|
||||
|
||||
|
||||
unless (@aved == 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
|
||||
|
|
@ -130,6 +139,30 @@ class TodosController < ApplicationController
|
|||
format.xml { render :xml => @todo.to_xml( :root => 'todo', :except => :user_id ) }
|
||||
end
|
||||
end
|
||||
|
||||
def add_predecessor
|
||||
@source_view = params['_source_view'] || 'todo'
|
||||
@predecessor = Todo.find(params['predecessor'])
|
||||
@todo = Todo.find(params['successor'])
|
||||
@original_state = @todo.state
|
||||
# Add predecessor
|
||||
@todo.add_predecessor(@predecessor)
|
||||
@todo.state = 'pending'
|
||||
@saved = @todo.save
|
||||
respond_to do |format|
|
||||
format.js
|
||||
end
|
||||
end
|
||||
|
||||
def remove_predecessor
|
||||
@source_view = params['_source_view'] || 'todo'
|
||||
@predecessor = Todo.find(params['predecessor'])
|
||||
@successor = @todo
|
||||
@removed = @successor.remove_predecessor(@predecessor)
|
||||
respond_to do |format|
|
||||
format.js
|
||||
end
|
||||
end
|
||||
|
||||
# Toggles the 'done' status of the action
|
||||
#
|
||||
|
|
@ -142,6 +175,18 @@ class TodosController < ApplicationController
|
|||
# check if this todo has a related recurring_todo. If so, create next todo
|
||||
@new_recurring_todo = check_for_next_todo(@todo) if @saved
|
||||
|
||||
if @todo.completed?
|
||||
@pending_to_activate = @todo.pending_to_activate
|
||||
@pending_to_activate.each do |t|
|
||||
t.activate!
|
||||
end
|
||||
else
|
||||
@active_to_block = @todo.active_to_block
|
||||
@active_to_block.each do |t|
|
||||
t.block!
|
||||
end
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.js do
|
||||
if @saved
|
||||
|
|
@ -190,7 +235,8 @@ class TodosController < ApplicationController
|
|||
@original_item_project_id = @todo.project_id
|
||||
@original_item_was_deferred = @todo.deferred?
|
||||
@original_item_due = @todo.due
|
||||
@original_item_due_id = get_due_id_for_calendar(@todo.due)
|
||||
@original_item_due_id = get_due_id_for_calendar(@todo.due)
|
||||
@original_item_predecessor_list = @todo.predecessors.map{|t| t.specification}.join(', ')
|
||||
|
||||
if params['todo']['project_id'].blank? && !params['project_name'].nil?
|
||||
if params['project_name'] == 'None'
|
||||
|
|
@ -231,15 +277,38 @@ class TodosController < ApplicationController
|
|||
|
||||
if params['done'] == '1' && !@todo.completed?
|
||||
@todo.complete!
|
||||
@todo.pending_to_activate.each do |t|
|
||||
t.activate!
|
||||
end
|
||||
end
|
||||
# strange. if checkbox is not checked, there is no 'done' in params.
|
||||
# Therefore I've used the negation
|
||||
if !(params['done'] == '1') && @todo.completed?
|
||||
@todo.activate!
|
||||
@todo.active_to_block.each do |t|
|
||||
t.block!
|
||||
end
|
||||
end
|
||||
|
||||
@todo.attributes = params["todo"]
|
||||
|
||||
@todo.add_predecessor_list(params[:predecessor_list])
|
||||
@saved = @todo.save
|
||||
if @saved && params[:predecessor_list]
|
||||
if @original_item_predecessor_list != params[:predecessor_list]
|
||||
# Possible state change with new dependencies
|
||||
if @todo.uncompleted_predecessors.empty?
|
||||
if @todo.state == 'pending'
|
||||
@todo.activate! # Activate pending if no uncompleted predecessors
|
||||
end
|
||||
else
|
||||
if @todo.state == 'active'
|
||||
@todo.block! # Block active if we got uncompleted predecessors
|
||||
end
|
||||
end
|
||||
end
|
||||
@todo.save!
|
||||
end
|
||||
|
||||
@context_changed = @original_item_context_id != @todo.context_id
|
||||
@todo_was_activated_from_deferred_state = @original_item_was_deferred && @todo.active?
|
||||
|
|
@ -297,16 +366,32 @@ class TodosController < ApplicationController
|
|||
@context_id = @todo.context_id
|
||||
@project_id = @todo.project_id
|
||||
|
||||
# activate successors if they only depend on this todo
|
||||
activated_successor_count = 0
|
||||
@pending_to_activate = []
|
||||
@todo.pending_successors.each do |successor|
|
||||
successor.uncompleted_predecessors.delete(@todo)
|
||||
if successor.uncompleted_predecessors.empty?
|
||||
successor.activate!
|
||||
@pending_to_activate << successor
|
||||
activated_successor_count += 1
|
||||
end
|
||||
end
|
||||
|
||||
@saved = @todo.destroy
|
||||
|
||||
# check if this todo has a related recurring_todo. If so, create next todo
|
||||
@new_recurring_todo = check_for_next_todo(@todo) if @saved
|
||||
|
||||
|
||||
respond_to do |format|
|
||||
|
||||
format.html do
|
||||
if @saved
|
||||
notify :notice, "Successfully deleted next action", 2.0
|
||||
message = "Successfully deleted next action"
|
||||
if activated_successor_count > 0
|
||||
message += " activated #{pluralize(activated_successor_count, 'pending action')}"
|
||||
end
|
||||
notify :notice, message, 2.0
|
||||
redirect_to :action => 'index'
|
||||
else
|
||||
notify :error, "Failed to delete the action", 2.0
|
||||
|
|
@ -356,7 +441,7 @@ class TodosController < ApplicationController
|
|||
@contexts_to_show = @contexts = current_user.contexts.find(:all, :include => [ :todos ])
|
||||
|
||||
current_user.deferred_todos.find_and_activate_ready
|
||||
@not_done_todos = current_user.deferred_todos
|
||||
@not_done_todos = current_user.deferred_todos + current_user.pending_todos
|
||||
@count = @not_done_todos.size
|
||||
@down_count = @count
|
||||
|
||||
|
|
@ -409,6 +494,9 @@ class TodosController < ApplicationController
|
|||
@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,
|
||||
: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
|
||||
|
|
@ -513,6 +601,46 @@ class TodosController < ApplicationController
|
|||
render :inline => "<%= auto_complete_result(@items, :name) %>"
|
||||
end
|
||||
|
||||
def auto_complete_for_predecessor
|
||||
unless params['id'].nil?
|
||||
get_todo_from_params
|
||||
# Begin matching todos in current project
|
||||
@items = current_user.todos.find(:all,
|
||||
:select => 'description, project_id, context_id, created_at',
|
||||
:conditions => [ '(todos.state = ? OR todos.state = ?) AND ' +
|
||||
'NOT (id = ?) AND lower(description) LIKE ? AND project_id = ?',
|
||||
'active', 'pending',
|
||||
@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',
|
||||
:conditions => [ '(todos.state = ? OR todos.state = ?) AND ' +
|
||||
'NOT (id = ?) AND lower(description) LIKE ?',
|
||||
'active', 'pending',
|
||||
params[:id], '%' + params[:q].downcase + '%' ],
|
||||
:order => 'description ASC',
|
||||
:limit => 10
|
||||
)
|
||||
end
|
||||
else
|
||||
# New todo - TODO: Filter on project
|
||||
@items = current_user.todos.find(:all,
|
||||
:select => 'description, project_id, context_id, created_at',
|
||||
:conditions => [ '(todos.state = ? OR todos.state = ?) AND lower(description) LIKE ?',
|
||||
'active', 'pending',
|
||||
'%' + params[:q].downcase + '%' ],
|
||||
:order => 'description ASC',
|
||||
:limit => 10
|
||||
)
|
||||
end
|
||||
render :inline => "<%= auto_complete_result2(@items) %>"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def get_todo_from_params
|
||||
|
|
@ -670,6 +798,7 @@ class TodosController < ApplicationController
|
|||
unless @todo.project_id == nil
|
||||
@down_count = current_user.projects.find(@todo.project_id).not_done_todos_including_hidden.count
|
||||
@deferred_count = current_user.projects.find(@todo.project_id).deferred_todos.count
|
||||
@pending_count = current_user.projects.find(@todo.project_id).pending_todos.count
|
||||
end
|
||||
end
|
||||
from.deferred do
|
||||
|
|
@ -956,6 +1085,10 @@ class TodosController < ApplicationController
|
|||
def tag_list
|
||||
@params['tag_list']
|
||||
end
|
||||
|
||||
def predecessor_list
|
||||
@params['predecessor_list']
|
||||
end
|
||||
|
||||
def parse_dates()
|
||||
@attributes['show_from'] = @prefs.parse_date(show_from)
|
||||
|
|
|
|||
|
|
@ -64,11 +64,15 @@ module TodosHelper
|
|||
:complete => todo_stop_waiting_js(todo))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def todo_start_waiting_js(todo)
|
||||
return "$('#ul#{dom_id(todo)}').css('visibility', 'hidden'); $('##{dom_id(todo)}').block({message: null})"
|
||||
end
|
||||
|
||||
def successor_start_waiting_js(successor)
|
||||
return "$('##{dom_id(successor, "successor")}').block({message: null})"
|
||||
end
|
||||
|
||||
def todo_stop_waiting_js(todo)
|
||||
return "$('##{dom_id(todo)}').unblock();enable_rich_interaction();"
|
||||
end
|
||||
|
|
@ -82,12 +86,15 @@ module TodosHelper
|
|||
|
||||
|
||||
def remote_toggle_checkbox
|
||||
check_box_tag('item_id', toggle_check_todo_path(@todo), @todo.completed?, :class => 'item-checkbox')
|
||||
check_box_tag('item_id', toggle_check_todo_path(@todo), @todo.completed?, :class => 'item-checkbox',
|
||||
:title => @todo.pending? ? 'Blocked by ' + @todo.uncompleted_predecessors.map(&:description).join(', ') : "", :readonly => @todo.pending?)
|
||||
end
|
||||
|
||||
def date_span
|
||||
if @todo.completed?
|
||||
"<span class=\"grey\">#{format_date( @todo.completed_at )}</span>"
|
||||
elsif @todo.pending?
|
||||
"<a title='Depends on: #{@todo.uncompleted_predecessors.map(&:description).join(', ')}'><span class=\"orange\">Pending</span></a> "
|
||||
elsif @todo.deferred?
|
||||
show_date( @todo.show_from )
|
||||
else
|
||||
|
|
@ -95,6 +102,22 @@ module TodosHelper
|
|||
end
|
||||
end
|
||||
|
||||
def successors_span
|
||||
unless @todo.pending_successors.empty?
|
||||
pending_count = @todo.pending_successors.length
|
||||
title = "Has #{pluralize(pending_count, 'pending action')}: #{@todo.pending_successors.map(&:description).join(', ')}"
|
||||
image_tag( 'successor_off.png', :width=>'10', :height=>'16', :border=>'0', :title => title )
|
||||
end
|
||||
end
|
||||
|
||||
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')
|
||||
end
|
||||
end
|
||||
|
||||
def tag_list_text
|
||||
@todo.tags.collect{|t| t.name}.join(', ')
|
||||
end
|
||||
|
|
@ -115,6 +138,10 @@ module TodosHelper
|
|||
if tag_list.empty? then "" else "<span class=\"tags\">#{tag_list}</span>" end
|
||||
end
|
||||
|
||||
def predecessor_list_text
|
||||
@todo.predecessors.map{|t| t.specification}.join(', ')
|
||||
end
|
||||
|
||||
def deferred_due_date
|
||||
if @todo.deferred? && @todo.due
|
||||
"(action due on #{format_date(@todo.due)})"
|
||||
|
|
@ -201,9 +228,10 @@ module TodosHelper
|
|||
end
|
||||
|
||||
def item_container_id (todo)
|
||||
if source_view_is :project
|
||||
return "p#{todo.project_id}items" if todo.active?
|
||||
return "tickler" if todo.deferred?
|
||||
if todo.deferred? or todo.pending?
|
||||
return "tickleritems"
|
||||
elsif source_view_is :project
|
||||
return "p#{todo.project_id}items"
|
||||
end
|
||||
return "c#{todo.context_id}items"
|
||||
end
|
||||
|
|
@ -221,6 +249,8 @@ module TodosHelper
|
|||
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
|
||||
|
||||
|
|
@ -281,4 +311,9 @@ module TodosHelper
|
|||
class_str = todo.starred? ? "starred_todo" : "unstarred_todo"
|
||||
image_tag("blank.png", :title =>"Star action", :class => class_str)
|
||||
end
|
||||
|
||||
def auto_complete_result2(entries, phrase = nil)
|
||||
return entries.map{|e| e.specification()}.join("\n") rescue ''
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
|||
7
app/models/dependency.rb
Normal file
7
app/models/dependency.rb
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
class Dependency < ActiveRecord::Base
|
||||
|
||||
belongs_to :predecessor, :foreign_key => 'predecessor_id', :class_name => 'Todo'
|
||||
belongs_to :successor, :foreign_key => 'successor_id', :class_name => 'Todo'
|
||||
|
||||
end
|
||||
|
||||
|
|
@ -20,6 +20,11 @@ class Project < ActiveRecord::Base
|
|||
:class_name => 'Todo',
|
||||
:conditions => ["todos.state = ? ", "deferred"],
|
||||
:order => "show_from"
|
||||
has_many :pending_todos,
|
||||
:include => [:context,:tags,:project],
|
||||
:class_name => 'Todo',
|
||||
:conditions => ["todos.state = ? ", "pending"],
|
||||
:order => "show_from"
|
||||
|
||||
has_many :notes, :dependent => :delete_all, :order => "created_at DESC"
|
||||
|
||||
|
|
|
|||
|
|
@ -4,12 +4,26 @@ class Todo < ActiveRecord::Base
|
|||
belongs_to :project
|
||||
belongs_to :user
|
||||
belongs_to :recurring_todo
|
||||
|
||||
has_many :predecessor_dependencies, :foreign_key => 'predecessor_id', :class_name => 'Dependency', :dependent => :destroy
|
||||
has_many :successor_dependencies, :foreign_key => 'successor_id', :class_name => 'Dependency', :dependent => :destroy
|
||||
has_many :predecessors, :through => :successor_dependencies
|
||||
has_many :successors, :through => :predecessor_dependencies
|
||||
has_many :uncompleted_predecessors, :through => :successor_dependencies,
|
||||
:source => :predecessor, :conditions => ['NOT (state = ?)', 'completed']
|
||||
has_many :pending_successors, :through => :predecessor_dependencies,
|
||||
:source => :successor, :conditions => ['state = ?', 'pending']
|
||||
|
||||
after_save :save_predecessors
|
||||
|
||||
named_scope :active, :conditions => { :state => 'active' }
|
||||
named_scope :not_completed, :conditions => ['NOT (state = ? )', 'completed']
|
||||
named_scope :are_due, :conditions => ['NOT (todos.due IS NULL)']
|
||||
|
||||
STARRED_TAG_NAME = "starred"
|
||||
RE_TODO = /[^"]+/
|
||||
RE_CONTEXT = /[^"]+/
|
||||
RE_PROJECT = /[^"]+/
|
||||
|
||||
acts_as_state_machine :initial => :active, :column => 'state'
|
||||
|
||||
|
|
@ -19,6 +33,7 @@ class Todo < ActiveRecord::Base
|
|||
state :project_hidden
|
||||
state :completed, :enter => Proc.new { |t| t.completed_at = Time.zone.now }, :exit => Proc.new { |t| t.completed_at = nil }
|
||||
state :deferred
|
||||
state :pending
|
||||
|
||||
event :defer do
|
||||
transitions :to => :deferred, :from => [:active]
|
||||
|
|
@ -30,6 +45,8 @@ class Todo < ActiveRecord::Base
|
|||
|
||||
event :activate do
|
||||
transitions :to => :active, :from => [:project_hidden, :completed, :deferred]
|
||||
transitions :to => :active, :from => [:pending], :guard => :no_uncompleted_predecessors_or_deferral?
|
||||
transitions :to => :deferred, :from => [:pending], :guard => :no_uncompleted_predecessors?
|
||||
end
|
||||
|
||||
event :hide do
|
||||
|
|
@ -40,6 +57,10 @@ class Todo < ActiveRecord::Base
|
|||
transitions :to => :deferred, :from => [:project_hidden], :guard => Proc.new{|t| !t.show_from.blank? }
|
||||
transitions :to => :active, :from => [:project_hidden]
|
||||
end
|
||||
|
||||
event :block do
|
||||
transitions :to => :pending, :from => [:active, :deferred]
|
||||
end
|
||||
|
||||
attr_protected :user
|
||||
|
||||
|
|
@ -51,15 +72,120 @@ class Todo < ActiveRecord::Base
|
|||
validates_presence_of :show_from, :if => :deferred?
|
||||
validates_presence_of :context
|
||||
|
||||
def initialize(*args)
|
||||
super(*args)
|
||||
@predecessor_array = nil # Used for deferred save of predecessors
|
||||
end
|
||||
|
||||
def no_uncompleted_predecessors_or_deferral?
|
||||
return (show_from.blank? or Time.zone.now > show_from and uncompleted_predecessors.empty?)
|
||||
end
|
||||
|
||||
def no_uncompleted_predecessors?
|
||||
return uncompleted_predecessors.empty?
|
||||
end
|
||||
|
||||
|
||||
# Returns a string with description <context, project>
|
||||
def specification
|
||||
project_name = project.is_a?(NullProject) ? "(none)" : project.name
|
||||
return "\"#{description}\" <\"#{context.title}\"; \"#{project_name}\">"
|
||||
end
|
||||
|
||||
def todo_from_specification(specification)
|
||||
# Split specification into parts: description <context, project>
|
||||
re_parts = /"(#{RE_TODO})"\s<"(#{RE_CONTEXT})";\s"(#{RE_PROJECT})">/
|
||||
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]
|
||||
todos = Todo.all(
|
||||
:joins => :context,
|
||||
:conditions => {
|
||||
:description => todo_description,
|
||||
:contexts => {:name => context_name}
|
||||
}
|
||||
)
|
||||
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
|
||||
end
|
||||
|
||||
def validate
|
||||
if !show_from.blank? && show_from < user.date
|
||||
errors.add("show_from", "must be a date in the future")
|
||||
end
|
||||
errors.add(:description, "may not contain \" characters") if /\"/.match(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
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def save_predecessors
|
||||
unless @predecessor_array.nil? # Only save predecessors if they changed
|
||||
current_array = predecessors.map{|p| p.specification}
|
||||
remove_array = current_array - @predecessor_array
|
||||
add_array = @predecessor_array - current_array
|
||||
|
||||
# This is probably a bit naive code...
|
||||
remove_array.each do |specification|
|
||||
t = todo_from_specification(specification)
|
||||
self.predecessors.delete(t) unless t.nil?
|
||||
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)
|
||||
else
|
||||
logger.error "Could not find #{specification}" # Unexpected since validation passed
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def remove_predecessor(predecessor)
|
||||
# remove predecessor and activate myself
|
||||
predecessors.delete(predecessor)
|
||||
self.activate!
|
||||
end
|
||||
|
||||
# Returns true if t is equal to self or a successor of self
|
||||
def is_successor?(t)
|
||||
if self == t
|
||||
return true
|
||||
elsif self.successors.empty?
|
||||
return false
|
||||
else
|
||||
self.successors.each do |item|
|
||||
if item.is_successor?(t)
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
def update_state_from_project
|
||||
if state == 'project_hidden' and !project.hidden?
|
||||
self.state = 'active'
|
||||
if self.uncompleted_predecessors.empty?
|
||||
self.state = 'active'
|
||||
else
|
||||
self.state = 'pending'
|
||||
end
|
||||
elsif state == 'active' and project.hidden?
|
||||
self.state = 'project_hidden'
|
||||
end
|
||||
|
|
@ -92,7 +218,7 @@ class Todo < ActiveRecord::Base
|
|||
def project
|
||||
original_project.nil? ? Project.null_object : original_project
|
||||
end
|
||||
|
||||
|
||||
alias_method :original_set_initial_state, :set_initial_state
|
||||
|
||||
def set_initial_state
|
||||
|
|
@ -138,6 +264,28 @@ class Todo < ActiveRecord::Base
|
|||
def from_recurring_todo?
|
||||
return self.recurring_todo_id != nil
|
||||
end
|
||||
|
||||
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)
|
||||
end
|
||||
|
||||
def add_predecessor(t)
|
||||
@predecessor_array = predecessors.map{|p| p.specification}
|
||||
@predecessor_array << t.specification
|
||||
end
|
||||
|
||||
# Return todos that should be activated if the current todo is completed
|
||||
def pending_to_activate
|
||||
return successors.find_all {|t| t.uncompleted_predecessors.empty?}
|
||||
end
|
||||
|
||||
# Return todos that should be blocked if the current todo is undone
|
||||
def active_to_block
|
||||
return successors.find_all {|t| t.active? or t.deferred?}
|
||||
end
|
||||
|
||||
# Rich Todo API
|
||||
|
||||
|
|
|
|||
|
|
@ -98,6 +98,10 @@ class User < ActiveRecord::Base
|
|||
find(:all, :conditions => ['show_from <= ?', Time.zone.now ]).collect { |t| t.activate! }
|
||||
end
|
||||
end
|
||||
has_many :pending_todos,
|
||||
:class_name => 'Todo',
|
||||
:conditions => [ 'state = ?', 'pending' ],
|
||||
:order => 'show_from ASC, todos.created_at DESC'
|
||||
has_many :completed_todos,
|
||||
:class_name => 'Todo',
|
||||
:conditions => ['todos.state = ? AND NOT(todos.completed_at IS NULL)', 'completed'],
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
</div>
|
||||
|
||||
<%= render :partial => "projects/project", :locals => { :project => @project, :collapsible => false } %>
|
||||
<%= render :partial => "todos/deferred", :locals => { :deferred => @deferred, :collapsible => false, :append_descriptor => "in this project", :parent_container_type => 'project' } %>
|
||||
<%= render :partial => "todos/deferred", :locals => { :deferred => @deferred, :collapsible => false, :append_descriptor => "in this project", :parent_container_type => 'project', :pending => @pending } %>
|
||||
<% unless @max_completed==0 -%>
|
||||
<%= render :partial => "todos/completed", :locals => { :done => @done, :collapsible => false, :suppress_project => true, :append_descriptor => "in this project" } %>
|
||||
<% end -%>
|
||||
|
|
|
|||
|
|
@ -51,6 +51,8 @@
|
|||
<%= text_field("todo", "show_from", "size" => 12, "class" => "Date", "tabindex" => 7, "autocomplete" => "off") %>
|
||||
</div>
|
||||
|
||||
<label for="predecessor_list">Depends on</label>
|
||||
<%= text_field_tag "predecessor_list", nil, :size => 30, :tabindex => 8 %>
|
||||
<%= source_view_tag( @source_view ) %>
|
||||
<%= hidden_field_tag :_tag_name, @tag_name.underscore.gsub(/\s+/,'_') if source_view_is :tag %>
|
||||
|
||||
|
|
|
|||
|
|
@ -3,15 +3,16 @@
|
|||
<% if collapsible %>
|
||||
<a href="#" class="container_toggle" id="toggle_deferred"><%= image_tag("collapse.png") %></a>
|
||||
<% end %>
|
||||
Deferred actions <%= append_descriptor ? append_descriptor : '' %>
|
||||
Deferred/pending actions <%= append_descriptor ? append_descriptor : '' %>
|
||||
</h2>
|
||||
|
||||
<div id="tickleritems" class="items toggle_target">
|
||||
<div id="tickler-empty-nd" style="display:<%= deferred.empty? ? 'block' : 'none'%>;">
|
||||
<div class="message"><p>Currently there are no deferred actions</p></div>
|
||||
<div id="tickler-empty-nd" style="display:<%= deferred.empty? && pending.empty? ? 'block' : 'none'%>;">
|
||||
<div class="message"><p>Currently there are no deferred or pending actions</p></div>
|
||||
</div>
|
||||
|
||||
<%= render :partial => "todos/todo", :collection => deferred, :locals => { :parent_container_type => parent_container_type } %>
|
||||
<%= render :partial => "todos/todo", :collection => pending, :locals => { :parent_container_type => parent_container_type } %>
|
||||
|
||||
</div><!-- [end:items] -->
|
||||
</div><!-- [end:tickler] -->
|
||||
</div><!-- [end:tickler] -->
|
||||
|
|
|
|||
|
|
@ -46,13 +46,21 @@
|
|||
</a>
|
||||
</div>
|
||||
|
||||
<label class="predecessor_list_label" for="<%= dom_id(@todo, 'predecessor_list') %>">Depends on (separate with commas)</label>
|
||||
<%= text_field_tag 'predecessor_list', predecessor_list_text, :id => dom_id(@todo, 'predecessor_list'), :size => 30, :tabindex => 15 %>
|
||||
<%= content_tag("div", "", :id => dom_id(@todo, 'predecessor_list')+"_auto_complete", :class => "auto_complete") %>
|
||||
<%= auto_complete_field dom_id(@todo, 'predecessor_list'), {
|
||||
:url => {:controller => 'todos', :action => 'auto_complete_for_predecessor', :id => @todo.id},
|
||||
:tokens => [',']
|
||||
} %>
|
||||
|
||||
<% if controller.controller_name == "project" || @todo.deferred? -%>
|
||||
<input type="hidden" name="on_project_page" value="true" />
|
||||
<% end -%>
|
||||
|
||||
<div class="submit_box">
|
||||
<div class="widgets">
|
||||
<button type="submit" class="positive" id="<%= dom_id(@todo, 'submit') %>" tabindex="15">
|
||||
<button type="submit" class="positive" id="<%= dom_id(@todo, 'submit') %>" tabindex="16">
|
||||
<%=image_tag("accept.png", :alt => "") %>
|
||||
Update
|
||||
</button>
|
||||
|
|
|
|||
26
app/views/todos/_successor.html.erb
Normal file
26
app/views/todos/_successor.html.erb
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
<%
|
||||
suppress_context ||= false
|
||||
suppress_project ||= false
|
||||
suppress_dependencies ||= false
|
||||
parameters = "_source_view=#{@source_view}"
|
||||
parameters += "&_tag_name=#{@tag_name}" if @source_view == 'tag'
|
||||
@z_index_counter = @z_index_counter - 1 # for IE z-index bug
|
||||
%>
|
||||
<div id="<%= dom_id(successor, 'successor') %>" class="item-container">
|
||||
<div id="<%= dom_id(successor, 'successor_line') %>">
|
||||
<div class="description<%= staleness_class( successor ) %>" style="margin-left: 20px">
|
||||
<span class="todo.descr"><%= h sanitize(successor.description) %></span>
|
||||
|
||||
<%= link_to_remote(
|
||||
image_tag("blank.png", :title => "Remove dependency (does not delete the action)", :align => "absmiddle", :class => "delete_item"),
|
||||
{:url => {:controller => 'todos', :action => 'remove_predecessor', :id => successor.id},
|
||||
:method => 'delete',
|
||||
:with => "'#{parameters}&predecessor=#{predecessor.id}'",
|
||||
:before => successor_start_waiting_js(successor)},
|
||||
{:style => "background: transparent;"}) %>
|
||||
|
||||
<%= render(:partial => "todos/toggle_successors", :locals => { :item => successor, :suppress_button => true }) unless successor.pending_successors.empty? %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -26,13 +26,16 @@ parameters += "&_tag_name=#{@tag_name}" if @source_view == 'tag'
|
|||
</li>
|
||||
</ul>
|
||||
<div class="description<%= staleness_class( todo ) %>">
|
||||
<%= grip_span %>
|
||||
<%= date_span -%>
|
||||
<span class="todo.descr"><%= h sanitize(todo.description) %></span>
|
||||
<% #= successors_span %>
|
||||
<%= image_tag_for_recurring_todo(todo) if @todo.from_recurring_todo? %>
|
||||
<%= tag_list %>
|
||||
<%= deferred_due_date %>
|
||||
<%= project_and_context_links( parent_container_type, :suppress_context => suppress_context, :suppress_project => suppress_project ) %>
|
||||
<%= render(:partial => "todos/toggle_notes", :locals => { :item => todo }) if todo.notes? %>
|
||||
<%= render(:partial => "todos/toggle_successors", :locals => { :item => todo }) unless todo.pending_successors.empty? %>
|
||||
</div>
|
||||
</div>
|
||||
<div id="<%= dom_id(todo, 'edit') %>" class="edit-form" style="display:none">
|
||||
|
|
|
|||
15
app/views/todos/_toggle_successors.html.erb
Normal file
15
app/views/todos/_toggle_successors.html.erb
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<%
|
||||
suppress_button ||= false
|
||||
%>
|
||||
<%= link_to(image_tag( 'blank.png', :width=>'16', :height=>'16', :border=>'0' ), "#", {:class => 'show_successors', :title => 'Show successors'}) unless suppress_button %>
|
||||
|
||||
<div class="todo_successors" id="<%= dom_id(item, 'successors') %>" style=<%= suppress_button ? "display:display" : "display:none" %> >
|
||||
<%= render :partial => "todos/successor",
|
||||
:collection => item.pending_successors,
|
||||
:locals => { :todo => item,
|
||||
:parent_container_type => parent_container_type,
|
||||
:suppress_dependencies => true,
|
||||
:predecessor => item }
|
||||
%>
|
||||
</div>
|
||||
|
||||
27
app/views/todos/add_predecessor.js.rjs
Normal file
27
app/views/todos/add_predecessor.js.rjs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
if @saved
|
||||
# show update message
|
||||
status_message = "Added #{@predecessor.description} as dependency."
|
||||
unless @original_state == 'pending'
|
||||
status_message += " #{@todo.description} set to pending"
|
||||
end
|
||||
# remove successor from page
|
||||
page[@todo].remove
|
||||
# regenerate predecessor to add arrow
|
||||
page[@predecessor].replace_html :partial => 'todos/todo', :locals => { :todo => @predecessor, :parent_container_type => parent_container_type }
|
||||
|
||||
# show in tickler box in project view
|
||||
if source_view_is_one_of :project, :tag
|
||||
page['tickler-empty-nd'].hide
|
||||
page.replace "tickler", :partial => 'todos/deferred', :locals => { :deferred => @todo.project.deferred_todos,
|
||||
:collapsible => false,
|
||||
:append_descriptor => "in this project",
|
||||
:parent_container_type => 'project',
|
||||
:pending => @todo.project.pending_todos }
|
||||
end
|
||||
|
||||
page << 'enable_rich_interaction();'
|
||||
page.notify :notice, status_message, 5.0
|
||||
else
|
||||
page.replace_html "status", content_tag("div", content_tag("h2", "Unable to add dependency"), "id" => "errorExplanation", "class" => "errorExplanation")
|
||||
end
|
||||
|
||||
|
|
@ -2,6 +2,7 @@ if @saved
|
|||
page.hide 'status'
|
||||
status_message = 'Added new next action'
|
||||
status_message += ' to tickler' if @todo.deferred?
|
||||
status_message += ' in pending state' if @todo.pending?
|
||||
status_message = 'Added new project / ' + status_message if @new_project_created
|
||||
status_message = 'Added new context / ' + status_message if @new_context_created
|
||||
page.notify :notice, status_message, 5.0
|
||||
|
|
@ -21,9 +22,19 @@ if @saved
|
|||
page.visual_effect :highlight, dom_id(@todo), :duration => 3
|
||||
page[empty_container_msg_div_id].hide unless empty_container_msg_div_id.nil?
|
||||
end
|
||||
# make sure the behavior of the new/updated todo is enabled
|
||||
page['tickler-empty-nd'].hide if source_view_is :deferred
|
||||
if (source_view_is :project and @todo.pending?) or (source_view_is :deferred)
|
||||
page['tickler-empty-nd'].hide # For some reason this does not work: page['tickler-empty-nd'].hide if (@todo.pending? or (source_view_is :deferred))
|
||||
end
|
||||
end
|
||||
# Update predecessors (if they exist and are visible)
|
||||
@todo.uncompleted_predecessors.each do |p|
|
||||
page << "if ($(\'#{item_container_id(p)}\')) {"
|
||||
page[p].replace_html :partial => 'todos/todo',
|
||||
:locals => { :todo => p, :parent_container_type => parent_container_type }
|
||||
page << "}"
|
||||
end
|
||||
# make sure the behavior of the new/updated todo is enabled
|
||||
page << "enable_rich_interaction();"
|
||||
else
|
||||
page.show 'status'
|
||||
page.replace_html 'status', "#{error_messages_for('todo', :object_name => 'action')}"
|
||||
|
|
|
|||
|
|
@ -23,6 +23,14 @@ if @saved
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Activate pending todos that are successors of the deleted
|
||||
@pending_to_activate.each do |t|
|
||||
logger.debug "#300: Removing #{t.description} from pending block and adding it to active"
|
||||
page[t].remove if source_view_is(:project) or source_view_is(:tag)
|
||||
page.insert_html :bottom, item_container_id(t), :partial => 'todos/todo', :locals => { :todo => t, :parent_container_type => parent_container_type }
|
||||
page.visual_effect :highlight, dom_id(t, 'line'), {'startcolor' => "'#99ff99'", :duration => 2}
|
||||
end
|
||||
else
|
||||
page.notify :error, "There was an error deleting the item #{@todo.description}", 8.0
|
||||
end
|
||||
|
|
|
|||
25
app/views/todos/remove_predecessor.js.rjs
Normal file
25
app/views/todos/remove_predecessor.js.rjs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
if @removed
|
||||
status_message = "Removed #{@successor.description} as dependency from #{@predecessor.description}."
|
||||
page.notify :notice, status_message, 5.0
|
||||
|
||||
# replace old predecessor with one without the successor
|
||||
page.replace dom_id(@predecessor), :partial => 'todos/todo', :locals => {
|
||||
:todo => @predecessor, :parent_container_type => parent_container_type }
|
||||
|
||||
# update display if pending->active
|
||||
if @successor.active?
|
||||
page[@successor].remove unless source_view_is_one_of(:todo, :context)
|
||||
page.insert_html :bottom, item_container_id(@successor), :partial => 'todos/todo', :locals => {
|
||||
:todo => @successor, :parent_container_type => parent_container_type }
|
||||
page.visual_effect :highlight, dom_id(@successor, 'line'), {'startcolor' => "'#99ff99'"}
|
||||
end
|
||||
|
||||
# update display if pending->deferred
|
||||
if @successor.deferred?
|
||||
page.replace dom_id(@successor), :partial => 'todos/todo', :locals => {
|
||||
:todo => @successor, :parent_container_type => parent_container_type }
|
||||
end
|
||||
page << "enable_rich_interaction();"
|
||||
else
|
||||
page.notify :error, "There was an error removing the dependency", 8.0
|
||||
end
|
||||
|
|
@ -8,7 +8,13 @@
|
|||
:locals => { :collapsible => true } %>
|
||||
|
||||
<% unless @deferred.nil? -%>
|
||||
<%= render :partial => "todos/deferred", :locals => { :deferred => @deferred, :collapsible => true, :append_descriptor => "tagged with ‘#{@tag_name}’", :parent_container_type => 'tag' } %>
|
||||
<%= render :partial => "todos/deferred", :locals => {
|
||||
:deferred => @deferred,
|
||||
:pending => @pending,
|
||||
:collapsible => true,
|
||||
:append_descriptor => "tagged with ‘#{@tag_name}’",
|
||||
:parent_container_type => 'tag'
|
||||
} %>
|
||||
<% end -%>
|
||||
|
||||
<% unless @hidden_todos.nil? -%>
|
||||
|
|
|
|||
|
|
@ -8,9 +8,16 @@ if @saved
|
|||
page.insert_html :top, "completed_containeritems", :partial => 'todos/todo', :locals => { :todo => @todo, :parent_container_type => "completed" }
|
||||
page.visual_effect :highlight, dom_id(@todo, 'line'), {'startcolor' => "'#99ff99'"}
|
||||
page[empty_container_msg_div_id].show if @down_count == 0 && !empty_container_msg_div_id.nil?
|
||||
page.show 'tickler-empty-nd' if source_view_is(:project) && @deferred_count == 0
|
||||
page.show 'tickler-empty-nd' if source_view_is(:project) && @deferred_count == 0 && @pending_count == 0
|
||||
page.hide 'empty-d' # If we've checked something as done, completed items can't be empty
|
||||
end
|
||||
# Activate pending todos that are successors of the completed
|
||||
@pending_to_activate.each do |t|
|
||||
logger.debug "#300: Removing #{t.description} from pending block and adding it to active"
|
||||
page[t].remove if source_view_is(:project) or source_view_is(:tag)
|
||||
page.insert_html :bottom, item_container_id(t), :partial => 'todos/todo', :locals => { :todo => t, :parent_container_type => parent_container_type }
|
||||
page.visual_effect :highlight, dom_id(t, 'line'), {'startcolor' => "'#99ff99'"}
|
||||
end
|
||||
|
||||
# remove container if empty
|
||||
if @remaining_in_context == 0 && source_view_is(:todo)
|
||||
|
|
@ -44,6 +51,15 @@ if @saved
|
|||
page.show "empty-d" if @completed_count == 0
|
||||
page.show "c"+@todo.context.id.to_s
|
||||
page[empty_container_msg_div_id].hide unless empty_container_msg_div_id.nil? # If we've checked something as undone, incomplete items can't be empty
|
||||
# If active todos are successors of the reactivated todo they will be blocked
|
||||
@active_to_block.each do |t|
|
||||
logger.debug "#300: Block #{t.description} that are a successor of #{@todo.description}"
|
||||
page[t].remove # Remove it from active
|
||||
if source_view_is(:project) or source_view_is(:tag) # Insert it in deferred/pending block if existing
|
||||
logger.debug "Insert #{t.description} in #{item_container_id(t)} block"
|
||||
page.insert_html :bottom, item_container_id(t), :partial => 'todos/todo', :locals => { :todo => t, :parent_container_type => parent_container_type }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
page.hide "status"
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ if @saved
|
|||
page.notify :notice, status_message, 5.0
|
||||
|
||||
if source_view_is_one_of(:todo, :context, :tag)
|
||||
if @context_changed || @todo.deferred?
|
||||
if @context_changed || @todo.deferred? || @todo.pending?
|
||||
page[@todo].remove
|
||||
|
||||
if (@remaining_in_context == 0)
|
||||
|
|
@ -87,7 +87,14 @@ if @saved
|
|||
page["p#{@todo.project_id}empty-nd"].hide
|
||||
page.replace_html "badge_count", @down_count
|
||||
else
|
||||
page.replace dom_id(@todo), :partial => 'todos/todo', :locals => { :todo => @todo, :parent_container_type => parent_container_type }
|
||||
page.replace_html "p#{@todo.project_id}items", :partial => 'todos/todo', :collection => @todo.project.not_done_todos,
|
||||
:locals => { :parent_container_type => parent_container_type }
|
||||
page.replace "tickler", :partial => 'todos/deferred', :locals => { :deferred => @todo.project.deferred_todos,
|
||||
:collapsible => false,
|
||||
:append_descriptor => "in this project",
|
||||
:parent_container_type => 'project',
|
||||
:pending => @todo.project.pending_todos }
|
||||
page['tickler-empty-nd'].show if (@deferred_count == 0 and @pending_count == 0)
|
||||
page.visual_effect :highlight, dom_id(@todo), :duration => 3
|
||||
end
|
||||
elsif source_view_is :deferred
|
||||
|
|
@ -135,6 +142,13 @@ if @saved
|
|||
else
|
||||
logger.error "unexpected source_view '#{params[:_source_view]}'"
|
||||
end
|
||||
# Update predecessors (if they exist and are visible)
|
||||
@todo.uncompleted_predecessors.each do |p|
|
||||
page << "if ($(\'#{item_container_id(p)}\')) {"
|
||||
page[p].replace_html :partial => 'todos/todo',
|
||||
:locals => { :todo => p, :parent_container_type => parent_container_type }
|
||||
page << "}"
|
||||
end
|
||||
else
|
||||
page.show 'error_status'
|
||||
page.replace_html 'error_status', "#{error_messages_for('todo')}"
|
||||
|
|
|
|||
13
db/migrate/20090516000646_add_todo_dependencies.rb
Normal file
13
db/migrate/20090516000646_add_todo_dependencies.rb
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
class AddTodoDependencies < ActiveRecord::Migration
|
||||
def self.up
|
||||
create_table :dependencies do |t|
|
||||
t.integer :successor_id, :null => false
|
||||
t.integer :predecessor_id, :null => false
|
||||
t.string :relationship_type
|
||||
end
|
||||
end
|
||||
|
||||
def self.down
|
||||
drop_table :dependencies
|
||||
end
|
||||
end
|
||||
Binary file not shown.
BIN
db/tracks-17-test.db
Normal file
BIN
db/tracks-17-test.db
Normal file
Binary file not shown.
BIN
public/images/grip.png
Normal file
BIN
public/images/grip.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 192 B |
BIN
public/images/successor_off.png
Normal file
BIN
public/images/successor_off.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.8 KiB |
BIN
public/images/successor_on.png
Normal file
BIN
public/images/successor_on.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.5 KiB |
|
|
@ -191,6 +191,8 @@ function enable_rich_interaction(){
|
|||
$('input[name=project[default_context_name]]').autocomplete(contextNames, {matchContains: true});
|
||||
$('input[name=project_name]').autocomplete(projectNames, {matchContains: true});
|
||||
$('input[name=tag_list]').autocomplete(tagNames, {multiple: true,multipleSeparator:',',matchContains:true});
|
||||
$('input[name=predecessor_list]').autocomplete('/todos/auto_complete_for_predecessor',
|
||||
{multiple: true,multipleSeparator:','});
|
||||
|
||||
/* have to bind on keypress because of limitataions of live() */
|
||||
$('input[name=project_name]').live('keypress', function(){
|
||||
|
|
@ -203,6 +205,23 @@ function enable_rich_interaction(){
|
|||
$('input[name=tag_list]').live('keypress', function(){
|
||||
$(this).attr('edited', 'true');
|
||||
});
|
||||
|
||||
/* Drag & Drop for successor/predecessor */
|
||||
function drop_todo(evt, ui) {
|
||||
dragged_todo = ui.draggable[0].id.split('_')[2];
|
||||
dropped_todo = this.id.split('_')[2];
|
||||
ui.draggable.hide();
|
||||
$(this).block({message: null});
|
||||
$.post('/todos/add_predecessor',
|
||||
{successor: dragged_todo, predecessor: dropped_todo},
|
||||
null, 'script');
|
||||
}
|
||||
|
||||
$('.item-show').draggable({handle: '.grip', revert: 'invalid'});
|
||||
$('.item-show').droppable({
|
||||
drop: drop_todo,
|
||||
hoverClass: 'hover'
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
|
|
@ -247,6 +266,10 @@ $(document).ready(function() {
|
|||
$(this).next().toggle("fast"); return false;
|
||||
});
|
||||
|
||||
$(".show_successors").live('click', function () {
|
||||
$(this).next().toggle("fast"); return false;
|
||||
});
|
||||
|
||||
/* fade flashes and alerts in automatically */
|
||||
$(".alert").fadeOut(8000);
|
||||
|
||||
|
|
|
|||
|
|
@ -364,6 +364,7 @@ $.Autocompleter = function(input, options) {
|
|||
// limit abortion to this input
|
||||
port: "autocomplete" + input.name,
|
||||
dataType: options.dataType,
|
||||
type: 'POST',
|
||||
url: options.url,
|
||||
data: $.extend({
|
||||
q: lastWord(term),
|
||||
|
|
@ -805,4 +806,4 @@ $.fn.selection = function(start, end) {
|
|||
}
|
||||
};
|
||||
|
||||
})(jQuery);
|
||||
})(jQuery);
|
||||
|
|
|
|||
|
|
@ -99,6 +99,9 @@ a.to_bottom:hover {background: transparent url(../images/bottom_on.png) no-repea
|
|||
a.show_notes, a.link_to_notes {background-image: url(../images/notes_off.png); background-repeat: no-repeat; padding: 1px; background-color: transparent;}
|
||||
a.show_notes:hover, a.link_to_notes:hover {background-image: url(../images/notes_on.png); background-repeat: no-repeat; padding: 1px; background-color: transparent;}
|
||||
|
||||
a.show_successors, a.link_to_successors {background-image: url(/images/successor_off.png); background-repeat: no-repeat; padding: 1px; background-color: transparent;}
|
||||
a.show_successors:hover, a.link_to_successors:hover {background-image: url(/images/successor_on.png); background-repeat: no-repeat; padding: 1px; background-color: transparent;}
|
||||
|
||||
/* Structural divs */
|
||||
|
||||
#content {
|
||||
|
|
@ -378,11 +381,7 @@ input.item-checkbox {
|
|||
|
||||
.description, .stale_l1, .stale_l2, .stale_l3 {
|
||||
margin-left: 60px;
|
||||
position:relative;
|
||||
}
|
||||
|
||||
.stale_l1, .stale_l2, .stale_l3 {
|
||||
padding-left: 3px;
|
||||
position:relative;
|
||||
}
|
||||
|
||||
.stale_l1 {
|
||||
|
|
@ -936,6 +935,15 @@ div.message {
|
|||
color: #666;
|
||||
}
|
||||
|
||||
.grip {
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.hover {
|
||||
background: #EAEAEA;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Error message styles */
|
||||
.fieldWithErrors {
|
||||
padding: 2px;
|
||||
|
|
@ -1262,6 +1270,7 @@ div.auto_complete ul {
|
|||
list-style-type:none;
|
||||
}
|
||||
div.auto_complete ul li {
|
||||
font-size: 1.0em;
|
||||
margin:0;
|
||||
padding:3px;
|
||||
list-style-type: none;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
type "todo_description", "choose era"
|
||||
type "todo_show_from", "1/1/2030"
|
||||
click "css=#todo-form-new-action .submit_box button"
|
||||
wait_for_element_present "xpath=//div[@id='tickler'] //div[@class='item-container']"
|
||||
wait_for_element_present "xpath=//div[@id='tickler'] //div[@class='item-container'] //a[@title='01/01/2030']"
|
||||
wait_for_element_present "css=div#tickler div.item-container"
|
||||
wait_for_element_present "css=div#tickler div.item-container a[title=01/01/2030]"
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ open "/projects/1"
|
|||
# add new todo
|
||||
type "todo_description", "a brand new todo"
|
||||
click "css=#todo-form-new-action .submit_box button"
|
||||
wait_for_element_present "xpath=//div[@id='p1items'] //div[@class='item-container']"
|
||||
wait_for_element_present "css=div#p1items div.item-container"
|
||||
|
||||
# wait for flash to mention that todo was added and verify existence of new todo
|
||||
wait_for_visible "flash"
|
||||
|
|
|
|||
|
|
@ -10,4 +10,4 @@ assert_context_count_incremented do
|
|||
assert_confirmation "New context \"errands\" will be also created. Are you sure?"
|
||||
end
|
||||
wait_for_not_visible "tickler-empty-nd"
|
||||
wait_for_element_present "xpath=//div[@class='item-container'] //a[@title='01/01/2030']"
|
||||
wait_for_element_present "css=div.item-container a[title=01/01/2030]"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue