notey 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7610a3ba662e8faccd42819f0422ba19dd6b23f65619de246b28b89b201d7cbd
4
- data.tar.gz: ce7e9b0e188efd5790e8b2d9a8545d0fdfc418133db9f7fca3f78cf529eb2e71
3
+ metadata.gz: 2ace1eb52d62353acc618570451eade8a1736dd225b8ce899c68eec551c51355
4
+ data.tar.gz: 4752a3d42bab1851769adaa9bf11fdfb730ceb1ce80f0371a7835af6c837761e
5
5
  SHA512:
6
- metadata.gz: 961ec39375572d803830b55c13bc140c44a2a999d7ce6bdc55ee5124ef5bf9f059adb46bbb87fce89af72b6a9bfb61ce5617bdd513fb0be725d48df2cbf88e4e
7
- data.tar.gz: cf7398efa6cd3d2a7f8afe4ff6a7dd83076db6cc4929f3ca80506c590d5ab86aadf5fea04ec6cb3cac8119d4d00ec34c7e3112dc2c25ecc4e8dde29f44b44125
6
+ metadata.gz: 2422c9b8fbe31ee6ac5907eb6f61a4f85ad46404c6c8fba15207923db5069e0886bb6ecf8c0fa52c65f2491cc506cda42f59f3964d7bf56b2be988d9b36b0332
7
+ data.tar.gz: fbccb4c605399312f12a45f0e15cf512f9542e794aa2fd582c45e6c731fb851d52a770ca7d194274ff1fde173ed62f475e1a13b767dc7bfdae0888357bc53ffd
data/README.md CHANGED
@@ -207,6 +207,42 @@ from anywhere else does nothing.
207
207
  and `values:`, and answer with an object responding to `ok?` and `message`. The
208
208
  engine's own pages call the same two actions.
209
209
 
210
+ ## Live updates
211
+
212
+ An open inbox can show a new notification the moment it is delivered, without a
213
+ reload. It is off until you turn it on, because it needs Turbo and a running
214
+ Action Cable server in production.
215
+
216
+ ```ruby
217
+ # config/initializers/notey.rb
218
+ Notey.live_updates = true
219
+ Notey.mark_read_url = ->(notification) { Rails.application.routes.url_helpers.notifications_path }
220
+ ```
221
+
222
+ `mark_read_url` is the url a pushed row's Mark read button posts to. A push is
223
+ rendered in a background job, which has no request to read that url from, so
224
+ you name it here. It is usually the same url you pass the inbox as `submit_url`.
225
+
226
+ With it on, the inbox and the engine's notifications page subscribe to one
227
+ stream per person per account, and:
228
+
229
+ - a notification delivered in-app appears at the top of that person's open
230
+ inbox in that account,
231
+ - marking one read shows it as read in the person's other open tabs,
232
+ - the unread count changes in every open tab of that person in that account.
233
+
234
+ Show the unread count anywhere on your page, and a push keeps it current:
235
+
236
+ ```erb
237
+ <%= render "notey/unread_count", person: current_user, account: current_account.id %>
238
+ ```
239
+
240
+ Notey does not install Turbo for you. Add `turbo-rails` to your Gemfile and load
241
+ its JavaScript. With live updates on and Turbo or `mark_read_url` missing, the
242
+ app refuses to boot and names what is missing. A push that fails, such as when
243
+ the cable server is down, is logged, and the read or delivery it came from still
244
+ goes through.
245
+
210
246
  ## Sending digests
211
247
 
212
248
  A type set to daily or weekly sends nothing when it happens. Run the window on a
@@ -6,13 +6,12 @@ module Notey
6
6
 
7
7
  def index
8
8
  @notifications = inbox.includes(:event).order(created_at: :desc).limit(PER_PAGE)
9
- @unread_count = inbox.unread.count
10
9
 
11
10
  Noticed::Notification.where(id: @notifications.map(&:id)).unseen.mark_as_seen
12
11
  end
13
12
 
14
13
  def update
15
- inbox.find(params[:id]).mark_as_read!
14
+ MarkRead.new(person: Current.member, account: Current.account_id, values: { read: params[:id] }).call
16
15
 
17
16
  redirect_to notifications_path
18
17
  end
@@ -3,6 +3,7 @@
3
3
  module Notey
4
4
  class InApp < Noticed::DeliveryMethod
5
5
  def deliver
6
+ LiveInbox.deliver(notification) if Notey.live_updates
6
7
  end
7
8
 
8
9
  def outbound?
@@ -9,15 +9,20 @@ module Notey
9
9
  end
10
10
 
11
11
  def call
12
- notification&.mark_as_read!
12
+ mark(notification) if notification
13
13
 
14
14
  Kept.new
15
15
  end
16
16
 
17
17
  private
18
18
 
19
+ def mark(notification)
20
+ notification.mark_as_read!
21
+ LiveInbox.read(notification) if Notey.live_updates
22
+ end
23
+
19
24
  def notification
20
- Inbox.for(@person, account_id: @account_id).find_by(id: @values[:read])
25
+ @notification ||= Inbox.for(@person, account_id: @account_id).find_by(id: @values[:read])
21
26
  end
22
27
  end
23
28
  end
@@ -1,16 +1,7 @@
1
1
  <% notifications = Notey::Inbox.for(person, account_id: account).includes(:event).order(created_at: :desc) %>
2
2
 
3
- <% notifications.each do |notification| %>
4
- <%= ui_panel do %>
5
- <div data-notification-id="<%= notification.id %>">
6
- <%= notification.event.title %>
7
- <%= ui_badge(label: notification.read? ? "Read" : "Unread") %>
8
- <% unless notification.read? %>
9
- <%= ui_form(action: submit_url, method: :patch) do %>
10
- <%= hidden_field_tag "read", notification.id, id: nil %>
11
- <%= ui_button(label: "Mark read") %>
12
- <% end %>
13
- <% end %>
14
- </div>
15
- <% end %>
16
- <% end %>
3
+ <%= render "notey/live_stream", person: person, account: account %>
4
+
5
+ <div id="notey_inbox">
6
+ <%= render partial: "notey/notification", collection: notifications, locals: { submit_url: submit_url } %>
7
+ </div>
@@ -0,0 +1 @@
1
+ <%= turbo_stream_from(*Notey::LiveInbox.stream(person, account)) if Notey.live_updates && account.present? %>
@@ -0,0 +1,14 @@
1
+ <div id="notey_notification_<%= notification.id %>">
2
+ <%= ui_panel do %>
3
+ <div data-notification-id="<%= notification.id %>">
4
+ <%= notification.event.title %>
5
+ <%= ui_badge(label: notification.read? ? "Read" : "Unread") %>
6
+ <% unless notification.read? %>
7
+ <%= ui_form(action: submit_url, method: :patch) do %>
8
+ <%= hidden_field_tag "read", notification.id, id: nil %>
9
+ <%= ui_button(label: "Mark read") %>
10
+ <% end %>
11
+ <% end %>
12
+ </div>
13
+ <% end %>
14
+ </div>
@@ -0,0 +1 @@
1
+ <span id="notey_unread_count"><%= Notey::Inbox.for(person, account_id: account).unread.count %></span>
@@ -1,17 +1,11 @@
1
+ <%= render "notey/live_stream", person: Notey::Current.member, account: Notey::Current.account_id %>
2
+
1
3
  <%= ui_page(max_width: :lg) do %>
2
- <%= ui_section(title: "Notifications", subtitle: "#{@unread_count} unread") do %>
3
- <% @notifications.each do |notification| %>
4
- <%= ui_panel do %>
5
- <div data-notification-id="<%= notification.id %>">
6
- <%= notification.event.class.name %>
7
- <%= ui_badge(label: notification.read? ? "Read" : "Unread") %>
8
- <% unless notification.read? %>
9
- <%= ui_form(action: notification_path(notification), method: :patch) do %>
10
- <%= ui_button(label: "Mark read") %>
11
- <% end %>
12
- <% end %>
13
- </div>
4
+ <%= ui_section(title: "Notifications", subtitle: safe_join([ render("notey/unread_count", person: Notey::Current.member, account: Notey::Current.account_id), " unread" ])) do %>
5
+ <div id="notey_inbox">
6
+ <% @notifications.each do |notification| %>
7
+ <%= render "notey/notification", notification: notification, submit_url: notification_path(notification) %>
14
8
  <% end %>
15
- <% end %>
9
+ </div>
16
10
  <% end %>
17
11
  <% end %>
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Notey
4
+ class LiveInbox
5
+ def self.stream(person, account_id)
6
+ [ person, :notey_inbox, account_id ]
7
+ end
8
+
9
+ def self.deliver(notification)
10
+ return if notification.account_id.blank?
11
+
12
+ pushing(notification) do
13
+ Turbo::StreamsChannel.broadcast_prepend_to(
14
+ *stream_of(notification), target: "notey_inbox", html: row(notification)
15
+ )
16
+ push_unread_count(notification)
17
+ end
18
+ end
19
+
20
+ def self.read(notification)
21
+ pushing(notification) do
22
+ Turbo::StreamsChannel.broadcast_replace_to(
23
+ *stream_of(notification), target: "notey_notification_#{notification.id}", html: row(notification)
24
+ )
25
+ push_unread_count(notification)
26
+ end
27
+ end
28
+
29
+ def self.pushing(notification)
30
+ yield
31
+ rescue StandardError => error
32
+ Rails.logger.error("notey could not push notification #{notification.id}: #{error.message}")
33
+ end
34
+
35
+ def self.push_unread_count(notification)
36
+ Turbo::StreamsChannel.broadcast_replace_to(
37
+ *stream_of(notification), target: "notey_unread_count", html: unread_count(notification)
38
+ )
39
+ end
40
+
41
+ def self.stream_of(notification)
42
+ stream(notification.recipient, notification.account_id)
43
+ end
44
+
45
+ def self.row(notification)
46
+ Notey::ApplicationController.render(
47
+ partial: "notey/notification",
48
+ locals: { notification: notification, submit_url: Notey.mark_read_url.call(notification) }
49
+ )
50
+ end
51
+
52
+ def self.unread_count(notification)
53
+ Notey::ApplicationController.render(
54
+ partial: "notey/unread_count",
55
+ locals: { person: notification.recipient, account: notification.account_id }
56
+ )
57
+ end
58
+ private_class_method :pushing, :push_unread_count, :stream_of, :row, :unread_count
59
+ end
60
+ end
data/lib/notey/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Notey
2
- VERSION = "0.4.0"
2
+ VERSION = "0.5.0"
3
3
  end
data/lib/notey.rb CHANGED
@@ -3,6 +3,7 @@ require "notey/engine"
3
3
  require "notey/channels"
4
4
  require "notey/destinations"
5
5
  require "notey/inbox"
6
+ require "notey/live_inbox"
6
7
  require "notey/digest_run"
7
8
  require "notey/records_attempt"
8
9
 
@@ -20,10 +21,12 @@ module Notey
20
21
  class UnsendableChannel < StandardError; end
21
22
  class UnsendableAttempt < StandardError; end
22
23
  class MissingRetention < StandardError; end
24
+ class MissingMarkReadUrl < StandardError; end
25
+ class MissingTurbo < StandardError; end
23
26
 
24
27
  class << self
25
28
  attr_writer :notification_url, :mailer_sender
26
- attr_accessor :attempt_retention
29
+ attr_accessor :attempt_retention, :live_updates, :mark_read_url
27
30
  end
28
31
 
29
32
  def self.mailer_sender
@@ -87,6 +90,7 @@ module Notey
87
90
  def self.check!
88
91
  registered_channels.each { |channel| check_channel!(channel) }
89
92
  check_sender!
93
+ check_live_updates! if live_updates
90
94
  end
91
95
 
92
96
  def self.check_channel!(channel)
@@ -116,6 +120,17 @@ module Notey
116
120
  raise MissingSender, "notey sends digests by email and no mailer_sender is set"
117
121
  end
118
122
 
123
+ def self.check_live_updates!
124
+ raise MissingTurbo, "notey pushes live updates with turbo-rails, which is not loaded" unless turbo_loaded?
125
+ return if mark_read_url
126
+
127
+ raise MissingMarkReadUrl, "notey pushes rows with a Mark read button and no mark_read_url is set"
128
+ end
129
+
130
+ def self.turbo_loaded?
131
+ defined?(::Turbo::StreamsChannel).present?
132
+ end
133
+
119
134
  def self.sends(notification_type, on:, addressed: false, via: nil)
120
135
  lambda do
121
136
  decision = Channels.decision_for(recipient, notification_type, account_id: event.account_id)
@@ -130,6 +145,8 @@ module Notey
130
145
 
131
146
  def self.reset!
132
147
  @host_channels = nil
148
+ @live_updates = nil
149
+ @mark_read_url = nil
133
150
  forget_notifiers
134
151
  end
135
152
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: notey
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - tylercschneider
@@ -101,8 +101,11 @@ files:
101
101
  - app/models/notey/save_preferences.rb
102
102
  - app/views/notey/_destinations.html.erb
103
103
  - app/views/notey/_inbox.html.erb
104
+ - app/views/notey/_live_stream.html.erb
104
105
  - app/views/notey/_my_destinations.html.erb
106
+ - app/views/notey/_notification.html.erb
105
107
  - app/views/notey/_preferences.html.erb
108
+ - app/views/notey/_unread_count.html.erb
106
109
  - app/views/notey/destinations/show.html.erb
107
110
  - app/views/notey/digest_mailer/digest.html.erb
108
111
  - app/views/notey/notification_mailer/notification.html.erb
@@ -122,6 +125,7 @@ files:
122
125
  - lib/notey/digest_run.rb
123
126
  - lib/notey/engine.rb
124
127
  - lib/notey/inbox.rb
128
+ - lib/notey/live_inbox.rb
125
129
  - lib/notey/records_attempt.rb
126
130
  - lib/notey/version.rb
127
131
  - lib/tasks/notey_tasks.rake