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
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddPageVersionIdToAlchemyElements < ActiveRecord::Migration[5.2]
4
+ class LocalPage < ActiveRecord::Base
5
+ self.table_name = :alchemy_pages
6
+ has_many :elements, class_name: "LocalElement", inverse_of: :page
7
+ has_many :versions, class_name: "LocalVersion", inverse_of: :page, foreign_key: :page_id
8
+ end
9
+
10
+ class LocalVersion < ActiveRecord::Base
11
+ self.table_name = :alchemy_page_versions
12
+ belongs_to :page, class_name: "LocalPage", inverse_of: :versions
13
+ has_many :elements, class_name: "LocalElement", inverse_of: :versions
14
+ end
15
+
16
+ class LocalElement < ActiveRecord::Base
17
+ self.table_name = :alchemy_elements
18
+ belongs_to :page, class_name: "LocalPage", inverse_of: :elements
19
+ belongs_to :page_version, class_name: "LocalVersion", inverse_of: :elements
20
+ end
21
+
22
+ def change
23
+ add_reference :alchemy_elements, :page_version,
24
+ index: false,
25
+ foreign_key: {
26
+ to_table: :alchemy_page_versions,
27
+ on_delete: :cascade,
28
+ }
29
+ add_index :alchemy_elements, [:page_version_id, :parent_element_id],
30
+ name: "idx_alchemy_elements_on_page_version_id_and_parent_element_id"
31
+ add_index :alchemy_elements, [:page_version_id, :position],
32
+ name: "idx_alchemy_elements_on_page_version_id_and_position"
33
+
34
+ # Add a page version for each page so we can add a not null constraint
35
+ reversible do |dir|
36
+ dir.up do
37
+ say_with_time "Create draft version for each page." do
38
+ LocalPage.find_each do |page|
39
+ next if page.versions.any?
40
+
41
+ page.versions.create!.tap do |version|
42
+ Alchemy::Element.where(page_id: page.id).update_all(page_version_id: version.id)
43
+ end
44
+ end
45
+ LocalVersion.count
46
+ end
47
+ end
48
+ end
49
+
50
+ change_column_null :alchemy_elements, :page_version_id, false
51
+
52
+ # Remove the existing page relation
53
+ remove_reference :alchemy_elements, :page,
54
+ null: false,
55
+ index: false,
56
+ foreign_key: {
57
+ to_table: :alchemy_pages,
58
+ on_delete: :cascade,
59
+ on_update: :cascade,
60
+ }
61
+ if index_exists? :alchemy_elements,
62
+ :parent_element_id,
63
+ name: "index_alchemy_elements_on_page_id_and_parent_element_id"
64
+ remove_index :alchemy_elements,
65
+ column: [:parent_element_id],
66
+ name: "index_alchemy_elements_on_page_id_and_parent_element_id"
67
+ end
68
+ if index_exists? :alchemy_elements,
69
+ :position,
70
+ name: "index_elements_on_page_id_and_position"
71
+ remove_index :alchemy_elements,
72
+ column: [:position],
73
+ name: "index_elements_on_page_id_and_position"
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RenamePublicOnAndPublicUntilOnAlchemyPages < ActiveRecord::Migration[6.0]
4
+ def change
5
+ remove_index :alchemy_pages, column: [:public_on, :public_until],
6
+ name: "index_alchemy_pages_on_public_on_and_public_until"
7
+ rename_column :alchemy_pages, :public_on, :legacy_public_on
8
+ rename_column :alchemy_pages, :public_until, :legacy_public_until
9
+ end
10
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddSanitizedBodyToAlchemyEssenceRichtexts < ActiveRecord::Migration[6.0]
4
+ def change
5
+ add_column :alchemy_essence_richtexts, :sanitized_body, :text
6
+ end
7
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddAlchemyEssenceHeadlines < ActiveRecord::Migration[6.0]
4
+ def change
5
+ create_table :alchemy_essence_headlines do |t|
6
+ t.text :body
7
+ t.integer :level
8
+ t.integer :size
9
+ t.timestamps
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateEssenceAudios < ActiveRecord::Migration[6.0]
4
+ def up
5
+ return if table_exists? :alchemy_essence_audios
6
+
7
+ create_table :alchemy_essence_audios do |t|
8
+ t.references :attachment
9
+ t.boolean :controls, default: true, null: false
10
+ t.boolean :autoplay, default: false
11
+ t.boolean :loop, default: false, null: false
12
+ t.boolean :muted, default: false, null: false
13
+ end
14
+ end
15
+
16
+ def down
17
+ drop_table :alchemy_essence_audios
18
+ end
19
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateEssenceVideos < ActiveRecord::Migration[6.0]
4
+ def up
5
+ return if table_exists? :alchemy_essence_videos
6
+
7
+ create_table :alchemy_essence_videos do |t|
8
+ t.references :attachment
9
+ t.string :width
10
+ t.string :height
11
+ t.boolean :allow_fullscreen, default: true, null: false
12
+ t.boolean :autoplay, default: false, null: false
13
+ t.boolean :controls, default: true, null: false
14
+ t.boolean :loop, default: false, null: false
15
+ t.boolean :muted, default: false, null: false
16
+ t.string :preload
17
+ end
18
+ end
19
+
20
+ def down
21
+ drop_table :alchemy_essence_videos
22
+ end
23
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateAlchemyIngredients < ActiveRecord::Migration[6.0]
4
+ def change
5
+ create_table :alchemy_ingredients do |t|
6
+ t.references :element, null: false, foreign_key: { to_table: :alchemy_elements, on_delete: :cascade }
7
+ t.string :type, index: true, null: false
8
+ t.string :role, null: false
9
+ t.text :value
10
+ if ActiveRecord::Migration.connection.adapter_name.match?(/postgres/i)
11
+ t.jsonb :data, default: {}
12
+ else
13
+ t.json :data
14
+ end
15
+ t.belongs_to :related_object, null: true, polymorphic: true, index: false
16
+ t.index [:element_id, :role], unique: true
17
+ t.index [:related_object_id, :related_object_type], name: "idx_alchemy_ingredient_relation"
18
+
19
+ t.timestamps
20
+ end
21
+ end
22
+ end
@@ -45,8 +45,10 @@ module Alchemy
45
45
  if @preview_config && uri
46
46
  uri_class.build(
47
47
  host: uri.host,
48
+ port: uri.port,
48
49
  path: page.url_path,
49
50
  userinfo: userinfo,
51
+ query: { alchemy_preview_mode: true }.to_param,
50
52
  ).to_s
51
53
  else
52
54
  routes.admin_page_path(page)
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Alchemy
3
- Deprecation = ActiveSupport::Deprecation.new("6.0", "Alchemy")
3
+ Deprecation = ActiveSupport::Deprecation.new("6.1", "Alchemy")
4
4
  end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dragonfly/image_magick/commands"
4
+
5
+ module Alchemy
6
+ module Dragonfly
7
+ module Processors
8
+ class AutoOrient
9
+ def call(content)
10
+ ::Dragonfly::ImageMagick::Commands.convert(
11
+ content,
12
+ "-auto-orient"
13
+ )
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dragonfly/image_magick/commands"
4
+
5
+ module Alchemy
6
+ module Dragonfly
7
+ module Processors
8
+ class CropResize
9
+ include ::Dragonfly::ParamValidators
10
+
11
+ IS_CROP_ARGUMENT = ->(args_string) {
12
+ args_string.match?(::Dragonfly::ImageMagick::Processors::Thumb::CROP_GEOMETRY)
13
+ }
14
+
15
+ IS_RESIZE_ARGUMENT = ->(args_string) {
16
+ args_string.match?(::Dragonfly::ImageMagick::Processors::Thumb::RESIZE_GEOMETRY)
17
+ }
18
+
19
+ def call(content, crop_argument, resize_argument)
20
+ validate!(crop_argument, &IS_CROP_ARGUMENT)
21
+ validate!(resize_argument, &IS_RESIZE_ARGUMENT)
22
+ ::Dragonfly::ImageMagick::Commands.convert(
23
+ content,
24
+ "-crop #{crop_argument} -resize #{resize_argument}"
25
+ )
26
+ end
27
+
28
+ def update_url(attrs, _args = "", opts = {})
29
+ format = opts["format"]
30
+ attrs.ext = format if format
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -3,7 +3,7 @@
3
3
  require "alchemy/logger"
4
4
 
5
5
  module Alchemy
6
- # Loads elements from given page
6
+ # Loads elements from given page version.
7
7
  #
8
8
  # Used by {Alchemy::Page#find_elements} and {Alchemy::ElementsHelper#render_elements} helper.
9
9
  #
@@ -30,22 +30,18 @@ module Alchemy
30
30
  @options = options
31
31
  end
32
32
 
33
- # @param page [Alchemy::Page|String]
34
- # The page the elements are loaded from. You can pass a page_layout String or a {Alchemy::Page} object.
35
- # @return [ActiveRecord::Relation]
36
- def elements(page:)
37
- elements = find_elements(page)
38
-
39
- if fallback_required?(elements)
40
- elements = elements.merge(fallback_elements)
41
- end
33
+ # @param page [Alchemy::PageVersion]
34
+ # The page version the elements are loaded from.
35
+ # @return [Alchemy::ElementsRepository]
36
+ def elements(page_version:)
37
+ elements = find_elements(page_version)
42
38
 
43
39
  if options[:reverse]
44
- elements = elements.reverse_order
40
+ elements = elements.reverse
45
41
  end
46
42
 
47
43
  if options[:random]
48
- elements = elements.reorder(Arel.sql(random_function))
44
+ elements = elements.random
49
45
  end
50
46
 
51
47
  elements.offset(options[:offset]).limit(options[:count])
@@ -53,17 +49,14 @@ module Alchemy
53
49
 
54
50
  private
55
51
 
56
- attr_reader :page, :options
52
+ attr_reader :options
57
53
 
58
- def find_elements(page_or_layout)
59
- @page = get_page(page_or_layout)
60
- return Alchemy::Element.none unless page
54
+ def find_elements(page_version)
55
+ return Alchemy::ElementsRepository.none unless page_version
61
56
 
62
- if options[:fixed]
63
- elements = page.fixed_elements
64
- else
65
- elements = page.elements
66
- end
57
+ elements = Alchemy::ElementsRepository.new(page_version.elements.available)
58
+ elements = elements.not_nested
59
+ elements = options[:fixed] ? elements.fixed : elements.unfixed
67
60
 
68
61
  if options[:only]
69
62
  elements = elements.named(options[:only])
@@ -75,44 +68,5 @@ module Alchemy
75
68
 
76
69
  elements
77
70
  end
78
-
79
- def get_page(page_or_layout)
80
- case page_or_layout
81
- when Alchemy::Page
82
- page_or_layout
83
- when String
84
- Alchemy::Deprecation.warn "Passing a String as `from_page` option to " \
85
- "`render_elements` is deprecated and will be removed with Alchemy 6.0. " \
86
- "Please load the page beforehand and pass it as an object instead."
87
- Alchemy::Page.find_by(
88
- language: Alchemy::Language.current,
89
- page_layout: page_or_layout,
90
- restricted: false,
91
- )
92
- end
93
- end
94
-
95
- def fallback_required?(elements)
96
- if options[:fallback]
97
- Alchemy::Deprecation.warn "Passing `fallback` options to `render_elements` is deprecated an will be removed with Alchemy 6.0."
98
- end
99
- options[:fallback] && elements
100
- .where(Alchemy::Element.table_name => {name: options[:fallback][:for]})
101
- .none?
102
- end
103
-
104
- def fallback_elements
105
- find_elements(options[:fallback][:from])
106
- .named(options[:fallback][:with] || options[:fallback][:for])
107
- end
108
-
109
- def random_function
110
- case ActiveRecord::Base.connection_config[:adapter]
111
- when "postgresql", "sqlite3"
112
- "RANDOM()"
113
- else
114
- "RAND()"
115
- end
116
- end
117
71
  end
118
72
  end
@@ -40,7 +40,7 @@ module Alchemy
40
40
  if Alchemy.user_class
41
41
  ActiveSupport.on_load(:active_record) do
42
42
  Alchemy.user_class.model_stamper
43
- Alchemy.user_class.stampable(stamper_class_name: Alchemy.user_class.name)
43
+ Alchemy.user_class.stampable(stamper_class_name: Alchemy.user_class_name)
44
44
  end
45
45
  end
46
46
  end
@@ -59,10 +59,9 @@ module Alchemy #:nodoc:
59
59
  scope :from_element, ->(name) { joins(:element).where(Element.table_name => { name: name }) }
60
60
 
61
61
  delegate :restricted?, to: :page, allow_nil: true
62
- delegate :trashed?, to: :element, allow_nil: true
63
62
  delegate :public?, to: :element, allow_nil: true
64
63
 
65
- after_save :touch_element
64
+ after_update :touch_element
66
65
 
67
66
  def acts_as_essence_class
68
67
  #{name}
data/lib/alchemy/hints.rb CHANGED
@@ -35,21 +35,25 @@ module Alchemy
35
35
  # @return String
36
36
  #
37
37
  def hint
38
- hint = definition["hint"]
38
+ hint = definition[:hint]
39
39
  if hint == true
40
- Alchemy.t(name, scope: hint_translation_scope)
40
+ Alchemy.t(hint_translation_attribute, scope: hint_translation_scope)
41
41
  else
42
42
  hint
43
43
  end
44
44
  end
45
45
 
46
- # Returns true if the element has a hint
46
+ # Returns true if the element has a hint defined
47
47
  def has_hint?
48
- hint.present?
48
+ !!definition[:hint]
49
49
  end
50
50
 
51
51
  private
52
52
 
53
+ def hint_translation_attribute
54
+ name
55
+ end
56
+
53
57
  def hint_translation_scope
54
58
  "#{self.class.model_name.to_s.demodulize.downcase}_hints"
55
59
  end
@@ -63,19 +63,6 @@ module Alchemy
63
63
  mapped_layouts_for_select(selectable_layouts(language_id, only_layoutpages))
64
64
  end
65
65
 
66
- # Returns page layouts including given layout ready for Rails' select form helper.
67
- #
68
- def layouts_with_own_for_select(page_layout_name, language_id, only_layoutpages = false)
69
- layouts = selectable_layouts(language_id, only_layoutpages)
70
- if layouts.detect { |l| l["name"] == page_layout_name }.nil?
71
- @map_array = [[human_layout_name(page_layout_name), page_layout_name]]
72
- else
73
- @map_array = []
74
- end
75
- mapped_layouts_for_select(layouts)
76
- end
77
- deprecate :layouts_with_own_for_select, deprecator: Alchemy::Deprecation
78
-
79
66
  # Returns all layouts that can be used for creating a new page.
80
67
  #
81
68
  # It removes all layouts from available layouts that are unique and already taken and that are marked as hide.
@@ -36,14 +36,13 @@ module Alchemy
36
36
  module GuestUser
37
37
  def alchemy_guest_user_rules
38
38
  can([:show, :download], Alchemy::Attachment) { |a| !a.restricted? }
39
- can :see, Alchemy::Page, restricted: false
40
39
 
41
40
  can :read, Alchemy::Content, Alchemy::Content.available.not_restricted do |c|
42
- c.public? && !c.restricted? && !c.trashed?
41
+ c.public? && !c.restricted?
43
42
  end
44
43
 
45
44
  can :read, Alchemy::Element, Alchemy::Element.available.not_restricted do |e|
46
- e.public? && !e.restricted? && !e.trashed?
45
+ e.public? && !e.restricted?
47
46
  end
48
47
 
49
48
  can :read, Alchemy::Page, Alchemy::Page.published.not_restricted do |p|
@@ -64,14 +63,13 @@ module Alchemy
64
63
 
65
64
  # Resources
66
65
  can [:show, :download], Alchemy::Attachment
67
- can :see, Alchemy::Page, restricted: true
68
66
 
69
67
  can :read, Alchemy::Content, Alchemy::Content.available do |c|
70
- c.public? && !c.trashed?
68
+ c.public?
71
69
  end
72
70
 
73
71
  can :read, Alchemy::Element, Alchemy::Element.available do |e|
74
- e.public? && !e.trashed?
72
+ e.public?
75
73
  end
76
74
 
77
75
  can :read, Alchemy::Page, Alchemy::Page.published do |p|
@@ -103,24 +101,27 @@ module Alchemy
103
101
  ]
104
102
 
105
103
  # Controller actions
106
- can :leave, :alchemy_admin
107
- can [:info, :help], :alchemy_admin_dashboard
108
- can :manage, :alchemy_admin_clipboard
109
- can :index, :trash
110
- can :edit, :alchemy_admin_layoutpages
111
- can :tree, :alchemy_admin_pages
104
+ can :leave, :alchemy_admin
105
+ can [:info, :help], :alchemy_admin_dashboard
106
+ can :manage, :alchemy_admin_clipboard
107
+ can :edit, :alchemy_admin_layoutpages
108
+ can :tree, :alchemy_admin_pages
112
109
 
113
110
  # Resources
114
- can [:read, :download], Alchemy::Attachment
115
- can :manage, Alchemy::Content
116
- can :manage, Alchemy::Element
117
- can :manage, Alchemy::EssenceFile
118
- can :manage, Alchemy::EssencePicture
119
- can :manage, Alchemy::LegacyPageUrl
120
- can :manage, Alchemy::Node
121
- can :read, Alchemy::Picture
111
+ can [:read, :download], Alchemy::Attachment
112
+ can :manage, Alchemy::Content
113
+ can :manage, Alchemy::Element
114
+ can :manage, Alchemy::EssenceAudio
115
+ can :manage, Alchemy::EssenceFile
116
+ can :manage, Alchemy::EssencePicture
117
+ can :manage, Alchemy::EssenceVideo
118
+ can :manage, Alchemy::Ingredient
119
+ can [:crop], Alchemy::Ingredients::Picture
120
+ can :manage, Alchemy::LegacyPageUrl
121
+ can :manage, Alchemy::Node
122
+ can [:read, :url], Alchemy::Picture
122
123
  can [:read, :autocomplete], Alchemy::Tag
123
- can(:edit_content, Alchemy::Page) { |p| p.editable_by?(@user) }
124
+ can(:edit_content, Alchemy::Page) { |p| p.editable_by?(@user) }
124
125
  end
125
126
  end
126
127
 
@@ -140,9 +141,6 @@ module Alchemy
140
141
  :alchemy_admin_users,
141
142
  ]
142
143
 
143
- # Controller actions
144
- can :clear, :trash
145
-
146
144
  # Resources
147
145
  can [
148
146
  :copy,
@@ -164,13 +162,16 @@ module Alchemy
164
162
  can([
165
163
  :create,
166
164
  :destroy,
167
- :publish,
168
165
  ], Alchemy::Page) { |p| p.editable_by?(@user) }
169
166
 
167
+ can(:publish, Alchemy::Page) do |page|
168
+ page.language.public? && page.editable_by?(@user)
169
+ end
170
+
170
171
  can :manage, Alchemy::Picture
171
172
  can :manage, Alchemy::Attachment
172
173
  can :manage, Alchemy::Tag
173
- can :index, Alchemy::Language
174
+ can :index, Alchemy::Language
174
175
  end
175
176
  end
176
177
 
@@ -185,14 +186,14 @@ module Alchemy
185
186
  alchemy_editor_rules
186
187
 
187
188
  # Navigation
188
- can :index, [:alchemy_admin_sites, :alchemy_admin_styleguide]
189
+ can :index, [:alchemy_admin_sites, :alchemy_admin_styleguide]
189
190
 
190
191
  # Controller actions
191
192
  can [:info, :update_check], :alchemy_admin_dashboard
192
193
 
193
194
  # Resources
194
- can :manage, Alchemy::Language
195
- can :manage, Alchemy::Site
195
+ can :manage, Alchemy::Language
196
+ can :manage, Alchemy::Site
196
197
  end
197
198
  end
198
199
 
@@ -286,6 +286,11 @@ module Alchemy
286
286
  resource_relation(column_name).try(:[], :attr_type)
287
287
  end
288
288
 
289
+ def resource_relation_class(association)
290
+ class_name = association.options[:class_name] || association.name.to_s.classify
291
+ class_name.constantize
292
+ end
293
+
289
294
  def resource_column_type(col)
290
295
  resource_relation_type(col.name) || (col.try(:array) ? :array : col.type)
291
296
  end
@@ -298,10 +303,15 @@ module Alchemy
298
303
  def map_relations
299
304
  self.resource_relations = {}
300
305
  model.alchemy_resource_relations.each do |name, options|
301
- name = name.to_s.gsub(/_id$/, "") # ensure that we don't have an id
302
- association = association_from_relation_name(name)
306
+ relation_name = name.to_s.gsub(/_id$/, "") # ensure that we don't have an id
307
+ association = association_from_relation_name(relation_name)
303
308
  foreign_key = association.options[:foreign_key] || "#{association.name}_id".to_sym
304
- resource_relations[foreign_key] = options.merge(model_association: association, name: name)
309
+ collection = options[:collection] || resource_relation_class(association).all
310
+ resource_relations[foreign_key] = options.merge(
311
+ model_association: association,
312
+ name: relation_name,
313
+ collection: collection,
314
+ )
305
315
  end
306
316
  end
307
317
 
@@ -62,6 +62,23 @@ module Alchemy
62
62
  end
63
63
  end
64
64
 
65
+ def remove_trashed_elements
66
+ puts "\n## Removing trashed elements"
67
+ elements = Alchemy::Element.unscoped.where(position: nil)
68
+ if elements.any?
69
+ log "Destroying #{elements.size} trashed elements"
70
+ nested_elements, parent_elements = elements.partition(&:parent_element_id)
71
+ (nested_elements + parent_elements).each do |element|
72
+ element.destroy
73
+ print "."
74
+ end
75
+ puts "\n"
76
+ log "Done", :message
77
+ else
78
+ log "No trashed elements found", :skip
79
+ end
80
+ end
81
+
65
82
  def remove_orphaned_contents
66
83
  puts "\n## Removing orphaned contents"
67
84
  contents = Alchemy::Content.unscoped.all
@@ -81,6 +98,18 @@ module Alchemy
81
98
  end
82
99
  end
83
100
 
101
+ def remove_duplicate_legacy_urls
102
+ puts "\n## Removing duplicate legacy URLs"
103
+ sql = <<~SQL
104
+ DELETE FROM alchemy_legacy_page_urls A USING alchemy_legacy_page_urls B
105
+ WHERE A.page_id = B.page_id
106
+ AND A.urlname = B.urlname
107
+ AND A.id < B.id
108
+ SQL
109
+ count = ActiveRecord::Base.connection.exec_delete(sql)
110
+ log "Deleted #{count} duplicate legacy URLs"
111
+ end
112
+
84
113
  private
85
114
 
86
115
  def destroy_orphaned_records(records, class_name)
@@ -265,7 +265,6 @@ RSpec.shared_examples_for "an essence" do
265
265
 
266
266
  context "delegations" do
267
267
  it { should delegate_method(:restricted?).to(:page) }
268
- it { should delegate_method(:trashed?).to(:element) }
269
268
  it { should delegate_method(:public?).to(:element) }
270
269
  end
271
270