integral 1.3.0 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +2 -30
- data/Rakefile +1 -1
- data/app/assets/images/integral/defaults/no_image_available.jpg +0 -0
- data/app/assets/javascripts/integral/backend.js +102 -11
- data/app/assets/javascripts/integral/frontend.js +37 -0
- data/app/assets/javascripts/integral/support/confirm_modal.coffee +2 -2
- data/app/assets/javascripts/integral/support/gallery.coffee +71 -54
- data/app/assets/javascripts/integral/support/lib/lazysizes.js +755 -0
- data/app/assets/javascripts/integral/support/lib/materialize-tags.js +49 -44
- data/app/assets/javascripts/integral/support/ls.instagram.js +57 -0
- data/app/assets/javascripts/integral/support/ls.twitter.js +66 -0
- data/app/assets/javascripts/integral/support/record_selector.coffee +1 -1
- data/app/assets/javascripts/integral/support/remote_form.coffee +5 -2
- data/app/assets/stylesheets/integral/backend.sass +2 -1
- data/app/assets/stylesheets/integral/backend/_foundation_settings.scss +3 -4
- data/app/assets/stylesheets/integral/backend/dashboard-layout.scss +4 -1
- data/app/assets/stylesheets/integral/backend/materialize-tags.sass +1 -1
- data/app/assets/stylesheets/integral/backend/modules/timeline.scss +214 -0
- data/app/assets/stylesheets/integral/backend/shared.sass +80 -11
- data/app/assets/stylesheets/integral/frontend.scss +45 -0
- data/app/assets/stylesheets/integral/frontend/_foundation_settings.scss +2 -2
- data/app/assets/stylesheets/integral/frontend/blog.sass +155 -142
- data/app/assets/stylesheets/integral/frontend/layout.sass +3 -3
- data/app/assets/stylesheets/integral/frontend/modules/article-footer.scss +55 -0
- data/app/assets/stylesheets/integral/frontend/modules/article.scss +34 -0
- data/app/assets/stylesheets/integral/frontend/modules/horizontal-post.scss +44 -0
- data/app/assets/stylesheets/integral/frontend/modules/inline-articles.scss +23 -0
- data/app/assets/stylesheets/integral/frontend/modules/latest-post.scss +37 -0
- data/app/assets/stylesheets/integral/frontend/modules/list-widget.scss +50 -0
- data/app/assets/stylesheets/integral/frontend/modules/piped-list.scss +33 -0
- data/app/assets/stylesheets/integral/frontend/modules/post-tags.scss +19 -0
- data/app/assets/stylesheets/integral/frontend/modules/scroll-container.scss +9 -0
- data/app/assets/stylesheets/integral/frontend/modules/sidebar-articles.scss +42 -0
- data/app/assets/stylesheets/integral/frontend/modules/sidebar-tags.scss +6 -0
- data/app/assets/stylesheets/integral/frontend/modules/sidebar-widget.scss +47 -0
- data/app/assets/stylesheets/integral/frontend/modules/vertical-post.scss +31 -0
- data/app/assets/stylesheets/integral/frontend/share_modal.sass +0 -5
- data/app/assets/stylesheets/integral/support/gallery.sass +8 -0
- data/app/assets/stylesheets/integral/support/media-query-indicator.sass +6 -0
- data/app/controllers/integral/application_controller.rb +7 -1
- data/app/controllers/integral/backend/activities_controller.rb +13 -2
- data/app/controllers/integral/backend/base_controller.rb +60 -7
- data/app/controllers/integral/backend/categories_controller.rb +49 -0
- data/app/controllers/integral/backend/pages_controller.rb +7 -2
- data/app/controllers/integral/backend/posts_controller.rb +8 -3
- data/app/controllers/integral/backend/static_pages_controller.rb +4 -0
- data/app/controllers/integral/backend/users_controller.rb +13 -7
- data/app/controllers/integral/categories_controller.rb +31 -0
- data/app/controllers/integral/pages_controller.rb +1 -1
- data/app/controllers/integral/posts_controller.rb +5 -3
- data/app/decorators/integral/category_decorator.rb +30 -0
- data/app/decorators/integral/category_version_decorator.rb +7 -0
- data/app/decorators/integral/image_version_decorator.rb +7 -0
- data/app/decorators/integral/list_decorator.rb +1 -1
- data/app/decorators/integral/list_version_decorator.rb +7 -0
- data/app/decorators/integral/page_version_decorator.rb +7 -0
- data/app/decorators/integral/post_decorator.rb +9 -1
- data/app/decorators/integral/post_version_decorator.rb +7 -0
- data/app/decorators/integral/user_decorator.rb +1 -1
- data/app/decorators/integral/user_version_decorator.rb +7 -0
- data/app/decorators/integral/version_decorator.rb +51 -12
- data/app/helpers/integral/backend/base_helper.rb +56 -2
- data/app/helpers/integral/blog_helper.rb +21 -4
- data/app/jobs/integral/webhook/delivery_job.rb +37 -0
- data/app/mailers/integral/contact_mailer.rb +4 -1
- data/app/models/concerns/integral/lazy_contentable.rb +54 -0
- data/app/models/concerns/integral/webhook/delivery.rb +30 -0
- data/app/models/concerns/integral/webhook/observable.rb +23 -0
- data/app/models/integral/category.rb +20 -0
- data/app/models/integral/category_version.rb +8 -0
- data/app/models/integral/list_item.rb +1 -2
- data/app/models/integral/page.rb +18 -3
- data/app/models/integral/post.rb +28 -1
- data/app/models/integral/version.rb +2 -2
- data/app/models/integral/webhook/endpoint.rb +40 -0
- data/app/models/integral/webhook/event.rb +20 -0
- data/app/policies/integral/base_policy.rb +1 -0
- data/app/policies/integral/category_policy.rb +9 -0
- data/app/serializers/integral/post_serializer.rb +24 -0
- data/app/uploaders/integral/avatar_uploader.rb +1 -1
- data/app/views/integral/backend/activities/_activity.haml +21 -0
- data/app/views/integral/backend/activities/_grid.haml +1 -2
- data/app/views/integral/backend/activities/shared/_grid.haml +3 -2
- data/app/views/integral/backend/activities/shared/{_listing.haml → index.haml} +1 -0
- data/app/views/integral/backend/activities/shared/{_log.haml → show.haml} +0 -0
- data/app/views/integral/backend/categories/_modal.haml +25 -0
- data/app/views/integral/backend/lists/_child_fields.haml +1 -1
- data/app/views/integral/backend/lists/_item_container.haml +1 -1
- data/app/views/integral/backend/lists/_item_modal.haml +1 -1
- data/app/views/integral/backend/lists/_list_item_fields.haml +1 -1
- data/app/views/integral/backend/pages/_form.haml +1 -4
- data/app/views/integral/backend/pages/_grid.haml +34 -9
- data/app/views/integral/backend/pages/edit.haml +9 -3
- data/app/views/integral/backend/pages/index.haml +11 -21
- data/app/views/integral/backend/pages/list.haml +22 -0
- data/app/views/integral/backend/pages/show.haml +48 -0
- data/app/views/integral/backend/posts/_form.haml +8 -6
- data/app/views/integral/backend/posts/_grid.haml +33 -7
- data/app/views/integral/backend/posts/index.haml +13 -19
- data/app/views/integral/backend/posts/list.haml +20 -0
- data/app/views/integral/backend/posts/show.haml +54 -0
- data/app/views/integral/backend/shared/_activity_modal.haml +13 -0
- data/app/views/integral/backend/shared/cards/_categories.haml +34 -0
- data/app/views/integral/backend/{static_pages/_card.haml → shared/cards/_object.haml} +0 -0
- data/app/views/integral/backend/shared/cards/_recent_activity.haml +20 -0
- data/app/views/integral/backend/shared/cards/_recent_pages.haml +19 -0
- data/app/views/integral/backend/shared/cards/_recent_posts.haml +18 -0
- data/app/views/integral/backend/shared/cards/_recent_user_activity.haml +1 -0
- data/app/views/integral/backend/shared/cards/_recent_users.haml +19 -0
- data/app/views/integral/backend/shared/cards/_top_post_authors.haml +19 -0
- data/app/views/integral/backend/shared/record_selector/_record.haml +6 -4
- data/app/views/integral/backend/static_pages/dashboard.haml +13 -11
- data/app/views/integral/backend/users/_grid.haml +24 -7
- data/app/views/integral/backend/users/index.haml +11 -17
- data/app/views/integral/backend/users/list.haml +18 -0
- data/app/views/integral/backend/users/show.haml +5 -11
- data/app/views/integral/categories/show.haml +5 -0
- data/app/views/integral/posts/_article_footer.haml +17 -0
- data/app/views/integral/posts/_card.haml +11 -0
- data/app/views/integral/posts/_latest_post.haml +8 -0
- data/app/views/integral/posts/_most_read_section.haml +8 -0
- data/app/views/integral/posts/_post.haml +11 -0
- data/app/views/integral/posts/_similar_posts.haml +5 -0
- data/app/views/integral/posts/index.haml +6 -5
- data/app/views/integral/posts/templates/default.haml +34 -33
- data/app/views/integral/shared/_subscribe_modal.haml +14 -0
- data/app/views/integral/shared/blog/_categories.haml +15 -0
- data/app/views/integral/shared/blog/_layout.haml +9 -0
- data/app/views/integral/shared/blog/_sidebar.haml +10 -0
- data/app/views/integral/shared/gallery/_placeholder.haml +1 -1
- data/app/views/integral/shared/gallery/_slide.haml +2 -2
- data/app/views/integral/shared/gallery/gallery.haml +5 -2
- data/app/views/integral/shared/sidebar/_item.haml +8 -0
- data/app/views/integral/shared/sidebar/_newsletter_signup.haml +7 -0
- data/app/views/integral/shared/sidebar/_popular_posts.haml +7 -0
- data/app/views/integral/shared/sidebar/_popular_tags.haml +7 -0
- data/app/views/integral/shared/sidebar/_recent_posts.haml +7 -0
- data/app/views/integral/tags/index.haml +2 -2
- data/app/views/integral/tags/show.haml +3 -6
- data/app/views/layouts/integral/backend.html.haml +3 -0
- data/app/views/layouts/integral/backend/_main_menu_items.haml +10 -0
- data/app/views/layouts/integral/frontend.html.haml +3 -3
- data/config/locales/en.yml +52 -49
- data/db/migrate/20190414172018_create_webhook_endpoints.rb +10 -0
- data/db/migrate/20190929191412_add_integral_post_categories.rb +13 -0
- data/db/migrate/20191203090008_add_image_to_integral_categories.rb +6 -0
- data/db/migrate/20200401210442_create_category_versions.rb +20 -0
- data/db/seeds.rb +3 -1
- data/lib/generators/integral/assets_generator.rb +2 -2
- data/lib/generators/integral/install_generator.rb +1 -1
- data/lib/generators/integral/views_generator.rb +1 -1
- data/lib/generators/templates/integral.rb +5 -0
- data/lib/integral.rb +3 -30
- data/lib/integral/acts_as_listable.rb +2 -2
- data/lib/integral/chart_renderer/base.rb +2 -0
- data/lib/integral/content_renderer.rb +2 -2
- data/lib/integral/engine.rb +2 -2
- data/lib/integral/grids/activities_grid.rb +15 -1
- data/lib/integral/list_item_renderer.rb +4 -2
- data/lib/integral/list_renderer.rb +1 -0
- data/lib/integral/middleware/page_router.rb +15 -6
- data/lib/integral/router.rb +19 -3
- data/lib/integral/version.rb +1 -1
- data/lib/integral/widgets/swiper_list.rb +3 -2
- data/public/images/integral/demo/continous-integration.png +0 -0
- data/public/images/integral/demo/foundation-frontend-framework.jpg +0 -0
- data/public/images/integral/demo/heroku.png +0 -0
- data/public/images/integral/demo/integral-cms-without-hassle.jpg +0 -0
- data/public/images/integral/demo/integral-features-activity-tracking.jpg +0 -0
- data/public/images/integral/demo/integral-features-contact-form.png +0 -0
- data/public/images/integral/demo/integral-features-design.jpg +0 -0
- data/public/images/integral/demo/integral-features-dynamic-pages.jpg +0 -0
- data/public/images/integral/demo/integral-features-image-management.jpg +0 -0
- data/public/images/integral/demo/integral-features-integrated-blog.jpg +0 -0
- data/public/images/integral/demo/integral-features-list-management.jpg +0 -0
- data/public/images/integral/demo/integral-features-seo-ready.jpg +0 -0
- data/public/images/integral/demo/integral-features-user-management.jpg +0 -0
- data/public/images/integral/demo/integral-presentation.png +0 -0
- data/spec/factories.rb +15 -7
- metadata +110 -98
- data/app/assets/javascripts/ckeditor/plugins/integral-card/icons/copywidget.png +0 -0
- data/app/assets/javascripts/ckeditor/plugins/integral-card/icons/editwidget.png +0 -0
- data/app/assets/javascripts/ckeditor/plugins/integral-card/icons/hidpi/copywidget.png +0 -0
- data/app/assets/javascripts/ckeditor/plugins/integral-card/icons/hidpi/editwidget.png +0 -0
- data/app/assets/javascripts/ckeditor/plugins/integral-card/icons/hidpi/removewidget.png +0 -0
- data/app/assets/javascripts/ckeditor/plugins/integral-card/icons/hidpi/widget.png +0 -0
- data/app/assets/javascripts/ckeditor/plugins/integral-card/icons/removewidget.png +0 -0
- data/app/assets/javascripts/ckeditor/plugins/integral-card/icons/widget.png +0 -0
- data/app/assets/javascripts/ckeditor/plugins/integral-card/plugin.js +0 -86
- data/app/assets/javascripts/ckeditor/plugins/integralrecentposts/dialogs/integralrecentposts.js +0 -40
- data/app/assets/javascripts/ckeditor/plugins/integralrecentposts/plugin.js +0 -32
- data/app/assets/javascripts/ckeditor/plugins/numericinput/LICENSE.md +0 -363
- data/app/assets/javascripts/ckeditor/plugins/numericinput/README.md +0 -16
- data/app/assets/javascripts/ckeditor/plugins/numericinput/plugin.js +0 -354
- data/app/assets/stylesheets/integral/frontend.sass +0 -25
- data/app/views/integral/backend/pages/activities.haml +0 -2
- data/app/views/integral/backend/pages/activity.haml +0 -1
- data/app/views/integral/backend/posts/activities.haml +0 -3
- data/app/views/integral/backend/posts/activity.haml +0 -1
- data/app/views/integral/posts/_collection.haml +0 -4
- data/app/views/integral/posts/_item.haml +0 -16
- data/app/views/integral/shared/_blog_layout.haml +0 -15
- data/app/views/integral/shared/_blog_sidebar.haml +0 -49
- data/lib/integral/slack_bot.rb +0 -45
@@ -0,0 +1,54 @@
|
|
1
|
+
# Adds lazy content behaviour to a model
|
2
|
+
module Integral
|
3
|
+
# Enable lazy loading WYSIWYG content
|
4
|
+
module LazyContentable
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
before_save :lazyload_content
|
9
|
+
end
|
10
|
+
|
11
|
+
# @return [String] body HTML ready for WYSIWYG editor
|
12
|
+
def editor_body
|
13
|
+
html = Nokogiri::HTML(body)
|
14
|
+
|
15
|
+
# Remove image lazyloading
|
16
|
+
html.css('img.lazyload').each do |element|
|
17
|
+
element.attributes['src'].value = element.attributes['data-src'].value
|
18
|
+
end
|
19
|
+
|
20
|
+
html.css('body').inner_html
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def lazyload_content
|
26
|
+
html = Nokogiri::HTML(body)
|
27
|
+
|
28
|
+
# Add lazyloading to tagged images
|
29
|
+
html.css('img.lazyload').each do |element|
|
30
|
+
element['data-src'] = element.attributes['src'].value
|
31
|
+
element.attributes['src'].value = ''
|
32
|
+
end
|
33
|
+
|
34
|
+
# Add lazy loading to oEmbeds
|
35
|
+
html.css('div[data-oembed-url]').each do |element|
|
36
|
+
next if element.css('blockquote.lazyload').any?
|
37
|
+
|
38
|
+
blockquote = element.css('blockquote').first
|
39
|
+
|
40
|
+
next unless blockquote.present?
|
41
|
+
|
42
|
+
blockquote['class'] = "#{blockquote.attributes['class']} lazyload"
|
43
|
+
|
44
|
+
if element['data-oembed-url'].starts_with?('https://www.instagram.com')
|
45
|
+
blockquote['data-instagram'] = 'instagram'
|
46
|
+
elsif element['data-oembed-url'].starts_with?('https://twitter.com')
|
47
|
+
blockquote['data-twitter'] = 'twitter'
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
self.body = html.css('body').inner_html
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Integral
|
2
|
+
module Webhook
|
3
|
+
# Helper to handle the delivery of webhook events
|
4
|
+
module Delivery
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
# Abstract method to be overriden
|
8
|
+
#
|
9
|
+
# @return [Hash] which repesents the object
|
10
|
+
def webhook_payload
|
11
|
+
{}
|
12
|
+
end
|
13
|
+
|
14
|
+
# Build event_name and delivery webhook
|
15
|
+
def deliver_webhook(action)
|
16
|
+
event_name = "#{self.class.name.underscore}_#{action}"
|
17
|
+
deliver_webhook_event(event_name, webhook_payload)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Create webhook event and deliver it to any endpoint listening
|
21
|
+
def deliver_webhook_event(event_name, payload)
|
22
|
+
event = Webhook::Event.new(event_name, payload || {})
|
23
|
+
|
24
|
+
Endpoint.for_event(event_name).each do |endpoint|
|
25
|
+
endpoint.deliver(event)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Integral
|
2
|
+
module Webhook
|
3
|
+
# Helper to deliver webhooks for each of the 3 major model lifestyle events - creation, update and deletion
|
4
|
+
module Observable
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
include Webhook::Delivery
|
7
|
+
|
8
|
+
included do
|
9
|
+
after_commit on: :create do
|
10
|
+
deliver_webhook(:created)
|
11
|
+
end
|
12
|
+
|
13
|
+
after_commit on: :update do
|
14
|
+
deliver_webhook(:updated)
|
15
|
+
end
|
16
|
+
|
17
|
+
after_commit on: :destroy do
|
18
|
+
deliver_webhook(:destroyed)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Integral
|
2
|
+
# Represents a user post category
|
3
|
+
class Category < ApplicationRecord
|
4
|
+
has_paper_trail class_name: 'Integral::CategoryVersion'
|
5
|
+
|
6
|
+
# Slugging
|
7
|
+
extend FriendlyId
|
8
|
+
friendly_id :title
|
9
|
+
|
10
|
+
# Associations
|
11
|
+
has_many :posts # TODO: Touch the posts on change
|
12
|
+
belongs_to :image, class_name: 'Integral::Image', optional: true
|
13
|
+
|
14
|
+
# Validations
|
15
|
+
validates :slug, presence: true
|
16
|
+
validates_format_of :slug, with: /\A[A-Za-z0-9]+(?:-[A-Za-z0-9]+)*\z/
|
17
|
+
validates :title, presence: true, length: { minimum: 4, maximum: 60 }
|
18
|
+
validates :description, presence: true, length: { minimum: 25, maximum: 300 }
|
19
|
+
end
|
20
|
+
end
|
@@ -2,14 +2,13 @@ module Integral
|
|
2
2
|
# Represents an item within a particular list
|
3
3
|
class ListItem < ApplicationRecord
|
4
4
|
after_initialize :set_defaults
|
5
|
-
before_save :touch_list
|
6
5
|
after_touch :touch_list
|
7
6
|
|
8
7
|
# Default scope orders by priority and includes children
|
9
8
|
default_scope { includes(:children).includes(:image).order(:priority) }
|
10
9
|
|
11
10
|
# Associations
|
12
|
-
belongs_to :list, optional: true
|
11
|
+
belongs_to :list, optional: true, touch: true
|
13
12
|
belongs_to :image, optional: true
|
14
13
|
has_and_belongs_to_many(:children,
|
15
14
|
-> { order(:priority) },
|
data/app/models/integral/page.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
module Integral
|
2
2
|
# Represents a public viewable page
|
3
3
|
class Page < ApplicationRecord
|
4
|
+
include LazyContentable
|
5
|
+
|
4
6
|
acts_as_paranoid # Soft-deletion
|
5
7
|
acts_as_listable # Listable Item
|
6
8
|
|
@@ -12,9 +14,9 @@ module Integral
|
|
12
14
|
# /foo, /foo/bar, /123/456
|
13
15
|
# Bad:
|
14
16
|
# //, foo, /foo bar, /foo?y=123, /foo$
|
15
|
-
PATH_REGEX =
|
17
|
+
PATH_REGEX = %r{\A/[/.a-zA-Z0-9-]+\z}.freeze
|
16
18
|
|
17
|
-
enum status: %i[draft published]
|
19
|
+
enum status: %i[draft published archived]
|
18
20
|
|
19
21
|
# Associations
|
20
22
|
belongs_to :parent, class_name: 'Integral::Page', optional: true
|
@@ -31,8 +33,13 @@ module Integral
|
|
31
33
|
validate :validate_path_is_not_black_listed
|
32
34
|
validate :validate_parent_is_available
|
33
35
|
|
36
|
+
# Callbacks
|
37
|
+
before_save :set_paper_trail_event
|
38
|
+
|
34
39
|
# Scopes
|
35
40
|
scope :search, ->(query) { where('lower(title) LIKE ? OR lower(path) LIKE ?', "%#{query.downcase}%", "%#{query.downcase}%") }
|
41
|
+
# TODO: Remove this on Rails 6 upgrade
|
42
|
+
scope :not_archived, -> { where.not(status: :archived) }
|
36
43
|
|
37
44
|
# Return all available parents
|
38
45
|
# TODO: Update parent behaviour
|
@@ -123,11 +130,18 @@ module Integral
|
|
123
130
|
|
124
131
|
private
|
125
132
|
|
133
|
+
def set_paper_trail_event
|
134
|
+
if persisted? && published? && status_changed?
|
135
|
+
self.paper_trail_event = :publish
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
126
139
|
# @return [Array] containing available human readable statuses against there numeric value
|
127
140
|
def self.available_statuses
|
128
141
|
[
|
129
142
|
['Draft', 0],
|
130
|
-
['Published', 1]
|
143
|
+
['Published', 1],
|
144
|
+
['Archived', 2]
|
131
145
|
]
|
132
146
|
end
|
133
147
|
|
@@ -142,6 +156,7 @@ module Integral
|
|
142
156
|
|
143
157
|
Integral.black_listed_paths.each do |black_listed_path|
|
144
158
|
next unless path&.starts_with?(black_listed_path)
|
159
|
+
|
145
160
|
valid = false
|
146
161
|
errors.add(:path, 'Invalid path')
|
147
162
|
break
|
data/app/models/integral/post.rb
CHANGED
@@ -2,6 +2,9 @@ module Integral
|
|
2
2
|
# Represents a user post
|
3
3
|
class Post < ApplicationRecord
|
4
4
|
include ActionView::Helpers::DateHelper
|
5
|
+
include LazyContentable
|
6
|
+
include Webhook::Observable
|
7
|
+
|
5
8
|
acts_as_paranoid # Soft-deletion
|
6
9
|
acts_as_listable if Integral.blog_enabled? # Listable Item
|
7
10
|
acts_as_taggable # Tagging
|
@@ -19,6 +22,7 @@ module Integral
|
|
19
22
|
|
20
23
|
# Associations
|
21
24
|
belongs_to :user
|
25
|
+
belongs_to :category
|
22
26
|
belongs_to :image, class_name: 'Integral::Image', optional: true
|
23
27
|
belongs_to :preview_image, class_name: 'Integral::Image', optional: true
|
24
28
|
|
@@ -29,7 +33,10 @@ module Integral
|
|
29
33
|
|
30
34
|
# Callbacks
|
31
35
|
before_save :set_published_at
|
36
|
+
before_save :set_paper_trail_event
|
32
37
|
before_save :set_tags_context
|
38
|
+
after_update :deliver_published_webhook_on_update
|
39
|
+
after_create :deliver_published_webhook_on_create
|
33
40
|
|
34
41
|
# Aliases
|
35
42
|
alias author user
|
@@ -111,6 +118,10 @@ module Integral
|
|
111
118
|
|
112
119
|
private
|
113
120
|
|
121
|
+
def webhook_payload
|
122
|
+
Integral::PostSerializer.new(self).serializable_hash
|
123
|
+
end
|
124
|
+
|
114
125
|
def set_slug
|
115
126
|
if slug_changed? && Post.exists_by_friendly_id?(slug)
|
116
127
|
self.slug = resolve_friendly_id_conflict([slug])
|
@@ -118,7 +129,15 @@ module Integral
|
|
118
129
|
end
|
119
130
|
|
120
131
|
def set_published_at
|
121
|
-
|
132
|
+
if published? && published_at.nil?
|
133
|
+
self.published_at = Time.now
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def set_paper_trail_event
|
138
|
+
if persisted? && published? && status_changed?
|
139
|
+
self.paper_trail_event = :publish
|
140
|
+
end
|
122
141
|
end
|
123
142
|
|
124
143
|
# Set the context of tags so that draft and archived tags are not displayed publicly
|
@@ -145,5 +164,13 @@ module Integral
|
|
145
164
|
contexts.delete(tag_context)
|
146
165
|
contexts
|
147
166
|
end
|
167
|
+
|
168
|
+
def deliver_published_webhook_on_update
|
169
|
+
deliver_webhook(:published) if status_changed? && published?
|
170
|
+
end
|
171
|
+
|
172
|
+
def deliver_published_webhook_on_create
|
173
|
+
deliver_webhook(:published) if published?
|
174
|
+
end
|
148
175
|
end
|
149
176
|
end
|
@@ -10,7 +10,7 @@ module Integral
|
|
10
10
|
def self.available_actions
|
11
11
|
available = []
|
12
12
|
|
13
|
-
%w[update create destroy].each do |item|
|
13
|
+
%w[update create destroy publish].each do |item|
|
14
14
|
available << [I18n.t("integral.actions.#{item}"), item]
|
15
15
|
end
|
16
16
|
|
@@ -21,7 +21,7 @@ module Integral
|
|
21
21
|
def self.available_objects
|
22
22
|
available = []
|
23
23
|
|
24
|
-
[Integral::Post, Integral::Page, Integral::List, Integral::Image, Integral::User].each do |item|
|
24
|
+
[Integral::Post, Integral::Category, Integral::Page, Integral::List, Integral::Image, Integral::User].concat(Integral.additional_tracked_classes).each do |item|
|
25
25
|
available << [item.model_name.human, item]
|
26
26
|
end
|
27
27
|
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Integral
|
2
|
+
# Webhook Implementation - https://benediktdeicke.com/2017/09/sending-webhooks-with-rails/
|
3
|
+
module Webhook
|
4
|
+
# A Webhook::Endpoint can listen to one or more Webhook::Event, everytime an Event is created
|
5
|
+
# it is sent (along with its payload) to the Endpoint target_url
|
6
|
+
class Endpoint < ApplicationRecord
|
7
|
+
self.table_name = 'integral_webhook_endpoints'
|
8
|
+
|
9
|
+
attribute :events, :string, array: true, default: []
|
10
|
+
|
11
|
+
validates :target_url,
|
12
|
+
presence: true,
|
13
|
+
format: URI.regexp(%w[http https])
|
14
|
+
|
15
|
+
validates :events,
|
16
|
+
presence: true
|
17
|
+
|
18
|
+
# @param events [Array] list of Integral::Webhook::Event names
|
19
|
+
#
|
20
|
+
# @return [Integral::Webhook::Endpoint] endpoints which are listening for the provided events
|
21
|
+
def self.for_event(events)
|
22
|
+
where('events @> ARRAY[?]::varchar[]', Array(events))
|
23
|
+
end
|
24
|
+
|
25
|
+
# Override the default setter to normalise the events and validate them (TODO)
|
26
|
+
def events=(events)
|
27
|
+
events = Array(events).map { |event| event.to_s.underscore }
|
28
|
+
# TODO: Validate events from specified list in Integral.config which can be used to
|
29
|
+
# list them on the backend for click and create
|
30
|
+
# super(Webhook::Event::EVENT_TYPES & events)
|
31
|
+
super(events)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Deliver a particular event to the endpoint
|
35
|
+
def deliver(event)
|
36
|
+
Webhook::DeliveryJob.perform_later(id, event.to_json)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Integral
|
2
|
+
# Webhook Implementation source from - https://benediktdeicke.com/2017/09/sending-webhooks-with-rails/
|
3
|
+
module Webhook
|
4
|
+
# An event instance which a Webhook::Endpoint might be listening too, for example a post publication, creation or deletion
|
5
|
+
class Event
|
6
|
+
attr_reader :event_name, :payload
|
7
|
+
|
8
|
+
def initialize(event_name, payload = {})
|
9
|
+
@event_name = event_name
|
10
|
+
@payload = payload
|
11
|
+
end
|
12
|
+
|
13
|
+
# Adds the event_name to the JSON representation
|
14
|
+
def as_json(*_args)
|
15
|
+
payload[:event_name] = event_name
|
16
|
+
payload
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Integral
|
2
|
+
# Used too transform Integral::Post records into JSON format
|
3
|
+
class PostSerializer
|
4
|
+
include FastJsonapi::ObjectSerializer
|
5
|
+
|
6
|
+
attributes :title, :description, :status, :slug, :created_at, :updated_at, :published_at, :url, :body
|
7
|
+
|
8
|
+
attribute :author do |post|
|
9
|
+
post.author&.name
|
10
|
+
end
|
11
|
+
|
12
|
+
attribute :tags do |post|
|
13
|
+
post.tags.map(&:name).join(',')
|
14
|
+
end
|
15
|
+
|
16
|
+
attribute :featured_image do |post|
|
17
|
+
post&.featured_image&.url(:large)
|
18
|
+
end
|
19
|
+
|
20
|
+
attribute :preview_image do |post|
|
21
|
+
post&.preview_image&.url(:large)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -2,7 +2,7 @@ module Integral
|
|
2
2
|
# Handles uploading user avatars
|
3
3
|
class AvatarUploader < ImageUploader
|
4
4
|
# Provide a default URL as a default if there hasn't been a file uploaded
|
5
|
-
def default_url
|
5
|
+
def default_url(*args)
|
6
6
|
ActionController::Base.helpers.asset_path('integral/defaults/user_avatar.jpg')
|
7
7
|
end
|
8
8
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
.timeline-item
|
2
|
+
.timeline-icon
|
3
|
+
= icon(activity.item_icon)
|
4
|
+
.timeline-content
|
5
|
+
.timeline-content-header
|
6
|
+
%p.timeline-content-event= activity.event
|
7
|
+
- if activity.whodunnit.present?
|
8
|
+
= link_to activity.whodunnit_url, class: 'timeline-content-user' do
|
9
|
+
= image_tag activity.whodunnit_avatar_url, class: :avatar
|
10
|
+
%span= activity.whodunnit_name.truncate(30)
|
11
|
+
- else
|
12
|
+
%p.timeline-content-user
|
13
|
+
= image_tag activity.whodunnit_avatar_url, class: :avatar
|
14
|
+
%span= activity.whodunnit_name.truncate(30)
|
15
|
+
%p.timeline-content-date= l(activity.created_at)
|
16
|
+
%p.timeline-content-title
|
17
|
+
= activity.whodunnit_name
|
18
|
+
= activity.event_verb.downcase
|
19
|
+
= link_to activity.item_title, activity.item_url
|
20
|
+
%hr
|
21
|
+
|