FEATURE: .ics support for events (#32)

FEATURE: .ics support for events (#32)

diff --git a/app/controllers/discourse_post_event/events_controller.rb b/app/controllers/discourse_post_event/events_controller.rb
index b2d50fe..6671e69 100644
--- a/app/controllers/discourse_post_event/events_controller.rb
+++ b/app/controllers/discourse_post_event/events_controller.rb
@@ -2,21 +2,24 @@
 
 module DiscoursePostEvent
   class EventsController < DiscoursePostEventController
+    skip_before_action :check_xhr, only: [ :index ], if: :ics_request?
+
     def index
-      topics = Topic.listable_topics.secured(guardian)
-      pms = Topic.private_messages_for_user(current_user)
-      events = Event
-        .visible
-        .not_expired
-        .joins(post: :topic)
-        .merge(Post.secured(guardian))
-        .merge(topics.or(pms).distinct)
-        .order(starts_at: :asc)
-
-      render json: ActiveModel::ArraySerializer.new(
-        events,
-        each_serializer: EventSerializer,
-        scope: guardian).as_json
+      @events = DiscoursePostEvent::EventFinder.search(current_user, filtered_events_params)
+
+      respond_to do |format|
+        format.ics {
+          filename = "events-#{@events.map(&:id).join('-')}"
+          response.headers['Content-Disposition'] = "attachment; filename=\"#{filename}.#{request.format.symbol}\""
+        }
+
+        format.json do
+          render json: ActiveModel::ArraySerializer.new(
+            @events,
+            each_serializer: EventSerializer,
+            scope: guardian).as_json
+        end
+      end
     end
 
     def invite
@@ -107,5 +110,13 @@ module DiscoursePostEvent
           raw_invitees: []
         )
     end
+
+    def ics_request?
+      request.format.symbol == :ics
+    end
+
+    def filtered_events_params
+      params.permit(:post_id)
+    end
   end
 end
diff --git a/app/views/discourse_post_event/events/index.ics.erb b/app/views/discourse_post_event/events/index.ics.erb
new file mode 100644
index 0000000..256d4af
--- /dev/null
+++ b/app/views/discourse_post_event/events/index.ics.erb
@@ -0,0 +1,16 @@
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Discourse//<%= Discourse.current_hostname %>//<%= Discourse.full_version %>//EN
+<% @events.each do |event| %>
+BEGIN:VEVENT
+ORGANIZER:MAILTO:<%= event.post.user.email %>
+UID:bookmark_reminder_#<%= event.id %>@<%= Discourse.current_hostname %>
+DTSTAMP:<%= event.starts_at.strftime("%Y%m%dT%H%M%SZ") %>
+DTSTART:<%= event.ends_at.presence ? event.ends_at.strftime("%Y%m%dT%H%M%SZ") : event.starts_at.strftime("%Y%m%dT%H%M%SZ") %>
+DTEND:<%= event.ends_at.presence ? (event.ends_at + 1.hour).strftime("%Y%m%dT%H%M%SZ") : (event.starts_at + 1.hour).strftime("%Y%m%dT%H%M%SZ") %>
+SUMMARY:<%= event.name.presence || event.post.topic.title %>
+DESCRIPTION:<%= PrettyText.format_for_email(event.post.excerpt, event.post).html_safe %>
+URL:<%= Discourse.base_url %>/t/-/<%= event.post.topic_id %>/<%= event.post.post_number %>
+END:VEVENT
+<% end %>
+END:VCALENDAR
diff --git a/app/views/discourse_post_event/upcoming_events/index.html.erb b/app/views/discourse_post_event/upcoming_events/index.html.erb
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/views/discourse_post_event/upcoming_events/index.html.erb
diff --git a/assets/javascripts/discourse/lib/google-calendar.js.es6 b/assets/javascripts/discourse/lib/google-calendar.js.es6
deleted file mode 100644
index 4ead2f1..0000000
--- a/assets/javascripts/discourse/lib/google-calendar.js.es6
+++ /dev/null
@@ -1,22 +0,0 @@
-import EmberObject from "@ember/object";
-
-export default EmberObject.extend({
-  init(params = {}) {
-    this.title = params.title;
-    this.startsAt = moment(params.startsAt);
-    this.endsAt = params.endsAt ? moment(params.endsAt) : null;
-  },
-
-  generateLink() {
-    const title = encodeURIComponent(this.title);
-    let dates = [this._formatDate(this.startsAt)];
-    dates.push(this._formatDate(this.endsAt || this.startsAt));
-    dates = `dates=${dates.join("/")}`;
-
-    return `https://www.google.com/calendar/event?action=TEMPLATE&text=${title}&${dates}`;
-  },
-
-  _formatDate(date) {
-    return date.toISOString().replace(/-|:|\.\d\d\d/g, "");
-  }
-});
diff --git a/assets/javascripts/discourse/widgets/add-to-calendar-button.js.es6 b/assets/javascripts/discourse/widgets/add-to-calendar-button.js.es6
index 070690f..3d11872 100644
--- a/assets/javascripts/discourse/widgets/add-to-calendar-button.js.es6
+++ b/assets/javascripts/discourse/widgets/add-to-calendar-button.js.es6
@@ -6,11 +6,10 @@ export default createWidget("add-to-calendar-button", {
 
   click(event) {
     event.preventDefault();
-    this.sendWidgetAction("addToGoogleCalendar");
+    this.sendWidgetAction("addToCalendar");
   },
 
   template: hbs`
-    {{d-icon "google"}}
     <span class="label">
       {{i18n "discourse_post_event.event_ui.add_to_calendar"}}
     </span>
diff --git a/assets/javascripts/discourse/widgets/discourse-post-event.js.es6 b/assets/javascripts/discourse/widgets/discourse-post-event.js.es6
index 0c9437e..1dc2fe1 100644
--- a/assets/javascripts/discourse/widgets/discourse-post-event.js.es6
+++ b/assets/javascripts/discourse/widgets/discourse-post-event.js.es6
@@ -5,7 +5,6 @@ import EmberObject from "@ember/object";
 import showModal from "discourse/lib/show-modal";
 import hbs from "discourse/widgets/hbs-compiler";
 import { createWidget } from "discourse/widgets/widget";
-import GoogleCalendar from "discourse/plugins/discourse-calendar/discourse/lib/google-calendar";
 import { routeAction } from "discourse/helpers/route-action";
 
 export default createWidget("discourse-post-event", {
@@ -89,17 +88,10 @@ export default createWidget("discourse-post-event", {
     ).call();
   },
 
-  addToGoogleCalendar() {
-    const link = GoogleCalendar.create({
-      title:
-        this.state.eventModel.name ||
-        this._cleanTopicTitle(
-          this.state.eventModel.post.topic.title,
-          this.state.eventModel.starts_at
-        ),
-      startsAt: this.state.eventModel.starts_at,
-      endsAt: this.state.eventModel.ends_at
-    }).generateLink();
+  addToCalendar() {
+    const link = Discourse.getURL(
+      `/discourse-post-event/events.ics?post_id=${this.state.eventModel.id}`
+    );
 
     window.open(link, "_blank", "noopener");
   },
@@ -195,7 +187,7 @@ export default createWidget("discourse-post-event", {
       }}
 
       <hr />
-      
+
       {{attach widget="discourse-post-event-invitees"
         attrs=(hash eventModel=state.eventModel)
       }}
diff --git a/lib/discourse_post_event/event_finder.rb b/lib/discourse_post_event/event_finder.rb
new file mode 100644
index 0000000..7029750
--- /dev/null
+++ b/lib/discourse_post_event/event_finder.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module DiscoursePostEvent
+  class EventFinder
+    def self.search(user, params = {})
+      guardian = Guardian.new(user)
+      topics = listable_topics(guardian)
+      pms = private_messages(user)
+
+      events = DiscoursePostEvent::Event
+        .visible
+        .not_expired
+
+      if params[:post_id]
+        events = events.where(id: Array(params[:post_id]))
+      end
+
+      events.joins(post: :topic)
+        .merge(Post.secured(guardian))
+        .merge(topics.or(pms).distinct)
+        .order(starts_at: :asc)
+    end
+
+    private
+
+    def self.listable_topics(guardian)
+      Topic.listable_topics.secured(guardian)
+    end
+
+    def self.private_messages(user)
+      Topic.private_messages_for_user(user)
+    end
+  end
+end
diff --git a/plugin.rb b/plugin.rb
index 054aa93..86503c9 100644
--- a/plugin.rb
+++ b/plugin.rb
@@ -25,7 +25,6 @@ register_svg_icon "fas fa-calendar-day"
 register_svg_icon "fas fa-question"
 register_svg_icon "fas fa-clock"
 register_svg_icon "fas fa-clock"
-register_svg_icon "fab fa-google"
 
 after_initialize do
   module ::DiscourseCalendar
@@ -81,10 +80,13 @@ after_initialize do
     "../app/controllers/discourse_post_event/upcoming_events_controller.rb",

[... diff too long, it was truncated ...]

GitHub sha: e0146360

This commit appears in #32 which was merged by jjaffeux.

A nice feature indeed! Also I appreciate all your tests about buying boats. :motor_boat:

1 Like