mirror of
https://github.com/TracksApp/tracks.git
synced 2025-12-24 11:10:12 +01:00
Merge branch 'master' of git@github.com:gorn/tracks
This commit is contained in:
commit
8aa573a73e
106 changed files with 2221 additions and 585 deletions
|
|
@ -9,12 +9,14 @@ require "redcloth"
|
|||
require 'date'
|
||||
require 'time'
|
||||
|
||||
# Commented the following line because of #744. It prevented rake db:migrate to
|
||||
# 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
|
||||
# when you feshly create a new database Old comment: We need this in development
|
||||
# mode, or you get 'method missing' errors
|
||||
#
|
||||
# Tag
|
||||
# Tag
|
||||
|
||||
class CannotAccessContext < RuntimeError; end
|
||||
|
||||
class ApplicationController < ActionController::Base
|
||||
|
||||
|
|
@ -113,7 +115,7 @@ class ApplicationController < ActionController::Base
|
|||
def format_date(date)
|
||||
if date
|
||||
date_format = prefs.date_format
|
||||
formatted_date = date.strftime("#{date_format}")
|
||||
formatted_date = date.in_time_zone(prefs.time_zone).strftime("#{date_format}")
|
||||
else
|
||||
formatted_date = ''
|
||||
end
|
||||
|
|
@ -160,7 +162,39 @@ class ApplicationController < ActionController::Base
|
|||
response.content_type = 'text/html'
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def create_todo_from_recurring_todo(rt, date=nil)
|
||||
# create todo and initialize with data from recurring_todo rt
|
||||
todo = current_user.todos.build( { :description => rt.description, :notes => rt.notes, :project_id => rt.project_id, :context_id => rt.context_id})
|
||||
|
||||
# set dates
|
||||
todo.recurring_todo_id = rt.id
|
||||
todo.due = rt.get_due_date(date)
|
||||
|
||||
show_from_date = rt.get_show_from_date(date)
|
||||
if show_from_date.nil?
|
||||
todo.show_from=nil
|
||||
else
|
||||
# make sure that show_from is not in the past
|
||||
todo.show_from = show_from_date < Time.zone.now ? nil : show_from_date
|
||||
end
|
||||
|
||||
saved = todo.save
|
||||
if saved
|
||||
todo.tag_with(rt.tag_list, current_user)
|
||||
todo.tags.reload
|
||||
end
|
||||
|
||||
# increate number of occurences created from recurring todo
|
||||
rt.inc_occurences
|
||||
|
||||
# mark recurring todo complete if there are no next actions left
|
||||
checkdate = todo.due.nil? ? todo.show_from : todo.due
|
||||
rt.toggle_completion! unless rt.has_next_todo(checkdate)
|
||||
|
||||
return saved ? todo : nil
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def admin_login_required
|
||||
|
|
@ -192,7 +226,7 @@ class ApplicationController < ActionController::Base
|
|||
def openid_enabled?
|
||||
self.class.openid_enabled?
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
def parse_date_per_user_prefs( s )
|
||||
|
|
@ -231,29 +265,5 @@ class ApplicationController < ActionController::Base
|
|||
def set_time_zone
|
||||
Time.zone = current_user.prefs.time_zone if logged_in?
|
||||
end
|
||||
|
||||
def create_todo_from_recurring_todo(rt, date=nil)
|
||||
# create todo and initialize with data from recurring_todo rt
|
||||
todo = current_user.todos.build( { :description => rt.description, :notes => rt.notes, :project_id => rt.project_id, :context_id => rt.context_id})
|
||||
|
||||
# set dates
|
||||
todo.due = rt.get_due_date(date)
|
||||
todo.show_from = rt.get_show_from_date(date)
|
||||
todo.recurring_todo_id = rt.id
|
||||
saved = todo.save
|
||||
if saved
|
||||
todo.tag_with(rt.tag_list, current_user)
|
||||
todo.tags.reload
|
||||
end
|
||||
|
||||
# increate number of occurences created from recurring todo
|
||||
rt.inc_occurences
|
||||
|
||||
# mark recurring todo complete if there are no next actions left
|
||||
checkdate = todo.due.nil? ? todo.show_from : todo.due
|
||||
rt.toggle_completion! unless rt.has_next_todo(checkdate)
|
||||
|
||||
return saved ? todo : nil
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
|||
|
|
@ -14,36 +14,10 @@ class BackendController < ApplicationController
|
|||
|
||||
def new_rich_todo(username, token, default_context_id, description, notes)
|
||||
check_token(username,token)
|
||||
description,context = split_by_char('@',description)
|
||||
description,project = split_by_char('>',description)
|
||||
if(!context.nil? && project.nil?)
|
||||
context,project = split_by_char('>',context)
|
||||
end
|
||||
# logger.info("context='#{context}' project='#{project}")
|
||||
|
||||
context_id = default_context_id
|
||||
unless(context.nil?)
|
||||
found_context = @user.active_contexts.find_by_namepart(context)
|
||||
found_context = @user.contexts.find_by_namepart(context) if found_context.nil?
|
||||
context_id = found_context.id unless found_context.nil?
|
||||
end
|
||||
check_context_belongs_to_user(context_id)
|
||||
|
||||
project_id = nil
|
||||
unless(project.blank?)
|
||||
if(project[0..3].downcase == "new:")
|
||||
found_project = @user.projects.build
|
||||
found_project.name = project[4..255+4].strip
|
||||
found_project.save!
|
||||
else
|
||||
found_project = @user.active_projects.find_by_namepart(project)
|
||||
found_project = @user.projects.find_by_namepart(project) if found_project.nil?
|
||||
end
|
||||
project_id = found_project.id unless found_project.nil?
|
||||
end
|
||||
|
||||
todo = create_todo(description, context_id, project_id, notes)
|
||||
todo.id
|
||||
item = Todo.from_rich_message(@user, default_context_id, description, notes)
|
||||
item.save
|
||||
raise item.errors.full_messages.to_s if item.new_record?
|
||||
item.id
|
||||
end
|
||||
|
||||
def list_contexts(username, token)
|
||||
|
|
@ -84,25 +58,6 @@ class BackendController < ApplicationController
|
|||
raise item.errors.full_messages.to_s if item.new_record?
|
||||
item
|
||||
end
|
||||
|
||||
def split_by_char(separator,string)
|
||||
parts = string.split(separator)
|
||||
|
||||
# if the separator is used more than once, concat the last parts this is
|
||||
# needed to get 'description @ @home > project' working for contexts
|
||||
# starting with @
|
||||
if parts.length > 2
|
||||
2.upto(parts.length-1) { |i| parts[1] += (separator +parts[i]) }
|
||||
end
|
||||
|
||||
return safe_strip(parts[0]), safe_strip(parts[1])
|
||||
end
|
||||
|
||||
def safe_strip(s)
|
||||
s.strip! unless s.nil?
|
||||
s
|
||||
end
|
||||
end
|
||||
|
||||
class InvalidToken < RuntimeError; end
|
||||
class CannotAccessContext < RuntimeError; end
|
||||
|
|
|
|||
|
|
@ -83,10 +83,14 @@ class ContextsController < ApplicationController
|
|||
end
|
||||
@context.attributes = params["context"]
|
||||
if @context.save
|
||||
if params['wants_render']
|
||||
if boolean_param('wants_render')
|
||||
respond_to do |format|
|
||||
format.js
|
||||
end
|
||||
elsif boolean_param('update_context_name')
|
||||
@contexts = current_user.projects
|
||||
render :template => 'contexts/update_context_name.js.rjs'
|
||||
return
|
||||
else
|
||||
render :text => success_text || 'Success'
|
||||
end
|
||||
|
|
@ -130,10 +134,10 @@ class ContextsController < ApplicationController
|
|||
def render_contexts_mobile
|
||||
lambda do
|
||||
@page_title = "TRACKS::List Contexts"
|
||||
@active_contexts = @contexts.find(:all, { :conditions => ["hide = ?", false]})
|
||||
@hidden_contexts = @contexts.find(:all, { :conditions => ["hide = ?", true]})
|
||||
@active_contexts = @contexts.active
|
||||
@hidden_contexts = @contexts.hidden
|
||||
@down_count = @active_contexts.size + @hidden_contexts.size
|
||||
cookies[:mobile_url]=request.request_uri
|
||||
cookies[:mobile_url]= {:value => request.request_uri, :secure => TRACKS_COOKIES_SECURE}
|
||||
render :action => 'index_mobile'
|
||||
end
|
||||
end
|
||||
|
|
@ -143,7 +147,7 @@ class ContextsController < ApplicationController
|
|||
@page_title = "TRACKS::List actions in "+@context.name
|
||||
@not_done = @not_done_todos.select {|t| t.context_id == @context.id }
|
||||
@down_count = @not_done.size
|
||||
cookies[:mobile_url]=request.request_uri
|
||||
cookies[:mobile_url]= {:value => request.request_uri, :secure => TRACKS_COOKIES_SECURE}
|
||||
@mobile_from_context = @context.id
|
||||
render :action => 'mobile_show_context'
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ class DataController < ApplicationController
|
|||
require 'csv'
|
||||
|
||||
def index
|
||||
@page_title = "TRACKS::Export"
|
||||
@page_title = "TRACKS::Export"
|
||||
end
|
||||
|
||||
def import
|
||||
|
|
@ -24,6 +24,7 @@ class DataController < ApplicationController
|
|||
all_tables['tags'] = current_user.tags.find(:all)
|
||||
all_tables['taggings'] = current_user.taggings.find(:all)
|
||||
all_tables['notes'] = current_user.notes.find(:all)
|
||||
all_tables['recurring_todos'] = current_user.recurring_todos.find(:all)
|
||||
|
||||
result = all_tables.to_yaml
|
||||
result.gsub!(/\n/, "\r\n") # TODO: general functionality for line endings
|
||||
|
|
@ -34,21 +35,21 @@ class DataController < ApplicationController
|
|||
content_type = 'text/csv'
|
||||
CSV::Writer.generate(result = "") do |csv|
|
||||
csv << ["id", "Context", "Project", "Description", "Notes", "Tags",
|
||||
"Created at", "Due", "Completed at", "User ID", "Show from",
|
||||
"state"]
|
||||
"Created at", "Due", "Completed at", "User ID", "Show from",
|
||||
"state"]
|
||||
current_user.todos.find(:all, :include => [:context, :project]).each do |todo|
|
||||
# Format dates in ISO format for easy sorting in spreadsheet
|
||||
# Print context and project names for easy viewing
|
||||
# Format dates in ISO format for easy sorting in spreadsheet Print
|
||||
# context and project names for easy viewing
|
||||
csv << [todo.id, todo.context.name,
|
||||
todo.project_id = todo.project_id.nil? ? "" : todo.project.name,
|
||||
todo.description,
|
||||
todo.notes, todo.tags.collect{|t| t.name}.join(', '),
|
||||
todo.created_at.to_formatted_s(:db),
|
||||
todo.due = todo.due? ? todo.due.to_formatted_s(:db) : "",
|
||||
todo.completed_at = todo.completed_at? ? todo.completed_at.to_formatted_s(:db) : "",
|
||||
todo.user_id,
|
||||
todo.show_from = todo.show_from? ? todo.show_from.to_formatted_s(:db) : "",
|
||||
todo.state]
|
||||
todo.project_id = todo.project_id.nil? ? "" : todo.project.name,
|
||||
todo.description,
|
||||
todo.notes, todo.tags.collect{|t| t.name}.join(', '),
|
||||
todo.created_at.to_formatted_s(:db),
|
||||
todo.due = todo.due? ? todo.due.to_formatted_s(:db) : "",
|
||||
todo.completed_at = todo.completed_at? ? todo.completed_at.to_formatted_s(:db) : "",
|
||||
todo.user_id,
|
||||
todo.show_from = todo.show_from? ? todo.show_from.to_formatted_s(:db) : "",
|
||||
todo.state]
|
||||
end
|
||||
end
|
||||
send_data(result, :filename => "todos.csv", :type => content_type)
|
||||
|
|
@ -58,16 +59,17 @@ class DataController < ApplicationController
|
|||
content_type = 'text/csv'
|
||||
CSV::Writer.generate(result = "") do |csv|
|
||||
csv << ["id", "User ID", "Project", "Note",
|
||||
"Created at", "Updated at"]
|
||||
# had to remove project include because it's association order is leaking through
|
||||
# and causing an ambiguous column ref even with_exclusive_scope didn't seem to help -JamesKebinger
|
||||
"Created at", "Updated at"]
|
||||
# had to remove project include because it's association order is leaking
|
||||
# through and causing an ambiguous column ref even with_exclusive_scope
|
||||
# didn't seem to help -JamesKebinger
|
||||
current_user.notes.find(:all,:order=>"notes.created_at").each do |note|
|
||||
# Format dates in ISO format for easy sorting in spreadsheet
|
||||
# Print context and project names for easy viewing
|
||||
# Format dates in ISO format for easy sorting in spreadsheet Print
|
||||
# context and project names for easy viewing
|
||||
csv << [note.id, note.user_id,
|
||||
note.project_id = note.project_id.nil? ? "" : note.project.name,
|
||||
note.body, note.created_at.to_formatted_s(:db),
|
||||
note.updated_at.to_formatted_s(:db)]
|
||||
note.project_id = note.project_id.nil? ? "" : note.project.name,
|
||||
note.body, note.created_at.to_formatted_s(:db),
|
||||
note.updated_at.to_formatted_s(:db)]
|
||||
end
|
||||
end
|
||||
send_data(result, :filename => "notes.csv", :type => content_type)
|
||||
|
|
@ -81,6 +83,7 @@ class DataController < ApplicationController
|
|||
result << current_user.tags.find(:all).to_xml(:skip_instruct => true)
|
||||
result << current_user.taggings.find(:all).to_xml(:skip_instruct => true)
|
||||
result << current_user.notes.find(:all).to_xml(:skip_instruct => true)
|
||||
result << current_user.recurring_todos.find(:all).to_xml(:skip_instruct => true)
|
||||
send_data(result, :filename => "tracks_backup.xml", :type => 'text/xml')
|
||||
end
|
||||
|
||||
|
|
@ -102,7 +105,6 @@ class DataController < ApplicationController
|
|||
@inarray = YAML::load(params['import']['yaml'])
|
||||
# arrays to handle id translations
|
||||
|
||||
|
||||
# contexts
|
||||
translate_context = Hash.new
|
||||
translate_context[nil] = nil
|
||||
|
|
@ -151,18 +153,18 @@ class DataController < ApplicationController
|
|||
|
||||
# state + dates
|
||||
case item.ivars['attributes']['state']
|
||||
when 'active' : newitem.activate!
|
||||
when 'project_hidden' : newitem.hide!
|
||||
when 'completed'
|
||||
newitem.complete!
|
||||
newitem.completed_at = adjust_time(item.ivars['attributes']['completed_at'])
|
||||
when 'deferred' : newitem.defer!
|
||||
when 'active' then newitem.activate!
|
||||
when 'project_hidden' then newitem.hide!
|
||||
when 'completed'
|
||||
newitem.complete!
|
||||
newitem.completed_at = adjust_time(item.ivars['attributes']['completed_at'])
|
||||
when 'deferred' then newitem.defer!
|
||||
end
|
||||
newitem.created_at = adjust_time(item.ivars['attributes']['created_at'])
|
||||
newitem.save(false)
|
||||
}
|
||||
|
||||
#tags
|
||||
# tags
|
||||
translate_tag = Hash.new
|
||||
translate_tag[nil] = nil
|
||||
current_user.tags.each { |item| item.destroy }
|
||||
|
|
@ -180,8 +182,8 @@ class DataController < ApplicationController
|
|||
newitem.user_id = current_user.id
|
||||
newitem.tag_id = translate_tag[newitem.tag_id]
|
||||
case newitem.taggable_type
|
||||
when 'Todo' : newitem.taggable_id = translate_todo[newitem.taggable_id]
|
||||
else newitem.taggable_id = 0
|
||||
when 'Todo' then newitem.taggable_id = translate_todo[newitem.taggable_id]
|
||||
else newitem.taggable_id = 0
|
||||
end
|
||||
newitem.save
|
||||
}
|
||||
|
|
@ -196,7 +198,6 @@ class DataController < ApplicationController
|
|||
newitem.created_at = adjust_time(item.ivars['attributes']['created_at'])
|
||||
newitem.save
|
||||
}
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -12,12 +12,12 @@ class FeedlistController < ApplicationController
|
|||
@contexts = current_user.contexts
|
||||
end
|
||||
|
||||
@active_projects = @projects.select{ |p| p.active? }
|
||||
@hidden_projects = @projects.select{ |p| p.hidden? }
|
||||
@completed_projects = @projects.select{ |p| p.completed? }
|
||||
@active_projects = current_user.projects.active
|
||||
@hidden_projects = current_user.projects.hidden
|
||||
@completed_projects = current_user.projects.completed
|
||||
|
||||
@active_contexts = @contexts.select{ |c| !c.hidden? }
|
||||
@hidden_contexts = @contexts.select{ |c| c.hidden? }
|
||||
@active_contexts = current_user.contexts.active
|
||||
@hidden_contexts = current_user.contexts.hidden
|
||||
|
||||
respond_to do |format|
|
||||
format.html { render :layout => 'standard' }
|
||||
|
|
|
|||
|
|
@ -20,10 +20,10 @@ class LoginController < ApplicationController
|
|||
session['noexpiry'] = params['user_noexpiry']
|
||||
msg = (should_expire_sessions?) ? "will expire after 1 hour of inactivity." : "will not expire."
|
||||
notify :notice, "Login successful: session #{msg}"
|
||||
cookies[:tracks_login] = { :value => @user.login, :expires => Time.now + 1.year }
|
||||
cookies[:tracks_login] = { :value => @user.login, :expires => Time.now + 1.year, :secure => TRACKS_COOKIES_SECURE }
|
||||
unless should_expire_sessions?
|
||||
@user.remember_me
|
||||
cookies[:auth_token] = { :value => @user.remember_token , :expires => @user.remember_token_expires_at }
|
||||
cookies[:auth_token] = { :value => @user.remember_token , :expires => @user.remember_token_expires_at, :secure => TRACKS_COOKIES_SECURE }
|
||||
end
|
||||
redirect_back_or_home
|
||||
return
|
||||
|
|
@ -94,12 +94,12 @@ class LoginController < ApplicationController
|
|||
session['noexpiry'] = session['user_noexpiry']
|
||||
msg = (should_expire_sessions?) ? "will expire after 1 hour of inactivity." : "will not expire."
|
||||
notify :notice, "You have successfully verified #{openid_url} as your identity. Login successful: session #{msg}"
|
||||
cookies[:tracks_login] = { :value => @user.login, :expires => Time.now + 1.year }
|
||||
cookies[:tracks_login] = { :value => @user.login, :expires => Time.now + 1.year, :secure => TRACKS_COOKIES_SECURE }
|
||||
unless should_expire_sessions?
|
||||
@user.remember_me
|
||||
cookies[:auth_token] = { :value => @user.remember_token , :expires => @user.remember_token_expires_at }
|
||||
cookies[:auth_token] = { :value => @user.remember_token , :expires => @user.remember_token_expires_at, :secure => TRACKS_COOKIES_SECURE }
|
||||
end
|
||||
cookies[:openid_url] = { :value => openid_url, :expires => Time.now + 1.year }
|
||||
cookies[:openid_url] = { :value => openid_url, :expires => Time.now + 1.year, :secure => TRACKS_COOKIES_SECURE }
|
||||
redirect_back_or_home
|
||||
else
|
||||
notify :warning, "You have successfully verified #{openid_url} as your identity, but you do not have a Tracks account. Please ask your administrator to sign you up."
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ class ProjectsController < ApplicationController
|
|||
end
|
||||
|
||||
def projects_and_actions
|
||||
@projects = @projects.select { |p| p.active? }
|
||||
@projects = @projects.active
|
||||
respond_to do |format|
|
||||
format.text {
|
||||
render :action => 'index_text_projects_and_actions', :layout => false, :content_type => Mime::TEXT
|
||||
|
|
@ -43,7 +43,7 @@ class ProjectsController < ApplicationController
|
|||
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
|
||||
@project.todos.send :with_scope, :find => { :include => [:context] } 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
|
||||
|
|
@ -83,7 +83,7 @@ class ProjectsController < ApplicationController
|
|||
@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'")
|
||||
@active_projects_count = current_user.projects.active.count
|
||||
@contexts = current_user.contexts
|
||||
respond_to do |format|
|
||||
format.js { @down_count = current_user.projects.size }
|
||||
|
|
@ -124,9 +124,9 @@ class ProjectsController < ApplicationController
|
|||
@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'")
|
||||
@active_projects_count = current_user.projects.active.count
|
||||
@hidden_projects_count = current_user.projects.hidden.count
|
||||
@completed_projects_count = current_user.projects.completed.count
|
||||
render :template => 'projects/update.js.rjs'
|
||||
return
|
||||
elsif boolean_param('update_status')
|
||||
|
|
@ -136,6 +136,10 @@ class ProjectsController < ApplicationController
|
|||
@initial_context_name = @project.default_context.name
|
||||
render :template => 'projects/update_default_context.js.rjs'
|
||||
return
|
||||
elsif boolean_param('update_project_name')
|
||||
@projects = current_user.projects
|
||||
render :template => 'projects/update_project_name.js.rjs'
|
||||
return
|
||||
else
|
||||
render :text => success_text || 'Success'
|
||||
return
|
||||
|
|
@ -157,9 +161,9 @@ class ProjectsController < ApplicationController
|
|||
|
||||
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'")
|
||||
@active_projects_count = current_user.projects.active.count
|
||||
@hidden_projects_count = current_user.projects.hidden.count
|
||||
@completed_projects_count = current_user.projects.completed.count
|
||||
respond_to do |format|
|
||||
format.js { @down_count = current_user.projects.size }
|
||||
format.xml { render :text => "Deleted project #{@project.name}" }
|
||||
|
|
@ -182,15 +186,22 @@ class ProjectsController < ApplicationController
|
|||
init_not_done_counts(['project'])
|
||||
end
|
||||
|
||||
def actionize
|
||||
@state = params['state']
|
||||
@projects = current_user.projects.actionize(current_user.id, :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? }
|
||||
@active_projects = @projects.active
|
||||
@hidden_projects = @projects.hidden
|
||||
@completed_projects = @projects.completed
|
||||
@no_projects = @projects.empty?
|
||||
@projects.cache_note_counts
|
||||
@new_project = current_user.projects.build
|
||||
|
|
@ -200,11 +211,11 @@ class ProjectsController < ApplicationController
|
|||
|
||||
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? }
|
||||
@active_projects = @projects.active
|
||||
@hidden_projects = @projects.hidden
|
||||
@completed_projects = @projects.completed
|
||||
@down_count = @active_projects.size + @hidden_projects.size + @completed_projects.size
|
||||
cookies[:mobile_url]=request.request_uri
|
||||
cookies[:mobile_url]= {:value => request.request_uri, :secure => TRACKS_COOKIES_SECURE}
|
||||
render :action => 'index_mobile'
|
||||
end
|
||||
end
|
||||
|
|
@ -217,7 +228,7 @@ class ProjectsController < ApplicationController
|
|||
@project_default_context = "The default context for this project is "+
|
||||
@project.default_context.name
|
||||
end
|
||||
cookies[:mobile_url]=request.request_uri
|
||||
cookies[:mobile_url]= {:value => request.request_uri, :secure => TRACKS_COOKIES_SECURE}
|
||||
@mobile_from_project = @project.id
|
||||
render :action => 'project_mobile'
|
||||
end
|
||||
|
|
|
|||
|
|
@ -6,8 +6,10 @@ class RecurringTodosController < ApplicationController
|
|||
append_before_filter :get_recurring_todo_from_param, :only => [:destroy, :toggle_check, :toggle_star, :edit, :update]
|
||||
|
||||
def index
|
||||
@recurring_todos = current_user.recurring_todos.find(:all, :conditions => ["state = ?", "active"])
|
||||
@completed_recurring_todos = current_user.recurring_todos.find(:all, :conditions => ["state = ?", "completed"])
|
||||
find_and_inactivate
|
||||
|
||||
@recurring_todos = current_user.recurring_todos.active
|
||||
@completed_recurring_todos = current_user.recurring_todos.completed
|
||||
@no_recurring_todos = @recurring_todos.size == 0
|
||||
@no_completed_recurring_todos = @completed_recurring_todos.size == 0
|
||||
@count = @recurring_todos.size
|
||||
|
|
@ -36,8 +38,8 @@ class RecurringTodosController < ApplicationController
|
|||
# the form for a new recurring todo and the edit form are on the same page.
|
||||
# Same goes for start_from and end_date
|
||||
params['recurring_todo']['recurring_period']=params['recurring_edit_todo']['recurring_period']
|
||||
params['recurring_todo']['end_date']=params['recurring_todo_edit_end_date']
|
||||
params['recurring_todo']['start_from']=params['recurring_todo_edit_start_from']
|
||||
params['recurring_todo']['end_date']=parse_date_per_user_prefs(params['recurring_todo_edit_end_date'])
|
||||
params['recurring_todo']['start_from']=parse_date_per_user_prefs(params['recurring_todo_edit_start_from'])
|
||||
|
||||
# update project
|
||||
if params['recurring_todo']['project_id'].blank? && !params['project_name'].nil?
|
||||
|
|
@ -84,6 +86,9 @@ class RecurringTodosController < ApplicationController
|
|||
|
||||
def create
|
||||
p = RecurringTodoCreateParamsHelper.new(params)
|
||||
p.attributes['end_date']=parse_date_per_user_prefs(p.attributes['end_date'])
|
||||
p.attributes['start_from']=parse_date_per_user_prefs(p.attributes['start_from'])
|
||||
|
||||
@recurring_todo = current_user.recurring_todos.build(p.selector_attributes)
|
||||
@recurring_todo.update_attributes(p.attributes)
|
||||
|
||||
|
|
@ -113,7 +118,7 @@ class RecurringTodosController < ApplicationController
|
|||
else
|
||||
@message += " / did not create todo"
|
||||
end
|
||||
@count = current_user.recurring_todos.count(:all, :conditions => ["state = ?", "active"])
|
||||
@count = current_user.recurring_todos.active.count
|
||||
else
|
||||
@message = "Error saving recurring todo"
|
||||
end
|
||||
|
|
@ -126,7 +131,7 @@ class RecurringTodosController < ApplicationController
|
|||
def destroy
|
||||
|
||||
# remove all references to this recurring todo
|
||||
@todos = current_user.todos.find(:all, {:conditions => ["recurring_todo_id = ?", params[:id]]})
|
||||
@todos = @recurring_todo.todos
|
||||
@number_of_todos = @todos.size
|
||||
@todos.each do |t|
|
||||
t.recurring_todo_id = nil
|
||||
|
|
@ -135,7 +140,7 @@ class RecurringTodosController < ApplicationController
|
|||
|
||||
# delete the recurring todo
|
||||
@saved = @recurring_todo.destroy
|
||||
@remaining = current_user.recurring_todos.count(:all)
|
||||
@remaining = current_user.recurring_todos.count
|
||||
|
||||
respond_to do |format|
|
||||
|
||||
|
|
@ -158,14 +163,15 @@ class RecurringTodosController < ApplicationController
|
|||
def toggle_check
|
||||
@saved = @recurring_todo.toggle_completion!
|
||||
|
||||
@count = current_user.recurring_todos.count(:all, :conditions => ["state = ?", "active"])
|
||||
@count = current_user.recurring_todos.active.count
|
||||
@remaining = @count
|
||||
|
||||
if @recurring_todo.active?
|
||||
@remaining = current_user.recurring_todos.count(:all, :conditions => ["state = ?", 'completed'])
|
||||
@remaining = current_user.recurring_todos.completed.count
|
||||
|
||||
# from completed back to active -> check if there is an active todo
|
||||
@active_todos = current_user.todos.count(:all, {:conditions => ["state = ? AND recurring_todo_id = ?", 'active',params[:id]]})
|
||||
# current_user.todos.count(:all, {:conditions => ["state = ? AND recurring_todo_id = ?", 'active',params[:id]]})
|
||||
@active_todos = @recurring_todo.todos.active.count
|
||||
# create todo if there is no active todo belonging to the activated
|
||||
# recurring_todo
|
||||
@new_recurring_todo = create_todo_from_recurring_todo(@recurring_todo) if @active_todos == 0
|
||||
|
|
@ -252,5 +258,11 @@ class RecurringTodosController < ApplicationController
|
|||
def get_recurring_todo_from_param
|
||||
@recurring_todo = current_user.recurring_todos.find(params[:id])
|
||||
end
|
||||
|
||||
def find_and_inactivate
|
||||
# find active recurring todos without active todos and inactivate them
|
||||
recurring_todos = current_user.recurring_todos.active
|
||||
recurring_todos.each { |rt| rt.toggle_completion! if rt.todos.not_completed.count == 0}
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ class StatsController < ApplicationController
|
|||
@page_title = 'TRACKS::Statistics'
|
||||
|
||||
@unique_tags = @tags.count(:all, {:group=>"tag_id"})
|
||||
@hidden_contexts = @contexts.find(:all, {:conditions => ["hide = ? ", true]})
|
||||
@hidden_contexts = @contexts.hidden
|
||||
@first_action = @actions.find(:first, :order => "created_at ASC")
|
||||
|
||||
get_stats_actions
|
||||
|
|
|
|||
|
|
@ -2,18 +2,19 @@ class TodosController < ApplicationController
|
|||
|
||||
helper :todos
|
||||
|
||||
skip_before_filter :login_required, :only => [:index]
|
||||
prepend_before_filter :login_or_feed_token_required, :only => [:index]
|
||||
append_before_filter :init, :except => [ :destroy, :completed, :completed_archive, :check_deferred, :toggle_check, :toggle_star, :edit, :update, :create ]
|
||||
skip_before_filter :login_required, :only => [:index, :calendar]
|
||||
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 ]
|
||||
append_before_filter :get_todo_from_params, :only => [ :edit, :toggle_check, :toggle_star, :show, :update, :destroy ]
|
||||
|
||||
session :off, :only => :index, :if => Proc.new { |req| is_feed_request(req) }
|
||||
|
||||
def index
|
||||
current_user.deferred_todos.find_and_activate_ready
|
||||
@projects = current_user.projects.find(:all, :include => [:default_context])
|
||||
@contexts = current_user.contexts.find(:all)
|
||||
|
||||
@contexts_to_show = @contexts.reject {|x| x.hide? }
|
||||
@contexts_to_show = current_user.contexts.active
|
||||
|
||||
respond_to do |format|
|
||||
format.html &render_todos_html
|
||||
|
|
@ -27,7 +28,7 @@ class TodosController < ApplicationController
|
|||
end
|
||||
|
||||
def new
|
||||
@projects = current_user.projects.select { |p| p.active? }
|
||||
@projects = current_user.projects.active
|
||||
@contexts = current_user.contexts.find(:all)
|
||||
respond_to do |format|
|
||||
format.m {
|
||||
|
|
@ -89,6 +90,7 @@ class TodosController < ApplicationController
|
|||
@contexts = current_user.contexts.find(:all) if @new_context_created
|
||||
@projects = current_user.projects.find(:all) if @new_project_created
|
||||
@initial_context_name = params['default_context_name']
|
||||
@initial_project_name = params['default_project_name']
|
||||
render :action => 'create'
|
||||
end
|
||||
format.xml do
|
||||
|
|
@ -105,6 +107,7 @@ class TodosController < ApplicationController
|
|||
@projects = current_user.projects.find(:all)
|
||||
@contexts = current_user.contexts.find(:all)
|
||||
@source_view = params['_source_view'] || 'todo'
|
||||
@tag_name = params['_tag_name']
|
||||
respond_to do |format|
|
||||
format.js
|
||||
end
|
||||
|
|
@ -113,7 +116,7 @@ class TodosController < ApplicationController
|
|||
def show
|
||||
respond_to do |format|
|
||||
format.m do
|
||||
@projects = current_user.projects.select { |p| p.active? }
|
||||
@projects = current_user.projects.active
|
||||
@contexts = current_user.contexts.find(:all)
|
||||
@edit_mobile = true
|
||||
@return_path=cookies[:mobile_url]
|
||||
|
|
@ -124,12 +127,14 @@ class TodosController < ApplicationController
|
|||
end
|
||||
|
||||
# Toggles the 'done' status of the action
|
||||
#
|
||||
#
|
||||
def toggle_check
|
||||
@source_view = params['_source_view'] || 'todo'
|
||||
@original_item_due = @todo.due
|
||||
@saved = @todo.toggle_completion!
|
||||
|
||||
|
||||
# check if this todo has a related recurring_todo. If so, create next todo
|
||||
check_for_next_todo if @saved
|
||||
@new_recurring_todo = check_for_next_todo(@todo) if @saved
|
||||
|
||||
respond_to do |format|
|
||||
format.js do
|
||||
|
|
@ -137,6 +142,10 @@ class TodosController < ApplicationController
|
|||
determine_remaining_in_context_count(@todo.context_id)
|
||||
determine_down_count
|
||||
determine_completed_count if @todo.completed?
|
||||
if source_view_is :calendar
|
||||
@original_item_due_id = get_due_id_for_calendar(@original_item_due)
|
||||
@old_due_empty = is_old_due_empty(@original_item_due_id)
|
||||
end
|
||||
end
|
||||
render
|
||||
end
|
||||
|
|
@ -170,6 +179,9 @@ class TodosController < ApplicationController
|
|||
@original_item_context_id = @todo.context_id
|
||||
@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)
|
||||
|
||||
if params['todo']['project_id'].blank? && !params['project_name'].nil?
|
||||
if params['project_name'] == 'None'
|
||||
project = Project.null_object
|
||||
|
|
@ -211,7 +223,7 @@ class TodosController < ApplicationController
|
|||
@todo.complete!
|
||||
end
|
||||
# strange. if checkbox is not checked, there is no 'done' in params.
|
||||
# Therfore I've used the negation
|
||||
# Therefore I've used the negation
|
||||
if !(params['done'] == '1') && @todo.completed?
|
||||
@todo.activate!
|
||||
end
|
||||
|
|
@ -219,7 +231,26 @@ class TodosController < ApplicationController
|
|||
@saved = @todo.update_attributes params["todo"]
|
||||
@context_changed = @original_item_context_id != @todo.context_id
|
||||
@todo_was_activated_from_deferred_state = @original_item_was_deferred && @todo.active?
|
||||
determine_remaining_in_context_count(@original_item_context_id) if @context_changed
|
||||
|
||||
if source_view_is :calendar
|
||||
@due_date_changed = @original_item_due != @todo.due
|
||||
if @due_date_changed
|
||||
@old_due_empty = is_old_due_empty(@original_item_due_id)
|
||||
if @todo.due.nil?
|
||||
# do not act further on date change when date is changed to nil
|
||||
@due_date_changed = false
|
||||
else
|
||||
@new_due_id = get_due_id_for_calendar(@todo.due)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if @context_changed
|
||||
determine_remaining_in_context_count(@original_item_context_id)
|
||||
else
|
||||
determine_remaining_in_context_count(@todo.context_id)
|
||||
end
|
||||
|
||||
@project_changed = @original_item_project_id != @todo.project_id
|
||||
if (@project_changed && !@original_item_project_id.nil?) then @remaining_undone_in_project = current_user.projects.find(@original_item_project_id).not_done_todo_count; end
|
||||
determine_down_count
|
||||
|
|
@ -229,7 +260,7 @@ class TodosController < ApplicationController
|
|||
format.m do
|
||||
if @saved
|
||||
if cookies[:mobile_url]
|
||||
cookies[:mobile_url] = nil
|
||||
cookies[:mobile_url] = {:value => nil, :secure => TRACKS_COOKIES_SECURE}
|
||||
redirect_to cookies[:mobile_url]
|
||||
else
|
||||
redirect_to formatted_todos_path(:m)
|
||||
|
|
@ -243,13 +274,14 @@ class TodosController < ApplicationController
|
|||
|
||||
def destroy
|
||||
@todo = get_todo_from_params
|
||||
@original_item_due = @todo.due
|
||||
@context_id = @todo.context_id
|
||||
@project_id = @todo.project_id
|
||||
|
||||
@saved = @todo.destroy
|
||||
|
||||
# check if this todo has a related recurring_todo. If so, create next todo
|
||||
check_for_next_todo
|
||||
|
||||
@saved = @todo.destroy
|
||||
@new_recurring_todo = check_for_next_todo(@todo) if @saved
|
||||
|
||||
respond_to do |format|
|
||||
|
||||
|
|
@ -268,6 +300,9 @@ class TodosController < ApplicationController
|
|||
determine_down_count
|
||||
if source_view_is_one_of(:todo, :deferred)
|
||||
determine_remaining_in_context_count(@context_id)
|
||||
elsif source_view_is :calendar
|
||||
@original_item_due_id = get_due_id_for_calendar(@original_item_due)
|
||||
@old_due_empty = is_old_due_empty(@original_item_due_id)
|
||||
end
|
||||
end
|
||||
render
|
||||
|
|
@ -337,46 +372,109 @@ class TodosController < ApplicationController
|
|||
def tag
|
||||
@source_view = params['_source_view'] || 'tag'
|
||||
@tag_name = params[:name]
|
||||
@page_title = "TRACKS::Tagged with \'#{@tag_name}\'"
|
||||
|
||||
# mobile tags are routed with :name ending on .m. So we need to chomp it
|
||||
@tag_name = @tag_name.chomp('.m') if mobile?
|
||||
|
||||
@tag = Tag.find_by_name(@tag_name)
|
||||
@tag = Tag.new(:name => @tag_name) if @tag.nil?
|
||||
|
||||
tag_collection = @tag.todos
|
||||
@not_done_todos = tag_collection.find(:all, :conditions => ['taggings.user_id = ? and state = ?', current_user.id, 'active'])
|
||||
|
||||
@not_done_todos = tag_collection.find(:all,
|
||||
:conditions => ['taggings.user_id = ? and state = ?', current_user.id, 'active'],
|
||||
:order => 'todos.due IS NULL, todos.due ASC, todos.created_at ASC')
|
||||
@hidden_todos = current_user.todos.find(:all,
|
||||
:include => [:taggings, :tags, :context],
|
||||
:conditions => ['tags.name = ? AND (todos.state = ? OR (contexts.hide = ? AND todos.state = ?))', @tag_name, 'project_hidden', true, 'active'])
|
||||
:conditions => ['tags.name = ? AND (todos.state = ? OR (contexts.hide = ? AND todos.state = ?))', @tag_name, 'project_hidden', true, 'active'],
|
||||
:order => 'todos.completed_at DESC, todos.created_at DESC')
|
||||
@deferred = tag_collection.find(:all,
|
||||
:conditions => ['taggings.user_id = ? and state = ?', current_user.id, 'deferred'],
|
||||
:order => 'show_from ASC, todos.created_at DESC')
|
||||
|
||||
# If you've set no_completed to zero, the completed items box isn't shown on
|
||||
# the tag page
|
||||
max_completed = current_user.prefs.show_number_completed
|
||||
@done = tag_collection.find(:all,
|
||||
:limit => max_completed,
|
||||
:conditions => ['taggings.user_id = ? and state = ?', current_user.id, 'completed'],
|
||||
:order => 'todos.completed_at DESC')
|
||||
|
||||
@contexts = current_user.contexts.find(:all)
|
||||
@contexts_to_show = @contexts.reject {|x| x.hide? }
|
||||
|
||||
@deferred = tag_collection.find(:all, :conditions => ['taggings.user_id = ? and state = ?', current_user.id, 'deferred'])
|
||||
|
||||
@page_title = "TRACKS::Tagged with \'#{@tag_name}\'"
|
||||
# If you've set no_completed to zero, the completed items box isn't shown on
|
||||
# the home page
|
||||
max_completed = current_user.prefs.show_number_completed
|
||||
@done = tag_collection.find(:all, :limit => max_completed, :conditions => ['taggings.user_id = ? and state = ?', current_user.id, 'completed'])
|
||||
# Set count badge to number of items with this tag
|
||||
@not_done_todos.empty? ? @count = 0 : @count = @not_done_todos.size
|
||||
@down_count = @count
|
||||
# @default_project_context_name_map =
|
||||
# build_default_project_context_name_map(@projects).to_json
|
||||
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
@default_project_context_name_map = build_default_project_context_name_map(@projects).to_json
|
||||
}
|
||||
format.m {
|
||||
cookies[:mobile_url]=request.request_uri
|
||||
cookies[:mobile_url]= {:value => request.request_uri, :secure => TRACKS_COOKIES_SECURE}
|
||||
render :action => "mobile_tag"
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def defer
|
||||
@source_view = params['_source_view'] || 'todo'
|
||||
numdays = params['days'].to_i
|
||||
@todo = Todo.find(params[:id])
|
||||
@todo.show_from = (@todo.show_from || @todo.user.date) + numdays.days
|
||||
@saved = @todo.save
|
||||
|
||||
determine_down_count
|
||||
determine_remaining_in_context_count(@todo.context_id)
|
||||
respond_to do |format|
|
||||
format.html { redirect_to :back }
|
||||
format.js {render :action => 'update'}
|
||||
end
|
||||
end
|
||||
|
||||
def calendar
|
||||
@source_view = params['_source_view'] || 'calendar'
|
||||
@page_title = "TRACKS::Calendar"
|
||||
|
||||
due_today_date = Time.zone.now
|
||||
due_this_week_date = Time.zone.now.end_of_week
|
||||
due_next_week_date = due_this_week_date + 7.days
|
||||
due_this_month_date = Time.zone.now.end_of_month
|
||||
|
||||
@due_today = current_user.todos.not_completed.find(:all,
|
||||
:include => [:taggings, :tags],
|
||||
:conditions => ['todos.due <= ?', due_today_date],
|
||||
:order => "due")
|
||||
@due_this_week = current_user.todos.not_completed.find(:all,
|
||||
:include => [:taggings, :tags],
|
||||
:conditions => ['todos.due > ? AND todos.due <= ?', due_today_date, due_this_week_date],
|
||||
:order => "due")
|
||||
@due_next_week = current_user.todos.not_completed.find(:all,
|
||||
:include => [:taggings, :tags],
|
||||
:conditions => ['todos.due > ? AND todos.due <= ?', due_this_week_date, due_next_week_date],
|
||||
:order => "due")
|
||||
@due_this_month = current_user.todos.not_completed.find(:all,
|
||||
:include => [:taggings, :tags],
|
||||
:conditions => ['todos.due > ? AND todos.due <= ?', due_next_week_date, due_this_month_date],
|
||||
:order => "due")
|
||||
@due_after_this_month = current_user.todos.not_completed.find(:all,
|
||||
:include => [:taggings, :tags],
|
||||
:conditions => ['todos.due > ?', due_this_month_date],
|
||||
:order => "due")
|
||||
|
||||
@count = current_user.todos.not_completed.are_due.count
|
||||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.ics {
|
||||
@due_all = current_user.todos.not_completed.are_due.find(:all, :order => "due")
|
||||
render :action => 'calendar', :layout => false, :content_type => Mime::ICS
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def get_todo_from_params
|
||||
@todo = current_user.todos.find(params['id'])
|
||||
|
|
@ -438,13 +536,17 @@ class TodosController < ApplicationController
|
|||
end
|
||||
|
||||
def with_parent_resource_scope(&block)
|
||||
@feed_title = "Actions "
|
||||
if (params[:context_id])
|
||||
@context = current_user.contexts.find_by_params(params)
|
||||
@feed_title = @feed_title + "in context '#{@context.name}'"
|
||||
Todo.send :with_scope, :find => {:conditions => ['todos.context_id = ?', @context.id]} do
|
||||
yield
|
||||
end
|
||||
elsif (params[:project_id])
|
||||
@project = current_user.projects.find_by_params(params)
|
||||
@feed_title = @feed_title + "in project '#{@project.name}'"
|
||||
@project_feed = true
|
||||
Todo.send :with_scope, :find => {:conditions => ['todos.project_id = ?', @project.id]} do
|
||||
yield
|
||||
end
|
||||
|
|
@ -608,7 +710,7 @@ class TodosController < ApplicationController
|
|||
lambda do
|
||||
@page_title = "All actions"
|
||||
@home = true
|
||||
cookies[:mobile_url]=request.request_uri
|
||||
cookies[:mobile_url]= { :value => request.request_uri, :secure => TRACKS_COOKIES_SECURE}
|
||||
determine_down_count
|
||||
|
||||
render :action => 'index'
|
||||
|
|
@ -620,7 +722,7 @@ class TodosController < ApplicationController
|
|||
render_rss_feed_for @todos, :feed => todo_feed_options,
|
||||
:item => {
|
||||
:title => :description,
|
||||
:link => lambda { |t| context_url(t.context) },
|
||||
:link => lambda { |t| @project_feed.nil? ? context_url(t.context) : project_url(t.project) },
|
||||
:guid => lambda { |t| todo_url(t) },
|
||||
:description => todo_feed_content
|
||||
}
|
||||
|
|
@ -628,7 +730,9 @@ class TodosController < ApplicationController
|
|||
end
|
||||
|
||||
def todo_feed_options
|
||||
Todo.feed_options(current_user)
|
||||
options = Todo.feed_options(current_user)
|
||||
options[:title] = @feed_title
|
||||
return options
|
||||
end
|
||||
|
||||
def todo_feed_content
|
||||
|
|
@ -674,18 +778,85 @@ class TodosController < ApplicationController
|
|||
['rss','atom','txt','ics'].include?(req.parameters[:format])
|
||||
end
|
||||
|
||||
def check_for_next_todo
|
||||
def check_for_next_todo(todo)
|
||||
# check if this todo has a related recurring_todo. If so, create next todo
|
||||
@new_recurring_todo = nil
|
||||
@recurring_todo = nil
|
||||
if @todo.from_recurring_todo?
|
||||
@recurring_todo = current_user.recurring_todos.find(@todo.recurring_todo_id)
|
||||
date_to_check = @todo.due.nil? ? @todo.show_from : @todo.due
|
||||
if @recurring_todo.active? && @recurring_todo.has_next_todo(date_to_check)
|
||||
date = date_to_check >= Date.today() ? date_to_check : Date.today()-1.day
|
||||
@new_recurring_todo = create_todo_from_recurring_todo(@recurring_todo, date)
|
||||
new_recurring_todo = nil
|
||||
recurring_todo = nil
|
||||
if todo.from_recurring_todo?
|
||||
recurring_todo = todo.recurring_todo
|
||||
|
||||
# check if there are active todos belonging to this recurring todo. only
|
||||
# add new one if all active todos are completed
|
||||
if recurring_todo.todos.active.count == 0
|
||||
|
||||
# check for next todo either from the due date or the show_from date
|
||||
date_to_check = todo.due.nil? ? todo.show_from : todo.due
|
||||
|
||||
# if both due and show_from are nil, check for a next todo from now
|
||||
date_to_check = Time.zone.now if date_to_check.nil?
|
||||
|
||||
if recurring_todo.active? && recurring_todo.has_next_todo(date_to_check)
|
||||
|
||||
# shift the reference date to yesterday if date_to_check is furher in
|
||||
# the past. This is to make sure we do not get older todos for overdue
|
||||
# todos. I.e. checking a daily todo that is overdue with 5 days will
|
||||
# create a new todo which is overdue by 4 days if we don't shift the
|
||||
# date. Discard the time part in the compare. We pick yesterday so
|
||||
# that new todos due for today will be created instead of new todos
|
||||
# for tomorrow.
|
||||
date = date_to_check.at_midnight >= Time.zone.now.at_midnight ? date_to_check : Time.zone.now-1.day
|
||||
|
||||
new_recurring_todo = create_todo_from_recurring_todo(recurring_todo, date)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return new_recurring_todo
|
||||
end
|
||||
|
||||
def get_due_id_for_calendar(due)
|
||||
return "" if due.nil?
|
||||
due_today_date = Time.zone.now
|
||||
due_this_week_date = Time.zone.now.end_of_week
|
||||
due_next_week_date = due_this_week_date + 7.days
|
||||
due_this_month_date = Time.zone.now.end_of_month
|
||||
if due <= due_today_date
|
||||
new_due_id = "due_today"
|
||||
elsif due <= due_this_week_date
|
||||
new_due_id = "due_this_week"
|
||||
elsif due <= due_next_week_date
|
||||
new_due_id = "due_next_week"
|
||||
elsif due <= due_this_month_date
|
||||
new_due_id = "due_this_month"
|
||||
else
|
||||
new_due_id = "due_after_this_month"
|
||||
end
|
||||
return new_due_id
|
||||
end
|
||||
|
||||
def is_old_due_empty(id)
|
||||
due_today_date = Time.zone.now
|
||||
due_this_week_date = Time.zone.now.end_of_week
|
||||
due_next_week_date = due_this_week_date + 7.days
|
||||
due_this_month_date = Time.zone.now.end_of_month
|
||||
case id
|
||||
when "due_today"
|
||||
return 0 == current_user.todos.not_completed.count(:all,
|
||||
:conditions => ['todos.due <= ?', due_today_date])
|
||||
when "due_this_week"
|
||||
return 0 == current_user.todos.not_completed.count(:all,
|
||||
:conditions => ['todos.due > ? AND todos.due <= ?', due_today_date, due_this_week_date])
|
||||
when "due_next_week"
|
||||
return 0 == current_user.todos.not_completed.count(:all,
|
||||
:conditions => ['todos.due > ? AND todos.due <= ?', due_this_week_date, due_next_week_date])
|
||||
when "due_this_month"
|
||||
return 0 == current_user.todos.not_completed.count(:all,
|
||||
:conditions => ['todos.due > ? AND todos.due <= ?', due_next_week_date, due_this_month_date])
|
||||
when "due_after_this_month"
|
||||
return 0 == current_user.todos.not_completed.count(:all,
|
||||
:conditions => ['todos.due > ?', due_this_month_date])
|
||||
else
|
||||
raise Exception.new, "unknown due id for calendar: '#{id}'"
|
||||
end
|
||||
end
|
||||
|
||||
class FindConditionBuilder
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ module RecurringTodosHelper
|
|||
def recurrence_time_span(rt)
|
||||
case rt.ends_on
|
||||
when "no_end_date"
|
||||
return ""
|
||||
return rt.start_from.nil? ? "" : "from " + format_date(rt.start_from)
|
||||
when "ends_on_number_of_times"
|
||||
return "for "+rt.number_of_occurences.to_s + " times"
|
||||
when "ends_on_end_date"
|
||||
|
|
@ -11,7 +11,7 @@ module RecurringTodosHelper
|
|||
ends = rt.end_date.nil? ? "" : " until " + format_date(rt.end_date)
|
||||
return starts+ends
|
||||
else
|
||||
raise Exception.new, "unknown recurrence time span selection (#{self.ends_on})"
|
||||
raise Exception.new, "unknown recurrence time span selection (#{self.ends_on})"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -28,10 +28,10 @@ module RecurringTodosHelper
|
|||
|
||||
def recurring_todo_tag_list
|
||||
tags_except_starred = @recurring_todo.tags.reject{|t| t.name == Todo::STARRED_TAG_NAME}
|
||||
tag_list = tags_except_starred.collect{|t| "<span class=\"tag #{t.name.gsub(' ','-')}\">" +
|
||||
tag_list = tags_except_starred.collect{|t| "<span class=\"tag #{t.name.gsub(' ','-')}\">" +
|
||||
# link_to(t.name, :controller => "todos", :action => "tag", :id =>
|
||||
# t.name) + TODO: tag view for recurring_todos (yet?)
|
||||
t.name +
|
||||
t.name +
|
||||
"</span>"}.join('')
|
||||
"<span class='tags'>#{tag_list}</span>"
|
||||
end
|
||||
|
|
@ -44,7 +44,7 @@ module RecurringTodosHelper
|
|||
str
|
||||
end
|
||||
|
||||
def recurring_todo_remote_star_icon
|
||||
def recurring_todo_remote_star_icon
|
||||
str = link_to( image_tag_for_star(@recurring_todo),
|
||||
toggle_star_recurring_todo_path(@recurring_todo),
|
||||
:class => "icon star_item", :title => "star the action '#{@recurring_todo.description}'")
|
||||
|
|
|
|||
|
|
@ -280,4 +280,8 @@ module TodosHelper
|
|||
image_tag("blank.png", :title =>"Star action", :class => class_str)
|
||||
end
|
||||
|
||||
def defer_link(days)
|
||||
link_to_remote image_tag("defer_#{days}.png", :alt => "Defer #{pluralize(days, 'day')}"), :url => {:controller => 'todos', :action => 'defer', :id => @todo.id, :days => days, :_source_view => (@source_view.underscore.gsub(/\s+/,'_') rescue "")}
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,7 +2,10 @@ class Context < ActiveRecord::Base
|
|||
|
||||
has_many :todos, :dependent => :delete_all, :include => :project, :order => "todos.completed_at DESC"
|
||||
belongs_to :user
|
||||
|
||||
|
||||
named_scope :active, :conditions => { :hide => false }
|
||||
named_scope :hidden, :conditions => { :hide => true }
|
||||
|
||||
acts_as_list :scope => :user
|
||||
extend NamePartFinder
|
||||
include Tracks::TodoList
|
||||
|
|
|
|||
34
app/models/message_gateway.rb
Normal file
34
app/models/message_gateway.rb
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
class MessageGateway < ActionMailer::Base
|
||||
include ActionView::Helpers::SanitizeHelper
|
||||
def receive(email)
|
||||
user = User.find(:first, :include => [:preference], :conditions => ["preferences.sms_email = ?", email.from[0].strip])
|
||||
if user.nil?
|
||||
user = User.find(:first, :include => [:preference], :conditions => ["preferences.sms_email = ?", email.from[0].strip[1,100]])
|
||||
end
|
||||
return if user.nil?
|
||||
context = user.prefs.sms_context
|
||||
|
||||
description = nil
|
||||
notes = nil
|
||||
|
||||
if email.content_type == "multipart/related"
|
||||
description = sanitize email.subject
|
||||
body_part = email.parts.find{|m| m.content_type == "text/plain"}
|
||||
notes = sanitize body_part.body.strip
|
||||
else
|
||||
if email.subject.empty?
|
||||
description = sanitize email.body.strip
|
||||
notes = nil
|
||||
else
|
||||
description = sanitize email.subject.strip
|
||||
notes = sanitize email.body.strip
|
||||
end
|
||||
end
|
||||
|
||||
# stupid T-Mobile often sends the same message multiple times
|
||||
return if user.todos.find(:first, :conditions => {:description => description})
|
||||
|
||||
todo = Todo.from_rich_message(user, context.id, description, notes)
|
||||
todo.save!
|
||||
end
|
||||
end
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
class Preference < ActiveRecord::Base
|
||||
belongs_to :user
|
||||
belongs_to :sms_context, :class_name => 'Context'
|
||||
|
||||
def self.due_styles
|
||||
{ :due_in_n_days => 0, :due_on => 1}
|
||||
|
|
@ -21,7 +22,7 @@ class Preference < ActiveRecord::Base
|
|||
|
||||
def parse_date(s)
|
||||
return nil if s.blank?
|
||||
Date.strptime(s, date_format)
|
||||
user.at_midnight(Date.strptime(s, date_format))
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -3,6 +3,10 @@ class Project < ActiveRecord::Base
|
|||
has_many :notes, :dependent => :delete_all, :order => "created_at DESC"
|
||||
belongs_to :default_context, :class_name => "Context", :foreign_key => "default_context_id"
|
||||
belongs_to :user
|
||||
|
||||
named_scope :active, :conditions => { :state => 'active' }
|
||||
named_scope :hidden, :conditions => { :state => 'hidden' }
|
||||
named_scope :completed, :conditions => { :state => 'completed'}
|
||||
|
||||
validates_presence_of :name, :message => "project must have a name"
|
||||
validates_length_of :name, :maximum => 255, :message => "project name must be less than 256 characters"
|
||||
|
|
@ -16,7 +20,7 @@ class Project < ActiveRecord::Base
|
|||
|
||||
state :active
|
||||
state :hidden, :enter => :hide_todos, :exit => :unhide_todos
|
||||
state :completed, :enter => Proc.new { |p| p.completed_at = Time.now.utc }, :exit => Proc.new { |p| p.completed_at = nil }
|
||||
state :completed, :enter => Proc.new { |p| p.completed_at = Time.zone.now }, :exit => Proc.new { |p| p.completed_at = nil }
|
||||
|
||||
event :activate do
|
||||
transitions :to => :active, :from => [:hidden, :completed]
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ class RecurringTodo < ActiveRecord::Base
|
|||
belongs_to :project
|
||||
belongs_to :user
|
||||
|
||||
has_many :todos
|
||||
|
||||
attr_protected :user
|
||||
|
||||
acts_as_state_machine :initial => :active, :column => 'state'
|
||||
|
|
@ -12,7 +14,7 @@ class RecurringTodo < ActiveRecord::Base
|
|||
t[:show_from], t.completed_at = nil, nil
|
||||
t.occurences_count = 0
|
||||
}
|
||||
state :completed, :enter => Proc.new { |t| t.completed_at = Time.now.utc }, :exit => Proc.new { |t| t.completed_at = nil }
|
||||
state :completed, :enter => Proc.new { |t| t.completed_at = Time.zone.now }, :exit => Proc.new { |t| t.completed_at = nil }
|
||||
|
||||
validates_presence_of :description
|
||||
validates_length_of :description, :maximum => 100
|
||||
|
|
@ -20,6 +22,9 @@ class RecurringTodo < ActiveRecord::Base
|
|||
|
||||
validates_presence_of :context
|
||||
|
||||
named_scope :active, :conditions => { :state => 'active'}
|
||||
named_scope :completed, :conditions => { :state => 'completed'}
|
||||
|
||||
event :complete do
|
||||
transitions :to => :completed, :from => [:active]
|
||||
end
|
||||
|
|
@ -29,7 +34,7 @@ class RecurringTodo < ActiveRecord::Base
|
|||
end
|
||||
|
||||
# the following recurrence patterns can be stored:
|
||||
#
|
||||
#
|
||||
# daily todos - recurrence_period = 'daily'
|
||||
# every nth day - nth stored in every_other1
|
||||
# every work day - only_work_days = true
|
||||
|
|
@ -243,7 +248,7 @@ class RecurringTodo < ActiveRecord::Base
|
|||
if self.recurrence_selector == 0
|
||||
return self.every_other2
|
||||
else
|
||||
return Time.now.month
|
||||
return Time.zone.now.month
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -257,7 +262,7 @@ class RecurringTodo < ActiveRecord::Base
|
|||
if self.recurrence_selector == 1
|
||||
return self.every_other2
|
||||
else
|
||||
return Time.now.month
|
||||
return Time.zone.now.month
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -392,13 +397,21 @@ class RecurringTodo < ActiveRecord::Base
|
|||
# previous is the due date of the previous todo or it is the completed_at
|
||||
# date when the completed_at date is after due_date (i.e. you did not make
|
||||
# the due date in time)
|
||||
#
|
||||
#
|
||||
# assumes self.recurring_period == 'daily'
|
||||
|
||||
# determine start
|
||||
if previous.nil?
|
||||
start = self.start_from.nil? ? Time.now.utc : self.start_from
|
||||
start = self.start_from.nil? ? Time.zone.now : self.start_from
|
||||
else
|
||||
# use the next day
|
||||
start = previous + 1.day
|
||||
|
||||
unless self.start_from.nil?
|
||||
# check if the start_from date is later than previous. If so, use
|
||||
# start_from as start to search for next date
|
||||
start = self.start_from if self.start_from > previous
|
||||
end
|
||||
end
|
||||
|
||||
if self.only_work_days
|
||||
|
|
@ -419,8 +432,9 @@ class RecurringTodo < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def get_weekly_date(previous)
|
||||
# determine start
|
||||
if previous == nil
|
||||
start = self.start_from.nil? ? Time.now.utc : self.start_from
|
||||
start = self.start_from.nil? ? Time.zone.now : self.start_from
|
||||
else
|
||||
start = previous + 1.day
|
||||
if start.wday() == 0
|
||||
|
|
@ -428,7 +442,13 @@ class RecurringTodo < ActiveRecord::Base
|
|||
# that week
|
||||
start += self.every_other1.week
|
||||
end
|
||||
unless self.start_from.nil?
|
||||
# check if the start_from date is later than previous. If so, use
|
||||
# start_from as start to search for next date
|
||||
start = self.start_from if self.start_from > previous
|
||||
end
|
||||
end
|
||||
|
||||
# check if there are any days left this week for the next todo
|
||||
start.wday().upto 6 do |i|
|
||||
return start + (i-start.wday()).days unless self.every_day[i,1] == ' '
|
||||
|
|
@ -447,29 +467,44 @@ class RecurringTodo < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def get_monthly_date(previous)
|
||||
if previous.nil?
|
||||
start = self.start_from.nil? ? Time.now.utc : self.start_from
|
||||
else
|
||||
start = previous
|
||||
end
|
||||
|
||||
start = determine_start(previous)
|
||||
day = self.every_other1
|
||||
n = self.every_other2
|
||||
|
||||
|
||||
case self.recurrence_selector
|
||||
when 0 # specific day of the month
|
||||
if start.mday >= day
|
||||
if start.mday >= day
|
||||
# there is no next day n in this month, search in next month
|
||||
start += n.months
|
||||
#
|
||||
# start += n.months
|
||||
#
|
||||
# The above seems to not work. Fiddle with timezone. Looks like we hit a
|
||||
# bug in rails here where 2008-12-01 +0100 plus 1.month becomes
|
||||
# 2008-12-31 +0100. For now, just calculate in UTC and convert back to
|
||||
# local timezone.
|
||||
#
|
||||
# TODO: recheck if future rails versions have this problem too
|
||||
start = Time.utc(start.year, start.month, start.day)+n.months
|
||||
start = Time.zone.local(start.year, start.month, start.day)
|
||||
|
||||
# go back to day
|
||||
end
|
||||
return Time.utc(start.year, start.month, day)
|
||||
return Time.zone.local(start.year, start.month, day)
|
||||
|
||||
when 1 # relative weekday of a month
|
||||
the_next = get_xth_day_of_month(self.every_other3, self.every_count, start.month, start.year)
|
||||
if the_next.nil? || the_next <= start
|
||||
# the nth day is already passed in this month, go to next month and try
|
||||
# again
|
||||
the_next = the_next+n.months
|
||||
|
||||
# fiddle with timezone. Looks like we hit a bug in rails here where
|
||||
# 2008-12-01 +0100 plus 1.month becomes 2008-12-31 +0100. For now, just
|
||||
# calculate in UTC and convert back to local timezone.
|
||||
# TODO: recheck if future rails versions have this problem too
|
||||
the_next = Time.utc(the_next.year, the_next.month, the_next.day)+n.months
|
||||
the_next = Time.zone.local(the_next.year, the_next.month, the_next.day)
|
||||
|
||||
# TODO: if there is still no match, start will be set to nil. if we ever
|
||||
# support 5th day of the month, we need to handle this case
|
||||
the_next = get_xth_day_of_month(self.every_other3, self.every_count, the_next.month, the_next.year)
|
||||
|
|
@ -483,14 +518,18 @@ class RecurringTodo < ActiveRecord::Base
|
|||
|
||||
def get_xth_day_of_month(x, weekday, month, year)
|
||||
if x == 5
|
||||
# last -> count backwards
|
||||
# last -> count backwards. use UTC to avoid strange timezone oddities
|
||||
# where last_day -= 1.day seems to shift tz+0100 to tz+0000
|
||||
last_day = Time.utc(year, month, Time.days_in_month(month))
|
||||
while last_day.wday != weekday
|
||||
last_day -= 1.day
|
||||
end
|
||||
return last_day
|
||||
# convert back to local timezone
|
||||
return Time.zone.local(last_day.year, last_day.month, last_day.day)
|
||||
else
|
||||
# 1-4th -> count upwards
|
||||
# 1-4th -> count upwards last -> count backwards. use UTC to avoid strange
|
||||
# timezone oddities where last_day -= 1.day seems to shift tz+0100 to
|
||||
# tz+0000
|
||||
start = Time.utc(year,month,1)
|
||||
n = x
|
||||
while n > 0
|
||||
|
|
@ -500,32 +539,31 @@ class RecurringTodo < ActiveRecord::Base
|
|||
n -= 1
|
||||
start += 1.day unless n==0
|
||||
end
|
||||
return start
|
||||
# convert back to local timezone
|
||||
return Time.zone.local(start.year, start.month, start.day)
|
||||
end
|
||||
end
|
||||
|
||||
def get_yearly_date(previous)
|
||||
if previous.nil?
|
||||
start = self.start_from.nil? ? Time.now.utc : self.start_from
|
||||
else
|
||||
start = previous
|
||||
end
|
||||
|
||||
start = determine_start(previous)
|
||||
day = self.every_other1
|
||||
month = self.every_other2
|
||||
|
||||
case self.recurrence_selector
|
||||
when 0 # specific day of a specific month
|
||||
# if there is no next month n in this year, search in next year
|
||||
if start.month >= month
|
||||
start = Time.utc(start.year+1, month, 1) if start.day >= day
|
||||
start = Time.utc(start.year, month, 1) if start.day <= day
|
||||
if start.month > month || (start.month == month && start.day >= day)
|
||||
# if there is no next month n and day m in this year, search in next
|
||||
# year
|
||||
start = Time.zone.local(start.year+1, month, 1)
|
||||
else
|
||||
# if there is a next month n, stay in this year
|
||||
start = Time.zone.local(start.year, month, 1)
|
||||
end
|
||||
return Time.utc(start.year, month, day)
|
||||
return Time.zone.local(start.year, month, day)
|
||||
|
||||
when 1 # relative weekday of a specific month
|
||||
# if there is no next month n in this year, search in next year
|
||||
the_next = start.month > month ? Time.utc(start.year+1, month, 1) : start
|
||||
the_next = start.month > month ? Time.zone.local(start.year+1, month, 1) : start
|
||||
|
||||
# get the xth day of the month
|
||||
the_next = get_xth_day_of_month(self.every_other3, self.every_count, month, the_next.year)
|
||||
|
|
@ -592,4 +630,19 @@ class RecurringTodo < ActiveRecord::Base
|
|||
errors.add("", "At least one day must be selected in the weekly pattern") if self.every_day == ' '
|
||||
end
|
||||
|
||||
def determine_start(previous)
|
||||
if previous.nil?
|
||||
start = self.start_from.nil? ? Time.zone.now : self.start_from
|
||||
else
|
||||
start = previous
|
||||
|
||||
unless self.start_from.nil?
|
||||
# check if the start_from date is later than previous. If so, use
|
||||
# start_from as start to search for next date
|
||||
start = self.start_from if self.start_from > previous
|
||||
end
|
||||
end
|
||||
return start
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,7 +4,11 @@ class Todo < ActiveRecord::Base
|
|||
belongs_to :project
|
||||
belongs_to :user
|
||||
belongs_to :recurring_todo
|
||||
|
||||
|
||||
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"
|
||||
|
||||
acts_as_state_machine :initial => :active, :column => 'state'
|
||||
|
|
@ -13,7 +17,7 @@ class Todo < ActiveRecord::Base
|
|||
# of state completed is not run, see #679
|
||||
state :active, :enter => Proc.new { |t| t[:show_from], t.completed_at = nil, nil }
|
||||
state :project_hidden
|
||||
state :completed, :enter => Proc.new { |t| t.completed_at = Time.now.utc }, :exit => Proc.new { |t| t.completed_at = nil }
|
||||
state :completed, :enter => Proc.new { |t| t.completed_at = Time.zone.now }, :exit => Proc.new { |t| t.completed_at = nil }
|
||||
state :deferred
|
||||
|
||||
event :defer do
|
||||
|
|
@ -68,6 +72,8 @@ class Todo < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def show_from=(date)
|
||||
# parse Date objects into the proper timezone
|
||||
date = user.at_midnight(date) if (date.is_a? Date)
|
||||
activate! if deferred? && date.blank?
|
||||
defer! if active? && !date.blank? && date > user.date
|
||||
self[:show_from] = date
|
||||
|
|
@ -125,4 +131,46 @@ class Todo < ActiveRecord::Base
|
|||
return self.recurring_todo_id != nil
|
||||
end
|
||||
|
||||
end
|
||||
# Rich Todo API
|
||||
|
||||
def self.from_rich_message(user, default_context_id, description, notes)
|
||||
fields = description.match /([^>@]*)@?([^>]*)>?(.*)/
|
||||
description = fields[1].strip
|
||||
context = fields[2].strip
|
||||
project = fields[3].strip
|
||||
|
||||
context = nil if context == ""
|
||||
project = nil if project == ""
|
||||
|
||||
context_id = default_context_id
|
||||
unless(context.nil?)
|
||||
found_context = user.active_contexts.find_by_namepart(context)
|
||||
found_context = user.contexts.find_by_namepart(context) if found_context.nil?
|
||||
context_id = found_context.id unless found_context.nil?
|
||||
end
|
||||
|
||||
unless user.contexts.exists? context_id
|
||||
raise(CannotAccessContext, "Cannot access a context that does not belong to this user.")
|
||||
end
|
||||
|
||||
project_id = nil
|
||||
unless(project.blank?)
|
||||
if(project[0..3].downcase == "new:")
|
||||
found_project = user.projects.build
|
||||
found_project.name = project[4..255+4].strip
|
||||
found_project.save!
|
||||
else
|
||||
found_project = user.active_projects.find_by_namepart(project)
|
||||
found_project = user.projects.find_by_namepart(project) if found_project.nil?
|
||||
end
|
||||
project_id = found_project.id unless found_project.nil?
|
||||
end
|
||||
|
||||
todo = user.todos.build
|
||||
todo.description = description
|
||||
todo.notes = notes
|
||||
todo.context_id = context_id
|
||||
todo.project_id = project_id unless project_id.nil?
|
||||
return todo
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -51,6 +51,21 @@ class User < ActiveRecord::Base
|
|||
self.update_positions(projects.map{ |p| p.id })
|
||||
return projects
|
||||
end
|
||||
def actionize(user_id, scope_conditions = {})
|
||||
@state = scope_conditions[:state]
|
||||
query_state = ""
|
||||
query_state = "AND project.state = '" + @state +"' "if @state
|
||||
projects = Project.find_by_sql([
|
||||
"SELECT project.id, count(todo.id) as p_count " +
|
||||
"FROM projects as project " +
|
||||
"LEFT OUTER JOIN todos as todo ON todo.project_id = project.id "+
|
||||
"WHERE project.user_id = ? AND NOT todo.state='completed' " +
|
||||
query_state +
|
||||
" GROUP BY project.id ORDER by p_count DESC",user_id])
|
||||
self.update_positions(projects.map{ |p| p.id })
|
||||
projects = find(:all, :conditions => scope_conditions)
|
||||
return projects
|
||||
end
|
||||
end
|
||||
has_many :active_projects,
|
||||
:class_name => 'Project',
|
||||
|
|
@ -59,7 +74,7 @@ class User < ActiveRecord::Base
|
|||
has_many :active_contexts,
|
||||
:class_name => 'Context',
|
||||
:order => 'position ASC',
|
||||
:conditions => [ 'hide = ?', 'true' ]
|
||||
:conditions => [ 'hide = ?', false ]
|
||||
has_many :todos,
|
||||
:order => 'todos.completed_at DESC, todos.created_at DESC',
|
||||
:dependent => :delete_all
|
||||
|
|
@ -71,7 +86,7 @@ class User < ActiveRecord::Base
|
|||
:conditions => [ 'state = ?', 'deferred' ],
|
||||
:order => 'show_from ASC, todos.created_at DESC' do
|
||||
def find_and_activate_ready
|
||||
find(:all, :conditions => ['show_from <= ?', proxy_owner.time ]).collect { |t| t.activate! }
|
||||
find(:all, :conditions => ['show_from <= ?', Time.zone.now ]).collect { |t| t.activate! }
|
||||
end
|
||||
end
|
||||
has_many :completed_todos,
|
||||
|
|
@ -169,7 +184,11 @@ class User < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def date
|
||||
time.to_date
|
||||
time.midnight
|
||||
end
|
||||
|
||||
def at_midnight(date)
|
||||
return TimeZone[prefs.time_zone].local(date.year, date.month, date.day, 0, 0, 0)
|
||||
end
|
||||
|
||||
def generate_token
|
||||
|
|
|
|||
|
|
@ -25,7 +25,9 @@
|
|||
<% end -%>
|
||||
<% if source_view_is :context %>
|
||||
<span class="in_place_editor_field" id="context_name_in_place_editor"><%= context.name %></span>
|
||||
<%= in_place_editor 'context_name_in_place_editor', { :url => { :controller => 'contexts', :action => 'update', :id => context.id, :field => 'name', :wants_render => false, :escape => false} , :options=>"{method:'put'}" } %>
|
||||
<%= in_place_editor 'context_name_in_place_editor', {
|
||||
:url => { :controller => 'contexts', :action => 'update', :id => context.id, :field => 'name', :update_context_name => true, :escape => false},
|
||||
:options=>"{method:'put'}", :script => true } %>
|
||||
<% else %>
|
||||
<%= link_to_context( context ) %>
|
||||
<% end %>
|
||||
|
|
|
|||
|
|
@ -53,4 +53,9 @@
|
|||
</div>
|
||||
<%
|
||||
sortable_element 'list-contexts', get_listing_sortable_options
|
||||
-%>
|
||||
-%>
|
||||
<script type="text/javascript">
|
||||
window.onload=function(){
|
||||
Nifty("div#context_new_container","normal");
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
10
app/views/contexts/update_context_name.js.rjs
Normal file
10
app/views/contexts/update_context_name.js.rjs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
page['context_name_in_place_editor'].replace_html @context.name
|
||||
|
||||
page['default_context_name_id'].value = @context.name
|
||||
page['todo_context_name'].value = @context.name
|
||||
|
||||
# renew context auto complete array
|
||||
page << "contextAutoCompleter.options.array = #{context_names_for_autocomplete}; contextAutoCompleter.changed = true"
|
||||
|
||||
status_message = "Name of context was changed"
|
||||
page.notify :notice, status_message, 5.0
|
||||
|
|
@ -1,44 +1,49 @@
|
|||
<div id="feeds">
|
||||
<div id="feedlegend">
|
||||
<h3>Exporting data</h3>
|
||||
<p>You can choose between the following formats:</p>
|
||||
<ul>
|
||||
<li><strong>YAML: </strong>Best for exporting data. <br/><i>Please note that importing YAML files is currently supported only in experimentally. Do not rely on it for backing up critical data.</i></li>
|
||||
<li><strong>CSV: </strong>Best for importing into spreadsheet or data analysis software</li>
|
||||
<li><strong>XML: </strong>Best for importing or repurposing the data</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
<table class="export_table">
|
||||
<tr>
|
||||
<th>Description</th>
|
||||
<th>Download link</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>YAML file containing all your actions, contexts, projects, tags and notes</td>
|
||||
<td><%= link_to "YAML file", :controller => 'data', :action => 'yaml_export' %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>CSV file containing all of your actions, with named contexts and projects</td>
|
||||
<td><%= link_to "CSV file (actions, contexts and projects)", :controller => 'data', :action => 'csv_actions' %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>CSV file containing all your notes</td>
|
||||
<td><%= link_to "CSV file (notes only)", :controller => 'data', :action => 'csv_notes' %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>XML file containing all your actions, contexts, projects, tags and notes</td>
|
||||
<td><%= link_to "XML file (actions only)", :controller => 'data', :action => 'xml_export' %></td>
|
||||
</tr>
|
||||
</table>
|
||||
</p>
|
||||
|
||||
<div id="feeds">
|
||||
<div id="feedlegend">
|
||||
<h3>Importing data</h3>
|
||||
<p>Curently there is a experimental support for importing YAML files. Beware: all your current data will be destroyed before importing the YAML file, so if you have access to the database, we strongly reccoment backing up the database right now in case that anything goes wrong.</p>
|
||||
<p><%= link_to "Start import", :controller => 'data', :action => 'yaml_form' %>.</p>
|
||||
<div id="display_box">
|
||||
<div id="feeds">
|
||||
<div id="feedlegend">
|
||||
<h3>Importing data</h3>
|
||||
<p>Curently there is a experimental support for importing YAML files.
|
||||
Beware: all your current data will be destroyed before importing the YAML
|
||||
file, so if you have access to the database, we strongly recomment backing up
|
||||
the database right now in case that anything goes wrong.
|
||||
</p>
|
||||
<p><%= link_to "Start import", :controller => 'data', :action => 'yaml_form' %>.</p>
|
||||
<br/><br/><h3>Exporting data</h3>
|
||||
<p>You can choose between the following formats:</p>
|
||||
<ul>
|
||||
<li><strong>YAML: </strong>Best for exporting data. <br/><i>Please note that importing YAML files is currently supported only in experimentally. Do not rely on it for backing up critical data.</i></li>
|
||||
<li><strong>CSV: </strong>Best for importing into spreadsheet or data analysis software</li>
|
||||
<li><strong>XML: </strong>Best for importing or repurposing the data</li>
|
||||
</ul>
|
||||
</div>
|
||||
<br/><br/>
|
||||
<table class="export_table">
|
||||
<tr>
|
||||
<th>Description</th>
|
||||
<th>Download link</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>YAML file containing all your actions, contexts, projects, tags and notes</td>
|
||||
<td><%= link_to "YAML file", :controller => 'data', :action => 'yaml_export' %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>CSV file containing all of your actions, with named contexts and projects</td>
|
||||
<td><%= link_to "CSV file (actions, contexts and projects)", :controller => 'data', :action => 'csv_actions' %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>CSV file containing all your notes</td>
|
||||
<td><%= link_to "CSV file (notes only)", :controller => 'data', :action => 'csv_notes' %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>XML file containing all your actions, contexts, projects, tags and notes</td>
|
||||
<td><%= link_to "XML file (actions only)", :controller => 'data', :action => 'xml_export' %></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div><!-- End of feeds -->
|
||||
</div>
|
||||
|
||||
</div><!-- End of feeds -->
|
||||
<script type="text/javascript">
|
||||
window.onload=function(){
|
||||
Nifty("div#feedlegend","normal");
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,17 +1,23 @@
|
|||
<div id="display_box">
|
||||
<div id="feeds">
|
||||
<div id="feedlegend">
|
||||
<p><b>Beware</b>: all your current data will be destroyed before importing the YAML file, so if you have access to the database, we strongly reccoment backing up the database right now in case that anything goes wrong.</p>
|
||||
<p>Paste the contents of the YAML file you exported into the text box below:</p>
|
||||
</div>
|
||||
<p>
|
||||
<% form_for :import, @import, :url => {:controller => 'data', :action => 'yaml_import'} do |f| %>
|
||||
<%= f.text_area :yaml %><br />
|
||||
<input type="submit" value="Import data">
|
||||
<% end %>
|
||||
</p>
|
||||
</div><!-- End of feeds -->
|
||||
<div id="feeds">
|
||||
<div id="feedlegend">
|
||||
<p><b>Beware</b>: all your current data will be destroyed before importing
|
||||
the YAML file, so if you have access to the database, we strongly recommend
|
||||
backing up the database right now in case that anything goes wrong.
|
||||
</p>
|
||||
<p>Paste the contents of the YAML file you exported into the text box below:</p>
|
||||
</div>
|
||||
<p>
|
||||
<% form_for :import, @import, :url => {:controller => 'data', :action => 'yaml_import'} do |f| %>
|
||||
<%= f.text_area :yaml %><br />
|
||||
<input type="submit" value="Import data">
|
||||
<% end %>
|
||||
</p>
|
||||
</div><!-- End of feeds -->
|
||||
</div><!-- End of display_box -->
|
||||
|
||||
<div id="input_box">
|
||||
</div><!-- End of input box -->
|
||||
<script type="text/javascript">
|
||||
window.onload=function(){
|
||||
Nifty("div#feedlegend","normal");
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -64,10 +64,13 @@
|
|||
Active projects with their actions
|
||||
</li>
|
||||
<li><h4>Feeds for incomplete actions in a specific context:</h4>
|
||||
<% if @active_contexts.empty? && @hidden_contexts.empty? -%>
|
||||
<ul><li>There need to be at least one context before you can request a feed</li></ul>
|
||||
<% else -%>
|
||||
<ul>
|
||||
<li>Step 1 - Choose the context you want a feed of:
|
||||
<select name="feed-contexts" id="feed-contexts">
|
||||
<%= options_from_collection_for_select(@active_contexts, "id", "name", @active_contexts.first.id) -%>
|
||||
<%= options_from_collection_for_select(@active_contexts, "id", "name", @active_contexts.first.id) unless @active_projects.empty?-%>
|
||||
<%= options_from_collection_for_select(@hidden_contexts, "id", "name") -%>
|
||||
</select>
|
||||
<%= observe_field "feed-contexts", :update => "feeds-for-context",
|
||||
|
|
@ -80,17 +83,21 @@
|
|||
<li>Step 2 - Select the feed for this context
|
||||
<div id="feedicons-context">
|
||||
<div id="feeds-for-context">
|
||||
<%= render :partial => 'feed_for_context', :locals => { :context => @active_contexts.first } %>
|
||||
<%= render :partial => 'feed_for_context', :locals => { :context => @active_contexts.empty? ? @hidden_contexts.first : @active_contexts.first } %>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<% end -%>
|
||||
</li>
|
||||
<li><h4>Feeds for incomplete actions in a specific project:</h4>
|
||||
<% if @active_projects.empty? && @hidden_projects.empty? -%>
|
||||
<ul><li>There need to be at least one project before you can request a feed</li></ul>
|
||||
<% else -%>
|
||||
<ul>
|
||||
<li>Step 1 - Choose the project you want a feed of:
|
||||
<select name="feed-projects" id="feed-projects">
|
||||
<%= options_from_collection_for_select(@active_projects, "id", "name", @active_projects.first.id) -%>
|
||||
<%= options_from_collection_for_select(@active_projects, "id", "name", @active_projects.first.id) unless @active_projects.empty?-%>
|
||||
<%= options_from_collection_for_select(@hidden_projects, "id", "name") -%>
|
||||
<%= options_from_collection_for_select(@completed_projects, "id", "name") -%>
|
||||
</select>
|
||||
|
|
@ -104,11 +111,12 @@
|
|||
<li>Step 2 - Select the feed for this project
|
||||
<div id="feedicons-project">
|
||||
<div id="feeds-for-project">
|
||||
<%= render :partial => 'feed_for_project', :locals => { :project => @active_projects.first } %>
|
||||
<%= render :partial => 'feed_for_project', :locals => { :project => @active_projects.empty? ? @hidden_projects.first : @active_projects.first } %>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<% end -%>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@ the newly created action.
|
|||
*)
|
||||
|
||||
(* Edit appropriately for your setup *)
|
||||
property myUsername to "<%= current_user.login %>"
|
||||
property myToken to "<%= current_user.token %>"
|
||||
property myContextID to <%= context.id %> (* <%= context.name %> *)
|
||||
property myUsername : "<%= current_user.login %>"
|
||||
property myToken : "<%= current_user.token %>"
|
||||
property myContextID : <%= context.id %> (* <%= context.name %> *)
|
||||
|
||||
-- this string is used when the message subject is empty
|
||||
property emptySubject : "No Subject Specified"
|
||||
|
|
|
|||
|
|
@ -12,6 +12,18 @@
|
|||
<p>Do you have one of your own to add? <a href="http://www.rousette.org.uk/projects/forums/viewforum/10/" title="Tracks | Tips and Tricks">Tell us about it in our Tips and Tricks forum
|
||||
</a> and we may include it on this page in a future versions of Tracks.</p>
|
||||
|
||||
<a name="message_gateway"> </a>
|
||||
<h2>Integrated email/SMS receiver</h2>
|
||||
<p>
|
||||
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:
|
||||
<ul>
|
||||
<li>Go to <%= link_to "Preferences", preferences_url %> and set your "From email" and "default email context" for todos sent in via email (which could come from an SMS message)</li>
|
||||
<li>In sendmail/qmail/postfix/whatever, set up an email address alias to pipe messages to <pre style="font-size:1.5em">/PATH/TO/RUBY/ruby /PATH/TO/TRACKS/script/runner -e production 'MessageGateway.receive(STDIN.read)'</pre></li>
|
||||
<li>Send an email to your newly configured address!</li>
|
||||
</ul>
|
||||
You can also use the Rich Todo API to send in tasks like "do laundry @ Home" or "Call Bill > project X". The subject of the message will fill description, context, and project, while the body will populate the tasks's note.
|
||||
</p>
|
||||
|
||||
<a name="applescript1-section"> </a>
|
||||
<h2>Add an Action with Applescript</h2>
|
||||
<p>This is a simple script that pops up a dialog box asking for a description, and then sends that to Tracks with a hard-coded context.</p>
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@
|
|||
<pre>
|
||||
<code>
|
||||
$ curl -u username:p4ssw0rd -H "Content-Type: text/xml" \
|
||||
-d "project[name]=Build a treehouse for the kids" \
|
||||
-d "<project><name>Build a treehouse for the kids</name></project>" \
|
||||
<%= home_url %>projects.xml -i
|
||||
>> HTTP/1.1 201 Created
|
||||
Location: <%= home_url %>projects/65.xml
|
||||
|
|
@ -86,7 +86,7 @@ Location: <%= home_url %>projects/65.xml
|
|||
<pre>
|
||||
<code>
|
||||
$ curl -u username:p4ssw0rd -H "Content-Type: text/xml" \
|
||||
-d "todo[description]=Model treehouse in SketchUp&todo[context_id]=2&todo[project_id]=65" \
|
||||
-d "<todo><description>Model treehouse in SketchUp</description><context_id>2</context_id><project_id>65<project_id>" \
|
||||
<%= home_url %>todos.xml -i
|
||||
>> HTTP/1.1 201 Created
|
||||
Location: <%= home_url %>todos/452.xml
|
||||
|
|
@ -99,7 +99,7 @@ Location: <%= home_url %>todos/452.xml
|
|||
<pre>
|
||||
<code>
|
||||
$ curl -u username:p4ssw0rd -H "Content-Type: text/xml" -X PUT \
|
||||
-d "todo[notes]=use maple texture" \
|
||||
-d "<todo><notes>use maple texture</notes></todos>" \
|
||||
<%= home_url %>todos/452.xml -i
|
||||
>> HTTP/1.1 200 OK
|
||||
...
|
||||
|
|
|
|||
|
|
@ -16,9 +16,6 @@
|
|||
<script type="text/javascript">
|
||||
window.onload=function(){
|
||||
Nifty("div#todo_new_action_container","normal");
|
||||
Nifty("div#project_new_project_container","normal");
|
||||
Nifty("div#context_new_container","normal");
|
||||
Nifty("div#recurring_new_container","normal");
|
||||
if ($('flash').visible()) { new Effect.Fade("flash",{duration:5.0}); }
|
||||
}
|
||||
</script>
|
||||
|
|
@ -49,7 +46,7 @@ window.onload=function(){
|
|||
<li><%= navigation_link("Home", home_path, {:accesskey => "t", :title => "Home"} ) %></li>
|
||||
<li><%= navigation_link( "Contexts", contexts_path, {:accesskey=>"c", :title=>"Contexts"} ) %></li>
|
||||
<li><%= navigation_link( "Projects", projects_path, {:accesskey=>"p", :title=>"Projects"} ) %></li>
|
||||
<li><%= navigation_link( "Tickler", tickler_path, :title => "Tickler" ) %></li>
|
||||
<li><%= navigation_link( "Tickler", tickler_path, {:accesskey =>"k", :title => "Tickler"} ) %></li>
|
||||
<li><%= navigation_link( "Done", done_path, {:accesskey=>"d", :title=>"Completed"} ) %></li>
|
||||
<li><%= navigation_link( "Notes", notes_path, {:accesskey => "o", :title => "Show all notes"} ) %></li>
|
||||
<li><%= navigation_link( "Preferences", preferences_path, {:accesskey => "u", :title => "Show my preferences"} ) %></li>
|
||||
|
|
@ -57,6 +54,7 @@ window.onload=function(){
|
|||
<% if current_user.is_admin? -%>
|
||||
<li><%= navigation_link("Admin", users_path, {:accesskey => "a", :title => "Add or delete users"} ) %></li>
|
||||
<% end -%>
|
||||
<li><%= navigation_link(image_tag("x-office-calendar.png", :size => "16X16", :border => 0), calendar_path, :title => "Calendar of due actions" ) %></li>
|
||||
<li><%= navigation_link(image_tag("recurring_menu16x16.png", :size => "16X16", :border => 0), {:controller => "recurring_todos", :action => "index"}, :title => "Manage recurring actions" ) %></li>
|
||||
<li><%= navigation_link(image_tag("feed-icon.png", :size => "16X16", :border => 0), {:controller => "feedlist", :action => "index"}, :title => "See a list of available feeds" ) %></li>
|
||||
<li><%= navigation_link(image_tag("menustar.gif", :size => "16X16", :border => 0), tag_path("starred"), :title => "See your starred actions" ) %></li>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<div id="<%= dom_id(note, 'container') %>">
|
||||
<h2><%= link_to("Note #{note.id}", note_path(note), :title => "Show note #{note.id}" ) %></h2>
|
||||
<div id="<%= dom_id(note) %>">
|
||||
<%= sanitize(textilize(note.body.gsub(/((https?:\/\/[^ \n\t]*))/, '"\1":\2'))) %>
|
||||
<%= sanitize(markdown(auto_link(note.body))) %>
|
||||
|
||||
<div class="note_footer">
|
||||
<%= link_to_remote(
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@
|
|||
<li><strong>refresh:</strong> automatic refresh interval for each of the pages (in minutes)</li>
|
||||
<li><strong>verbose action descriptor:</strong> when true, show project/context name in action listing; when false show [P]/[C] with tool tips</li>
|
||||
<li><strong>mobile todos per page:</strong> the maximum number of actions to show on a single page in the mobile view</li>
|
||||
<li><strong>From email:</string> the email address you use for sending todos as email or SMS messages to your Tracks account</li>
|
||||
<li><string>Default email context:</string> the context to which tasks sent in via email or SMS should be added</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
|
@ -67,6 +69,8 @@
|
|||
<%= row_with_text_field('refresh') %>
|
||||
<%= row_with_select_field("verbose_action_descriptors") %>
|
||||
<%= row_with_text_field("mobile_todos_per_page") %>
|
||||
<%= row_with_text_field("sms_email") %>
|
||||
<%= table_row("sms_context", false) { select('prefs', 'sms_context_id', current_user.contexts.map{|c| [c.name, c.id]}) } %>
|
||||
|
||||
<tr><td><%= submit_tag "Update" %></td>
|
||||
<td><%= link_to "Cancel", :action => 'index' %></td>
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@
|
|||
<li>Refresh interval (in minutes): <span class="highlight"><%= prefs.refresh %></span></li>
|
||||
<li>Verbose action descriptors: <span class="highlight"><%= prefs.verbose_action_descriptors %></span></li>
|
||||
<li>Actions per page (Mobile View): <span class="highlight"><%= prefs.mobile_todos_per_page %></span></li>
|
||||
<li>From email: <span class="highlight"><%= prefs.sms_email %></span></li>
|
||||
<li>Default email context: <span class="highlight"><%= prefs.sms_context.nil? ? "None" : prefs.sms_context.name %></span></li>
|
||||
</ul>
|
||||
<div class="actions">
|
||||
<%= link_to "Edit preferences »", { :controller => 'preferences', :action => 'edit'}, :class => 'edit_link' %>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<% @not_done = project.not_done_todos -%>
|
||||
<% #@not_done = project.not_done_todos -%>
|
||||
|
||||
<div id="p<%= project.id %>" class="container project">
|
||||
<h2>
|
||||
|
|
@ -6,7 +6,9 @@
|
|||
<a href="#" class="container_toggle" id="toggle_p<%= project.id %>"><%= image_tag("collapse.png") %></a>
|
||||
<% end %>
|
||||
<span class="in_place_editor_field" id="project_name_in_place_editor"><%= project.name %></span>
|
||||
<%= in_place_editor 'project_name_in_place_editor', { :url => { :controller => 'projects', :action => 'update', :id => project.id, :field => 'name', :wants_render => false, :escape => false} , :options=>"{method:'put'}" } %>
|
||||
<%= in_place_editor 'project_name_in_place_editor', {
|
||||
:url => { :controller => 'projects', :action => 'update', :id => project.id, :field => 'name', :update_project_name => true, :escape => false} ,
|
||||
:options=>"{method:'put'}", :script => true} %>
|
||||
</h2>
|
||||
<% unless project.description.blank? -%>
|
||||
<div class="project_description"><%= sanitize(project.description) %></div>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
<div class="project-state-group" id="list-<%= state %>-projects-container" <%= " style=\"display:none\"" if project_state_group.empty? %>>
|
||||
<h2><span id="<%= state %>-projects-count" class="badge"><%= project_state_group.length %></span><%= state.titlecase %> Projects</h2>
|
||||
<div class="menu_sort"><span class="sort_separator">Sort </span>
|
||||
<div class="alpha_sort">
|
||||
<%= link_to("Sort Alphabetically", alphabetize_projects_path(:state => state),
|
||||
<%= link_to("Alphabetically", alphabetize_projects_path(:state => state),
|
||||
:class => "alphabetize_link", :title => "Sort these projects alphabetically") %>
|
||||
<% apply_behavior '.alphabetize_link:click', :prevent_default => true do |page, element|
|
||||
page.confirming 'Are you sure that you want to sort these projects alphabetically? This will replace the existing sort order.' do
|
||||
|
|
@ -10,10 +11,21 @@
|
|||
page << remote_to_href(:complete => "alphaSort.stopWaiting()")
|
||||
end
|
||||
end
|
||||
%></div><span class="sort_separator"> | </span><div class="tasks_sort">
|
||||
<%= link_to("By number of tasks", actionize_projects_path(:state => state),
|
||||
:class => "actionize_link", :title => "Sort these projects by number of tasks") %>
|
||||
<% apply_behavior '.actionize_link:click', :prevent_default => true do |page, element|
|
||||
page.confirming 'Are you sure that you want to sort these projects by the number of tasks? This will replace the existing sort order.' do
|
||||
page << "tasksSort = this.up('.tasks_sort');
|
||||
tasksSort.startWaiting();"
|
||||
page << remote_to_href(:complete => "tasksSort.stopWaiting()")
|
||||
end
|
||||
end
|
||||
%></div>
|
||||
</div>
|
||||
|
||||
<div id="list-<%= state %>-projects" class="project-list">
|
||||
<%= render :partial => 'project_listing', :collection => project_state_group %>
|
||||
</div>
|
||||
<%= sortable_element "list-#{state}-projects", get_listing_sortable_options("list-#{state}-projects") %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
6
app/views/projects/actionize.js.rjs
Normal file
6
app/views/projects/actionize.js.rjs
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
list_id = "list-#{@state}-projects"
|
||||
page.replace_html list_id,
|
||||
:partial => 'project_listing',
|
||||
:collection => @projects
|
||||
page.sortable list_id, get_listing_sortable_options(list_id)
|
||||
|
||||
|
|
@ -61,4 +61,10 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
window.onload=function(){
|
||||
Nifty("div#project_new_project_container","normal");
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
10
app/views/projects/update_project_name.js.rjs
Normal file
10
app/views/projects/update_project_name.js.rjs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
page['project_name_in_place_editor'].replace_html @project.name
|
||||
|
||||
page['default_project_name_id'].value = @project.name
|
||||
page['todo_project_name'].value = @project.name
|
||||
|
||||
# renew project auto complete array
|
||||
page << "projectAutoCompleter.options.array = #{project_names_for_autocomplete}; projectAutoCompleter.changed = true"
|
||||
|
||||
status_message = "Name of project was changed"
|
||||
page.notify :notice, status_message, 5.0
|
||||
|
|
@ -6,7 +6,16 @@
|
|||
<div class="description">
|
||||
<span class="todo.descr"><%= sanitize(recurring_todo.description) %></span> <%= recurring_todo_tag_list %>
|
||||
<span class='recurrence_pattern'>
|
||||
[<%=recurrence_target(recurring_todo)%> <%= recurring_todo.recurrence_pattern %> <%= recurrence_time_span(recurring_todo) %>]
|
||||
<%
|
||||
rt = recurrence_target(recurring_todo)
|
||||
rp = recurring_todo.recurrence_pattern
|
||||
# only add space if recurrence_pattern has content
|
||||
rp = " " + rp if !rp.nil?
|
||||
rts = recurrence_time_span(recurring_todo)
|
||||
# only add space if recurrence_time_span has content
|
||||
rts = " " + rts if !(rts == "")
|
||||
%>
|
||||
[<%=rt%><%=rp%><%=rts%>]
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -101,22 +101,22 @@
|
|||
<div id="recurring_monthly" style="display:none">
|
||||
<label>Settings for monthly recurring actions</label><br/>
|
||||
<%= radio_button_tag('recurring_todo[monthly_selector]', 'monthly_every_x_day', true)%> Day <%=
|
||||
text_field_tag('recurring_todo[monthly_every_x_day]', Time.now.mday, {"size" => 3, "tabindex" => 9}) %> on every <%=
|
||||
text_field_tag('recurring_todo[monthly_every_x_day]', Time.zone.now.mday, {"size" => 3, "tabindex" => 9}) %> on every <%=
|
||||
text_field_tag('recurring_todo[monthly_every_x_month]', 1, {"size" => 3, "tabindex" => 10}) %> month<br/>
|
||||
<%= radio_button_tag('recurring_todo[monthly_selector]', 'monthly_every_xth_day')%> The <%=
|
||||
select_tag('recurring_todo[monthly_every_xth_day]', options_for_select(@xth_day), {}) %> <%=
|
||||
select_tag('recurring_todo[monthly_day_of_week]' , options_for_select(@days_of_week, Time.now.wday), {}) %> of every <%=
|
||||
select_tag('recurring_todo[monthly_day_of_week]' , options_for_select(@days_of_week, Time.zone.now.wday), {}) %> of every <%=
|
||||
text_field_tag('recurring_todo[monthly_every_x_month2]', 1, {"size" => 3, "tabindex" => 11}) %> month<br/>
|
||||
</div>
|
||||
<div id="recurring_yearly" style="display:none">
|
||||
<label>Settings for yearly recurring actions</label><br/>
|
||||
<%= radio_button_tag('recurring_todo[yearly_selector]', 'yearly_every_x_day', true)%> Every <%=
|
||||
select_tag('recurring_todo[yearly_month_of_year]', options_for_select(@months_of_year, Time.now.month), {}) %> <%=
|
||||
text_field_tag('recurring_todo[yearly_every_x_day]', Time.now.day, "size" => 3, "tabindex" => 9) %><br/>
|
||||
select_tag('recurring_todo[yearly_month_of_year]', options_for_select(@months_of_year, Time.zone.now.month), {}) %> <%=
|
||||
text_field_tag('recurring_todo[yearly_every_x_day]', Time.zone.now.day, "size" => 3, "tabindex" => 9) %><br/>
|
||||
<%= radio_button_tag('recurring_todo[yearly_selector]', 'yearly_every_xth_day')%> The <%=
|
||||
select_tag('recurring_todo[yearly_every_xth_day]', options_for_select(@xth_day), {}) %> <%=
|
||||
select_tag('recurring_todo[yearly_day_of_week]', options_for_select(@days_of_week, Time.now.wday), {}) %> of <%=
|
||||
select_tag('recurring_todo[yearly_month_of_year2]', options_for_select(@months_of_year, Time.now.month), {}) %><br/>
|
||||
select_tag('recurring_todo[yearly_day_of_week]', options_for_select(@days_of_week, Time.zone.now.wday), {}) %> of <%=
|
||||
select_tag('recurring_todo[yearly_month_of_year2]', options_for_select(@months_of_year, Time.zone.now.month), {}) %><br/>
|
||||
</div>
|
||||
<div id="recurring_target">
|
||||
<label>Set recurrence on</label><br/>
|
||||
|
|
|
|||
|
|
@ -40,3 +40,9 @@
|
|||
apply_behaviour "#recurring_edit_period:click",
|
||||
"TracksForm.hide_all_edit_recurring(); $('recurring_edit_'+TracksForm.get_edit_period()).show();"
|
||||
-%>
|
||||
|
||||
<script type="text/javascript">
|
||||
window.onload=function(){
|
||||
Nifty("div#recurring_new_container","normal");
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
<div id="todo_new_action_container">
|
||||
|
||||
<div id="toggle_action_new" class="hide_form">
|
||||
<a title="Hide new action form" accesskey="n">« Hide form</a>
|
||||
<a title="Hide new action form" accesskey="n" href="#">« Hide form</a>
|
||||
<% apply_behavior '#toggle_action_new a:click', :prevent_default => true do |page|
|
||||
page << "TracksForm.toggle('toggle_action_new', 'todo_new_action', 'todo-form-new-action',
|
||||
'« Hide form', 'Hide next action form',
|
||||
|
|
@ -34,6 +34,7 @@
|
|||
<label for="todo_notes">Notes</label>
|
||||
<%= text_area( "todo", "notes", "cols" => 29, "rows" => 6, "tabindex" => 2) %>
|
||||
|
||||
<input id="default_project_name_id" name="default_project_name" type="hidden" value="<%=@initial_project_name-%>" />
|
||||
<label for="todo_project_name">Project</label>
|
||||
<input id="todo_project_name" name="project_name" autocomplete="off" tabindex="3" size="30" type="text" value="<%= @initial_project_name %>" />
|
||||
<div class="page_name_auto_complete" id="project_list" style="display:none"></div>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
<div id="error_status"><%= error_messages_for("todo", :object_name => 'action') %></div>
|
||||
|
||||
<%= hidden_field( "todo", "id" ) %>
|
||||
<%= source_view_tag( @source_view ) %>
|
||||
<%= hidden_field( "todo", "id" ) -%>
|
||||
<%= source_view_tag( @source_view ) -%>
|
||||
<%= "<INPUT TYPE=\"hidden\" name=\"_tag_name\" value=\""+ @tag_name+"\">" unless @tag_name.nil? -%>
|
||||
|
||||
<label for="<%= dom_id(@todo, 'description') %>">Description</label>
|
||||
<%= text_field( "todo", "description", "size" => 30, "tabindex" => 8) %>
|
||||
|
|
@ -48,16 +49,16 @@ Event.observe($('<%= dom_id(@todo, 'context_name') %>'), "click", <%= dom_id(@to
|
|||
<div class="due_input">
|
||||
<label for="<%= dom_id(@todo, 'due_label') %>">Due</label>
|
||||
<%= date_field_tag("todo[due]", dom_id(@todo, 'due'), format_date(@todo.due), "tabindex" => 13) %>
|
||||
<a href="#" id="<%= dom_id(@todo, 'due_x') %>" class="date_clear">
|
||||
<%= image_tag("cancel.png", :alt => "") %>
|
||||
<a href="#" id="<%= dom_id(@todo, 'due_x') %>" class="date_clear" title="Clear due date">
|
||||
<%= image_tag("delete_off.png", :alt => "Clear due date") %>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="show_from_input">
|
||||
<label for="<%= dom_id(@todo, 'show_from') %>">Show from</label>
|
||||
<%= date_field_tag("todo[show_from]", dom_id(@todo, 'show_from'), format_date(@todo.show_from), "tabindex" => 14) %>
|
||||
<a href="#" id="<%= dom_id(@todo, 'show_from_x') %>" class="date_clear">
|
||||
<%= image_tag("cancel.png", :alt => "") %>
|
||||
<a href="#" id="<%= dom_id(@todo, 'show_from_x') %>" class="date_clear" title="Clear show from date">
|
||||
<%= image_tag("delete_off.png", :alt => "Clear show from date") %>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -33,8 +33,8 @@ end %>
|
|||
<h2><label for="tag_list">Tags (separate with commas)</label></h2>
|
||||
<%= text_field_tag "tag_list", @tag_list_text, :size => 30, :tabindex => 6 %>
|
||||
<h2><label for="todo_due">Due</label></h2>
|
||||
<%= date_select("todo", "due", :order => [:day, :month, :year],
|
||||
:start_year => this_year, :include_blank => true) %>
|
||||
<%= date_select("todo", "due", {:order => [:day, :month, :year],
|
||||
:start_year => this_year, :include_blank => true}, :tabindex => 7) %>
|
||||
<h2><label for="todo_show_from">Show from</label></h2>
|
||||
<%= date_select("todo", "show_from", :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}, :tabindex => 8) %>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
<%= remote_star_icon %>
|
||||
<%= remote_toggle_checkbox unless source_view_is :deferred %>
|
||||
<div class="description<%= staleness_class( todo ) %>">
|
||||
<% unless @todo.completed? %><span class="defer-container"><%= defer_link(1) %> <%= defer_link(7) %></span><% end %>
|
||||
<%= date_span -%>
|
||||
<span class="todo.descr"><%= h sanitize(todo.description) %></span>
|
||||
<%= link_to(image_tag("recurring16x16.png"), {:controller => "recurring_todos", :action => "index"}, :class => "recurring_icon") if @todo.from_recurring_todo? %>
|
||||
|
|
|
|||
|
|
@ -3,5 +3,5 @@
|
|||
element.next('.todo_notes').toggle
|
||||
end -%>
|
||||
<div class="todo_notes" id="<%= dom_id(item, 'notes') %>" style="display:none">
|
||||
<%= markdown( item.notes.gsub(/((https?:\/\/[^ \n\t]*))/, '"\1":\2') ) %>
|
||||
<%= sanitize(markdown( auto_link(item.notes)) ) %>
|
||||
</div>
|
||||
68
app/views/todos/calendar.html.erb
Normal file
68
app/views/todos/calendar.html.erb
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
<div id="display_box">
|
||||
|
||||
<div class="container">
|
||||
<h2>Due today</h2>
|
||||
<div id="empty_due_today" <%= "style=\"display:none\"" unless @due_today.empty? %>>
|
||||
No actions due today
|
||||
</div>
|
||||
<div id="due_today">
|
||||
<%= render :partial => "todos/todo", :collection => @due_today %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<h2>Due in rest of this week</h2>
|
||||
<div id="empty_due_this_week" <%= "style=\"display:none\"" unless @due_this_week.empty? %>>
|
||||
No actions due in rest of this week
|
||||
</div>
|
||||
<div id="due_this_week">
|
||||
<%= render :partial => "todos/todo", :collection => @due_this_week %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<h2>Due next week</h2>
|
||||
<div id="empty_due_next_week" <%= "style=\"display:none\"" unless @due_next_week.empty? %>>
|
||||
No actions due in next week
|
||||
</div>
|
||||
<div id="due_next_week">
|
||||
<%= render :partial => "todos/todo", :collection => @due_next_week %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<h2>Due in rest of <%= Time.zone.now.strftime("%B") %> </h2>
|
||||
<div id="empty_due_this_month" <%= "style=\"display:none\"" unless @due_this_month.empty? %>>
|
||||
No actions due in rest of this month
|
||||
</div>
|
||||
<div id="due_this_month">
|
||||
<%= render :partial => "todos/todo", :collection => @due_this_month %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<h2>Due in <%= (Time.zone.now+1.month).strftime("%B") %> and later</h2>
|
||||
<div id="empty_due_after_this_month" <%= "style=\"display:none\"" unless @due_after_this_month.empty? %>>
|
||||
No actions due after this month
|
||||
</div>
|
||||
<div id="due_after_this_month">
|
||||
<%= render :partial => "todos/todo", :collection => @due_after_this_month %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div><!-- End of display_box -->
|
||||
<div class="input_box" id="input_box">
|
||||
<!--
|
||||
<input class="hide_tickler" id="hide_tickler" type="checkbox" tabindex="5" name="hide_tickler" checked="true"/>
|
||||
<label for="hide_tickler"> Show actions in tickler</label>
|
||||
<br/><br/>
|
||||
-->
|
||||
<p><%= link_to('<span class="feed">iCal</span>', {:format => 'ics', :token => current_user.token}, :title => "iCal feed" ) %>
|
||||
- Get this calendar in iCal format</p>
|
||||
</div>
|
||||
|
||||
<%
|
||||
apply_behavior 'input.hide_tickler:click', :prevent_default => true do |page|
|
||||
page << "alert('hiding action in tickler from calendar is not yet implemented');"
|
||||
end
|
||||
%>
|
||||
31
app/views/todos/calendar.ics.erb
Normal file
31
app/views/todos/calendar.ics.erb
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
BEGIN:VCALENDAR
|
||||
PRODID:-//TRACKS//<%= TRACKS_VERSION %>//EN
|
||||
VERSION:2.0
|
||||
CALSCALE:GREGORIAN
|
||||
METHOD:PUBLISH
|
||||
X-WR-CALNAME:Tracks
|
||||
<% for todo in @due_all
|
||||
due_date = todo.due
|
||||
overdue_text = ""
|
||||
if due_date.at_midnight < Time.zone.now.at_midnight
|
||||
due_date = Time.zone.now
|
||||
overdue_text = "Overdue: "
|
||||
end
|
||||
%>BEGIN:VEVENT
|
||||
DTSTART;VALUE=DATE:<%= due_date.strftime("%Y%m%d") %>
|
||||
DTEND;VALUE=DATE:<%= (due_date+1.day).strftime("%Y%m%d") %>
|
||||
DTSTAMP:<%= due_date.strftime("%Y%m%dT%H%M%SZ") %>
|
||||
UID:<%= todo_url(todo) %>
|
||||
CLASS:PUBLIC
|
||||
CATEGORIES:Tracks
|
||||
CREATED:<%= todo.created_at.strftime("%Y%m%dT%H%M%SZ") %>
|
||||
DESCRIPTION:<%= format_ical_notes(todo.notes) %>
|
||||
LAST-MODIFIED:<%= todo.updated_at.strftime("%Y%m%dT%H%M%SZ") %>
|
||||
LOCATION:
|
||||
SEQUENCE:0
|
||||
STATUS:CONFIRMED
|
||||
SUMMARY:<%= overdue_text + todo.description %>
|
||||
TRANSP:TRANSPARENT
|
||||
END:VEVENT
|
||||
<% end
|
||||
%>END:VCALENDAR
|
||||
|
|
@ -8,6 +8,7 @@ if @saved
|
|||
page['badge_count'].replace_html @down_count
|
||||
page.send :record, "Form.reset('todo-form-new-action');Form.focusFirstElement('todo-form-new-action')"
|
||||
page['todo_context_name'].value = @initial_context_name
|
||||
page['todo_project_name'].value = @initial_project_name
|
||||
page << "updateContextNamesForAutoComplete(#{context_names_for_autocomplete})" if @new_context_created
|
||||
page << "projectAutoCompleter.options.array = #{project_names_for_autocomplete}" if @new_project_created
|
||||
if should_show_new_item()
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
if @saved
|
||||
page[@todo].remove
|
||||
page.show "empty_"+@original_item_due_id if @old_due_empty
|
||||
page['badge_count'].replace_html @down_count
|
||||
|
||||
# remove context if empty
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<% form_tag formatted_todos_path(:m), :method => :post do %>
|
||||
<%= render :partial => 'edit_mobile' %>
|
||||
<p><input type="submit" value="Create" tabindex="6" /></p>
|
||||
<p><input type="submit" value="Create" tabindex="12" accesskey="#" /></p>
|
||||
<% end -%>
|
||||
<%= link_to "Back", @return_path %>
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
<% form_tag formatted_todo_path(@todo, :m), :method => :put do %>
|
||||
<%= render :partial => 'edit_mobile', :locals => { :parent_container_type => "show_mobile" } %>
|
||||
<p><input type="submit" value="Update" tabindex="6" /></p>
|
||||
<p><input type="submit" value="Update" tabindex="6" accesskey="#" /></p>
|
||||
<% end -%>
|
||||
<%= link_to "Cancel", @return_path %>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
if @saved
|
||||
page[@todo].remove
|
||||
page.show "empty_"+@original_item_due_id if @old_due_empty
|
||||
if @todo.completed?
|
||||
|
||||
# completed todos move from their context to the completed container
|
||||
|
|
@ -23,10 +24,12 @@ if @saved
|
|||
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 if @new_recurring_todo.nil?
|
||||
if @todo.recurring_todo.todos.active.count == 0
|
||||
page.notify :notice, "There is no next action after the recurring action you just finished. The recurrence is completed", 6.0 if @new_recurring_todo.nil?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
else
|
||||
# todo is activated from completed container
|
||||
page.call "todoItems.ensureVisibleWithEffectAppear", item_container_id(@todo)
|
||||
|
|
|
|||
|
|
@ -6,19 +6,28 @@ if @saved
|
|||
status_message = 'Added new context / ' + status_message if @new_context_created
|
||||
page.notify :notice, status_message, 5.0
|
||||
|
||||
# #update auto completer arrays for context and project
|
||||
# update auto completer arrays for context and project
|
||||
page << "contextAutoCompleter.options.array = #{context_names_for_autocomplete}; contextAutoCompleter.changed = true" if @new_context_created
|
||||
page << "projectAutoCompleter.options.array = #{project_names_for_autocomplete}; projectAutoCompleter.changed = true" if @new_project_created
|
||||
|
||||
if source_view_is_one_of(:todo, :context, :tag)
|
||||
if @context_changed || @todo.deferred?
|
||||
page[@todo].remove
|
||||
|
||||
if (@remaining_in_context == 0)
|
||||
# remove context container from page if empty
|
||||
source_view do |from|
|
||||
from.todo { page.visual_effect :fade, "c#{@original_item_context_id}", :duration => 0.4 }
|
||||
from.tag { page.visual_effect :fade, "c#{@original_item_context_id}", :duration => 0.4 }
|
||||
from.context { page.show "c#{@original_item_context_id}empty-nd" }
|
||||
if @context_changed
|
||||
source_view do |from|
|
||||
from.todo { page.visual_effect :fade, "c#{@original_item_context_id}", :duration => 0.4 }
|
||||
from.tag { page.visual_effect :fade, "c#{@original_item_context_id}", :duration => 0.4 }
|
||||
from.context { page.show "c#{@original_item_context_id}empty-nd" }
|
||||
end
|
||||
else
|
||||
source_view do |from|
|
||||
from.todo { page.visual_effect :fade, item_container_id(@todo), :duration => 0.4 }
|
||||
from.tag { page.visual_effect :fade, item_container_id(@todo), :duration => 0.4 }
|
||||
from.context { page.show "c#{@original_item_context_id}empty-nd" }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -92,6 +101,23 @@ if @saved
|
|||
elsif source_view_is :stats
|
||||
page.replace dom_id(@todo), :partial => 'todos/todo', :locals => { :parent_container_type => parent_container_type }
|
||||
page.visual_effect :highlight, dom_id(@todo), :duration => 3
|
||||
elsif source_view_is :calendar
|
||||
if @due_date_changed
|
||||
page[@todo].remove
|
||||
page.show "empty_"+@original_item_due_id if @old_due_empty
|
||||
page.hide "empty_"+@new_due_id
|
||||
page.insert_html :bottom, @new_due_id, :partial => 'todos/todo'
|
||||
page.visual_effect :highlight, dom_id(@todo), :duration => 3
|
||||
else
|
||||
if @todo.due.nil?
|
||||
# due date removed
|
||||
page[@todo].remove
|
||||
page.show "empty_"+@original_item_due_id if @old_due_empty
|
||||
else
|
||||
page.replace dom_id(@todo), :partial => 'todos/todo', :locals => { :parent_container_type => parent_container_type }
|
||||
page.visual_effect :highlight, dom_id(@todo), :duration => 3
|
||||
end
|
||||
end
|
||||
else
|
||||
logger.error "unexpected source_view '#{params[:_source_view]}'"
|
||||
end
|
||||
|
|
|
|||
|
|
@ -93,8 +93,17 @@ if (AUTHENTICATION_SCHEMES.include? 'open_id')
|
|||
#requires ruby-openid gem to be installed
|
||||
end
|
||||
|
||||
# setting this to true will make the cookies only available over HTTPS
|
||||
TRACKS_COOKIES_SECURE = false
|
||||
|
||||
MOBILE_CONTENT_TYPE = 'tracks/mobile'
|
||||
Mime::Type.register(MOBILE_CONTENT_TYPE, :m)
|
||||
|
||||
TRACKS_VERSION='1.7-devel'
|
||||
tracks_version='1.7-devel'
|
||||
|
||||
# comment out next two lines if you do not want (or can not) the date of the
|
||||
# last git commit in the footer
|
||||
info=`git log --pretty=format:"%ai" -1`
|
||||
tracks_version=tracks_version + ' ('+info+')'
|
||||
|
||||
TRACKS_VERSION=tracks_version
|
||||
|
|
|
|||
|
|
@ -28,6 +28,10 @@ ActionController::Routing::Routes.draw do |map|
|
|||
projects.resources :todos, :name_prefix => "project_"
|
||||
end
|
||||
|
||||
map.resources :projects, :collection => {:order => :post, :actionize => :post} do |projects|
|
||||
projects.resources :todos, :name_prefix => "project_"
|
||||
end
|
||||
|
||||
map.resources :todos,
|
||||
:member => {:toggle_check => :put, :toggle_star => :put},
|
||||
:collection => {:check_deferred => :post, :filter_to_context => :post, :filter_to_project => :post}
|
||||
|
|
@ -43,9 +47,12 @@ ActionController::Routing::Routes.draw do |map|
|
|||
# so /todos/tag/version1.5.xml will result in :name => 'version1.5.xml'
|
||||
# UPDATE: added support for mobile view. All tags ending on .m will be
|
||||
# routed to mobile view of tags.
|
||||
todos.tag 'todos/tag/:name', :action => "tag", :format => 'm', :name => /.*\.m/
|
||||
todos.tag 'todos/tag/:name.m', :action => "tag", :format => 'm'
|
||||
todos.tag 'todos/tag/:name', :action => "tag", :name => /.*/
|
||||
|
||||
todos.calendar 'calendar.ics', :action => "calendar", :format => 'ics'
|
||||
todos.calendar 'calendar', :action => "calendar"
|
||||
|
||||
todos.mobile 'mobile', :action => "index", :format => 'm'
|
||||
todos.mobile_abbrev 'm', :action => "index", :format => 'm'
|
||||
todos.mobile_abbrev_new 'm/new', :action => "new", :format => 'm'
|
||||
|
|
@ -55,6 +62,10 @@ ActionController::Routing::Routes.draw do |map|
|
|||
map.feeds 'feeds', :controller => 'feedlist', :action => 'index'
|
||||
map.feeds 'feeds.m', :controller => 'feedlist', :action => 'index', :format => 'm'
|
||||
|
||||
if Rails.env == 'test'
|
||||
map.connect '/selenium_helper/login', :controller => 'selenium_helper', :action => 'login'
|
||||
end
|
||||
|
||||
map.preferences 'preferences', :controller => 'preferences', :action => 'index'
|
||||
map.integrations 'integrations', :controller => 'integrations', :action => 'index'
|
||||
|
||||
|
|
|
|||
11
db/migrate/041_add_sms_to_preference.rb
Normal file
11
db/migrate/041_add_sms_to_preference.rb
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
class AddSmsToPreference < ActiveRecord::Migration
|
||||
def self.up
|
||||
add_column :preferences, :sms_email, :string
|
||||
add_column :preferences, :sms_context_id, :integer
|
||||
end
|
||||
|
||||
def self.down
|
||||
remove_column :preferences, :sms_context_id
|
||||
remove_column :preferences, :sms_email
|
||||
end
|
||||
end
|
||||
39
db/migrate/042_change_dates_to_datetimes.rb
Normal file
39
db/migrate/042_change_dates_to_datetimes.rb
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
class ChangeDatesToDatetimes < ActiveRecord::Migration
|
||||
def self.up
|
||||
change_column :todos, :show_from, :datetime
|
||||
change_column :todos, :due, :datetime
|
||||
change_column :recurring_todos, :start_from, :datetime
|
||||
change_column :recurring_todos, :end_date, :datetime
|
||||
|
||||
User.all(:include => [:todos, :recurring_todos]).each do |user|
|
||||
if !user.prefs ## ugly hack for strange edge-case of not having preferences object
|
||||
user.instance_eval do
|
||||
def at_midnight(date)
|
||||
return Time.zone.local(date.year, date.month, date.day, 0, 0, 0)
|
||||
end
|
||||
def time
|
||||
Time.zone.now
|
||||
end
|
||||
end
|
||||
end
|
||||
user.todos.each do |todo|
|
||||
todo[:show_from] = user.at_midnight(todo.show_from) unless todo.show_from.nil?
|
||||
todo[:due] = user.at_midnight(todo.due) unless todo.due.nil?
|
||||
todo.save_with_validation(false)
|
||||
end
|
||||
|
||||
user.recurring_todos.each do |todo|
|
||||
todo[:start_from] = user.at_midnight(todo.start_from) unless todo.start_from.nil?
|
||||
todo[:end_date] = user.at_midnight(todo.end_date) unless todo.end_date.nil?
|
||||
todo.save_with_validation(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.down
|
||||
change_column :todos, :show_from, :date
|
||||
change_column :todos, :due, :date
|
||||
change_column :recurring_todos, :start_from, :date
|
||||
change_column :recurring_todos, :end_date, :date
|
||||
end
|
||||
end
|
||||
10
db/migrate/043_add_updated_at_to_todos.rb
Normal file
10
db/migrate/043_add_updated_at_to_todos.rb
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
class AddUpdatedAtToTodos < ActiveRecord::Migration
|
||||
def self.up
|
||||
add_column :todos, :updated_at, :timestamp
|
||||
execute 'update todos set updated_at = created_at where completed_at IS NULL'
|
||||
execute 'update todos set updated_at = completed_at where NOT completed_at IS NULL'
|
||||
end
|
||||
def self.down
|
||||
remove_column :todos, :updated_at
|
||||
end
|
||||
end
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
* Homepage: http://www.rousette.org.uk/projects/
|
||||
* Author: bsag (http://www.rousette.org.uk/)
|
||||
* Contributors: Nicholas Lee, Lolindrath, Jim Ray, Arnaud Limbourg, Timothy Martens, Luke Melia, John Leonard, Jim Strupp, Eric Lesh, Damien Cirotteau, Janet Riley, Reinier Balt, Jacqui Maher, James Kebinger, Jeffrey Gipson
|
||||
* Contributors: Nicholas Lee, Lolindrath, Jim Ray, Arnaud Limbourg, Timothy Martens, Luke Melia, John Leonard, Jim Strupp, Eric Lesh, Damien Cirotteau, Janet Riley, Reinier Balt, Jacqui Maher, James Kebinger, Jeffrey Gipson, Eric Allen
|
||||
* Version: 1.6
|
||||
* Copyright: (cc) 2004-2008 rousette.org.uk
|
||||
* License: GNU GPL
|
||||
|
|
@ -13,6 +13,21 @@ Trac (for bug reports and feature requests): http://dev.rousette.org.uk/report/6
|
|||
|
||||
Wiki (deprecated - please use Trac): http://www.rousette.org.uk/projects/wiki/
|
||||
|
||||
== Version 1.7dev
|
||||
|
||||
New features:
|
||||
1. Recurring todos
|
||||
2. Cleanup of feed page and add feed for starred actions
|
||||
3. Initial importer of yaml files (still very EXPERIMENTAL)
|
||||
4. New interface to import an email / sms messages into Tracks (needs an email server on the same server as Tracks)
|
||||
5. New buttons to quickly defer an action 1 or 7 days
|
||||
6. Calendar view to review due actions, includes iCal feed to use in your calendar app (tested with Google Calendar, Evolution, Outlook 2007)
|
||||
7. You can now sort projects on number of active todos
|
||||
|
||||
Under the hood:
|
||||
1. Move selenium tests to RSpec
|
||||
2. Bugfixes
|
||||
|
||||
== Version 1.6
|
||||
1. upgrade to rails 2.0.2
|
||||
2. new mobile interface (with some iPhone compatibility fixes)
|
||||
|
|
|
|||
|
|
@ -1,113 +1,113 @@
|
|||
module AuthenticatedTestHelper
|
||||
# Sets the current user in the session from the user fixtures.
|
||||
def login_as(user)
|
||||
@request.session['user_id'] = user ? users(user).id : nil
|
||||
end
|
||||
|
||||
def content_type(type)
|
||||
@request.env['Content-Type'] = type
|
||||
end
|
||||
|
||||
def accept(accept)
|
||||
@request.env["HTTP_ACCEPT"] = accept
|
||||
end
|
||||
|
||||
def authorize_as(user)
|
||||
if user
|
||||
@request.env["HTTP_AUTHORIZATION"] = "Basic #{Base64.encode64("#{users(user).login}:test")}"
|
||||
accept 'application/xml'
|
||||
content_type 'application/xml'
|
||||
else
|
||||
@request.env["HTTP_AUTHORIZATION"] = nil
|
||||
accept nil
|
||||
content_type nil
|
||||
end
|
||||
end
|
||||
|
||||
# http://project.ioni.st/post/217#post-217
|
||||
#
|
||||
# def test_new_publication
|
||||
# assert_difference(Publication, :count) do
|
||||
# post :create, :publication => {...}
|
||||
# # ...
|
||||
# end
|
||||
# end
|
||||
#
|
||||
def assert_difference(object, method = nil, difference = 1)
|
||||
initial_value = object.send(method)
|
||||
yield
|
||||
assert_equal initial_value + difference, object.send(method), "#{object}##{method}"
|
||||
end
|
||||
|
||||
def assert_no_difference(object, method, &block)
|
||||
assert_difference object, method, 0, &block
|
||||
end
|
||||
|
||||
# Assert the block redirects to the login
|
||||
#
|
||||
# assert_requires_login(:bob) { |c| c.get :edit, :id => 1 }
|
||||
#
|
||||
def assert_requires_login(login = nil)
|
||||
yield HttpLoginProxy.new(self, login)
|
||||
end
|
||||
|
||||
def assert_http_authentication_required(login = nil)
|
||||
yield XmlLoginProxy.new(self, login)
|
||||
end
|
||||
|
||||
def reset!(*instance_vars)
|
||||
instance_vars = [:controller, :request, :response] unless instance_vars.any?
|
||||
instance_vars.collect! { |v| "@#{v}".to_sym }
|
||||
instance_vars.each do |var|
|
||||
instance_variable_set(var, instance_variable_get(var).class.new)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class BaseLoginProxy
|
||||
attr_reader :controller
|
||||
attr_reader :options
|
||||
def initialize(controller, login)
|
||||
@controller = controller
|
||||
@login = login
|
||||
end
|
||||
|
||||
private
|
||||
def authenticated
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def check
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def method_missing(method, *args)
|
||||
@controller.reset!
|
||||
authenticate
|
||||
@controller.send(method, *args)
|
||||
check
|
||||
end
|
||||
end
|
||||
|
||||
class HttpLoginProxy < BaseLoginProxy
|
||||
protected
|
||||
def authenticate
|
||||
@controller.login_as @login if @login
|
||||
end
|
||||
|
||||
def check
|
||||
@controller.assert_redirected_to :controller => 'account', :action => 'login'
|
||||
end
|
||||
end
|
||||
|
||||
class XmlLoginProxy < BaseLoginProxy
|
||||
protected
|
||||
def authenticate
|
||||
@controller.accept 'application/xml'
|
||||
@controller.authorize_as @login if @login
|
||||
end
|
||||
|
||||
def check
|
||||
@controller.assert_response 401
|
||||
end
|
||||
module AuthenticatedTestHelper
|
||||
# Sets the current user in the session from the user fixtures.
|
||||
def login_as(user)
|
||||
@request.session['user_id'] = user ? users(user).id : nil
|
||||
end
|
||||
|
||||
def content_type(type)
|
||||
@request.env['Content-Type'] = type
|
||||
end
|
||||
|
||||
def accept(accept)
|
||||
@request.env["HTTP_ACCEPT"] = accept
|
||||
end
|
||||
|
||||
def authorize_as(user)
|
||||
if user
|
||||
@request.env["HTTP_AUTHORIZATION"] = "Basic #{Base64.encode64("#{users(user).login}:test")}"
|
||||
accept 'application/xml'
|
||||
content_type 'application/xml'
|
||||
else
|
||||
@request.env["HTTP_AUTHORIZATION"] = nil
|
||||
accept nil
|
||||
content_type nil
|
||||
end
|
||||
end
|
||||
|
||||
# http://project.ioni.st/post/217#post-217
|
||||
#
|
||||
# def test_new_publication
|
||||
# assert_difference(Publication, :count) do
|
||||
# post :create, :publication => {...}
|
||||
# # ...
|
||||
# end
|
||||
# end
|
||||
#
|
||||
def assert_difference(object, method = nil, difference = 1)
|
||||
initial_value = object.send(method)
|
||||
yield
|
||||
assert_equal initial_value + difference, object.send(method), "#{object}##{method}"
|
||||
end
|
||||
|
||||
def assert_no_difference(object, method, &block)
|
||||
assert_difference object, method, 0, &block
|
||||
end
|
||||
|
||||
# Assert the block redirects to the login
|
||||
#
|
||||
# assert_requires_login(:bob) { |c| c.get :edit, :id => 1 }
|
||||
#
|
||||
def assert_requires_login(login = nil)
|
||||
yield HttpLoginProxy.new(self, login)
|
||||
end
|
||||
|
||||
def assert_http_authentication_required(login = nil)
|
||||
yield XmlLoginProxy.new(self, login)
|
||||
end
|
||||
|
||||
def reset!(*instance_vars)
|
||||
instance_vars = [:controller, :request, :response] unless instance_vars.any?
|
||||
instance_vars.collect! { |v| "@#{v}".to_sym }
|
||||
instance_vars.each do |var|
|
||||
instance_variable_set(var, instance_variable_get(var).class.new)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class BaseLoginProxy
|
||||
attr_reader :controller
|
||||
attr_reader :options
|
||||
def initialize(controller, login)
|
||||
@controller = controller
|
||||
@login = login
|
||||
end
|
||||
|
||||
private
|
||||
def authenticated
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def check
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def method_missing(method, *args)
|
||||
@controller.reset!
|
||||
authenticate
|
||||
@controller.send(method, *args)
|
||||
check
|
||||
end
|
||||
end
|
||||
|
||||
class HttpLoginProxy < BaseLoginProxy
|
||||
protected
|
||||
def authenticate
|
||||
@controller.login_as @login if @login
|
||||
end
|
||||
|
||||
def check
|
||||
@controller.assert_redirected_to :controller => 'account', :action => 'login'
|
||||
end
|
||||
end
|
||||
|
||||
class XmlLoginProxy < BaseLoginProxy
|
||||
protected
|
||||
def authenticate
|
||||
@controller.accept 'application/xml'
|
||||
@controller.authorize_as @login if @login
|
||||
end
|
||||
|
||||
def check
|
||||
@controller.assert_response 401
|
||||
end
|
||||
end
|
||||
|
|
@ -48,7 +48,7 @@ module LoginSystem
|
|||
session['user_id'] = user.id
|
||||
set_current_user(user)
|
||||
current_user.remember_me
|
||||
cookies[:auth_token] = { :value => current_user.remember_token , :expires => current_user.remember_token_expires_at }
|
||||
cookies[:auth_token] = { :value => current_user.remember_token , :expires => current_user.remember_token_expires_at, :secure => TRACKS_COOKIES_SECURE }
|
||||
flash[:notice] = "Logged in successfully. Welcome back!"
|
||||
end
|
||||
end
|
||||
|
|
|
|||
BIN
public/images/defer_1.png
Normal file
BIN
public/images/defer_1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3 KiB |
BIN
public/images/defer_7.png
Normal file
BIN
public/images/defer_7.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.1 KiB |
BIN
public/images/x-office-calendar.png
Normal file
BIN
public/images/x-office-calendar.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 515 B |
2
public/javascripts/controls.js
vendored
2
public/javascripts/controls.js
vendored
|
|
@ -211,7 +211,7 @@ Autocompleter.Base = Class.create({
|
|||
markPrevious: function() {
|
||||
if(this.index > 0) this.index--
|
||||
else this.index = this.entryCount-1;
|
||||
this.getEntry(this.index).scrollIntoView(true);
|
||||
this.getEntry(this.index).scrollIntoView(false);
|
||||
},
|
||||
|
||||
markNext: function() {
|
||||
|
|
|
|||
|
|
@ -407,7 +407,7 @@ if(event.keyCode==Event.KEY_TAB||event.keyCode==Event.KEY_RETURN||(Prototype.Bro
|
|||
{this.index=element.autocompleteIndex;this.render();}
|
||||
Event.stop(event);},onClick:function(event){var element=Event.findElement(event,'LI');this.index=element.autocompleteIndex;this.selectEntry();this.hide();},onBlur:function(event){setTimeout(this.hide.bind(this),250);this.hasFocus=false;this.active=false;},render:function(){if(this.entryCount>0){for(var i=0;i<this.entryCount;i++)
|
||||
this.index==i?Element.addClassName(this.getEntry(i),"selected"):Element.removeClassName(this.getEntry(i),"selected");if(this.hasFocus){this.show();this.active=true;}}else{this.active=false;this.hide();}},markPrevious:function(){if(this.index>0)this.index--
|
||||
else this.index=this.entryCount-1;this.getEntry(this.index).scrollIntoView(true);},markNext:function(){if(this.index<this.entryCount-1)this.index++
|
||||
else this.index=this.entryCount-1;this.getEntry(this.index).scrollIntoView(false);},markNext:function(){if(this.index<this.entryCount-1)this.index++
|
||||
else this.index=0;this.getEntry(this.index).scrollIntoView(false);},getEntry:function(index){return this.update.firstChild.childNodes[index];},getCurrentEntry:function(){return this.getEntry(this.index);},selectEntry:function(){this.active=false;this.updateElement(this.getCurrentEntry());},updateElement:function(selectedElement){if(this.options.updateElement){this.options.updateElement(selectedElement);return;}
|
||||
var value='';if(this.options.select){var nodes=$(selectedElement).select('.'+this.options.select)||[];if(nodes.length>0)value=Element.collectTextNodes(nodes[0],this.options.select);}else
|
||||
value=Element.collectTextNodesIgnoreClass(selectedElement,'informal');var bounds=this.getTokenBounds();if(bounds[0]!=-1){var newValue=this.element.value.substr(0,bounds[0]);var whitespace=this.element.value.substr(bounds[0]).match(/^\s+/);if(whitespace)
|
||||
|
|
@ -692,11 +692,14 @@ div#list-active-projects, div#list-hidden-projects, div#list-completed-projects,
|
|||
margin:20px 0px 8px 13px
|
||||
}
|
||||
|
||||
div.alpha_sort {
|
||||
div.menu_sort {
|
||||
margin-top:-20px;
|
||||
float:right;
|
||||
}
|
||||
|
||||
div.alpha_sort, div.tasks_sort,span.sort_separator {
|
||||
float:left;
|
||||
}
|
||||
|
||||
.container td {
|
||||
border: none;
|
||||
|
|
@ -808,6 +811,14 @@ input#go_to_project, input#context_hide {
|
|||
width:100%;
|
||||
}
|
||||
|
||||
.edit_todo_form .Date {
|
||||
width:89%;
|
||||
}
|
||||
|
||||
.edit_todo_form a.date_clear:hover {
|
||||
background: #CCCCCC;
|
||||
}
|
||||
|
||||
.edit_todo_form .tag_list_label {
|
||||
clear:both;
|
||||
}
|
||||
|
|
@ -1193,8 +1204,9 @@ body.integrations textarea {
|
|||
width:80%;
|
||||
background-color:#ddd;
|
||||
}
|
||||
|
||||
.date_clear
|
||||
{
|
||||
float: right;
|
||||
}
|
||||
.defer-container {
|
||||
float:right;
|
||||
}
|
||||
.defer-container a:hover {
|
||||
background-color: inherit;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -129,7 +129,8 @@ div.buttons, div.buttons a, div.buttons a:hover {text-align: right; margin-right
|
|||
div#list-active-projects, div#list-hidden-projects, div#list-completed-projects, div#list-contexts, div#projects-empty-nd {clear:right; border: 1px solid #999}
|
||||
.project-state-group h2 {margin:20px 0px 8px 13px}
|
||||
.search-result-group h2 {margin:20px 0px 8px 13px }
|
||||
div.alpha_sort {margin-top:-20px; float:right}
|
||||
div.menu_sort {margin-top:-20px; float:right}
|
||||
div.alpha_sort, div.tasks_sort,span.sort_separator {float:left}
|
||||
.container td {border: none; padding-bottom: 5px}
|
||||
.container form {border: none}
|
||||
div.project_description {background: #eee; padding: 5px; margin-top: 0px; margin-left: -5px; margin-right: -5px; color: #666; font-style: italic; font-size: 12px; font-weight: normal}
|
||||
|
|
@ -149,6 +150,8 @@ input#go_to_project, input#context_hide {width: 5%}
|
|||
#todo-form-new-action .submit_box, #project_form .submit_box, #context_form .submit_box {height: 25px; padding: 5px 0; text-align: center; clear: right}
|
||||
.edit_todo_form .submit_box {height: 25px; padding: 5px 0; text-align: center; clear: right}
|
||||
.edit_todo_form input, .edit_todo_form textarea {width:100%}
|
||||
.edit_todo_form .Date {width:89%}
|
||||
.edit_todo_form a.date_clear:hover {background: #CCCCCC}
|
||||
.edit_todo_form .tag_list_label {clear:both}
|
||||
.edit_todo_form .due_input, .edit_todo_form .show_from_input, .edit_todo_form .project_input, .edit_todo_form .context_input {width:48%}
|
||||
.edit_todo_form .show_from_input, .edit_todo_form .context_input {float: right}
|
||||
|
|
@ -218,7 +221,8 @@ body.integrations h2 {margin-top:40px; padding-top:20px; margin-bottom:10px; bor
|
|||
body.integrations p, body.integrations li {font-size:1.0em}
|
||||
body.integrations li {list-style-type: disc; list-style-position: inside; margin-left:30px}
|
||||
body.integrations textarea {margin:10px; padding:3px; width:80%; background-color:#ddd}
|
||||
.date_clear {float: right}
|
||||
.defer-container {float:right}
|
||||
.defer-container a:hover {background-color: inherit}
|
||||
div.calendar {position: relative}
|
||||
.calendar, .calendar table {border: 1px solid #556; font-size: 11px; color: #000; cursor: default; background: #eef; z-index: 110; font-family: tahoma,verdana,sans-serif}
|
||||
.calendar .button {text-align: center; padding: 2px}
|
||||
|
|
@ -10,7 +10,7 @@ describe Todo do
|
|||
|
||||
def create_todo(attributes={})
|
||||
todo = Todo.new(valid_attributes(attributes))
|
||||
todo.stub!(:user).and_return(mock_model(User, :date => Time.now))
|
||||
todo.stub!(:user).and_return(mock_model(User, :date => Time.zone.now))
|
||||
todo.save!
|
||||
todo
|
||||
end
|
||||
|
|
@ -32,7 +32,7 @@ describe Todo do
|
|||
|
||||
it 'ensures that show_from is a date in the future' do
|
||||
todo = Todo.new(valid_attributes)
|
||||
todo.stub!(:user).and_return(mock_model(User, :date => Time.now))
|
||||
todo.stub!(:user).and_return(mock_model(User, :date => Time.zone.now))
|
||||
todo.show_from = 3.days.ago
|
||||
todo.should have(1).error_on(:show_from)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ describe User do
|
|||
it 'has many active contexts' do
|
||||
User.should have_many(:active_contexts).
|
||||
with_order('position ASC').
|
||||
with_conditions('hide = ?', 'true').
|
||||
with_conditions('hide = ?', false).
|
||||
with_class_name('Context')
|
||||
end
|
||||
|
||||
|
|
@ -178,4 +178,20 @@ describe User do
|
|||
@user.remember_token_expires_at.should be_between(before, after)
|
||||
end
|
||||
end
|
||||
|
||||
it "should not activate todos that are showing when UTC is tomorrow" do
|
||||
context = Context.create(:name => 'a context')
|
||||
user = User.create(:login => 'user7', :password => 'foobar', :password_confirmation => 'foobar')
|
||||
user.save!
|
||||
user.create_preference
|
||||
user.preference.update_attribute('time_zone', 'Pacific Time (US & Canada)')
|
||||
# Time.zone = 'Pacific Time (US & Canada)'
|
||||
Time.stub!(:now).and_return(Time.new.end_of_day - 20.minutes)
|
||||
todo = user.todos.build(:description => 'test task', :context => context, :show_from => user.date + 1.days)
|
||||
todo.save!
|
||||
|
||||
user.deferred_todos.find_and_activate_ready
|
||||
user = User.find(user.id)
|
||||
user.deferred_todos.should include(todo)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
36
spec/views/notes/_notes.rhtml_spec.rb
Normal file
36
spec/views/notes/_notes.rhtml_spec.rb
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
require File.dirname(__FILE__) + '/../../spec_helper'
|
||||
|
||||
describe "/notes/_notes.rhtml" do
|
||||
before :each do
|
||||
@project = mock_model(Project, :name => "a project")
|
||||
@note = mock_model(Note, :body => "this is a note", :project => @project,
|
||||
:created_at => Time.now, :updated_at? => false)
|
||||
@controller.template.stub!(:apply_behavior)
|
||||
@controller.template.stub!(:format_date)
|
||||
@controller.template.stub!(:render)
|
||||
@controller.template.stub!(:form_remote_tag)
|
||||
end
|
||||
|
||||
it "should render" do
|
||||
render :partial => "/notes/notes", :locals => {:notes => @note}
|
||||
response.should have_tag("div.note_footer")
|
||||
end
|
||||
|
||||
it "should auto-link URLs" do
|
||||
@note.stub!(:body).and_return("http://www.google.com/")
|
||||
render :partial => "/notes/notes", :locals => {:notes => @note}
|
||||
response.should have_tag("a[href=\"http://www.google.com/\"]")
|
||||
end
|
||||
|
||||
it "should auto-link embedded URLs" do
|
||||
@note.stub!(:body).and_return("this is cool: http://www.google.com/")
|
||||
render :partial => "/notes/notes", :locals => {:notes => @note}
|
||||
response.should have_tag("a[href=\"http://www.google.com/\"]")
|
||||
end
|
||||
|
||||
it "should parse Textile links correctly" do
|
||||
@note.stub!(:body).and_return("\"link\":http://www.google.com/")
|
||||
render :partial => "/notes/notes", :locals => {:notes => @note}
|
||||
response.should have_tag("a[href=\"http://www.google.com/\"]")
|
||||
end
|
||||
end
|
||||
33
spec/views/todos/_toggle_notes.rhtml_spec.rb
Normal file
33
spec/views/todos/_toggle_notes.rhtml_spec.rb
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
require File.dirname(__FILE__) + '/../../spec_helper'
|
||||
|
||||
describe "/todos/_toggle_notes.rhtml" do
|
||||
# include ControllerHelper
|
||||
|
||||
before :each do
|
||||
@item = mock_model(Todo, :notes => "this is a note")
|
||||
@controller.template.stub!(:apply_behavior)
|
||||
end
|
||||
|
||||
it "should render" do
|
||||
render :partial => "/todos/toggle_notes", :locals => {:item => @item}
|
||||
response.should have_tag("div.todo_notes")
|
||||
end
|
||||
|
||||
it "should auto-link URLs" do
|
||||
@item.stub!(:notes).and_return("http://www.google.com/")
|
||||
render :partial => "/todos/toggle_notes", :locals => {:item => @item}
|
||||
response.should have_tag("a[href=\"http://www.google.com/\"]")
|
||||
end
|
||||
|
||||
it "should auto-link embedded URLs" do
|
||||
@item.stub!(:notes).and_return("this is cool: http://www.google.com/")
|
||||
render :partial => "/todos/toggle_notes", :locals => {:item => @item}
|
||||
response.should have_tag("a[href=\"http://www.google.com/\"]")
|
||||
end
|
||||
|
||||
it "should parse Textile URLs correctly" do
|
||||
@item.stub!(:notes).and_return("\"link\":http://www.google.com/")
|
||||
render :partial => "/todos/toggle_notes", :locals => {:item => @item}
|
||||
response.should have_tag("a[href=\"http://www.google.com/\"]")
|
||||
end
|
||||
end
|
||||
18
test/fixtures/contexts.yml
vendored
18
test/fixtures/contexts.yml
vendored
|
|
@ -113,3 +113,21 @@ someday_maybe:
|
|||
user_id: 1
|
||||
created_at: <%= today %>
|
||||
updated_at: <%= today %>
|
||||
|
||||
inbox:
|
||||
id: 13
|
||||
name: Inbox
|
||||
position: 1
|
||||
hide: false
|
||||
user_id: 4
|
||||
created_at: <%= today %>
|
||||
updated_at: <%= today %>
|
||||
|
||||
anothercontext:
|
||||
id: 14
|
||||
name: anothercontext
|
||||
position: 2
|
||||
hide: false
|
||||
user_id: 4
|
||||
created_at: <%= today %>
|
||||
updated_at: <%= today %>
|
||||
|
|
|
|||
20
test/fixtures/preferences.yml
vendored
20
test/fixtures/preferences.yml
vendored
|
|
@ -34,3 +34,23 @@ other_user_prefs:
|
|||
time_zone: "London"
|
||||
verbose_action_descriptors: false
|
||||
show_project_on_todo_done: true
|
||||
|
||||
sms_user_prefs:
|
||||
id: 3
|
||||
user_id: 4
|
||||
staleness_starts: 7
|
||||
date_format: "%d/%m/%Y"
|
||||
title_date_format: "%A, %d %B %Y"
|
||||
show_number_completed: 5
|
||||
show_completed_projects_in_sidebar: true
|
||||
show_hidden_contexts_in_sidebar: true
|
||||
show_hidden_projects_in_sidebar: true
|
||||
admin_email: butshesagirl@rousette.org.uk
|
||||
week_starts: 1
|
||||
due_style: 0
|
||||
refresh: 0
|
||||
time_zone: "London"
|
||||
verbose_action_descriptors: false
|
||||
show_project_on_todo_done: true
|
||||
sms_email: 5555555555@tmomail.net
|
||||
sms_context_id: 13
|
||||
374
test/fixtures/sample_mms.txt
vendored
Normal file
374
test/fixtures/sample_mms.txt
vendored
Normal file
|
|
@ -0,0 +1,374 @@
|
|||
Return-Path: <15555555555/TYPE=PLMN@tmomail.net>
|
||||
Date: Fri, 6 Jun 2008 21:38:26 -0400
|
||||
From: 15555555555@tmomail.net
|
||||
To: gtd@tracks.com
|
||||
Message-ID: <3645873.13759311212802713215.JavaMail.mms@rlyatl28>
|
||||
Subject: This is the subject
|
||||
MIME-Version: 1.0
|
||||
Content-Type: multipart/related; type="text/html";
|
||||
boundary="----=_Part_1240237_22156211.1212802713213"
|
||||
X-UA: treo_600
|
||||
Importance: Normal
|
||||
X-Mms-Sender-Visibility: Show
|
||||
X-MMS-Message-Type: MM4_forward.REQ
|
||||
X-Priority: 3
|
||||
X-Proofpoint-Spam-Reason: safe
|
||||
|
||||
------=_Part_1240237_22156211.1212802713213
|
||||
Content-Type: text/html
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
Content-ID: <0000>
|
||||
Content-Location:mms.smil
|
||||
Content-Disposition: inline
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>T-Mobile</title>=20
|
||||
<!--
|
||||
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
|
||||
If you can read this text, but much of the message below se=
|
||||
ems unreadable, you might be using an e-mail program that does not work wit=
|
||||
h HTML.
|
||||
=20
|
||||
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D-->
|
||||
<style type=3D"text/css">
|
||||
<!--
|
||||
=20
|
||||
.footer {
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-size: 11px;
|
||||
color: #555555;
|
||||
text-decoration: none;
|
||||
}
|
||||
.normal {
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-size: 10px;
|
||||
color: #555555;
|
||||
text-decoration: none;
|
||||
}
|
||||
=20
|
||||
-->
|
||||
</style>
|
||||
</head>
|
||||
<body marginwidth=3D"0" marginheight=3D"0" leftmargin=3D"0" topmarg=
|
||||
in=3D"0" bgcolor=3D"#ffffff">
|
||||
<table border=3D"0" width=3D"600" cellspacing=3D"0" cellpad=
|
||||
ding=3D"0">
|
||||
<tr>
|
||||
<td width=3D"20" rowspan=3D"8"><img src=3D"=
|
||||
cid:tmobilespace.gif" width=3D"20" height=3D"20"></td>
|
||||
<td width=3D"600" colspan=3D"2"><img src=3D=
|
||||
"cid:tmobilespace.gif" width=3D"600" height=3D"20"></td>
|
||||
<td width=3D"20" rowspan=3D"8"><img src=3D"=
|
||||
cid:tmobilespace.gif" width=3D"20" height=3D"20"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width=3D"600" colspan=3D"2"><img src=3D=
|
||||
"cid:dottedline600.gif" width=3D"600"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width=3D"370">
|
||||
<!-- presentation starts here -->
|
||||
<table border=3D0><tr><tr><td colspan=3D1=
|
||||
align=3D"Left">This is the message body</td></tr></tr><TR><TD width=3D350 =
|
||||
colSpan=3D1><IMG height=3D30 src=3D"cid:tmobilespace.gif" width=3D350></TD=
|
||||
></TR><TR><TD width=3D350 colSpan=3D4><IMG src=3D"cid:dottedline350.gif" w=
|
||||
idth=3D350></TD></TR><TR><TR><TD width=3D350 colSpan=3D4><IMG height=3D30 s=
|
||||
rc=3D"cid:tmobilespace.gif" width=3D350></TD></TR></table> =20
|
||||
<!-- presentation ends here -->
|
||||
</td>
|
||||
<td width=3D"240" bgcolor=3D"#f2f2f2"><BR><=
|
||||
/td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width=3D"600" colspan=3D"2"><img src=3D=
|
||||
"cid:tmobilelogo.gif" width=3D"600" height=3D"105"></td>
|
||||
</tr>
|
||||
</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width=3D"600" colspan=3D"2"><img src=3D=
|
||||
"cid:tmobilespace.gif" width=3D"600" height=3D"40"></td>
|
||||
</tr>
|
||||
</table>
|
||||
</body></html>
|
||||
|
||||
|
||||
------=_Part_1240237_22156211.1212802713213
|
||||
Content-Type: text/plain; charset=utf-8; name=text.txt
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Content-ID: <133>
|
||||
Content-Location: text.txt
|
||||
Content-Disposition: inline
|
||||
|
||||
This is the message body
|
||||
------=_Part_1240237_22156211.1212802713213
|
||||
Content-Type: image/gif; name=dottedline350.gif
|
||||
Content-Transfer-Encoding: base64
|
||||
Content-Disposition: attachment; filename=dottedline350.gif
|
||||
Content-ID: <dottedline350.gif>
|
||||
|
||||
R0lGODlhXgEBAPcAAAAAAIAAAACAAICAAAAAgIAAgACAgICAgMDAwP8AAAD/AP//AAAA//8A/wD/
|
||||
/////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMwAAZgAAmQAAzAAA/wAzAAAzMwAzZgAzmQAzzAAz/wBm
|
||||
AABmMwBmZgBmmQBmzABm/wCZAACZMwCZZgCZmQCZzACZ/wDMAADMMwDMZgDMmQDMzADM/wD/AAD/
|
||||
MwD/ZgD/mQD/zAD//zMAADMAMzMAZjMAmTMAzDMA/zMzADMzMzMzZjMzmTMzzDMz/zNmADNmMzNm
|
||||
ZjNmmTNmzDNm/zOZADOZMzOZZjOZmTOZzDOZ/zPMADPMMzPMZjPMmTPMzDPM/zP/ADP/MzP/ZjP/
|
||||
mTP/zDP//2YAAGYAM2YAZmYAmWYAzGYA/2YzAGYzM2YzZmYzmWYzzGYz/2ZmAGZmM2ZmZmZmmWZm
|
||||
zGZm/2aZAGaZM2aZZmaZmWaZzGaZ/2bMAGbMM2bMZmbMmWbMzGbM/2b/AGb/M2b/Zmb/mWb/zGb/
|
||||
/5kAAJkAM5kAZpkAmZkAzJkA/5kzAJkzM5kzZpkzmZkzzJkz/5lmAJlmM5lmZplmmZlmzJlm/5mZ
|
||||
AJmZM5mZZpmZmZmZzJmZ/5nMAJnMM5nMZpnMmZnMzJnM/5n/AJn/M5n/Zpn/mZn/zJn//8wAAMwA
|
||||
M8wAZswAmcwAzMwA/8wzAMwzM8wzZswzmcwzzMwz/8xmAMxmM8xmZsxmmcxmzMxm/8yZAMyZM8yZ
|
||||
ZsyZmcyZzMyZ/8zMAMzMM8zMZszMmczMzMzM/8z/AMz/M8z/Zsz/mcz/zMz///8AAP8AM/8AZv8A
|
||||
mf8AzP8A//8zAP8zM/8zZv8zmf8zzP8z//9mAP9mM/9mZv9mmf9mzP9m//+ZAP+ZM/+ZZv+Zmf+Z
|
||||
zP+Z///MAP/MM//MZv/Mmf/MzP/M////AP//M///Zv//mf//zP///yH5BAEAABAALAAAAABeAQEA
|
||||
AAg1AFP9+yeQ4MCCCA8qNMgwYcOFDiNCnPiwokSLFC9qzMgRo8eNHzuCHCmyZMiTJFGaTMlSYUAA
|
||||
Ow==
|
||||
------=_Part_1240237_22156211.1212802713213
|
||||
Content-Type: image/gif; name=dottedline600.gif
|
||||
Content-Transfer-Encoding: base64
|
||||
Content-Disposition: attachment; filename=dottedline600.gif
|
||||
Content-ID: <dottedline600.gif>
|
||||
|
||||
R0lGODlhWAIBAPcAAAAAAIAAAACAAICAAAAAgIAAgACAgICAgMDAwP8AAAD/AP//AAAA//8A/wD/
|
||||
/////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMwAAZgAAmQAAzAAA/wAzAAAzMwAzZgAzmQAzzAAz/wBm
|
||||
AABmMwBmZgBmmQBmzABm/wCZAACZMwCZZgCZmQCZzACZ/wDMAADMMwDMZgDMmQDMzADM/wD/AAD/
|
||||
MwD/ZgD/mQD/zAD//zMAADMAMzMAZjMAmTMAzDMA/zMzADMzMzMzZjMzmTMzzDMz/zNmADNmMzNm
|
||||
ZjNmmTNmzDNm/zOZADOZMzOZZjOZmTOZzDOZ/zPMADPMMzPMZjPMmTPMzDPM/zP/ADP/MzP/ZjP/
|
||||
mTP/zDP//2YAAGYAM2YAZmYAmWYAzGYA/2YzAGYzM2YzZmYzmWYzzGYz/2ZmAGZmM2ZmZmZmmWZm
|
||||
zGZm/2aZAGaZM2aZZmaZmWaZzGaZ/2bMAGbMM2bMZmbMmWbMzGbM/2b/AGb/M2b/Zmb/mWb/zGb/
|
||||
/5kAAJkAM5kAZpkAmZkAzJkA/5kzAJkzM5kzZpkzmZkzzJkz/5lmAJlmM5lmZplmmZlmzJlm/5mZ
|
||||
AJmZM5mZZpmZmZmZzJmZ/5nMAJnMM5nMZpnMmZnMzJnM/5n/AJn/M5n/Zpn/mZn/zJn//8wAAMwA
|
||||
M8wAZswAmcwAzMwA/8wzAMwzM8wzZswzmcwzzMwz/8xmAMxmM8xmZsxmmcxmzMxm/8yZAMyZM8yZ
|
||||
ZsyZmcyZzMyZ/8zMAMzMM8zMZszMmczMzMzM/8z/AMz/M8z/Zsz/mcz/zMz///8AAP8AM/8AZv8A
|
||||
mf8AzP8A//8zAP8zM/8zZv8zmf8zzP8z//9mAP9mM/9mZv9mmf9mzP9m//+ZAP+ZM/+ZZv+Zmf+Z
|
||||
zP+Z///MAP/MM//MZv/Mmf/MzP/M////AP//M///Zv//mf//zP///yH5BAEAABAALAAAAABYAgEA
|
||||
AAhFAFP9+yeQ4MCCCA8qNMgwYcOFDiNCnPiwokSLFC9qzMgRo8eNHzuCHCmyZMiTJFGaTMlypUuV
|
||||
MFvGfCmzJs2bM3Pa9BgQADs=
|
||||
------=_Part_1240237_22156211.1212802713213
|
||||
Content-Type: image/gif; name=tmobilelogo.gif
|
||||
Content-Transfer-Encoding: base64
|
||||
Content-Disposition: attachment; filename=tmobilelogo.gif
|
||||
Content-ID: <tmobilelogo.gif>
|
||||
|
||||
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
|
||||
d3Pf9lg/d3Vf94INCAA7
|
||||
------=_Part_1240237_22156211.1212802713213
|
||||
Content-Type: image/gif; name=tmobilespace.gif
|
||||
Content-Transfer-Encoding: base64
|
||||
Content-Disposition: attachment; filename=tmobilespace.gif
|
||||
Content-ID: <tmobilespace.gif>
|
||||
|
||||
R0lGODlhAQABAPcAAAAAAIAAAACAAICAAAAAgIAAgACAgMDAwMDcwKbK8AAAMzMAADMAMwAzMxYW
|
||||
FhwcHCIiIikpKVVVVU1NTUJCQjk5Of98gP9QUNYAk8zs/+/Wxufn1q2pkDP/AGYAAJkAAMwAAAAz
|
||||
ADMzAGYzAJkzAMwzAP8zAABmADNmAGZmAJlmAMxmAP9mAACZADOZAGaZAJmZAMyZAP+ZAADMADPM
|
||||
AGbMAJnMAMzMAP/MAGb/AJn/AMz/AAD/MzMA/2YAM5kAM8wAM/8AMwAz/zMzM2YzM5kzM8wzM/8z
|
||||
MwBmMzNmM2ZmM5lmM8xmM/9mMwCZMzOZM2aZM5mZM8yZM/+ZMwDMMzPMM2bMM5nMM8zMM//MMzP/
|
||||
M2b/M5n/M8z/M///MwAAZjMAZmYAZpkAZswAZv8AZgAzZjMzZmYzZpkzZswzZv8zZgBmZjNmZmZm
|
||||
ZplmZsxmZgCZZjOZZmaZZpmZZsyZZv+ZZgDMZjPMZpnMZszMZv/MZgD/ZjP/Zpn/Zsz/Zv8AzMwA
|
||||
/wCZmZkzmZkAmcwAmQAAmTMzmWYAmcwzmf8AmQBmmTNmmWYzmZlmmcxmmf8zmTOZmWaZmZmZmcyZ
|
||||
mf+ZmQDMmTPMmWbMZpnMmczMmf/MmQD/mTP/mWbMmZn/mcz/mf//mQAAzDMAmWYAzJkAzMwAzAAz
|
||||
mTMzzGYzzJkzzMwzzP8zzABmzDNmzGZmmZlmzMxmzP9mmQCZzDOZzGaZzJmZzMyZzP+ZzADMzDPM
|
||||
zGbMzJnMzMzMzP/MzAD/zDP/zGb/mZn/zMz/zP//zDMAzGYA/5kA/wAzzDMz/2Yz/5kz/8wz//8z
|
||||
/wBm/zNm/2ZmzJlm/8xm//9mzACZ/zOZ/2aZ/5mZ/8yZ//+Z/wDM/zPM/2bM/5nM/8zM///M/zP/
|
||||
/2b/zJn//8z///9mZmb/Zv//ZmZm//9m/2b//6UAIV9fX3d3d4aGhpaWlsvLy7KystfX193d3ePj
|
||||
4+rq6vHx8fj4+P/78KCgpICAgP8AAAD/AP//AAAA//8A/wD//////ywAAAAAAQABAAAIBAD/BQQA
|
||||
Ow==
|
||||
------=_Part_1240237_22156211.1212802713213--
|
||||
12
test/fixtures/sample_sms.txt
vendored
Normal file
12
test/fixtures/sample_sms.txt
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
Return-Path: <5555555555@tmomail.net>
|
||||
Date: Tue, 3 Jun 2008 23:11:26 -0400
|
||||
From: 5555555555@tmomail.net
|
||||
To: gtd@tracks.com
|
||||
Message-ID: <6100602.65827251212549086388.JavaMail.imb@mgwatl02.cns.mms.com>
|
||||
Subject:
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain;charset=utf-8
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Importance: Normal
|
||||
|
||||
message_content
|
||||
16
test/fixtures/todos.yml
vendored
16
test/fixtures/todos.yml
vendored
|
|
@ -95,7 +95,7 @@ end
|
|||
completed_at: ~
|
||||
user_id: 1
|
||||
|
||||
7:
|
||||
book:
|
||||
id: 7
|
||||
context_id: 6
|
||||
project_id: 3
|
||||
|
|
@ -230,3 +230,17 @@ end
|
|||
completed_at: ~
|
||||
show_from: <%= next_week %>
|
||||
user_id: 2
|
||||
|
||||
18:
|
||||
id: 18
|
||||
user_id: 1
|
||||
context_id: 1
|
||||
project_id: 2
|
||||
description: Call Bill Gates every day
|
||||
notes: ~
|
||||
state: active
|
||||
created_at: <%= last_week %>
|
||||
due: <%= last_week %>
|
||||
completed_at: ~
|
||||
show_from: ~
|
||||
recurring_todo_id: 1
|
||||
10
test/fixtures/users.yml
vendored
10
test/fixtures/users.yml
vendored
|
|
@ -28,3 +28,13 @@ ldap_user:
|
|||
first_name: John
|
||||
last_name: Deere
|
||||
auth_type: ldap
|
||||
|
||||
sms_user:
|
||||
id: 4
|
||||
login: sms_user
|
||||
crypted_password: <%= Digest::SHA1.hexdigest("#{Tracks::Config.salt}--sesame--") %>
|
||||
token: <%= Digest::SHA1.hexdigest("sms_userSun Feb 19 14:42:45 GMT 20060.408173979260027") %>
|
||||
is_admin: false
|
||||
first_name: SMS
|
||||
last_name: Tester
|
||||
auth_type: database
|
||||
|
|
@ -25,7 +25,7 @@ class ProjectsControllerTest < TodoContainerControllerTestBase
|
|||
assert_equal 1, assigns['deferred'].size
|
||||
|
||||
t = p.not_done_todos[0]
|
||||
t.show_from = 1.days.from_now.utc.to_date
|
||||
t.show_from = 1.days.from_now.utc
|
||||
t.save!
|
||||
|
||||
get :show, :id => p.to_param
|
||||
|
|
@ -218,6 +218,15 @@ class ProjectsControllerTest < TodoContainerControllerTestBase
|
|||
get :index, { :format => "txt", :token => users(:admin_user).token }
|
||||
assert_response :ok
|
||||
end
|
||||
|
||||
def test_actionize_sorts_active_projects_by_number_of_tasks
|
||||
login_as :admin_user
|
||||
u = users(:admin_user)
|
||||
post :actionize, :state => "active", :format => 'js'
|
||||
assert_equal 1, projects(:gardenclean).position
|
||||
assert_equal 2, projects(:moremoney).position
|
||||
assert_equal 3, projects(:timemachine).position
|
||||
end
|
||||
|
||||
def test_alphabetize_sorts_active_projects_alphabetically
|
||||
login_as :admin_user
|
||||
|
|
|
|||
|
|
@ -17,6 +17,167 @@ class RecurringTodosControllerTest < ActionController::TestCase
|
|||
login_as(:admin_user)
|
||||
xhr :post, :destroy, :id => 1, :_source_view => 'todo'
|
||||
assert_rjs :page, "recurring_todo_1", :remove
|
||||
begin
|
||||
rc = RecurringTodo.find(1)
|
||||
rescue
|
||||
rc = nil
|
||||
end
|
||||
assert_nil rc
|
||||
end
|
||||
|
||||
def test_new_recurring_todo
|
||||
login_as(:admin_user)
|
||||
orig_rt_count = RecurringTodo.count
|
||||
orig_todo_count = Todo.count
|
||||
put :create,
|
||||
"context_name"=>"library",
|
||||
"project_name"=>"Build a working time machine",
|
||||
"recurring_todo" =>
|
||||
{
|
||||
"daily_every_x_days"=>"1",
|
||||
"daily_selector"=>"daily_every_x_day",
|
||||
"description"=>"new recurring pattern",
|
||||
"end_date" => "31/08/2010",
|
||||
"ends_on" => "ends_on_end_date",
|
||||
"monthly_day_of_week" => "1",
|
||||
"monthly_every_x_day" => "18",
|
||||
"monthly_every_x_month2" => "1",
|
||||
"monthly_every_x_month" => "1",
|
||||
"monthly_every_xth_day"=>"1",
|
||||
"monthly_selector"=>"monthly_every_x_day",
|
||||
"notes"=>"with some notes",
|
||||
"number_of_occurences" => "",
|
||||
"recurring_period"=>"yearly",
|
||||
"recurring_show_days_before"=>"10",
|
||||
"recurring_target"=>"due_date",
|
||||
"start_from"=>"18/08/2008",
|
||||
"weekly_every_x_week"=>"1",
|
||||
"weekly_return_monday"=>"m",
|
||||
"yearly_day_of_week"=>"1",
|
||||
"yearly_every_x_day"=>"8",
|
||||
"yearly_every_xth_day"=>"1",
|
||||
"yearly_month_of_year2"=>"8",
|
||||
"yearly_month_of_year"=>"6",
|
||||
"yearly_selector"=>"yearly_every_x_day"
|
||||
},
|
||||
"tag_list"=>"one, two, three, four"
|
||||
|
||||
# check new recurring todo added
|
||||
assert_equal orig_rt_count+1, RecurringTodo.count
|
||||
# check new todo added
|
||||
assert_equal orig_todo_count+1, Todo.count
|
||||
end
|
||||
|
||||
def test_recurring_todo_toggle_check
|
||||
# the test fixtures did add recurring_todos but not the corresponding todos,
|
||||
# so we check complete and uncheck to force creation of a todo from the
|
||||
# pattern
|
||||
login_as(:admin_user)
|
||||
|
||||
# mark as complete
|
||||
xhr :post, :toggle_check, :id=>1, :_source_view=>""
|
||||
recurring_todo_1 = RecurringTodo.find(1)
|
||||
assert recurring_todo_1.completed?
|
||||
|
||||
# remove remaining todo
|
||||
todo = Todo.find_by_recurring_todo_id(1)
|
||||
todo.recurring_todo_id = 2
|
||||
todo.save
|
||||
|
||||
todo_count = Todo.count
|
||||
|
||||
# mark as active
|
||||
xhr :post, :toggle_check, :id=>1, :_source_view=>""
|
||||
recurring_todo_1.reload
|
||||
assert recurring_todo_1.active?
|
||||
|
||||
# by making active, a new todo should be created from the pattern
|
||||
assert_equal todo_count+1, Todo.count
|
||||
|
||||
# find the new todo and check its description
|
||||
new_todo = Todo.find_by_recurring_todo_id 1
|
||||
assert_equal "Call Bill Gates every day", new_todo.description
|
||||
end
|
||||
|
||||
def test_creating_recurring_todo_with_show_from_in_past
|
||||
login_as(:admin_user)
|
||||
|
||||
@yearly = RecurringTodo.find(5) # yearly on june 8th
|
||||
|
||||
# change due date in four days from now and show from 10 days before, i.e. 6
|
||||
# days ago
|
||||
target_date = Time.now.utc + 4.days
|
||||
@yearly.every_other1 = target_date.day
|
||||
@yearly.every_other2 = target_date.month
|
||||
@yearly.show_from_delta = 10
|
||||
assert @yearly.save
|
||||
|
||||
# toggle twice to force generation of new todo
|
||||
xhr :post, :toggle_check, :id=>5, :_source_view=>""
|
||||
xhr :post, :toggle_check, :id=>5, :_source_view=>""
|
||||
|
||||
new_todo = Todo.find_by_recurring_todo_id 5
|
||||
|
||||
# due date should be the target_date
|
||||
assert_equal users(:admin_user).at_midnight(Date.new(target_date.year, target_date.month, target_date.day)), new_todo.due
|
||||
|
||||
# show_from should be nil since now+4.days-10.days is in the past
|
||||
assert_equal nil, new_todo.show_from
|
||||
end
|
||||
|
||||
def test_last_sunday_of_march
|
||||
# this test is a duplicate of the unit test. Only this test covers the
|
||||
# codepath in the controllers
|
||||
|
||||
login_as(:admin_user)
|
||||
|
||||
orig_rt_count = RecurringTodo.count
|
||||
orig_todo_count = Todo.count
|
||||
|
||||
put :create,
|
||||
"context_name"=>"library",
|
||||
"project_name"=>"Build a working time machine",
|
||||
"recurring_todo" =>
|
||||
{
|
||||
"daily_every_x_days"=>"1",
|
||||
"daily_selector"=>"daily_every_x_day",
|
||||
"description"=>"new recurring pattern",
|
||||
"end_date" => "",
|
||||
"ends_on" => "no_end_date",
|
||||
"monthly_day_of_week" => "1",
|
||||
"monthly_every_x_day" => "22",
|
||||
"monthly_every_x_month2" => "1",
|
||||
"monthly_every_x_month" => "1",
|
||||
"monthly_every_xth_day"=>"1",
|
||||
"monthly_selector"=>"monthly_every_x_day",
|
||||
"notes"=>"with some notes",
|
||||
"number_of_occurences" => "",
|
||||
"recurring_period"=>"yearly",
|
||||
"recurring_show_days_before"=>"0",
|
||||
"recurring_target"=>"due_date",
|
||||
"start_from"=>"",
|
||||
"weekly_every_x_week"=>"1",
|
||||
"weekly_return_monday"=>"w",
|
||||
"yearly_day_of_week"=>"0",
|
||||
"yearly_every_x_day"=>"22",
|
||||
"yearly_every_xth_day"=>"5",
|
||||
"yearly_month_of_year2"=>"3",
|
||||
"yearly_month_of_year"=>"10",
|
||||
"yearly_selector"=>"yearly_every_xth_day"
|
||||
},
|
||||
"tag_list"=>"one, two, three, four"
|
||||
|
||||
# check new recurring todo added
|
||||
assert_equal orig_rt_count+1, RecurringTodo.count
|
||||
# check new todo added
|
||||
assert_equal orig_todo_count+1, Todo.count
|
||||
|
||||
# find the newly created todo
|
||||
new_todo = Todo.find_by_description("new recurring pattern")
|
||||
assert !new_todo.nil?
|
||||
|
||||
# the date should be 29 march 2009
|
||||
assert_equal Time.zone.local(2009,3,29), new_todo.due
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ class StatsControllerTest < Test::Unit::TestCase
|
|||
assert_equal 3, assigns['projects'].count
|
||||
assert_equal 3, assigns['projects'].count(:conditions => "state = 'active'")
|
||||
assert_equal 10, assigns['contexts'].count
|
||||
assert_equal 15, assigns['actions'].count
|
||||
assert_equal 16, assigns['actions'].count
|
||||
assert_equal 4, assigns['tags'].count
|
||||
assert_equal 2, assigns['unique_tags'].size
|
||||
assert_equal 2.week.ago.utc.beginning_of_day, assigns['first_action'].created_at
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ require 'todos_controller'
|
|||
class TodosController; def rescue_action(e) raise e end; end
|
||||
|
||||
class TodosControllerTest < Test::Rails::TestCase
|
||||
fixtures :users, :preferences, :projects, :contexts, :todos, :tags, :taggings
|
||||
fixtures :users, :preferences, :projects, :contexts, :todos, :tags, :taggings, :recurring_todos
|
||||
|
||||
def setup
|
||||
@controller = TodosController.new
|
||||
|
|
@ -70,7 +70,7 @@ class TodosControllerTest < Test::Rails::TestCase
|
|||
login_as(:admin_user)
|
||||
xhr :post, :destroy, :id => 1, :_source_view => 'todo'
|
||||
assert_rjs :page, "todo_1", :remove
|
||||
#assert_rjs :replace_html, "badge-count", '9'
|
||||
# #assert_rjs :replace_html, "badge-count", '9'
|
||||
end
|
||||
|
||||
def test_create_todo
|
||||
|
|
@ -90,11 +90,11 @@ class TodosControllerTest < Test::Rails::TestCase
|
|||
|
||||
def test_fail_to_create_todo_via_xml
|
||||
login_as(:admin_user)
|
||||
#try to create with no context, which is not valid
|
||||
# #try to create with no context, which is not valid
|
||||
put :create, :format => "xml", "request" => { "project_name"=>"Build a working time machine", "todo"=>{"notes"=>"", "description"=>"Call Warren Buffet to find out how much he makes per day", "due"=>"30/11/2006"}, "tag_list"=>"foo bar" }
|
||||
assert_response 422
|
||||
assert_xml_select "errors" do
|
||||
assert_xml_select "error", "Context can't be blank"
|
||||
assert_xml_select "error", "Context can't be blank"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -124,9 +124,9 @@ class TodosControllerTest < Test::Rails::TestCase
|
|||
def test_update_todo_to_deferred_is_reflected_in_badge_count
|
||||
login_as(:admin_user)
|
||||
get :index
|
||||
assert_equal 10, assigns['count']
|
||||
assert_equal 11, assigns['count']
|
||||
xhr :post, :update, :id => 1, :_source_view => 'todo', "context_name"=>"library", "project_name"=>"Make more money than Billy Gates", "todo"=>{"id"=>"1", "notes"=>"", "description"=>"Call Warren Buffet to find out how much he makes per day", "due"=>"30/11/2006", "show_from"=>"30/11/2030"}, "tag_list"=>"foo bar"
|
||||
assert_equal 9, assigns['down_count']
|
||||
assert_equal 10, assigns['down_count']
|
||||
end
|
||||
|
||||
def test_update_todo
|
||||
|
|
@ -137,7 +137,7 @@ class TodosControllerTest < Test::Rails::TestCase
|
|||
assert_equal "Call Warren Buffet to find out how much he makes per day", t.description
|
||||
assert_equal "foo, bar", t.tag_list
|
||||
expected = Date.new(2006,11,30)
|
||||
actual = t.due
|
||||
actual = t.due.to_date
|
||||
assert_equal expected, actual, "Expected #{expected.to_s(:db)}, was #{actual.to_s(:db)}"
|
||||
end
|
||||
|
||||
|
|
@ -180,7 +180,7 @@ class TodosControllerTest < Test::Rails::TestCase
|
|||
login_as(:admin_user)
|
||||
get :index, { :format => "rss" }
|
||||
assert_equal 'application/rss+xml', @response.content_type
|
||||
#puts @response.body
|
||||
# puts @response.body
|
||||
|
||||
assert_xml_select 'rss[version="2.0"]' do
|
||||
assert_select 'channel' do
|
||||
|
|
@ -188,12 +188,12 @@ class TodosControllerTest < Test::Rails::TestCase
|
|||
assert_select '>description', "Actions for #{users(:admin_user).display_name}"
|
||||
assert_select 'language', 'en-us'
|
||||
assert_select 'ttl', '40'
|
||||
assert_select 'item', 10 do
|
||||
assert_select 'item', 11 do
|
||||
assert_select 'title', /.+/
|
||||
assert_select 'description', /.*/
|
||||
assert_select 'link', %r{http://test.host/contexts/.+}
|
||||
assert_select 'guid', %r{http://test.host/todos/.+}
|
||||
assert_select 'pubDate', projects(:timemachine).updated_at.to_s(:rfc822)
|
||||
assert_select 'pubDate', todos(:book).updated_at.to_s(:rfc822)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -237,15 +237,15 @@ class TodosControllerTest < Test::Rails::TestCase
|
|||
login_as :admin_user
|
||||
get :index, { :format => "atom" }
|
||||
assert_equal 'application/atom+xml', @response.content_type
|
||||
#puts @response.body
|
||||
# #puts @response.body
|
||||
|
||||
assert_xml_select 'feed[xmlns="http://www.w3.org/2005/Atom"]' do
|
||||
assert_xml_select '>title', 'Tracks Actions'
|
||||
assert_xml_select '>subtitle', "Actions for #{users(:admin_user).display_name}"
|
||||
assert_xml_select 'entry', 10 do
|
||||
assert_xml_select 'entry', 11 do
|
||||
assert_xml_select 'title', /.+/
|
||||
assert_xml_select 'content[type="html"]', /.*/
|
||||
assert_xml_select 'published', /(#{Regexp.escape(projects(:timemachine).updated_at.xmlschema)}|#{Regexp.escape(projects(:moremoney).updated_at.xmlschema)})/
|
||||
assert_xml_select 'published', /(#{Regexp.escape(todos(:book).updated_at.xmlschema)}|#{Regexp.escape(projects(:moremoney).updated_at.xmlschema)})/
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -273,7 +273,7 @@ class TodosControllerTest < Test::Rails::TestCase
|
|||
get :index, { :format => "txt" }
|
||||
assert_equal 'text/plain', @response.content_type
|
||||
assert !(/ /.match(@response.body))
|
||||
#puts @response.body
|
||||
# #puts @response.body
|
||||
end
|
||||
|
||||
def test_text_feed_not_accessible_to_anonymous_user_without_token
|
||||
|
|
@ -299,7 +299,7 @@ class TodosControllerTest < Test::Rails::TestCase
|
|||
get :index, { :format => "ics" }
|
||||
assert_equal 'text/calendar', @response.content_type
|
||||
assert !(/ /.match(@response.body))
|
||||
#puts @response.body
|
||||
# #puts @response.body
|
||||
end
|
||||
|
||||
def test_mobile_index_uses_text_html_content_type
|
||||
|
|
@ -311,16 +311,16 @@ class TodosControllerTest < Test::Rails::TestCase
|
|||
def test_mobile_index_assigns_down_count
|
||||
login_as(:admin_user)
|
||||
get :index, { :format => "m" }
|
||||
assert_equal 10, assigns['down_count']
|
||||
assert_equal 11, assigns['down_count']
|
||||
end
|
||||
|
||||
def test_mobile_create_action_creates_a_new_todo
|
||||
login_as(:admin_user)
|
||||
post :create, {"format"=>"m", "todo"=>{"context_id"=>"2",
|
||||
"due(1i)"=>"2007", "due(2i)"=>"1", "due(3i)"=>"2",
|
||||
"show_from(1i)"=>"", "show_from(2i)"=>"", "show_from(3i)"=>"",
|
||||
"project_id"=>"1",
|
||||
"notes"=>"test notes", "description"=>"test_mobile_create_action", "state"=>"0"}}
|
||||
"due(1i)"=>"2007", "due(2i)"=>"1", "due(3i)"=>"2",
|
||||
"show_from(1i)"=>"", "show_from(2i)"=>"", "show_from(3i)"=>"",
|
||||
"project_id"=>"1",
|
||||
"notes"=>"test notes", "description"=>"test_mobile_create_action", "state"=>"0"}}
|
||||
t = Todo.find_by_description("test_mobile_create_action")
|
||||
assert_not_nil t
|
||||
assert_equal 2, t.context_id
|
||||
|
|
@ -328,26 +328,26 @@ class TodosControllerTest < Test::Rails::TestCase
|
|||
assert t.active?
|
||||
assert_equal 'test notes', t.notes
|
||||
assert_nil t.show_from
|
||||
assert_equal Date.new(2007,1,2).to_s, t.due.to_s
|
||||
assert_equal Date.new(2007,1,2), t.due.to_date
|
||||
end
|
||||
|
||||
def test_mobile_create_action_redirects_to_mobile_home_page_when_successful
|
||||
login_as(:admin_user)
|
||||
post :create, {"format"=>"m", "todo"=>{"context_id"=>"2",
|
||||
"due(1i)"=>"2007", "due(2i)"=>"1", "due(3i)"=>"2",
|
||||
"show_from(1i)"=>"", "show_from(2i)"=>"", "show_from(3i)"=>"",
|
||||
"project_id"=>"1",
|
||||
"notes"=>"test notes", "description"=>"test_mobile_create_action", "state"=>"0"}}
|
||||
"due(1i)"=>"2007", "due(2i)"=>"1", "due(3i)"=>"2",
|
||||
"show_from(1i)"=>"", "show_from(2i)"=>"", "show_from(3i)"=>"",
|
||||
"project_id"=>"1",
|
||||
"notes"=>"test notes", "description"=>"test_mobile_create_action", "state"=>"0"}}
|
||||
assert_redirected_to '/m'
|
||||
end
|
||||
|
||||
def test_mobile_create_action_renders_new_template_when_save_fails
|
||||
login_as(:admin_user)
|
||||
post :create, {"format"=>"m", "todo"=>{"context_id"=>"2",
|
||||
"due(1i)"=>"2007", "due(2i)"=>"1", "due(3i)"=>"2",
|
||||
"show_from(1i)"=>"", "show_from(2i)"=>"", "show_from(3i)"=>"",
|
||||
"project_id"=>"1",
|
||||
"notes"=>"test notes", "state"=>"0"}, "tag_list"=>"test, test2"}
|
||||
"due(1i)"=>"2007", "due(2i)"=>"1", "due(3i)"=>"2",
|
||||
"show_from(1i)"=>"", "show_from(2i)"=>"", "show_from(3i)"=>"",
|
||||
"project_id"=>"1",
|
||||
"notes"=>"test notes", "state"=>"0"}, "tag_list"=>"test, test2"}
|
||||
assert_template 'todos/new'
|
||||
end
|
||||
|
||||
|
|
@ -357,4 +357,132 @@ class TodosControllerTest < Test::Rails::TestCase
|
|||
assert_equal '"{\\"Build a working time machine\\": \\"lab\\"}"', assigns(:default_project_context_name_map)
|
||||
end
|
||||
|
||||
def test_toggle_check_on_recurring_todo
|
||||
login_as(:admin_user)
|
||||
|
||||
# link todo_1 and recurring_todo_1
|
||||
recurring_todo_1 = RecurringTodo.find(1)
|
||||
todo_1 = Todo.find_by_recurring_todo_id(1)
|
||||
|
||||
# mark todo_1 as complete by toggle_check
|
||||
xhr :post, :toggle_check, :id => todo_1.id, :_source_view => 'todo'
|
||||
todo_1.reload
|
||||
assert todo_1.completed?
|
||||
|
||||
# check that there is only one active todo belonging to recurring_todo
|
||||
count = Todo.count(:all, :conditions => {:recurring_todo_id => recurring_todo_1.id, :state => 'active'})
|
||||
assert_equal 1, count
|
||||
|
||||
# check there is a new todo linked to the recurring pattern
|
||||
next_todo = Todo.find(:first, :conditions => {:recurring_todo_id => recurring_todo_1.id, :state => 'active'})
|
||||
assert_equal "Call Bill Gates every day", next_todo.description
|
||||
# check that the new todo is not the same as todo_1
|
||||
assert_not_equal todo_1.id, next_todo.id
|
||||
|
||||
# change recurrence pattern to monthly and set show_from 2 days before due
|
||||
# date this forces the next todo to be put in the tickler
|
||||
recurring_todo_1.show_from_delta = 2
|
||||
recurring_todo_1.recurring_period = 'monthly'
|
||||
recurring_todo_1.recurrence_selector = 0
|
||||
recurring_todo_1.every_other1 = 1
|
||||
recurring_todo_1.every_other2 = 2
|
||||
recurring_todo_1.every_other3 = 5
|
||||
recurring_todo_1.save
|
||||
|
||||
# mark next_todo as complete by toggle_check
|
||||
xhr :post, :toggle_check, :id => next_todo.id, :_source_view => 'todo'
|
||||
next_todo.reload
|
||||
assert next_todo.completed?
|
||||
|
||||
# check that there are three todos belonging to recurring_todo: two
|
||||
# completed and one deferred
|
||||
count = Todo.count(:all, :conditions => {:recurring_todo_id => recurring_todo_1.id})
|
||||
assert_equal 3, count
|
||||
|
||||
# check there is a new todo linked to the recurring pattern in the tickler
|
||||
next_todo = Todo.find(:first, :conditions => {:recurring_todo_id => recurring_todo_1.id, :state => 'deferred'})
|
||||
assert !next_todo.nil?
|
||||
assert_equal "Call Bill Gates every day", next_todo.description
|
||||
# check that the todo is in the tickler
|
||||
assert !next_todo.show_from.nil?
|
||||
end
|
||||
|
||||
def test_toggle_check_on_rec_todo_show_from_today
|
||||
login_as(:admin_user)
|
||||
|
||||
# link todo_1 and recurring_todo_1
|
||||
recurring_todo_1 = RecurringTodo.find(1)
|
||||
todo_1 = Todo.find_by_recurring_todo_id(1)
|
||||
today = Time.now.utc.at_midnight
|
||||
|
||||
# change recurrence pattern to monthly and set show_from to today
|
||||
recurring_todo_1.target = 'show_from_date'
|
||||
recurring_todo_1.recurring_period = 'monthly'
|
||||
recurring_todo_1.recurrence_selector = 0
|
||||
recurring_todo_1.every_other1 = today.day
|
||||
recurring_todo_1.every_other2 = 1
|
||||
recurring_todo_1.save
|
||||
|
||||
# mark todo_1 as complete by toggle_check, this gets rid of todo_1 that was
|
||||
# not correctly created from the adjusted recurring pattern we defined
|
||||
# above.
|
||||
xhr :post, :toggle_check, :id => todo_1.id, :_source_view => 'todo'
|
||||
todo_1.reload
|
||||
assert todo_1.completed?
|
||||
|
||||
# locate the new todo. This todo is created from the adjusted recurring
|
||||
# pattern defined in this test
|
||||
new_todo = Todo.find(:first, :conditions => {:recurring_todo_id => recurring_todo_1.id, :state => 'active'})
|
||||
assert !new_todo.nil?
|
||||
|
||||
# mark new_todo as complete by toggle_check
|
||||
xhr :post, :toggle_check, :id => new_todo.id, :_source_view => 'todo'
|
||||
new_todo.reload
|
||||
assert todo_1.completed?
|
||||
|
||||
# locate the new todo in tickler
|
||||
new_todo = Todo.find(:first, :conditions => {:recurring_todo_id => recurring_todo_1.id, :state => 'deferred'})
|
||||
assert !new_todo.nil?
|
||||
|
||||
assert_equal "Call Bill Gates every day", new_todo.description
|
||||
# check that the new todo is not the same as todo_1
|
||||
assert_not_equal todo_1.id, new_todo.id
|
||||
|
||||
# check that the new_todo is in the tickler to show next month
|
||||
assert !new_todo.show_from.nil?
|
||||
assert_equal Time.utc(today.year, today.month+1, today.day), new_todo.show_from
|
||||
end
|
||||
|
||||
def test_check_for_next_todo
|
||||
login_as :admin_user
|
||||
|
||||
recurring_todo_1 = RecurringTodo.find(5)
|
||||
@todo = Todo.find_by_recurring_todo_id(1)
|
||||
assert @todo.from_recurring_todo?
|
||||
# rewire @todo to yearly recurring todo
|
||||
@todo.recurring_todo_id = 5
|
||||
|
||||
# make todo due tomorrow and change recurring date also to tomorrow
|
||||
@todo.due = Time.zone.now + 1.day
|
||||
@todo.save
|
||||
recurring_todo_1.every_other1 = @todo.due.day
|
||||
recurring_todo_1.every_other2 = @todo.due.month
|
||||
recurring_todo_1.save
|
||||
|
||||
# mark todo complete
|
||||
xhr :post, :toggle_check, :id => @todo.id, :_source_view => 'todo'
|
||||
@todo.reload
|
||||
assert @todo.completed?
|
||||
|
||||
# check that there is no active todo
|
||||
next_todo = Todo.find(:first, :conditions => {:recurring_todo_id => recurring_todo_1.id, :state => 'active'})
|
||||
assert next_todo.nil?
|
||||
|
||||
# check for new deferred todo
|
||||
next_todo = Todo.find(:first, :conditions => {:recurring_todo_id => recurring_todo_1.id, :state => 'deferred'})
|
||||
assert !next_todo.nil?
|
||||
# check that the due date of the new todo is later than tomorrow
|
||||
assert next_todo.due > @todo.due
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ class UsersControllerTest < Test::Rails::TestCase
|
|||
get :index
|
||||
assert_response :success
|
||||
assert_equal "TRACKS::Manage Users", assigns['page_title']
|
||||
assert_equal 3, assigns['total_users']
|
||||
assert_equal 4, assigns['total_users']
|
||||
assert_equal "/users", session['return-to']
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ class UsersXmlApiTest < ActionController::IntegrationTest
|
|||
get '/users.xml', {}, basic_auth_headers()
|
||||
assert_response :success
|
||||
assert_tag :tag => "users",
|
||||
:children => { :count => 3, :only => { :tag => "user" } }
|
||||
:children => { :count => 4, :only => { :tag => "user" } }
|
||||
assert_no_tag :tag => "password"
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ assert_element_present "todo_9"
|
|||
|
||||
# add new action to existing context
|
||||
type "todo_description", "a new action"
|
||||
type "todo_context_name", "Agenda"
|
||||
type "todo_context_name", "agenda"
|
||||
click "css=#todo-form-new-action .submit_box button"
|
||||
wait_for_visible "flash"
|
||||
|
||||
|
|
|
|||
10
test/selenium/home/defer_todo_empty_context.rsel
Normal file
10
test/selenium/home/defer_todo_empty_context.rsel
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
setup :fixtures => :all
|
||||
login :as => 'admin'
|
||||
open "/"
|
||||
click "edit_icon_todo_5"
|
||||
wait_for_element_present "show_from_todo_5"
|
||||
type "show_from_todo_5", "1/1/2030"
|
||||
click "css=#submit_todo_5"
|
||||
wait_for_element_not_present "todo_5"
|
||||
assert_text 'badge_count', '9'
|
||||
wait_for_not_visible "c5"
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
setup :fixtures => :all
|
||||
login :as => 'admin'
|
||||
open "/"
|
||||
wait_for_element_present '//div[@id="line_todo_5"]//img[@alt="Defer_1"]/..'
|
||||
click '//div[@id="line_todo_5"]//img[@alt="Defer_1"]/..'
|
||||
wait_for_element_not_present "todo_5"
|
||||
assert_text 'badge_count', '9'
|
||||
wait_for_not_visible "c5"
|
||||
|
|
@ -2,4 +2,4 @@ setup :fixtures => :all
|
|||
login :as => 'admin'
|
||||
open '/'
|
||||
click "xpath=//div[@id='c1'] //div[@id='todo_9'] //input[@class='item-checkbox']"
|
||||
wait_for_element_present "xpath=//div[@id='completed'] //div[@id='todo_9']"
|
||||
wait_for_element_present "xpath=//div[@id='completed_container'] //div[@id='todo_9']"
|
||||
|
|
|
|||
|
|
@ -2,5 +2,5 @@ setup :fixtures => :all
|
|||
login :as => 'admin'
|
||||
open '/'
|
||||
click "xpath=//div[@id='c5'] //div[@id='todo_5'] //input[@class='item-checkbox']"
|
||||
wait_for_element_present "xpath=//div[@id='completed'] //div[@id='todo_5']"
|
||||
wait_for_element_present "xpath=//div[@id='completed_container'] //div[@id='todo_5']"
|
||||
wait_for_not_visible 'c5'
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
setup :fixtures => :all
|
||||
login :as => 'admin'
|
||||
open '/'
|
||||
click "xpath=//div[@id='completed'] //div[@id='todo_3'] //input[@class='item-checkbox']"
|
||||
click "xpath=//div[@id='completed_container'] //div[@id='todo_3'] //input[@class='item-checkbox']"
|
||||
wait_for_element_present "xpath=//div[@id='c4'] //div[@id='todo_3']"
|
||||
assert_not_visible "c4empty-nd"
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ login :as => 'admin'
|
|||
open '/m'
|
||||
wait_for_text 'css=h1 span.count', '10'
|
||||
|
||||
click_and_wait "link=Add new action"
|
||||
click_and_wait "link=0-Add new action"
|
||||
|
||||
type "todo_notes", "test notes"
|
||||
type "todo_description", "test name"
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ wait_for_title "All actions"
|
|||
wait_for_text 'css=h1 span.count', '10'
|
||||
|
||||
# open context page
|
||||
click_and_wait "link=Contexts"
|
||||
click_and_wait "link=2-Contexts"
|
||||
# verify_title "All actions in context agenda"
|
||||
# choose agenda context
|
||||
click_and_wait "link=agenda"
|
||||
|
|
@ -18,7 +18,7 @@ click_and_wait "link=foo"
|
|||
verify_title "TRACKS::Tagged with 'foo'"
|
||||
wait_for_text 'css=h1 span.count', '2'
|
||||
|
||||
click_and_wait "link=Projects"
|
||||
click_and_wait "link=3-Projects"
|
||||
wait_for_text 'css=h1 span.count', '3'
|
||||
click_and_wait "link=Build a working time machine"
|
||||
wait_for_text 'css=h1 span.count', '3'
|
||||
|
|
|
|||
|
|
@ -3,5 +3,5 @@ login :as => 'admin'
|
|||
open "/projects/1"
|
||||
include_partial 'project_detail/add_deferred_todo'
|
||||
click "xpath=//div[@id='tickler'] //div[@id='todo_15'] //input[@class='item-checkbox']"
|
||||
wait_for_element_present "xpath=//div[@id='completed'] //div[@id='todo_15']"
|
||||
wait_for_element_present "xpath=//div[@id='completed_container'] //div[@id='todo_15']"
|
||||
assert_not_visible "tickler-empty-nd"
|
||||
|
|
@ -11,6 +11,6 @@ assert_text 'badge_count', '1'
|
|||
|
||||
# mark one complete
|
||||
click "xpath=//div[@id='c1'] //div[@id='todo_1'] //input[@class='item-checkbox']"
|
||||
wait_for_element_present "xpath=//div[@id='completed'] //div[@id='todo_1']"
|
||||
wait_for_element_present "xpath=//div[@id='completed_container'] //div[@id='todo_1']"
|
||||
|
||||
assert_text 'badge_count', '0'
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ class Test::Rails::TestCase < Test::Unit::TestCase
|
|||
end
|
||||
|
||||
def next_week
|
||||
1.week.from_now.utc.to_date
|
||||
1.week.from_now.utc
|
||||
end
|
||||
|
||||
# Courtesy of http://habtm.com/articles/2006/02/20/assert-yourself-man-redirecting-with-rjs
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ class ContextTest < Test::Rails::TestCase
|
|||
end
|
||||
|
||||
def test_delete_context_deletes_todos_within_it
|
||||
assert_equal 6, @agenda.todos.count
|
||||
assert_equal 7, @agenda.todos.count
|
||||
agenda_todo_ids = @agenda.todos.collect{|t| t.id }
|
||||
@agenda.destroy
|
||||
agenda_todo_ids.each do |todo_id|
|
||||
|
|
@ -62,11 +62,11 @@ class ContextTest < Test::Rails::TestCase
|
|||
end
|
||||
|
||||
def test_not_done_todos
|
||||
assert_equal 5, @agenda.not_done_todos.size
|
||||
assert_equal 6, @agenda.not_done_todos.size
|
||||
t = @agenda.not_done_todos[0]
|
||||
t.complete!
|
||||
t.save!
|
||||
assert_equal 4, Context.find(@agenda.id).not_done_todos.size
|
||||
assert_equal 5, Context.find(@agenda.id).not_done_todos.size
|
||||
end
|
||||
|
||||
def test_done_todos
|
||||
|
|
|
|||
76
test/unit/message_gateway_test.rb
Normal file
76
test/unit/message_gateway_test.rb
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
require File.dirname(__FILE__) + '/../test_helper'
|
||||
|
||||
class MessageGatewayTest < Test::Rails::TestCase
|
||||
fixtures :users, :contexts
|
||||
|
||||
def setup
|
||||
@user = users(:sms_user)
|
||||
@inbox = contexts(:inbox)
|
||||
end
|
||||
|
||||
def load_message(filename)
|
||||
MessageGateway.receive(File.read(File.join(RAILS_ROOT, 'test', 'fixtures', filename)))
|
||||
end
|
||||
|
||||
def test_sms_with_no_subject
|
||||
todo_count = Todo.count
|
||||
|
||||
load_message('sample_sms.txt')
|
||||
# assert some stuff about it being created
|
||||
assert_equal(todo_count+1, Todo.count)
|
||||
|
||||
message_todo = Todo.find(:first, :conditions => {:description => "message_content"})
|
||||
assert_not_nil(message_todo)
|
||||
|
||||
assert_equal(@inbox, message_todo.context)
|
||||
assert_equal(@user, message_todo.user)
|
||||
end
|
||||
|
||||
def test_double_sms
|
||||
todo_count = Todo.count
|
||||
load_message('sample_sms.txt')
|
||||
load_message('sample_sms.txt')
|
||||
assert_equal(todo_count+1, Todo.count)
|
||||
end
|
||||
|
||||
def test_mms_with_subject
|
||||
todo_count = Todo.count
|
||||
|
||||
load_message('sample_mms.txt')
|
||||
|
||||
# assert some stuff about it being created
|
||||
assert_equal(todo_count+1, Todo.count)
|
||||
|
||||
message_todo = Todo.find(:first, :conditions => {:description => "This is the subject"})
|
||||
assert_not_nil(message_todo)
|
||||
|
||||
assert_equal(@inbox, message_todo.context)
|
||||
assert_equal(@user, message_todo.user)
|
||||
assert_equal("This is the message body", message_todo.notes)
|
||||
end
|
||||
|
||||
def test_no_user
|
||||
todo_count = Todo.count
|
||||
badmessage = File.read(File.join(RAILS_ROOT, 'test', 'fixtures', 'sample_sms.txt'))
|
||||
badmessage.gsub!("5555555555", "notauser")
|
||||
MessageGateway.receive(badmessage)
|
||||
assert_equal(todo_count, Todo.count)
|
||||
end
|
||||
|
||||
def test_direct_to_context
|
||||
message = File.read(File.join(RAILS_ROOT, 'test', 'fixtures', 'sample_sms.txt'))
|
||||
|
||||
valid_context_msg = message.gsub('message_content', 'this is a task @ anothercontext')
|
||||
invalid_context_msg = message.gsub('message_content', 'this is also a task @ notacontext')
|
||||
|
||||
MessageGateway.receive(valid_context_msg)
|
||||
valid_context_todo = Todo.find(:first, :conditions => {:description => "this is a task"})
|
||||
assert_not_nil(valid_context_todo)
|
||||
assert_equal(contexts(:anothercontext), valid_context_todo.context)
|
||||
|
||||
MessageGateway.receive(invalid_context_msg)
|
||||
invalid_context_todo = Todo.find(:first, :conditions => {:description => 'this is also a task'})
|
||||
assert_not_nil(invalid_context_todo)
|
||||
assert_equal(@inbox, invalid_context_todo.context)
|
||||
end
|
||||
end
|
||||
|
|
@ -20,7 +20,7 @@ class PreferenceTest < Test::Rails::TestCase
|
|||
end
|
||||
|
||||
def test_parse_date
|
||||
assert_equal Date.new(2007, 5, 20).to_s, @admin_user.preference.parse_date('20/5/2007').to_s
|
||||
assert_equal @admin_user.at_midnight(Date.new(2007, 5, 20)).to_s, @admin_user.preference.parse_date('20/5/2007').to_s
|
||||
end
|
||||
|
||||
def test_parse_date_returns_nil_if_string_is_empty
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue