kalindar 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: {}
|