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.
- checksums.yaml +5 -5
- data/README.md +4 -3
- data/app/assets/javascripts/integral/backend.js +86 -2
- data/app/assets/javascripts/integral/support/character_counter.js +13 -8
- data/app/assets/javascripts/integral/support/list.coffee +1 -0
- data/app/assets/javascripts/integral/support/record_selector.coffee +2 -0
- data/app/assets/javascripts/integral/support/slug_generator.coffee +1 -0
- data/app/assets/stylesheets/integral/backend.sass +34 -23
- data/app/assets/stylesheets/integral/backend/_foundation_settings.scss +2 -2
- data/app/assets/stylesheets/integral/backend/dashboard-layout.scss +27 -63
- data/app/assets/stylesheets/integral/backend/devise.sass +2 -3
- data/app/assets/stylesheets/integral/backend/materialize-tags.sass +1 -0
- data/app/assets/stylesheets/integral/backend/modules/dropdown_pane_notifications.scss +150 -0
- data/app/assets/stylesheets/integral/backend/modules/dropdown_pane_profile.scss +59 -0
- data/app/assets/stylesheets/integral/backend/shared.sass +41 -2
- data/app/assets/stylesheets/integral/frontend/layout.sass +10 -0
- data/app/assets/stylesheets/integral/support/media-query-indicator.sass +4 -4
- data/app/controllers/integral/backend/activities_controller.rb +21 -27
- data/app/controllers/integral/backend/base_controller.rb +87 -37
- data/app/controllers/integral/backend/images_controller.rb +26 -8
- data/app/controllers/integral/backend/lists_controller.rb +2 -14
- data/app/controllers/integral/backend/notification_subscriptions_controller.rb +23 -0
- data/app/controllers/integral/backend/pages_controller.rb +0 -4
- data/app/controllers/integral/backend/posts_controller.rb +0 -4
- data/app/controllers/integral/backend/settings_controller.rb +4 -0
- data/app/controllers/integral/backend/static_pages_controller.rb +6 -0
- data/app/controllers/integral/backend/users_controller.rb +43 -24
- data/app/controllers/integral/blog_controller.rb +12 -0
- data/app/controllers/integral/categories_controller.rb +17 -3
- data/app/controllers/integral/tags_controller.rb +5 -2
- data/app/decorators/integral/base_decorator.rb +16 -0
- data/app/decorators/integral/image_decorator.rb +2 -2
- data/app/decorators/integral/list_decorator.rb +1 -13
- data/app/decorators/integral/notification/notification_decorator.rb +74 -0
- data/app/decorators/integral/page_decorator.rb +1 -13
- data/app/decorators/integral/post_decorator.rb +1 -2
- data/app/decorators/integral/user_decorator.rb +1 -13
- data/app/decorators/integral/version_decorator.rb +8 -4
- data/app/helpers/integral/backend/base_helper.rb +97 -31
- data/app/jobs/integral/application_job.rb +1 -0
- data/app/jobs/integral/newsletter_signup_job.rb +0 -2
- data/app/mailers/integral/devise_mailer.rb +6 -0
- data/app/models/concerns/integral/notification/subscribable.rb +67 -0
- data/app/models/integral/application_record.rb +9 -0
- data/app/models/integral/category.rb +9 -0
- data/app/models/integral/image.rb +40 -3
- data/app/models/integral/list.rb +10 -2
- data/app/models/integral/list_item.rb +14 -14
- data/app/models/integral/list_item_connection.rb +6 -0
- data/app/models/integral/notification/notification.rb +28 -0
- data/app/models/integral/notification/subscription.rb +14 -0
- data/app/models/integral/page.rb +15 -8
- data/app/models/integral/post.rb +11 -13
- data/app/models/integral/user.rb +45 -2
- data/app/policies/integral/base_policy.rb +7 -12
- data/app/policies/integral/page_policy.rb +1 -0
- data/app/policies/integral/version_policy.rb +0 -8
- data/app/views/devise/invitations/edit.haml +1 -4
- data/app/views/devise/mailer/invitation_instructions.inky-haml +20 -0
- data/app/views/integral/backend/activities/grid/_dropdown_actions.haml +1 -0
- data/app/views/integral/backend/activities/grid/_row_content.haml +13 -0
- data/app/views/integral/backend/activities/index.haml +7 -13
- data/app/views/integral/backend/activities/shared/_grid.haml +35 -20
- data/app/views/integral/backend/activities/shared/index.haml +12 -12
- data/app/views/integral/backend/activities/shared/show.haml +7 -7
- data/app/views/integral/backend/activities/show.haml +1 -1
- data/app/views/integral/backend/categories/_modal.haml +2 -3
- data/app/views/integral/backend/images/_form.haml +13 -25
- data/app/views/integral/backend/images/edit.haml +1 -9
- data/app/views/integral/backend/images/grid/_dropdown_actions.haml +5 -0
- data/app/views/integral/backend/images/grid/_row_content.haml +5 -0
- data/app/views/integral/backend/images/index.haml +11 -17
- data/app/views/integral/backend/images/list.haml +11 -0
- data/app/views/integral/backend/images/show.haml +26 -0
- data/app/views/integral/backend/lists/_form.haml +6 -19
- data/app/views/integral/backend/lists/_item_modal.haml +3 -3
- data/app/views/integral/backend/lists/_manager.haml +11 -13
- data/app/views/integral/backend/lists/edit.haml +6 -20
- data/app/views/integral/backend/lists/grid/_dropdown_actions.haml +9 -0
- data/app/views/integral/backend/lists/grid/_row_content.haml +3 -0
- data/app/views/integral/backend/lists/index.haml +11 -17
- data/app/views/integral/backend/lists/list.haml +11 -0
- data/app/views/integral/backend/lists/show.haml +30 -0
- data/app/views/integral/backend/notifications/_notification.haml +21 -0
- data/app/views/integral/backend/pages/_form.haml +19 -43
- data/app/views/integral/backend/pages/edit.haml +4 -12
- data/app/views/integral/backend/pages/grid/_dropdown_actions.haml +11 -0
- data/app/views/integral/backend/pages/grid/_row_content.haml +5 -0
- data/app/views/integral/backend/pages/index.haml +6 -6
- data/app/views/integral/backend/pages/list.haml +12 -19
- data/app/views/integral/backend/pages/show.haml +19 -35
- data/app/views/integral/backend/posts/_form.haml +18 -56
- data/app/views/integral/backend/posts/edit.haml +4 -14
- data/app/views/integral/backend/posts/grid/_dropdown_actions.haml +10 -0
- data/app/views/integral/backend/posts/grid/_row_content.haml +6 -0
- data/app/views/integral/backend/posts/index.haml +6 -6
- data/app/views/integral/backend/posts/list.haml +11 -18
- data/app/views/integral/backend/posts/new.haml +0 -1
- data/app/views/integral/backend/posts/show.haml +18 -41
- data/app/views/integral/backend/shared/_breadcrumbs.haml +7 -4
- data/app/views/integral/backend/shared/_image_preview.haml +10 -3
- data/app/views/integral/backend/shared/_image_selector.haml +1 -1
- data/app/views/integral/backend/shared/_notification_subscription_toggle.haml +22 -0
- data/app/views/integral/backend/shared/action_bar/_index.haml +9 -0
- data/app/views/integral/backend/shared/action_bar/_show.haml +3 -0
- data/app/views/integral/backend/shared/cards/_at_a_glance.haml +3 -3
- data/app/views/integral/backend/shared/cards/_categories.haml +27 -28
- data/app/views/integral/backend/shared/cards/_object.haml +1 -1
- data/app/views/integral/backend/shared/cards/_recent_activity.haml +12 -12
- data/app/views/integral/backend/shared/cards/_recent_resources.haml +17 -0
- data/app/views/integral/backend/shared/cards/_top_post_authors.haml +14 -15
- data/app/views/integral/backend/shared/cards/_welcome.haml +24 -25
- data/app/views/integral/backend/shared/{_empty_grid.haml → grid/_empty.haml} +0 -0
- data/app/views/integral/backend/shared/grid/_form.haml +9 -0
- data/app/views/integral/backend/shared/grid/_grid.haml +21 -0
- data/app/views/integral/backend/shared/{_pagination.haml → grid/_pagination.haml} +0 -0
- data/app/views/integral/backend/shared/grid/_row_layout.haml +8 -0
- data/app/views/integral/backend/shared/record_selector/_collection.haml +1 -0
- data/app/views/integral/backend/shared/record_selector/_modal.haml +9 -10
- data/app/views/integral/backend/static_pages/dashboard.haml +6 -7
- data/app/views/integral/backend/users/_form.haml +34 -46
- data/app/views/integral/backend/users/grid/_dropdown_actions.haml +17 -0
- data/app/views/integral/backend/users/grid/_row_content.haml +8 -0
- data/app/views/integral/backend/users/index.haml +6 -6
- data/app/views/integral/backend/users/list.haml +10 -16
- data/app/views/integral/backend/users/show.haml +10 -1
- data/app/views/integral/categories/show.haml +3 -3
- data/app/views/integral/posts/_article_footer.haml +1 -1
- data/app/views/integral/posts/_card.haml +1 -1
- data/app/views/integral/posts/_most_read_section.haml +1 -1
- data/app/views/integral/posts/_post.haml +1 -1
- data/app/views/integral/posts/templates/default.haml +1 -1
- data/app/views/integral/shared/sidebar/_item.haml +1 -1
- data/app/views/layouts/integral/backend.html.haml +24 -5
- data/app/views/layouts/integral/backend/_create_dropdown.haml +1 -30
- data/app/views/layouts/integral/backend/_main_menu_items.haml +1 -101
- data/config/initializers/devise.rb +1 -1
- data/config/locales/en.yml +60 -2
- data/config/routes.rb +2 -0
- data/db/migrate/20200407022636_create_integral_notifications.rb +25 -0
- data/db/migrate/20200421223602_add_status_to_integral_users.rb +5 -0
- data/db/seeds.rb +15 -11
- data/lib/integral.rb +1 -0
- data/lib/integral/acts_as_integral.rb +115 -0
- data/lib/integral/acts_as_listable.rb +1 -1
- data/lib/integral/engine.rb +9 -0
- data/lib/integral/grids/activities_grid.rb +0 -1
- data/lib/integral/grids/lists_grid.rb +1 -0
- data/lib/integral/grids/posts_grid.rb +5 -1
- data/lib/integral/grids/users_grid.rb +5 -0
- data/lib/integral/list_renderer.rb +5 -1
- data/lib/integral/router.rb +20 -4
- data/lib/integral/version.rb +1 -1
- data/spec/factories.rb +15 -1
- metadata +45 -39
- data/app/decorators/integral/category_version_decorator.rb +0 -7
- data/app/decorators/integral/image_version_decorator.rb +0 -7
- data/app/decorators/integral/list_version_decorator.rb +0 -7
- data/app/decorators/integral/page_version_decorator.rb +0 -7
- data/app/decorators/integral/post_version_decorator.rb +0 -7
- data/app/decorators/integral/user_version_decorator.rb +0 -7
- data/app/views/devise/mailer/invitation_instructions.html.erb +0 -13
- data/app/views/integral/backend/activities/_grid.haml +0 -22
- data/app/views/integral/backend/images/_grid.haml +0 -16
- data/app/views/integral/backend/lists/_grid.haml +0 -14
- data/app/views/integral/backend/pages/_grid.haml +0 -46
- data/app/views/integral/backend/posts/_grid.haml +0 -51
- data/app/views/integral/backend/shared/_grid.haml +0 -18
- data/app/views/integral/backend/shared/cards/_recent_pages.haml +0 -19
- data/app/views/integral/backend/shared/cards/_recent_posts.haml +0 -18
- data/app/views/integral/backend/shared/cards/_recent_users.haml +0 -19
- data/app/views/integral/backend/users/_grid.haml +0 -36
|
@@ -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
|
-
|
|
7
|
-
|
|
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.
|
|
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
|
data/app/models/integral/list.rb
CHANGED
|
@@ -3,8 +3,12 @@ module Integral
|
|
|
3
3
|
class List < ApplicationRecord
|
|
4
4
|
default_scope { includes(:list_items) }
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
|
73
|
-
|
|
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,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
|
data/app/models/integral/page.rb
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
data/app/models/integral/post.rb
CHANGED
|
@@ -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.
|
|
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
|
|
167
|
+
deliver_webhook(:published) if saved_change_to_status? && published?
|
|
170
168
|
end
|
|
171
169
|
|
|
172
170
|
def deliver_published_webhook_on_create
|
data/app/models/integral/user.rb
CHANGED
|
@@ -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
|
-
|
|
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)
|