mirror of
https://github.com/TracksApp/tracks.git
synced 2025-12-16 23:30:12 +01:00
* Started to add some of the framework for iCal integration (not working yet)
* Added validation for next actions (on description and notes field) git-svn-id: http://www.rousette.org.uk/svn/tracks-repos/trunk@7 a4c988fc-2ded-0310-b66e-134b36920a42
This commit is contained in:
parent
2b65b3162b
commit
49b8fa1007
6 changed files with 326 additions and 7 deletions
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
require_dependency "login_system"
|
require_dependency "login_system"
|
||||||
require_dependency "redcloth"
|
require_dependency "redcloth"
|
||||||
|
require_dependency "iCal"
|
||||||
require 'date'
|
require 'date'
|
||||||
|
|
||||||
$delete_img = "<img src=\"/images/delete.png\" width=\"10\" height=\"10\" />"
|
$delete_img = "<img src=\"/images/delete.png\" width=\"10\" height=\"10\" />"
|
||||||
|
|
|
||||||
|
|
@ -32,24 +32,34 @@ class TodoController < ApplicationController
|
||||||
# Parameters from form fields should be passed to create new item
|
# Parameters from form fields should be passed to create new item
|
||||||
#
|
#
|
||||||
def add_item
|
def add_item
|
||||||
item = Todo.new
|
@item = Todo.new
|
||||||
item.attributes = @params["new_item"]
|
@item.attributes = @params["new_item"]
|
||||||
|
|
||||||
# Convert the date format entered (as set in config/settings.yml)
|
# Convert the date format entered (as set in config/settings.yml)
|
||||||
# to the mysql format YYYY-MM-DD
|
# to the mysql format YYYY-MM-DD
|
||||||
if @params["new_item"]["due"] != ""
|
if @params["new_item"]["due"] != ""
|
||||||
date_fmt = app_configurations["formats"]["date"]
|
date_fmt = app_configurations["formats"]["date"]
|
||||||
formatted_date = DateTime.strptime(@params["new_item"]["due"], "#{date_fmt}")
|
formatted_date = DateTime.strptime(@params["new_item"]["due"], "#{date_fmt}")
|
||||||
item.due = formatted_date.strftime("%Y-%m-%d")
|
@item.due = formatted_date.strftime("%Y-%m-%d")
|
||||||
else
|
else
|
||||||
item.due = "0000-00-00"
|
@item.due = "0000-00-00"
|
||||||
end
|
end
|
||||||
|
|
||||||
if item.save
|
# This doesn't seem to be working. No error, but the error
|
||||||
|
# message isn't printed either.
|
||||||
|
unless @item.errors.empty?
|
||||||
|
error_msg = ''
|
||||||
|
@item.errors.each_full do |message|
|
||||||
|
error_msg << message
|
||||||
|
end
|
||||||
|
return error_msg
|
||||||
|
end
|
||||||
|
|
||||||
|
if @item.save
|
||||||
flash["confirmation"] = "Next action was successfully added"
|
flash["confirmation"] = "Next action was successfully added"
|
||||||
redirect_to( :action => "list" )
|
redirect_to( :action => "list" )
|
||||||
else
|
else
|
||||||
flash["warning"] = "Couldn't add the action"
|
flash["warning"] = "Couldn't add the action because of an error: #{error_msg}"
|
||||||
redirect_to( :action => "list" )
|
redirect_to( :action => "list" )
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,14 @@ class Todo < ActiveRecord::Base
|
||||||
belongs_to :context
|
belongs_to :context
|
||||||
belongs_to :project
|
belongs_to :project
|
||||||
|
|
||||||
|
# Description field can't be empty, and must be < 100 bytes
|
||||||
|
# Notes must be < 60,000 bytes (65,000 actually, but I'm being cautious)
|
||||||
|
validates_presence_of :description, :message => "no description provided"
|
||||||
|
validates_length_of :description, :maximum => 100, :message => "description is too long"
|
||||||
|
validates_lenght_of :notes, :maximum => 60000, :message => "notes are too long"
|
||||||
|
#validates_format_of :due, :with => /^[\d]{2,2}\/[\d]{2,2}\/[\d]{4,4}$/, :message => "date format incorrect"
|
||||||
|
|
||||||
|
|
||||||
def before_save
|
def before_save
|
||||||
# Add a creation date (Ruby object format) to item before it's saved
|
# Add a creation date (Ruby object format) to item before it's saved
|
||||||
# if there is no existing creation date (this prevents creation date
|
# if there is no existing creation date (this prevents creation date
|
||||||
|
|
@ -13,4 +21,5 @@ class Todo < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,15 @@
|
||||||
</div><!- End of display_box -->
|
</div><!- End of display_box -->
|
||||||
|
|
||||||
<div id="input_box">
|
<div id="input_box">
|
||||||
|
<h2>Today's events are: </h2>
|
||||||
|
<ul>
|
||||||
|
<% ical = ICal::ICalReader.new("Personal") %>
|
||||||
|
<% ical.todaysEvents do |event| %>
|
||||||
|
<%= puts "<li>#{event.startTime} - #{event.summary}</li>" %>
|
||||||
|
<% end %>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
<h2>Add next action</h2>
|
<h2>Add next action</h2>
|
||||||
<form method="post" action="add_item">
|
<form method="post" action="add_item">
|
||||||
<label for="new_item_description">Next action</label><br />
|
<label for="new_item_description">Next action</label><br />
|
||||||
|
|
@ -83,6 +92,7 @@
|
||||||
<br />
|
<br />
|
||||||
<input type="submit" value="Add item">
|
<input type="submit" value="Add item">
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<% if @flash["confirmation"] %><div class="confirmation"><%= @flash["confirmation"] %></div><% end %>
|
<% if @flash["confirmation"] %><div class="confirmation"><%= @flash["confirmation"] %></div><% end %>
|
||||||
|
|
|
||||||
289
tracks/lib/iCal.rb
Normal file
289
tracks/lib/iCal.rb
Normal file
|
|
@ -0,0 +1,289 @@
|
||||||
|
|
||||||
|
require 'date'
|
||||||
|
require 'ParseDate'
|
||||||
|
|
||||||
|
module ICal
|
||||||
|
|
||||||
|
SECONDS_PER_DAY = 24 * 60 * 60
|
||||||
|
|
||||||
|
class ICalReader
|
||||||
|
@@iCalFolder = "/Users/jchappell/Library/Calendars"
|
||||||
|
|
||||||
|
def initialize(calendarName = nil)
|
||||||
|
@events = []
|
||||||
|
@iCalFiles = Dir[@@iCalFolder + "/*.ics"]
|
||||||
|
@iCalFiles.sort! {|x, y| x.downcase <=> y.downcase}
|
||||||
|
if calendarName then
|
||||||
|
fullName = @@iCalFolder + "/" + calendarName + ".ics"
|
||||||
|
@iCalFiles = @iCalFiles.select {|name| name == fullName}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def calendars
|
||||||
|
@iCalFiles.collect {|name| File.basename(name, ".ics")}
|
||||||
|
end
|
||||||
|
|
||||||
|
def readEvents
|
||||||
|
@events = []
|
||||||
|
@iCalFiles.each do |fileName|
|
||||||
|
lines = File.readlines(fileName);
|
||||||
|
inEvent = false
|
||||||
|
eventLines = []
|
||||||
|
lines.each do |line|
|
||||||
|
if line =~ /^BEGIN:VEVENT/ then
|
||||||
|
inEvent = true
|
||||||
|
eventLines = []
|
||||||
|
end
|
||||||
|
|
||||||
|
if inEvent
|
||||||
|
eventLines << line
|
||||||
|
if line =~ /^END:VEVENT/ then
|
||||||
|
inEvent = false
|
||||||
|
@events << parseEvent(eventLines)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
@events
|
||||||
|
end
|
||||||
|
|
||||||
|
def parseEvent(lines)
|
||||||
|
event = ICalEvent.new()
|
||||||
|
startDate = nil
|
||||||
|
rule = nil
|
||||||
|
|
||||||
|
lines.each do |line|
|
||||||
|
if line =~ /^SUMMARY:(.*)/ then
|
||||||
|
event.summary = $1
|
||||||
|
elsif line =~ /^DTSTART;.*:(.*).*/ then
|
||||||
|
startDate = parseDate($1)
|
||||||
|
elsif line =~ /^EXDATE.*:(.*)/ then
|
||||||
|
event.addExceptionDate(parseDate($1))
|
||||||
|
elsif line =~ /^RRULE:(.*)/ then
|
||||||
|
rule = $1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
event.startDate = startDate
|
||||||
|
event.addRecurrenceRule(rule)
|
||||||
|
event
|
||||||
|
end
|
||||||
|
|
||||||
|
def parseDate(dateStr)
|
||||||
|
# We constrain the year to 1970 because Time won't handle lesser years
|
||||||
|
# If it is less than 1970 then its probably a birthday or something
|
||||||
|
# in which case, we don't really care about the year
|
||||||
|
year = dateStr[0,4].to_i
|
||||||
|
year = 1970 if year < 1970
|
||||||
|
|
||||||
|
month = dateStr[4,2].to_i
|
||||||
|
day = dateStr[6,2].to_i
|
||||||
|
hour = dateStr[9,2].to_i
|
||||||
|
minute = dateStr[11,2].to_i
|
||||||
|
Time.local(year, month, day, hour, minute)
|
||||||
|
end
|
||||||
|
|
||||||
|
def events
|
||||||
|
readEvents if @events == []
|
||||||
|
@events
|
||||||
|
end
|
||||||
|
|
||||||
|
def selectEvents(&predicate)
|
||||||
|
#
|
||||||
|
# The start date of each event could be different due to recurring events
|
||||||
|
# Since we can assume the date is the same for all events, we just compare
|
||||||
|
# the times when sorting
|
||||||
|
#
|
||||||
|
now = Time.now
|
||||||
|
events.select(&predicate).sort do |event1, event2|
|
||||||
|
time1 = Time.local(now.year, now.month, now.day, event1.startDate.hour,
|
||||||
|
event1.startDate.min)
|
||||||
|
time2 = Time.local(now.year, now.month, now.day, event2.startDate.hour,
|
||||||
|
event2.startDate.min)
|
||||||
|
time1 <=> time2
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def todaysEvents
|
||||||
|
#events.select {|event| event.startsToday? }
|
||||||
|
selectEvents {|event| event.startsToday? }
|
||||||
|
end
|
||||||
|
|
||||||
|
def tomorrowsEvents
|
||||||
|
#events.select {|event| event.startsTomorrow? }
|
||||||
|
selectEvents {|event| event.startsTomorrow?}
|
||||||
|
end
|
||||||
|
|
||||||
|
def eventsFor(date)
|
||||||
|
#events.select {|event| event.startsOn?(date)}
|
||||||
|
selectEvents {|event| event.startsOn?(date)}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class DateParser
|
||||||
|
# Given a date as a string, returns a Time object
|
||||||
|
def DateParser.parse(dateStr)
|
||||||
|
dateValues = ParseDate::parsedate(dateStr)
|
||||||
|
Time.local(*dateValues[0, 3])
|
||||||
|
end
|
||||||
|
|
||||||
|
def DateParser.format(date)
|
||||||
|
date.strftime("%m/%d/%Y")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class ICalEvent
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
@exceptionDates = []
|
||||||
|
end
|
||||||
|
|
||||||
|
def <=>(otherEvent)
|
||||||
|
return @startDate <=> otherEvent.startDate
|
||||||
|
end
|
||||||
|
|
||||||
|
def addExceptionDate(date)
|
||||||
|
@exceptionDates << date
|
||||||
|
end
|
||||||
|
|
||||||
|
def addRecurrenceRule(rule)
|
||||||
|
@dateSet = DateSet.new(@startDate, rule)
|
||||||
|
end
|
||||||
|
|
||||||
|
def startsToday?
|
||||||
|
startsOn?(Time.now)
|
||||||
|
end
|
||||||
|
|
||||||
|
def startsTomorrow?
|
||||||
|
tomorrow = Time.now + SECONDS_PER_DAY;
|
||||||
|
startsOn?(tomorrow)
|
||||||
|
end
|
||||||
|
|
||||||
|
def startsOn?(date)
|
||||||
|
(startDate.year == date.year and startDate.month == date.month and
|
||||||
|
startDate.day == date.day) or @dateSet.includes?(date)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s
|
||||||
|
"#{@startDate.strftime("%m/%d/%Y (%I:%M %p)")} - #{@summary}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def startTime
|
||||||
|
@startDate
|
||||||
|
end
|
||||||
|
|
||||||
|
attr_accessor :startDate, :summary
|
||||||
|
end
|
||||||
|
|
||||||
|
class DateSet
|
||||||
|
|
||||||
|
def initialize(startDate, rule)
|
||||||
|
@startDate = startDate
|
||||||
|
@frequency = nil
|
||||||
|
@count = nil
|
||||||
|
@untilDate = nil
|
||||||
|
@byMonth = nil
|
||||||
|
@byDay = nil
|
||||||
|
parseRecurrenceRule(rule)
|
||||||
|
end
|
||||||
|
|
||||||
|
def parseRecurrenceRule(rule)
|
||||||
|
|
||||||
|
if rule =~ /FREQ=(.*?);/ then
|
||||||
|
@frequency = $1
|
||||||
|
end
|
||||||
|
|
||||||
|
if rule =~ /COUNT=(\d*)/ then
|
||||||
|
@count = $1.to_i
|
||||||
|
end
|
||||||
|
|
||||||
|
if rule =~ /UNTIL=(.*?);/ then
|
||||||
|
@untilDate = DateParser.parse($1)
|
||||||
|
#puts @untilDate
|
||||||
|
end
|
||||||
|
|
||||||
|
if rule =~ /INTERVAL=(\d*)/ then
|
||||||
|
@interval = $1.to_i
|
||||||
|
end
|
||||||
|
|
||||||
|
if rule =~ /BYMONTH=(.*?);/ then
|
||||||
|
@byMonth = $1
|
||||||
|
end
|
||||||
|
|
||||||
|
if rule =~ /BYDAY=(.*?);/ then
|
||||||
|
@byDay = $1
|
||||||
|
#puts "byDay = #{@byDay}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s
|
||||||
|
puts "#<DateSet: starts: #{@startDate.strftime("%m/%d/%Y")}, occurs: #{@frequency}, count: #{@count}, until: #{@until}, byMonth: #{@byMonth}, byDay: #{@byDay}>"
|
||||||
|
end
|
||||||
|
|
||||||
|
def includes?(date)
|
||||||
|
return true if date == @startDate
|
||||||
|
return false if @untilDate and date > @untilDate
|
||||||
|
|
||||||
|
case @frequency
|
||||||
|
when 'DAILY'
|
||||||
|
#if @untilDate then
|
||||||
|
# return (@startDate..@untilDate).include?(date)
|
||||||
|
#end
|
||||||
|
increment = @interval ? @interval : 1
|
||||||
|
d = @startDate
|
||||||
|
counter = 0
|
||||||
|
until d > date
|
||||||
|
|
||||||
|
if @count then
|
||||||
|
counter += 1
|
||||||
|
if counter >= @count
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
d += (increment * SECONDS_PER_DAY)
|
||||||
|
if d.day == date.day and
|
||||||
|
d.year == date.year and
|
||||||
|
d.month == date.month then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
when 'WEEKLY'
|
||||||
|
return true if @startDate.wday == date.wday
|
||||||
|
|
||||||
|
when 'MONTHLY'
|
||||||
|
|
||||||
|
when 'YEARLY'
|
||||||
|
end
|
||||||
|
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
attr_reader :frequency
|
||||||
|
attr_accessor :startDate
|
||||||
|
end
|
||||||
|
|
||||||
|
if $0 == __FILE__ then
|
||||||
|
#reader = ICalReader.new("Test")
|
||||||
|
reader = ICalReader.new
|
||||||
|
|
||||||
|
puts
|
||||||
|
puts "Today"
|
||||||
|
puts "====="
|
||||||
|
puts reader.todaysEvents
|
||||||
|
|
||||||
|
puts
|
||||||
|
puts "Tomorrow"
|
||||||
|
puts "========"
|
||||||
|
puts reader.tomorrowsEvents
|
||||||
|
|
||||||
|
puts
|
||||||
|
puts "08/14/2003 Events"
|
||||||
|
puts "================="
|
||||||
|
puts reader.eventsFor(Time.local(2003, 8, 14))
|
||||||
|
puts
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
@ -185,7 +185,7 @@ h2 a:hover {
|
||||||
border: 1px solid #ED2E38;
|
border: 1px solid #ED2E38;
|
||||||
background-color: #F6979C;
|
background-color: #F6979C;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
color: #007E00;
|
color: #ED2E38;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue