kalindar 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +8 -8
- data/Procfile +1 -0
- data/kalindar.gemspec +1 -1
- data/lib/kalindar.rb +2 -1
- data/lib/kalindar/app.rb +80 -40
- data/lib/kalindar/calendar.rb +12 -0
- data/lib/kalindar/event.rb +121 -6
- data/lib/kalindar/event_calendar.rb +40 -15
- data/lib/kalindar/locales/de.yml +18 -0
- data/lib/kalindar/locales/en.yml +16 -0
- data/lib/kalindar/public/kalindar.css +30 -12
- data/lib/kalindar/public/kalindar.js +26 -1
- data/lib/kalindar/version.rb +1 -1
- data/lib/kalindar/views/edit_event.slim +37 -0
- data/lib/kalindar/views/event_list.slim +13 -7
- data/lib/kalindar/views/layout.slim +2 -1
- data/lib/kalindar/views/new_event.slim +3 -2
- data/spec/kalindar_spec.rb +116 -52
- data/spec/testcal.ics +11 -0
- metadata +7 -3
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
NDY4ODFlYzA2NjAxZjdjZGU1YzdjM2Q1ZDA0OTZkNTA1NTRmMTFlYw==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
NzkxNTk3ODk1ZjI4NDQ2ODJiMzJiNTI3OTE5YTA5NGQ5NDZhMzUwNQ==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
ZGZjMTJkYTZjMzY2ZWMzMjlkZjU2ZGQyMmQ1NGU3NGZlZDQ1ZDI4YmQ2MGVl
|
10
|
+
ZDVlZmE4YmY2ZTgyODgwNmE0NDczODgzOGNkNDlkY2M4ZDIzMTc1MTdmNjNj
|
11
|
+
YmE5NDgyMzc1MWNhNGJmMDlmNjQ4NjM4ZDlmNWExYzE5YzJiZGY=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
Njk3YzdhMmFhYjNiYjNjNTc3OWU0ZjU2NDcwYTJlOGUzZTU3ODM3MTY0MWE3
|
14
|
+
MzdkOTMyY2RmYTk2OWY3NDQxNjViNjYzZjQyOWI3MGYyMGQzOTRmYWJlZDQ0
|
15
|
+
YzkyNjI1ZjExMjg4MjIyMTFjZTRlZDI5OTFiMWI0YmM2NTBiNTI=
|
data/Procfile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
web: bundle exec rackup config.ru -p $PORT
|
data/kalindar.gemspec
CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
|
|
10
10
|
spec.email = ["felix.wolfsteller@gmail.com"]
|
11
11
|
spec.summary = %q{Web-Interface to ics files}
|
12
12
|
spec.description = %q{Web-Interface to ics files with sinatra}
|
13
|
-
spec.homepage = ""
|
13
|
+
spec.homepage = "https://github.com/fwolfst/kalindar"
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
16
16
|
spec.files = `git ls-files -z`.split("\x0")
|
data/lib/kalindar.rb
CHANGED
data/lib/kalindar/app.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
|
-
require 'sinatra/base'
|
2
|
-
require 'slim'
|
3
1
|
require 'time'
|
4
2
|
require 'json'
|
3
|
+
require 'securerandom'
|
4
|
+
require 'sinatra/base'
|
5
|
+
require 'slim'
|
5
6
|
require 'i18n'
|
6
7
|
require 'i18n/backend/fallbacks'
|
7
8
|
|
9
|
+
# Sinatra App for Kalindar, show ics files.
|
8
10
|
class KalindarApp < Sinatra::Base
|
9
11
|
$conf = JSON.load(File.new('config.json'))
|
10
12
|
$cal = EventCalendar.new($conf['calendar_files'])
|
@@ -22,6 +24,9 @@ class KalindarApp < Sinatra::Base
|
|
22
24
|
# We like pretty html indentation
|
23
25
|
set :slim, :pretty => true
|
24
26
|
|
27
|
+
# Allow inclusion in iframe.
|
28
|
+
set :protection, :except => :frame_options
|
29
|
+
|
25
30
|
helpers do
|
26
31
|
def li_day_class day
|
27
32
|
return "sunday" if day.sunday?
|
@@ -40,6 +45,17 @@ class KalindarApp < Sinatra::Base
|
|
40
45
|
redirect '/events'
|
41
46
|
end
|
42
47
|
|
48
|
+
get '/events/:year/:month' do
|
49
|
+
@events = {}
|
50
|
+
# Events from start time to 31 days later
|
51
|
+
date = Date.new(params[:year].to_i, params[:month].to_i, 1)
|
52
|
+
(date .. date + 30).each do |day|
|
53
|
+
#@events[d] = $cal.events_for(d)
|
54
|
+
@events[day] = $cal.find_events day.to_date
|
55
|
+
end
|
56
|
+
slim :event_list
|
57
|
+
end
|
58
|
+
|
43
59
|
get '/events' do
|
44
60
|
@events = {}
|
45
61
|
# events from today to in 30 days
|
@@ -50,51 +66,75 @@ class KalindarApp < Sinatra::Base
|
|
50
66
|
slim :event_list
|
51
67
|
end
|
52
68
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
# Adds minutes to start_time.
|
63
|
-
def end_time_from_params params, start_time
|
64
|
-
minutes = case params['duration']
|
65
|
-
when '15m' then 15
|
66
|
-
when '30m' then 30
|
67
|
-
when '60m' then 60
|
68
|
-
when '90m' then 90
|
69
|
-
when '120m' then 120
|
70
|
-
when '1d' then 24 * 60
|
71
|
-
when '2d' then 24 * 2 * 60
|
72
|
-
when '5d' then 24 * 5 * 60
|
73
|
-
when '1w' then 24 * 7 * 60
|
74
|
-
end
|
75
|
-
start_time + Rational(minutes, 1440)
|
69
|
+
get '/events/twoday' do
|
70
|
+
@events = {}
|
71
|
+
# events from today to in 30 days
|
72
|
+
(DateTime.now .. DateTime.now + 30).each do |day|
|
73
|
+
#@events[d] = $cal.events_for(d)
|
74
|
+
@events[day] = $cal.find_events day.to_date
|
75
|
+
end
|
76
|
+
@events = @events.values.flatten.sort_by {|e| e.start_time}
|
77
|
+
slim :twoday_list
|
76
78
|
end
|
77
79
|
|
78
|
-
# Add event, save ics file.
|
80
|
+
# Add new event, save ics file.
|
79
81
|
put '/event' do
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
82
|
+
errors = EventParamHelper.check_params params
|
83
|
+
if !errors.empty?
|
84
|
+
slim :new_event, :locals => {'start_date' => Date.parse(params[:start_day])}
|
85
|
+
end
|
86
|
+
begin
|
87
|
+
event = Event.create_from_params params
|
88
|
+
rescue
|
89
|
+
return 502, "Eingabefehler"
|
90
|
+
end
|
91
|
+
|
89
92
|
$cal.calendars.first.events << event
|
90
|
-
|
91
|
-
$cal.calendars.first.export_to io
|
92
|
-
io.close
|
93
|
+
$cal.calendars.first.write_back!
|
93
94
|
|
94
|
-
|
95
|
+
if request.xhr?
|
96
|
+
@events = {}
|
97
|
+
# Events from today to in 30 days
|
98
|
+
(DateTime.now .. DateTime.now + 30).each do |day|
|
99
|
+
@events[day] = $cal.find_events day.to_date
|
100
|
+
end
|
101
|
+
slim :event_list, :layout => false
|
102
|
+
else
|
103
|
+
redirect '/'
|
104
|
+
end
|
95
105
|
end
|
96
106
|
|
107
|
+
# Show new event template.
|
97
108
|
get '/event/new/:day' do
|
98
|
-
|
109
|
+
# Aim is to get a new event in every case
|
110
|
+
#@event = Event.create_from_params params
|
111
|
+
@event = Event.new(RiCal::Component::Event.new($cal.calendars.first))
|
112
|
+
@event.dtstart = Date.parse(params[:day])
|
113
|
+
slim :new_event, :locals => {'start_date' => Date.parse(params[:day])}
|
114
|
+
end
|
115
|
+
|
116
|
+
# Yet empty route.
|
117
|
+
get '/event/delete/:uuid' do
|
118
|
+
redirect back
|
119
|
+
end
|
120
|
+
|
121
|
+
# Show edit view.
|
122
|
+
get '/event/edit/:uuid' do
|
123
|
+
event = $cal.find_by_uid params[:uuid]
|
124
|
+
if event.nil?
|
125
|
+
redirect back
|
126
|
+
else
|
127
|
+
slim :edit_event, :locals => {'event' => event}
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# Edit/save an event.
|
132
|
+
put '/event/edit/:uuid' do
|
133
|
+
# validate_params
|
134
|
+
puts params
|
135
|
+
event = $cal.find_by_uid(params[:uuid])
|
136
|
+
event.update params
|
137
|
+
$cal.calendars.first.write_back!
|
138
|
+
redirect '/'
|
99
139
|
end
|
100
140
|
end
|
data/lib/kalindar/event.rb
CHANGED
@@ -1,24 +1,139 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
|
1
3
|
# Delegator with some handy shortcuts
|
2
4
|
class Event < SimpleDelegator
|
5
|
+
# Time it starts at day, or '...'
|
3
6
|
def start_time_f day
|
4
|
-
puts "start #{start_time} : #{start_time.class} #{start_time.to_date} #{day}"
|
5
|
-
|
6
|
-
|
7
|
+
#puts "start #{start_time} : #{start_time.class} #{start_time.to_date} #{day}"
|
8
|
+
if dtstart.class == Date
|
9
|
+
# whole day
|
10
|
+
""
|
11
|
+
elsif start_time.to_date == day.to_date
|
12
|
+
start_time.strftime('%H:%M')
|
13
|
+
else
|
14
|
+
"..."
|
15
|
+
end
|
7
16
|
end
|
17
|
+
|
18
|
+
# Time it finishes at day, or '...'
|
8
19
|
def finish_time_f day
|
9
|
-
|
10
|
-
|
20
|
+
if dtend.class == Date
|
21
|
+
# whole day
|
22
|
+
""
|
23
|
+
elsif finish_time.to_date == day.to_date
|
24
|
+
finish_time.strftime('%H:%M')
|
25
|
+
else
|
26
|
+
return "..."
|
27
|
+
end
|
11
28
|
end
|
29
|
+
|
30
|
+
# Time it finishes and or starts at day, or '...'
|
12
31
|
def time_f day
|
13
32
|
start = start_time_f day
|
14
33
|
finish = finish_time_f day
|
15
|
-
if start == finish && start == "
|
34
|
+
if start == finish && start == ""
|
35
|
+
# whole day
|
36
|
+
""
|
37
|
+
elsif start == finish && start == "..."
|
16
38
|
"..."
|
17
39
|
else
|
18
40
|
"#{start_time_f day} - #{finish_time_f day}"
|
19
41
|
end
|
20
42
|
end
|
43
|
+
|
44
|
+
# Date and time from and to
|
21
45
|
def from_to_f
|
22
46
|
return "#{dtstart.to_datetime.strftime("%d.%m. %H:%M")} - #{dtend.to_datetime.strftime("%d.%m. %H:%M")}"
|
23
47
|
end
|
48
|
+
|
49
|
+
# Create DateTime from yyyymmdd + h + m .
|
50
|
+
def self.start_time_from_params params
|
51
|
+
start_day = Date.parse(params['start_day'])
|
52
|
+
if !params[:start_time]
|
53
|
+
return start_day
|
54
|
+
end
|
55
|
+
|
56
|
+
hour, minute = params[:start_time].match(/(\d\d):(\d\d)/)[1,2]
|
57
|
+
start_time = DateTime.new(start_day.year,
|
58
|
+
start_day.month, start_day.day, hour.to_i, minute.to_i)
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.start_date_from params
|
62
|
+
Date.parse(params['start_day'])
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
def update params
|
67
|
+
begin
|
68
|
+
hour, minute = params['start_time'].match(/(\d\d):(\d\d)/)[1,2]
|
69
|
+
start_day = Date.parse(params['start_day'])
|
70
|
+
start_time = DateTime.new(start_day.year,
|
71
|
+
start_day.month, start_day.day, hour.to_i, minute.to_i)
|
72
|
+
self.dtstart = start_time
|
73
|
+
minutes = EventParamHelper.duration params['duration']
|
74
|
+
self.dtend = start_time + Rational(minutes, 1440)
|
75
|
+
rescue => e
|
76
|
+
STDERR.puts "event#update params: problems with (up)date #{e.message}"
|
77
|
+
end
|
78
|
+
|
79
|
+
self.summary = params['summary'] if params['summary']
|
80
|
+
self.description = params['description'] if params['description']
|
81
|
+
self.location = params['location'] if params['location']
|
82
|
+
end
|
83
|
+
|
84
|
+
# Create a new event from params as given by new_event form of kalindar.
|
85
|
+
# this should eventually go somewhere else, but its better here than in app already.
|
86
|
+
def self.create_from_params params
|
87
|
+
event = RiCal::Component::Event.new($cal.calendars.first)
|
88
|
+
event.uid = SecureRandom.uuid
|
89
|
+
if params['summary']
|
90
|
+
event.summary = params['summary']
|
91
|
+
end
|
92
|
+
if params['description']
|
93
|
+
event.description = params['description']
|
94
|
+
end
|
95
|
+
if params['location']
|
96
|
+
event.location = params['location']
|
97
|
+
end
|
98
|
+
|
99
|
+
# Access should be made failsafe.
|
100
|
+
start_time = start_time_from_params(params)
|
101
|
+
event.dtstart = start_time
|
102
|
+
minutes = EventParamHelper.duration params['duration']
|
103
|
+
event.dtend = start_time + Rational(minutes, 1440)
|
104
|
+
Event.new event
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
end
|
109
|
+
|
110
|
+
module EventParamHelper
|
111
|
+
|
112
|
+
# minutes for abbrevations
|
113
|
+
@@duration_param = {
|
114
|
+
'15m' => 15,
|
115
|
+
'30m' => 30,
|
116
|
+
'60m' => 60,
|
117
|
+
'90m' => 90,
|
118
|
+
'120m' => 120,
|
119
|
+
'1d' => 24 * 60,
|
120
|
+
'2d' => 24 * 2 * 60,
|
121
|
+
'5d' => 24 * 5 * 60,
|
122
|
+
'1w' => 24 * 7 * 60
|
123
|
+
}
|
124
|
+
def self.duration duration_p
|
125
|
+
# throw
|
126
|
+
@@duration_param[duration_p]
|
127
|
+
end
|
128
|
+
|
129
|
+
def self.check_params params
|
130
|
+
errors = {}
|
131
|
+
if not(params[:start_time] =~ /\d\d:\d\d/)
|
132
|
+
errors[:start_time] = ''
|
133
|
+
end
|
134
|
+
if not(duration params[:duration])
|
135
|
+
errors[:duration] = ''
|
136
|
+
end
|
137
|
+
errors
|
138
|
+
end
|
24
139
|
end
|
@@ -1,26 +1,36 @@
|
|
1
1
|
require 'ri_cal'
|
2
|
+
require 'kalindar/calendar'
|
2
3
|
|
4
|
+
# Public facing methods should return Event Decorators,
|
5
|
+
# private methods can return "raw" RiCal::Component::Event s.
|
3
6
|
class EventCalendar
|
4
7
|
attr_accessor :calendars
|
5
|
-
attr_accessor :filenames # decorator?
|
6
|
-
# also decoreate event for access to calendar?
|
7
8
|
|
9
|
+
# Given filename or array of filenames, initialize the calendar.
|
8
10
|
def initialize filename
|
9
11
|
@calendars = []
|
10
|
-
@filenames = []
|
11
12
|
if filename.class == Array
|
12
|
-
filename.each {|file| read_file file}
|
13
|
+
filename.each {|file| read_file file, true}
|
13
14
|
else
|
14
|
-
read_file filename
|
15
|
+
read_file filename, true
|
15
16
|
end
|
16
17
|
end
|
17
18
|
|
18
|
-
|
19
|
+
# Opens calendar
|
20
|
+
# param: create if true, create empty calendar file if not existant.
|
21
|
+
def read_file filename, create
|
22
|
+
if create && !File.exist?(filename)
|
23
|
+
File.open(filename, 'w') do |file|
|
24
|
+
(RiCal::Component::Calendar.new).export_to file
|
25
|
+
end
|
26
|
+
end
|
19
27
|
@calendars << File.open(filename, 'r') do |file|
|
20
28
|
RiCal.parse file
|
21
|
-
end.flatten
|
22
|
-
|
23
|
-
|
29
|
+
end.flatten.map do |calendar|
|
30
|
+
c = Calendar.new calendar
|
31
|
+
c.filename = filename
|
32
|
+
c
|
33
|
+
end
|
24
34
|
@calendars.flatten!
|
25
35
|
end
|
26
36
|
|
@@ -59,19 +69,33 @@ class EventCalendar
|
|
59
69
|
events.flatten
|
60
70
|
end
|
61
71
|
|
72
|
+
# Find (non-recuring) events that begin, end or cover the given day.
|
62
73
|
def find_events date
|
63
74
|
#events = @calendars.map &:events
|
64
75
|
@calendars.map do |calendar|
|
65
76
|
calendar.events.select { |event|
|
66
|
-
|
77
|
+
# If end-date is a Date (vs DateTime) let it be
|
78
|
+
# All day/multiple day events
|
79
|
+
if event.dtstart.class == Date && event.dtend.class == Date
|
80
|
+
event.dtstart.to_date == date || (event.dtstart < date && event.dtend > date)
|
81
|
+
else
|
82
|
+
event.dtstart.to_date == date || event.dtend.to_date == date || (event.dtstart < date && event.dtend > date)
|
83
|
+
# occurrences need to be re-enabled
|
84
|
+
#||!event.occurrences(:overlapping => [date, date +1]).empty?
|
85
|
+
end
|
67
86
|
}
|
68
87
|
end.flatten.map do |event|
|
69
88
|
Event.new event
|
70
89
|
end
|
90
|
+
# check flat_map enumerable method
|
71
91
|
end
|
72
92
|
|
73
|
-
def
|
74
|
-
|
93
|
+
def find_by_uid uuid
|
94
|
+
# we want to pick only the first! whats the method? detect is one, find another
|
95
|
+
@calendars.map(&:events).flatten.each do |event|
|
96
|
+
return Event.new(event) if event.uid == uuid
|
97
|
+
end
|
98
|
+
nil
|
75
99
|
end
|
76
100
|
|
77
101
|
private
|
@@ -81,13 +105,13 @@ class EventCalendar
|
|
81
105
|
end
|
82
106
|
|
83
107
|
def dtmonth_end year, month
|
84
|
-
puts "last date for #{year} - #{month}"
|
108
|
+
#puts "last date for #{year} - #{month}"
|
85
109
|
last_day = Date.civil(year, month, -1)
|
86
110
|
Icalendar::Values::Date.new('20120102')#"#{year}#{month}#{last_day}")
|
87
111
|
end
|
88
112
|
|
89
113
|
def date_between? date, start_date, end_date
|
90
|
-
puts "d #{date} st #{start_date} e #{end_date}"
|
114
|
+
#puts "d #{date} st #{start_date} e #{end_date}"
|
91
115
|
date > start_date && date < end_date
|
92
116
|
end
|
93
117
|
|
@@ -96,7 +120,8 @@ class EventCalendar
|
|
96
120
|
end
|
97
121
|
|
98
122
|
def event_includes? event, date
|
99
|
-
event.dtstart.class == event.dtend.class && event.dtstart.class == Icalendar::Values::DateTime && (date_between?(date, event.dtstart, event.dtend))
|
123
|
+
incl = event.dtstart.class == event.dtend.class && event.dtstart.class == Icalendar::Values::DateTime && (date_between?(date, event.dtstart, event.dtend))
|
124
|
+
incl
|
100
125
|
end
|
101
126
|
end
|
102
127
|
|
data/lib/kalindar/locales/de.yml
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
de:
|
2
2
|
create_event: "Neuer Termin"
|
3
3
|
new_event: "Neuer Termin"
|
4
|
+
edit: "ändern"
|
5
|
+
edit_event: "Termin ändern"
|
4
6
|
start_date: "Anfangsdatum"
|
5
7
|
when: "Uhrzeit"
|
6
8
|
summary: "Titel"
|
@@ -19,6 +21,22 @@ de:
|
|
19
21
|
5d: "5 Tage"
|
20
22
|
1w: "eine Woche"
|
21
23
|
save: "speichern"
|
24
|
+
cancel: "abbrechen"
|
22
25
|
time:
|
23
26
|
formats:
|
24
27
|
short: "%d.%m.%Y"
|
28
|
+
hm: "%H:%M"
|
29
|
+
# Seems better option for names (with e.g. %a)
|
30
|
+
# http://blog.lingohub.com/developers/2013/08/internationalization-for-ruby-i18n-gem/
|
31
|
+
date:
|
32
|
+
day:
|
33
|
+
day_names:
|
34
|
+
- Montag
|
35
|
+
- Dienstag
|
36
|
+
- Mittwoch
|
37
|
+
- Donnerstag
|
38
|
+
- Freitag
|
39
|
+
- Samstag
|
40
|
+
- Sonntag
|
41
|
+
formats:
|
42
|
+
short: "%d.%m.%Y"
|
data/lib/kalindar/locales/en.yml
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
en:
|
2
2
|
create_event: "New Event"
|
3
3
|
new_event: "New Event"
|
4
|
+
edit: "edit"
|
5
|
+
edit_event: "edit event"
|
4
6
|
start_date: "Start Date"
|
5
7
|
when: "Time"
|
6
8
|
summary: "Summary"
|
@@ -19,6 +21,20 @@ en:
|
|
19
21
|
5d: "5 days"
|
20
22
|
1w: "a week"
|
21
23
|
save: "save"
|
24
|
+
cancel: "cancel"
|
22
25
|
time:
|
23
26
|
formats:
|
24
27
|
short: "%Y/%m/%d"
|
28
|
+
hm: "%H:%M"
|
29
|
+
date:
|
30
|
+
day:
|
31
|
+
day_names:
|
32
|
+
- Monday
|
33
|
+
- Tuesday
|
34
|
+
- Wednesday
|
35
|
+
- Thursday
|
36
|
+
- Friday
|
37
|
+
- Saturday
|
38
|
+
- Sunday
|
39
|
+
formats:
|
40
|
+
short: "%Y/%m/%d"
|
@@ -1,46 +1,56 @@
|
|
1
|
+
#main a{
|
2
|
+
font-size: smaller;
|
3
|
+
}
|
4
|
+
.dayname {
|
5
|
+
font-size: larger;
|
6
|
+
margin: 0 0 0 0;
|
7
|
+
padding: 0 0 0 0;
|
8
|
+
border-bottom: solid 1px black;
|
9
|
+
}
|
1
10
|
ul.day_list {
|
2
11
|
list-style: none;
|
3
|
-
border-top: solid 1px;
|
4
|
-
border-left: solid 1px;
|
5
|
-
border-right: solid 1px;
|
6
|
-
background-color: #FEEEFE;
|
7
12
|
padding-left: 0em;
|
8
13
|
margin-left: 0.3em;
|
9
14
|
width: 18em;
|
15
|
+
margin-top: 0.1em;
|
16
|
+
margin-bottom: 0.1em;
|
10
17
|
}
|
11
18
|
|
12
19
|
li.day {
|
13
20
|
border-top: solid 1px;
|
21
|
+
border-bottom: solid 1px;
|
14
22
|
border-left: solid 1px;
|
15
|
-
|
23
|
+
border-right: solid 1px;
|
24
|
+
background-color: #F4F4F4;
|
25
|
+
margin-bottom: 0.5em;
|
16
26
|
}
|
17
27
|
|
18
28
|
li.saturday {
|
19
29
|
border-top: solid 1px;
|
20
|
-
border-
|
30
|
+
border-bottom: solid 1px;
|
21
31
|
background-color: #DADAEE;
|
32
|
+
margin-bottom: 1em;
|
22
33
|
}
|
23
34
|
|
24
35
|
li.sunday {
|
25
36
|
border-top: solid 1px;
|
26
|
-
border-
|
37
|
+
border-bottom: solid 1px;
|
27
38
|
background-color: #BBBBEE;
|
39
|
+
margin-bottom: 3em;
|
28
40
|
}
|
29
41
|
|
30
42
|
ul.event_list {
|
31
43
|
list-style: none;
|
32
|
-
border-top: solid 1px;
|
33
|
-
border-bottom: solid 1px;
|
34
44
|
background-color: #FEFEFE;
|
35
|
-
margin-left: 0.5em;
|
36
45
|
padding-left: 0.5em;
|
37
|
-
padding-right:
|
46
|
+
padding-right: 0.5em;
|
38
47
|
padding-top: 0em;
|
39
48
|
padding-bottom: 0em;
|
49
|
+
margin-top: 0.1em;
|
50
|
+
margin-bottom: 0.1em;
|
40
51
|
}
|
41
52
|
|
42
53
|
ul.event_list li {
|
43
|
-
border-bottom: solid 1px;
|
44
54
|
margin-left: 0.1em;
|
45
55
|
clear: both;
|
46
56
|
}
|
@@ -51,11 +61,15 @@ ul.event_list li {
|
|
51
61
|
|
52
62
|
#time {
|
53
63
|
float: left;
|
64
|
+
font-size: smaller;
|
65
|
+
/*
|
54
66
|
width: 6.8em;
|
67
|
+
*/
|
55
68
|
}
|
56
69
|
|
57
70
|
#summary {
|
58
71
|
font-weight: bold;
|
72
|
+
text-align: center;
|
59
73
|
}
|
60
74
|
|
61
75
|
.hidden_new_event_mask {
|
@@ -68,3 +82,7 @@ ul.event_list li {
|
|
68
82
|
margin-left: 0.5em;
|
69
83
|
font-size: 80%;
|
70
84
|
}
|
85
|
+
|
86
|
+
#edit_event_link {
|
87
|
+
margin-left: 1em;
|
88
|
+
}
|
@@ -1,7 +1,7 @@
|
|
1
1
|
"a#new_event_link".onClick(function () {
|
2
2
|
console.log(this);
|
3
3
|
//$('hidden_new_event_mask').fade();
|
4
|
-
this.parent().
|
4
|
+
this.parent().first('.hidden_new_event_mask').fade();
|
5
5
|
// Do not follow link.
|
6
6
|
return false;
|
7
7
|
});
|
@@ -10,3 +10,28 @@
|
|
10
10
|
console.log(this.parent());
|
11
11
|
this.first("#hidden_description").fade();
|
12
12
|
});
|
13
|
+
|
14
|
+
"#cancel".onClick(function() {
|
15
|
+
this.parent.parent().fade();
|
16
|
+
});
|
17
|
+
|
18
|
+
"#show_new_event_link".onClick(function() {
|
19
|
+
console.log(this.parent().first('#day_hidden_input').value());
|
20
|
+
day = this.parent().first('#day_hidden_input').value();
|
21
|
+
this.parent().first('.new_event_mask').load('/event/new/' + day);
|
22
|
+
this.parent().first('.new_event_mask').fade();
|
23
|
+
return false;
|
24
|
+
});
|
25
|
+
|
26
|
+
"#new_event_form".onSubmit(function(event) {
|
27
|
+
event.stop();
|
28
|
+
var me = this;
|
29
|
+
this.send({
|
30
|
+
/*onComplete: function() {alert("complete");}*/
|
31
|
+
onSuccess: function(resp) {
|
32
|
+
console.log(resp);
|
33
|
+
$('main').html(resp.responseText);
|
34
|
+
},
|
35
|
+
onFailure: function(resp) {alert(resp.responseText);},
|
36
|
+
});
|
37
|
+
});
|
data/lib/kalindar/version.rb
CHANGED
@@ -0,0 +1,37 @@
|
|
1
|
+
form.pure-form.pure-form-stacked action="/event/edit/#{event.uid}" method="post"
|
2
|
+
input type="hidden" name="_method" value="put"
|
3
|
+
input type="hidden" name="uid" value=event.uid
|
4
|
+
|
5
|
+
fieldset
|
6
|
+
legend = t 'edit_event'
|
7
|
+
|
8
|
+
label for="start_day" = t 'start_date'
|
9
|
+
input type="text" readonly=true name="start_day" value="#{l event.start_time, :format => :short}"
|
10
|
+
|
11
|
+
label for="start_time" = t 'when'
|
12
|
+
input type="text" name="start_time" placeholder="HH:MM" value="#{l event.start_time, :format => :hm}"
|
13
|
+
|
14
|
+
label for="start" = t 'summary'
|
15
|
+
input type="text" name="summary" placeholder="summary" value=event.summary
|
16
|
+
|
17
|
+
label for="location" = t 'location'
|
18
|
+
input type="text" name="location" placeholder="place" value=event.location
|
19
|
+
|
20
|
+
label for="description" = t 'description'
|
21
|
+
textarea name="description" rows=3 width=40
|
22
|
+
= event.description
|
23
|
+
|
24
|
+
label for="duration" = t 'duration'
|
25
|
+
select name="duration"
|
26
|
+
option value="15m" = t '15m'
|
27
|
+
option value="30m" = t '30m'
|
28
|
+
option value="60m" = t '60m'
|
29
|
+
option value="90m" = t '90m'
|
30
|
+
option value="120m" = t '120m'
|
31
|
+
option value="whole" = t 'whole_day'
|
32
|
+
option value="1d" = t '1d'
|
33
|
+
option value="2d" = t '2d'
|
34
|
+
option value="5d" = t '5d'
|
35
|
+
option value="1w" = t '1w'
|
36
|
+
button.pure-button type="submit" value="save" = t 'save'
|
37
|
+
button.pure-button type="submit" value="cancel" = t 'cancel'
|
@@ -1,10 +1,11 @@
|
|
1
1
|
ul.day_list
|
2
2
|
- @events.each do |day, evs|
|
3
3
|
li class=li_day_class(day)
|
4
|
-
|
5
|
-
| #{l day, :format => :short}
|
6
|
-
|
7
|
-
|
4
|
+
.dayname
|
5
|
+
| #{t(:"date.day.day_names")[day.wday-1]} #{l day, :format => :short}
|
6
|
+
input#day_hidden_input type="hidden" name="day" value="#{day.strftime('%Y%m%d')}"
|
7
|
+
a#show_new_event_link href="/event/new/#{day.strftime('%Y%m%d')}" #{t 'create_event'}
|
8
|
+
.new_event_mask style="display: none;"
|
8
9
|
.hidden_new_event_mask
|
9
10
|
== slim :new_event, :layout => false, :locals => {'start_date' => day.strftime("%Y%m%d")}
|
10
11
|
ul.event_list
|
@@ -13,11 +14,16 @@ ul.day_list
|
|
13
14
|
#event
|
14
15
|
#time
|
15
16
|
| #{ev.time_f day}
|
17
|
+
- if ev.location && !ev.location.empty?
|
18
|
+
| [#{ev.location}]
|
19
|
+
br
|
16
20
|
#summary
|
17
|
-
|
18
|
-
|
19
|
-
| [#{ev.location}]
|
21
|
+
| #{ev.summary}
|
22
|
+
a#edit_event_link href="/event/edit/#{ev.uid}" = t 'edit'
|
20
23
|
#hidden_description
|
21
24
|
| (#{ev.from_to_f})
|
22
25
|
br
|
23
26
|
| #{ev.description}
|
27
|
+
hr(width="20%")
|
28
|
+
#nav
|
29
|
+
|
@@ -1,4 +1,4 @@
|
|
1
|
-
form.pure-form.pure-form-stacked action="/event" method="post"
|
1
|
+
form.pure-form.pure-form-stacked#new_event_form action="/event" method="post"
|
2
2
|
input type="hidden" name="_method" value="put"
|
3
3
|
fieldset
|
4
4
|
legend = t 'new_event'
|
@@ -8,7 +8,7 @@ form.pure-form.pure-form-stacked action="/event" method="post"
|
|
8
8
|
input type="text" readonly=true name="start_day" value="#{start_date}"
|
9
9
|
- else
|
10
10
|
label for="start_day" = t 'start_date'
|
11
|
-
input type="text" name="start_day" value=
|
11
|
+
input type="text" name="start_day" value=event.start_date placeholder="YYYYMMDD")
|
12
12
|
|
13
13
|
label for="start_time" = t 'when'
|
14
14
|
input type="text" name="start_time" value="" placeholder="HH:MM"
|
@@ -35,3 +35,4 @@ form.pure-form.pure-form-stacked action="/event" method="post"
|
|
35
35
|
option value="5d" = t '5d'
|
36
36
|
option value="1w" = t '1w'
|
37
37
|
button.pure-button type="submit" value="save" = t 'save'
|
38
|
+
button.pure-button#cancel type="submit" value="save" = t 'cancel'
|
data/spec/kalindar_spec.rb
CHANGED
@@ -7,80 +7,144 @@ describe Kalindar do
|
|
7
7
|
end
|
8
8
|
|
9
9
|
describe EventCalendar do
|
10
|
-
|
11
|
-
cal = EventCalendar.new 'spec/testcal.ics'
|
12
|
-
end
|
10
|
+
subject { EventCalendar.new 'spec/testcal.ics' }
|
13
11
|
|
14
|
-
|
15
|
-
|
16
|
-
|
12
|
+
describe "#new" do
|
13
|
+
it 'parses an ics file' do
|
14
|
+
cal = EventCalendar.new 'spec/testcal.ics'
|
15
|
+
end
|
16
|
+
it 'initializes alternatively with a list of ics files' do
|
17
|
+
cal = EventCalendar.new ['spec/testcal.ics', 'spec/testcal2.ics']
|
18
|
+
expect(cal.calendars.length).to eql 2
|
19
|
+
end
|
17
20
|
end
|
18
21
|
|
19
22
|
it 'exposes filename of parsed calendar' do
|
20
23
|
cal = EventCalendar.new ['spec/testcal.ics', 'spec/testcal2.ics']
|
21
|
-
expect(cal.
|
24
|
+
expect(cal.calendars.first.filename).to eql 'spec/testcal.ics'
|
22
25
|
end
|
23
26
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
27
|
+
describe "#find_events" do
|
28
|
+
it 'finds events given date' do
|
29
|
+
events = subject.find_events (Date.new(2014, 07, 27))
|
30
|
+
event_names = events.map(&:summary)
|
31
|
+
expect(event_names.include? "onehour").to eq true
|
32
|
+
expect(event_names.include? "allday").to eq true
|
33
|
+
end
|
34
|
+
it 'handles whole day endtime correctly (ends next day)' do
|
35
|
+
events = subject.find_events (Date.new(2014, 07, 28))
|
36
|
+
event_names = events.map(&:summary)
|
37
|
+
expect(event_names.include? "allday").to eq false
|
38
|
+
end
|
39
|
+
it 'finds multiday events that cover the given date' do
|
40
|
+
events = subject.find_events (Date.new(2014, 07, 27))
|
41
|
+
expect(events.map(&:summary).include? "multidays").to eq true
|
42
|
+
end
|
43
|
+
it 'wraps events as Event delegates' do
|
44
|
+
events = subject.find_events (Date.new(2014, 07, 27))
|
45
|
+
events.each do |event|
|
46
|
+
expect(event.is_a? Event).to eq true
|
47
|
+
end
|
48
|
+
end
|
30
49
|
end
|
31
50
|
|
32
51
|
it 'finds events that reocur' do
|
33
|
-
|
34
|
-
events = cal.find_events (Date.new(2014, 07, 27))
|
52
|
+
events = subject.find_events (Date.new(2014, 07, 27))
|
35
53
|
event_names = events.map(&:summary)
|
36
54
|
expect(event_names.include? "daily").to eq true
|
37
55
|
end
|
38
56
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
57
|
+
describe "#events_in" do
|
58
|
+
it 'accesses events between two dates' do
|
59
|
+
events = subject.events_in(Date.new(2014, 07, 27), Date.new(2014, 07, 28))
|
60
|
+
event_names = events.map(&:summary)
|
61
|
+
expect(event_names).to eq ["allday", "onehour", "daily", "daily"]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe "#events_in" do
|
66
|
+
it '#events_in by day' do
|
67
|
+
events = subject.events_in(Date.new(2014, 7, 27), Date.new(2014, 7, 28))
|
68
|
+
event_names = events.map(&:summary)
|
69
|
+
expect(event_names).to eq ["allday", "onehour", "daily", "allday", "daily"]
|
70
|
+
expect(event_names.class).to eq({}.class)
|
71
|
+
end
|
72
|
+
it 'wraps in Event Delegate' do
|
73
|
+
events = subject.events_in(Date.new(2014, 7, 27), Date.new(2014, 7, 28))
|
74
|
+
expect(events.collect{|e| e.is_a? Event}.length).to eq events.length
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe "#find_by_uid" do
|
79
|
+
it 'finds by uuid' do
|
80
|
+
event = subject.find_by_uid 'cb523dc2-eab8-49c9-a99f-ed69ac3b65d0'
|
81
|
+
expect(event.summary).to eq 'allday'
|
82
|
+
end
|
83
|
+
it 'wraps in Event Delegate' do
|
84
|
+
event = subject.find_by_uid 'cb523dc2-eab8-49c9-a99f-ed69ac3b65d0'
|
85
|
+
expect(event.is_a? Event).to eq true
|
86
|
+
end
|
44
87
|
end
|
88
|
+
end
|
45
89
|
|
46
|
-
|
90
|
+
describe "Event" do
|
91
|
+
subject(:allday_event) {}
|
92
|
+
subject(:events) {
|
47
93
|
cal = EventCalendar.new 'spec/testcal.ics'
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
94
|
+
cal.events_in(Date.new(2014, 8, 27), Date.new(2014, 8, 28))
|
95
|
+
}
|
96
|
+
subject(:allday_event) {
|
97
|
+
cal = EventCalendar.new 'spec/testcal.ics'
|
98
|
+
cal.find_by_uid("cb523dc2-eab8-49c9-a99f-ed69ac3b65d0")
|
99
|
+
}
|
100
|
+
subject(:multiday_event) {
|
101
|
+
cal = EventCalendar.new 'spec/testcal.ics'
|
102
|
+
cal.find_by_uid("4a129461-cd74-4b3a-a307-faa1e8846cc2")
|
103
|
+
}
|
104
|
+
|
105
|
+
describe "#start_time_f" do
|
106
|
+
it "returns the time if given day is start day" do
|
107
|
+
expect(events[0].start_time_f Date.new(2014, 8, 27)).to eq "12:00"
|
108
|
+
expect(events[0].start_time_f Date.new(2014, 8, 28)).to eq "..."
|
109
|
+
end
|
52
110
|
end
|
53
111
|
|
54
|
-
describe "
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
events = cal.events_in(Date.new(2014, 8, 27), Date.new(2014, 8, 28))
|
59
|
-
expect(Event.new(events[0]).start_time_f Date.new(2014, 8, 27)).to eq "12:00"
|
60
|
-
expect(Event.new(events[0]).start_time_f Date.new(2014, 8, 28)).to eq ""
|
61
|
-
end
|
112
|
+
describe "#finish_time_f" do
|
113
|
+
it "returns the time if given day is end day" do
|
114
|
+
expect(events[0].finish_time_f Date.new(2014, 8, 27)).to eq "..."
|
115
|
+
expect(events[0].finish_time_f Date.new(2014, 8, 28)).to eq "13:00"
|
62
116
|
end
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
expect(Event.new(events[0]).finish_time_f Date.new(2014, 8, 28)).to eq "13:00"
|
69
|
-
end
|
117
|
+
end
|
118
|
+
|
119
|
+
describe "#time_f" do
|
120
|
+
it "returns the from to time for given day" do
|
121
|
+
expect(allday_event.time_f Date.new(2014, 7, 27)).to eq ""
|
70
122
|
end
|
71
|
-
|
72
|
-
|
73
|
-
cal = EventCalendar.new 'spec/testcal.ics'
|
74
|
-
events = cal.events_in(Date.new(2014, 8, 27), Date.new(2014, 8, 28))
|
75
|
-
expect(Event.new(events[0]).time_f Date.new(2014, 8, 27)).to eq "12:00 - "
|
76
|
-
end
|
123
|
+
it "renders multiday in between with ... " do
|
124
|
+
expect(multiday_event.time_f Date.new(2014, 7, 27)).to eq "..."
|
77
125
|
end
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
126
|
+
end
|
127
|
+
describe "#from_to_f" do
|
128
|
+
it "returns the from to time" do
|
129
|
+
expect(events[0].from_to_f).to eq "27.08. 12:00 - 28.08. 13:00"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
describe "#update" do
|
134
|
+
it "updates from params" do
|
135
|
+
# should be fail safe, too
|
136
|
+
cal = EventCalendar.new 'spec/testcal.ics'
|
137
|
+
event = cal.find_by_uid("aea8d217-8025-4d9b-88e6-3df9e6abd33c")
|
138
|
+
|
139
|
+
params = {'location' => 'a place', 'description' => 'exact description', 'summary'=> 'synopsis', 'duration' => '15m', 'start_day' => '20140808', 'start_time' => '10:10'}
|
140
|
+
event.update params
|
141
|
+
|
142
|
+
event_fetched = cal.find_by_uid("aea8d217-8025-4d9b-88e6-3df9e6abd33c")
|
143
|
+
expect(event_fetched.location).to eql 'a place'
|
144
|
+
expect(event_fetched.description).to eql 'exact description'
|
145
|
+
expect(event_fetched.summary).to eql 'synopsis'
|
146
|
+
expect(event_fetched.dtstart).to eq DateTime.new(2014, 8, 8, 10, 10)
|
147
|
+
expect(event_fetched.dtend).to eq DateTime.new(2014, 8, 8, 10, 25)
|
84
148
|
end
|
85
149
|
end
|
86
150
|
end
|
data/spec/testcal.ics
CHANGED
@@ -51,6 +51,17 @@ UID:aea8d217-8025-4d9b-88e6-3df9e6abd33c
|
|
51
51
|
SUMMARY:2days
|
52
52
|
DTSTART;TZID=Europe/Berlin:20140827T120000
|
53
53
|
DTEND;TZID=Europe/Berlin:20140828T130000
|
54
|
+
CLASS:PUBLIC
|
55
|
+
TRANSP:OPAQUE
|
56
|
+
END:VEVENT
|
57
|
+
BEGIN:VEVENT
|
58
|
+
CREATED:20140814T060630Z
|
59
|
+
LAST-MODIFIED:20140814T060654Z
|
60
|
+
DTSTAMP:20140814T060654Z
|
61
|
+
UID:4a129461-cd74-4b3a-a307-faa1e8846cc2
|
62
|
+
SUMMARY:multidays
|
63
|
+
DTSTART;TZID=Europe/Berlin:20140726T090000
|
64
|
+
DTEND;TZID=Europe/Berlin:20140729T100000
|
54
65
|
TRANSP:OPAQUE
|
55
66
|
CLASS:PUBLIC
|
56
67
|
END:VEVENT
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kalindar
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Felix Wolfsteller
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-08-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ri_cal
|
@@ -119,7 +119,9 @@ files:
|
|
119
119
|
- .rspec
|
120
120
|
- .travis.yml
|
121
121
|
- Gemfile
|
122
|
+
- Gemfile.lock
|
122
123
|
- LICENSE.txt
|
124
|
+
- Procfile
|
123
125
|
- README.md
|
124
126
|
- Rakefile
|
125
127
|
- config.json
|
@@ -127,6 +129,7 @@ files:
|
|
127
129
|
- kalindar.gemspec
|
128
130
|
- lib/kalindar.rb
|
129
131
|
- lib/kalindar/app.rb
|
132
|
+
- lib/kalindar/calendar.rb
|
130
133
|
- lib/kalindar/event.rb
|
131
134
|
- lib/kalindar/event_calendar.rb
|
132
135
|
- lib/kalindar/locales/de.yml
|
@@ -140,13 +143,14 @@ files:
|
|
140
143
|
- lib/kalindar/public/right.js
|
141
144
|
- lib/kalindar/version.rb
|
142
145
|
- lib/kalindar/views/.layout.haml.swp
|
146
|
+
- lib/kalindar/views/edit_event.slim
|
143
147
|
- lib/kalindar/views/event_list.slim
|
144
148
|
- lib/kalindar/views/layout.slim
|
145
149
|
- lib/kalindar/views/new_event.slim
|
146
150
|
- spec/kalindar_spec.rb
|
147
151
|
- spec/spec_helper.rb
|
148
152
|
- spec/testcal.ics
|
149
|
-
homepage:
|
153
|
+
homepage: https://github.com/fwolfst/kalindar
|
150
154
|
licenses:
|
151
155
|
- MIT
|
152
156
|
metadata: {}
|