alchemy_cms 5.2.0 → 6.0.0.b3

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 (289) 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 +114 -5
  7. data/Gemfile +8 -1
  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/_extends.scss +15 -2
  23. data/app/assets/stylesheets/alchemy/admin.scss +1 -1
  24. data/app/assets/stylesheets/alchemy/archive.scss +20 -5
  25. data/app/assets/stylesheets/alchemy/buttons.scss +0 -4
  26. data/app/assets/stylesheets/alchemy/elements.scss +73 -61
  27. data/app/assets/stylesheets/alchemy/images.scss +8 -0
  28. data/app/assets/stylesheets/alchemy/node-select.scss +4 -3
  29. data/app/assets/stylesheets/alchemy/page-select.scss +1 -0
  30. data/app/controllers/alchemy/admin/attachments_controller.rb +8 -4
  31. data/app/controllers/alchemy/admin/base_controller.rb +5 -7
  32. data/app/controllers/alchemy/admin/elements_controller.rb +59 -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 +7 -22
  40. data/app/controllers/alchemy/admin/pictures_controller.rb +56 -17
  41. data/app/controllers/alchemy/admin/resources_controller.rb +84 -10
  42. data/app/controllers/alchemy/api/elements_controller.rb +13 -4
  43. data/app/controllers/alchemy/api/pages_controller.rb +4 -3
  44. data/app/controllers/concerns/alchemy/admin/archive_overlay.rb +13 -3
  45. data/app/controllers/concerns/alchemy/admin/crop_action.rb +26 -0
  46. data/app/decorators/alchemy/element_editor.rb +26 -1
  47. data/app/decorators/alchemy/ingredient_editor.rb +158 -0
  48. data/app/helpers/alchemy/admin/elements_helper.rb +1 -0
  49. data/app/helpers/alchemy/admin/essences_helper.rb +1 -1
  50. data/app/helpers/alchemy/admin/ingredients_helper.rb +42 -0
  51. data/app/helpers/alchemy/elements_block_helper.rb +23 -6
  52. data/app/helpers/alchemy/elements_helper.rb +12 -5
  53. data/app/helpers/alchemy/pages_helper.rb +3 -11
  54. data/app/jobs/alchemy/base_job.rb +11 -0
  55. data/app/jobs/alchemy/publish_page_job.rb +11 -0
  56. data/app/models/alchemy/attachment.rb +24 -7
  57. data/app/models/alchemy/content.rb +1 -6
  58. data/app/models/alchemy/content/factory.rb +23 -27
  59. data/app/models/alchemy/element.rb +39 -72
  60. data/app/models/alchemy/element/definitions.rb +29 -27
  61. data/app/models/alchemy/element/element_contents.rb +131 -122
  62. data/app/models/alchemy/element/element_essences.rb +111 -98
  63. data/app/models/alchemy/element/element_ingredients.rb +184 -0
  64. data/app/models/alchemy/element/presenters.rb +104 -85
  65. data/app/models/alchemy/elements_repository.rb +126 -0
  66. data/app/models/alchemy/essence_audio.rb +12 -0
  67. data/app/models/alchemy/essence_headline.rb +40 -0
  68. data/app/models/alchemy/essence_picture.rb +4 -116
  69. data/app/models/alchemy/essence_richtext.rb +12 -0
  70. data/app/models/alchemy/essence_video.rb +12 -0
  71. data/app/models/alchemy/image_cropper_settings.rb +87 -0
  72. data/app/models/alchemy/ingredient.rb +183 -0
  73. data/app/models/alchemy/ingredient_validator.rb +97 -0
  74. data/app/models/alchemy/ingredients/audio.rb +29 -0
  75. data/app/models/alchemy/ingredients/boolean.rb +21 -0
  76. data/app/models/alchemy/ingredients/datetime.rb +20 -0
  77. data/app/models/alchemy/ingredients/file.rb +30 -0
  78. data/app/models/alchemy/ingredients/headline.rb +42 -0
  79. data/app/models/alchemy/ingredients/html.rb +19 -0
  80. data/app/models/alchemy/ingredients/link.rb +16 -0
  81. data/app/models/alchemy/ingredients/node.rb +23 -0
  82. data/app/models/alchemy/ingredients/page.rb +23 -0
  83. data/app/models/alchemy/ingredients/picture.rb +41 -0
  84. data/app/models/alchemy/ingredients/richtext.rb +57 -0
  85. data/app/models/alchemy/ingredients/select.rb +10 -0
  86. data/app/models/alchemy/ingredients/text.rb +17 -0
  87. data/app/models/alchemy/ingredients/video.rb +33 -0
  88. data/app/models/alchemy/language.rb +0 -11
  89. data/app/models/alchemy/page.rb +76 -33
  90. data/app/models/alchemy/page/fixed_attributes.rb +53 -51
  91. data/app/models/alchemy/page/page_elements.rb +186 -205
  92. data/app/models/alchemy/page/page_naming.rb +66 -64
  93. data/app/models/alchemy/page/page_natures.rb +139 -142
  94. data/app/models/alchemy/page/page_scopes.rb +117 -102
  95. data/app/models/alchemy/page/publisher.rb +50 -0
  96. data/app/models/alchemy/page/url_path.rb +1 -1
  97. data/app/models/alchemy/page_version.rb +58 -0
  98. data/app/models/alchemy/picture.rb +18 -40
  99. data/app/models/alchemy/picture/calculations.rb +2 -8
  100. data/app/models/alchemy/picture/preprocessor.rb +2 -0
  101. data/app/models/alchemy/picture/transformations.rb +24 -96
  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 +2 -3
  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/attachments/index.html.erb +2 -3
  113. data/app/views/alchemy/admin/crop.html.erb +36 -0
  114. data/app/views/alchemy/admin/elements/_element.html.erb +14 -10
  115. data/app/views/alchemy/admin/elements/{_element_footer.html.erb → _footer.html.erb} +0 -0
  116. data/app/views/alchemy/admin/elements/{_new_element_form.html.erb → _form.html.erb} +1 -1
  117. data/app/views/alchemy/admin/elements/{_element_header.html.erb → _header.html.erb} +1 -1
  118. data/app/views/alchemy/admin/elements/{_element_toolbar.html.erb → _toolbar.html.erb} +5 -6
  119. data/app/views/alchemy/admin/elements/create.js.erb +1 -1
  120. data/app/views/alchemy/admin/elements/{trash.js.erb → destroy.js.erb} +2 -6
  121. data/app/views/alchemy/admin/elements/fold.js.erb +2 -2
  122. data/app/views/alchemy/admin/elements/new.html.erb +3 -3
  123. data/app/views/alchemy/admin/elements/order.js.erb +0 -17
  124. data/app/views/alchemy/admin/elements/update.js.erb +3 -2
  125. data/app/views/alchemy/admin/essence_audios/edit.html.erb +7 -0
  126. data/app/views/alchemy/admin/essence_pictures/update.js.erb +0 -1
  127. data/app/views/alchemy/admin/essence_videos/edit.html.erb +11 -0
  128. data/app/views/alchemy/admin/ingredients/_audio_fields.html.erb +4 -0
  129. data/app/views/alchemy/admin/ingredients/_file_fields.html.erb +18 -0
  130. data/app/views/alchemy/admin/ingredients/_picture_fields.html.erb +25 -0
  131. data/app/views/alchemy/admin/ingredients/_video_fields.html.erb +8 -0
  132. data/app/views/alchemy/admin/ingredients/edit.html.erb +4 -0
  133. data/app/views/alchemy/admin/layoutpages/edit.html.erb +0 -5
  134. data/app/views/alchemy/admin/nodes/_node.html.erb +2 -2
  135. data/app/views/alchemy/admin/pages/_anchor_link.html.erb +1 -1
  136. data/app/views/alchemy/admin/pages/_external_link.html.erb +1 -1
  137. data/app/views/alchemy/admin/pages/_file_link.html.erb +1 -1
  138. data/app/views/alchemy/admin/pages/_form.html.erb +0 -6
  139. data/app/views/alchemy/admin/pages/_internal_link.html.erb +1 -1
  140. data/app/views/alchemy/admin/pages/_tinymce_custom_config.html.erb +5 -2
  141. data/app/views/alchemy/admin/pages/_toolbar.html.erb +1 -1
  142. data/app/views/alchemy/admin/pages/edit.html.erb +36 -24
  143. data/app/views/alchemy/admin/pages/index.html.erb +2 -9
  144. data/app/views/alchemy/admin/partials/_remote_search_form.html.erb +2 -4
  145. data/app/views/alchemy/admin/partials/_routes.html.erb +7 -11
  146. data/app/views/alchemy/admin/partials/_search_form.html.erb +9 -0
  147. data/app/views/alchemy/admin/pictures/_archive.html.erb +1 -1
  148. data/app/views/alchemy/admin/pictures/_archive_overlay.html.erb +1 -1
  149. data/app/views/alchemy/admin/pictures/_filter_and_size_bar.html.erb +5 -7
  150. data/app/views/alchemy/admin/pictures/_infos.html.erb +0 -1
  151. data/app/views/alchemy/admin/pictures/_picture_to_assign.html.erb +4 -4
  152. data/app/views/alchemy/admin/pictures/assign.js.erb +10 -0
  153. data/app/views/alchemy/admin/pictures/index.html.erb +8 -3
  154. data/app/views/alchemy/admin/resources/_filter.html.erb +12 -0
  155. data/app/views/alchemy/admin/resources/_filter_bar.html.erb +14 -17
  156. data/app/views/alchemy/admin/resources/_form.html.erb +3 -0
  157. data/app/views/alchemy/admin/resources/_table_header.html.erb +15 -0
  158. data/app/views/alchemy/admin/resources/index.html.erb +3 -11
  159. data/app/views/alchemy/essences/_essence_audio_editor.html.erb +4 -0
  160. data/app/views/alchemy/essences/_essence_audio_view.html.erb +15 -0
  161. data/app/views/alchemy/essences/_essence_file_editor.html.erb +15 -6
  162. data/app/views/alchemy/essences/_essence_headline_editor.html.erb +36 -0
  163. data/app/views/alchemy/essences/_essence_headline_view.html.erb +10 -0
  164. data/app/views/alchemy/essences/_essence_link_editor.html.erb +8 -4
  165. data/app/views/alchemy/essences/_essence_picture_editor.html.erb +27 -12
  166. data/app/views/alchemy/essences/_essence_picture_view.html.erb +3 -3
  167. data/app/views/alchemy/essences/_essence_text_editor.html.erb +12 -4
  168. data/app/views/alchemy/essences/_essence_video_editor.html.erb +4 -0
  169. data/app/views/alchemy/essences/_essence_video_view.html.erb +18 -0
  170. data/app/views/alchemy/essences/shared/_essence_picture_tools.html.erb +21 -16
  171. data/app/views/alchemy/essences/shared/_linkable_essence_tools.html.erb +2 -2
  172. data/app/views/alchemy/ingredients/_audio_editor.html.erb +5 -0
  173. data/app/views/alchemy/ingredients/_audio_view.html.erb +14 -0
  174. data/app/views/alchemy/ingredients/_boolean_editor.html.erb +11 -0
  175. data/app/views/alchemy/ingredients/_boolean_view.html.erb +1 -0
  176. data/app/views/alchemy/ingredients/_datetime_editor.html.erb +17 -0
  177. data/app/views/alchemy/ingredients/_datetime_view.html.erb +9 -0
  178. data/app/views/alchemy/ingredients/_file_editor.html.erb +52 -0
  179. data/app/views/alchemy/ingredients/_file_view.html.erb +17 -0
  180. data/app/views/alchemy/ingredients/_headline_editor.html.erb +30 -0
  181. data/app/views/alchemy/ingredients/_headline_view.html.erb +9 -0
  182. data/app/views/alchemy/ingredients/_html_editor.html.erb +8 -0
  183. data/app/views/alchemy/ingredients/_html_view.html.erb +1 -0
  184. data/app/views/alchemy/ingredients/_link_editor.html.erb +24 -0
  185. data/app/views/alchemy/ingredients/_link_view.html.erb +9 -0
  186. data/app/views/alchemy/ingredients/_node_editor.html.erb +26 -0
  187. data/app/views/alchemy/ingredients/_node_view.html.erb +1 -0
  188. data/app/views/alchemy/ingredients/_page_editor.html.erb +25 -0
  189. data/app/views/alchemy/ingredients/_page_view.html.erb +4 -0
  190. data/app/views/alchemy/ingredients/_picture_editor.html.erb +60 -0
  191. data/app/views/alchemy/ingredients/_picture_view.html.erb +5 -0
  192. data/app/views/alchemy/ingredients/_richtext_editor.html.erb +12 -0
  193. data/app/views/alchemy/ingredients/_richtext_view.html.erb +3 -0
  194. data/app/views/alchemy/ingredients/_select_editor.html.erb +30 -0
  195. data/app/views/alchemy/ingredients/_select_view.html.erb +1 -0
  196. data/app/views/alchemy/ingredients/_text_editor.html.erb +20 -0
  197. data/app/views/alchemy/ingredients/_text_view.html.erb +16 -0
  198. data/app/views/alchemy/ingredients/_video_editor.html.erb +5 -0
  199. data/app/views/alchemy/ingredients/_video_view.html.erb +17 -0
  200. data/app/views/alchemy/ingredients/shared/_link_tools.html.erb +20 -0
  201. data/app/views/alchemy/ingredients/shared/_picture_tools.html.erb +57 -0
  202. data/config/brakeman.ignore +66 -159
  203. data/config/initializers/dragonfly.rb +10 -0
  204. data/config/locales/alchemy.en.yml +108 -64
  205. data/config/routes.rb +17 -22
  206. data/db/migrate/20201207131309_create_page_versions.rb +19 -0
  207. data/db/migrate/20201207135820_add_page_version_id_to_alchemy_elements.rb +76 -0
  208. data/db/migrate/20210205143548_rename_public_on_and_public_until_on_alchemy_pages.rb +10 -0
  209. data/db/migrate/20210326105046_add_sanitized_body_to_alchemy_essence_richtexts.rb +7 -0
  210. data/db/migrate/20210406093436_add_alchemy_essence_headlines.rb +12 -0
  211. data/db/migrate/20210506135919_create_essence_audios.rb +19 -0
  212. data/db/migrate/20210506140258_create_essence_videos.rb +23 -0
  213. data/db/migrate/20210508091432_create_alchemy_ingredients.rb +22 -0
  214. data/lib/alchemy/admin/preview_url.rb +2 -0
  215. data/lib/alchemy/deprecation.rb +1 -1
  216. data/lib/alchemy/dragonfly/processors/auto_orient.rb +18 -0
  217. data/lib/alchemy/dragonfly/processors/crop_resize.rb +35 -0
  218. data/lib/alchemy/elements_finder.rb +14 -60
  219. data/lib/alchemy/essence.rb +1 -2
  220. data/lib/alchemy/forms/builder.rb +21 -1
  221. data/lib/alchemy/hints.rb +8 -4
  222. data/lib/alchemy/page_layout.rb +0 -13
  223. data/lib/alchemy/permissions.rb +30 -29
  224. data/lib/alchemy/resource.rb +13 -3
  225. data/lib/alchemy/resource_filter.rb +40 -0
  226. data/lib/alchemy/resources_helper.rb +1 -16
  227. data/lib/alchemy/tasks/tidy.rb +29 -0
  228. data/lib/alchemy/test_support.rb +2 -11
  229. data/lib/alchemy/test_support/essence_shared_examples.rb +0 -1
  230. data/lib/alchemy/test_support/factories/element_factory.rb +8 -8
  231. data/lib/alchemy/test_support/factories/essence_audio_factory.rb +7 -0
  232. data/lib/alchemy/test_support/factories/essence_video_factory.rb +7 -0
  233. data/lib/alchemy/test_support/factories/ingredient_factory.rb +25 -0
  234. data/lib/alchemy/test_support/factories/page_factory.rb +20 -1
  235. data/lib/alchemy/test_support/factories/page_version_factory.rb +23 -0
  236. data/lib/alchemy/test_support/having_crop_action_examples.rb +170 -0
  237. data/lib/alchemy/test_support/having_picture_thumbnails_examples.rb +646 -0
  238. data/lib/alchemy/test_support/shared_ingredient_editor_examples.rb +21 -0
  239. data/lib/alchemy/test_support/shared_ingredient_examples.rb +75 -0
  240. data/lib/alchemy/tinymce.rb +17 -0
  241. data/lib/alchemy/upgrader/six_point_zero.rb +21 -0
  242. data/lib/alchemy/upgrader/tasks/add_page_versions.rb +33 -0
  243. data/lib/alchemy/upgrader/tasks/ingredients_migrator.rb +62 -0
  244. data/lib/alchemy/version.rb +1 -1
  245. data/lib/alchemy_cms.rb +1 -0
  246. data/lib/generators/alchemy/elements/elements_generator.rb +1 -0
  247. data/lib/generators/alchemy/elements/templates/view.html.erb +9 -0
  248. data/lib/generators/alchemy/elements/templates/view.html.haml +9 -0
  249. data/lib/generators/alchemy/elements/templates/view.html.slim +9 -0
  250. data/lib/generators/alchemy/ingredient/ingredient_generator.rb +38 -0
  251. data/lib/generators/alchemy/ingredient/templates/editor.html.erb +14 -0
  252. data/lib/generators/alchemy/ingredient/templates/model.rb.tt +13 -0
  253. data/lib/generators/alchemy/ingredient/templates/view.html.erb +1 -0
  254. data/lib/generators/alchemy/install/templates/dragonfly.rb.tt +1 -1
  255. data/lib/generators/alchemy/menus/templates/node.html.erb +1 -1
  256. data/lib/generators/alchemy/menus/templates/node.html.haml +1 -1
  257. data/lib/generators/alchemy/menus/templates/node.html.slim +1 -1
  258. data/lib/generators/alchemy/menus/templates/wrapper.html.erb +1 -1
  259. data/lib/generators/alchemy/menus/templates/wrapper.html.haml +1 -1
  260. data/lib/generators/alchemy/menus/templates/wrapper.html.slim +1 -1
  261. data/lib/tasks/alchemy/thumbnails.rake +4 -2
  262. data/lib/tasks/alchemy/tidy.rake +12 -0
  263. data/lib/tasks/alchemy/upgrade.rake +26 -0
  264. data/package.json +3 -2
  265. data/package/admin.js +11 -1
  266. data/package/src/__tests__/i18n.spec.js +23 -0
  267. data/package/src/file_editors.js +28 -0
  268. data/package/src/i18n.js +1 -3
  269. data/package/src/image_cropper.js +103 -0
  270. data/package/src/image_loader.js +58 -0
  271. data/package/src/node_tree.js +5 -5
  272. data/package/src/picture_editors.js +169 -0
  273. data/package/src/utils/__tests__/ajax.spec.js +20 -12
  274. data/package/src/utils/ajax.js +8 -3
  275. data/vendor/assets/javascripts/jquery_plugins/jquery.Jcrop.min.js +3 -18
  276. data/vendor/assets/stylesheets/jquery.Jcrop.min.scss +2 -28
  277. metadata +292 -55
  278. data/app/assets/javascripts/alchemy/alchemy.image_cropper.js.coffee +0 -44
  279. data/app/assets/javascripts/alchemy/alchemy.trash_window.js.coffee +0 -30
  280. data/app/assets/stylesheets/alchemy/trash.scss +0 -8
  281. data/app/controllers/alchemy/admin/trash_controller.rb +0 -44
  282. data/app/views/alchemy/admin/attachments/_filter_bar.html.erb +0 -29
  283. data/app/views/alchemy/admin/essence_files/assign.js.erb +0 -3
  284. data/app/views/alchemy/admin/essence_pictures/assign.js.erb +0 -4
  285. data/app/views/alchemy/admin/essence_pictures/crop.html.erb +0 -48
  286. data/app/views/alchemy/admin/pictures/_filter_bar.html.erb +0 -30
  287. data/app/views/alchemy/admin/trash/clear.js.erb +0 -4
  288. data/app/views/alchemy/admin/trash/index.html.erb +0 -31
  289. data/lib/alchemy/test_support/factories.rb +0 -16
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ module Ingredients
5
+ # A simple line of text
6
+ #
7
+ # Optionally it can have a link
8
+ #
9
+ class Text < Alchemy::Ingredient
10
+ store_accessor :data,
11
+ :link,
12
+ :link_target,
13
+ :link_title,
14
+ :link_class_name
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ module Ingredients
5
+ # A video attachment
6
+ #
7
+ class Video < Alchemy::Ingredient
8
+ store_accessor :data,
9
+ :allow_fullscreen,
10
+ :autoplay,
11
+ :controls,
12
+ :height,
13
+ :loop,
14
+ :muted,
15
+ :preload,
16
+ :width
17
+
18
+ related_object_alias :attachment, class_name: "Alchemy::Attachment"
19
+
20
+ delegate :name, to: :attachment, allow_nil: true
21
+
22
+ # The first 30 characters of the attachments name
23
+ #
24
+ # Used by the Element#preview_text method.
25
+ #
26
+ # @param [Integer] max_length (30)
27
+ #
28
+ def preview_text(max_length = 30)
29
+ name.to_s[0..max_length - 1]
30
+ end
31
+ end
32
+ end
33
+ end
@@ -54,9 +54,6 @@ module Alchemy
54
54
  after_update :set_pages_language,
55
55
  if: :should_set_pages_language?
56
56
 
57
- after_update :unpublish_pages,
58
- if: :should_unpublish_pages?
59
-
60
57
  before_destroy if: -> { pages.any? } do
61
58
  errors.add(:pages, :still_present)
62
59
  throw(:abort)
@@ -170,13 +167,5 @@ module Alchemy
170
167
  def set_pages_language
171
168
  pages.update_all language_code: code
172
169
  end
173
-
174
- def should_unpublish_pages?
175
- saved_changes[:public] == [true, false]
176
- end
177
-
178
- def unpublish_pages
179
- pages.update_all(public_on: nil, public_until: nil)
180
- end
181
170
  end
182
171
  end
@@ -35,6 +35,12 @@
35
35
  # locked_at :datetime
36
36
  #
37
37
 
38
+ require_dependency "alchemy/page/fixed_attributes"
39
+ require_dependency "alchemy/page/page_scopes"
40
+ require_dependency "alchemy/page/page_natures"
41
+ require_dependency "alchemy/page/page_naming"
42
+ require_dependency "alchemy/page/page_elements"
43
+
38
44
  module Alchemy
39
45
  class Page < BaseRecord
40
46
  include Alchemy::Hints
@@ -109,6 +115,9 @@ module Alchemy
109
115
  has_many :folded_pages
110
116
  has_many :legacy_urls, class_name: "Alchemy::LegacyPageUrl"
111
117
  has_many :nodes, class_name: "Alchemy::Node", inverse_of: :page
118
+ has_many :versions, class_name: "Alchemy::PageVersion", inverse_of: :page, dependent: :destroy
119
+ has_one :draft_version, -> { drafts }, class_name: "Alchemy::PageVersion"
120
+ has_one :public_version, -> { published }, class_name: "Alchemy::PageVersion"
112
121
 
113
122
  before_validation :set_language,
114
123
  if: -> { language.nil? }
@@ -117,6 +126,9 @@ module Alchemy
117
126
  validates_format_of :page_layout, with: /\A[a-z0-9_-]+\z/, unless: -> { page_layout.blank? }
118
127
  validates_presence_of :parent, unless: -> { layoutpage? || language_root? }
119
128
 
129
+ before_create -> { versions.build },
130
+ if: -> { versions.none? }
131
+
120
132
  before_save :set_language_code,
121
133
  if: -> { language.present? }
122
134
 
@@ -126,9 +138,6 @@ module Alchemy
126
138
  before_save :inherit_restricted_status,
127
139
  if: -> { parent && parent.restricted? }
128
140
 
129
- before_save :set_published_at,
130
- if: -> { public_on.present? && published_at.nil? }
131
-
132
141
  before_save :set_fixed_attributes,
133
142
  if: -> { fixed_attributes.any? }
134
143
 
@@ -138,14 +147,22 @@ module Alchemy
138
147
  after_update -> { nodes.update_all(updated_at: Time.current) }
139
148
 
140
149
  # Concerns
141
- include Alchemy::Page::PageScopes
142
- include Alchemy::Page::PageNatures
143
- include Alchemy::Page::PageNaming
144
- include Alchemy::Page::PageElements
150
+ include PageScopes
151
+ include PageNatures
152
+ include PageNaming
153
+ include PageElements
145
154
 
146
155
  # site_name accessor
147
156
  delegate :name, to: :site, prefix: true, allow_nil: true
148
157
 
158
+ # Old public_on and public_until attributes for historical reasons
159
+ #
160
+ # These attributes now exist on the page versions
161
+ #
162
+ attr_readonly :legacy_public_on, :legacy_public_until
163
+ deprecate :legacy_public_on, deprecator: Alchemy::Deprecation
164
+ deprecate :legacy_public_until, deprecator: Alchemy::Deprecation
165
+
149
166
  # Class methods
150
167
  #
151
168
  class << self
@@ -165,7 +182,16 @@ module Alchemy
165
182
  end
166
183
 
167
184
  def alchemy_resource_filters
168
- %w[published not_public restricted]
185
+ [
186
+ {
187
+ name: :by_page_layout,
188
+ values: PageLayout.all.map { |p| [Alchemy.t(p["name"], scope: "page_layout_names"), p["name"]] },
189
+ },
190
+ {
191
+ name: :status,
192
+ values: %w[published not_public restricted],
193
+ },
194
+ ]
169
195
  end
170
196
 
171
197
  def searchable_alchemy_resource_attributes
@@ -207,11 +233,13 @@ module Alchemy
207
233
  # @return [Alchemy::Page]
208
234
  #
209
235
  def copy(source, differences = {})
210
- page = Alchemy::Page.new(attributes_from_source_for_copy(source, differences))
211
- page.tag_list = source.tag_list
212
- if page.save!
213
- copy_elements(source, page)
214
- page
236
+ transaction do
237
+ page = Alchemy::Page.new(attributes_from_source_for_copy(source, differences))
238
+ page.tag_list = source.tag_list
239
+ if page.save!
240
+ copy_elements(source, page)
241
+ page
242
+ end
215
243
  end
216
244
  end
217
245
 
@@ -291,7 +319,9 @@ module Alchemy
291
319
  # Instance methods
292
320
  #
293
321
 
294
- # Returns elements from page.
322
+ # Returns elements from pages public version.
323
+ #
324
+ # You can pass another page_version to load elements from in the options.
295
325
  #
296
326
  # @option options [Array<String>|String] :only
297
327
  # Returns only elements with given names
@@ -310,11 +340,14 @@ module Alchemy
310
340
  # @option options [Class] :finder (Alchemy::ElementsFinder)
311
341
  # A class that will return elements from page.
312
342
  # Use this for your custom element loading logic.
343
+ # @option options [Alchemy::PageVersion] :page_version
344
+ # A page version to load elements from.
345
+ # Uses the pages public_version by default.
313
346
  #
314
347
  # @return [ActiveRecord::Relation]
315
348
  def find_elements(options = {})
316
349
  finder = options[:finder] || Alchemy::ElementsFinder.new(options)
317
- finder.elements(page: self)
350
+ finder.elements(page_version: options[:page_version] || public_version)
318
351
  end
319
352
 
320
353
  # = The url_path for this page
@@ -423,22 +456,36 @@ module Alchemy
423
456
  end
424
457
  end
425
458
 
426
- # Publishes the page.
459
+ # Creates a public version of the page.
427
460
  #
428
- # Sets +public_on+ and the +published_at+ value to current time
429
- # and resets +public_until+ to nil
461
+ # Sets the +published_at+ value to current time
430
462
  #
431
463
  # The +published_at+ attribute is used as +cache_key+.
432
464
  #
433
- def publish!
434
- current_time = Time.current
435
- update_columns(
436
- published_at: current_time,
437
- public_on: already_public_for?(current_time) ? public_on : current_time,
438
- public_until: still_public_for?(current_time) ? public_until : nil,
439
- )
465
+ def publish!(current_time = Time.current)
466
+ update(published_at: current_time)
467
+ PublishPageJob.perform_later(self, public_on: current_time)
440
468
  end
441
469
 
470
+ # Sets the public_on date on the published version
471
+ #
472
+ # Builds a new version if none exists yet.
473
+ # Destroys public version if empty time is set
474
+ #
475
+ def public_on=(time)
476
+ if public_version && time.blank?
477
+ public_version.destroy!
478
+ # Need to reset the public version on the instance so we do not need to reload
479
+ self.public_version = nil
480
+ elsif public_version
481
+ public_version.public_on = time
482
+ elsif time.present?
483
+ versions.build(public_on: time)
484
+ end
485
+ end
486
+
487
+ delegate :public_until=, to: :public_version, allow_nil: true
488
+
442
489
  # Updates an Alchemy::Page based on a new ordering to be applied to it
443
490
  #
444
491
  # Note: Page's urls should not be updated (and a legacy URL created) if nesting is OFF
@@ -460,7 +507,7 @@ module Alchemy
460
507
 
461
508
  # Holds an instance of +FixedAttributes+
462
509
  def fixed_attributes
463
- @_fixed_attributes ||= Alchemy::Page::FixedAttributes.new(self)
510
+ @_fixed_attributes ||= FixedAttributes.new(self)
464
511
  end
465
512
 
466
513
  # True if given attribute name is defined as fixed
@@ -479,12 +526,12 @@ module Alchemy
479
526
  (editor_roles & user.alchemy_roles).any?
480
527
  end
481
528
 
482
- # Returns the value of +public_on+ attribute
529
+ # Returns the value of +public_on+ attribute from public version
483
530
  #
484
531
  # If it's a fixed attribute then the fixed value is returned instead
485
532
  #
486
533
  def public_on
487
- attribute_fixed?(:public_on) ? fixed_attributes[:public_on] : self[:public_on]
534
+ attribute_fixed?(:public_on) ? fixed_attributes[:public_on] : public_version&.public_on
488
535
  end
489
536
 
490
537
  # Returns the value of +public_until+ attribute
@@ -492,7 +539,7 @@ module Alchemy
492
539
  # If it's a fixed attribute then the fixed value is returned instead
493
540
  #
494
541
  def public_until
495
- attribute_fixed?(:public_until) ? fixed_attributes[:public_until] : self[:public_until]
542
+ attribute_fixed?(:public_until) ? fixed_attributes[:public_until] : public_version&.public_until
496
543
  end
497
544
 
498
545
  # Returns the name of the creator of this page.
@@ -556,9 +603,5 @@ module Alchemy
556
603
  def create_legacy_url
557
604
  legacy_urls.find_or_create_by(urlname: urlname_before_last_save)
558
605
  end
559
-
560
- def set_published_at
561
- self.published_at = Time.current
562
- end
563
606
  end
564
607
  end
@@ -1,66 +1,68 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Alchemy
4
- # = Fixed page attributes
5
- #
6
- # Fixed page attributes are not allowed to be changed by the user.
7
- #
8
- # Define fixed page attributes on the page layout definition of a page.
9
- #
10
- # == Example
11
- #
12
- # # page_layout.yml
13
- # - name: Index
14
- # unique: true
15
- # fixed_attributes:
16
- # - public_on: nil
17
- # - public_until: nil
18
- #
19
- class Page::FixedAttributes
20
- attr_reader :page
21
-
22
- def initialize(page)
23
- @page = page
24
- end
25
-
26
- # All fixed attributes defined on page
4
+ class Page < BaseRecord
5
+ # = Fixed page attributes
27
6
  #
28
- # Aliased as +#all+
7
+ # Fixed page attributes are not allowed to be changed by the user.
29
8
  #
30
- # @return Hash
9
+ # Define fixed page attributes on the page layout definition of a page.
31
10
  #
32
- def attributes
33
- @_attributes ||= page.definition.fetch("fixed_attributes", {}).symbolize_keys
34
- end
35
- alias_method :all, :attributes
36
-
37
- # True if fixed attributes are defined on page
38
- #
39
- # Aliased as +#present?+
11
+ # == Example
40
12
  #
41
- # @return Boolean
13
+ # # page_layout.yml
14
+ # - name: Index
15
+ # unique: true
16
+ # fixed_attributes:
17
+ # - public_on: nil
18
+ # - public_until: nil
42
19
  #
43
- def any?
44
- attributes.present?
45
- end
46
- alias_method :present?, :any?
20
+ class FixedAttributes
21
+ attr_reader :page
47
22
 
48
- # True if given attribute name is defined on page
49
- #
50
- # @return Boolean
51
- #
52
- def fixed?(name)
53
- return false if name.nil?
23
+ def initialize(page)
24
+ @page = page
25
+ end
54
26
 
55
- attributes.key?(name.to_sym)
56
- end
27
+ # All fixed attributes defined on page
28
+ #
29
+ # Aliased as +#all+
30
+ #
31
+ # @return Hash
32
+ #
33
+ def attributes
34
+ @_attributes ||= page.definition.fetch("fixed_attributes", {}).symbolize_keys
35
+ end
36
+ alias_method :all, :attributes
57
37
 
58
- # Returns the attribute by key
59
- #
60
- def [](name)
61
- return nil if name.nil?
38
+ # True if fixed attributes are defined on page
39
+ #
40
+ # Aliased as +#present?+
41
+ #
42
+ # @return Boolean
43
+ #
44
+ def any?
45
+ attributes.present?
46
+ end
47
+ alias_method :present?, :any?
48
+
49
+ # True if given attribute name is defined on page
50
+ #
51
+ # @return Boolean
52
+ #
53
+ def fixed?(name)
54
+ return false if name.nil?
55
+
56
+ attributes.key?(name.to_sym)
57
+ end
58
+
59
+ # Returns the attribute by key
60
+ #
61
+ def [](name)
62
+ return nil if name.nil?
62
63
 
63
- attributes[name.to_sym]
64
+ attributes[name.to_sym]
65
+ end
64
66
  end
65
67
  end
66
68
  end
@@ -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