spina 1.1.2 → 2.0.0.beta

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 (199) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/fonts/spina/ics_spina.eot +0 -0
  3. data/app/assets/fonts/spina/ics_spina.svg +285 -267
  4. data/app/assets/fonts/spina/ics_spina.ttf +0 -0
  5. data/app/assets/fonts/spina/ics_spina.woff +0 -0
  6. data/app/assets/fonts/spina/ics_spina.woff2 +0 -0
  7. data/app/assets/icons/spina/bold.svg +1 -1
  8. data/app/assets/icons/spina/code.svg +1 -1
  9. data/app/assets/icons/spina/image.svg +1 -10
  10. data/app/assets/icons/spina/indent.svg +1 -0
  11. data/app/assets/icons/spina/italic.svg +1 -1
  12. data/app/assets/icons/spina/link.svg +1 -1
  13. data/app/assets/icons/spina/list-ol.svg +1 -0
  14. data/app/assets/icons/spina/list.svg +1 -0
  15. data/app/assets/icons/spina/outdent.svg +1 -0
  16. data/app/assets/icons/spina/preview/icons_spina-preview.html +0 -2
  17. data/app/assets/icons/spina/preview/ics_spina-preview.html +121 -3
  18. data/app/assets/icons/spina/quote-right.svg +1 -0
  19. data/app/assets/icons/spina/redo.svg +1 -0
  20. data/app/assets/icons/spina/strikethrough.svg +1 -0
  21. data/app/assets/icons/spina/undo.svg +1 -0
  22. data/app/assets/images/spina/favicon.png +0 -0
  23. data/app/assets/javascripts/spina/admin/application.js +1 -2
  24. data/app/assets/javascripts/spina/admin/controllers/attachment_picker_controller.js +15 -0
  25. data/app/assets/javascripts/spina/admin/controllers/image_form_controller.js +5 -4
  26. data/app/assets/javascripts/spina/admin/controllers/infinite_scroll_controller.js +20 -22
  27. data/app/assets/javascripts/spina/admin/controllers/media_picker_controller.js +171 -0
  28. data/app/assets/javascripts/spina/admin/controllers/modal_controller.js +18 -0
  29. data/app/assets/javascripts/spina/admin/controllers/repeater_form_controller.js +38 -0
  30. data/app/assets/javascripts/spina/admin/media_gallery.coffee +0 -3
  31. data/app/assets/javascripts/spina/admin/notifications.coffee +1 -1
  32. data/app/assets/javascripts/spina/admin/pages.coffee.erb +3 -20
  33. data/app/assets/javascripts/spina/admin/scaffold.coffee +1 -3
  34. data/app/assets/javascripts/spina/admin/trix.coffee.erb +14 -18
  35. data/app/assets/stylesheets/spina.sass +10 -8
  36. data/app/assets/stylesheets/spina/_buttons.sass +57 -31
  37. data/app/assets/stylesheets/spina/_configuration.sass.erb +1 -1
  38. data/app/assets/stylesheets/spina/_forms.sass +72 -77
  39. data/app/assets/stylesheets/spina/_gallery.sass +22 -4
  40. data/app/assets/stylesheets/spina/_ics_spina.scss +8 -0
  41. data/app/assets/stylesheets/spina/_media_picker.sass +133 -0
  42. data/app/assets/stylesheets/spina/_modal.sass +35 -2
  43. data/app/assets/stylesheets/spina/_notifications.sass +20 -14
  44. data/app/assets/stylesheets/spina/_pages.sass +131 -0
  45. data/app/assets/stylesheets/spina/_sortable_lists.sass +14 -8
  46. data/app/assets/stylesheets/spina/_tables.sass +7 -4
  47. data/app/assets/stylesheets/spina/_trix_custom.sass +29 -32
  48. data/app/controllers/concerns/spina/current_methods.rb +26 -0
  49. data/app/controllers/concerns/spina/frontend.rb +9 -0
  50. data/app/controllers/spina/admin/accounts_controller.rb +9 -13
  51. data/app/controllers/spina/admin/admin_controller.rb +5 -1
  52. data/app/controllers/spina/admin/attachments_controller.rb +0 -22
  53. data/app/controllers/spina/admin/images_controller.rb +1 -1
  54. data/app/controllers/spina/admin/media_picker_controller.rb +18 -19
  55. data/app/controllers/spina/admin/pages_controller.rb +3 -7
  56. data/app/controllers/spina/admin/resources_controller.rb +9 -4
  57. data/app/controllers/spina/application_controller.rb +6 -23
  58. data/app/controllers/spina/pages_controller.rb +11 -13
  59. data/app/controllers/spina/sitemaps_controller.rb +6 -6
  60. data/app/helpers/spina/admin/pages_helper.rb +22 -20
  61. data/app/helpers/spina/{files_helper.rb → attachments_helper.rb} +1 -1
  62. data/app/helpers/spina/images_helper.rb +9 -10
  63. data/app/helpers/spina/pages_helper.rb +23 -4
  64. data/app/models/concerns/spina/partable.rb +19 -7
  65. data/app/models/concerns/spina/translated_content.rb +19 -0
  66. data/app/models/spina/account.rb +3 -10
  67. data/app/models/spina/attachment.rb +0 -4
  68. data/app/models/spina/current.rb +1 -0
  69. data/app/models/spina/image.rb +0 -4
  70. data/app/models/spina/page.rb +31 -23
  71. data/app/models/spina/parts/attachment.rb +22 -0
  72. data/app/models/spina/parts/base.rb +12 -0
  73. data/app/models/spina/parts/image.rb +39 -0
  74. data/app/models/spina/parts/image_collection.rb +23 -0
  75. data/app/models/spina/parts/image_variant.rb +19 -0
  76. data/app/models/spina/parts/line.rb +7 -0
  77. data/app/models/spina/parts/multi_line.rb +7 -0
  78. data/app/models/spina/parts/option.rb +13 -0
  79. data/app/models/spina/parts/repeater.rb +10 -0
  80. data/app/models/spina/parts/repeater_content.rb +15 -0
  81. data/app/models/spina/parts/text.rb +7 -0
  82. data/app/models/spina/resource.rb +4 -12
  83. data/app/presenters/spina/content_presenter.rb +51 -0
  84. data/app/presenters/spina/menu_presenter.rb +2 -1
  85. data/app/views/layouts/spina/admin/admin.html.haml +3 -7
  86. data/app/views/layouts/spina/admin/pages.html.haml +5 -4
  87. data/app/views/layouts/spina/login.html.haml +0 -3
  88. data/app/views/spina/admin/accounts/_form.html.haml +13 -7
  89. data/app/views/spina/admin/accounts/analytics.html.haml +5 -3
  90. data/app/views/spina/admin/accounts/social.html.haml +13 -7
  91. data/app/views/spina/admin/accounts/style.html.haml +26 -21
  92. data/app/views/spina/admin/attachments/index.html.haml +1 -1
  93. data/app/views/spina/admin/images/_image.html.haml +1 -1
  94. data/app/views/spina/admin/images/index.html.haml +7 -5
  95. data/app/views/spina/admin/media_folders/_media_folder.html.haml +1 -1
  96. data/app/views/spina/admin/media_folders/show.html.haml +2 -2
  97. data/app/views/spina/admin/media_picker/_image.html.haml +3 -0
  98. data/app/views/spina/admin/media_picker/_media_picker.html.haml +32 -0
  99. data/app/views/spina/admin/media_picker/_media_picker_grid.html.haml +25 -0
  100. data/app/views/spina/admin/media_picker/_modal.html.haml +1 -1
  101. data/app/views/spina/admin/media_picker/infinite_scroll.js.erb +4 -4
  102. data/app/views/spina/admin/media_picker/select.js.erb +2 -0
  103. data/app/views/spina/admin/media_picker/show.html.haml +1 -0
  104. data/app/views/spina/admin/media_picker/show.js.erb +1 -1
  105. data/app/views/spina/admin/pages/_form.html.haml +9 -7
  106. data/app/views/spina/admin/pages/_form_advanced.html.haml +19 -17
  107. data/app/views/spina/admin/pages/_form_page_content.html.haml +14 -13
  108. data/app/views/spina/admin/pages/_form_page_seo.html.haml +15 -4
  109. data/app/views/spina/admin/pages/_page.html.haml +5 -3
  110. data/app/views/spina/admin/pages/_page_nested_list.html.haml +2 -3
  111. data/app/views/spina/admin/pages/index.html.haml +5 -4
  112. data/app/views/spina/admin/parts/attachments/_form.html.haml +6 -0
  113. data/app/views/spina/admin/parts/image_collections/_fields.html.haml +7 -0
  114. data/app/views/spina/admin/parts/image_collections/_form.html.haml +12 -0
  115. data/app/views/spina/admin/parts/images/_form.html.haml +18 -0
  116. data/app/views/spina/admin/parts/lines/_form.html.haml +2 -0
  117. data/app/views/spina/admin/parts/multi_lines/_form.html.haml +2 -0
  118. data/app/views/spina/admin/parts/options/_form.html.haml +3 -0
  119. data/app/views/spina/admin/parts/repeaters/_fields.html.haml +15 -0
  120. data/app/views/spina/admin/parts/repeaters/_form.html.haml +17 -0
  121. data/app/views/spina/admin/parts/texts/_form.html.haml +6 -0
  122. data/app/views/spina/admin/resources/edit.html.haml +28 -12
  123. data/app/views/spina/admin/resources/show.html.haml +6 -3
  124. data/app/views/spina/admin/shared/_notifications.html.haml +4 -4
  125. data/config/locales/TH.yml +10 -0
  126. data/config/locales/bg.yml +10 -0
  127. data/config/locales/de.yml +10 -0
  128. data/config/locales/en.yml +10 -0
  129. data/config/locales/es.yml +39 -29
  130. data/config/locales/fr.yml +10 -0
  131. data/config/locales/id.yml +10 -0
  132. data/config/locales/it.yml +10 -0
  133. data/config/locales/nl.yml +10 -0
  134. data/config/locales/pl.yml +24 -14
  135. data/config/locales/pt-BR.yml +10 -0
  136. data/config/locales/ro.yml +10 -0
  137. data/config/locales/ru.yml +10 -0
  138. data/config/locales/sv.yml +10 -0
  139. data/config/locales/tr.yml +10 -0
  140. data/config/locales/zh-CN.yml +10 -0
  141. data/config/routes.rb +13 -0
  142. data/db/migrate/11_create_spina_resources.rb +0 -2
  143. data/db/migrate/12_add_url_title_to_spina_page_translations.rb +5 -0
  144. data/db/migrate/13_add_json_attributes_to_spina_accounts.rb +5 -0
  145. data/db/migrate/14_add_json_attributes_to_spina_pages.rb +5 -0
  146. data/db/migrate/15_add_slug_to_spina_resources.rb +5 -0
  147. data/lib/generators/spina/templates/app/views/demo/pages/demo.html.haml +13 -21
  148. data/lib/generators/spina/templates/app/views/demo/pages/homepage.html.haml +1 -1
  149. data/lib/generators/spina/templates/config/initializers/mobility.rb +13 -4
  150. data/lib/generators/spina/templates/config/initializers/spina.rb +4 -0
  151. data/lib/generators/spina/templates/config/initializers/themes/default.rb +7 -7
  152. data/lib/generators/spina/templates/config/initializers/themes/demo.rb +32 -44
  153. data/lib/spina.rb +18 -3
  154. data/lib/spina/attr_json_spina_parts_model.rb +29 -0
  155. data/lib/spina/engine.rb +14 -6
  156. data/lib/spina/part.rb +19 -0
  157. data/lib/spina/theme.rb +1 -1
  158. data/lib/spina/version.rb +1 -1
  159. data/lib/tasks/spina_tasks.rake +1 -1
  160. data/vendor/assets/javascripts/spina/sortable.js +2 -2
  161. metadata +83 -53
  162. data/app/assets/javascripts/spina/admin/account.coffee +0 -16
  163. data/app/models/concerns/spina/image_collectable.rb +0 -23
  164. data/app/models/concerns/spina/optionable.rb +0 -12
  165. data/app/models/concerns/spina/part.rb +0 -38
  166. data/app/models/spina/attachment_collection.rb +0 -20
  167. data/app/models/spina/image_collection.rb +0 -23
  168. data/app/models/spina/image_collections_image.rb +0 -6
  169. data/app/models/spina/layout_part.rb +0 -22
  170. data/app/models/spina/line.rb +0 -10
  171. data/app/models/spina/option.rb +0 -17
  172. data/app/models/spina/page_part.rb +0 -22
  173. data/app/models/spina/structure.rb +0 -14
  174. data/app/models/spina/structure_item.rb +0 -22
  175. data/app/models/spina/structure_part.rb +0 -20
  176. data/app/models/spina/text.rb +0 -10
  177. data/app/views/dummy/show.html.haml +0 -1
  178. data/app/views/spina/admin/attachments/_attachment_collection.html.haml +0 -2
  179. data/app/views/spina/admin/attachments/_select.html.haml +0 -17
  180. data/app/views/spina/admin/attachments/_select_collection.html.haml +0 -18
  181. data/app/views/spina/admin/attachments/insert.js.erb +0 -5
  182. data/app/views/spina/admin/attachments/insert_collection.js.coffee +0 -7
  183. data/app/views/spina/admin/attachments/select.js.erb +0 -2
  184. data/app/views/spina/admin/attachments/select_collection.js.erb +0 -2
  185. data/app/views/spina/admin/image_collections/_image_collection.html.haml +0 -5
  186. data/app/views/spina/admin/layout_partables/lines/_form.html.haml +0 -2
  187. data/app/views/spina/admin/partables/attachment_collections/_form.html.haml +0 -14
  188. data/app/views/spina/admin/partables/attachments/_form.html.haml +0 -13
  189. data/app/views/spina/admin/partables/image_collections/_form.html.haml +0 -16
  190. data/app/views/spina/admin/partables/images/_form.html.haml +0 -21
  191. data/app/views/spina/admin/partables/lines/_form.html.haml +0 -5
  192. data/app/views/spina/admin/partables/options/_form.html.haml +0 -7
  193. data/app/views/spina/admin/partables/photo_collections/_form.html.haml +0 -4
  194. data/app/views/spina/admin/partables/photos/_form.html.haml +0 -4
  195. data/app/views/spina/admin/partables/structures/_form.html.haml +0 -21
  196. data/app/views/spina/admin/partables/texts/_form.html.haml +0 -8
  197. data/app/views/spina/admin/structure_items/_fields.html.haml +0 -15
  198. data/app/views/spina/admin/structure_partables/attachment_collections/_form.html.haml +0 -14
  199. data/app/views/spina/admin/structure_partables/attachments/_form.html.haml +0 -13
@@ -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,16 +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
- else
12
- "https://placehold.it/100x100.png"
13
- end
4
+ def thumbnail_url(image)
5
+ return "" if image.nil?
6
+ main_app.url_for(image.variant(resize: "400x300^", crop: "400x300+0+0"))
14
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
+
15
14
  end
16
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
@@ -106,19 +111,22 @@ module Spina
106
111
  end
107
112
 
108
113
  def localized_materialized_path
109
- if I18n.locale == I18n.default_locale
114
+ if Mobility.locale == I18n.default_locale
110
115
  generate_materialized_path.prepend('/')
111
116
  else
112
- generate_materialized_path.prepend("/#{I18n.locale}/").gsub(/\/\z/, "")
117
+ generate_materialized_path.prepend("/#{Mobility.locale}/").gsub(/\/\z/, "")
113
118
  end
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
@@ -0,0 +1,12 @@
1
+ module Spina
2
+ module Parts
3
+ class Base
4
+ include AttrJson::Model
5
+
6
+ attr_json_config(unknown_key: :strip)
7
+
8
+ attr_json :title, :string
9
+ attr_json :name, :string
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,39 @@
1
+ module Spina
2
+ module Parts
3
+ class Image < Base
4
+ attr_json :image_id, :integer, default: nil
5
+ attr_json :signed_blob_id, :string, default: nil
6
+ attr_json :alt, :string, default: ""
7
+ attr_json :filename, :string, default: ""
8
+
9
+ def to_s
10
+ alt.presence || filename.presence || Spina::Image.model_name.human
11
+ end
12
+
13
+ def content
14
+ self
15
+ end
16
+
17
+ def svg?
18
+ filename =~ /\.svg\z/
19
+ end
20
+
21
+ def spina_image
22
+ Spina::Image.find_by(id: image_id)
23
+ end
24
+
25
+ def present?
26
+ signed_blob_id.present?
27
+ end
28
+
29
+ def signed_id
30
+ signed_blob_id
31
+ end
32
+
33
+ def variant(options)
34
+ Spina::Parts::ImageVariant.new(self, options)
35
+ end
36
+
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,23 @@
1
+ module Spina
2
+ module Parts
3
+ class ImageCollection < Base
4
+ include AttrJson::NestedAttributes
5
+
6
+ attr_json :images, Image.to_type, array: true
7
+ attr_json_accepts_nested_attributes_for :images
8
+
9
+ def to_s
10
+ Spina::Parts::ImageCollection.model_name.human
11
+ end
12
+
13
+ def content
14
+ (images || [])
15
+ end
16
+
17
+ def image_ids
18
+ content.map(&:image_id)
19
+ end
20
+
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,19 @@
1
+ module Spina
2
+ module Parts
3
+ class ImageVariant
4
+ attr_reader :blob
5
+
6
+ def initialize(image, options)
7
+ @blob = image
8
+ @options = options
9
+ end
10
+
11
+ def variation
12
+ OpenStruct.new({
13
+ key: ActiveStorage::Variation.encode(@options)
14
+ })
15
+ end
16
+
17
+ end
18
+ end
19
+ end