From 62d888a020ffa7397fd1fb36c72ae71eb9682177 Mon Sep 17 00:00:00 2001 From: Marvin Frederickson Date: Tue, 11 Sep 2018 17:03:53 -0800 Subject: [PATCH] fixes #18, partial on #19, adds two new views --- Gemfile.lock | 272 +++++++++++++++++++++++--------------- README.md | 57 ++++++++ app/models/calendar.rb | 103 +++++++++++---- concerto_calendar.gemspec | 3 +- 4 files changed, 303 insertions(+), 132 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index ccd2edd..1d48f71 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,123 +1,179 @@ PATH remote: . specs: - concerto_calendar (0.0.1) - google-api-client (~> 0.9.1) - rails (~> 3.2.11) + concerto_calendar (0.0.9) + google-api-client (~> 0.9) + icalendar + rails GEM remote: http://rubygems.org/ specs: - actionmailer (3.2.13) - actionpack (= 3.2.13) - mail (~> 2.5.3) - actionpack (3.2.13) - activemodel (= 3.2.13) - activesupport (= 3.2.13) - builder (~> 3.0.0) - erubis (~> 2.7.0) - journey (~> 1.0.4) - rack (~> 1.4.5) - rack-cache (~> 1.2) - rack-test (~> 0.6.1) - sprockets (~> 2.2.1) - activemodel (3.2.13) - activesupport (= 3.2.13) - builder (~> 3.0.0) - activerecord (3.2.13) - activemodel (= 3.2.13) - activesupport (= 3.2.13) - arel (~> 3.0.2) - tzinfo (~> 0.3.29) - activeresource (3.2.13) - activemodel (= 3.2.13) - activesupport (= 3.2.13) - activesupport (3.2.13) - i18n (= 0.6.1) - multi_json (~> 1.0) - addressable (2.3.4) - arel (3.0.2) - autoparse (0.3.3) - addressable (>= 2.3.1) - extlib (>= 0.9.15) - multi_json (>= 1.0.0) - builder (3.0.4) - erubis (2.7.0) - extlib (0.9.16) - faraday (0.8.7) - multipart-post (~> 1.1) - google-api-client (0.6.4) - addressable (>= 2.3.2) - autoparse (>= 0.3.3) - extlib (>= 0.9.15) - faraday (~> 0.8.4) - jwt (>= 0.1.5) - launchy (>= 2.1.1) - multi_json (>= 1.0.0) - signet (~> 0.4.5) - uuidtools (>= 2.1.0) - hike (1.2.3) - i18n (0.6.1) - journey (1.0.4) - json (1.8.0) - jwt (0.1.8) - multi_json (>= 1.5) - launchy (2.3.0) + actioncable (5.2.1) + actionpack (= 5.2.1) + nio4r (~> 2.0) + websocket-driver (>= 0.6.1) + actionmailer (5.2.1) + actionpack (= 5.2.1) + actionview (= 5.2.1) + activejob (= 5.2.1) + mail (~> 2.5, >= 2.5.4) + rails-dom-testing (~> 2.0) + actionpack (5.2.1) + actionview (= 5.2.1) + activesupport (= 5.2.1) + rack (~> 2.0) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + actionview (5.2.1) + activesupport (= 5.2.1) + builder (~> 3.1) + erubi (~> 1.4) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.3) + activejob (5.2.1) + activesupport (= 5.2.1) + globalid (>= 0.3.6) + activemodel (5.2.1) + activesupport (= 5.2.1) + activerecord (5.2.1) + activemodel (= 5.2.1) + activesupport (= 5.2.1) + arel (>= 9.0) + activestorage (5.2.1) + actionpack (= 5.2.1) + activerecord (= 5.2.1) + marcel (~> 0.3.1) + activesupport (5.2.1) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 0.7, < 2) + minitest (~> 5.1) + tzinfo (~> 1.1) + addressable (2.5.2) + public_suffix (>= 2.0.2, < 4.0) + arel (9.0.0) + builder (3.2.3) + concurrent-ruby (1.0.5) + crass (1.0.4) + declarative (0.0.9) + declarative-option (0.1.0) + erubi (1.7.1) + faraday (0.9.2) + multipart-post (>= 1.2, < 3) + globalid (0.4.1) + activesupport (>= 4.2.0) + google-api-client (0.10.3) addressable (~> 2.3) - mail (2.5.4) - mime-types (~> 1.16) - treetop (~> 1.4.8) - mime-types (1.23) - multi_json (1.7.6) - multipart-post (1.2.0) - polyglot (0.3.3) - rack (1.4.5) - rack-cache (1.2) - rack (>= 0.4) - rack-ssl (1.3.3) - rack - rack-test (0.6.2) - rack (>= 1.0) - rails (3.2.13) - actionmailer (= 3.2.13) - actionpack (= 3.2.13) - activerecord (= 3.2.13) - activeresource (= 3.2.13) - activesupport (= 3.2.13) - bundler (~> 1.0) - railties (= 3.2.13) - railties (3.2.13) - actionpack (= 3.2.13) - activesupport (= 3.2.13) - rack-ssl (~> 1.3.2) + googleauth (~> 0.5) + httpclient (~> 2.7) + hurley (~> 0.1) + memoist (~> 0.11) + mime-types (>= 1.6) + representable (~> 3.0) + retriable (>= 2.0, < 4.0) + googleauth (0.5.1) + faraday (~> 0.9) + jwt (~> 1.4) + logging (~> 2.0) + memoist (~> 0.12) + multi_json (~> 1.11) + os (~> 0.9) + signet (~> 0.7) + httpclient (2.8.3) + hurley (0.2) + i18n (1.1.0) + concurrent-ruby (~> 1.0) + icalendar (2.5.0) + ice_cube (~> 0.16) + ice_cube (0.16.3) + jwt (1.5.6) + little-plugger (1.1.4) + logging (2.2.2) + little-plugger (~> 1.1) + multi_json (~> 1.10) + loofah (2.2.2) + crass (~> 1.0.2) + nokogiri (>= 1.5.9) + mail (2.7.0) + mini_mime (>= 0.1.1) + marcel (0.3.2) + mimemagic (~> 0.3.2) + memoist (0.16.0) + method_source (0.9.0) + mime-types (3.2.2) + mime-types-data (~> 3.2015) + mime-types-data (3.2018.0812) + mimemagic (0.3.2) + mini_mime (1.0.1) + mini_portile2 (2.3.0) + minitest (5.11.3) + multi_json (1.13.1) + multipart-post (2.0.0) + nio4r (2.3.1) + nokogiri (1.8.4) + mini_portile2 (~> 2.3.0) + os (0.9.6) + public_suffix (3.0.3) + rack (2.0.5) + rack-test (1.1.0) + rack (>= 1.0, < 3) + rails (5.2.1) + actioncable (= 5.2.1) + actionmailer (= 5.2.1) + actionpack (= 5.2.1) + actionview (= 5.2.1) + activejob (= 5.2.1) + activemodel (= 5.2.1) + activerecord (= 5.2.1) + activestorage (= 5.2.1) + activesupport (= 5.2.1) + bundler (>= 1.3.0) + railties (= 5.2.1) + sprockets-rails (>= 2.0.0) + rails-dom-testing (2.0.3) + activesupport (>= 4.2.0) + nokogiri (>= 1.6) + rails-html-sanitizer (1.0.4) + loofah (~> 2.2, >= 2.2.2) + railties (5.2.1) + actionpack (= 5.2.1) + activesupport (= 5.2.1) + method_source rake (>= 0.8.7) - rdoc (~> 3.4) - thor (>= 0.14.6, < 2.0) - rake (10.0.4) - rdoc (3.12.2) - json (~> 1.4) - signet (0.4.5) - addressable (>= 2.2.3) - faraday (~> 0.8.1) - jwt (>= 0.1.5) - multi_json (>= 1.0.0) - sprockets (2.2.2) - hike (~> 1.2) - multi_json (~> 1.0) - rack (~> 1.0) - tilt (~> 1.1, != 1.3.0) - sqlite3 (1.3.7) - thor (0.18.1) - tilt (1.4.1) - treetop (1.4.14) - polyglot - polyglot (>= 0.3.1) - tzinfo (0.3.37) - uuidtools (2.1.4) + thor (>= 0.19.0, < 2.0) + rake (12.3.1) + representable (3.0.4) + declarative (< 0.1.0) + declarative-option (< 0.2.0) + uber (< 0.2.0) + retriable (3.1.1) + signet (0.7.3) + addressable (~> 2.3) + faraday (~> 0.9) + jwt (~> 1.5) + multi_json (~> 1.10) + sprockets (3.7.2) + concurrent-ruby (~> 1.0) + rack (> 1, < 3) + sprockets-rails (3.2.1) + actionpack (>= 4.0) + activesupport (>= 4.0) + sprockets (>= 3.0.0) + thor (0.20.0) + thread_safe (0.3.6) + tzinfo (1.2.5) + thread_safe (~> 0.1) + uber (0.1.0) + websocket-driver (0.6.5) + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.3) PLATFORMS ruby DEPENDENCIES concerto_calendar! - sqlite3 + +BUNDLED WITH + 1.16.3 diff --git a/README.md b/README.md index f64fc52..102c6ca 100644 --- a/README.md +++ b/README.md @@ -4,3 +4,60 @@ This plugin provides support for displaying Google Calendar or iCal entries in C To install this plugin, go to the Plugin management page in concerto, select RubyGems as the source and "concerto_calendar" as the gem name. Concerto 2 Calendar is licensed under the Apache License, Version 2.0. + +## Frontend Styling the 'List Details' Output Format + +Here is the styling that I like to use for output of this type. + +``` +@import url('https://fonts.googleapis.com/css?family=Kalam:300'); + +ul.cal * { + font-family: 'Kalam', cursive; +} + +ul.cal li { + break-inside: avoid; + margin-top: 0; +} + +ul.events { + list-style: none; + padding-left: 0; + margin-bottom: .25em; +} + +ul.events li { + margin-bottom: .5em; +} + +li.event-date h2 { + padding-bottom: .1em; + margin-bottom: .25em; + margin-top: 0; + border-bottom: solid 2px #222; +} + +ul.cal { + list-style: none; + column-count: 3; + column-gap: 2em; + padding: 0 1em; +} + +.event-time { + display: inline-block; +} + +.event-title { + display: inline-block; + font-weight: bold; +} + +.event-location, .event-description { + display: block; + color: #777; + font-size: 0.8em; + padding-left: 3em; +} +``` diff --git a/app/models/calendar.rb b/app/models/calendar.rb index e312e80..df4559d 100644 --- a/app/models/calendar.rb +++ b/app/models/calendar.rb @@ -36,7 +36,9 @@ def add_item(name, description, location, start_time, end_time) DISPLAY_FORMATS = { "List (Multiple)" => "headlines", - "Detailed (Single)" => "detailed" + "List (Custom)" => "custom_list", + "Detailed (Single)" => "detailed", + 'Detailed List' => 'detailed_list' } CALENDAR_SOURCES = { # exclude RSS and ATOM since cant get individual fields "Google" => "google", @@ -70,6 +72,23 @@ def build_content htmltext.data = "

#{result.name}

#{items_to_html(items, day_format, time_format)}" contents << htmltext end + when 'custom_list' # 5 items per entry, titles only + result.items.each_slice(5).with_index do |items, index| + htmltext = HtmlText.new() + htmltext.name = "#{result.name} (#{index+1})" + # heredoc terminator enclosed in singlequotes to prevent interpolation + item_template = <<-'EOT' +
+
#{title}
+
#{date}
+
#{time}
+
#{location}
+
#{description}
+
+ EOT + htmltext.data = "

#{result.name}

#{items_to_custom_html(items, day_format, time_format, item_template)}
" + contents << htmltext + end when 'detailed' # each item is a separate entry, title and description result.items.each_with_index do |item, index| htmltext = HtmlText.new() @@ -77,6 +96,11 @@ def build_content htmltext.data = item_to_html(item, day_format, time_format) contents << htmltext end + when 'detailed_list' # all items in one entry, with details (and classes for css) + htmltext = HtmlText.new() + htmltext.name = result.name + htmltext.data = items_to_list_html(result.items, day_format, time_format) + contents << htmltext else raise ArgumentError, 'Unexpected output format for Calendar feed.' end @@ -90,17 +114,17 @@ def fetch_calendar client_key = self.config['api_key'] calendar_id = self.config['calendar_id'] calendar_source = self.config['calendar_source'] - start_date = self.config['start_date'].strip.empty? ? Clock.time.beginning_of_day.iso8601 : self.config['start_date'].to_time.beginning_of_day.iso8601 + start_date = self.config['start_date'].strip.empty? ? Clock.time.beginning_of_day : self.config['start_date'].to_time.beginning_of_day # end_date is not used by the google api, as the resulting behavior is unexpected - end_date = self.config['end_date'].strip.empty? ? (start_date.to_time.beginning_of_day + self.config['days_ahead'].to_i.days).end_of_day.iso8601 : self.config['end_date'].to_time.beginning_of_day.iso8601 + end_date = self.config['end_date'].strip.empty? ? (start_date.to_time.beginning_of_day + self.config['days_ahead'].to_i.days).end_of_day : self.config['end_date'].to_time.end_of_day case calendar_source when 'google' if !client_key.empty? # ---------------------------------- google calendar api v3 via client api - require 'google/apis/calendar_v3' + require 'google/apis/calendar_v3' - client = Google::Apis::CalendarV3::CalendarService.new + client = Google::Apis::CalendarV3::CalendarService.new client.key = client_key begin @@ -108,7 +132,7 @@ def fetch_calendar max_results: self.config['max_results'], single_events: true, order_by: 'startTime', - time_min: start_date) + time_min: start_date.iso8601) # convert to common data structure #result.error_message = tmp.error_message if tmp.error? @@ -131,35 +155,25 @@ def fetch_calendar # so respect self.config[max_results] and start_date and end_date (which incorporates the days ahead) require 'open-uri' require 'icalendar' + require 'icalendar/recurrence' begin url = self.config['calendar_url'] calendars = nil open(URI.parse(url)) do |cal| - calendars = Icalendar.parse(cal) + calendars = Icalendar::Calendar.parse(cal) end max_results = self.config['max_results'].to_i - result.name = self.name # iCal doesn't provide a calendar name, so use the user's provided name + result.name = self.name # iCal doesn't provide a calendar name, so use the user's provided name + calendars.first.events.each do |item| title = item.summary description = item.description location = item.location - # behave a little differently depending on whether or not our iCal has a timezone defined - # this behavior is somewhat lazy/sloppy in that we're assuming the existence of a defined timezone means - # that times are local ('correct'), whereas the absence of a timezone means the data is - # likely in UTC/Zulu time - if calendars.first.has_timezone? - item_start_time = item.dtstart.to_datetime unless item.dtstart.nil? - item_end_time = item.dtend.to_datetime unless item.dtend.nil? - else - item_start_time = Time.zone.parse(item.dtstart.to_s).to_datetime unless item.dtstart.nil? - item_end_time = Time.zone.parse(item.dtend.to_s).to_datetime unless item.dtend.nil? - end - item_end_time = nil if item_end_time == item_start_time - # make sure the item's start date is within the specified range - if item_start_time >= start_date && item_start_time < end_date - result.add_item(title, description, location, item_start_time, item_end_time) + + item.occurrences_between(start_date, end_date).each do |occurrence| + result.add_item(title, description, location, occurrence.start_time.in_time_zone(Time.zone), occurrence.end_time.in_time_zone(Time.zone)) end end @@ -217,6 +231,49 @@ def items_to_html(items, day_format, time_format) return html.join("") end + # display date (only when it changes) / times with title... + def items_to_custom_html(items, day_format, time_format, item_template) + html = [] + items.each do |item| + start_time = item.start_time.strftime(time_format) + end_time = item.end_time.strftime(time_format) unless item.end_time.nil? + + html << item_template.gsub('#{title}', item.name).gsub('#{date}', item.start_time.strftime(day_format)).gsub('#{time}', (end_time.nil? || start_time == end_time) ? start_time : "#{start_time} - #{end_time}").gsub('#{location}', item.location) + ######################################## TODO BUGGY!!! + #.gsub('#{description}', item.description.present? ? item.description : '') + end + return html.join("") + end + + # list format + def items_to_list_html(items, day_format, time_format) + html = [] + # wrap the whole thing so users can style it + html << "" + return html.join("") + end + # calendar api parameters and preferred view (output_format) def self.form_attributes attributes = super() diff --git a/concerto_calendar.gemspec b/concerto_calendar.gemspec index 91d5215..41bc3dc 100644 --- a/concerto_calendar.gemspec +++ b/concerto_calendar.gemspec @@ -19,6 +19,7 @@ Gem::Specification.new do |s| s.add_dependency "rails" s.add_dependency "google-api-client", "~> 0.9" - s.add_dependency "icalendar" + s.add_dependency "icalendar", '~> 2.5' + s.add_dependency "icalendar-recurrence", '~> 1.1.2' end