activity_notification 1.0.0 → 1.0.1

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.
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
+