activity_notification 1.0.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +3 -3
  3. data/README.md +49 -9
  4. data/activity_notification.gemspec +1 -1
  5. data/app/controllers/activity_notification/notifications_controller.rb +75 -31
  6. data/app/mailers/activity_notification/mailer.rb +19 -4
  7. data/app/views/activity_notification/mailer/default/batch_default.html.erb +79 -0
  8. data/app/views/activity_notification/mailer/default/batch_default.text.erb +13 -0
  9. data/app/views/activity_notification/mailer/default/default.html.erb +75 -10
  10. data/app/views/activity_notification/mailer/default/default.text.erb +2 -2
  11. data/app/views/activity_notification/notifications/default/_default.html.erb +15 -14
  12. data/app/views/activity_notification/notifications/default/_default_without_grouping.html.erb +165 -0
  13. data/app/views/activity_notification/notifications/default/_index.html.erb +8 -4
  14. data/app/views/activity_notification/notifications/default/destroy.js.erb +2 -2
  15. data/app/views/activity_notification/notifications/default/index.html.erb +9 -5
  16. data/app/views/activity_notification/notifications/default/open.js.erb +6 -2
  17. data/app/views/activity_notification/notifications/default/open_all.js.erb +6 -2
  18. data/lib/activity_notification/apis/notification_api.rb +42 -9
  19. data/lib/activity_notification/helpers/view_helpers.rb +48 -19
  20. data/lib/activity_notification/mailers/helpers.rb +74 -37
  21. data/lib/activity_notification/models/concerns/target.rb +290 -26
  22. data/lib/activity_notification/models/notification.rb +85 -29
  23. data/lib/activity_notification/roles/acts_as_target.rb +4 -2
  24. data/lib/activity_notification/version.rb +1 -1
  25. data/spec/concerns/apis/notification_api_spec.rb +46 -0
  26. data/spec/concerns/models/target_spec.rb +281 -22
  27. data/spec/controllers/notifications_controller_shared_examples.rb +77 -0
  28. data/spec/helpers/view_helpers_spec.rb +39 -3
  29. data/spec/mailers/mailer_spec.rb +54 -1
  30. data/spec/models/notification_spec.rb +11 -0
  31. data/spec/rails_app/app/models/user.rb +1 -1
  32. data/spec/rails_app/app/views/layouts/_header.html.erb +2 -0
  33. data/spec/rails_app/lib/mailer_previews/mailer_preview.rb +6 -0
  34. data/spec/roles/acts_as_target_spec.rb +1 -1
  35. metadata +7 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 428fe6b5adf0df335cd293284bfaa4ef7edcd415
4
- data.tar.gz: 10d9652b853e719fd4f50886682446fabac270a6
3
+ metadata.gz: eafbddc7c4427310930baf3d6f23464f4d0c2baa
4
+ data.tar.gz: 38dce4c34e4e22e06be7132d588906500127c017
5
5
  SHA512:
6
- metadata.gz: 974b2c19ee0e9fec32dd9853325076300c0b31c0c38c5516ae3c51a0122966bbb65ed442d2eca1fa53a2fd81d61e0372622679849fe32d77b09c983068d40c95
7
- data.tar.gz: e40bc4f0b7c8570a493fd325c73937db18f7223068278ef8da5348591ffb0417bdf624c1f6ac7fda7405a2e726efcfdeedfe7599d454ba3943c6b43c91b860b9
6
+ metadata.gz: 4da1eff72e8906c9398bb43f9273ca1c788ce91debeccfc4ec9d371fa6032b11c12fb60594c5191e37717f88df4b1d020b82506357c469c9f1894ba0930ac992
7
+ data.tar.gz: 08ce06b4beaf320607e276d7dabd9486c58b7f979aa29ee522da90a7c9972eda2bc8148cadad1b90c66dd46bbead0fa819a0bff43a5fd17b9c8b9a1db5e4c113
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- activity_notification (1.0.0)
4
+ activity_notification (1.0.1)
5
5
  activerecord (>= 4.2.0)
6
6
  i18n (>= 0.5.0)
7
7
  jquery-rails (>= 3.1.1)
@@ -164,7 +164,7 @@ GEM
164
164
  actionpack (>= 4.0)
165
165
  activesupport (>= 4.0)
166
166
  sprockets (>= 3.0.0)
167
- sqlite3 (1.3.11)
167
+ sqlite3 (1.3.12)
168
168
  term-ansicolor (1.4.0)
169
169
  tins (~> 1.0)
170
170
  thor (0.19.1)
@@ -197,7 +197,7 @@ DEPENDENCIES
197
197
  rails-controller-testing
198
198
  rspec-rails (~> 3.5.1)
199
199
  simplecov (~> 0.12.0)
200
- sqlite3 (~> 1.3.11)
200
+ sqlite3 (~> 1.3.12)
201
201
  timecop
202
202
  yard (~> 0.9.5)
203
203
  yard-activesupport-concern (~> 0.0.1)
data/README.md CHANGED
@@ -21,6 +21,7 @@
21
21
  * Notification views (presentation of notifications)
22
22
  * Grouping notifications (grouping like `"Kevin and 7 other users posted comments to this article"`)
23
23
  * Email notification
24
+ * Batch email notification
24
25
  * Integration with [Devise](https://github.com/plataformatec/devise) authentication
25
26
 
26
27
  ### Notification index
@@ -55,8 +56,11 @@
55
56
  1. [Setup mailer](#setup-mailer)
56
57
  2. [Email templates](#email-templates)
57
58
  3. [i18n for email](#i18n-for-email)
58
- 2. [Grouping notifications](#grouping-notifications)
59
- 3. [Integration with Devise](#integration-with-devise)
59
+ 2. [Batch email notification](#batch-email-notification)
60
+ 1. [Batch email templates](#batch-email-templates)
61
+ 2. [i18n for batch email](#i18n-for-batch-email)
62
+ 3. [Grouping notifications](#grouping-notifications)
63
+ 4. [Integration with Devise](#integration-with-devise)
60
64
  4. [Testing](#testing)
61
65
  5. [Documentation](#documentation)
62
66
  6. **[Common examples](#common-examples)**
@@ -308,7 +312,7 @@ If you want to set notification index in the common layout, such as common heade
308
312
  <%= render_notifications_of current_user, index_content: :with_attributes %>
309
313
  ```
310
314
 
311
- Then, content named :notification_index will be prepared and you can use it in your partial template.
315
+ Then, content named `:notification_index` will be prepared and you can use it in your partial template.
312
316
 
313
317
  ```activity_notifications/notifications/users/_index.html.erb
314
318
  ...
@@ -330,29 +334,29 @@ For example, if you have an notification with `:key` set to `"notification.comme
330
334
 
331
335
  *Hint*: the `"notification."` prefix in `:key` is completely optional, you can skip it in your projects or use this prefix only to make namespace.
332
336
 
333
- If you would like to fallback to a partial, you can utilize the `fallback` parameter to specify the path of a partial to use when one is missing:
337
+ If you would like to fallback to a partial, you can utilize the `:fallback` parameter to specify the path of a partial to use when one is missing:
334
338
 
335
339
  ```erb
336
- <%= render_notification(@notification, target: :users, fallback: 'default') %>
340
+ <%= render_notification(@notification, target: :users, fallback: :default) %>
337
341
  ```
338
342
 
339
- When used in this manner, if a partial with the specified `:key` cannot be located it will use the partial defined in the `fallback` instead. In the example above this would resolve to `activity_notification/notifications/users/_default.html.(|erb|haml|slim|something_else)`.
343
+ When used in this manner, if a partial with the specified `:key` cannot be located it will use the partial defined in the `:fallback` instead. In the example above this would resolve to `activity_notification/notifications/users/_default.html.(|erb|haml|slim|something_else)`.
340
344
 
341
345
  If you do not specify `:target` option like this,
342
346
 
343
347
  ```erb
344
- <%= render_notification(@notification, fallback: 'default') %>
348
+ <%= render_notification(@notification, fallback: :default) %>
345
349
  ```
346
350
 
347
351
  the gem will look for a partial in `default` as the target type which means `activity_notification/notifications/default/_default.html.(|erb|haml|slim|something_else)`.
348
352
 
349
- If a view file does not exist then ActionView::MisingTemplate will be raised. If you wish to fallback to the old behaviour and use an i18n based translation in this situation you can specify a `:fallback` parameter of `text` to fallback to this mechanism like such:
353
+ If a view file does not exist then ActionView::MisingTemplate will be raised. If you wish to fallback to the old behaviour and use an i18n based translation in this situation you can specify a `:fallback` parameter of `:text` to fallback to this mechanism like such:
350
354
 
351
355
  ```erb
352
356
  <%= render_notification(@notification, fallback: :text) %>
353
357
  ```
354
358
 
355
- Default views of `activity_notification` depends on jQuery and you have to add requirements to `application.js` in your apps:
359
+ Finally, default views of `activity_notification` depends on jQuery and you have to add requirements to `application.js` in your apps:
356
360
 
357
361
  ```app/assets/javascripts/application.js
358
362
  //= require jquery
@@ -456,6 +460,42 @@ notification:
456
460
  mail_subject: 'New comment to your article'
457
461
  ```
458
462
 
463
+ ### Batch email notification
464
+
465
+ `activity_notification` provides batch email notification to the notification targets. You can send notification daily or hourly with scheduler like `whenever`.
466
+
467
+ You can automatically send batch notification email for unopened notifications only to the all specified targets with `batch_key`.
468
+
469
+ ```ruby
470
+ # Send batch notification email to the users with unopened notifications
471
+ User.send_batch_unopened_notification_email(batch_key: 'batch.comment.post')
472
+ ```
473
+
474
+ You can also add conditions to filter notifications, like this:
475
+
476
+ ```ruby
477
+ # Send batch notification email to the users with unopened notifications of specified key in 1 hour
478
+ User.send_batch_unopened_notification_email(batch_key: 'batch.comment.post', filtered_by_key: 'comment.post', custom_filter: ["created_at >= ?", time.hour.ago])
479
+ ```
480
+
481
+ #### Batch email templates
482
+
483
+ `activity_notification` will look for batch email template in the same way as email notification using `batch_key`.
484
+ `batch_key` is specified by `:batch_key` option. If the option is not specified, The key of the first notification will be used as `batch_key`.
485
+
486
+ #### i18n for batch email
487
+
488
+ The subject of batch notification email also can be put in your locale `.yml` files as `mail_subject` field for `batch_key`.
489
+
490
+ ```yaml
491
+ notification:
492
+ user:
493
+ batch:
494
+ comment:
495
+ post:
496
+ mail_subject: 'New comments to your article'
497
+ ```
498
+
459
499
  ### Grouping notifications
460
500
 
461
501
  `activity_notification` provides the function for automatically grouping notifications. When you created a notification like this, all *unopened* notifications to the same target will be grouped by `article` set as `:group` options:
@@ -25,7 +25,7 @@ Gem::Specification.new do |s|
25
25
  s.add_dependency 'activerecord', '>= 4.2.0'
26
26
  s.add_dependency 'jquery-rails', '>= 3.1.1'
27
27
 
28
- s.add_development_dependency 'sqlite3', '~> 1.3.11'
28
+ s.add_development_dependency 'sqlite3', '~> 1.3.12'
29
29
  s.add_development_dependency 'rspec-rails', '~> 3.5.1'
30
30
  s.add_development_dependency 'factory_girl_rails', '~> 4.7.0'
31
31
  s.add_development_dependency 'simplecov', '~> 0.12.0'
@@ -15,13 +15,21 @@ module ActivityNotification
15
15
  #
16
16
  # GET /:target_type/:target_id/notifcations
17
17
  # @overload index(params)
18
- # @param [Hash] params Request parameters
19
- # @option params [String] :filter (nil) Filter option to load notification index. Nothing means auto loading. 'opened' means opened only and 'unopened' means unopened only.
20
- # @option params [String] :limit (nil) Limit to query for notifications
21
- # @option params [String] :reload ('true') Whether notification index will be reloaded
18
+ # @param [Hash] params Request parameter options for notification index
19
+ # @option params [String] :filter (nil) Filter option to load notification index (Nothing as auto, 'opened' or 'unopened')
20
+ # @option params [String] :limit (nil) Limit to query for notifications
21
+ # @option params [String] :reverse ('false') If notification index will be ordered as earliest first
22
+ # @option params [String] :without_grouping ('false') If notification index will include group members
23
+ # @option params [String] :with_group_members ('false') If notification index will include group members
24
+ # @option params [String] :filtered_by_type (nil) Notifiable type for filter
25
+ # @option params [String] :filtered_by_group_type (nil) Group type for filter, valid with :filtered_by_group_id
26
+ # @option params [String] :filtered_by_group_id (nil) Group instance id for filter, valid with :filtered_by_group_type
27
+ # @option params [String] :filtered_by_key (nil) Key of the notification for filter
28
+ # @option params [String] :reload ('true') Whether notification index will be reloaded
22
29
  # @return [Responce] HTML view as default or JSON of notification index with json format parameter
23
30
  def index
24
- @notifications = load_notification_index(params[:filter], params[:limit]) if params[:reload].to_s.to_boolean(true)
31
+ set_index_options
32
+ @notifications = load_notification_index(@index_options) if params[:reload].to_s.to_boolean(true)
25
33
  respond_to do |format|
26
34
  format.html # index.html.erb
27
35
  format.json { render json: @notifications.to_json(include: [:target, :notifiable, :group]) }
@@ -36,6 +44,8 @@ module ActivityNotification
36
44
  # @option params [String] :filter (nil) Filter option to load notification index (Nothing as auto, 'opened' or 'unopened')
37
45
  # @option params [String] :limit (nil) Limit to query for notifications
38
46
  # @option params [String] :reload ('true') Whether notification index will be reloaded
47
+ # @option params [String] :without_grouping ('false') If notification index will include group members
48
+ # @option params [String] :with_group_members ('false') If notification index will include group members
39
49
  # @option params [String] :filtered_by_type (nil) Notifiable type for filter
40
50
  # @option params [String] :filtered_by_group_type (nil) Group type for filter, valid only :filtered_by_group_id
41
51
  # @option params [String] :filtered_by_group_id (nil) Group instance id for filter, valid only :filtered_by_group_type
@@ -43,7 +53,7 @@ module ActivityNotification
43
53
  # @return [Responce] JavaScript view for ajax request or redirects to back as default
44
54
  def open_all
45
55
  @target.open_all_notifications(params)
46
- return_back_or_ajax(params[:filter], params[:limit])
56
+ return_back_or_ajax
47
57
  end
48
58
 
49
59
  # Shows a notification.
@@ -61,13 +71,15 @@ module ActivityNotification
61
71
  #
62
72
  # @overload destroy(params)
63
73
  # @param [Hash] params Request parameters
64
- # @option params [String] :filter (nil) Filter option to load notification index (Nothing as auto, 'opened' or 'unopened')
65
- # @option params [String] :limit (nil) Limit to query for notifications
66
- # @option params [String] :reload ('true') Whether notification index will be reloaded
74
+ # @option params [String] :filter (nil) Filter option to load notification index (Nothing as auto, 'opened' or 'unopened')
75
+ # @option params [String] :limit (nil) Limit to query for notifications
76
+ # @option params [String] :reload ('true') Whether notification index will be reloaded
77
+ # @option params [String] :without_grouping ('false') If notification index will include group members
78
+ # @option params [String] :with_group_members ('false') If notification index will include group members
67
79
  # @return [Responce] JavaScript view for ajax request or redirects to back as default
68
80
  def destroy
69
81
  @notification.destroy
70
- return_back_or_ajax(params[:filter], params[:limit])
82
+ return_back_or_ajax
71
83
  end
72
84
 
73
85
  # Opens a notification.
@@ -75,16 +87,19 @@ module ActivityNotification
75
87
  # POST /:target_type/:target_id/notifcations/:id/open
76
88
  # @overload open(params)
77
89
  # @param [Hash] params Request parameters
78
- # @option params [String] :move ('false') Whether redirects to notifiable_path after the notification is opened
79
- # @option params [String] :filter (nil) Filter option to load notification index (Nothing as auto, 'opened' or 'unopened')
80
- # @option params [String] :limit (nil) Limit to query for notifications
81
- # @option params [String] :reload ('true') Whether notification index will be reloaded
90
+ # @option params [String] :move ('false') Whether redirects to notifiable_path after the notification is opened
91
+ # @option params [String] :filter (nil) Filter option to load notification index (Nothing as auto, 'opened' or 'unopened')
92
+ # @option params [String] :limit (nil) Limit to query for notifications
93
+ # @option params [String] :reload ('true') Whether notification index will be reloaded
94
+ # @option params [String] :without_grouping ('false') If notification index will include group members
95
+ # @option params [String] :with_group_members ('false') If notification index will include group members
82
96
  # @return [Responce] JavaScript view for ajax request or redirects to back as default
83
97
  def open
84
- @notification.open!
98
+ with_members = !(params[:with_group_members].to_s.to_boolean(false) || params[:without_grouping].to_s.to_boolean(false))
99
+ @notification.open!(with_members: with_members)
85
100
  params[:move].to_s.to_boolean(false) ?
86
101
  move :
87
- return_back_or_ajax(params[:filter], params[:limit])
102
+ return_back_or_ajax
88
103
  end
89
104
 
90
105
  # Moves to notifiable_path of the notification.
@@ -146,20 +161,40 @@ module ActivityNotification
146
161
  end
147
162
  end
148
163
 
164
+ # Sets options to load notification index from request parameters.
165
+ # @api protected
166
+ # @return [Hash] options to load notification index
167
+ def set_index_options
168
+ limit = params[:limit].to_i > 0 ? params[:limit].to_i : nil
169
+ reverse = params[:reverse].present? ?
170
+ params[:reverse].to_s.to_boolean(false) : nil
171
+ with_group_members = params[:with_group_members].present? || params[:without_grouping].present? ?
172
+ params[:with_group_members].to_s.to_boolean(false) || params[:without_grouping].to_s.to_boolean(false) : nil
173
+ @index_options = params.permit(:filter, :filtered_by_type, :filtered_by_group_type, :filtered_by_group_id, :filtered_by_key)
174
+ .to_h.symbolize_keys
175
+ .merge(limit: limit, reverse: reverse, with_group_members: with_group_members)
176
+ end
177
+
149
178
  # Loads notification index with request parameters.
150
179
  # @api protected
151
- # @param [String] filter Filter option to load notification index (Nothing as auto, 'opened' or 'unopened')
152
- # @param [String] limit Limit to query for notifications
180
+ # @param [Hash] params Request parameter options for notification index
181
+ # @option params [String] :filter (nil) Filter option to load notification index (Nothing as auto, 'opened' or 'unopened')
182
+ # @option params [Integer] :limit (nil) Limit to query for notifications
183
+ # @option params [Boolean] :reverse (false) If notification index will be ordered as earliest first
184
+ # @option params [Boolean] :without_grouping (false) If notification index will include group members
185
+ # @option params [String] :filtered_by_type (nil) Notifiable type for filter
186
+ # @option params [String] :filtered_by_group_type (nil) Group type for filter, valid with :filtered_by_group_id
187
+ # @option params [String] :filtered_by_group_id (nil) Group instance id for filter, valid with :filtered_by_group_type
188
+ # @option params [String] :filtered_by_key (nil) Key of the notification for filter
153
189
  # @return [Array] Array of notification index
154
- def load_notification_index(filter, limit)
155
- limit = limit.to_i > 0 ? limit.to_i : nil
156
- case filter
190
+ def load_notification_index(options = {})
191
+ case options[:filter]
157
192
  when 'opened'
158
- @target.opened_notification_index_with_attributes({ limit: limit })
193
+ @target.opened_notification_index_with_attributes(options)
159
194
  when 'unopened'
160
- @target.unopened_notification_index_with_attributes({ limit: limit })
195
+ @target.unopened_notification_index_with_attributes(options)
161
196
  else
162
- @target.notification_index_with_attributes({ limit: limit })
197
+ @target.notification_index_with_attributes(options)
163
198
  end
164
199
  end
165
200
 
@@ -171,23 +206,32 @@ module ActivityNotification
171
206
 
172
207
  # Returns JavaScript view for ajax request or redirects to back as default.
173
208
  # @api protected
174
- # @param [String] filter Filter option to load notification index (Nothing as auto, 'opened' or 'unopened')
175
- # @param [String] limit Limit to query for notifications
209
+ # @option params [String] :filter (nil) Filter option to load notification index (Nothing as auto, 'opened' or 'unopened')
210
+ # @option params [Integer] :limit (nil) Limit to query for notifications
211
+ # @option params [Boolean] :reverse (false) If notification index will be ordered as earliest first
212
+ # @option params [Boolean] :without_grouping (false) If notification index will include group members
213
+ # @option params [String] :filtered_by_type (nil) Notifiable type for filter
214
+ # @option params [String] :filtered_by_group_type (nil) Group type for filter, valid with :filtered_by_group_id
215
+ # @option params [String] :filtered_by_group_id (nil) Group instance id for filter, valid with :filtered_by_group_type
216
+ # @option params [String] :filtered_by_key (nil) Key of the notification for filter
176
217
  # @return [Responce] JavaScript view for ajax request or redirects to back as default
177
- def return_back_or_ajax(filter, limit)
178
- @notifications = load_notification_index(params[:filter], params[:limit]) if params[:reload].to_s.to_boolean(true)
218
+ def return_back_or_ajax
219
+ set_index_options
220
+ if params[:reload].to_s.to_boolean(true)
221
+ @notifications = load_notification_index(@index_options)
222
+ end
179
223
  respond_to do |format|
180
224
  if request.xhr?
181
225
  format.js
182
226
  # :skip-rails4:
183
227
  elsif Rails::VERSION::MAJOR >= 5
184
- redirect_back fallback_location: { action: :index }, filter: filter, limit: limit and return
228
+ redirect_back fallback_location: { action: :index }, **@index_options and return
185
229
  # :skip-rails4:
186
230
  # :skip-rails5:
187
231
  elsif request.referer
188
- redirect_to :back, filter: filter, limit: limit and return
232
+ redirect_to :back, **@index_options and return
189
233
  else
190
- redirect_to action: :index, filter: filter, limit: limit and return
234
+ redirect_to action: :index, **@index_options and return
191
235
  end
192
236
  # :skip-rails5:
193
237
  end
@@ -4,18 +4,33 @@ if defined?(ActionMailer)
4
4
  include ActivityNotification::Mailers::Helpers
5
5
 
6
6
  # Sends notification email.
7
+ #
7
8
  # @param [Notification] notification Notification instance to send email
8
- # @param [Hash] options Options for notification email
9
+ # @param [Hash] options Options for notification email
9
10
  # @option options [String, Symbol] :fallback (:default) Fallback template to use when MissingTemplate is raised
11
+ # @return [Mail::Message|ActionMailer::DeliveryJob] Email message or its delivery job, return NilClass for wrong target
10
12
  def send_notification_email(notification, options = {})
11
13
  options[:fallback] ||= :default
12
14
  if options[:fallback] == :none
13
15
  options.delete(:fallback)
14
16
  end
15
- if notification.target.notification_email_allowed?(notification.notifiable, notification.key) and
16
- notification.notifiable.notification_email_allowed?(notification.target, notification.key)
17
- notification_mail(notification, options)
17
+ notification_mail(notification, options)
18
+ end
19
+
20
+ # Sends batch notification email.
21
+ #
22
+ # @param [Object] target Target of batch notification email
23
+ # @param [Array<Notification>] notifications Target notifications to send batch notification email
24
+ # @param [Hash] options Options for notification email
25
+ # @option options [String, Symbol] :fallback (:batch_default) Fallback template to use when MissingTemplate is raised
26
+ # @option options [String] :batch_key (nil) Key of the batch notification email, a key of the first notification will be used if not specified
27
+ # @return [Mail::Message|ActionMailer::DeliveryJob] Email message or its delivery job, return NilClass for wrong target
28
+ def send_batch_notification_email(target, notifications, options = {})
29
+ options[:fallback] ||= :batch_default
30
+ if options[:fallback] == :none
31
+ options.delete(:fallback)
18
32
  end
33
+ batch_notification_mail(target, notifications, options)
19
34
  end
20
35
 
21
36
  end
@@ -0,0 +1,79 @@
1
+ <!doctype html>
2
+ <html lang="ja">
3
+ <head>
4
+ <meta content="text/html; charset=UTF-8" />
5
+ <style>
6
+ body {
7
+ font-family: 'Lucida Grande', 'Hiragino Kaku Gothic ProN', Meiryo, sans-serif;
8
+ color: #4f4f4f;
9
+ font-weight: normal;
10
+ font-style: normal;
11
+ }
12
+ p{
13
+ font-size: 14px;
14
+ }
15
+ .activity_wrapper {
16
+ padding: 15px 10px;
17
+ position: relative;
18
+ }
19
+ .activity_wrapper:after{
20
+ content: "";
21
+ clear: both;
22
+ display: block;
23
+ }
24
+ .activity_wrapper .user_image {
25
+ float: left;
26
+ width: 40px;
27
+ height: 40px;
28
+ background-position: center;
29
+ background-repeat: no-repeat;
30
+ background-size: cover;
31
+ background-color: #979797;
32
+ }
33
+ .activity_wrapper .user_desc_wrapper {
34
+ float: left;
35
+ width: calc(100% - 130px);
36
+ margin-top: 0;
37
+ margin-left: 10px;
38
+ }
39
+ .activity_wrapper .user_desc_wrapper .user_desc {
40
+ font-size: 12px;
41
+ line-height: 1.6;
42
+ margin-top: 0;
43
+ margin-bottom: 0;
44
+ }
45
+ .activity_wrapper .user_desc_wrapper .user_desc span{
46
+ color: #979797;
47
+ font-weight: bold;
48
+ }
49
+ .activity_wrapper .user_desc_wrapper .user_desc strong{
50
+ font-weight: bold;
51
+ }
52
+ </style>
53
+ <%= yield :head %>
54
+ </head>
55
+ <body>
56
+ <p>Dear <%= @target.printable_target_name %></p>
57
+ <% @notifications.each do |notification| %>
58
+ <div class="activity_wrapper">
59
+ <div class="user_image"></div>
60
+ <div class="user_desc_wrapper">
61
+ <p class="user_desc">
62
+ <strong><%= notification.notifier.present? ? notification.notifier.printable_notifier_name : 'Someone' %></strong>
63
+ notified you of
64
+ <%= notification.notifiable.printable_notifiable_name(notification.target) %><%= notification.group.present? ? " in #{notification.group.printable_group_name}." : "." %><br>
65
+ <span><%= notification.created_at.strftime("%b %d %H:%M") %></span>
66
+ </p>
67
+ <p class="user_desc">
68
+ <span>
69
+ <%= link_to "Move to notified #{notification.notifiable.printable_type.downcase}", move_notification_url_for(notification, open: true) %>
70
+ </span>
71
+ </p>
72
+ </div>
73
+ </div>
74
+ <% end %>
75
+ <p>Thank you!</p>
76
+ </body>
77
+ </html>
78
+
79
+