alchemy_cms 5.2.4 → 6.0.0.b1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (269) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +6 -14
  3. data/.gitignore +0 -1
  4. data/.hound.yml +1 -1
  5. data/.rubocop.yml +46 -4
  6. data/CHANGELOG.md +80 -25
  7. data/Gemfile +4 -2
  8. data/README.md +5 -2
  9. data/alchemy_cms.gemspec +78 -65
  10. data/app/assets/javascripts/alchemy/admin.js +0 -2
  11. data/app/assets/javascripts/alchemy/alchemy.base.js.coffee +0 -27
  12. data/app/assets/javascripts/alchemy/alchemy.confirm_dialog.js.coffee +2 -1
  13. data/app/assets/javascripts/alchemy/alchemy.dialog.js.coffee +1 -1
  14. data/app/assets/javascripts/alchemy/alchemy.dragndrop.js.coffee +0 -25
  15. data/app/assets/javascripts/alchemy/alchemy.element_editors.js.coffee +1 -1
  16. data/app/assets/javascripts/alchemy/alchemy.elements_window.js.coffee +2 -0
  17. data/app/assets/javascripts/alchemy/alchemy.fixed_elements.js +1 -1
  18. data/app/assets/javascripts/alchemy/alchemy.gui.js.coffee +3 -1
  19. data/app/assets/javascripts/alchemy/alchemy.image_overlay.coffee +1 -1
  20. data/app/assets/javascripts/alchemy/alchemy.link_dialog.js.coffee +40 -27
  21. data/app/assets/javascripts/alchemy/templates/node_folder.hbs +1 -1
  22. data/app/assets/stylesheets/alchemy/admin.scss +1 -1
  23. data/app/assets/stylesheets/alchemy/archive.scss +4 -4
  24. data/app/assets/stylesheets/alchemy/buttons.scss +0 -4
  25. data/app/assets/stylesheets/alchemy/elements.scss +73 -61
  26. data/app/assets/stylesheets/alchemy/images.scss +8 -0
  27. data/app/assets/stylesheets/alchemy/node-select.scss +4 -3
  28. data/app/assets/stylesheets/alchemy/page-select.scss +1 -0
  29. data/app/assets/stylesheets/tinymce/skins/alchemy/skin.min.css.scss +6 -6
  30. data/app/controllers/alchemy/admin/attachments_controller.rb +6 -2
  31. data/app/controllers/alchemy/admin/base_controller.rb +5 -7
  32. data/app/controllers/alchemy/admin/elements_controller.rb +58 -34
  33. data/app/controllers/alchemy/admin/essence_audios_controller.rb +30 -0
  34. data/app/controllers/alchemy/admin/essence_files_controller.rb +0 -14
  35. data/app/controllers/alchemy/admin/essence_pictures_controller.rb +8 -79
  36. data/app/controllers/alchemy/admin/essence_videos_controller.rb +33 -0
  37. data/app/controllers/alchemy/admin/ingredients_controller.rb +30 -0
  38. data/app/controllers/alchemy/admin/layoutpages_controller.rb +0 -1
  39. data/app/controllers/alchemy/admin/pages_controller.rb +6 -13
  40. data/app/controllers/alchemy/admin/pictures_controller.rb +35 -9
  41. data/app/controllers/alchemy/api/elements_controller.rb +10 -5
  42. data/app/controllers/alchemy/api/pages_controller.rb +2 -4
  43. data/app/controllers/concerns/alchemy/admin/archive_overlay.rb +13 -3
  44. data/app/controllers/concerns/alchemy/admin/crop_action.rb +26 -0
  45. data/app/decorators/alchemy/element_editor.rb +23 -1
  46. data/app/decorators/alchemy/ingredient_editor.rb +154 -0
  47. data/app/helpers/alchemy/admin/elements_helper.rb +1 -0
  48. data/app/helpers/alchemy/admin/essences_helper.rb +1 -1
  49. data/app/helpers/alchemy/admin/ingredients_helper.rb +42 -0
  50. data/app/helpers/alchemy/elements_block_helper.rb +22 -7
  51. data/app/helpers/alchemy/elements_helper.rb +12 -5
  52. data/app/helpers/alchemy/pages_helper.rb +3 -11
  53. data/app/jobs/alchemy/base_job.rb +11 -0
  54. data/app/jobs/alchemy/publish_page_job.rb +11 -0
  55. data/app/models/alchemy/attachment.rb +1 -1
  56. data/app/models/alchemy/content/factory.rb +23 -27
  57. data/app/models/alchemy/content.rb +1 -6
  58. data/app/models/alchemy/element/definitions.rb +29 -27
  59. data/app/models/alchemy/element/element_contents.rb +131 -122
  60. data/app/models/alchemy/element/element_essences.rb +100 -98
  61. data/app/models/alchemy/element/element_ingredients.rb +176 -0
  62. data/app/models/alchemy/element/presenters.rb +89 -87
  63. data/app/models/alchemy/element.rb +40 -73
  64. data/app/models/alchemy/elements_repository.rb +126 -0
  65. data/app/models/alchemy/essence_audio.rb +12 -0
  66. data/app/models/alchemy/essence_headline.rb +40 -0
  67. data/app/models/alchemy/essence_picture.rb +4 -116
  68. data/app/models/alchemy/essence_richtext.rb +12 -0
  69. data/app/models/alchemy/essence_video.rb +12 -0
  70. data/app/models/alchemy/image_cropper_settings.rb +87 -0
  71. data/app/models/alchemy/ingredient.rb +219 -0
  72. data/app/models/alchemy/ingredient_validator.rb +97 -0
  73. data/app/models/alchemy/ingredients/audio.rb +29 -0
  74. data/app/models/alchemy/ingredients/boolean.rb +21 -0
  75. data/app/models/alchemy/ingredients/datetime.rb +20 -0
  76. data/app/models/alchemy/ingredients/file.rb +30 -0
  77. data/app/models/alchemy/ingredients/headline.rb +42 -0
  78. data/app/models/alchemy/ingredients/html.rb +19 -0
  79. data/app/models/alchemy/ingredients/link.rb +16 -0
  80. data/app/models/alchemy/ingredients/node.rb +23 -0
  81. data/app/models/alchemy/ingredients/page.rb +23 -0
  82. data/app/models/alchemy/ingredients/picture.rb +41 -0
  83. data/app/models/alchemy/ingredients/richtext.rb +57 -0
  84. data/app/models/alchemy/ingredients/select.rb +10 -0
  85. data/app/models/alchemy/ingredients/text.rb +17 -0
  86. data/app/models/alchemy/ingredients/video.rb +33 -0
  87. data/app/models/alchemy/language.rb +0 -11
  88. data/app/models/alchemy/node.rb +1 -1
  89. data/app/models/alchemy/page/fixed_attributes.rb +53 -51
  90. data/app/models/alchemy/page/page_elements.rb +186 -205
  91. data/app/models/alchemy/page/page_naming.rb +66 -64
  92. data/app/models/alchemy/page/page_natures.rb +139 -142
  93. data/app/models/alchemy/page/page_scopes.rb +113 -102
  94. data/app/models/alchemy/page/publisher.rb +50 -0
  95. data/app/models/alchemy/page/url_path.rb +1 -1
  96. data/app/models/alchemy/page.rb +67 -33
  97. data/app/models/alchemy/page_version.rb +58 -0
  98. data/app/models/alchemy/picture/calculations.rb +2 -8
  99. data/app/models/alchemy/picture/preprocessor.rb +2 -0
  100. data/app/models/alchemy/picture/transformations.rb +24 -96
  101. data/app/models/alchemy/picture.rb +4 -2
  102. data/app/models/concerns/alchemy/picture_thumbnails.rb +181 -0
  103. data/app/models/concerns/alchemy/touch_elements.rb +2 -2
  104. data/app/presenters/alchemy/picture_view.rb +88 -0
  105. data/app/serializers/alchemy/element_serializer.rb +5 -0
  106. data/app/serializers/alchemy/page_tree_serializer.rb +3 -2
  107. data/app/services/alchemy/delete_elements.rb +44 -0
  108. data/app/services/alchemy/duplicate_element.rb +56 -0
  109. data/app/views/alchemy/admin/attachments/_archive_overlay.html.erb +1 -2
  110. data/app/views/alchemy/admin/attachments/_file_to_assign.html.erb +3 -3
  111. data/app/views/alchemy/admin/attachments/assign.js.erb +11 -0
  112. data/app/views/alchemy/admin/crop.html.erb +36 -0
  113. data/app/views/alchemy/admin/elements/_element.html.erb +14 -10
  114. data/app/views/alchemy/admin/elements/{_element_footer.html.erb → _footer.html.erb} +0 -0
  115. data/app/views/alchemy/admin/elements/{_new_element_form.html.erb → _form.html.erb} +1 -1
  116. data/app/views/alchemy/admin/elements/{_element_header.html.erb → _header.html.erb} +1 -1
  117. data/app/views/alchemy/admin/elements/{_element_toolbar.html.erb → _toolbar.html.erb} +5 -6
  118. data/app/views/alchemy/admin/elements/{trash.js.erb → destroy.js.erb} +1 -3
  119. data/app/views/alchemy/admin/elements/new.html.erb +3 -3
  120. data/app/views/alchemy/admin/elements/order.js.erb +0 -17
  121. data/app/views/alchemy/admin/elements/update.js.erb +3 -2
  122. data/app/views/alchemy/admin/essence_audios/edit.html.erb +7 -0
  123. data/app/views/alchemy/admin/essence_pictures/update.js.erb +0 -1
  124. data/app/views/alchemy/admin/essence_videos/edit.html.erb +11 -0
  125. data/app/views/alchemy/admin/ingredients/_audio_fields.html.erb +4 -0
  126. data/app/views/alchemy/admin/ingredients/_file_fields.html.erb +18 -0
  127. data/app/views/alchemy/admin/ingredients/_picture_fields.html.erb +25 -0
  128. data/app/views/alchemy/admin/ingredients/_video_fields.html.erb +8 -0
  129. data/app/views/alchemy/admin/ingredients/edit.html.erb +4 -0
  130. data/app/views/alchemy/admin/layoutpages/edit.html.erb +0 -5
  131. data/app/views/alchemy/admin/nodes/_node.html.erb +2 -2
  132. data/app/views/alchemy/admin/pages/_anchor_link.html.erb +1 -1
  133. data/app/views/alchemy/admin/pages/_external_link.html.erb +1 -1
  134. data/app/views/alchemy/admin/pages/_file_link.html.erb +1 -1
  135. data/app/views/alchemy/admin/pages/_form.html.erb +0 -6
  136. data/app/views/alchemy/admin/pages/_internal_link.html.erb +1 -1
  137. data/app/views/alchemy/admin/pages/_tinymce_custom_config.html.erb +5 -2
  138. data/app/views/alchemy/admin/pages/edit.html.erb +36 -24
  139. data/app/views/alchemy/admin/partials/_remote_search_form.html.erb +2 -4
  140. data/app/views/alchemy/admin/partials/_routes.html.erb +7 -11
  141. data/app/views/alchemy/admin/pictures/_filter_and_size_bar.html.erb +4 -8
  142. data/app/views/alchemy/admin/pictures/_infos.html.erb +0 -1
  143. data/app/views/alchemy/admin/pictures/_picture_to_assign.html.erb +4 -4
  144. data/app/views/alchemy/admin/pictures/assign.js.erb +10 -0
  145. data/app/views/alchemy/admin/resources/_form.html.erb +1 -0
  146. data/app/views/alchemy/essences/_essence_audio_editor.html.erb +4 -0
  147. data/app/views/alchemy/essences/_essence_audio_view.html.erb +15 -0
  148. data/app/views/alchemy/essences/_essence_file_editor.html.erb +15 -6
  149. data/app/views/alchemy/essences/_essence_headline_editor.html.erb +36 -0
  150. data/app/views/alchemy/essences/_essence_headline_view.html.erb +10 -0
  151. data/app/views/alchemy/essences/_essence_link_editor.html.erb +8 -4
  152. data/app/views/alchemy/essences/_essence_picture_editor.html.erb +27 -12
  153. data/app/views/alchemy/essences/_essence_text_editor.html.erb +12 -4
  154. data/app/views/alchemy/essences/_essence_video_editor.html.erb +4 -0
  155. data/app/views/alchemy/essences/_essence_video_view.html.erb +18 -0
  156. data/app/views/alchemy/essences/shared/_essence_picture_tools.html.erb +21 -16
  157. data/app/views/alchemy/essences/shared/_linkable_essence_tools.html.erb +2 -2
  158. data/app/views/alchemy/ingredients/_audio_editor.html.erb +5 -0
  159. data/app/views/alchemy/ingredients/_audio_view.html.erb +14 -0
  160. data/app/views/alchemy/ingredients/_boolean_editor.html.erb +11 -0
  161. data/app/views/alchemy/ingredients/_boolean_view.html.erb +1 -0
  162. data/app/views/alchemy/ingredients/_datetime_editor.html.erb +17 -0
  163. data/app/views/alchemy/ingredients/_datetime_view.html.erb +9 -0
  164. data/app/views/alchemy/ingredients/_file_editor.html.erb +50 -0
  165. data/app/views/alchemy/ingredients/_file_view.html.erb +17 -0
  166. data/app/views/alchemy/ingredients/_headline_editor.html.erb +30 -0
  167. data/app/views/alchemy/ingredients/_headline_view.html.erb +9 -0
  168. data/app/views/alchemy/ingredients/_html_editor.html.erb +8 -0
  169. data/app/views/alchemy/ingredients/_html_view.html.erb +1 -0
  170. data/app/views/alchemy/ingredients/_link_editor.html.erb +24 -0
  171. data/app/views/alchemy/ingredients/_link_view.html.erb +9 -0
  172. data/app/views/alchemy/ingredients/_node_editor.html.erb +25 -0
  173. data/app/views/alchemy/ingredients/_node_view.html.erb +1 -0
  174. data/app/views/alchemy/ingredients/_page_editor.html.erb +24 -0
  175. data/app/views/alchemy/ingredients/_page_view.html.erb +4 -0
  176. data/app/views/alchemy/ingredients/_picture_editor.html.erb +59 -0
  177. data/app/views/alchemy/ingredients/_picture_view.html.erb +5 -0
  178. data/app/views/alchemy/ingredients/_richtext_editor.html.erb +12 -0
  179. data/app/views/alchemy/ingredients/_richtext_view.html.erb +3 -0
  180. data/app/views/alchemy/ingredients/_select_editor.html.erb +29 -0
  181. data/app/views/alchemy/ingredients/_select_view.html.erb +1 -0
  182. data/app/views/alchemy/ingredients/_text_editor.html.erb +19 -0
  183. data/app/views/alchemy/ingredients/_text_view.html.erb +16 -0
  184. data/app/views/alchemy/ingredients/_video_editor.html.erb +5 -0
  185. data/app/views/alchemy/ingredients/_video_view.html.erb +17 -0
  186. data/app/views/alchemy/ingredients/shared/_link_tools.html.erb +20 -0
  187. data/app/views/alchemy/ingredients/shared/_picture_tools.html.erb +57 -0
  188. data/config/brakeman.ignore +66 -159
  189. data/config/initializers/dragonfly.rb +10 -0
  190. data/config/locales/alchemy.en.yml +23 -15
  191. data/config/routes.rb +17 -22
  192. data/db/migrate/20201207131309_create_page_versions.rb +19 -0
  193. data/db/migrate/20201207135820_add_page_version_id_to_alchemy_elements.rb +76 -0
  194. data/db/migrate/20210205143548_rename_public_on_and_public_until_on_alchemy_pages.rb +10 -0
  195. data/db/migrate/20210326105046_add_sanitized_body_to_alchemy_essence_richtexts.rb +7 -0
  196. data/db/migrate/20210406093436_add_alchemy_essence_headlines.rb +12 -0
  197. data/db/migrate/20210506135919_create_essence_audios.rb +19 -0
  198. data/db/migrate/20210506140258_create_essence_videos.rb +23 -0
  199. data/db/migrate/20210508091432_create_alchemy_ingredients.rb +22 -0
  200. data/lib/alchemy/admin/preview_url.rb +2 -0
  201. data/lib/alchemy/deprecation.rb +1 -1
  202. data/lib/alchemy/dragonfly/processors/auto_orient.rb +18 -0
  203. data/lib/alchemy/dragonfly/processors/crop_resize.rb +35 -0
  204. data/lib/alchemy/elements_finder.rb +14 -60
  205. data/lib/alchemy/engine.rb +1 -1
  206. data/lib/alchemy/essence.rb +1 -2
  207. data/lib/alchemy/hints.rb +8 -4
  208. data/lib/alchemy/page_layout.rb +0 -13
  209. data/lib/alchemy/permissions.rb +30 -29
  210. data/lib/alchemy/resource.rb +13 -3
  211. data/lib/alchemy/tasks/tidy.rb +29 -0
  212. data/lib/alchemy/test_support/essence_shared_examples.rb +0 -1
  213. data/lib/alchemy/test_support/factories/element_factory.rb +8 -8
  214. data/lib/alchemy/test_support/factories/essence_audio_factory.rb +7 -0
  215. data/lib/alchemy/test_support/factories/essence_video_factory.rb +7 -0
  216. data/lib/alchemy/test_support/factories/ingredient_factory.rb +25 -0
  217. data/lib/alchemy/test_support/factories/page_factory.rb +20 -1
  218. data/lib/alchemy/test_support/factories/page_version_factory.rb +23 -0
  219. data/lib/alchemy/test_support/having_crop_action_examples.rb +170 -0
  220. data/lib/alchemy/test_support/having_picture_thumbnails_examples.rb +646 -0
  221. data/lib/alchemy/test_support/shared_ingredient_editor_examples.rb +21 -0
  222. data/lib/alchemy/test_support/shared_ingredient_examples.rb +57 -0
  223. data/lib/alchemy/test_support.rb +2 -11
  224. data/lib/alchemy/tinymce.rb +17 -0
  225. data/lib/alchemy/upgrader/five_point_zero.rb +0 -32
  226. data/lib/alchemy/upgrader/six_point_zero.rb +21 -0
  227. data/lib/alchemy/upgrader/tasks/add_page_versions.rb +33 -0
  228. data/lib/alchemy/upgrader/tasks/ingredients_migrator.rb +51 -0
  229. data/lib/alchemy/version.rb +1 -1
  230. data/lib/generators/alchemy/elements/elements_generator.rb +1 -0
  231. data/lib/generators/alchemy/elements/templates/view.html.erb +9 -0
  232. data/lib/generators/alchemy/elements/templates/view.html.haml +9 -0
  233. data/lib/generators/alchemy/elements/templates/view.html.slim +9 -0
  234. data/lib/generators/alchemy/ingredient/ingredient_generator.rb +38 -0
  235. data/lib/generators/alchemy/ingredient/templates/editor.html.erb +14 -0
  236. data/lib/generators/alchemy/ingredient/templates/model.rb.tt +13 -0
  237. data/lib/generators/alchemy/ingredient/templates/view.html.erb +1 -0
  238. data/lib/generators/alchemy/install/install_generator.rb +1 -2
  239. data/lib/generators/alchemy/install/templates/dragonfly.rb.tt +1 -1
  240. data/lib/generators/alchemy/menus/templates/node.html.erb +1 -1
  241. data/lib/generators/alchemy/menus/templates/node.html.haml +1 -1
  242. data/lib/generators/alchemy/menus/templates/node.html.slim +1 -1
  243. data/lib/generators/alchemy/menus/templates/wrapper.html.erb +1 -1
  244. data/lib/generators/alchemy/menus/templates/wrapper.html.haml +1 -1
  245. data/lib/generators/alchemy/menus/templates/wrapper.html.slim +1 -1
  246. data/lib/tasks/alchemy/tidy.rake +12 -0
  247. data/lib/tasks/alchemy/upgrade.rake +21 -15
  248. data/package/admin.js +9 -1
  249. data/package/src/file_editors.js +28 -0
  250. data/package/src/image_cropper.js +103 -0
  251. data/package/src/image_loader.js +58 -0
  252. data/package/src/node_tree.js +5 -5
  253. data/package/src/picture_editors.js +169 -0
  254. data/package/src/utils/__tests__/ajax.spec.js +20 -12
  255. data/package/src/utils/ajax.js +8 -3
  256. data/package.json +3 -2
  257. data/vendor/assets/javascripts/jquery_plugins/jquery.Jcrop.min.js +3 -18
  258. data/vendor/assets/stylesheets/jquery.Jcrop.min.scss +2 -28
  259. metadata +285 -56
  260. data/app/assets/javascripts/alchemy/alchemy.image_cropper.js.coffee +0 -44
  261. data/app/assets/javascripts/alchemy/alchemy.trash_window.js.coffee +0 -30
  262. data/app/assets/stylesheets/alchemy/trash.scss +0 -8
  263. data/app/controllers/alchemy/admin/trash_controller.rb +0 -44
  264. data/app/views/alchemy/admin/essence_files/assign.js.erb +0 -3
  265. data/app/views/alchemy/admin/essence_pictures/assign.js.erb +0 -4
  266. data/app/views/alchemy/admin/essence_pictures/crop.html.erb +0 -48
  267. data/app/views/alchemy/admin/trash/clear.js.erb +0 -4
  268. data/app/views/alchemy/admin/trash/index.html.erb +0 -31
  269. data/lib/alchemy/test_support/factories.rb +0 -20
@@ -1,237 +1,218 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Alchemy
4
- module Page::PageElements
5
- extend ActiveSupport::Concern
6
-
7
- included do
8
- attr_accessor :autogenerate_elements
9
-
10
- has_many :all_elements,
11
- -> { order(:position) },
12
- class_name: "Alchemy::Element",
13
- inverse_of: :page
14
- has_many :elements,
15
- -> { order(:position).not_nested.unfixed.available },
16
- class_name: "Alchemy::Element",
17
- inverse_of: :page
18
- has_many :trashed_elements,
19
- -> { Element.trashed.order(:position) },
20
- class_name: "Alchemy::Element",
21
- inverse_of: :page
22
- has_many :fixed_elements,
23
- -> { order(:position).fixed.available },
24
- class_name: "Alchemy::Element",
25
- inverse_of: :page
26
- has_many :dependent_destroyable_elements,
27
- -> { not_nested },
28
- class_name: "Alchemy::Element",
29
- dependent: :destroy
30
- has_many :contents, through: :elements
31
- has_and_belongs_to_many :to_be_swept_elements, -> { distinct },
32
- class_name: "Alchemy::Element",
33
- join_table: ElementToPage.table_name
34
-
35
- after_create :generate_elements,
36
- unless: -> { autogenerate_elements == false }
37
-
38
- after_update :trash_not_allowed_elements!,
39
- if: :saved_change_to_page_layout?
40
-
41
- after_update(if: :saved_change_to_page_layout?) do
42
- Alchemy::Deprecation.warn(
43
- "Autogenerating elements on page_layout change is deprecated and will be removed from Alchemy 6.0"
44
- )
45
- generate_elements
46
- end
47
- end
4
+ class Page < BaseRecord
5
+ module PageElements
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ attr_accessor :autogenerate_elements
10
+
11
+ with_options(
12
+ class_name: "Alchemy::Element",
13
+ through: :public_version,
14
+ inverse_of: :page,
15
+ source: :elements,
16
+ ) do
17
+ has_many :all_elements
18
+ has_many :elements, -> { not_nested.unfixed.available }
19
+ has_many :fixed_elements, -> { fixed.available }
20
+ end
48
21
 
49
- module ClassMethods
50
- deprecate :trashed_elements, deprecator: Alchemy::Deprecation
22
+ has_many :contents, through: :elements
23
+ has_and_belongs_to_many :to_be_swept_elements, -> { distinct },
24
+ class_name: "Alchemy::Element",
25
+ join_table: ElementToPage.table_name
51
26
 
52
- # Copy page elements
53
- #
54
- # @param source [Alchemy::Page]
55
- # @param target [Alchemy::Page]
56
- # @return [Array]
57
- #
58
- def copy_elements(source, target)
59
- source_elements = source.all_elements.not_nested.not_trashed
60
- source_elements.order(:position).map do |source_element|
61
- Element.copy(source_element, {
62
- page_id: target.id,
63
- }).tap(&:move_to_bottom)
64
- end
27
+ after_create :generate_elements,
28
+ unless: -> { autogenerate_elements == false }
65
29
  end
66
- end
67
30
 
68
- # All available element definitions that can actually be placed on current page.
69
- #
70
- # It extracts all definitions that are unique or limited and already on page.
71
- #
72
- # == Example of unique element:
73
- #
74
- # - name: headline
75
- # unique: true
76
- # contents:
77
- # - name: headline
78
- # type: EssenceText
79
- #
80
- # == Example of limited element:
81
- #
82
- # - name: article
83
- # amount: 2
84
- # contents:
85
- # - name: text
86
- # type: EssenceRichtext
87
- #
88
- def available_element_definitions(only_element_named = nil)
89
- @_element_definitions ||= if only_element_named
90
- definition = Element.definition_by_name(only_element_named)
91
- element_definitions_by_name(definition["nestable_elements"])
92
- else
93
- element_definitions
31
+ module ClassMethods
32
+ # Copy page elements
33
+ #
34
+ # @param source [Alchemy::Page]
35
+ # @param target [Alchemy::Page]
36
+ # @return [Array]
37
+ #
38
+ def copy_elements(source, target)
39
+ repository = source.draft_version.element_repository
40
+ transaction do
41
+ Element.acts_as_list_no_update do
42
+ repository.not_nested.each.with_index(1) do |element, position|
43
+ Alchemy::DuplicateElement.new(element, repository: repository).call(
44
+ page_version_id: target.draft_version.id,
45
+ position: position,
46
+ )
47
+ end
48
+ end
49
+ end
94
50
  end
51
+ end
95
52
 
96
- return [] if @_element_definitions.blank?
53
+ # All available element definitions that can actually be placed on current page.
54
+ #
55
+ # It extracts all definitions that are unique or limited and already on page.
56
+ #
57
+ # == Example of unique element:
58
+ #
59
+ # - name: headline
60
+ # unique: true
61
+ # contents:
62
+ # - name: headline
63
+ # type: EssenceText
64
+ #
65
+ # == Example of limited element:
66
+ #
67
+ # - name: article
68
+ # amount: 2
69
+ # contents:
70
+ # - name: text
71
+ # type: EssenceRichtext
72
+ #
73
+ def available_element_definitions(only_element_named = nil)
74
+ @_element_definitions ||= if only_element_named
75
+ definition = Element.definition_by_name(only_element_named)
76
+ element_definitions_by_name(definition["nestable_elements"])
77
+ else
78
+ element_definitions
79
+ end
97
80
 
98
- existing_elements = all_elements.not_nested.not_trashed
99
- @_existing_element_names = existing_elements.pluck(:name)
100
- delete_unique_element_definitions!
101
- delete_outnumbered_element_definitions!
81
+ return [] if @_element_definitions.blank?
102
82
 
103
- @_element_definitions
104
- end
83
+ existing_elements = draft_version.elements.not_nested
84
+ @_existing_element_names = existing_elements.pluck(:name)
85
+ delete_unique_element_definitions!
86
+ delete_outnumbered_element_definitions!
105
87
 
106
- # All names of elements that can actually be placed on current page.
107
- #
108
- def available_element_names
109
- @_available_element_names ||= available_element_definitions.map { |e| e["name"] }
110
- end
88
+ @_element_definitions
89
+ end
111
90
 
112
- # Available element definitions excluding nested unique elements.
113
- #
114
- def available_elements_within_current_scope(parent)
115
- @_available_elements = if parent
116
- parents_unique_nested_elements = parent.nested_elements.where(unique: true).pluck(:name)
117
- available_element_definitions(parent.name).reject do |e|
118
- parents_unique_nested_elements.include? e["name"]
119
- end
120
- else
121
- available_element_definitions
122
- end
123
- end
91
+ # All names of elements that can actually be placed on current page.
92
+ #
93
+ def available_element_names
94
+ @_available_element_names ||= available_element_definitions.map { |e| e["name"] }
95
+ end
124
96
 
125
- # All element definitions defined for page's page layout
126
- #
127
- # Warning: Since elements can be unique or limited in number,
128
- # it is more safe to ask for +available_element_definitions+
129
- #
130
- def element_definitions
131
- @_element_definitions ||= element_definitions_by_name(element_definition_names)
132
- end
97
+ # Available element definitions excluding nested unique elements.
98
+ #
99
+ def available_elements_within_current_scope(parent)
100
+ @_available_elements = if parent
101
+ parents_unique_nested_elements = parent.nested_elements.where(unique: true).pluck(:name)
102
+ available_element_definitions(parent.name).reject do |e|
103
+ parents_unique_nested_elements.include? e["name"]
104
+ end
105
+ else
106
+ available_element_definitions
107
+ end
108
+ end
133
109
 
134
- # All element definitions defined for page's page layout including nestable element definitions
135
- #
136
- def descendent_element_definitions
137
- definitions = element_definitions_by_name(element_definition_names)
138
- definitions.select { |d| d.key?("nestable_elements") }.each do |d|
139
- definitions += element_definitions_by_name(d["nestable_elements"])
110
+ # All element definitions defined for page's page layout
111
+ #
112
+ # Warning: Since elements can be unique or limited in number,
113
+ # it is more safe to ask for +available_element_definitions+
114
+ #
115
+ def element_definitions
116
+ @_element_definitions ||= element_definitions_by_name(element_definition_names)
140
117
  end
141
- definitions.uniq { |d| d["name"] }
142
- end
143
118
 
144
- # All names of elements that are defined in the page definition.
145
- #
146
- # Assign elements to a page in +config/alchemy/page_layouts.yml+.
147
- #
148
- # == Example of page_layouts.yml:
149
- #
150
- # - name: contact
151
- # elements: [headline, contactform]
152
- #
153
- def element_definition_names
154
- definition["elements"] || []
155
- end
119
+ # All element definitions defined for page's page layout including nestable element definitions
120
+ #
121
+ def descendent_element_definitions
122
+ definitions = element_definitions_by_name(element_definition_names)
123
+ definitions.select { |d| d.key?("nestable_elements") }.each do |d|
124
+ definitions += element_definitions_by_name(d["nestable_elements"])
125
+ end
126
+ definitions.uniq { |d| d["name"] }
127
+ end
156
128
 
157
- # Element definitions with given name(s)
158
- #
159
- # @param [Array || String]
160
- # one or many Alchemy::Element names. Pass +'all'+ to get all Element definitions
161
- # @return [Array]
162
- # An Array of element definitions
163
- #
164
- def element_definitions_by_name(names)
165
- return [] if names.blank?
166
-
167
- if names.to_s == "all"
168
- Element.definitions
169
- else
170
- Element.definitions.select { |e| names.include? e["name"] }
129
+ # All names of elements that are defined in the page definition.
130
+ #
131
+ # Assign elements to a page in +config/alchemy/page_layouts.yml+.
132
+ #
133
+ # == Example of page_layouts.yml:
134
+ #
135
+ # - name: contact
136
+ # elements: [headline, contactform]
137
+ #
138
+ def element_definition_names
139
+ definition["elements"] || []
171
140
  end
172
- end
173
141
 
174
- # Returns all elements that should be feeded via rss.
175
- #
176
- # Define feedable elements in your +page_layouts.yml+:
177
- #
178
- # - name: news
179
- # feed: true
180
- # feed_elements: [element_name, element_2_name]
181
- #
182
- def feed_elements
183
- elements.named(definition["feed_elements"])
184
- end
142
+ # Element definitions with given name(s)
143
+ #
144
+ # @param [Array || String]
145
+ # one or many Alchemy::Element names. Pass +'all'+ to get all Element definitions
146
+ # @return [Array]
147
+ # An Array of element definitions
148
+ #
149
+ def element_definitions_by_name(names)
150
+ return [] if names.blank?
185
151
 
186
- # Returns an array of all EssenceRichtext contents ids from not folded elements
187
- #
188
- def richtext_contents_ids
189
- Alchemy::Content.joins(:element)
190
- .where(Element.table_name => { page_id: id, folded: false })
191
- .select(&:has_tinymce?)
192
- .collect(&:id)
193
- end
152
+ if names.to_s == "all"
153
+ Element.definitions
154
+ else
155
+ Element.definitions.select { |e| names.include? e["name"] }
156
+ end
157
+ end
194
158
 
195
- private
159
+ # Returns all elements that should be feeded via rss.
160
+ #
161
+ # Define feedable elements in your +page_layouts.yml+:
162
+ #
163
+ # - name: news
164
+ # feed: true
165
+ # feed_elements: [element_name, element_2_name]
166
+ #
167
+ def feed_elements
168
+ elements.named(definition["feed_elements"])
169
+ end
196
170
 
197
- # Looks in the page_layout descripion, if there are elements to autogenerate.
198
- #
199
- # And if so, it generates them.
200
- #
201
- def generate_elements
202
- existing_elements = all_elements.not_nested.not_trashed
203
- existing_element_names = existing_elements.pluck(:name).uniq
204
- definition.fetch("autogenerate", []).each do |element_name|
205
- next if existing_element_names.include?(element_name)
171
+ # Returns an array of all EssenceRichtext contents ids from not folded elements
172
+ #
173
+ def richtext_contents_ids
174
+ Alchemy::Content.joins(:element)
175
+ .where(Element.table_name => { page_version_id: draft_version.id, folded: false })
176
+ .select(&:has_tinymce?)
177
+ .collect(&:id)
178
+ end
206
179
 
207
- Element.create(page: self, name: element_name)
180
+ # Returns an array of all Richtext ingredients ids from not folded elements
181
+ #
182
+ def richtext_ingredients_ids
183
+ Alchemy::Ingredient.richtexts.joins(:element)
184
+ .where(Element.table_name => { page_version_id: draft_version.id, folded: false })
185
+ .select(&:has_tinymce?)
186
+ .collect(&:id)
208
187
  end
209
- end
210
188
 
211
- # Trashes all elements that are not allowed for this page_layout.
212
- def trash_not_allowed_elements!
213
- not_allowed_elements = elements.where([
214
- "#{Element.table_name}.name NOT IN (?)",
215
- element_definition_names,
216
- ])
217
- not_allowed_elements.to_a.map(&:trash!)
218
- end
219
- deprecate :trash_not_allowed_elements!, deprecator: Alchemy::Deprecation
189
+ private
220
190
 
221
- # Deletes unique and already present definitions from @_element_definitions.
222
- #
223
- def delete_unique_element_definitions!
224
- @_element_definitions.delete_if do |element|
225
- element["unique"] && @_existing_element_names.include?(element["name"])
191
+ # Looks in the page_layout descripion, if there are elements to autogenerate.
192
+ #
193
+ # And if so, it generates them.
194
+ #
195
+ def generate_elements
196
+ definition.fetch("autogenerate", []).each do |element_name|
197
+ Element.create(page: self, page_version: draft_version, name: element_name)
198
+ end
199
+ end
200
+
201
+ # Deletes unique and already present definitions from @_element_definitions.
202
+ #
203
+ def delete_unique_element_definitions!
204
+ @_element_definitions.delete_if do |element|
205
+ element["unique"] && @_existing_element_names.include?(element["name"])
206
+ end
226
207
  end
227
- end
228
208
 
229
- # Deletes limited and outnumbered definitions from @_element_definitions.
230
- #
231
- def delete_outnumbered_element_definitions!
232
- @_element_definitions.delete_if do |element|
233
- outnumbered = @_existing_element_names.select { |name| name == element["name"] }
234
- element["amount"] && outnumbered.count >= element["amount"].to_i
209
+ # Deletes limited and outnumbered definitions from @_element_definitions.
210
+ #
211
+ def delete_outnumbered_element_definitions!
212
+ @_element_definitions.delete_if do |element|
213
+ outnumbered = @_existing_element_names.select { |name| name == element["name"] }
214
+ element["amount"] && outnumbered.count >= element["amount"].to_i
215
+ end
235
216
  end
236
217
  end
237
218
  end
@@ -1,85 +1,87 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Alchemy
4
- module Page::PageNaming
5
- extend ActiveSupport::Concern
6
- include NameConversions
7
- RESERVED_URLNAMES = %w(admin messages new)
4
+ class Page < BaseRecord
5
+ module PageNaming
6
+ extend ActiveSupport::Concern
7
+ include NameConversions
8
+ RESERVED_URLNAMES = %w(admin messages new)
8
9
 
9
- included do
10
- before_validation :set_urlname,
11
- if: :renamed?,
12
- unless: -> { name.blank? }
10
+ included do
11
+ before_validation :set_urlname,
12
+ if: :renamed?,
13
+ unless: -> { name.blank? }
13
14
 
14
- validates :name,
15
- presence: true
16
- validates :urlname,
17
- uniqueness: { scope: [:language_id, :layoutpage], if: -> { urlname.present? } },
18
- exclusion: { in: RESERVED_URLNAMES },
19
- length: { minimum: 3, if: -> { urlname.present? } }
15
+ validates :name,
16
+ presence: true
17
+ validates :urlname,
18
+ uniqueness: { scope: [:language_id, :layoutpage], if: -> { urlname.present? } },
19
+ exclusion: { in: RESERVED_URLNAMES },
20
+ length: { minimum: 3, if: -> { urlname.present? } }
20
21
 
21
- before_save :set_title,
22
- if: -> { title.blank? }
22
+ before_save :set_title,
23
+ if: -> { title.blank? }
23
24
 
24
- after_update :update_descendants_urlnames,
25
- if: :saved_change_to_urlname?
25
+ after_update :update_descendants_urlnames,
26
+ if: :saved_change_to_urlname?
26
27
 
27
- after_move :update_urlname!
28
- end
28
+ after_move :update_urlname!
29
+ end
29
30
 
30
- # Returns true if name or urlname has changed.
31
- def renamed?
32
- name_changed? || urlname_changed?
33
- end
31
+ # Returns true if name or urlname has changed.
32
+ def renamed?
33
+ name_changed? || urlname_changed?
34
+ end
34
35
 
35
- # Makes a slug of all ancestors urlnames including mine and delimit them be slash.
36
- # So the whole path is stored as urlname in the database.
37
- def update_urlname!
38
- new_urlname = nested_url_name
39
- if urlname != new_urlname
40
- legacy_urls.create(urlname: urlname)
41
- update_column(:urlname, new_urlname)
36
+ # Makes a slug of all ancestors urlnames including mine and delimit them be slash.
37
+ # So the whole path is stored as urlname in the database.
38
+ def update_urlname!
39
+ new_urlname = nested_url_name
40
+ if urlname != new_urlname
41
+ legacy_urls.create(urlname: urlname)
42
+ update_column(:urlname, new_urlname)
43
+ end
42
44
  end
43
- end
44
45
 
45
- # Returns always the last part of a urlname path
46
- def slug
47
- urlname.to_s.split("/").last
48
- end
46
+ # Returns always the last part of a urlname path
47
+ def slug
48
+ urlname.to_s.split("/").last
49
+ end
49
50
 
50
- private
51
+ private
51
52
 
52
- def update_descendants_urlnames
53
- reload
54
- descendants.each(&:update_urlname!)
55
- end
53
+ def update_descendants_urlnames
54
+ reload
55
+ descendants.each(&:update_urlname!)
56
+ end
56
57
 
57
- # Sets the urlname to a url friendly slug.
58
- # Either from name, or if present, from urlname.
59
- # The urlname contains the whole path including parent urlnames.
60
- def set_urlname
61
- self[:urlname] = nested_url_name
62
- end
58
+ # Sets the urlname to a url friendly slug.
59
+ # Either from name, or if present, from urlname.
60
+ # The urlname contains the whole path including parent urlnames.
61
+ def set_urlname
62
+ self[:urlname] = nested_url_name
63
+ end
63
64
 
64
- def set_title
65
- self[:title] = name
66
- end
65
+ def set_title
66
+ self[:title] = name
67
+ end
67
68
 
68
- # Converts the given name into an url friendly string.
69
- #
70
- # Names shorter than 3 will be filled up with dashes,
71
- # so it does not collidate with the language code.
72
- #
73
- def converted_url_name
74
- url_name = convert_to_urlname(slug.blank? ? name : slug)
75
- url_name.rjust(3, "-")
76
- end
69
+ # Converts the given name into an url friendly string.
70
+ #
71
+ # Names shorter than 3 will be filled up with dashes,
72
+ # so it does not collidate with the language code.
73
+ #
74
+ def converted_url_name
75
+ url_name = convert_to_urlname(slug.blank? ? name : slug)
76
+ url_name.rjust(3, "-")
77
+ end
77
78
 
78
- def nested_url_name
79
- if parent&.language_root?
80
- converted_url_name
81
- else
82
- [parent&.urlname, converted_url_name].compact.join("/")
79
+ def nested_url_name
80
+ if parent&.language_root?
81
+ converted_url_name
82
+ else
83
+ [parent&.urlname, converted_url_name].compact.join("/")
84
+ end
83
85
  end
84
86
  end
85
87
  end