integral 1.4.0 → 1.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.
Files changed (172) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +4 -3
  3. data/app/assets/javascripts/integral/backend.js +86 -2
  4. data/app/assets/javascripts/integral/support/character_counter.js +13 -8
  5. data/app/assets/javascripts/integral/support/list.coffee +1 -0
  6. data/app/assets/javascripts/integral/support/record_selector.coffee +2 -0
  7. data/app/assets/javascripts/integral/support/slug_generator.coffee +1 -0
  8. data/app/assets/stylesheets/integral/backend.sass +34 -23
  9. data/app/assets/stylesheets/integral/backend/_foundation_settings.scss +2 -2
  10. data/app/assets/stylesheets/integral/backend/dashboard-layout.scss +27 -63
  11. data/app/assets/stylesheets/integral/backend/devise.sass +2 -3
  12. data/app/assets/stylesheets/integral/backend/materialize-tags.sass +1 -0
  13. data/app/assets/stylesheets/integral/backend/modules/dropdown_pane_notifications.scss +150 -0
  14. data/app/assets/stylesheets/integral/backend/modules/dropdown_pane_profile.scss +59 -0
  15. data/app/assets/stylesheets/integral/backend/shared.sass +41 -2
  16. data/app/assets/stylesheets/integral/frontend/layout.sass +10 -0
  17. data/app/assets/stylesheets/integral/support/media-query-indicator.sass +4 -4
  18. data/app/controllers/integral/backend/activities_controller.rb +21 -27
  19. data/app/controllers/integral/backend/base_controller.rb +87 -37
  20. data/app/controllers/integral/backend/images_controller.rb +26 -8
  21. data/app/controllers/integral/backend/lists_controller.rb +2 -14
  22. data/app/controllers/integral/backend/notification_subscriptions_controller.rb +23 -0
  23. data/app/controllers/integral/backend/pages_controller.rb +0 -4
  24. data/app/controllers/integral/backend/posts_controller.rb +0 -4
  25. data/app/controllers/integral/backend/settings_controller.rb +4 -0
  26. data/app/controllers/integral/backend/static_pages_controller.rb +6 -0
  27. data/app/controllers/integral/backend/users_controller.rb +43 -24
  28. data/app/controllers/integral/blog_controller.rb +12 -0
  29. data/app/controllers/integral/categories_controller.rb +17 -3
  30. data/app/controllers/integral/tags_controller.rb +5 -2
  31. data/app/decorators/integral/base_decorator.rb +16 -0
  32. data/app/decorators/integral/image_decorator.rb +2 -2
  33. data/app/decorators/integral/list_decorator.rb +1 -13
  34. data/app/decorators/integral/notification/notification_decorator.rb +74 -0
  35. data/app/decorators/integral/page_decorator.rb +1 -13
  36. data/app/decorators/integral/post_decorator.rb +1 -2
  37. data/app/decorators/integral/user_decorator.rb +1 -13
  38. data/app/decorators/integral/version_decorator.rb +8 -4
  39. data/app/helpers/integral/backend/base_helper.rb +97 -31
  40. data/app/jobs/integral/application_job.rb +1 -0
  41. data/app/jobs/integral/newsletter_signup_job.rb +0 -2
  42. data/app/mailers/integral/devise_mailer.rb +6 -0
  43. data/app/models/concerns/integral/notification/subscribable.rb +67 -0
  44. data/app/models/integral/application_record.rb +9 -0
  45. data/app/models/integral/category.rb +9 -0
  46. data/app/models/integral/image.rb +40 -3
  47. data/app/models/integral/list.rb +10 -2
  48. data/app/models/integral/list_item.rb +14 -14
  49. data/app/models/integral/list_item_connection.rb +6 -0
  50. data/app/models/integral/notification/notification.rb +28 -0
  51. data/app/models/integral/notification/subscription.rb +14 -0
  52. data/app/models/integral/page.rb +15 -8
  53. data/app/models/integral/post.rb +11 -13
  54. data/app/models/integral/user.rb +45 -2
  55. data/app/policies/integral/base_policy.rb +7 -12
  56. data/app/policies/integral/page_policy.rb +1 -0
  57. data/app/policies/integral/version_policy.rb +0 -8
  58. data/app/views/devise/invitations/edit.haml +1 -4
  59. data/app/views/devise/mailer/invitation_instructions.inky-haml +20 -0
  60. data/app/views/integral/backend/activities/grid/_dropdown_actions.haml +1 -0
  61. data/app/views/integral/backend/activities/grid/_row_content.haml +13 -0
  62. data/app/views/integral/backend/activities/index.haml +7 -13
  63. data/app/views/integral/backend/activities/shared/_grid.haml +35 -20
  64. data/app/views/integral/backend/activities/shared/index.haml +12 -12
  65. data/app/views/integral/backend/activities/shared/show.haml +7 -7
  66. data/app/views/integral/backend/activities/show.haml +1 -1
  67. data/app/views/integral/backend/categories/_modal.haml +2 -3
  68. data/app/views/integral/backend/images/_form.haml +13 -25
  69. data/app/views/integral/backend/images/edit.haml +1 -9
  70. data/app/views/integral/backend/images/grid/_dropdown_actions.haml +5 -0
  71. data/app/views/integral/backend/images/grid/_row_content.haml +5 -0
  72. data/app/views/integral/backend/images/index.haml +11 -17
  73. data/app/views/integral/backend/images/list.haml +11 -0
  74. data/app/views/integral/backend/images/show.haml +26 -0
  75. data/app/views/integral/backend/lists/_form.haml +6 -19
  76. data/app/views/integral/backend/lists/_item_modal.haml +3 -3
  77. data/app/views/integral/backend/lists/_manager.haml +11 -13
  78. data/app/views/integral/backend/lists/edit.haml +6 -20
  79. data/app/views/integral/backend/lists/grid/_dropdown_actions.haml +9 -0
  80. data/app/views/integral/backend/lists/grid/_row_content.haml +3 -0
  81. data/app/views/integral/backend/lists/index.haml +11 -17
  82. data/app/views/integral/backend/lists/list.haml +11 -0
  83. data/app/views/integral/backend/lists/show.haml +30 -0
  84. data/app/views/integral/backend/notifications/_notification.haml +21 -0
  85. data/app/views/integral/backend/pages/_form.haml +19 -43
  86. data/app/views/integral/backend/pages/edit.haml +4 -12
  87. data/app/views/integral/backend/pages/grid/_dropdown_actions.haml +11 -0
  88. data/app/views/integral/backend/pages/grid/_row_content.haml +5 -0
  89. data/app/views/integral/backend/pages/index.haml +6 -6
  90. data/app/views/integral/backend/pages/list.haml +12 -19
  91. data/app/views/integral/backend/pages/show.haml +19 -35
  92. data/app/views/integral/backend/posts/_form.haml +18 -56
  93. data/app/views/integral/backend/posts/edit.haml +4 -14
  94. data/app/views/integral/backend/posts/grid/_dropdown_actions.haml +10 -0
  95. data/app/views/integral/backend/posts/grid/_row_content.haml +6 -0
  96. data/app/views/integral/backend/posts/index.haml +6 -6
  97. data/app/views/integral/backend/posts/list.haml +11 -18
  98. data/app/views/integral/backend/posts/new.haml +0 -1
  99. data/app/views/integral/backend/posts/show.haml +18 -41
  100. data/app/views/integral/backend/shared/_breadcrumbs.haml +7 -4
  101. data/app/views/integral/backend/shared/_image_preview.haml +10 -3
  102. data/app/views/integral/backend/shared/_image_selector.haml +1 -1
  103. data/app/views/integral/backend/shared/_notification_subscription_toggle.haml +22 -0
  104. data/app/views/integral/backend/shared/action_bar/_index.haml +9 -0
  105. data/app/views/integral/backend/shared/action_bar/_show.haml +3 -0
  106. data/app/views/integral/backend/shared/cards/_at_a_glance.haml +3 -3
  107. data/app/views/integral/backend/shared/cards/_categories.haml +27 -28
  108. data/app/views/integral/backend/shared/cards/_object.haml +1 -1
  109. data/app/views/integral/backend/shared/cards/_recent_activity.haml +12 -12
  110. data/app/views/integral/backend/shared/cards/_recent_resources.haml +17 -0
  111. data/app/views/integral/backend/shared/cards/_top_post_authors.haml +14 -15
  112. data/app/views/integral/backend/shared/cards/_welcome.haml +24 -25
  113. data/app/views/integral/backend/shared/{_empty_grid.haml → grid/_empty.haml} +0 -0
  114. data/app/views/integral/backend/shared/grid/_form.haml +9 -0
  115. data/app/views/integral/backend/shared/grid/_grid.haml +21 -0
  116. data/app/views/integral/backend/shared/{_pagination.haml → grid/_pagination.haml} +0 -0
  117. data/app/views/integral/backend/shared/grid/_row_layout.haml +8 -0
  118. data/app/views/integral/backend/shared/record_selector/_collection.haml +1 -0
  119. data/app/views/integral/backend/shared/record_selector/_modal.haml +9 -10
  120. data/app/views/integral/backend/static_pages/dashboard.haml +6 -7
  121. data/app/views/integral/backend/users/_form.haml +34 -46
  122. data/app/views/integral/backend/users/grid/_dropdown_actions.haml +17 -0
  123. data/app/views/integral/backend/users/grid/_row_content.haml +8 -0
  124. data/app/views/integral/backend/users/index.haml +6 -6
  125. data/app/views/integral/backend/users/list.haml +10 -16
  126. data/app/views/integral/backend/users/show.haml +10 -1
  127. data/app/views/integral/categories/show.haml +3 -3
  128. data/app/views/integral/posts/_article_footer.haml +1 -1
  129. data/app/views/integral/posts/_card.haml +1 -1
  130. data/app/views/integral/posts/_most_read_section.haml +1 -1
  131. data/app/views/integral/posts/_post.haml +1 -1
  132. data/app/views/integral/posts/templates/default.haml +1 -1
  133. data/app/views/integral/shared/sidebar/_item.haml +1 -1
  134. data/app/views/layouts/integral/backend.html.haml +24 -5
  135. data/app/views/layouts/integral/backend/_create_dropdown.haml +1 -30
  136. data/app/views/layouts/integral/backend/_main_menu_items.haml +1 -101
  137. data/config/initializers/devise.rb +1 -1
  138. data/config/locales/en.yml +60 -2
  139. data/config/routes.rb +2 -0
  140. data/db/migrate/20200407022636_create_integral_notifications.rb +25 -0
  141. data/db/migrate/20200421223602_add_status_to_integral_users.rb +5 -0
  142. data/db/seeds.rb +15 -11
  143. data/lib/integral.rb +1 -0
  144. data/lib/integral/acts_as_integral.rb +115 -0
  145. data/lib/integral/acts_as_listable.rb +1 -1
  146. data/lib/integral/engine.rb +9 -0
  147. data/lib/integral/grids/activities_grid.rb +0 -1
  148. data/lib/integral/grids/lists_grid.rb +1 -0
  149. data/lib/integral/grids/posts_grid.rb +5 -1
  150. data/lib/integral/grids/users_grid.rb +5 -0
  151. data/lib/integral/list_renderer.rb +5 -1
  152. data/lib/integral/router.rb +20 -4
  153. data/lib/integral/version.rb +1 -1
  154. data/spec/factories.rb +15 -1
  155. metadata +45 -39
  156. data/app/decorators/integral/category_version_decorator.rb +0 -7
  157. data/app/decorators/integral/image_version_decorator.rb +0 -7
  158. data/app/decorators/integral/list_version_decorator.rb +0 -7
  159. data/app/decorators/integral/page_version_decorator.rb +0 -7
  160. data/app/decorators/integral/post_version_decorator.rb +0 -7
  161. data/app/decorators/integral/user_version_decorator.rb +0 -7
  162. data/app/views/devise/mailer/invitation_instructions.html.erb +0 -13
  163. data/app/views/integral/backend/activities/_grid.haml +0 -22
  164. data/app/views/integral/backend/images/_grid.haml +0 -16
  165. data/app/views/integral/backend/lists/_grid.haml +0 -14
  166. data/app/views/integral/backend/pages/_grid.haml +0 -46
  167. data/app/views/integral/backend/posts/_grid.haml +0 -51
  168. data/app/views/integral/backend/shared/_grid.haml +0 -18
  169. data/app/views/integral/backend/shared/cards/_recent_pages.haml +0 -19
  170. data/app/views/integral/backend/shared/cards/_recent_posts.haml +0 -18
  171. data/app/views/integral/backend/shared/cards/_recent_users.haml +0 -19
  172. data/app/views/integral/backend/users/_grid.haml +0 -36
@@ -0,0 +1,6 @@
1
+ module Integral
2
+ class DeviseMailer < Devise::Mailer
3
+ layout 'integral/mailer'
4
+ helper 'integral/mail'
5
+ end
6
+ end
@@ -0,0 +1,67 @@
1
+ module Integral
2
+ module Notification
3
+ module Subscribable
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ attr_accessor :integral_notification_action
8
+
9
+ has_many :notification_subscriptions, as: :subscribable, class_name: "Integral::Notification::Subscription"
10
+
11
+ after_commit on: :create do
12
+ create_notifications(:create)
13
+ end
14
+
15
+ after_commit on: :update do
16
+ create_notifications(:update)
17
+ end
18
+
19
+ after_commit on: :destroy do
20
+ create_notifications(:destroy)
21
+ end
22
+ end
23
+
24
+ class_methods do
25
+ def notification_subscriptions(omit_users:)
26
+ Integral::Notification::Subscription.where(subscribable_type: self.name).where.not(user_id: omit_users)
27
+ end
28
+
29
+ def subscribable?
30
+ true
31
+ end
32
+ end
33
+
34
+ def subscribable?
35
+ true
36
+ end
37
+
38
+ def notifiable_users
39
+ object_notification_subscriptions = notification_subscriptions.group_by(&:state)
40
+ object_notification_subscription_user_ids = object_notification_subscriptions.values.flatten.map(&:user_id)
41
+
42
+ class_notification_subscriptions = self.class.notification_subscriptions(omit_users: object_notification_subscription_user_ids).group_by(&:state)
43
+ class_notification_subscription_user_ids = class_notification_subscriptions.values.flatten.map(&:user_id)
44
+
45
+ top_level_notification_subscription_user_ids = Integral::User.where(notify_me: true).where.not(id: object_notification_subscription_user_ids + class_notification_subscription_user_ids).pluck(:id)
46
+
47
+ notifiable_user_ids = (object_notification_subscriptions['subscribed']&.map(&:user_id) || []) + (class_notification_subscriptions['subscribed']&.map(&:user_id) || []) + top_level_notification_subscription_user_ids
48
+ notifiable_user_ids -= [PaperTrail.request.whodunnit]
49
+
50
+ User.find(notifiable_user_ids).select { |user| Pundit.policy!(user, self).receives_notifications? }
51
+ end
52
+
53
+ private
54
+
55
+ # TODO Improvements:
56
+ # - Move into a Job - Pass through the created_at time otherwise the notification sets it when the notifcation was created, rather than when the action was committed
57
+ # - Anyway to create multiple in one go but still run callbacks?
58
+ def create_notifications(action)
59
+ if PaperTrail.request.whodunnit
60
+ notifiable_users.each do |notifiable|
61
+ Integral::Notification::Notification.create!(subscribable: self, recipient: notifiable, action: (integral_notification_action || action), actor_id: PaperTrail.request.whodunnit)
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -2,5 +2,14 @@ module Integral
2
2
  # Base Integral Model.
3
3
  class ApplicationRecord < ActiveRecord::Base
4
4
  self.abstract_class = true
5
+
6
+ # @return [Array] containing available human readable statuses against there numeric value
7
+ def self.available_statuses(opts = { reverse: false })
8
+ available_statuses = statuses.map do |key, value|
9
+ [I18n.t("integral.statuses.#{key}"), key]
10
+ end
11
+
12
+ opts[:reverse] ? available_statuses.each(&:reverse!) : available_statuses
13
+ end
5
14
  end
6
15
  end
@@ -1,6 +1,11 @@
1
1
  module Integral
2
2
  # Represents a user post category
3
3
  class Category < ApplicationRecord
4
+ acts_as_integral({
5
+ backend_main_menu: { enabled: false },
6
+ backend_create_menu: { enabled: false }
7
+ })
8
+
4
9
  has_paper_trail class_name: 'Integral::CategoryVersion'
5
10
 
6
11
  # Slugging
@@ -16,5 +21,9 @@ module Integral
16
21
  validates_format_of :slug, with: /\A[A-Za-z0-9]+(?:-[A-Za-z0-9]+)*\z/
17
22
  validates :title, presence: true, length: { minimum: 4, maximum: 60 }
18
23
  validates :description, presence: true, length: { minimum: 25, maximum: 300 }
24
+
25
+ def self.integral_icon
26
+ 'tags'
27
+ end
19
28
  end
20
29
  end
@@ -3,8 +3,12 @@ module Integral
3
3
  class Image < ApplicationRecord
4
4
  before_save :touch_list_items
5
5
 
6
- # Soft-deletion
7
- acts_as_paranoid
6
+ acts_as_integral({
7
+ notifications: { enabled: false },
8
+ backend_main_menu: { order: 40 },
9
+ backend_create_menu: { order: 30 }
10
+ }) # Integral Goodness
11
+ acts_as_paranoid # Soft-deletion
8
12
 
9
13
  validates :file, presence: true
10
14
  validates :title, presence: true, length: { minimum: 5, maximum: 50 }
@@ -45,11 +49,44 @@ module Integral
45
49
  def self.listable_options
46
50
  {
47
51
  record_title: I18n.t('integral.backend.record_selector.images.record'),
48
- selector_path: Engine.routes.url_helpers.backend_images_path,
52
+ selector_path: Engine.routes.url_helpers.list_backend_images_path,
49
53
  selector_title: I18n.t('integral.backend.record_selector.images.title')
50
54
  }
51
55
  end
52
56
 
57
+ def self.integral_icon
58
+ 'image'
59
+ end
60
+
61
+ def self.integral_backend_main_menu_item
62
+ {
63
+ icon: integral_icon,
64
+ order: integral_options.dig(:backend_main_menu, :order),
65
+ label: model_name.human.pluralize,
66
+ url: url_helpers.send("backend_img_index_url"),
67
+ authorize_class: self,
68
+ authorize_action: :index,
69
+ list_items: [
70
+ { label: I18n.t('integral.navigation.dashboard'), url: url_helpers.send("backend_img_index_url"), authorize_class: self, authorize_action: :index },
71
+ { label: I18n.t('integral.actions.create'), url: url_helpers.send("new_backend_img_url"), authorize_class: self, authorize_action: :new },
72
+ { label: I18n.t('integral.navigation.listing'), url: url_helpers.send("list_backend_img_index_url"), authorize_class: self, authorize_action: :list },
73
+ ]
74
+ }
75
+ end
76
+
77
+ # @return [Hash] hash representing the class, used to render within the create menu
78
+ def self.integral_backend_create_menu_item
79
+ {
80
+ icon: integral_icon,
81
+ order: integral_options.dig(:backend_create_menu, :order),
82
+ label: model_name.human,
83
+ url: url_helpers.send("new_backend_img_url"),
84
+ # authorize: proc { policy(self).index? }, can't use this as self is in wrong context
85
+ authorize_class: self,
86
+ authorize_action: :new,
87
+ }
88
+ end
89
+
53
90
  private
54
91
 
55
92
  def touch_list_items
@@ -3,8 +3,12 @@ module Integral
3
3
  class List < ApplicationRecord
4
4
  default_scope { includes(:list_items) }
5
5
 
6
- # Soft-deletion
7
- acts_as_paranoid
6
+ acts_as_integral({
7
+ backend_main_menu: { order: 50 },
8
+ backend_create_menu: { order: 40 }
9
+ }) # Integral Goodness
10
+
11
+ acts_as_paranoid # Soft-deletion
8
12
 
9
13
  # Associations
10
14
  has_many :list_items
@@ -48,6 +52,10 @@ module Integral
48
52
  new_list
49
53
  end
50
54
 
55
+ def self.integral_icon
56
+ 'list'
57
+ end
58
+
51
59
  private
52
60
 
53
61
  def validate_unlocked
@@ -1,24 +1,17 @@
1
1
  module Integral
2
2
  # Represents an item within a particular list
3
3
  class ListItem < ApplicationRecord
4
- after_initialize :set_defaults
5
- after_touch :touch_list
6
-
7
4
  # Default scope orders by priority and includes children
8
5
  default_scope { includes(:children).includes(:image).order(:priority) }
9
6
 
10
7
  # Associations
11
8
  belongs_to :list, optional: true, touch: true
12
9
  belongs_to :image, optional: true
13
- has_and_belongs_to_many(:children,
14
- -> { order(:priority) },
15
- join_table: 'integral_list_item_connections',
16
- foreign_key: 'parent_id',
17
- association_foreign_key: 'child_id',
18
- class_name: 'ListItem',
19
- after_add: :touch_updated_at,
20
- after_remove: :touch_updated_at,
21
- optional: true)
10
+ has_many :list_item_connections, foreign_key: 'parent_id'
11
+ has_many :children, -> { order(:priority) }, through: :list_item_connections
12
+ has_many :inverse_list_item_connections, class_name: "ListItemConnection", foreign_key: "child_id"
13
+ # NOTE: A List Item only has one parent
14
+ has_many :parents, :through => :inverse_list_item_connections
22
15
 
23
16
  # Validations
24
17
  validate :validate_child_absence
@@ -26,6 +19,12 @@ module Integral
26
19
  # Nested forms
27
20
  accepts_nested_attributes_for :children, reject_if: :all_blank, allow_destroy: true
28
21
 
22
+ # Callbacks
23
+ after_initialize :set_defaults
24
+ after_commit :touch_parent, on: :update
25
+ after_destroy :touch_parent
26
+ after_touch :touch_list
27
+
29
28
  # @return [Array] list of types available for a list item
30
29
  def self.types_collection
31
30
  collection = [
@@ -69,8 +68,9 @@ module Integral
69
68
  self.type ||= 'Integral::Basic'
70
69
  end
71
70
 
72
- def touch_updated_at(_list_item)
73
- touch if persisted?
71
+ def touch_parent
72
+ parent = parents.first
73
+ parent.touch if parent.present? && parent.persisted?
74
74
  end
75
75
 
76
76
  def touch_list
@@ -0,0 +1,6 @@
1
+ module Integral
2
+ class ListItemConnection < ApplicationRecord
3
+ belongs_to :parent, touch: true, class_name: 'Integral::ListItem'
4
+ belongs_to :child, touch: true, class_name: 'Integral::ListItem', dependent: :destroy
5
+ end
6
+ end
@@ -0,0 +1,28 @@
1
+ module Integral
2
+ module Notification
3
+ class Notification < ApplicationRecord
4
+ belongs_to :recipient, class_name: "User"
5
+ belongs_to :actor, class_name: "User", optional: true
6
+ belongs_to :subscribable, polymorphic: true
7
+
8
+ scope :unread, ->{ where(read_at: nil) }
9
+ scope :recent, ->{ order(created_at: :desc).limit(per_page) }
10
+
11
+ self.per_page = 8
12
+
13
+ validates :action, presence: true
14
+
15
+ def read!
16
+ update!(read_at: Time.now())
17
+ end
18
+
19
+ def unread?
20
+ read_at.nil?
21
+ end
22
+
23
+ def to_partial_path
24
+ 'integral/backend/notifications/notification'
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,14 @@
1
+ module Integral
2
+ module Notification
3
+ class Subscription < ApplicationRecord
4
+ self.table_name = 'integral_notification_subscriptions'
5
+
6
+ belongs_to :subscribable, polymorphic: true, optional: true
7
+ belongs_to :user
8
+
9
+ enum state: { subscribed: 'subscribed', unsubscribed: 'unsubscribed' }
10
+
11
+ validates :subscribable_type, :state, presence: true
12
+ end
13
+ end
14
+ end
@@ -4,6 +4,11 @@ module Integral
4
4
  include LazyContentable
5
5
 
6
6
  acts_as_paranoid # Soft-deletion
7
+ acts_as_integral({
8
+ backend_main_menu: { order: 20 },
9
+ backend_create_menu: { order: 10 }
10
+ }) # Integral Goodness
11
+
7
12
  acts_as_listable # Listable Item
8
13
 
9
14
  has_paper_trail class_name: 'Integral::PageVersion'
@@ -35,6 +40,7 @@ module Integral
35
40
 
36
41
  # Callbacks
37
42
  before_save :set_paper_trail_event
43
+ before_save :set_integral_notification_action
38
44
 
39
45
  # Scopes
40
46
  scope :search, ->(query) { where('lower(title) LIKE ? OR lower(path) LIKE ?', "%#{query.downcase}%", "%#{query.downcase}%") }
@@ -71,11 +77,15 @@ module Integral
71
77
  {
72
78
  icon: 'file',
73
79
  record_title: I18n.t('integral.backend.record_selector.pages.record'),
74
- selector_path: Engine.routes.url_helpers.backend_pages_path,
80
+ selector_path: Engine.routes.url_helpers.list_backend_pages_path,
75
81
  selector_title: I18n.t('integral.backend.record_selector.pages.title')
76
82
  }
77
83
  end
78
84
 
85
+ def self.integral_icon
86
+ 'file'
87
+ end
88
+
79
89
  # @return [Hash] the instance as a card
80
90
  def to_card
81
91
  # subtitle = self.published_at.present? ? I18n.t('integral.blog.posted_ago', time: time_ago_in_words(self.published_at)) : I18n.t('integral.records.status.draft')
@@ -136,13 +146,10 @@ module Integral
136
146
  end
137
147
  end
138
148
 
139
- # @return [Array] containing available human readable statuses against there numeric value
140
- def self.available_statuses
141
- [
142
- ['Draft', 0],
143
- ['Published', 1],
144
- ['Archived', 2]
145
- ]
149
+ def set_integral_notification_action
150
+ if persisted? && published? && status_changed?
151
+ self.integral_notification_action = :publish
152
+ end
146
153
  end
147
154
 
148
155
  def validate_parent_is_available
@@ -5,6 +5,10 @@ module Integral
5
5
  include LazyContentable
6
6
  include Webhook::Observable
7
7
 
8
+ acts_as_integral({
9
+ backend_main_menu: { order: 30, enabled: Integral.blog_enabled? },
10
+ backend_create_menu: { order: 20, enabled: Integral.blog_enabled? }
11
+ }) # Integral Goodness
8
12
  acts_as_paranoid # Soft-deletion
9
13
  acts_as_listable if Integral.blog_enabled? # Listable Item
10
14
  acts_as_taggable # Tagging
@@ -21,7 +25,7 @@ module Integral
21
25
  self.per_page = 8 if respond_to? :per_page
22
26
 
23
27
  # Associations
24
- belongs_to :user
28
+ belongs_to :user, -> { with_deleted }
25
29
  belongs_to :category
26
30
  belongs_to :image, class_name: 'Integral::Image', optional: true
27
31
  belongs_to :preview_image, class_name: 'Integral::Image', optional: true
@@ -45,16 +49,6 @@ module Integral
45
49
  # Scopes
46
50
  scope :search, ->(query) { where('lower(title) LIKE ? OR lower(slug) LIKE ?', "%#{query.downcase}%", "%#{query.downcase}%") }
47
51
 
48
- # @return [Array] containing available human readable statuses against there numeric value
49
- def self.available_statuses(opts = { reverse: false })
50
- statuses = [
51
- [I18n.t('integral.records.status.draft'), 0],
52
- [I18n.t('integral.records.status.published'), 1]
53
- ]
54
-
55
- opts[:reverse] ? statuses.each(&:reverse!) : statuses
56
- end
57
-
58
52
  # Increments the view count of the post if a PostViewing is successfully added
59
53
  #
60
54
  # @param ip_address [String] Viewers IP address
@@ -101,11 +95,15 @@ module Integral
101
95
  {
102
96
  icon: 'rss',
103
97
  record_title: I18n.t('integral.backend.record_selector.posts.record'),
104
- selector_path: Engine.routes.url_helpers.backend_posts_path,
98
+ selector_path: Engine.routes.url_helpers.list_backend_posts_path,
105
99
  selector_title: I18n.t('integral.backend.record_selector.posts.title')
106
100
  }
107
101
  end
108
102
 
103
+ def self.integral_icon
104
+ 'rss'
105
+ end
106
+
109
107
  # @return [String] Current tag context
110
108
  def tag_context
111
109
  status
@@ -166,7 +164,7 @@ module Integral
166
164
  end
167
165
 
168
166
  def deliver_published_webhook_on_update
169
- deliver_webhook(:published) if status_changed? && published?
167
+ deliver_webhook(:published) if saved_change_to_status? && published?
170
168
  end
171
169
 
172
170
  def deliver_published_webhook_on_create
@@ -1,8 +1,11 @@
1
1
  module Integral
2
2
  # User model used to represent a authenticated user
3
3
  class User < ApplicationRecord
4
- # Soft-deletion
5
- acts_as_paranoid
4
+ acts_as_paranoid # Soft-deletion
5
+ acts_as_integral({
6
+ backend_main_menu: { order: 60 },
7
+ backend_create_menu: { order: 50 }
8
+ }) # Integral Goodness
6
9
 
7
10
  mount_uploader :avatar, AvatarUploader
8
11
  process_in_background :avatar
@@ -11,9 +14,14 @@ module Integral
11
14
  # :confirmable, :timeoutable, :omniauthable, registerable and lockable
12
15
  devise :invitable, :database_authenticatable, :recoverable, :rememberable, :trackable, :validatable
13
16
 
17
+ enum status: %i[pending active blocked]
18
+
14
19
  # Relations
15
20
  has_many :role_assignments
16
21
  has_many :roles, through: :role_assignments
22
+ # notification_subscription is used by Subscribable concern - Users can have notification_subscriptions AND be subscribable
23
+ has_many :own_notification_subscriptions, class_name: "Integral::Notification::Subscription"
24
+ has_many :notifications, class_name: "Integral::Notification::Notification", foreign_key: :recipient_id
17
25
 
18
26
  # Validations
19
27
  validates :name, :email, presence: true
@@ -34,6 +42,10 @@ module Integral
34
42
  roles.map { |r| r.name.underscore.to_sym }.any? { |user_role| role_sym.include?(user_role) }
35
43
  end
36
44
 
45
+ def self.integral_icon
46
+ 'user'
47
+ end
48
+
37
49
  # @return [Array] containing available locales
38
50
  def self.available_locales
39
51
  available_locales = []
@@ -56,6 +68,37 @@ module Integral
56
68
  super
57
69
  end
58
70
 
71
+ def multiple_page_notifications?
72
+ notifications.count > Integral::Notification::Notification.per_page
73
+ end
74
+
75
+ # @param subscribable [Class or Instance]
76
+ def receives_notifications_for?(subscribable)
77
+ if subscribable.is_a?(Class)
78
+ subscription = own_notification_subscriptions.find_by(subscribable_type: subscribable.name, subscribable_id: nil)
79
+
80
+ return subscription.subscribed? if subscription
81
+ else
82
+ instance_subscription = own_notification_subscriptions.find_by(subscribable_type: subscribable.class.name, subscribable_id: subscribable.id)
83
+
84
+ return instance_subscription.subscribed? if instance_subscription
85
+
86
+ class_level_subscription = own_notification_subscriptions.find_by(subscribable_type: subscribable.class.name, subscribable_id: nil)
87
+
88
+ return class_level_subscription.subscribed? if class_level_subscription
89
+ end
90
+
91
+ notify_me
92
+ end
93
+
94
+ def active_for_authentication?
95
+ super && !blocked?
96
+ end
97
+
98
+ def inactive_message
99
+ blocked? ? :blocked : super
100
+ end
101
+
59
102
  private
60
103
 
61
104
  def send_devise_notification(notification, *args)