diff --git a/app/assets/stylesheets/notifications.css b/app/assets/stylesheets/notifications.css index 878eb25c5..b50e35a2c 100644 --- a/app/assets/stylesheets/notifications.css +++ b/app/assets/stylesheets/notifications.css @@ -40,6 +40,13 @@ } } + /* Grouped notifications + /* ------------------------------------------------------------------------ */ + + .notification--grouped { + border: 3px solid red; + } + /* Read items /* ------------------------------------------------------------------------ */ diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb index c557624be..69eab11a6 100644 --- a/app/helpers/notifications_helper.rb +++ b/app/helpers/notifications_helper.rb @@ -20,7 +20,10 @@ module NotificationsHelper end def notification_tag(notification, &) - tag.div id: dom_id(notification), class: "tray__item tray__item--notification", data: { navigable_list_target: "item" } do + tag.div id: dom_id(notification), class: "tray__item tray__item--notification", data: { + navigable_list_target: "item", + notifications_tray_target: "notification", + card_id: notification.card.id, timestamp: notification.created_at.to_i } do concat( link_to(notification, class: [ "card card--notification", { "card--closed": notification.card.closed? }, { "unread": !notification.read? } ], diff --git a/app/javascript/controllers/notifications_tray_controller.js b/app/javascript/controllers/notifications_tray_controller.js new file mode 100644 index 000000000..b356c4b20 --- /dev/null +++ b/app/javascript/controllers/notifications_tray_controller.js @@ -0,0 +1,57 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static targets = [ "notification", "hiddenNotifications" ] + static classes = [ "grouped" ] + + notificationTargetConnected(notification) { + this.#groupNotificationIfNeeded(notification) + } + + #groupNotificationIfNeeded(notification) { + const groupedNotifications = this.#groupedNotificationsFor(notification) + + if (groupedNotifications.length > 1) { + this.#renderGroup(groupedNotifications) + } + } + + #groupedNotificationsFor(notification) { + const cardId = notification.dataset.cardId + return this.notificationTargets + .filter(notification => notification.dataset.cardId === cardId) + .sort((notification_1, notification_2) => parseInt(notification_1.dataset.timestamp) - parseInt(notification_2.dataset.timestamp)) + } + + #renderGroup(groupedNotifications) { + this.#showAsGrouped(groupedNotifications[0], groupedNotifications.length) + groupedNotifications.slice(1).forEach(notification => this.#hideInGroup(notification)) + } + + #showAsGrouped(notification, size) { + notification.classList.add(this.groupedClass) + this.#showGroupedNotification(notification) + this.#setGroupCount(notification, size) + } + + #hideInGroup(notification) { + this.#hideGroupedNotification(notification) + this.#setGroupCount(notification, "") + } + + // We use a hidden container instead of hiding the notifications directly so that the CSS that sort the + // tray element indexes doesn't get messed up. + #showGroupedNotification(notification) { + if (notification.parentElement === this.hiddenNotificationsTarget) { + this.hiddenNotificationsTarget.parentElement.insertBefore(notification, this.hiddenNotificationsTarget) + } + } + + #hideGroupedNotification(notification) { + this.hiddenNotificationsTarget.appendChild(notification) + } + + #setGroupCount(notification, count) { + notification.querySelector("[data-group-count]").textContent = count + } +} diff --git a/app/views/notifications/_notification.html.erb b/app/views/notifications/_notification.html.erb index b945960b8..49abafcb6 100644 --- a/app/views/notifications/_notification.html.erb +++ b/app/views/notifications/_notification.html.erb @@ -3,6 +3,7 @@
<%= render "notifications/notification/#{notification.source_type.underscore}/header", notification: notification %> +
diff --git a/app/views/notifications/_tray.html.erb b/app/views/notifications/_tray.html.erb index a009835a4..d2fa40a5d 100644 --- a/app/views/notifications/_tray.html.erb +++ b/app/views/notifications/_tray.html.erb @@ -7,7 +7,7 @@ data-navigable-list-actionable-items-value="true" data-navigable-list-reverse-navigation-value="true" data-action="keydown->navigable-list#navigate dialog:show@document->navigable-list#reset turbo-visit->navigable-list#reset keydown.esc->dialog#close:stop click@document->dialog#closeOnClickOutside"> - <%= turbo_frame_tag "notifications", src: tray_notifications_path, refresh: "morph" %> + <%= turbo_frame_tag "notifications", src: tray_notifications_path, refresh: "morph", data: { controller: "notifications-tray", notifications_tray_grouped_class: "notification--grouped" } %>
diff --git a/app/views/notifications/trays/show.html.erb b/app/views/notifications/trays/show.html.erb index f61c52d51..a4700253b 100644 --- a/app/views/notifications/trays/show.html.erb +++ b/app/views/notifications/trays/show.html.erb @@ -1,3 +1,4 @@ -<%= turbo_frame_tag "notifications" do %> +<%= turbo_frame_tag "notifications", data: { controller: "notifications-tray", notifications_tray_grouped_class: "notification--grouped" } do %> <%= render partial: "notifications/notification", collection: @notifications, cached: true %> + <% end %>