integral 1.3.0 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (205) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -30
  3. data/Rakefile +1 -1
  4. data/app/assets/images/integral/defaults/no_image_available.jpg +0 -0
  5. data/app/assets/javascripts/integral/backend.js +102 -11
  6. data/app/assets/javascripts/integral/frontend.js +37 -0
  7. data/app/assets/javascripts/integral/support/confirm_modal.coffee +2 -2
  8. data/app/assets/javascripts/integral/support/gallery.coffee +71 -54
  9. data/app/assets/javascripts/integral/support/lib/lazysizes.js +755 -0
  10. data/app/assets/javascripts/integral/support/lib/materialize-tags.js +49 -44
  11. data/app/assets/javascripts/integral/support/ls.instagram.js +57 -0
  12. data/app/assets/javascripts/integral/support/ls.twitter.js +66 -0
  13. data/app/assets/javascripts/integral/support/record_selector.coffee +1 -1
  14. data/app/assets/javascripts/integral/support/remote_form.coffee +5 -2
  15. data/app/assets/stylesheets/integral/backend.sass +2 -1
  16. data/app/assets/stylesheets/integral/backend/_foundation_settings.scss +3 -4
  17. data/app/assets/stylesheets/integral/backend/dashboard-layout.scss +4 -1
  18. data/app/assets/stylesheets/integral/backend/materialize-tags.sass +1 -1
  19. data/app/assets/stylesheets/integral/backend/modules/timeline.scss +214 -0
  20. data/app/assets/stylesheets/integral/backend/shared.sass +80 -11
  21. data/app/assets/stylesheets/integral/frontend.scss +45 -0
  22. data/app/assets/stylesheets/integral/frontend/_foundation_settings.scss +2 -2
  23. data/app/assets/stylesheets/integral/frontend/blog.sass +155 -142
  24. data/app/assets/stylesheets/integral/frontend/layout.sass +3 -3
  25. data/app/assets/stylesheets/integral/frontend/modules/article-footer.scss +55 -0
  26. data/app/assets/stylesheets/integral/frontend/modules/article.scss +34 -0
  27. data/app/assets/stylesheets/integral/frontend/modules/horizontal-post.scss +44 -0
  28. data/app/assets/stylesheets/integral/frontend/modules/inline-articles.scss +23 -0
  29. data/app/assets/stylesheets/integral/frontend/modules/latest-post.scss +37 -0
  30. data/app/assets/stylesheets/integral/frontend/modules/list-widget.scss +50 -0
  31. data/app/assets/stylesheets/integral/frontend/modules/piped-list.scss +33 -0
  32. data/app/assets/stylesheets/integral/frontend/modules/post-tags.scss +19 -0
  33. data/app/assets/stylesheets/integral/frontend/modules/scroll-container.scss +9 -0
  34. data/app/assets/stylesheets/integral/frontend/modules/sidebar-articles.scss +42 -0
  35. data/app/assets/stylesheets/integral/frontend/modules/sidebar-tags.scss +6 -0
  36. data/app/assets/stylesheets/integral/frontend/modules/sidebar-widget.scss +47 -0
  37. data/app/assets/stylesheets/integral/frontend/modules/vertical-post.scss +31 -0
  38. data/app/assets/stylesheets/integral/frontend/share_modal.sass +0 -5
  39. data/app/assets/stylesheets/integral/support/gallery.sass +8 -0
  40. data/app/assets/stylesheets/integral/support/media-query-indicator.sass +6 -0
  41. data/app/controllers/integral/application_controller.rb +7 -1
  42. data/app/controllers/integral/backend/activities_controller.rb +13 -2
  43. data/app/controllers/integral/backend/base_controller.rb +60 -7
  44. data/app/controllers/integral/backend/categories_controller.rb +49 -0
  45. data/app/controllers/integral/backend/pages_controller.rb +7 -2
  46. data/app/controllers/integral/backend/posts_controller.rb +8 -3
  47. data/app/controllers/integral/backend/static_pages_controller.rb +4 -0
  48. data/app/controllers/integral/backend/users_controller.rb +13 -7
  49. data/app/controllers/integral/categories_controller.rb +31 -0
  50. data/app/controllers/integral/pages_controller.rb +1 -1
  51. data/app/controllers/integral/posts_controller.rb +5 -3
  52. data/app/decorators/integral/category_decorator.rb +30 -0
  53. data/app/decorators/integral/category_version_decorator.rb +7 -0
  54. data/app/decorators/integral/image_version_decorator.rb +7 -0
  55. data/app/decorators/integral/list_decorator.rb +1 -1
  56. data/app/decorators/integral/list_version_decorator.rb +7 -0
  57. data/app/decorators/integral/page_version_decorator.rb +7 -0
  58. data/app/decorators/integral/post_decorator.rb +9 -1
  59. data/app/decorators/integral/post_version_decorator.rb +7 -0
  60. data/app/decorators/integral/user_decorator.rb +1 -1
  61. data/app/decorators/integral/user_version_decorator.rb +7 -0
  62. data/app/decorators/integral/version_decorator.rb +51 -12
  63. data/app/helpers/integral/backend/base_helper.rb +56 -2
  64. data/app/helpers/integral/blog_helper.rb +21 -4
  65. data/app/jobs/integral/webhook/delivery_job.rb +37 -0
  66. data/app/mailers/integral/contact_mailer.rb +4 -1
  67. data/app/models/concerns/integral/lazy_contentable.rb +54 -0
  68. data/app/models/concerns/integral/webhook/delivery.rb +30 -0
  69. data/app/models/concerns/integral/webhook/observable.rb +23 -0
  70. data/app/models/integral/category.rb +20 -0
  71. data/app/models/integral/category_version.rb +8 -0
  72. data/app/models/integral/list_item.rb +1 -2
  73. data/app/models/integral/page.rb +18 -3
  74. data/app/models/integral/post.rb +28 -1
  75. data/app/models/integral/version.rb +2 -2
  76. data/app/models/integral/webhook/endpoint.rb +40 -0
  77. data/app/models/integral/webhook/event.rb +20 -0
  78. data/app/policies/integral/base_policy.rb +1 -0
  79. data/app/policies/integral/category_policy.rb +9 -0
  80. data/app/serializers/integral/post_serializer.rb +24 -0
  81. data/app/uploaders/integral/avatar_uploader.rb +1 -1
  82. data/app/views/integral/backend/activities/_activity.haml +21 -0
  83. data/app/views/integral/backend/activities/_grid.haml +1 -2
  84. data/app/views/integral/backend/activities/shared/_grid.haml +3 -2
  85. data/app/views/integral/backend/activities/shared/{_listing.haml → index.haml} +1 -0
  86. data/app/views/integral/backend/activities/shared/{_log.haml → show.haml} +0 -0
  87. data/app/views/integral/backend/categories/_modal.haml +25 -0
  88. data/app/views/integral/backend/lists/_child_fields.haml +1 -1
  89. data/app/views/integral/backend/lists/_item_container.haml +1 -1
  90. data/app/views/integral/backend/lists/_item_modal.haml +1 -1
  91. data/app/views/integral/backend/lists/_list_item_fields.haml +1 -1
  92. data/app/views/integral/backend/pages/_form.haml +1 -4
  93. data/app/views/integral/backend/pages/_grid.haml +34 -9
  94. data/app/views/integral/backend/pages/edit.haml +9 -3
  95. data/app/views/integral/backend/pages/index.haml +11 -21
  96. data/app/views/integral/backend/pages/list.haml +22 -0
  97. data/app/views/integral/backend/pages/show.haml +48 -0
  98. data/app/views/integral/backend/posts/_form.haml +8 -6
  99. data/app/views/integral/backend/posts/_grid.haml +33 -7
  100. data/app/views/integral/backend/posts/index.haml +13 -19
  101. data/app/views/integral/backend/posts/list.haml +20 -0
  102. data/app/views/integral/backend/posts/show.haml +54 -0
  103. data/app/views/integral/backend/shared/_activity_modal.haml +13 -0
  104. data/app/views/integral/backend/shared/cards/_categories.haml +34 -0
  105. data/app/views/integral/backend/{static_pages/_card.haml → shared/cards/_object.haml} +0 -0
  106. data/app/views/integral/backend/shared/cards/_recent_activity.haml +20 -0
  107. data/app/views/integral/backend/shared/cards/_recent_pages.haml +19 -0
  108. data/app/views/integral/backend/shared/cards/_recent_posts.haml +18 -0
  109. data/app/views/integral/backend/shared/cards/_recent_user_activity.haml +1 -0
  110. data/app/views/integral/backend/shared/cards/_recent_users.haml +19 -0
  111. data/app/views/integral/backend/shared/cards/_top_post_authors.haml +19 -0
  112. data/app/views/integral/backend/shared/record_selector/_record.haml +6 -4
  113. data/app/views/integral/backend/static_pages/dashboard.haml +13 -11
  114. data/app/views/integral/backend/users/_grid.haml +24 -7
  115. data/app/views/integral/backend/users/index.haml +11 -17
  116. data/app/views/integral/backend/users/list.haml +18 -0
  117. data/app/views/integral/backend/users/show.haml +5 -11
  118. data/app/views/integral/categories/show.haml +5 -0
  119. data/app/views/integral/posts/_article_footer.haml +17 -0
  120. data/app/views/integral/posts/_card.haml +11 -0
  121. data/app/views/integral/posts/_latest_post.haml +8 -0
  122. data/app/views/integral/posts/_most_read_section.haml +8 -0
  123. data/app/views/integral/posts/_post.haml +11 -0
  124. data/app/views/integral/posts/_similar_posts.haml +5 -0
  125. data/app/views/integral/posts/index.haml +6 -5
  126. data/app/views/integral/posts/templates/default.haml +34 -33
  127. data/app/views/integral/shared/_subscribe_modal.haml +14 -0
  128. data/app/views/integral/shared/blog/_categories.haml +15 -0
  129. data/app/views/integral/shared/blog/_layout.haml +9 -0
  130. data/app/views/integral/shared/blog/_sidebar.haml +10 -0
  131. data/app/views/integral/shared/gallery/_placeholder.haml +1 -1
  132. data/app/views/integral/shared/gallery/_slide.haml +2 -2
  133. data/app/views/integral/shared/gallery/gallery.haml +5 -2
  134. data/app/views/integral/shared/sidebar/_item.haml +8 -0
  135. data/app/views/integral/shared/sidebar/_newsletter_signup.haml +7 -0
  136. data/app/views/integral/shared/sidebar/_popular_posts.haml +7 -0
  137. data/app/views/integral/shared/sidebar/_popular_tags.haml +7 -0
  138. data/app/views/integral/shared/sidebar/_recent_posts.haml +7 -0
  139. data/app/views/integral/tags/index.haml +2 -2
  140. data/app/views/integral/tags/show.haml +3 -6
  141. data/app/views/layouts/integral/backend.html.haml +3 -0
  142. data/app/views/layouts/integral/backend/_main_menu_items.haml +10 -0
  143. data/app/views/layouts/integral/frontend.html.haml +3 -3
  144. data/config/locales/en.yml +52 -49
  145. data/db/migrate/20190414172018_create_webhook_endpoints.rb +10 -0
  146. data/db/migrate/20190929191412_add_integral_post_categories.rb +13 -0
  147. data/db/migrate/20191203090008_add_image_to_integral_categories.rb +6 -0
  148. data/db/migrate/20200401210442_create_category_versions.rb +20 -0
  149. data/db/seeds.rb +3 -1
  150. data/lib/generators/integral/assets_generator.rb +2 -2
  151. data/lib/generators/integral/install_generator.rb +1 -1
  152. data/lib/generators/integral/views_generator.rb +1 -1
  153. data/lib/generators/templates/integral.rb +5 -0
  154. data/lib/integral.rb +3 -30
  155. data/lib/integral/acts_as_listable.rb +2 -2
  156. data/lib/integral/chart_renderer/base.rb +2 -0
  157. data/lib/integral/content_renderer.rb +2 -2
  158. data/lib/integral/engine.rb +2 -2
  159. data/lib/integral/grids/activities_grid.rb +15 -1
  160. data/lib/integral/list_item_renderer.rb +4 -2
  161. data/lib/integral/list_renderer.rb +1 -0
  162. data/lib/integral/middleware/page_router.rb +15 -6
  163. data/lib/integral/router.rb +19 -3
  164. data/lib/integral/version.rb +1 -1
  165. data/lib/integral/widgets/swiper_list.rb +3 -2
  166. data/public/images/integral/demo/continous-integration.png +0 -0
  167. data/public/images/integral/demo/foundation-frontend-framework.jpg +0 -0
  168. data/public/images/integral/demo/heroku.png +0 -0
  169. data/public/images/integral/demo/integral-cms-without-hassle.jpg +0 -0
  170. data/public/images/integral/demo/integral-features-activity-tracking.jpg +0 -0
  171. data/public/images/integral/demo/integral-features-contact-form.png +0 -0
  172. data/public/images/integral/demo/integral-features-design.jpg +0 -0
  173. data/public/images/integral/demo/integral-features-dynamic-pages.jpg +0 -0
  174. data/public/images/integral/demo/integral-features-image-management.jpg +0 -0
  175. data/public/images/integral/demo/integral-features-integrated-blog.jpg +0 -0
  176. data/public/images/integral/demo/integral-features-list-management.jpg +0 -0
  177. data/public/images/integral/demo/integral-features-seo-ready.jpg +0 -0
  178. data/public/images/integral/demo/integral-features-user-management.jpg +0 -0
  179. data/public/images/integral/demo/integral-presentation.png +0 -0
  180. data/spec/factories.rb +15 -7
  181. metadata +110 -98
  182. data/app/assets/javascripts/ckeditor/plugins/integral-card/icons/copywidget.png +0 -0
  183. data/app/assets/javascripts/ckeditor/plugins/integral-card/icons/editwidget.png +0 -0
  184. data/app/assets/javascripts/ckeditor/plugins/integral-card/icons/hidpi/copywidget.png +0 -0
  185. data/app/assets/javascripts/ckeditor/plugins/integral-card/icons/hidpi/editwidget.png +0 -0
  186. data/app/assets/javascripts/ckeditor/plugins/integral-card/icons/hidpi/removewidget.png +0 -0
  187. data/app/assets/javascripts/ckeditor/plugins/integral-card/icons/hidpi/widget.png +0 -0
  188. data/app/assets/javascripts/ckeditor/plugins/integral-card/icons/removewidget.png +0 -0
  189. data/app/assets/javascripts/ckeditor/plugins/integral-card/icons/widget.png +0 -0
  190. data/app/assets/javascripts/ckeditor/plugins/integral-card/plugin.js +0 -86
  191. data/app/assets/javascripts/ckeditor/plugins/integralrecentposts/dialogs/integralrecentposts.js +0 -40
  192. data/app/assets/javascripts/ckeditor/plugins/integralrecentposts/plugin.js +0 -32
  193. data/app/assets/javascripts/ckeditor/plugins/numericinput/LICENSE.md +0 -363
  194. data/app/assets/javascripts/ckeditor/plugins/numericinput/README.md +0 -16
  195. data/app/assets/javascripts/ckeditor/plugins/numericinput/plugin.js +0 -354
  196. data/app/assets/stylesheets/integral/frontend.sass +0 -25
  197. data/app/views/integral/backend/pages/activities.haml +0 -2
  198. data/app/views/integral/backend/pages/activity.haml +0 -1
  199. data/app/views/integral/backend/posts/activities.haml +0 -3
  200. data/app/views/integral/backend/posts/activity.haml +0 -1
  201. data/app/views/integral/posts/_collection.haml +0 -4
  202. data/app/views/integral/posts/_item.haml +0 -16
  203. data/app/views/integral/shared/_blog_layout.haml +0 -15
  204. data/app/views/integral/shared/_blog_sidebar.haml +0 -49
  205. 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
@@ -0,0 +1,8 @@
1
+ # Integral namespace
2
+ module Integral
3
+ # Record PaperTrail of Integral::Category
4
+ class CategoryVersion < Version
5
+ self.table_name = :integral_category_versions
6
+ self.sequence_name = :integral_category_versions_id_seq
7
+ end
8
+ 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) },
@@ -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 = /\A\/[\/.a-zA-Z0-9-]+\z/
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
@@ -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
- self.published_at = Time.now if published? && published_at.nil?
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
@@ -39,6 +39,7 @@ module Integral
39
39
 
40
40
  alias destroy? manager?
41
41
  alias index? manager?
42
+ alias list? manager?
42
43
  alias show? manager?
43
44
  alias new? manager?
44
45
  alias create? manager?
@@ -0,0 +1,9 @@
1
+ module Integral
2
+ # Handles Category authorization
3
+ class CategoryPolicy < BasePolicy
4
+ # @return [Symbol] role name
5
+ def role_name
6
+ :post_manager
7
+ end
8
+ end
9
+ 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
+