mirror of
https://github.com/TracksApp/tracks.git
synced 2026-01-16 06:05:29 +01:00
Merge branch 'master' into new-gui
Conflicts: Gemfile.lock
This commit is contained in:
parent
fa537fbeb0
commit
eb1502d4e0
28 changed files with 385 additions and 221 deletions
|
|
@ -95,7 +95,7 @@ class ApplicationController < ActionController::Base
|
|||
if todos_parent.nil?
|
||||
count = 0
|
||||
elsif (todos_parent.is_a?(Project) && todos_parent.hidden?)
|
||||
count = eval "@project_project_hidden_todo_counts[#{todos_parent.id}]"
|
||||
count = @project_project_hidden_todo_counts[todos_parent.id]
|
||||
else
|
||||
count = eval "@#{todos_parent.class.to_s.downcase}_not_done_counts[#{todos_parent.id}]"
|
||||
end
|
||||
|
|
|
|||
|
|
@ -747,34 +747,29 @@ class TodosController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def get_not_completed_for_predecessor(relation, todo_id=nil)
|
||||
items = relation.todos.not_completed.
|
||||
where('(LOWER(todos.description) LIKE ?)', "%#{params[:term].downcase}%")
|
||||
items = items.where("AND NOT(todos.id=?)", todo_id) unless todo_id.nil?
|
||||
|
||||
items.
|
||||
includes(:context, :project).
|
||||
reorder('description ASC').
|
||||
limit(10)
|
||||
end
|
||||
|
||||
def auto_complete_for_predecessor
|
||||
unless params['id'].nil?
|
||||
get_todo_from_params
|
||||
# Begin matching todos in current project, excluding @todo itself
|
||||
@items = @todo.project.todos.not_completed.
|
||||
where('(LOWER(todos.description) LIKE ?) AND NOT(todos.id=?)', "%#{params[:term].downcase}%", @todo.id).
|
||||
includes(:context, :project).
|
||||
reorder('description ASC').
|
||||
limit(10) unless @todo.project.nil?
|
||||
@items = get_not_completed_for_predecessor(@todo.project, @todo.id) unless @todo.project.nil?
|
||||
# Then look in the current context, excluding @todo itself
|
||||
@items = @todo.context.todos.not_completed.
|
||||
where('(LOWER(todos.description) LIKE ?) AND NOT(todos.id=?)', "%#{params[:term].downcase}%", @todo.id).
|
||||
includes(:context, :project).
|
||||
reorder('description ASC').
|
||||
limit(10) unless !@items.empty? || @todo.context.nil?
|
||||
@items = get_not_completed_for_predecessor(@todo.context, @todo.id) unless !@items.empty? || @todo.context.nil?
|
||||
# Match todos in other projects, excluding @todo itself
|
||||
@items = current_user.todos.not_completed.
|
||||
where('(LOWER(todos.description) LIKE ?) AND NOT(todos.id=?)', "%#{params[:term].downcase}%", @todo.id).
|
||||
includes(:context, :project).
|
||||
reorder('description ASC').
|
||||
limit(10) unless !@items.empty?
|
||||
@items = get_not_completed_for_predecessor(current_user, @todo.id) unless !@items.empty?
|
||||
else
|
||||
# New todo - TODO: Filter on current project in project view
|
||||
@items = current_user.todos.not_completed.
|
||||
where('(LOWER(todos.description) LIKE ?)', "%#{params[:term].downcase}%").
|
||||
includes(:context, :project).
|
||||
reorder('description ASC').
|
||||
limit(10)
|
||||
@items = get_not_complete_for_predecessor(current_user)
|
||||
end
|
||||
render :inline => format_dependencies_as_json_for_auto_complete(@items)
|
||||
end
|
||||
|
|
@ -1020,27 +1015,23 @@ end
|
|||
end
|
||||
end
|
||||
|
||||
def find_completed(relation, id, include_hidden)
|
||||
todos = relation.find(id).todos.completed
|
||||
todos = todos.not_hidden if !include_hidden
|
||||
return todos
|
||||
end
|
||||
|
||||
def determine_completed_count
|
||||
todos=nil
|
||||
|
||||
source_view do |from|
|
||||
from.todo do
|
||||
@completed_count = current_user.todos.not_hidden.completed.count
|
||||
end
|
||||
from.context do
|
||||
todos = current_user.contexts.find(@todo.context_id).todos.completed
|
||||
todos = todos.not_hidden if !@todo.context.hidden?
|
||||
@completed_count = todos.count
|
||||
end
|
||||
from.project do
|
||||
unless @todo.project_id == nil
|
||||
todos = current_user.projects.find(@todo.project_id).todos.completed
|
||||
todos = todos.not_hidden if !@todo.project.hidden?
|
||||
@completed_count = todos.count
|
||||
end
|
||||
end
|
||||
from.tag do
|
||||
@completed_count = current_user.todos.with_tag(@tag.id).completed.count
|
||||
end
|
||||
from.todo { todos = current_user.todos.not_hidden.completed }
|
||||
from.context { todos = find_completed(current_user.contexts, @todo.context_id, @todo.context.hidden?) }
|
||||
from.project { todos = find_completed(current_user.projects, @todo.project_id, @todo.project.hidden?) unless @todo.project_id.nil? }
|
||||
from.tag { todos = current_user.todos.with_tag(@tag.id).completed }
|
||||
end
|
||||
|
||||
@completed_count = todos.nil? ? 0 : todos.count
|
||||
end
|
||||
|
||||
def determine_deferred_tag_count(tag_name)
|
||||
|
|
@ -1195,23 +1186,20 @@ end
|
|||
end
|
||||
end
|
||||
|
||||
def parse_date_for_update(date, error_msg)
|
||||
begin
|
||||
parse_date_per_user_prefs(date)
|
||||
rescue
|
||||
@todo.errors[:base] << error_msg
|
||||
end
|
||||
end
|
||||
|
||||
def update_date_for_update(key)
|
||||
params['todo'][key] = params["todo"].has_key?(key) ? parse_date_for_update(params["todo"][key], t("todos.error.invalid_#{key}_date")) : ""
|
||||
end
|
||||
|
||||
def update_due_and_show_from_dates
|
||||
if params["todo"].has_key?("due")
|
||||
begin
|
||||
params["todo"]["due"] = parse_date_per_user_prefs(params["todo"]["due"])
|
||||
rescue
|
||||
@todo.errors[:base] << "Invalid due date"
|
||||
end
|
||||
else
|
||||
params["todo"]["due"] = ""
|
||||
end
|
||||
if params['todo']['show_from']
|
||||
begin
|
||||
params['todo']['show_from'] = parse_date_per_user_prefs(params['todo']['show_from'])
|
||||
rescue
|
||||
@todo.errors[:base] << "Invalid show from date"
|
||||
end
|
||||
end
|
||||
%w{ due show_from }.each {|date| update_date_for_update(date) }
|
||||
end
|
||||
|
||||
def update_completed_state
|
||||
|
|
@ -1283,18 +1271,18 @@ end
|
|||
completed_todos.completed_after(start_of_this_day).includes(includes[:include])
|
||||
end
|
||||
|
||||
def get_done_in_period(before, after, includes = {:include => Todo::DEFAULT_INCLUDES})
|
||||
completed_todos.completed_before(before).completed_after(after).includes(includes[:include])
|
||||
end
|
||||
|
||||
# all completed todos [begin_of_week, start_of_today]
|
||||
def get_done_rest_of_week(completed_todos, includes = {:include => Todo::DEFAULT_INCLUDES})
|
||||
start_of_this_week = Time.zone.now.beginning_of_week
|
||||
start_of_this_day = Time.zone.now.beginning_of_day
|
||||
completed_todos.completed_before(start_of_this_day).completed_after(start_of_this_week).includes(includes[:include])
|
||||
get_done_in_period(Time.zone.now.beginning_of_day, Time.zone.now.beginning_of_week)
|
||||
end
|
||||
|
||||
# all completed todos [begin_of_month, begin_of_week]
|
||||
def get_done_rest_of_month(completed_todos, includes = {:include => Todo::DEFAULT_INCLUDES})
|
||||
start_of_this_month = Time.zone.now.beginning_of_month
|
||||
start_of_this_week = Time.zone.now.beginning_of_week
|
||||
completed_todos.completed_before(start_of_this_week).completed_after(start_of_this_month).includes(includes[:include])
|
||||
get_done_in_period(Time.zone.now.beginning_of_week, Time.zone.now.beginning_of_month)
|
||||
end
|
||||
|
||||
def get_not_done_todos
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
class MessageGateway < ActionMailer::Base
|
||||
include ActionView::Helpers::SanitizeHelper
|
||||
extend ActionView::Helpers::SanitizeHelper::ClassMethods
|
||||
|
||||
def receive(email)
|
||||
user = get_receiving_user_from_email_address(email)
|
||||
|
|
@ -85,11 +83,11 @@ class MessageGateway < ActionMailer::Base
|
|||
end
|
||||
|
||||
def get_text_or_nil(text)
|
||||
return text ? sanitize(text.strip) : nil
|
||||
return text ? text.strip : nil
|
||||
end
|
||||
|
||||
def get_decoded_text_or_nil(text)
|
||||
return text ? sanitize(text.decoded.strip) : nil
|
||||
return text ? text.decoded.strip : nil
|
||||
end
|
||||
|
||||
def get_first_text_plain_part(email)
|
||||
|
|
@ -99,7 +97,7 @@ class MessageGateway < ActionMailer::Base
|
|||
# remove all parts that are not text/plain
|
||||
parts.reject{|part| !part.content_type.start_with?("text/plain") }
|
||||
|
||||
return parts.count > 0 ? sanitize(parts[0].decoded.strip) : ""
|
||||
return parts.count > 0 ? parts[0].decoded.strip : ""
|
||||
end
|
||||
|
||||
def get_all_parts(parts)
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ class Project < ActiveRecord::Base
|
|||
scope :uncompleted, -> { where("NOT(state = ?)", 'completed') }
|
||||
|
||||
scope :with_name_or_description, lambda { |body| where("name LIKE ? OR description LIKE ?", body, body) }
|
||||
scope :with_namepart, lambda { |body| where("name LIKE ?", body + '%') }
|
||||
|
||||
validates_presence_of :name
|
||||
validates_length_of :name, :maximum => 255
|
||||
|
|
@ -88,7 +89,7 @@ class Project < ActiveRecord::Base
|
|||
# as a result of acts_as_state_machine calling state=() to update the attribute
|
||||
def transition_to(candidate_state)
|
||||
case candidate_state.to_sym
|
||||
when aasm_current_state
|
||||
when aasm.current_state
|
||||
return
|
||||
when :hidden
|
||||
hide!
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ class Todo < ActiveRecord::Base
|
|||
|
||||
# state machine
|
||||
include AASM
|
||||
aasm_initial_state Proc.new { |t| (t.show_from && t.user && (t.show_from > t.user.date)) ? :deferred : :active}
|
||||
aasm_initial_state = Proc.new { |t| (t.show_from && t.user && (t.show_from > t.user.date)) ? :deferred : :active}
|
||||
|
||||
aasm :column => :state do
|
||||
|
||||
|
|
|
|||
|
|
@ -1,28 +1,72 @@
|
|||
require 'date'
|
||||
class RichMessageExtractor
|
||||
include ActionView::Helpers::SanitizeHelper
|
||||
extend ActionView::Helpers::SanitizeHelper::ClassMethods
|
||||
|
||||
RICH_MESSAGE_FIELDS_REGEX = /([^>@]*)@?([^>]*)>?(.*)/
|
||||
PROJECT_MARKER = '~'
|
||||
CONTEXT_MARKER = '@'
|
||||
TICKLER_MARKER = '>'
|
||||
DUE_MARKER = '<'
|
||||
TAG_MARKER = '#'
|
||||
STAR_MARKER = '*'
|
||||
|
||||
ALL_MARKERS = [
|
||||
PROJECT_MARKER,
|
||||
CONTEXT_MARKER,
|
||||
TICKLER_MARKER,
|
||||
DUE_MARKER,
|
||||
TAG_MARKER,
|
||||
STAR_MARKER
|
||||
]
|
||||
|
||||
def initialize(message)
|
||||
@message = message
|
||||
end
|
||||
|
||||
def description
|
||||
fields[1].strip
|
||||
desc = select_for('')
|
||||
desc.blank? ? '' : sanitize(desc[1].strip)
|
||||
end
|
||||
|
||||
def context
|
||||
fields[2].strip
|
||||
context = select_for(CONTEXT_MARKER)
|
||||
context.blank? ? '' : sanitize(context[1].strip)
|
||||
end
|
||||
|
||||
def project
|
||||
stripped = fields[3].strip
|
||||
stripped.blank? ? nil : stripped
|
||||
project = select_for PROJECT_MARKER
|
||||
project.blank? ? nil : sanitize(project[1].strip)
|
||||
end
|
||||
|
||||
def tags
|
||||
string = @message.dup
|
||||
tags = []
|
||||
# Regex only matches one tag, so recurse until we have them all
|
||||
while string.match /#(.*?)(?=[#{ALL_MARKERS.join}]|\Z)/
|
||||
tags << sanitize($1)
|
||||
string.gsub!(/##{$1}/,'')
|
||||
end
|
||||
tags.empty? ? nil : tags
|
||||
end
|
||||
|
||||
def due
|
||||
due = select_for DUE_MARKER
|
||||
due.blank? ? nil : Date.parse(due[1].strip)
|
||||
end
|
||||
|
||||
def show_from
|
||||
show_from = select_for TICKLER_MARKER
|
||||
show_from.blank? ? nil : Date.parse(show_from[1].strip)
|
||||
end
|
||||
|
||||
def starred?
|
||||
@message.include? '*'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fields
|
||||
@message.match(RICH_MESSAGE_FIELDS_REGEX)
|
||||
end
|
||||
def select_for symbol
|
||||
@message.match /#{symbol}(.*?)(?=[#{ALL_MARKERS.join}]|\Z)/
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
|||
|
|
@ -14,6 +14,10 @@ class TodoFromRichMessage
|
|||
description = extractor.description
|
||||
context = extractor.context
|
||||
project = extractor.project
|
||||
show_from = extractor.show_from
|
||||
due = extractor.due
|
||||
tags = extractor.tags
|
||||
star = extractor.starred?
|
||||
|
||||
context_id = default_context_id
|
||||
if context.present?
|
||||
|
|
@ -33,17 +37,21 @@ class TodoFromRichMessage
|
|||
found_project.name = project[4..259].strip
|
||||
found_project.save!
|
||||
else
|
||||
found_project = user.projects.active.find_by_namepart(project)
|
||||
found_project = user.projects.find_by_namepart(project) if found_project.nil?
|
||||
found_project = user.projects.active.with_namepart(project).first
|
||||
found_project = user.projects.with_namepart(project).first if found_project.nil?
|
||||
end
|
||||
project_id = found_project.id unless found_project.nil?
|
||||
end
|
||||
|
||||
todo = user.todos.build
|
||||
todo = user.todos.build
|
||||
todo.description = description
|
||||
todo.raw_notes = notes
|
||||
todo.context_id = context_id
|
||||
todo.project_id = project_id unless project_id.nil?
|
||||
todo.raw_notes = notes
|
||||
todo.context_id = context_id
|
||||
todo.project_id = project_id unless project_id.nil?
|
||||
todo.show_from = show_from if show_from.is_a? Date
|
||||
todo.due = due if due.is_a? Date
|
||||
todo.tag_with tags unless tags.nil? || tags.empty?
|
||||
todo.starred = star
|
||||
todo
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -131,5 +131,5 @@
|
|||
<li>Enable the "Add any gadget by URL" feature. You will find it at bottom of the list. Select Enable radio button and click Save Changes button.</li>
|
||||
<li>Now you can see Gadgets tab added to Gmail Settings. Go to the Gadgets tab</li>
|
||||
<li>Paste following link to the Add a gadget by its URL: and then click Add button:<br/>
|
||||
<pre><%= integrations_url + "/google_gadget" %></pre></li>
|
||||
<pre><%= integrations_url + "/google_gadget.xml" %></pre></li>
|
||||
</ul>
|
||||
|
|
@ -14,6 +14,7 @@
|
|||
<li><a href="#email-cron-section">Automatically Email Yourself Upcoming Actions</a></li>
|
||||
<li><a href="#message_gateway">Integrate Tracks with an email server to be able to send an action through email to Tracks</a></li>
|
||||
<li><a href="#mailgun">Send emails to Tracks with Mailgun</a>
|
||||
<li><a href="#todo_rich_message_format">Rich Todo Message email format</a>
|
||||
<li><a href="#google_gadget">Add Tracks as a Google Gmail gadget</a></li>
|
||||
</ul><br/>
|
||||
<p>Do you have one of your own to add?
|
||||
|
|
@ -122,6 +123,7 @@
|
|||
|
||||
<a name="mailgun"> </a>
|
||||
<h2>Send emails to Tracks with Mailgun</h2>
|
||||
<p>
|
||||
If you want to email tasks to Tracks, but cannot run a mailserver on the same host,
|
||||
you could use the <a href='www.mailgun.com'>Mailgun</a> support built in to Tracks.
|
||||
</p>
|
||||
|
|
@ -155,6 +157,36 @@ mailmap:
|
|||
<p>All the comments about the email format from the section above apply to the
|
||||
Mailgun handling, as the data is processed the same way</p>
|
||||
|
||||
<a name="todo_rich_message_format"> </a>
|
||||
<h2>Rich Todo Message Format</h2>
|
||||
<p> For both of the above methods, the follow format can be used:</p>
|
||||
<pre>my awesome todo @context ~project <131012 >131009 #tag1 #tag2 *</pre>
|
||||
<p>The fields are:</p>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Symbol</th><th>Meaning</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>@</td><td>The context to place the Todo in</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>~</td><td>The project to place the Todo in</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><</td><td>The due date for the Todo (may be 2 digits for day, 4 digits for month-day, or 6 digits for yeah-month-day)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>></td><td>The due date for the Todo (may be 2 digits for day, 4 digits for month-day, or 6 digits for yeah-month-day)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>#</td><td>A tag to apply to the Todo - may be used multiple times</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>*</td><td>Flag to star the Todo</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p>All symbols are optional, and text up to the first symbol (or end of string) is used as the description of the todo</p>
|
||||
|
||||
<a name="google_gadget"> </a>
|
||||
<h2>Add Tracks as a Google Gmail gadget</h2>
|
||||
<p>
|
||||
|
|
|
|||
|
|
@ -28,4 +28,4 @@
|
|||
<% else -%><%= render :partial => "notes/notes_summary", :collection => @project.notes %>
|
||||
<% end -%>
|
||||
<h2><%= t('projects.settings') %></h2>
|
||||
<%= t('projects.state', :state => project.aasm_current_state.to_s) %>. <%= @project_default_context %>
|
||||
<%= t('projects.state', :state => project.aasm.current_state.to_s) %>. <%= @project_default_context %>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
# TODO: is this dead code?
|
||||
page.select('#project_status .active span').each do |element|
|
||||
element.className = @project.aasm_current_state == :active ? 'active_state' : 'inactive_state'
|
||||
element.className = @project.aasm.current_state == :active ? 'active_state' : 'inactive_state'
|
||||
end
|
||||
page.select('#project_status .hidden span').each do |element|
|
||||
element.className = @project.aasm_current_state == :hidden ? 'active_state' : 'inactive_state'
|
||||
element.className = @project.aasm.current_state == :hidden ? 'active_state' : 'inactive_state'
|
||||
end
|
||||
page.select('#project_status .completed span').each do |element|
|
||||
element.className = @project.aasm_current_state == :completed ? 'active_state' : 'inactive_state'
|
||||
element.className = @project.aasm.current_state == :completed ? 'active_state' : 'inactive_state'
|
||||
end
|
||||
page.notify :notice, "Set project status to #{@project.aasm_current_state}", 5.0
|
||||
page.notify :notice, "Set project status to #{@project.aasm.current_state}", 5.0
|
||||
|
|
|
|||
|
|
@ -4,36 +4,42 @@
|
|||
<h2><Actions><%= t('common.actions') %></h2>
|
||||
|
||||
<form method="get" action="<%= edit_todo_path(@todo, :format => :m)%>">
|
||||
<button><%=t('todos.edit_action')%></button>
|
||||
<input type="hidden" name="_method" value="put" />
|
||||
<button><%=t('todos.edit_action')%></button>
|
||||
<input type="hidden" name="_method" value="put" />
|
||||
</form>
|
||||
|
||||
<form method="post" action="<%=toggle_star_todo_path(@todo, :format=>:m)%>">
|
||||
<button><%=t('todos.star_action')%></button>
|
||||
<input type="hidden" name="_method" value="put" />
|
||||
<button><%=t('todos.star_action')%></button>
|
||||
<input type="hidden" name="_method" value="put" />
|
||||
<%= token_tag %>
|
||||
</form>
|
||||
|
||||
<form method="post" action="<%=toggle_check_todo_path(@todo, :format=>:m)%>">
|
||||
<button><%= t('todos.mark_complete')%></button>
|
||||
<input type="hidden" name="_method" value="put" />
|
||||
<button><%= t('todos.mark_complete')%></button>
|
||||
<input type="hidden" name="_method" value="put" />
|
||||
<%= token_tag %>
|
||||
</form>
|
||||
|
||||
<form method="post" action="<%=defer_todo_path(@todo, :format=>:m, :days => 1)%>">
|
||||
<button><%=t('todos.defer_x_days', :count => 1)%></button>
|
||||
<input type="hidden" name="_method" value="put" />
|
||||
<button><%=t('todos.defer_x_days', :count => 1)%></button>
|
||||
<input type="hidden" name="_method" value="put" />
|
||||
<%= token_tag %>
|
||||
</form>
|
||||
|
||||
<form method="post" action="<%=defer_todo_path(@todo, :format=>:m, :days => 2)%>">
|
||||
<button><%=t('todos.defer_x_days', :count => 2)%></button>
|
||||
<input type="hidden" name="_method" value="put" />
|
||||
<button><%=t('todos.defer_x_days', :count => 2)%></button>
|
||||
<input type="hidden" name="_method" value="put" />
|
||||
<%= token_tag %>
|
||||
</form>
|
||||
|
||||
<form method="post" action="<%=defer_todo_path(@todo, :format=>:m, :days => 3)%>">
|
||||
<button><%=t('todos.defer_x_days', :count => 3)%></button>
|
||||
<input type="hidden" name="_method" value="put" />
|
||||
<button><%=t('todos.defer_x_days', :count => 3)%></button>
|
||||
<input type="hidden" name="_method" value="put" />
|
||||
<%= token_tag %>
|
||||
</form>
|
||||
|
||||
<form method="post" action="<%=defer_todo_path(@todo, :format=>:m, :days => 7)%>">
|
||||
<button><%=t('todos.defer_x_days', :count => 7)%></button>
|
||||
<input type="hidden" name="_method" value="put" />
|
||||
<button><%=t('todos.defer_x_days', :count => 7)%></button>
|
||||
<input type="hidden" name="_method" value="put" />
|
||||
<%= token_tag %>
|
||||
</form>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue