spina 1.1.4 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of spina might be problematic. Click here for more details.

Files changed (178) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/icons/spina/preview/icons_spina-preview.html +0 -2
  3. data/app/assets/icons/spina/preview/ics_spina-preview.html +0 -2
  4. data/app/assets/images/spina/favicon.png +0 -0
  5. data/app/assets/javascripts/spina/admin/application.js +1 -2
  6. data/app/assets/javascripts/spina/admin/controllers/attachment_picker_controller.js +15 -0
  7. data/app/assets/javascripts/spina/admin/controllers/image_form_controller.js +5 -4
  8. data/app/assets/javascripts/spina/admin/controllers/infinite_scroll_controller.js +20 -22
  9. data/app/assets/javascripts/spina/admin/controllers/media_picker_controller.js +171 -0
  10. data/app/assets/javascripts/spina/admin/controllers/modal_controller.js +18 -0
  11. data/app/assets/javascripts/spina/admin/controllers/repeater_form_controller.js +38 -0
  12. data/app/assets/javascripts/spina/admin/media_gallery.coffee +0 -3
  13. data/app/assets/javascripts/spina/admin/notifications.coffee +1 -1
  14. data/app/assets/javascripts/spina/admin/pages.coffee.erb +3 -20
  15. data/app/assets/javascripts/spina/admin/scaffold.coffee +1 -3
  16. data/app/assets/javascripts/spina/admin/trix.coffee.erb +14 -18
  17. data/app/assets/stylesheets/spina.sass +10 -8
  18. data/app/assets/stylesheets/spina/_buttons.sass +57 -31
  19. data/app/assets/stylesheets/spina/_forms.sass +66 -74
  20. data/app/assets/stylesheets/spina/_gallery.sass +18 -2
  21. data/app/assets/stylesheets/spina/_media_picker.sass +133 -0
  22. data/app/assets/stylesheets/spina/_modal.sass +35 -2
  23. data/app/assets/stylesheets/spina/_notifications.sass +20 -14
  24. data/app/assets/stylesheets/spina/_pages.sass +131 -0
  25. data/app/assets/stylesheets/spina/_sortable_lists.sass +14 -8
  26. data/app/assets/stylesheets/spina/_trix_custom.sass +15 -5
  27. data/app/controllers/concerns/spina/current_methods.rb +26 -0
  28. data/app/controllers/concerns/spina/frontend.rb +9 -0
  29. data/app/controllers/spina/admin/accounts_controller.rb +9 -13
  30. data/app/controllers/spina/admin/admin_controller.rb +5 -1
  31. data/app/controllers/spina/admin/attachments_controller.rb +0 -22
  32. data/app/controllers/spina/admin/images_controller.rb +1 -1
  33. data/app/controllers/spina/admin/media_picker_controller.rb +18 -19
  34. data/app/controllers/spina/admin/pages_controller.rb +2 -6
  35. data/app/controllers/spina/admin/resources_controller.rb +9 -4
  36. data/app/controllers/spina/application_controller.rb +6 -23
  37. data/app/controllers/spina/pages_controller.rb +11 -13
  38. data/app/controllers/spina/sitemaps_controller.rb +6 -6
  39. data/app/helpers/spina/admin/pages_helper.rb +22 -20
  40. data/app/helpers/spina/{files_helper.rb → attachments_helper.rb} +1 -1
  41. data/app/helpers/spina/images_helper.rb +9 -18
  42. data/app/helpers/spina/pages_helper.rb +23 -4
  43. data/app/models/concerns/spina/partable.rb +19 -7
  44. data/app/models/concerns/spina/translated_content.rb +19 -0
  45. data/app/models/spina/account.rb +3 -10
  46. data/app/models/spina/attachment.rb +0 -4
  47. data/app/models/spina/current.rb +1 -0
  48. data/app/models/spina/image.rb +0 -4
  49. data/app/models/spina/page.rb +29 -21
  50. data/app/models/spina/parts/attachment.rb +22 -0
  51. data/app/models/spina/parts/base.rb +12 -0
  52. data/app/models/spina/parts/image.rb +39 -0
  53. data/app/models/spina/parts/image_collection.rb +23 -0
  54. data/app/models/spina/parts/image_variant.rb +19 -0
  55. data/app/models/spina/parts/line.rb +7 -0
  56. data/app/models/spina/parts/multi_line.rb +7 -0
  57. data/app/models/spina/parts/option.rb +13 -0
  58. data/app/models/spina/parts/repeater.rb +10 -0
  59. data/app/models/spina/parts/repeater_content.rb +15 -0
  60. data/app/models/spina/parts/text.rb +7 -0
  61. data/app/models/spina/resource.rb +4 -12
  62. data/app/presenters/spina/content_presenter.rb +51 -0
  63. data/app/presenters/spina/menu_presenter.rb +2 -1
  64. data/app/views/layouts/spina/admin/admin.html.haml +3 -7
  65. data/app/views/layouts/spina/admin/pages.html.haml +5 -4
  66. data/app/views/layouts/spina/login.html.haml +0 -3
  67. data/app/views/spina/admin/accounts/_form.html.haml +13 -7
  68. data/app/views/spina/admin/accounts/analytics.html.haml +5 -3
  69. data/app/views/spina/admin/accounts/social.html.haml +13 -7
  70. data/app/views/spina/admin/accounts/style.html.haml +26 -21
  71. data/app/views/spina/admin/attachments/index.html.haml +1 -1
  72. data/app/views/spina/admin/images/_image.html.haml +1 -1
  73. data/app/views/spina/admin/images/index.html.haml +7 -5
  74. data/app/views/spina/admin/media_folders/_media_folder.html.haml +1 -1
  75. data/app/views/spina/admin/media_folders/show.html.haml +2 -2
  76. data/app/views/spina/admin/media_picker/_image.html.haml +3 -0
  77. data/app/views/spina/admin/media_picker/_media_picker.html.haml +32 -0
  78. data/app/views/spina/admin/media_picker/_media_picker_grid.html.haml +25 -0
  79. data/app/views/spina/admin/media_picker/_modal.html.haml +1 -1
  80. data/app/views/spina/admin/media_picker/infinite_scroll.js.erb +4 -4
  81. data/app/views/spina/admin/media_picker/select.js.erb +2 -0
  82. data/app/views/spina/admin/media_picker/show.html.haml +1 -0
  83. data/app/views/spina/admin/media_picker/show.js.erb +1 -1
  84. data/app/views/spina/admin/pages/_form.html.haml +9 -7
  85. data/app/views/spina/admin/pages/_form_advanced.html.haml +19 -17
  86. data/app/views/spina/admin/pages/_form_page_content.html.haml +14 -13
  87. data/app/views/spina/admin/pages/_form_page_seo.html.haml +15 -4
  88. data/app/views/spina/admin/pages/_page.html.haml +5 -3
  89. data/app/views/spina/admin/pages/_page_nested_list.html.haml +2 -3
  90. data/app/views/spina/admin/pages/index.html.haml +5 -4
  91. data/app/views/spina/admin/parts/attachments/_form.html.haml +6 -0
  92. data/app/views/spina/admin/parts/image_collections/_fields.html.haml +7 -0
  93. data/app/views/spina/admin/parts/image_collections/_form.html.haml +12 -0
  94. data/app/views/spina/admin/parts/images/_form.html.haml +18 -0
  95. data/app/views/spina/admin/parts/lines/_form.html.haml +2 -0
  96. data/app/views/spina/admin/parts/multi_lines/_form.html.haml +2 -0
  97. data/app/views/spina/admin/parts/options/_form.html.haml +3 -0
  98. data/app/views/spina/admin/parts/repeaters/_fields.html.haml +15 -0
  99. data/app/views/spina/admin/parts/repeaters/_form.html.haml +17 -0
  100. data/app/views/spina/admin/parts/texts/_form.html.haml +6 -0
  101. data/app/views/spina/admin/resources/edit.html.haml +28 -12
  102. data/app/views/spina/admin/resources/show.html.haml +6 -3
  103. data/app/views/spina/admin/shared/_notifications.html.haml +4 -4
  104. data/config/locales/TH.yml +9 -0
  105. data/config/locales/bg.yml +9 -0
  106. data/config/locales/de.yml +9 -0
  107. data/config/locales/en.yml +9 -0
  108. data/config/locales/es.yml +9 -0
  109. data/config/locales/fr.yml +9 -0
  110. data/config/locales/id.yml +9 -0
  111. data/config/locales/it.yml +9 -0
  112. data/config/locales/nl.yml +9 -0
  113. data/config/locales/pl.yml +23 -14
  114. data/config/locales/pt-BR.yml +9 -0
  115. data/config/locales/ro.yml +9 -0
  116. data/config/locales/ru.yml +9 -0
  117. data/config/locales/sv.yml +9 -0
  118. data/config/locales/tr.yml +9 -0
  119. data/config/locales/zh-CN.yml +9 -0
  120. data/config/routes.rb +13 -0
  121. data/db/migrate/11_create_spina_resources.rb +0 -2
  122. data/db/migrate/12_add_url_title_to_spina_page_translations.rb +5 -0
  123. data/db/migrate/13_add_json_attributes_to_spina_accounts.rb +5 -0
  124. data/db/migrate/14_add_json_attributes_to_spina_pages.rb +5 -0
  125. data/db/migrate/15_add_slug_to_spina_resources.rb +5 -0
  126. data/lib/generators/spina/templates/app/views/demo/pages/demo.html.haml +13 -21
  127. data/lib/generators/spina/templates/app/views/demo/pages/homepage.html.haml +1 -1
  128. data/lib/generators/spina/templates/config/initializers/mobility.rb +13 -4
  129. data/lib/generators/spina/templates/config/initializers/spina.rb +4 -0
  130. data/lib/generators/spina/templates/config/initializers/themes/default.rb +7 -7
  131. data/lib/generators/spina/templates/config/initializers/themes/demo.rb +32 -44
  132. data/lib/spina.rb +18 -3
  133. data/lib/spina/attr_json_spina_parts_model.rb +29 -0
  134. data/lib/spina/engine.rb +14 -6
  135. data/lib/spina/part.rb +19 -0
  136. data/lib/spina/theme.rb +1 -1
  137. data/lib/spina/version.rb +1 -1
  138. data/lib/tasks/spina_tasks.rake +1 -1
  139. data/vendor/assets/javascripts/spina/sortable.js +2 -2
  140. metadata +73 -51
  141. data/app/assets/javascripts/spina/admin/account.coffee +0 -16
  142. data/app/models/concerns/spina/image_collectable.rb +0 -23
  143. data/app/models/concerns/spina/optionable.rb +0 -12
  144. data/app/models/concerns/spina/part.rb +0 -38
  145. data/app/models/spina/attachment_collection.rb +0 -20
  146. data/app/models/spina/image_collection.rb +0 -23
  147. data/app/models/spina/image_collections_image.rb +0 -6
  148. data/app/models/spina/layout_part.rb +0 -22
  149. data/app/models/spina/line.rb +0 -10
  150. data/app/models/spina/option.rb +0 -17
  151. data/app/models/spina/page_part.rb +0 -22
  152. data/app/models/spina/structure.rb +0 -14
  153. data/app/models/spina/structure_item.rb +0 -22
  154. data/app/models/spina/structure_part.rb +0 -20
  155. data/app/models/spina/text.rb +0 -10
  156. data/app/views/dummy/show.html.haml +0 -1
  157. data/app/views/spina/admin/attachments/_attachment_collection.html.haml +0 -2
  158. data/app/views/spina/admin/attachments/_select.html.haml +0 -17
  159. data/app/views/spina/admin/attachments/_select_collection.html.haml +0 -18
  160. data/app/views/spina/admin/attachments/insert.js.erb +0 -5
  161. data/app/views/spina/admin/attachments/insert_collection.js.coffee +0 -7
  162. data/app/views/spina/admin/attachments/select.js.erb +0 -2
  163. data/app/views/spina/admin/attachments/select_collection.js.erb +0 -2
  164. data/app/views/spina/admin/image_collections/_image_collection.html.haml +0 -5
  165. data/app/views/spina/admin/layout_partables/lines/_form.html.haml +0 -2
  166. data/app/views/spina/admin/partables/attachment_collections/_form.html.haml +0 -14
  167. data/app/views/spina/admin/partables/attachments/_form.html.haml +0 -13
  168. data/app/views/spina/admin/partables/image_collections/_form.html.haml +0 -16
  169. data/app/views/spina/admin/partables/images/_form.html.haml +0 -21
  170. data/app/views/spina/admin/partables/lines/_form.html.haml +0 -5
  171. data/app/views/spina/admin/partables/options/_form.html.haml +0 -7
  172. data/app/views/spina/admin/partables/photo_collections/_form.html.haml +0 -4
  173. data/app/views/spina/admin/partables/photos/_form.html.haml +0 -4
  174. data/app/views/spina/admin/partables/structures/_form.html.haml +0 -21
  175. data/app/views/spina/admin/partables/texts/_form.html.haml +0 -8
  176. data/app/views/spina/admin/structure_items/_fields.html.haml +0 -15
  177. data/app/views/spina/admin/structure_partables/attachment_collections/_form.html.haml +0 -14
  178. data/app/views/spina/admin/structure_partables/attachments/_form.html.haml +0 -13
@@ -1,20 +1,18 @@
1
- module Spina
2
- class PagesController < Spina::ApplicationController
3
- include Spina::Frontend
1
+ class Spina::PagesController < Spina::ApplicationController
2
+ include Spina::Frontend
4
3
 
5
- before_action :current_spina_user_can_view_page?, except: [:robots]
4
+ before_action :current_spina_user_can_view_page?, except: [:robots]
6
5
 
7
- helper_method :page
6
+ helper_method :page
8
7
 
9
- def homepage
10
- render_with_template(page)
11
- end
8
+ def homepage
9
+ render_with_template(page)
10
+ end
12
11
 
13
- private
12
+ private
14
13
 
15
- def current_spina_user_can_view_page?
16
- raise ActiveRecord::RecordNotFound unless current_spina_user.present? || page.live?
17
- end
14
+ def current_spina_user_can_view_page?
15
+ raise ActiveRecord::RecordNotFound unless current_spina_user.present? || page.live?
16
+ end
18
17
 
19
- end
20
18
  end
@@ -1,8 +1,8 @@
1
- module Spina
2
- class SitemapsController < Spina::ApplicationController
3
- def show
4
- I18n.locale = I18n.default_locale
5
- @pages = Page.live.sorted
6
- end
1
+ class Spina::SitemapsController < Spina::ApplicationController
2
+
3
+ def show
4
+ I18n.locale = I18n.default_locale
5
+ @pages = Spina::Page.live.sorted
7
6
  end
7
+
8
8
  end
@@ -1,34 +1,36 @@
1
1
  module Spina
2
2
  module Admin
3
3
  module PagesHelper
4
- def link_to_add_structure_item_fields(f, &block)
5
- item = StructureItem.new
6
- fields = f.fields_for(:structure_items, item, child_index: item.object_id) do |builder|
7
- build_structure_parts(f.object.page_part.name, item)
8
- render("spina/admin/structure_items/fields", f: builder)
4
+
5
+ def link_to_add_repeater_fields(f)
6
+ repeater_content = Spina::Parts::RepeaterContent.new(name: f.object.name, title: f.object.title)
7
+ fields = f.fields_for(:content, [repeater_content], child_index: repeater_content.object_id) do |builder|
8
+ render("spina/admin/parts/repeaters/fields", f: builder)
9
9
  end
10
- link_to '#', class: "add_structure_item_fields button button-link", data: {id: item.object_id, fields: fields.gsub("\n", "")} do
10
+ link_to '#', class: "add_structure_item_fields button button-link", data: {id: repeater_content.object_id, fields: fields.gsub("\n", "")} do
11
11
  icon('plus')
12
12
  end
13
13
  end
14
14
 
15
- def build_structure_parts(name, item)
16
- structure = current_theme.structures.find { |structure| structure[:name] == name }
17
- return item.parts unless structure.present?
18
- structure[:structure_parts].map do |attributes|
19
- part = item.parts.where(name: attributes[:name]).first_or_initialize(attributes)
20
- part.partable = part.partable_type.constantize.new if part.partable.blank?
21
- part.options = attributes[:options]
22
- part
15
+ def data_attrs_for_image_collection(f)
16
+ image = Spina::Parts::Image.new
17
+ fields = f.fields_for(:images, [image], child_index: image.object_id) do |builder|
18
+ render("spina/admin/parts/image_collections/fields", f: builder)
23
19
  end
20
+ {fields: fields.gsub("\n", ""), id: image.object_id}
24
21
  end
25
22
 
26
- def partable_partial_namespace(partable)
27
- partable_type_partial_namespace(partable.model_name.to_s)
23
+ def build_parts(partable, parts)
24
+ I18n.with_locale(@locale) do
25
+ parts.map do |part|
26
+ part_attributes = current_theme.parts.find{|p|p[:name].to_s == part.to_s}
27
+ partable.part(part_attributes)
28
+ end
29
+ end
28
30
  end
29
31
 
30
- def partable_type_partial_namespace(partable_type)
31
- partable_type.tableize.sub(/\Aspina\//, '')
32
+ def parts_partial_namespace(part_type)
33
+ part_type.tableize.sub(/\Aspina\/parts\//, '')
32
34
  end
33
35
 
34
36
  def flatten_nested_hash(hash)
@@ -36,14 +38,14 @@ module Spina
36
38
  end
37
39
 
38
40
  def page_ancestry_options(page)
39
- pages = Spina::Page.active.regular_pages
41
+ pages = Spina::Page.active.regular_pages.includes(:translations)
40
42
  pages = pages.where.not(id: page.subtree.ids) unless page.new_record? || !page.methods.include?(:subtree)
41
43
 
42
44
  (flatten_nested_hash(pages.arrange(order: :position)).map do |page|
43
45
  next if page.depth >= Spina.config.max_page_depth - 1
44
46
  page_menu_title = page.depth.zero? ? page.menu_title : " #{page.menu_title}".indent(page.depth, '-')
45
47
  [page_menu_title, page.id]
46
- end << [page.parent&.menu_title, page&.parent_id].compact).uniq.compact
48
+ end << [page.parent&.menu_title, page&.parent_id].compact).map(&:presence).uniq.compact
47
49
  end
48
50
 
49
51
  def option_label(part, value)
@@ -1,5 +1,5 @@
1
1
  module Spina
2
- module FilesHelper
2
+ module AttachmentsHelper
3
3
 
4
4
  def file_url(file)
5
5
  main_app.rails_service_blob_path(file.signed_id, file.filename)
@@ -1,24 +1,15 @@
1
1
  module Spina
2
2
  module ImagesHelper
3
3
 
4
- # We wrap some Rails logic inside our own helper method
5
- # because the resolve directive in ActiveStorage's routes
6
- # doesn't work outside the main app in 5.2.0.rc2
7
- def variant(image, options)
8
- if image.attached? && image.variable?
9
- variant = image.variant(options)
10
- main_app.rails_blob_representation_path(variant.blob.signed_id, variant.variation.key, variant.blob.filename)
11
- elsif image.image?
12
- # Allows SVGs to be displayed as they are not variable. Requires:
13
- # - https://github.com/Dreamersoul/administrate-field-active_storage/issues/36#issuecomment-608446819 to be applied
14
- #
15
- # Additionally, https://github.com/hopsoft/active_storage_svg_sanitizer/
16
- # should be added if users are able to upload their own SVG content due to:
17
- # https://github.com/rails/rails/issues/34665#issuecomment-445888009
18
- main_app.url_for(image)
19
- else
20
- "https://placehold.it/100x100.png"
21
- end
4
+ def thumbnail_url(image)
5
+ return "" if image.nil?
6
+ main_app.url_for(image.variant(resize: "400x300^", crop: "400x300+0+0"))
22
7
  end
8
+
9
+ def embedded_image_url(image)
10
+ return "" if image.nil?
11
+ main_app.url_for(image.variant(resize: Spina.config.embedded_image_size))
12
+ end
13
+
23
14
  end
24
15
  end
@@ -1,17 +1,36 @@
1
1
  module Spina
2
2
  module PagesHelper
3
3
 
4
- def content(part_name)
5
- current_page.content(part_name)
4
+ def content(part_name = nil)
5
+ Current.page.content(part_name)
6
6
  end
7
7
 
8
8
  def has_content?(part_name)
9
- current_page.has_content?(part_name)
9
+ Current.page.has_content?(part_name)
10
+ end
11
+
12
+ def repeater(part)
13
+ part = Current.page.find_part(part)&.content unless part.is_a? Array
14
+ part&.each do |repeater_content|
15
+ repeater_content.view_context = self
16
+ yield(repeater_content)
17
+ end
18
+ end
19
+
20
+ def images(part)
21
+ part = Current.page.find_part(part)&.content unless part.is_a? Array
22
+ part&.each do |image|
23
+ yield(image)
24
+ end
10
25
  end
11
26
 
12
27
  def current_page
13
28
  Current.page
14
29
  end
15
30
 
31
+ def current_account
32
+ Current.account
33
+ end
34
+
16
35
  end
17
- end
36
+ end
@@ -2,23 +2,35 @@ module Spina
2
2
  module Partable
3
3
  extend ActiveSupport::Concern
4
4
 
5
+ attr_accessor :view_context
6
+
5
7
  included do
8
+
6
9
  def part(attributes)
7
- part = parts.where(name: attributes[:name]).first_or_initialize(attributes)
8
- part.partable = part.partable_type.constantize.new if part.partable.blank?
9
- part.options = attributes[:options]
10
+ part = find_part(attributes[:name]) || attributes[:part_type].constantize.new
11
+
12
+ # Copy all attributes to part
13
+ %w(name title options).each do |attribute|
14
+ part.public_send("#{attribute}=", attributes[attribute.to_sym]) if part.respond_to?(attribute)
15
+ end
16
+
10
17
  part
11
18
  end
12
19
 
13
20
  def has_content?(name)
14
- content(name).present?
21
+ find_part(name).present?
15
22
  end
16
23
 
17
- def content(name)
18
- part = parts.find_by(name: name)
19
- part.try(:content)
24
+ def content(name = nil)
25
+ name ? find_part(name)&.content : content_presenter
20
26
  end
21
27
 
28
+ private
29
+
30
+ def content_presenter
31
+ @content_presenter ||= ContentPresenter.new(view_context, self)
32
+ end
33
+
22
34
  end
23
35
  end
24
36
  end
@@ -0,0 +1,19 @@
1
+ module Spina
2
+ module TranslatedContent
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+
7
+ # Store each locale's content in [locale]_content as an array of parts
8
+ Spina.config.locales.each do |locale|
9
+ attr_json "#{locale}_content".to_sym, AttrJson::Type::SpinaPartsModel.new, array: true, default: -> { [] }
10
+ attr_json_accepts_nested_attributes_for "#{locale}_content".to_sym
11
+ end
12
+
13
+ def find_part(name)
14
+ send("#{I18n.locale}_content").find{|part| part.name.to_s == name.to_s}
15
+ end
16
+
17
+ end
18
+ end
19
+ end
@@ -1,25 +1,18 @@
1
1
  module Spina
2
2
  class Account < ApplicationRecord
3
+ include AttrJson::Record
4
+ include AttrJson::NestedAttributes
3
5
  include Partable
6
+ include TranslatedContent
4
7
 
5
8
  serialize :preferences
6
9
 
7
- has_many :layout_parts, dependent: :destroy
8
- accepts_nested_attributes_for :layout_parts, allow_destroy: true
9
-
10
- alias_attribute :layout_part, :part
11
- alias_attribute :parts, :layout_parts
12
-
13
10
  after_save :bootstrap_website
14
11
 
15
12
  def to_s
16
13
  name
17
14
  end
18
15
 
19
- def content(layout_part)
20
- layout_parts.find_by(name: layout_part).try(:content)
21
- end
22
-
23
16
  def self.serialized_attr_accessor(*args)
24
17
  args.each do |method_name|
25
18
  define_method method_name do
@@ -2,10 +2,6 @@ module Spina
2
2
  class Attachment < ApplicationRecord
3
3
  has_one_attached :file
4
4
 
5
- has_one :page_part, as: :page_partable
6
- has_many :structure_parts, as: :structure_partable
7
- has_and_belongs_to_many :attachment_collections, join_table: 'spina_attachment_collections_attachments'
8
-
9
5
  attr_accessor :_destroy
10
6
 
11
7
  scope :sorted, -> { order('file ASC') }
@@ -1,5 +1,6 @@
1
1
  module Spina
2
2
  class Current < ActiveSupport::CurrentAttributes
3
3
  attribute :page
4
+ attribute :account
4
5
  end
5
6
  end
@@ -4,11 +4,7 @@ module Spina
4
4
 
5
5
  has_one_attached :file
6
6
 
7
- has_many :page_parts, as: :page_partable
8
- has_many :structure_parts, as: :structure_partable
9
-
10
7
  scope :sorted, -> { order('created_at DESC') }
11
- scope :sorted_by_image_collection, -> { order('position') }
12
8
 
13
9
  def name
14
10
  file.try(:filename).to_s
@@ -1,16 +1,14 @@
1
1
  module Spina
2
2
  class Page < ApplicationRecord
3
3
  extend Mobility
4
+ include AttrJson::Record
5
+ include AttrJson::NestedAttributes
4
6
  include Partable
7
+ include TranslatedContent
5
8
 
6
9
  # Stores the old path when generating a new materialized_path
7
10
  attr_accessor :old_path
8
11
 
9
- # Page contains multiple parts called PageParts
10
- has_many :page_parts, dependent: :destroy, inverse_of: :page
11
- alias_attribute :parts, :page_parts
12
- accepts_nested_attributes_for :page_parts, allow_destroy: true
13
-
14
12
  # Orphaned pages are adopted by parent pages if available, otherwise become root
15
13
  has_ancestry orphan_strategy: :adopt
16
14
 
@@ -28,10 +26,12 @@ module Spina
28
26
  scope :live, -> { active.where(draft: false) }
29
27
  scope :in_menu, -> { where(show_in_menu: true) }
30
28
 
29
+ # Copy resource from parent
30
+ before_save :set_resource_from_parent, if: -> { parent.present? }
31
+
31
32
  # Save children to update all materialized_paths
32
33
  after_save :save_children
33
34
  after_save :touch_navigations
34
- after_save -> { page_parts.each(&:save) }
35
35
 
36
36
  # Create a 301 redirect if materialized_path changed
37
37
  after_save :rewrite_rule
@@ -41,7 +41,7 @@ module Spina
41
41
 
42
42
  translates :title, fallbacks: true
43
43
  translates :description, :materialized_path
44
- translates :menu_title, :seo_title, default: -> { title }
44
+ translates :menu_title, :seo_title, :url_title, default: -> { title }
45
45
 
46
46
  def to_s
47
47
  name
@@ -51,8 +51,8 @@ module Spina
51
51
  id
52
52
  end
53
53
 
54
- def url_title
55
- title.try(:parameterize)
54
+ def slug
55
+ url_title&.parameterize
56
56
  end
57
57
 
58
58
  def custom_page?
@@ -74,12 +74,17 @@ module Spina
74
74
  def next_sibling
75
75
  siblings.where('position > ?', position).sorted.first
76
76
  end
77
-
77
+
78
78
  def set_materialized_path
79
79
  self.old_path = materialized_path
80
80
  self.materialized_path = localized_materialized_path
81
- self.materialized_path = localized_materialized_path + "-#{Page.i18n.where(materialized_path: materialized_path).count}" if Page.i18n.where(materialized_path: materialized_path).where.not(id: id).count > 0
82
- materialized_path
81
+
82
+ # Append counter to duplicate materialized_path
83
+ i = 0
84
+ while duplicate_materialized_path?
85
+ i += 1
86
+ self.materialized_path = localized_materialized_path.concat("-#{i}")
87
+ end
83
88
  end
84
89
 
85
90
  def cache_key
@@ -91,12 +96,12 @@ module Spina
91
96
  theme.view_templates.find { |template| template[:name] == view_template_name }
92
97
  end
93
98
 
94
- def view_template_page_parts(theme)
95
- theme.page_parts.select { |page_part| page_part[:name].in? view_template_config(theme)[:page_parts] }
96
- end
97
-
98
99
  private
99
100
 
101
+ def set_resource_from_parent
102
+ self.resource_id = parent.resource_id
103
+ end
104
+
100
105
  def touch_navigations
101
106
  navigations.update_all(updated_at: Time.zone.now)
102
107
  end
@@ -114,11 +119,14 @@ module Spina
114
119
  end
115
120
 
116
121
  def generate_materialized_path
117
- if root?
118
- name == 'homepage' ? '' : "#{url_title}"
119
- else
120
- ancestors.collect(&:url_title).append(url_title).join('/')
121
- end
122
+ path_fragments = [resource&.slug]
123
+ path_fragments.append *ancestors.collect(&:slug)
124
+ path_fragments.append(slug) unless name == 'homepage'
125
+ path_fragments.compact.map(&:parameterize).join('/')
126
+ end
127
+
128
+ def duplicate_materialized_path?
129
+ self.class.where.not(id: id).i18n.where(materialized_path: materialized_path).exists?
122
130
  end
123
131
 
124
132
  end
@@ -0,0 +1,22 @@
1
+ module Spina
2
+ module Parts
3
+ class Attachment < Base
4
+ attr_json :attachment_id, :integer, default: nil
5
+ attr_json :signed_blob_id, :string, default: nil
6
+ attr_json :filename, :string, default: ""
7
+
8
+ def content
9
+ self
10
+ end
11
+
12
+ def present?
13
+ signed_blob_id.present?
14
+ end
15
+
16
+ def signed_id
17
+ signed_blob_id
18
+ end
19
+
20
+ end
21
+ end
22
+ end