From c4f50fa63488b61887c68ec7954c698d0b3b5a8a Mon Sep 17 00:00:00 2001 From: Kevin McConnell Date: Thu, 16 Jan 2025 15:07:47 +0000 Subject: [PATCH 01/27] Move notifications to tray controller --- .../trays_controller.rb} | 4 ++-- app/views/notifications/_tray.html.erb | 2 +- app/views/notifications/index.html.erb | 3 --- app/views/notifications/trays/show.html.erb | 3 +++ config/routes.rb | 4 +++- .../trays_controller_test.rb} | 6 +++--- 6 files changed, 12 insertions(+), 10 deletions(-) rename app/controllers/{notifications_controller.rb => notifications/trays_controller.rb} (53%) delete mode 100644 app/views/notifications/index.html.erb create mode 100644 app/views/notifications/trays/show.html.erb rename test/controllers/{notifications_controller_test.rb => notifications/trays_controller_test.rb} (55%) diff --git a/app/controllers/notifications_controller.rb b/app/controllers/notifications/trays_controller.rb similarity index 53% rename from app/controllers/notifications_controller.rb rename to app/controllers/notifications/trays_controller.rb index 22e40167e..4a01a1ee9 100644 --- a/app/controllers/notifications_controller.rb +++ b/app/controllers/notifications/trays_controller.rb @@ -1,5 +1,5 @@ -class NotificationsController < ApplicationController - def index +class Notifications::TraysController < ApplicationController + def show @notifications = Current.user.notifications.unread.ordered.limit(20) end end diff --git a/app/views/notifications/_tray.html.erb b/app/views/notifications/_tray.html.erb index 71ada65cb..af4385822 100644 --- a/app/views/notifications/_tray.html.erb +++ b/app/views/notifications/_tray.html.erb @@ -1,5 +1,5 @@ <%= turbo_stream_from Current.user, :notifications %> <%= tag.div id: "notification-tray", class: "notification-tray flex flex-column gap", data: { turbo_permanent: true } do %> - <%= turbo_frame_tag("notifications", src: notifications_path) %> + <%= turbo_frame_tag("notifications", src: notifications_tray_path) %> <% end %> diff --git a/app/views/notifications/index.html.erb b/app/views/notifications/index.html.erb deleted file mode 100644 index ee91b07e4..000000000 --- a/app/views/notifications/index.html.erb +++ /dev/null @@ -1,3 +0,0 @@ -<%= turbo_frame_tag "notifications" do %> - <%= render @notifications %> -<% end %> diff --git a/app/views/notifications/trays/show.html.erb b/app/views/notifications/trays/show.html.erb new file mode 100644 index 000000000..051055670 --- /dev/null +++ b/app/views/notifications/trays/show.html.erb @@ -0,0 +1,3 @@ +<%= turbo_frame_tag "notifications" do %> + <%= render collection: @notifications, partial: "notifications/notification" %> +<% end %> diff --git a/config/routes.rb b/config/routes.rb index f122bb209..92fcc6806 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -18,7 +18,9 @@ Rails.application.routes.draw do end resources :bubbles - resources :notifications + namespace :notifications do + resource :tray + end resources :buckets do resources :bubbles do diff --git a/test/controllers/notifications_controller_test.rb b/test/controllers/notifications/trays_controller_test.rb similarity index 55% rename from test/controllers/notifications_controller_test.rb rename to test/controllers/notifications/trays_controller_test.rb index fc4c2bfee..7d44a3e00 100644 --- a/test/controllers/notifications_controller_test.rb +++ b/test/controllers/notifications/trays_controller_test.rb @@ -1,12 +1,12 @@ require "test_helper" -class NotificationsControllerTest < ActionDispatch::IntegrationTest +class Notifications::TraysControllerTest < ActionDispatch::IntegrationTest setup do sign_in_as :kevin end - test "index" do - get notifications_url + test "show" do + get notifications_tray_url assert_response :success assert_select "div", text: /Layout is broken/ From 6706802b89eca551ce49f072b08b8abf0fcaf933 Mon Sep 17 00:00:00 2001 From: Kevin McConnell Date: Thu, 16 Jan 2025 15:26:12 +0000 Subject: [PATCH 02/27] Add an index view for all notifications --- app/assets/stylesheets/notifications.css | 126 +++++++++--------- app/controllers/notifications_controller.rb | 6 + app/controllers/readings_controller.rb | 2 +- app/models/notification.rb | 9 +- app/views/buckets/index.html.erb | 2 + app/views/notifications/index.html.erb | 27 ++++ config/routes.rb | 3 +- ...344_change_notification_read_to_read_at.rb | 12 ++ db/schema.rb | 6 +- .../notifications_controller_test.rb | 7 + test/fixtures/notifications.yml | 2 - 11 files changed, 130 insertions(+), 72 deletions(-) create mode 100644 app/controllers/notifications_controller.rb create mode 100644 app/views/notifications/index.html.erb create mode 100644 db/migrate/20250116163344_change_notification_read_to_read_at.rb create mode 100644 test/controllers/notifications_controller_test.rb diff --git a/app/assets/stylesheets/notifications.css b/app/assets/stylesheets/notifications.css index 4b58106a9..0bcba7d3a 100644 --- a/app/assets/stylesheets/notifications.css +++ b/app/assets/stylesheets/notifications.css @@ -2,83 +2,83 @@ height: 4rem; inset: auto auto var(--block-space); position: fixed; -} -.notification { - inset: auto auto 0; - position: absolute; - transform: translate(var(--offsetX), var(--offsetY)); - transform-origin: center center; - transition: transform 0.2s ease-in-out; - z-index: var(--z-index); + .notification { + inset: auto auto 0; + position: absolute; + transform: translate(var(--offsetX), var(--offsetY)); + transform-origin: center center; + transition: transform 0.2s ease-in-out; + z-index: var(--z-index); - .notification__content { - color: var(--color-ink); - inline-size: var(--width); - } + .notification__content { + color: var(--color-ink); + inline-size: var(--width); + } - &:nth-child(1) { - --offsetX: 0; - --offsetY: 0; - --width: 40ch; - --z-index: 0; - } - - &:nth-child(2) { - --offsetX: 0.5ch; - --offsetY: calc(var(--block-space-half) * -1); - --width: 39ch; - --z-index: -1; - } - - &:nth-child(3) { - --offsetX: 1ch; - --offsetY: calc(var(--block-space) * -1); - --width: 38ch; - --z-index: -2; - } - - &:nth-child(4) { - --offsetX: 1.5ch; - --offsetY: calc(var(--block-space) * -1.5); - --width: 37ch; - --z-index: -3; - } - - &:nth-child(5) { - --offsetX: 2ch; - --offsetY: calc(var(--block-space) * -2); - --width: 36ch; - --z-index: -4; - } - - &:nth-child(1n + 6) { - display: none; - } - - .notification-tray:hover & { - &:nth-child(2) { + &:nth-child(1) { --offsetX: 0; - --offsetY: calc((100% + var(--block-space-half)) * -1); + --offsetY: 0; --width: 40ch; + --z-index: 0; + } + + &:nth-child(2) { + --offsetX: 0.5ch; + --offsetY: calc(var(--block-space-half) * -1); + --width: 39ch; + --z-index: -1; } &:nth-child(3) { - --offsetX: 0; - --offsetY: calc((100% + var(--block-space-half)) * -2); - --width: 40ch; + --offsetX: 1ch; + --offsetY: calc(var(--block-space) * -1); + --width: 38ch; + --z-index: -2; } &:nth-child(4) { - --offsetX: 0; - --offsetY: calc((100% + var(--block-space-half)) * -3); - --width: 40ch; + --offsetX: 1.5ch; + --offsetY: calc(var(--block-space) * -1.5); + --width: 37ch; + --z-index: -3; } &:nth-child(5) { - --offsetX: 0; - --offsetY: calc((100% + var(--block-space-half)) * -4); - --width: 40ch; + --offsetX: 2ch; + --offsetY: calc(var(--block-space) * -2); + --width: 36ch; + --z-index: -4; + } + + &:nth-child(1n + 6) { + display: none; + } + + .notification-tray:hover & { + &:nth-child(2) { + --offsetX: 0; + --offsetY: calc((100% + var(--block-space-half)) * -1); + --width: 40ch; + } + + &:nth-child(3) { + --offsetX: 0; + --offsetY: calc((100% + var(--block-space-half)) * -2); + --width: 40ch; + } + + &:nth-child(4) { + --offsetX: 0; + --offsetY: calc((100% + var(--block-space-half)) * -3); + --width: 40ch; + } + + &:nth-child(5) { + --offsetX: 0; + --offsetY: calc((100% + var(--block-space-half)) * -4); + --width: 40ch; + } } } } diff --git a/app/controllers/notifications_controller.rb b/app/controllers/notifications_controller.rb new file mode 100644 index 000000000..85b656c4c --- /dev/null +++ b/app/controllers/notifications_controller.rb @@ -0,0 +1,6 @@ +class NotificationsController < ApplicationController + def index + @read = Current.user.notifications.read.ordered + @unread = Current.user.notifications.unread.ordered + end +end diff --git a/app/controllers/readings_controller.rb b/app/controllers/readings_controller.rb index 9c5798ed3..1bf106efd 100644 --- a/app/controllers/readings_controller.rb +++ b/app/controllers/readings_controller.rb @@ -8,6 +8,6 @@ class ReadingsController < ApplicationController private def mark_bubble_notifications_read - Current.user.notifications.where(bubble: @bubble).update(read: true) + Current.user.notifications.where(bubble: @bubble).update(read_at: Time.current) end end diff --git a/app/models/notification.rb b/app/models/notification.rb index 531be12bb..da1cc9614 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -4,10 +4,15 @@ class Notification < ApplicationRecord belongs_to :bubble belongs_to :resource, polymorphic: true - scope :unread, -> { where(read: false) } - scope :ordered, -> { order(read: :desc, created_at: :desc) } + scope :unread, -> { where(read_at: nil) } + scope :read, -> { where.not(read_at: nil) } + scope :ordered, -> { order(read_at: :desc, created_at: :desc) } delegate :creator, to: :event broadcasts_to ->(notification) { [ notification.user, :notifications ] }, inserts_by: :prepend + + def read? + read_at.present? + end end diff --git a/app/views/buckets/index.html.erb b/app/views/buckets/index.html.erb index 7c2f59a91..baf1e0ddd 100644 --- a/app/views/buckets/index.html.erb +++ b/app/views/buckets/index.html.erb @@ -7,6 +7,8 @@ Account settings <% end %> + <%= link_to "All notifications", notifications_path %> + <%= link_to new_bucket_path, class: "btn flex-item-justify-end", style: "view-transition-name: new-bucket" do %> <%= image_tag "add.svg", aria: { hidden: true }, size: 24 %> Add a new project diff --git a/app/views/notifications/index.html.erb b/app/views/notifications/index.html.erb new file mode 100644 index 000000000..84e2b4254 --- /dev/null +++ b/app/views/notifications/index.html.erb @@ -0,0 +1,27 @@ +<% @page_title = "Notifications" %> + +<% content_for :header do %> + +<% end %> + +<% if @unread.any? %> +

New for you

+ <%= render @unread %> +<% end %> + +<% if @read.any? %> +

Previously read

+<%= render @read %> +<% end %> diff --git a/config/routes.rb b/config/routes.rb index 92fcc6806..6860a6cf0 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -18,8 +18,9 @@ Rails.application.routes.draw do end resources :bubbles + resources :notifications, only: :index namespace :notifications do - resource :tray + resource :tray, only: :show end resources :buckets do diff --git a/db/migrate/20250116163344_change_notification_read_to_read_at.rb b/db/migrate/20250116163344_change_notification_read_to_read_at.rb new file mode 100644 index 000000000..c9bd6bf64 --- /dev/null +++ b/db/migrate/20250116163344_change_notification_read_to_read_at.rb @@ -0,0 +1,12 @@ +class ChangeNotificationReadToReadAt < ActiveRecord::Migration[8.1] + def change + remove_index :notifications, %i[ user_id read created_at ], order: { read: :desc, created_at: :desc } + + change_table :notifications do |t| + t.remove :read + t.datetime :read_at + + t.index %i[ user_id read_at created_at ], order: { read_at: :desc, created_at: :desc } + end + end +end diff --git a/db/schema.rb b/db/schema.rb index a11bb2a66..e1b644f2d 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.1].define(version: 2025_01_15_122810) do +ActiveRecord::Schema[8.1].define(version: 2025_01_16_163344) do create_table "accesses", force: :cascade do |t| t.integer "bucket_id", null: false t.integer "user_id", null: false @@ -183,13 +183,13 @@ ActiveRecord::Schema[8.1].define(version: 2025_01_15_122810) do t.integer "bubble_id", null: false t.string "resource_type", null: false t.integer "resource_id", null: false - t.boolean "read", default: false, null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.datetime "read_at" t.index ["bubble_id"], name: "index_notifications_on_bubble_id" t.index ["event_id"], name: "index_notifications_on_event_id" t.index ["resource_type", "resource_id"], name: "index_notifications_on_resource" - t.index ["user_id", "read", "created_at"], name: "index_notifications_on_user_id_and_read_and_created_at", order: { read: :desc, created_at: :desc } + t.index ["user_id", "read_at", "created_at"], name: "index_notifications_on_user_id_and_read_at_and_created_at", order: { read_at: :desc, created_at: :desc } t.index ["user_id"], name: "index_notifications_on_user_id" end diff --git a/test/controllers/notifications_controller_test.rb b/test/controllers/notifications_controller_test.rb new file mode 100644 index 000000000..20b28201d --- /dev/null +++ b/test/controllers/notifications_controller_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class NotificationsControllerTest < ActionDispatch::IntegrationTest + # test "the truth" do + # assert true + # end +end diff --git a/test/fixtures/notifications.yml b/test/fixtures/notifications.yml index 89a8256ee..7bff2665c 100644 --- a/test/fixtures/notifications.yml +++ b/test/fixtures/notifications.yml @@ -3,7 +3,6 @@ logo_published_kevin: event: logo_published bubble: logo resource: logo (Bubble) - read: false created_at: <%= 1.week.ago %> layout_commented_kevin: @@ -11,5 +10,4 @@ layout_commented_kevin: event: layout_commented bubble: layout resource: layout_overflowing_david (Comment) - read: false created_at: <%= 1.week.ago %> From cb6651e2a71ce93a1dc6a568e606c6213badf690 Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Fri, 17 Jan 2025 12:42:46 -0600 Subject: [PATCH 03/27] Style notifications index; add truncation for long notifications --- app/assets/stylesheets/notifications.css | 35 +++++++++++-------- .../notifications/_notification.html.erb | 6 ++-- app/views/notifications/index.html.erb | 16 ++++++--- 3 files changed, 34 insertions(+), 23 deletions(-) diff --git a/app/assets/stylesheets/notifications.css b/app/assets/stylesheets/notifications.css index 0bcba7d3a..6c8c1b355 100644 --- a/app/assets/stylesheets/notifications.css +++ b/app/assets/stylesheets/notifications.css @@ -1,7 +1,7 @@ .notification-tray { - height: 4rem; inset: auto auto var(--block-space); position: fixed; + z-index: 1; .notification { inset: auto auto 0; @@ -11,43 +11,38 @@ transition: transform 0.2s ease-in-out; z-index: var(--z-index); - .notification__content { - color: var(--color-ink); - inline-size: var(--width); - } - &:nth-child(1) { --offsetX: 0; --offsetY: 0; - --width: 40ch; + --width: 38ch; --z-index: 0; } &:nth-child(2) { --offsetX: 0.5ch; --offsetY: calc(var(--block-space-half) * -1); - --width: 39ch; + --width: 37ch; --z-index: -1; } &:nth-child(3) { --offsetX: 1ch; --offsetY: calc(var(--block-space) * -1); - --width: 38ch; + --width: 36ch; --z-index: -2; } &:nth-child(4) { --offsetX: 1.5ch; --offsetY: calc(var(--block-space) * -1.5); - --width: 37ch; + --width: 35ch; --z-index: -3; } &:nth-child(5) { --offsetX: 2ch; --offsetY: calc(var(--block-space) * -2); - --width: 36ch; + --width: 34ch; --z-index: -4; } @@ -59,26 +54,36 @@ &:nth-child(2) { --offsetX: 0; --offsetY: calc((100% + var(--block-space-half)) * -1); - --width: 40ch; + --width: 38ch; } &:nth-child(3) { --offsetX: 0; --offsetY: calc((100% + var(--block-space-half)) * -2); - --width: 40ch; + --width: 38ch; } &:nth-child(4) { --offsetX: 0; --offsetY: calc((100% + var(--block-space-half)) * -3); - --width: 40ch; + --width: 38ch; } &:nth-child(5) { --offsetX: 0; --offsetY: calc((100% + var(--block-space-half)) * -4); - --width: 40ch; + --width: 38ch; } } } } + +.notification__content { + color: var(--color-ink); + inline-size: var(--width, auto); + + .notificiations-list--read & { + border: 1px solid var(--color-subtle); + box-shadow: none; + } +} diff --git a/app/views/notifications/_notification.html.erb b/app/views/notifications/_notification.html.erb index cba5a252f..40506ebb3 100644 --- a/app/views/notifications/_notification.html.erb +++ b/app/views/notifications/_notification.html.erb @@ -5,9 +5,9 @@ <%= avatar_image_tag(notification.creator) %> -
- <%= notification_title(notification) %> -
<%= notification_body(notification) %> · <%= local_datetime_tag(notification.created_at, style: :ago) %>
+
+ <%= notification_title(notification) %> +
<%= notification_body(notification) %> · <%= local_datetime_tag(notification.created_at, style: :ago) %>
<% end %> diff --git a/app/views/notifications/index.html.erb b/app/views/notifications/index.html.erb index 84e2b4254..aed1aa3b7 100644 --- a/app/views/notifications/index.html.erb +++ b/app/views/notifications/index.html.erb @@ -7,7 +7,9 @@ Go back <% end %> -

All notifications

+
+

<%= @page_title %>

+
<%= link_to new_bucket_path, class: "btn flex-item-justify-end", style: "view-transition-name: new-bucket" do %> <%= image_tag "add.svg", aria: { hidden: true }, size: 24 %> @@ -17,11 +19,15 @@ <% end %> <% if @unread.any? %> -

New for you

- <%= render @unread %> +
+

New for you

+ <%= render @unread %> +
<% end %> <% if @read.any? %> -

Previously read

-<%= render @read %> +
+

Previously seen

+ <%= render @read %> +
<% end %> From be14a910bafeefb9b296509d0f8a7e43a2b84a34 Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Fri, 17 Jan 2025 13:06:41 -0600 Subject: [PATCH 04/27] Needed when truncating so the avatar isn't squished --- app/views/notifications/_notification.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/notifications/_notification.html.erb b/app/views/notifications/_notification.html.erb index 40506ebb3..f7414d64c 100644 --- a/app/views/notifications/_notification.html.erb +++ b/app/views/notifications/_notification.html.erb @@ -1,7 +1,7 @@ <% cache notification do %> <%= notification_tag notification do %>
-
+
<%= avatar_image_tag(notification.creator) %>
From 6b832876a2b9957aadf632896b6d368eed8dc0d7 Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Fri, 17 Jan 2025 15:30:33 -0600 Subject: [PATCH 05/27] Add a notifications button to the tray for reaching the index when you have no unreads --- app/assets/images/bell.svg | 1 + app/assets/stylesheets/notifications.css | 12 ++++++------ app/views/buckets/index.html.erb | 2 -- app/views/notifications/_tray.html.erb | 5 +++++ 4 files changed, 12 insertions(+), 8 deletions(-) create mode 100644 app/assets/images/bell.svg diff --git a/app/assets/images/bell.svg b/app/assets/images/bell.svg new file mode 100644 index 000000000..52a5611fb --- /dev/null +++ b/app/assets/images/bell.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/stylesheets/notifications.css b/app/assets/stylesheets/notifications.css index 6c8c1b355..a3d66f171 100644 --- a/app/assets/stylesheets/notifications.css +++ b/app/assets/stylesheets/notifications.css @@ -1,5 +1,5 @@ .notification-tray { - inset: auto auto var(--block-space); + inset: auto auto var(--block-space) var(--inline-space); position: fixed; z-index: 1; @@ -15,35 +15,35 @@ --offsetX: 0; --offsetY: 0; --width: 38ch; - --z-index: 0; + --z-index: 1; } &:nth-child(2) { --offsetX: 0.5ch; --offsetY: calc(var(--block-space-half) * -1); --width: 37ch; - --z-index: -1; + --z-index: 0; } &:nth-child(3) { --offsetX: 1ch; --offsetY: calc(var(--block-space) * -1); --width: 36ch; - --z-index: -2; + --z-index: -1; } &:nth-child(4) { --offsetX: 1.5ch; --offsetY: calc(var(--block-space) * -1.5); --width: 35ch; - --z-index: -3; + --z-index: -2; } &:nth-child(5) { --offsetX: 2ch; --offsetY: calc(var(--block-space) * -2); --width: 34ch; - --z-index: -4; + --z-index: -3; } &:nth-child(1n + 6) { diff --git a/app/views/buckets/index.html.erb b/app/views/buckets/index.html.erb index baf1e0ddd..7c2f59a91 100644 --- a/app/views/buckets/index.html.erb +++ b/app/views/buckets/index.html.erb @@ -7,8 +7,6 @@ Account settings <% end %> - <%= link_to "All notifications", notifications_path %> - <%= link_to new_bucket_path, class: "btn flex-item-justify-end", style: "view-transition-name: new-bucket" do %> <%= image_tag "add.svg", aria: { hidden: true }, size: 24 %> Add a new project diff --git a/app/views/notifications/_tray.html.erb b/app/views/notifications/_tray.html.erb index af4385822..8a781deba 100644 --- a/app/views/notifications/_tray.html.erb +++ b/app/views/notifications/_tray.html.erb @@ -2,4 +2,9 @@ <%= tag.div id: "notification-tray", class: "notification-tray flex flex-column gap", data: { turbo_permanent: true } do %> <%= turbo_frame_tag("notifications", src: notifications_tray_path) %> + + <%= link_to notifications_path, class: "btn flex-item-justify-start" do %> + <%= image_tag "bell.svg", aria: { hidden: true }, size: 24 %> + See all notifications + <% end %> <% end %> From 00e905dd288952a3c4e727ccd01e1811c803285a Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Fri, 17 Jan 2025 15:37:43 -0600 Subject: [PATCH 06/27] `New for you` empty state --- app/assets/stylesheets/utilities.css | 4 ++-- app/views/notifications/index.html.erb | 12 ++++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/app/assets/stylesheets/utilities.css b/app/assets/stylesheets/utilities.css index 761a24b5d..17d5747dd 100644 --- a/app/assets/stylesheets/utilities.css +++ b/app/assets/stylesheets/utilities.css @@ -168,8 +168,8 @@ .translucent { opacity: var(--opacity, 0.5); } /* Borders */ -.border { border: var(--border-size, 1px) solid var(--border-color, var(--color-subtle)); } -.border-top { border-top: var(--border-size, 1px) solid var(--border-color, var(--color-subtle)); } +.border { border: var(--border-size, 1px) var(--border-style, solid) var(--border-color, var(--color-subtle)); } +.border-top { border-top: var(--border-size, 1px) var(--border-style, solid) var(--border-color, var(--color-subtle)); } .borderless { border: 0; } /* Border radius */ diff --git a/app/views/notifications/index.html.erb b/app/views/notifications/index.html.erb index aed1aa3b7..efbb8c4e9 100644 --- a/app/views/notifications/index.html.erb +++ b/app/views/notifications/index.html.erb @@ -18,12 +18,16 @@ <% end %> -<% if @unread.any? %> -
+
+ <% if @unread.any? %>

New for you

<%= render @unread %> -
-<% end %> + <% else %> +
+ Nothing new for you. +
+ <% end %> +
<% if @read.any? %>
From cd22046efe51e303b4106912f16c2048ed9efb1c Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Sun, 19 Jan 2025 22:27:02 -0600 Subject: [PATCH 07/27] Refactor --- app/assets/stylesheets/notifications.css | 86 ++++++------------------ 1 file changed, 21 insertions(+), 65 deletions(-) diff --git a/app/assets/stylesheets/notifications.css b/app/assets/stylesheets/notifications.css index a3d66f171..959c4592a 100644 --- a/app/assets/stylesheets/notifications.css +++ b/app/assets/stylesheets/notifications.css @@ -1,79 +1,35 @@ .notification-tray { - inset: auto auto var(--block-space) var(--inline-space); + inset: auto auto var(--block-space-double) var(--inline-space); position: fixed; z-index: 1; .notification { + --position: 1; + --scale: calc(1 - var(--position) / 30); + --offset: calc((var(--position) - 1) * -1rem); + --z: calc(5 - var(--position)); + inset: auto auto 0; + inline-size: 38ch; position: absolute; - transform: translate(var(--offsetX), var(--offsetY)); - transform-origin: center center; - transition: transform 0.2s ease-in-out; - z-index: var(--z-index); + scale: var(--scale); + transform: translateY(var(--offset)); + transform-origin: center; + transition: scale 0.2s ease-out, transform 0.2s ease-out; + z-index: var(--z); - &:nth-child(1) { - --offsetX: 0; - --offsetY: 0; - --width: 38ch; - --z-index: 1; - } + &:nth-child(1) { --position: 0; } + &:nth-child(2) { --position: 1; } + &:nth-child(3) { --position: 2; } + &:nth-child(4) { --position: 3; } + &:nth-child(5) { --position: 4; } + &:nth-child(6) { --position: 5; } - &:nth-child(2) { - --offsetX: 0.5ch; - --offsetY: calc(var(--block-space-half) * -1); - --width: 37ch; - --z-index: 0; - } - - &:nth-child(3) { - --offsetX: 1ch; - --offsetY: calc(var(--block-space) * -1); - --width: 36ch; - --z-index: -1; - } - - &:nth-child(4) { - --offsetX: 1.5ch; - --offsetY: calc(var(--block-space) * -1.5); - --width: 35ch; - --z-index: -2; - } - - &:nth-child(5) { - --offsetX: 2ch; - --offsetY: calc(var(--block-space) * -2); - --width: 34ch; - --z-index: -3; - } - - &:nth-child(1n + 6) { - display: none; - } + &:nth-child(1n + 7) { display: none; } .notification-tray:hover & { - &:nth-child(2) { - --offsetX: 0; - --offsetY: calc((100% + var(--block-space-half)) * -1); - --width: 38ch; - } - - &:nth-child(3) { - --offsetX: 0; - --offsetY: calc((100% + var(--block-space-half)) * -2); - --width: 38ch; - } - - &:nth-child(4) { - --offsetX: 0; - --offsetY: calc((100% + var(--block-space-half)) * -3); - --width: 38ch; - } - - &:nth-child(5) { - --offsetX: 0; - --offsetY: calc((100% + var(--block-space-half)) * -4); - --width: 38ch; - } + --scale: 1; + --offset: calc((100% + var(--block-space-half)) * (var(--position) * -1)); } } } From 536dfa65d758e30ff8c85155102046a23ef23fd9 Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Mon, 20 Jan 2025 13:00:56 -0600 Subject: [PATCH 08/27] Open tray on click, stub mark-as-read button --- app/assets/stylesheets/notifications.css | 43 +++++++++++++++++------- app/views/notifications/_tray.html.erb | 21 +++++++++--- 2 files changed, 47 insertions(+), 17 deletions(-) diff --git a/app/assets/stylesheets/notifications.css b/app/assets/stylesheets/notifications.css index 959c4592a..a6789dea0 100644 --- a/app/assets/stylesheets/notifications.css +++ b/app/assets/stylesheets/notifications.css @@ -1,16 +1,21 @@ .notification-tray { - inset: auto auto var(--block-space-double) var(--inline-space); + --size: 38ch; + + inline-size: var(--size); + inset: auto auto var(--block-space) var(--inline-space); position: fixed; z-index: 1; .notification { + --outline-color: var(--color-link); + --offset: calc((var(--position) - 1) * (var(--block-space) * -1)); --position: 1; --scale: calc(1 - var(--position) / 30); - --offset: calc((var(--position) - 1) * -1rem); - --z: calc(5 - var(--position)); + --z: calc(6 - var(--position)); inset: auto auto 0; - inline-size: 38ch; + inline-size: var(--size); + pointer-events: none; position: absolute; scale: var(--scale); transform: translateY(var(--offset)); @@ -18,28 +23,42 @@ transition: scale 0.2s ease-out, transform 0.2s ease-out; z-index: var(--z); - &:nth-child(1) { --position: 0; } - &:nth-child(2) { --position: 1; } - &:nth-child(3) { --position: 2; } - &:nth-child(4) { --position: 3; } - &:nth-child(5) { --position: 4; } - &:nth-child(6) { --position: 5; } + &:nth-child(1) { --position: 1; } + &:nth-child(2) { --position: 2; } + &:nth-child(3) { --position: 3; } + &:nth-child(4) { --position: 4; } + &:nth-child(5) { --position: 5; } + &:nth-child(6) { --position: 6; } &:nth-child(1n + 7) { display: none; } - .notification-tray:hover & { + .notification-tray[open] & { --scale: 1; --offset: calc((100% + var(--block-space-half)) * (var(--position) * -1)); + pointer-events: unset; } } } .notification__content { color: var(--color-ink); - inline-size: var(--width, auto); .notificiations-list--read & { border: 1px solid var(--color-subtle); box-shadow: none; } } + +.notification-tray__expander { + --hover-size: 0; + --outline-size: 0; + + cursor: pointer; + inset: 0; + position: absolute; + z-index: 6; + + .notification-tray[open] & { + display: none; + } +} diff --git a/app/views/notifications/_tray.html.erb b/app/views/notifications/_tray.html.erb index 8a781deba..6e2356f5e 100644 --- a/app/views/notifications/_tray.html.erb +++ b/app/views/notifications/_tray.html.erb @@ -1,10 +1,21 @@ <%= turbo_stream_from Current.user, :notifications %> -<%= tag.div id: "notification-tray", class: "notification-tray flex flex-column gap", data: { turbo_permanent: true } do %> +<%= tag.dialog id: "notification-tray", class: "notification-tray flex flex-column gap unpad borderless", + data: { controller: "dialog", turbo_permanent: true, dialog_modal_value: false, dialog_target: "dialog", action: "keydown.esc@document->dialog#close click@document->dialog#closeOnClickOutside" } do %> <%= turbo_frame_tag("notifications", src: notifications_tray_path) %> - <%= link_to notifications_path, class: "btn flex-item-justify-start" do %> - <%= image_tag "bell.svg", aria: { hidden: true }, size: 24 %> - See all notifications - <% end %> +
+ <%= link_to notifications_path, class: "btn" do %> + <%= image_tag "bell.svg", aria: { hidden: true }, size: 24 %> + See all notifications + <% end %> + + +
+ + <% end %> From 69e235b0820480c97c9ba299d816ffe266272ee9 Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Mon, 20 Jan 2025 15:32:34 -0600 Subject: [PATCH 09/27] Move to right side, smooth transition --- app/assets/stylesheets/notifications.css | 19 ++++++++++++++++++- app/views/notifications/_tray.html.erb | 13 ++++++------- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/app/assets/stylesheets/notifications.css b/app/assets/stylesheets/notifications.css index a6789dea0..847d1fc5e 100644 --- a/app/assets/stylesheets/notifications.css +++ b/app/assets/stylesheets/notifications.css @@ -1,8 +1,10 @@ .notification-tray { --size: 38ch; + block-size: calc(4ch + var(--block-space-double)); inline-size: var(--size); - inset: auto auto var(--block-space) var(--inline-space); + inset: auto var(--inline-space) var(--block-space-half) auto; + padding: var(--block-space-half) var(--inline-space); position: fixed; z-index: 1; @@ -35,11 +37,26 @@ .notification-tray[open] & { --scale: 1; --offset: calc((100% + var(--block-space-half)) * (var(--position) * -1)); + + margin-block-end: calc(var(--block-space) * -1); pointer-events: unset; } } } +.notification-tray__actions { + opacity: 0; + scale: calc(1 - 1 / 30); + transform: translateY(100%); + transition: opacity 0.2s ease-out, scale 0.2s ease-out, transform 0.2s ease-out; + + .notification-tray[open] & { + opacity: 1; + scale: 1; + transform: translateY(0); + } +} + .notification__content { color: var(--color-ink); diff --git a/app/views/notifications/_tray.html.erb b/app/views/notifications/_tray.html.erb index 6e2356f5e..86c0cc9f5 100644 --- a/app/views/notifications/_tray.html.erb +++ b/app/views/notifications/_tray.html.erb @@ -1,21 +1,20 @@ <%= turbo_stream_from Current.user, :notifications %> -<%= tag.dialog id: "notification-tray", class: "notification-tray flex flex-column gap unpad borderless", +<%= tag.dialog id: "notification-tray", class: "notification-tray flex flex-column gap unpad borderless justify-end", data: { controller: "dialog", turbo_permanent: true, dialog_modal_value: false, dialog_target: "dialog", action: "keydown.esc@document->dialog#close click@document->dialog#closeOnClickOutside" } do %> <%= turbo_frame_tag("notifications", src: notifications_tray_path) %> -
+
+ + <%= link_to notifications_path, class: "btn" do %> <%= image_tag "bell.svg", aria: { hidden: true }, size: 24 %> See all notifications <% end %> - -
<% end %> From 02573b6dc895baadf4902fbea0f855ff92deb95d Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Mon, 20 Jan 2025 17:58:56 -0600 Subject: [PATCH 10/27] Set state for when there are no new notifications --- app/assets/stylesheets/notifications.css | 39 ++++++++++++++++++- .../controllers/dialog_controller.js | 1 + app/views/notifications/_tray.html.erb | 15 ++++--- 3 files changed, 46 insertions(+), 9 deletions(-) diff --git a/app/assets/stylesheets/notifications.css b/app/assets/stylesheets/notifications.css index 847d1fc5e..21bf884ce 100644 --- a/app/assets/stylesheets/notifications.css +++ b/app/assets/stylesheets/notifications.css @@ -55,6 +55,42 @@ scale: 1; transform: translateY(0); } + + .notification-tray:has(.notification) & { + .btn img { + display: none; + } + } + + .notification-tray:not(:has(.notification)) & { + opacity: 1; + scale: 1; + transform: none; + transition: none; + + .notification-tray__all_action { + --btn-border-radius: 50%; + --btn-padding: 0; + + aspect-ratio: 1; + block-size: var(--btn-size); + display: grid; + inline-size: var(--btn-size); + place-items: center; + + > * { + grid-area: 1/1; + } + + span { + display: none; + } + } + + .notification-tray__read_action { + display: none; + } + } } .notification__content { @@ -75,7 +111,8 @@ position: absolute; z-index: 6; - .notification-tray[open] & { + .notification-tray[open] &, + .notification-tray:not(:has(.notification)) & { display: none; } } diff --git a/app/javascript/controllers/dialog_controller.js b/app/javascript/controllers/dialog_controller.js index dcea9bdf9..fd4d122e0 100644 --- a/app/javascript/controllers/dialog_controller.js +++ b/app/javascript/controllers/dialog_controller.js @@ -26,6 +26,7 @@ export default class extends Controller { close() { this.dialogTarget.close() + this.dialogTarget.blur() } closeOnClickOutside({ target }) { diff --git a/app/views/notifications/_tray.html.erb b/app/views/notifications/_tray.html.erb index 86c0cc9f5..fa4c650b4 100644 --- a/app/views/notifications/_tray.html.erb +++ b/app/views/notifications/_tray.html.erb @@ -1,20 +1,19 @@ <%= turbo_stream_from Current.user, :notifications %> -<%= tag.dialog id: "notification-tray", class: "notification-tray flex flex-column gap unpad borderless justify-end", - data: { controller: "dialog", turbo_permanent: true, dialog_modal_value: false, dialog_target: "dialog", action: "keydown.esc@document->dialog#close click@document->dialog#closeOnClickOutside" } do %> - <%= turbo_frame_tag("notifications", src: notifications_tray_path) %> - -
- - <%= link_to notifications_path, class: "btn" do %> + <%= link_to notifications_path, class: "notification-tray__all_action btn flex-item-justify-end" do %> <%= image_tag "bell.svg", aria: { hidden: true }, size: 24 %> See all notifications <% end %>
- <% end %> From 404c420a58cb8d8f6aa4d1e1eb01b1487de8c5f1 Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Mon, 20 Jan 2025 18:20:58 -0600 Subject: [PATCH 11/27] Add unread dot --- app/assets/stylesheets/notifications.css | 18 ++++++++++++++++++ app/views/notifications/_notification.html.erb | 6 +++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/notifications.css b/app/assets/stylesheets/notifications.css index 21bf884ce..a41f36635 100644 --- a/app/assets/stylesheets/notifications.css +++ b/app/assets/stylesheets/notifications.css @@ -116,3 +116,21 @@ display: none; } } + +.notification__unread_indicator { + --size: 0.8em; + + padding-block-start: var(--size); + + .dot { + aspect-ratio: 1; + background-color: var(--color-marker); + border-radius: 50%; + block-size: var(--size); + inline-size: var(--size); + } + + .notificiations-list--read & { + display: none; + } +} diff --git a/app/views/notifications/_notification.html.erb b/app/views/notifications/_notification.html.erb index f7414d64c..d309015b3 100644 --- a/app/views/notifications/_notification.html.erb +++ b/app/views/notifications/_notification.html.erb @@ -5,10 +5,14 @@ <%= avatar_image_tag(notification.creator) %>
-
+
<%= notification_title(notification) %>
<%= notification_body(notification) %> · <%= local_datetime_tag(notification.created_at, style: :ago) %>
+ +
+
+
<% end %> <% end %> From 5d5dc8fb730e9ee60575ef756696372f74bc7006 Mon Sep 17 00:00:00 2001 From: Kevin McConnell Date: Tue, 21 Jan 2025 13:56:53 +0000 Subject: [PATCH 12/27] Wire up notifications "Mark all as read" button --- .../notifications/mark_all_reads_controller.rb | 5 +++++ app/controllers/readings_controller.rb | 2 +- app/models/notification.rb | 6 ++++++ app/views/notifications/_tray.html.erb | 4 ++-- .../mark_all_reads/create.html.erb | 1 + config/routes.rb | 1 + .../mark_all_reads_controller_test.rb | 17 +++++++++++++++++ 7 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 app/controllers/notifications/mark_all_reads_controller.rb create mode 100644 app/views/notifications/mark_all_reads/create.html.erb create mode 100644 test/controllers/notifications/mark_all_reads_controller_test.rb diff --git a/app/controllers/notifications/mark_all_reads_controller.rb b/app/controllers/notifications/mark_all_reads_controller.rb new file mode 100644 index 000000000..2aad42028 --- /dev/null +++ b/app/controllers/notifications/mark_all_reads_controller.rb @@ -0,0 +1,5 @@ +class Notifications::MarkAllReadsController < ApplicationController + def create + Current.user.notifications.unread.read_all + end +end diff --git a/app/controllers/readings_controller.rb b/app/controllers/readings_controller.rb index 1bf106efd..fb7112b76 100644 --- a/app/controllers/readings_controller.rb +++ b/app/controllers/readings_controller.rb @@ -8,6 +8,6 @@ class ReadingsController < ApplicationController private def mark_bubble_notifications_read - Current.user.notifications.where(bubble: @bubble).update(read_at: Time.current) + Current.user.notifications.where(bubble: @bubble).read_all end end diff --git a/app/models/notification.rb b/app/models/notification.rb index da1cc9614..05d38c940 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -12,6 +12,12 @@ class Notification < ApplicationRecord broadcasts_to ->(notification) { [ notification.user, :notifications ] }, inserts_by: :prepend + class << self + def read_all + update!(read_at: Time.current) + end + end + def read? read_at.present? end diff --git a/app/views/notifications/_tray.html.erb b/app/views/notifications/_tray.html.erb index fa4c650b4..476f0bc78 100644 --- a/app/views/notifications/_tray.html.erb +++ b/app/views/notifications/_tray.html.erb @@ -4,9 +4,9 @@ data: { controller: "dialog", turbo_permanent: true, dialog_modal_value: false, dialog_target: "dialog", action: "keydown.esc->dialog#close:stop click@document->dialog#closeOnClickOutside" } do %> <%= turbo_frame_tag("notifications", src: notifications_tray_path) -%>
- + <% end %> <%= link_to notifications_path, class: "notification-tray__all_action btn flex-item-justify-end" do %> <%= image_tag "bell.svg", aria: { hidden: true }, size: 24 %> diff --git a/app/views/notifications/mark_all_reads/create.html.erb b/app/views/notifications/mark_all_reads/create.html.erb new file mode 100644 index 000000000..52d6bcd10 --- /dev/null +++ b/app/views/notifications/mark_all_reads/create.html.erb @@ -0,0 +1 @@ +<%= turbo_frame_tag "notifications" %> diff --git a/config/routes.rb b/config/routes.rb index 6860a6cf0..bea4b1c2a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -21,6 +21,7 @@ Rails.application.routes.draw do resources :notifications, only: :index namespace :notifications do resource :tray, only: :show + resource :mark_all_read, only: :create end resources :buckets do diff --git a/test/controllers/notifications/mark_all_reads_controller_test.rb b/test/controllers/notifications/mark_all_reads_controller_test.rb new file mode 100644 index 000000000..ea43e1be6 --- /dev/null +++ b/test/controllers/notifications/mark_all_reads_controller_test.rb @@ -0,0 +1,17 @@ +require "test_helper" + +class Notifications::MarkAllReadsControllerTest < ActionDispatch::IntegrationTest + setup do + sign_in_as :kevin + end + + test "show" do + assert_changes -> { notifications(:logo_published_kevin).reload.read? }, from: false, to: true do + assert_changes -> { notifications(:layout_commented_kevin).reload.read? }, from: false, to: true do + post notifications_mark_all_read_url + end + end + + assert_response :success + end +end From 42fa37d347738a6f6cb11fa6d4fc6fc7a6aa9a0a Mon Sep 17 00:00:00 2001 From: Kevin McConnell Date: Tue, 21 Jan 2025 14:47:56 +0000 Subject: [PATCH 13/27] Send beacon request on disconnect as well This is so we can dismiss any notifications that arrived for a bubble while we were viewing that bubble. Otherwise, they'll stick around until we leave the page and come back. --- app/javascript/controllers/beacon_controller.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/javascript/controllers/beacon_controller.js b/app/javascript/controllers/beacon_controller.js index 07182bb36..794565f78 100644 --- a/app/javascript/controllers/beacon_controller.js +++ b/app/javascript/controllers/beacon_controller.js @@ -7,4 +7,8 @@ export default class extends Controller { connect() { post(this.urlValue, { responseKind: "turbo-stream" }) } + + disconnect() { + post(this.urlValue, { responseKind: "turbo-stream" }) + } } From 85d5f7d7d70f1fee2ba151f24a8c9c491689a5f9 Mon Sep 17 00:00:00 2001 From: Kevin McConnell Date: Tue, 21 Jan 2025 17:26:16 +0000 Subject: [PATCH 14/27] Allow filtering by stage --- app/models/bubble/staged.rb | 2 ++ app/models/filter.rb | 1 + app/models/filter/params.rb | 12 ++++++++++- app/models/filter/resources.rb | 1 + app/models/filter/summarized.rb | 8 ++++++- app/views/bubbles/_filters.html.erb | 21 +++++++++++++++++++ ...1174109_create_filter_stages_join_table.rb | 8 +++++++ db/schema.rb | 9 +++++++- test/models/filter_test.rb | 7 +++++++ 9 files changed, 66 insertions(+), 3 deletions(-) create mode 100644 db/migrate/20250121174109_create_filter_stages_join_table.rb diff --git a/app/models/bubble/staged.rb b/app/models/bubble/staged.rb index 7f3997698..2b16734b9 100644 --- a/app/models/bubble/staged.rb +++ b/app/models/bubble/staged.rb @@ -3,6 +3,8 @@ module Bubble::Staged included do belongs_to :stage, class_name: "Workflow::Stage", optional: true + + scope :in_stage, ->(stage) { where stage: stage } end def workflow diff --git a/app/models/filter.rb b/app/models/filter.rb index 73b03c9c4..1fc09f68b 100644 --- a/app/models/filter.rb +++ b/app/models/filter.rb @@ -24,6 +24,7 @@ class Filter < ApplicationRecord result = result.assigned_to(assignees.ids) if assignees.present? result = result.assigned_by(assigners.ids) if assigners.present? result = result.in_bucket(buckets.ids) if buckets.present? + result = result.in_stage(stages.ids) if stages.present? result = result.tagged_with(tags.ids) if tags.present? result = terms.reduce(result) do |result, term| result.mentioning(term) diff --git a/app/models/filter/params.rb b/app/models/filter/params.rb index 91970d5b9..9d5fabfd3 100644 --- a/app/models/filter/params.rb +++ b/app/models/filter/params.rb @@ -1,7 +1,16 @@ module Filter::Params extend ActiveSupport::Concern - PERMITTED_PARAMS = [ :assignment_status, :indexed_by, assignee_ids: [], assigner_ids: [], bucket_ids: [], tag_ids: [], terms: [] ] + PERMITTED_PARAMS = [ + :assignment_status, + :indexed_by, + assignee_ids: [], + assigner_ids: [], + bucket_ids: [], + stage_ids: [], + tag_ids: [], + terms: [] + ] class_methods do def find_by_params(params) @@ -30,6 +39,7 @@ module Filter::Params params[:terms] = terms params[:tag_ids] = tags.ids params[:bucket_ids] = buckets.ids + params[:stage_ids] = stages.ids params[:assignee_ids] = assignees.ids params[:assigner_ids] = assigners.ids end.compact_blank.reject(&method(:default_value?)) diff --git a/app/models/filter/resources.rb b/app/models/filter/resources.rb index 537c52c35..dabeac4f7 100644 --- a/app/models/filter/resources.rb +++ b/app/models/filter/resources.rb @@ -4,6 +4,7 @@ module Filter::Resources included do has_and_belongs_to_many :tags has_and_belongs_to_many :buckets + has_and_belongs_to_many :stages, class_name: "Workflow::Stage", join_table: "filters_stages" has_and_belongs_to_many :assignees, class_name: "User", join_table: "assignees_filters", association_foreign_key: "assignee_id" has_and_belongs_to_many :assigners, class_name: "User", join_table: "assigners_filters", association_foreign_key: "assigner_id" end diff --git a/app/models/filter/summarized.rb b/app/models/filter/summarized.rb index fe65518d4..71f692c12 100644 --- a/app/models/filter/summarized.rb +++ b/app/models/filter/summarized.rb @@ -1,6 +1,6 @@ module Filter::Summarized def summary - [ index_summary, tag_summary, assignee_summary, assigner_summary, terms_summary ].compact.to_sentence + " #{bucket_summary}" + [ index_summary, tag_summary, assignee_summary, assigner_summary, stage_summary, terms_summary ].compact.to_sentence + " #{bucket_summary}" end private @@ -28,6 +28,12 @@ module Filter::Summarized end end + def stage_summary + if stages.any? + "staged in #{stages.pluck(:name).to_choice_sentence}" + end + end + def bucket_summary if buckets.any? "in #{buckets.pluck(:name).to_choice_sentence}" diff --git a/app/views/bubbles/_filters.html.erb b/app/views/bubbles/_filters.html.erb index 1538e22a2..fbd146e3e 100644 --- a/app/views/bubbles/_filters.html.erb +++ b/app/views/bubbles/_filters.html.erb @@ -35,6 +35,10 @@ <%= filter_chip_tag "by #{assigner.name}", filter.as_params_without(:assigner_ids, assigner.id) %> <% end %> + <% filter.stages.each do |stage| %> + <%= filter_chip_tag "in #{stage.name}", filter.as_params_without(:stage_ids, stage.id) %> + <% end %> + <% if filter.buckets.many? %> <% filter.buckets.each do |bucket| %> <%= filter_chip_tag "in #{bucket.name}", filter.as_params_without(:bucket_ids, bucket.id) %> @@ -188,6 +192,23 @@ <% end %> + + +
  • In Stage
  • + <% Current.account.workflows.order(:name).each do |workflow| %> +
  • <%= workflow.name %>
  • + + <% workflow.stages.order(:name).each do |stage| %> +
  • + <%= label_tag "stage_ids_#{stage.id}", class: "btn filter__button" do %> + <%= check_box_tag "stage_ids[]", stage.id, filter.stages.include?(stage), id: "stage_ids_#{stage.id}", hidden: true %> + <%= stage.name %> + <%= image_tag "close.svg", aria: { hidden: true }, size: 24 %> + <% end %> +
  • + <% end %> + <% end %> +
    <% end %> diff --git a/db/migrate/20250121174109_create_filter_stages_join_table.rb b/db/migrate/20250121174109_create_filter_stages_join_table.rb new file mode 100644 index 000000000..268e648d3 --- /dev/null +++ b/db/migrate/20250121174109_create_filter_stages_join_table.rb @@ -0,0 +1,8 @@ +class CreateFilterStagesJoinTable < ActiveRecord::Migration[8.1] + def change + create_join_table :filters, :stages do |t| + t.index :filter_id + t.index :stage_id + end + end +end diff --git a/db/schema.rb b/db/schema.rb index a11bb2a66..688750b25 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.1].define(version: 2025_01_15_122810) do +ActiveRecord::Schema[8.1].define(version: 2025_01_21_174109) do create_table "accesses", force: :cascade do |t| t.integer "bucket_id", null: false t.integer "user_id", null: false @@ -160,6 +160,13 @@ ActiveRecord::Schema[8.1].define(version: 2025_01_15_122810) do t.index ["creator_id", "params_digest"], name: "index_filters_on_creator_id_and_params_digest", unique: true end + create_table "filters_stages", id: false, force: :cascade do |t| + t.integer "filter_id", null: false + t.integer "stage_id", null: false + t.index ["filter_id"], name: "index_filters_stages_on_filter_id" + t.index ["stage_id"], name: "index_filters_stages_on_stage_id" + end + create_table "filters_tags", id: false, force: :cascade do |t| t.integer "filter_id", null: false t.integer "tag_id", null: false diff --git a/test/models/filter_test.rb b/test/models/filter_test.rb index c653551dd..7d2a5e222 100644 --- a/test/models/filter_test.rb +++ b/test/models/filter_test.rb @@ -8,6 +8,7 @@ class FilterTest < ActiveSupport::TestCase bubbles(:layout).capture Comment.new(body: "I hate haggis") bubbles(:logo).capture Comment.new(body: "I love haggis") + bubbles(:logo).update(stage: workflow_stages(:qa_triage)) end assert_not_includes users(:kevin).filters.new.bubbles, @new_bubble @@ -18,6 +19,9 @@ class FilterTest < ActiveSupport::TestCase filter = users(:david).filters.new assigner_ids: [ users(:david).id ], tag_ids: [ tags(:mobile).id ] assert_equal [ bubbles(:layout) ], filter.bubbles + filter = users(:david).filters.new stage_ids: [ workflow_stages(:qa_triage).id ] + assert_equal [ bubbles(:logo) ], filter.bubbles + filter = users(:david).filters.new assignment_status: "unassigned", bucket_ids: [ @new_bucket.id ] assert_equal [ @new_bubble ], filter.bubbles @@ -100,6 +104,9 @@ class FilterTest < ActiveSupport::TestCase test "summary" do assert_equal "Most discussed, tagged #Mobile, and assigned to JZ in all projects", filters(:jz_assignments).summary + + filters(:jz_assignments).update!(stages: workflow_stages(:qa_triage, :qa_in_progress)) + assert_equal "Most discussed, tagged #Mobile, assigned to JZ, and staged in Triage or In Progress in all projects", filters(:jz_assignments).summary end test "params without a key-value pair" do From bd6e0b62b1a4759343fcbca045e4b393b94e1270 Mon Sep 17 00:00:00 2001 From: Kevin McConnell Date: Fri, 24 Jan 2025 10:36:33 +0000 Subject: [PATCH 15/27] Split up a big view --- app/views/bubbles/_filters.html.erb | 217 +-------------------------- app/views/filters/_dialog.html.erb | 174 +++++++++++++++++++++ app/views/filters/_settings.html.erb | 40 +++++ 3 files changed, 216 insertions(+), 215 deletions(-) create mode 100644 app/views/filters/_dialog.html.erb create mode 100644 app/views/filters/_settings.html.erb diff --git a/app/views/bubbles/_filters.html.erb b/app/views/bubbles/_filters.html.erb index fbd146e3e..eb1c64ed2 100644 --- a/app/views/bubbles/_filters.html.erb +++ b/app/views/bubbles/_filters.html.erb @@ -10,219 +10,6 @@ <% end %> -
    - - -
    - <%= filter_chip_tag filter.indexed_by.humanize, filter.as_params_without(:indexed_by, filter.indexed_by) unless filter.default_indexed_by? %> - - <% filter.tags.each do |tag| %> - <%= filter_chip_tag tag.hashtag, filter.as_params_without(:tag_ids, tag.id) %> - <% end %> - - <% filter.assignees.each do |assignee| %> - <%= filter_chip_tag "for #{assignee.name}", filter.as_params_without(:assignee_ids, assignee.id) %> - <% end %> - - <% if filter.assignment_status.present? %> - <%= filter_chip_tag filter.assignment_status.humanize, filter.as_params_without(:assignment_status, filter.assignment_status) %> - <% end %> - - <% filter.assigners.each do |assigner| %> - <%= filter_chip_tag "by #{assigner.name}", filter.as_params_without(:assigner_ids, assigner.id) %> - <% end %> - - <% filter.stages.each do |stage| %> - <%= filter_chip_tag "in #{stage.name}", filter.as_params_without(:stage_ids, stage.id) %> - <% end %> - - <% if filter.buckets.many? %> - <% filter.buckets.each do |bucket| %> - <%= filter_chip_tag "in #{bucket.name}", filter.as_params_without(:bucket_ids, bucket.id) %> - <% end %> - <% end %> - - <% filter.terms.each do |term| %> - <%= filter_chip_tag %Q("#{term}"), filter.as_params_without(:terms, term) %> - <% end %> -
    -
    - - -
    -
    - - -

    - <%= image_tag "filter.svg", aria: { hidden: true }, size: 30 %> - Filter -

    - -
    - -
    -
    - -
    - <%= form_tag bubbles_path, class: "flex align-center gap input input--actor txt-small fill-white border-radius", method: :get do %> - <% @filter.as_params.each do |key, value| %> - <%= filter_hidden_field_tag key, value %> - <% end %> - - <%= search_field_tag "terms[]", nil, class: "input full-width", placeholder: "By keyword…", id: nil %> - - - <% end %> -
    - - <%= form_with url: bubbles_path, id: :filter_form, method: :get, class: "flex flex-column gap full-width", style: "--border-color: var(--color-subtle)" do %> - <% Array(params[:terms]).each do |term| %> - <%= hidden_field_tag "terms[]", term, id: nil %> - <% end %> - -
    - -
  • Sort by
  • -
  • - <%= label_tag "indexed_by_#{filter.default_indexed_by}", class: "btn filter__button" do %> - <%= radio_button_tag "indexed_by", filter.default_indexed_by, filter.default_indexed_by?, hidden: true %> - Most active - <%= image_tag "close.svg", aria: { hidden: true }, size: 24 %> - <% end %> -
  • - - <% Filter::INDEXES.each do |index| %> -
  • - <%= label_tag "indexed_by_#{index}", class: "btn filter__button" do %> - <%= radio_button_tag "indexed_by", index, filter.indexed_by == index, hidden: true %> - <%= index.humanize %> - <%= image_tag "close.svg", aria: { hidden: true }, size: 24 %> - <% end %> -
  • - <% end %> -
    - - -
  • In Project
  • - -
  • - <%= button_tag "All Projects", type: :button, class: "btn filter__button", data: { action: "filter-form#clearCategory", filter_form_name_param: "bucket_ids[]" } %> -
  • - <% Current.user.buckets.order(:name).each do |bucket| %> -
  • - <%= label_tag "bucket_ids_#{bucket.id}", class: "btn filter__button" do %> - <%= check_box_tag "bucket_ids[]", bucket.id, filter.buckets.include?(bucket), id: "bucket_ids_#{bucket.id}", hidden: true %> - <%= bucket.name %> - <%= image_tag "close.svg", aria: { hidden: true }, size: 24 %> - <% end %> -
  • - <% end %> -
    - - -
  • Tagged
  • - <% Current.account.tags.order(:title).each do |tag| %> -
  • - <%= label_tag "tag_ids_#{tag.id}", class: "btn filter__button" do %> - <%= check_box_tag "tag_ids[]", tag.id, filter.tags.include?(tag), id: "tag_ids_#{tag.id}", hidden: true %> - <%= tag.hashtag %> - <%= image_tag "close.svg", aria: { hidden: true }, size: 24 %> - <% end %> -
  • - <% end %> -
    - - -
  • Assigned to…
  • -
  • - <%= label_tag "assignment_status_unassigned", class: "btn filter__button" do %> - <%= check_box_tag "assignment_status", "unassigned", filter.assignment_status.unassigned?, id: "assignment_status_unassigned", - hidden: true, data: { action: "filter-form#clearCategory", filter_form_name_param: "assignee_ids[],assigner_ids[]" } %> - No one - <%= image_tag "close.svg", aria: { hidden: true }, size: 24 %> - <% end %> -
  • -
  • - <%= label_tag "assignee_ids_me", class: "btn filter__button" do %> - <%= check_box_tag "assignee_ids[]", Current.user.id, filter.assignees.include?(Current.user), id: "assignee_ids_me", - hidden: true, data: { action: "filter-form#clearCategory", filter_form_name_param: "assignment_status" } %> - Me - <%= image_tag "close.svg", aria: { hidden: true }, size: 24 %> - <% end %> -
  • - <% Current.account.users.active.without(Current.user).order(:name).each do |user| %> -
  • - <%= label_tag "assignee_ids_#{user.id}", user.name, class: "btn filter__button" do %> - <%= check_box_tag "assignee_ids[]", user.id, filter.assignees.include?(user), id: "assignee_ids_#{user.id}", - hidden: true, data: { action: "filter-form#clearCategory", filter_form_name_param: "assignment_status" } %> - <%= user.name %> - <%= image_tag "close.svg", aria: { hidden: true }, size: 24 %> - <% end %> -
  • - <% end %> -
    - - -
  • Assigned by…
  • -
  • - <%= label_tag "assigner_ids_me", class: "btn filter__button" do %> - <%= check_box_tag "assigner_ids[]", Current.user.id, filter.assigners.include?(Current.user), id: "assigner_ids_me", - hidden: true, data: { action: "filter-form#clearCategory", filter_form_name_param: "assignment_status" } %> - Me - <%= image_tag "close.svg", aria: { hidden: true }, size: 24 %> - <% end %> -
  • - <% Current.account.users.active.without(Current.user).order(:name).each do |user| %> -
  • - <%= label_tag "assigner_ids_#{user.id}", class: "btn filter__button" do %> - <%= check_box_tag "assigner_ids[]", user.id, filter.assigners.include?(user), id: "assigner_ids_#{user.id}", - hidden: true, data: { action: "filter-form#clearCategory", filter_form_name_param: "assignment_status" } %> - <%= user.name %> - <%= image_tag "close.svg", aria: { hidden: true }, size: 24 %> - <% end %> -
  • - <% end %> -
    - - -
  • In Stage
  • - <% Current.account.workflows.order(:name).each do |workflow| %> -
  • <%= workflow.name %>
  • - - <% workflow.stages.order(:name).each do |stage| %> -
  • - <%= label_tag "stage_ids_#{stage.id}", class: "btn filter__button" do %> - <%= check_box_tag "stage_ids[]", stage.id, filter.stages.include?(stage), id: "stage_ids_#{stage.id}", hidden: true %> - <%= stage.name %> - <%= image_tag "close.svg", aria: { hidden: true }, size: 24 %> - <% end %> -
  • - <% end %> - <% end %> -
    -
    - <% end %> - -
    - <%= tag.button class: "btn", form: :filter_form, formaction: filters_path, formmethod: :post do %> - <%= image_tag "bubbles.svg", aria: { hidden: true }, size: 24 %> - Save to home - <% end %> - - -
    -
    -
    + <%= render "filters/settings", filter: filter %> + <%= render "filters/dialog", filter: filter %>
    diff --git a/app/views/filters/_dialog.html.erb b/app/views/filters/_dialog.html.erb new file mode 100644 index 000000000..343bca8ad --- /dev/null +++ b/app/views/filters/_dialog.html.erb @@ -0,0 +1,174 @@ + +
    +
    + + +

    + <%= image_tag "filter.svg", aria: { hidden: true }, size: 30 %> + Filter +

    + +
    + +
    +
    + +
    + <%= form_tag bubbles_path, class: "flex align-center gap input input--actor txt-small fill-white border-radius", method: :get do %> + <% @filter.as_params.each do |key, value| %> + <%= filter_hidden_field_tag key, value %> + <% end %> + + <%= search_field_tag "terms[]", nil, class: "input full-width", placeholder: "By keyword…", id: nil %> + + + <% end %> +
    + + <%= form_with url: bubbles_path, id: :filter_form, method: :get, class: "flex flex-column gap full-width", style: "--border-color: var(--color-subtle)" do %> + <% Array(params[:terms]).each do |term| %> + <%= hidden_field_tag "terms[]", term, id: nil %> + <% end %> + +
    + +
  • Sort by
  • +
  • + <%= label_tag "indexed_by_#{filter.default_indexed_by}", class: "btn filter__button" do %> + <%= radio_button_tag "indexed_by", filter.default_indexed_by, filter.default_indexed_by?, hidden: true %> + Most active + <%= image_tag "close.svg", aria: { hidden: true }, size: 24 %> + <% end %> +
  • + + <% Filter::INDEXES.each do |index| %> +
  • + <%= label_tag "indexed_by_#{index}", class: "btn filter__button" do %> + <%= radio_button_tag "indexed_by", index, filter.indexed_by == index, hidden: true %> + <%= index.humanize %> + <%= image_tag "close.svg", aria: { hidden: true }, size: 24 %> + <% end %> +
  • + <% end %> +
    + + +
  • In Project
  • + +
  • + <%= button_tag "All Projects", type: :button, class: "btn filter__button", data: { action: "filter-form#clearCategory", filter_form_name_param: "bucket_ids[]" } %> +
  • + <% Current.user.buckets.order(:name).each do |bucket| %> +
  • + <%= label_tag "bucket_ids_#{bucket.id}", class: "btn filter__button" do %> + <%= check_box_tag "bucket_ids[]", bucket.id, filter.buckets.include?(bucket), id: "bucket_ids_#{bucket.id}", hidden: true %> + <%= bucket.name %> + <%= image_tag "close.svg", aria: { hidden: true }, size: 24 %> + <% end %> +
  • + <% end %> +
    + + +
  • Tagged
  • + <% Current.account.tags.order(:title).each do |tag| %> +
  • + <%= label_tag "tag_ids_#{tag.id}", class: "btn filter__button" do %> + <%= check_box_tag "tag_ids[]", tag.id, filter.tags.include?(tag), id: "tag_ids_#{tag.id}", hidden: true %> + <%= tag.hashtag %> + <%= image_tag "close.svg", aria: { hidden: true }, size: 24 %> + <% end %> +
  • + <% end %> +
    + + +
  • Assigned to…
  • +
  • + <%= label_tag "assignment_status_unassigned", class: "btn filter__button" do %> + <%= check_box_tag "assignment_status", "unassigned", filter.assignment_status.unassigned?, id: "assignment_status_unassigned", + hidden: true, data: { action: "filter-form#clearCategory", filter_form_name_param: "assignee_ids[],assigner_ids[]" } %> + No one + <%= image_tag "close.svg", aria: { hidden: true }, size: 24 %> + <% end %> +
  • +
  • + <%= label_tag "assignee_ids_me", class: "btn filter__button" do %> + <%= check_box_tag "assignee_ids[]", Current.user.id, filter.assignees.include?(Current.user), id: "assignee_ids_me", + hidden: true, data: { action: "filter-form#clearCategory", filter_form_name_param: "assignment_status" } %> + Me + <%= image_tag "close.svg", aria: { hidden: true }, size: 24 %> + <% end %> +
  • + <% Current.account.users.active.without(Current.user).order(:name).each do |user| %> +
  • + <%= label_tag "assignee_ids_#{user.id}", user.name, class: "btn filter__button" do %> + <%= check_box_tag "assignee_ids[]", user.id, filter.assignees.include?(user), id: "assignee_ids_#{user.id}", + hidden: true, data: { action: "filter-form#clearCategory", filter_form_name_param: "assignment_status" } %> + <%= user.name %> + <%= image_tag "close.svg", aria: { hidden: true }, size: 24 %> + <% end %> +
  • + <% end %> +
    + + +
  • Assigned by…
  • +
  • + <%= label_tag "assigner_ids_me", class: "btn filter__button" do %> + <%= check_box_tag "assigner_ids[]", Current.user.id, filter.assigners.include?(Current.user), id: "assigner_ids_me", + hidden: true, data: { action: "filter-form#clearCategory", filter_form_name_param: "assignment_status" } %> + Me + <%= image_tag "close.svg", aria: { hidden: true }, size: 24 %> + <% end %> +
  • + <% Current.account.users.active.without(Current.user).order(:name).each do |user| %> +
  • + <%= label_tag "assigner_ids_#{user.id}", class: "btn filter__button" do %> + <%= check_box_tag "assigner_ids[]", user.id, filter.assigners.include?(user), id: "assigner_ids_#{user.id}", + hidden: true, data: { action: "filter-form#clearCategory", filter_form_name_param: "assignment_status" } %> + <%= user.name %> + <%= image_tag "close.svg", aria: { hidden: true }, size: 24 %> + <% end %> +
  • + <% end %> +
    + + +
  • In Stage
  • + <% Current.account.workflows.order(:name).each do |workflow| %> +
  • <%= workflow.name %>
  • + + <% workflow.stages.order(:name).each do |stage| %> +
  • + <%= label_tag "stage_ids_#{stage.id}", class: "btn filter__button" do %> + <%= check_box_tag "stage_ids[]", stage.id, filter.stages.include?(stage), id: "stage_ids_#{stage.id}", hidden: true %> + <%= stage.name %> + <%= image_tag "close.svg", aria: { hidden: true }, size: 24 %> + <% end %> +
  • + <% end %> + <% end %> +
    +
    + <% end %> + +
    + <%= tag.button class: "btn", form: :filter_form, formaction: filters_path, formmethod: :post do %> + <%= image_tag "bubbles.svg", aria: { hidden: true }, size: 24 %> + Save to home + <% end %> + + +
    +
    +
    diff --git a/app/views/filters/_settings.html.erb b/app/views/filters/_settings.html.erb new file mode 100644 index 000000000..6e9abcd05 --- /dev/null +++ b/app/views/filters/_settings.html.erb @@ -0,0 +1,40 @@ +
    + + +
    + <%= filter_chip_tag filter.indexed_by.humanize, filter.as_params_without(:indexed_by, filter.indexed_by) unless filter.default_indexed_by? %> + + <% filter.tags.each do |tag| %> + <%= filter_chip_tag tag.hashtag, filter.as_params_without(:tag_ids, tag.id) %> + <% end %> + + <% filter.assignees.each do |assignee| %> + <%= filter_chip_tag "for #{assignee.name}", filter.as_params_without(:assignee_ids, assignee.id) %> + <% end %> + + <% if filter.assignment_status.present? %> + <%= filter_chip_tag filter.assignment_status.humanize, filter.as_params_without(:assignment_status, filter.assignment_status) %> + <% end %> + + <% filter.assigners.each do |assigner| %> + <%= filter_chip_tag "by #{assigner.name}", filter.as_params_without(:assigner_ids, assigner.id) %> + <% end %> + + <% filter.stages.each do |stage| %> + <%= filter_chip_tag "in #{stage.name}", filter.as_params_without(:stage_ids, stage.id) %> + <% end %> + + <% if filter.buckets.many? %> + <% filter.buckets.each do |bucket| %> + <%= filter_chip_tag "in #{bucket.name}", filter.as_params_without(:bucket_ids, bucket.id) %> + <% end %> + <% end %> + + <% filter.terms.each do |term| %> + <%= filter_chip_tag %Q("#{term}"), filter.as_params_without(:terms, term) %> + <% end %> +
    +
    From 2370bd0053d894597ad8318ca7cd4b23acef09b9 Mon Sep 17 00:00:00 2001 From: Kevin McConnell Date: Fri, 24 Jan 2025 10:43:46 +0000 Subject: [PATCH 16/27] Add a test case --- test/models/filter_test.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/models/filter_test.rb b/test/models/filter_test.rb index 7d2a5e222..28842e66a 100644 --- a/test/models/filter_test.rb +++ b/test/models/filter_test.rb @@ -107,6 +107,9 @@ class FilterTest < ActiveSupport::TestCase filters(:jz_assignments).update!(stages: workflow_stages(:qa_triage, :qa_in_progress)) assert_equal "Most discussed, tagged #Mobile, assigned to JZ, and staged in Triage or In Progress in all projects", filters(:jz_assignments).summary + + filters(:jz_assignments).update!(stages: [], assignees: [], tags: [], buckets: [ buckets(:writebook) ]) + assert_equal "Most discussed in Writebook", filters(:jz_assignments).summary end test "params without a key-value pair" do From 1f3bc1134f27f4d28b30e4c26ee2a0c3f6c20f16 Mon Sep 17 00:00:00 2001 From: Kevin McConnell Date: Fri, 24 Jan 2025 17:42:36 +0000 Subject: [PATCH 17/27] Paginate the read notifications --- Gemfile | 1 + Gemfile.lock | 4 +++ app/controllers/notifications_controller.rb | 7 ++++-- app/helpers/notifications_helper.rb | 6 +++++ .../fetch_on_visible_controller.js | 25 +++++++++++++++++++ app/views/notifications/index.html.erb | 8 +++--- .../notifications/index.turbo_stream.erb | 7 ++++++ 7 files changed, 53 insertions(+), 5 deletions(-) create mode 100644 app/javascript/controllers/fetch_on_visible_controller.js create mode 100644 app/views/notifications/index.turbo_stream.erb diff --git a/Gemfile b/Gemfile index 5c3b0ff65..fdeb0ea2b 100644 --- a/Gemfile +++ b/Gemfile @@ -22,6 +22,7 @@ gem "thruster", require: false # Features gem "bcrypt", "~> 3.1.7" +gem "geared_pagination", "~> 1.2" gem "rqrcode" gem "redcarpet" gem "rouge" diff --git a/Gemfile.lock b/Gemfile.lock index 26b1c521b..cff4ceb78 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -157,6 +157,9 @@ GEM fugit (1.11.1) et-orbi (~> 1, >= 1.2.11) raabro (~> 1.4) + geared_pagination (1.2.0) + activesupport (>= 5.0) + addressable (>= 2.5.0) globalid (1.2.1) activesupport (>= 6.1) hotwire-spark (0.1.12) @@ -397,6 +400,7 @@ DEPENDENCIES brakeman capybara debug + geared_pagination (~> 1.2) hotwire-spark hotwire_combobox! importmap-rails diff --git a/app/controllers/notifications_controller.rb b/app/controllers/notifications_controller.rb index 85b656c4c..83e5db488 100644 --- a/app/controllers/notifications_controller.rb +++ b/app/controllers/notifications_controller.rb @@ -1,6 +1,9 @@ class NotificationsController < ApplicationController def index - @read = Current.user.notifications.read.ordered - @unread = Current.user.notifications.unread.ordered + set_page_and_extract_portion_from Current.user.notifications.read.ordered + + if @page.first? + @unread = Current.user.notifications.unread.ordered + end end end diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb index ddc142efb..bb0dade2a 100644 --- a/app/helpers/notifications_helper.rb +++ b/app/helpers/notifications_helper.rb @@ -25,6 +25,12 @@ module NotificationsHelper data: { turbo_frame: "_top" }, & end + def notifications_next_page_link(page) + unless @page.last? + tag.div id: "next_page", data: { controller: "fetch-on-visible", fetch_on_visible_url_value: notifications_path(page: @page.next_param) } + end + end + private def notification_event_action(notification) if notification_is_for_initial_assignement?(notification) diff --git a/app/javascript/controllers/fetch_on_visible_controller.js b/app/javascript/controllers/fetch_on_visible_controller.js new file mode 100644 index 000000000..42f1a36da --- /dev/null +++ b/app/javascript/controllers/fetch_on_visible_controller.js @@ -0,0 +1,25 @@ +import { Controller } from "@hotwired/stimulus" +import { get } from "@rails/request.js" + +export default class extends Controller { + static values = { url: String } + + connect() { + this.#observe() + } + + #observe() { + const observer = new IntersectionObserver((entries) => { + const visible = !!entries.find(entry => entry.isIntersecting) + if (visible) { + this.#fetch() + } + }) + + observer.observe(this.element) + } + + #fetch() { + get(this.urlValue, { responseKind: "turbo-stream" }) + } +} diff --git a/app/views/notifications/index.html.erb b/app/views/notifications/index.html.erb index efbb8c4e9..1f302e623 100644 --- a/app/views/notifications/index.html.erb +++ b/app/views/notifications/index.html.erb @@ -29,9 +29,11 @@ <% end %> -<% if @read.any? %> -
    +<% if @page.records.any? %> +

    Previously seen

    - <%= render @read %> + <%= render @page.records %>
    + + <%= notifications_next_page_link(@page) %> <% end %> diff --git a/app/views/notifications/index.turbo_stream.erb b/app/views/notifications/index.turbo_stream.erb new file mode 100644 index 000000000..00d744dcd --- /dev/null +++ b/app/views/notifications/index.turbo_stream.erb @@ -0,0 +1,7 @@ +<%= turbo_stream.append :notifications_list_read do %> + <%= render @page.records %> +<% end %> + +<%= turbo_stream.replace :next_page do %> + <%= notifications_next_page_link(@page) %> +<% end %> From 57a1e4d621fb5d080e334a7c66fcd8e82f721fef Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Fri, 24 Jan 2025 13:29:48 -0600 Subject: [PATCH 18/27] This shouldn't be here --- app/views/notifications/index.html.erb | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/views/notifications/index.html.erb b/app/views/notifications/index.html.erb index 1f302e623..35942d06a 100644 --- a/app/views/notifications/index.html.erb +++ b/app/views/notifications/index.html.erb @@ -11,10 +11,7 @@

    <%= @page_title %>

    - <%= link_to new_bucket_path, class: "btn flex-item-justify-end", style: "view-transition-name: new-bucket" do %> - <%= image_tag "add.svg", aria: { hidden: true }, size: 24 %> - Add a new project - <% end %> +
    <% end %> From 823602bcee735ae5e9201e7ff9506bff4867e84c Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Fri, 24 Jan 2025 13:56:03 -0600 Subject: [PATCH 19/27] Avoid leaving the tray open --- app/helpers/notifications_helper.rb | 2 +- app/views/notifications/_tray.html.erb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb index bb0dade2a..9d3b9a259 100644 --- a/app/helpers/notifications_helper.rb +++ b/app/helpers/notifications_helper.rb @@ -22,7 +22,7 @@ module NotificationsHelper def notification_tag(notification, &) link_to notification.resource, id: dom_id(notification), class: "notification border-radius", - data: { turbo_frame: "_top" }, & + data: { action: "click->dialog#close", turbo_frame: "_top" }, & end def notifications_next_page_link(page) diff --git a/app/views/notifications/_tray.html.erb b/app/views/notifications/_tray.html.erb index 476f0bc78..55fbfa570 100644 --- a/app/views/notifications/_tray.html.erb +++ b/app/views/notifications/_tray.html.erb @@ -4,11 +4,11 @@ data: { controller: "dialog", turbo_permanent: true, dialog_modal_value: false, dialog_target: "dialog", action: "keydown.esc->dialog#close:stop click@document->dialog#closeOnClickOutside" } do %> <%= turbo_frame_tag("notifications", src: notifications_tray_path) -%>
    - <%= button_to notifications_mark_all_read_path, class: "notification-tray__read_action btn flex-item-justify-start", data: { turbo_frame: "notifications" } do %> + <%= button_to notifications_mark_all_read_path, class: "notification-tray__read_action btn flex-item-justify-start", data: { action: "click->dialog#close", turbo_frame: "notifications" } do %> Mark all as read <% end %> - <%= link_to notifications_path, class: "notification-tray__all_action btn flex-item-justify-end" do %> + <%= link_to notifications_path, class: "notification-tray__all_action btn flex-item-justify-end", data: { action: "click->dialog#close"} do %> <%= image_tag "bell.svg", aria: { hidden: true }, size: 24 %> See all notifications <% end %> From 40f39257129cc178fe136a03c4dffebba48160e8 Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Fri, 24 Jan 2025 14:09:59 -0600 Subject: [PATCH 20/27] Add more context --- app/helpers/notifications_helper.rb | 3 ++- app/views/notifications/_notification.html.erb | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb index 9d3b9a259..5143ede77 100644 --- a/app/helpers/notifications_helper.rb +++ b/app/helpers/notifications_helper.rb @@ -4,6 +4,8 @@ module NotificationsHelper if notification.resource.is_a? Comment "RE: " + title + elsif notification_event_action(notification) == "assigned" + "Assigned to you: " + title else title end @@ -13,7 +15,6 @@ module NotificationsHelper name = notification.creator.name case notification_event_action(notification) - when "assigned" then "#{name} assigned to you" when "popped" then "Popped by by #{name}" when "published" then "Added by #{name}" else name diff --git a/app/views/notifications/_notification.html.erb b/app/views/notifications/_notification.html.erb index d309015b3..7271626e4 100644 --- a/app/views/notifications/_notification.html.erb +++ b/app/views/notifications/_notification.html.erb @@ -7,7 +7,7 @@
    <%= notification_title(notification) %> -
    <%= notification_body(notification) %> · <%= local_datetime_tag(notification.created_at, style: :ago) %>
    +
    <%= notification_body(notification) %> · <%= notification.bubble.bucket.name %> · <%= local_datetime_tag(notification.created_at, style: :ago) %>
    From 3c66f1ade77dd1868049e6eecc816b242af36eae Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Fri, 24 Jan 2025 14:15:54 -0600 Subject: [PATCH 21/27] Spacing --- app/assets/stylesheets/notifications.css | 1 + app/views/notifications/_notification.html.erb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/notifications.css b/app/assets/stylesheets/notifications.css index a41f36635..ea8f772c6 100644 --- a/app/assets/stylesheets/notifications.css +++ b/app/assets/stylesheets/notifications.css @@ -128,6 +128,7 @@ border-radius: 50%; block-size: var(--size); inline-size: var(--size); + margin-inline-end: var(--inline-space-half); } .notificiations-list--read & { diff --git a/app/views/notifications/_notification.html.erb b/app/views/notifications/_notification.html.erb index 7271626e4..9a95ad834 100644 --- a/app/views/notifications/_notification.html.erb +++ b/app/views/notifications/_notification.html.erb @@ -1,6 +1,6 @@ <% cache notification do %> <%= notification_tag notification do %> -
    +
    <%= avatar_image_tag(notification.creator) %>
    From 62ef5d90a627179467c679916064b67e9f2d6583 Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Fri, 24 Jan 2025 14:18:38 -0600 Subject: [PATCH 22/27] Match notification dot --- app/assets/stylesheets/utilities.css | 1 + app/views/notifications/index.html.erb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/utilities.css b/app/assets/stylesheets/utilities.css index a512aca62..ff51ea9ff 100644 --- a/app/assets/stylesheets/utilities.css +++ b/app/assets/stylesheets/utilities.css @@ -32,6 +32,7 @@ .txt-reversed { color: var(--color-ink-reversed); } .txt-negative { color: var(--color-negative); } .txt-subtle { color: var(--color-subtle-dark); } +.txt-alert { color: var(--color-marker); } .txt-undecorated { text-decoration: none; } .txt-tight-lines { line-height: 1.2; } .txt-normal { font-weight: 400; font-style: normal; } diff --git a/app/views/notifications/index.html.erb b/app/views/notifications/index.html.erb index 35942d06a..5040fe0ac 100644 --- a/app/views/notifications/index.html.erb +++ b/app/views/notifications/index.html.erb @@ -17,7 +17,7 @@
    <% if @unread.any? %> -

    New for you

    +

    New for you

    <%= render @unread %> <% else %>
    From 3e7fbbd401f980ffb7fe6fed8a26468eb380d379 Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Fri, 24 Jan 2025 14:26:24 -0600 Subject: [PATCH 23/27] Tighten labels --- app/views/bubbles/_publish.html.erb | 2 +- app/views/bubbles/show.html.erb | 8 ++++---- app/views/bubbles/sidebar/_assignment.html.erb | 2 +- app/views/bubbles/sidebar/_tag.html.erb | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/views/bubbles/_publish.html.erb b/app/views/bubbles/_publish.html.erb index 1079e1b0b..bd0609a27 100644 --- a/app/views/bubbles/_publish.html.erb +++ b/app/views/bubbles/_publish.html.erb @@ -1,3 +1,3 @@ <%= button_to bucket_bubble_publish_path(bubble.bucket, bubble), class: "btn txt-small btn--link" do %> - Publish + Post to project <% end %> diff --git a/app/views/bubbles/show.html.erb b/app/views/bubbles/show.html.erb index cc98b66fb..e733ea104 100644 --- a/app/views/bubbles/show.html.erb +++ b/app/views/bubbles/show.html.erb @@ -32,7 +32,7 @@ <%= form_with model: @bubble, url: bucket_bubble_path(@bubble.bucket, @bubble), data: { controller: "form" } do |form| %> <% end %> @@ -49,7 +49,7 @@ <%= form_with model: @bubble, url: bucket_bubble_path(@bubble.bucket, @bubble), data: { controller: "form" } do |form| %>
    diff --git a/app/views/bubbles/sidebar/_assignment.html.erb b/app/views/bubbles/sidebar/_assignment.html.erb index ba759ebdf..215f0821f 100644 --- a/app/views/bubbles/sidebar/_assignment.html.erb +++ b/app/views/bubbles/sidebar/_assignment.html.erb @@ -1,7 +1,7 @@
    diff --git a/app/views/bubbles/sidebar/_tag.html.erb b/app/views/bubbles/sidebar/_tag.html.erb index 46fd40b08..e2fe107c1 100644 --- a/app/views/bubbles/sidebar/_tag.html.erb +++ b/app/views/bubbles/sidebar/_tag.html.erb @@ -1,7 +1,7 @@
    From 7e97f1b672bdcf84607f964bb44228100d9c3e3c Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Fri, 24 Jan 2025 14:54:10 -0600 Subject: [PATCH 24/27] Let's try without the big drag button, extend beyond the column a touch --- app/assets/stylesheets/bubbles.css | 2 ++ app/views/bubbles/list/_divider.html.erb | 8 ++------ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/app/assets/stylesheets/bubbles.css b/app/assets/stylesheets/bubbles.css index 864f0d34d..be3f39de4 100644 --- a/app/assets/stylesheets/bubbles.css +++ b/app/assets/stylesheets/bubbles.css @@ -402,8 +402,10 @@ --border-size: 2px; --divider-color: var(--color-subtle-dark); + block-size: calc(100% + 4ch); font-size: 1rem; cursor: grab; + margin-inline: -1rem; visibility: hidden; } diff --git a/app/views/bubbles/list/_divider.html.erb b/app/views/bubbles/list/_divider.html.erb index 633408719..f26ebf177 100644 --- a/app/views/bubbles/list/_divider.html.erb +++ b/app/views/bubbles/list/_divider.html.erb @@ -1,12 +1,8 @@ -
  • +
  • Drag up or down
    -
    - Top 10 + Bubble up 10
    From ec65f8a529980154e0cf25d9e6ea24b7a758485a Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Fri, 24 Jan 2025 14:55:24 -0600 Subject: [PATCH 25/27] Avoid overlap --- app/assets/stylesheets/filters.css | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/stylesheets/filters.css b/app/assets/stylesheets/filters.css index 745989b44..3d31519d7 100644 --- a/app/assets/stylesheets/filters.css +++ b/app/assets/stylesheets/filters.css @@ -86,4 +86,5 @@ inline-size: auto !important; max-inline-size: var(--panel-size) !important; + z-index: 1; } From ad73853fe07c9b5f8016cfcf496ac27e319b9ce4 Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Fri, 24 Jan 2025 15:04:11 -0600 Subject: [PATCH 26/27] Let's not assume it's a due date --- app/views/bubbles/list/_bubble.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/bubbles/list/_bubble.html.erb b/app/views/bubbles/list/_bubble.html.erb index 8836fd8f2..0f5702313 100644 --- a/app/views/bubbles/list/_bubble.html.erb +++ b/app/views/bubbles/list/_bubble.html.erb @@ -24,7 +24,7 @@ <% if bubble.due_on.present? %> <% end %>
  • From 43ee37a42206646f1198aad271a299fec2c7dd25 Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Fri, 24 Jan 2025 15:06:02 -0600 Subject: [PATCH 27/27] Leave some space for the tray --- app/assets/stylesheets/layout.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/layout.css b/app/assets/stylesheets/layout.css index 3cb50a695..b30ba7172 100644 --- a/app/assets/stylesheets/layout.css +++ b/app/assets/stylesheets/layout.css @@ -1,6 +1,6 @@ body { display: grid; - grid-template-rows: auto 1fr auto var(--block-space); + grid-template-rows: auto 1fr auto 10rem; } :where(#main) {