integral 1.4.0 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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)
|