FEATURE: more dynamic event dates in topic titles (#61)

FEATURE: more dynamic event dates in topic titles (#61)

  • always auto updated
  • correctly computed, based on past/current/future event
  • displays a green indicator when an event is on going
diff --git a/app/models/discourse_post_event/event.rb b/app/models/discourse_post_event/event.rb
index d5cc189..c79afa9 100644
--- a/app/models/discourse_post_event/event.rb
+++ b/app/models/discourse_post_event/event.rb
@@ -16,6 +16,10 @@ module DiscoursePostEvent
         TopicCustomField.where(
           topic_id: self.post.topic_id, name: TOPIC_POST_EVENT_STARTS_AT
         ).delete_all
+
+        TopicCustomField.where(
+          topic_id: self.post.topic_id, name: TOPIC_POST_EVENT_ENDS_AT
+        ).delete_all
       end
     end
 
@@ -30,7 +34,18 @@ module DiscoursePostEvent
             created_at: Time.now,
             updated_at: Time.now
           },
-          unique_by: %i[name topic_id]
+          unique_by: 'idx_topic_custom_fields_topic_post_event_starts_at'
+        )
+
+        TopicCustomField.upsert(
+          {
+            topic_id: self.post.topic_id,
+            name: TOPIC_POST_EVENT_ENDS_AT,
+            value: self.ends_at,
+            created_at: Time.now,
+            updated_at: Time.now
+          },
+          unique_by: 'idx_topic_custom_fields_topic_post_event_ends_at'
         )
       end
     end
diff --git a/assets/javascripts/initializers/decorate-topic-title.js.es6 b/assets/javascripts/initializers/decorate-topic-title.js.es6
index 8fcaf4b..2b96cb1 100644
--- a/assets/javascripts/initializers/decorate-topic-title.js.es6
+++ b/assets/javascripts/initializers/decorate-topic-title.js.es6
@@ -1,57 +1,34 @@
-import I18n from "I18n";
 import { withPluginApi } from "discourse/lib/plugin-api";
-import guessDateFormat from "discourse/plugins/discourse-calendar/lib/guess-best-date-format";
+import eventRelativeDate from "discourse/plugins/discourse-calendar/lib/event-relative-date";
 
 function initializeDecorateTopicTitle(api) {
   api.decorateTopicTitle((topic, node, topicTitleType) => {
-    const startsAt = topic.event_starts_at;
-
-    if (startsAt) {
-      const date = moment.utc(startsAt);
-
-      if (topicTitleType === "topic-list-item-title") {
-        if (node.querySelector(".event-date-container")) {
-          return;
-        }
-
-        const formattedDate = date
-          .tz(moment.tz.guess())
-          .format(guessDateFormat(date));
-        if (moment().isBefore(date)) {
-          node.title = I18n.t("discourse_post_event.topic_title.starts_at", {
-            date: formattedDate,
-          });
-        } else {
-          node.title = I18n.t("discourse_post_event.topic_title.ended_at", {
-            date: formattedDate,
-          });
-        }
-
-        const eventdateContainer = document.createElement("div");
-        eventdateContainer.classList.add("event-date-container");
+    if (!topic.event_starts_at || !topic.event_ends_at) {
+      return;
+    }
 
-        const eventDate = document.createElement("span");
-        eventDate.classList.add("event-date", "relative-future-date");
-        eventDate.dataset.time = date.tz(moment.tz.guess()).valueOf();
+    if (
+      topicTitleType === "topic-list-item-title" ||
+      topicTitleType === "header-title"
+    ) {
+      if (node.querySelector(".event-date-container")) {
+        // we already injected the event
+        return;
+      }
 
-        eventDate.innerText = date.tz(moment.tz.guess()).from(moment());
+      const eventdateContainer = document.createElement("div");
+      eventdateContainer.classList.add("event-date-container");
 
-        eventdateContainer.appendChild(eventDate);
-        node.appendChild(eventdateContainer);
-      }
+      const eventDate = document.createElement("span");
+      eventDate.classList.add("event-date", "event-relative-date");
+      eventDate.dataset.starts_at = topic.event_starts_at;
+      eventDate.dataset.ends_at = topic.event_ends_at;
 
-      if (topicTitleType === "header-title") {
-        if (node.querySelector(".event-date")) {
-          return;
-        }
+      eventdateContainer.appendChild(eventDate);
+      node.appendChild(eventdateContainer);
 
-        const child = document.createElement("span");
-        child.classList.add("event-date");
-        child.innerText = date
-          .tz(moment.tz.guess())
-          .format(guessDateFormat(date));
-        node.appendChild(child);
-      }
+      // we force a first computation, as waiting for the auto update might take time
+      eventRelativeDate(eventDate);
     }
   });
 }
diff --git a/assets/javascripts/initializers/event-relative-date.js.es6 b/assets/javascripts/initializers/event-relative-date.js.es6
new file mode 100644
index 0000000..1be669b
--- /dev/null
+++ b/assets/javascripts/initializers/event-relative-date.js.es6
@@ -0,0 +1,37 @@
+import { isTesting } from "discourse-common/config/environment";
+import { later, cancel } from "@ember/runloop";
+import eventRelativeDate from "discourse/plugins/discourse-calendar/lib/event-relative-date";
+
+function computeRelativeEventDates() {
+  document
+    .querySelectorAll(".event-relative-date.topic-list")
+    .forEach((dateContainer) => eventRelativeDate(dateContainer));
+}
+
+export default {
+  name: "event-future-date",
+
+  initialize() {
+    computeRelativeEventDates();
+
+    if (!isTesting()) {
+      this._tick();
+    }
+  },
+
+  teardown() {
+    if (this._interval) {
+      cancel(this._interval);
+      this._interval = null;
+    }
+  },
+
+  _tick() {
+    this._interval && cancel(this._interval);
+
+    this._interval = later(() => {
+      computeRelativeEventDates();
+      this._tick();
+    }, 60 * 1000);
+  },
+};
diff --git a/assets/javascripts/initializers/future-relative-date.js.es6 b/assets/javascripts/initializers/future-relative-date.js.es6
deleted file mode 100644
index 65f8c7c..0000000
--- a/assets/javascripts/initializers/future-relative-date.js.es6
+++ /dev/null
@@ -1,33 +0,0 @@
-import { isTesting } from "discourse-common/config/environment";
-import { later, cancel } from "@ember/runloop";
-
-export default {
-  name: "relative-future-date",
-
-  initialize() {
-    if (!isTesting()) {
-      this._tick();
-    }
-  },
-
-  teardown() {
-    if (this._interval) {
-      cancel(this._interval);
-      this._interval = null;
-    }
-  },
-
-  _tick() {
-    this._interval && cancel(this._interval);
-
-    this._interval = later(() => {
-      document.querySelectorAll(".relative-future-date").forEach((date) => {
-        date.innerText = moment(parseInt(date.dataset.time, 10))
-          .tz(moment.tz.guess())
-          .from(moment());
-      });
-
-      this._tick();
-    }, 30 * 1000);
-  },
-};
diff --git a/assets/javascripts/lib/event-relative-date.js.es6 b/assets/javascripts/lib/event-relative-date.js.es6
new file mode 100644
index 0000000..b4f138d
--- /dev/null
+++ b/assets/javascripts/lib/event-relative-date.js.es6
@@ -0,0 +1,50 @@
+import I18n from "I18n";
+
+function _computeCurrentEvent(container, endsAt) {
+  const indicator = document.createElement("div");
+  indicator.classList.add("indicator");
+  container.appendChild(indicator);
+
+  const text = document.createElement("span");
+  text.classList.add("text");
+  text.innerText = I18n.t("discourse_post_event.topic_title.ends_in_duration", {
+    duration: endsAt.from(moment()),
+  });
+  container.appendChild(text);
+}
+
+function _computePastEvent(container, endsAt) {
+  container.innerText = endsAt.from(moment());
+}
+
+function _computeFutureEvent(container, startsAt) {
+  container.innerText = startsAt.from(moment());
+}
+
+export default function eventRelativeDate(container) {
+  container.classList.remove("past", "current", "future");
+  container.innerHTML = "";
+
+  const startsAt = moment
+    .utc(container.dataset.starts_at)
+    .tz(moment.tz.guess());
+  const endsAt = moment.utc(container.dataset.ends_at).tz(moment.tz.guess());
+
+  if (startsAt.isAfter(moment()) && endsAt.isAfter(moment())) {
+    container.classList.add("future");
+    _computeFutureEvent(container, startsAt);
+    return;
+  }
+

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

GitHub sha: 95603501

1 Like

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