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,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.shared_examples_for "an alchemy ingredient editor" do
4
+ let(:ingredient_editor) { Alchemy::IngredientEditor.new(ingredient) }
5
+
6
+ before do
7
+ view.class.send :include, Alchemy::Admin::BaseHelper
8
+ view.class.send :include, Alchemy::Admin::IngredientsHelper
9
+ allow(element_editor).to receive(:ingredients) { [ingredient_editor] }
10
+ end
11
+
12
+ subject do
13
+ render element_editor
14
+ rendered
15
+ end
16
+
17
+ it "renders a ingredient editor", :aggregate_failures do
18
+ is_expected.to have_css(".ingredient-editor.#{ingredient_editor.partial_name}")
19
+ is_expected.to have_css("[data-ingredient-role]")
20
+ end
21
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "shoulda-matchers"
4
+
5
+ RSpec.shared_examples_for "an alchemy ingredient" do
6
+ let(:element) { build(:alchemy_element, name: "element_with_ingredients") }
7
+
8
+ subject(:ingredient) do
9
+ described_class.new(
10
+ element: element,
11
+ role: "headline",
12
+ )
13
+ end
14
+
15
+ it { is_expected.to belong_to(:element).touch(true).class_name("Alchemy::Element") }
16
+ it { is_expected.to belong_to(:related_object).optional }
17
+ it { is_expected.to validate_presence_of(:role) }
18
+ it { is_expected.to validate_presence_of(:type) }
19
+ it { expect(subject.data).to eq({}) }
20
+
21
+ describe "#settings" do
22
+ subject { ingredient.settings }
23
+
24
+ context "without element" do
25
+ let(:element) { nil }
26
+
27
+ it { is_expected.to eq({}) }
28
+ end
29
+
30
+ context "with element" do
31
+ before do
32
+ expect(element).to receive(:ingredient_definition_for) do
33
+ {
34
+ settings: {
35
+ linkable: true,
36
+ },
37
+ }.with_indifferent_access
38
+ end
39
+ end
40
+
41
+ it { is_expected.to eq({ linkable: true }.with_indifferent_access) }
42
+ end
43
+ end
44
+
45
+ describe "#definition" do
46
+ subject { ingredient.definition }
47
+
48
+ context "without element" do
49
+ let(:element) { nil }
50
+
51
+ it { is_expected.to eq({}) }
52
+ end
53
+
54
+ context "with element" do
55
+ let(:definition) do
56
+ {
57
+ role: "headline",
58
+ type: "Text",
59
+ default: "Hello World",
60
+ settings: {
61
+ linkable: true,
62
+ },
63
+ }.with_indifferent_access
64
+ end
65
+
66
+ before do
67
+ expect(element).to receive(:ingredient_definition_for) { definition }
68
+ end
69
+
70
+ it "returns ingredient definition" do
71
+ is_expected.to eq(definition)
72
+ end
73
+ end
74
+ end
75
+ end
@@ -38,6 +38,10 @@ module Alchemy
38
38
  content_definitions_from_elements(page.descendent_element_definitions)
39
39
  end
40
40
 
41
+ def custom_config_ingredients(page)
42
+ ingredient_definitions_from_elements(page.descendent_element_definitions)
43
+ end
44
+
41
45
  private
42
46
 
43
47
  def content_definitions_from_elements(definitions)
@@ -52,6 +56,19 @@ module Alchemy
52
56
  contents.map { |c| c.merge("element" => el["name"]) }
53
57
  end.flatten.compact
54
58
  end
59
+
60
+ def ingredient_definitions_from_elements(definitions)
61
+ definitions.collect do |el|
62
+ next if el["ingredients"].blank?
63
+
64
+ ingredients = el["ingredients"].select do |c|
65
+ c["settings"] && c["settings"]["tinymce"].is_a?(Hash)
66
+ end
67
+ next if ingredients.blank?
68
+
69
+ ingredients.map { |c| c.merge("element" => el["name"]) }
70
+ end.flatten.compact
71
+ end
55
72
  end
56
73
  end
57
74
  end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "tasks/add_page_versions"
4
+ require_relative "tasks/ingredients_migrator"
5
+
6
+ module Alchemy
7
+ class Upgrader::SixPointZero < Upgrader
8
+ class << self
9
+ def create_public_page_versions
10
+ desc "Create public page versions for pages"
11
+ Alchemy::Upgrader::Tasks::AddPageVersions.new.create_public_page_versions
12
+ end
13
+
14
+ def create_ingredients
15
+ desc "Create ingredients for elements with ingredients defined"
16
+ Alchemy::Upgrader::Tasks::IngredientsMigrator.new.create_ingredients
17
+ log "Done.", :success
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "alchemy/upgrader"
4
+
5
+ module Alchemy::Upgrader::Tasks
6
+ class AddPageVersions < Thor
7
+ include Thor::Actions
8
+
9
+ no_tasks do
10
+ def create_public_page_versions
11
+ Alchemy::Deprecation.silence do
12
+ Alchemy::Page.where.not(legacy_public_on: nil).find_each do |page|
13
+ next if page.versions.published.any?
14
+
15
+ Alchemy::Page.transaction do
16
+ page.versions.create!(
17
+ public_on: page.legacy_public_on,
18
+ public_until: page.legacy_public_until
19
+ ).tap do |version|
20
+ # We must not use .find_each here to not mess up the order of elements
21
+ page.draft_version.elements.not_nested.available.each do |element|
22
+ Alchemy::Element.copy(element, page_version_id: version.id)
23
+ end
24
+ end
25
+ end
26
+
27
+ print "."
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "alchemy/upgrader"
4
+
5
+ module Alchemy::Upgrader::Tasks
6
+ class IngredientsMigrator < Thor
7
+ include Thor::Actions
8
+
9
+ no_tasks do
10
+ def create_ingredients
11
+ Alchemy::Deprecation.silence do
12
+ elements_with_ingredients = Alchemy::ElementDefinition.all.select { |d| d.key?(:ingredients) }
13
+ if ENV["ONLY"]
14
+ elements_with_ingredients = elements_with_ingredients.select { |d| d[:name].in? ENV["ONLY"].split(",") }
15
+ end
16
+ # eager load all elements that have ingredients defined but no ingredient records yet.
17
+ all_elements = Alchemy::Element
18
+ .named(elements_with_ingredients.map { |d| d[:name] })
19
+ .includes(contents: { essence: :ingredient_association })
20
+ .left_outer_joins(:ingredients).where(alchemy_ingredients: { id: nil })
21
+ .to_a
22
+ elements_with_ingredients.map do |element_definition|
23
+ elements = all_elements.select { |e| e.name == element_definition[:name] }
24
+ if elements.any?
25
+ puts "-- Creating ingredients for #{elements.count} #{element_definition[:name]}(s)"
26
+ elements.each do |element|
27
+ Alchemy::Element.transaction do
28
+ element_definition[:ingredients].each do |ingredient_definition|
29
+ content = element.content_by_name(ingredient_definition[:role])
30
+ next unless content
31
+
32
+ essence = content.essence
33
+ ingredient = element.ingredients.build(
34
+ role: ingredient_definition[:role],
35
+ type: Alchemy::Ingredient.normalize_type(ingredient_definition[:type]),
36
+ )
37
+ belongs_to_associations = essence.class.reflect_on_all_associations(:belongs_to)
38
+ if belongs_to_associations.any?
39
+ ingredient.related_object = essence.public_send(belongs_to_associations.first.name)
40
+ else
41
+ ingredient.value = content.ingredient
42
+ end
43
+ data = ingredient.class.stored_attributes.fetch(:data, []).each_with_object({}) do |attr, d|
44
+ d[attr] = essence.public_send(attr)
45
+ end
46
+ ingredient.data = data
47
+ print "."
48
+ ingredient.save!
49
+ content.destroy!
50
+ end
51
+ end
52
+ end
53
+ puts "\n"
54
+ else
55
+ puts "-- No #{element_definition[:name]} elements found for migration."
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Alchemy
4
- VERSION = "5.2.0"
4
+ VERSION = "6.0.0.b3"
5
5
 
6
6
  def self.version
7
7
  VERSION
data/lib/alchemy_cms.rb CHANGED
@@ -54,6 +54,7 @@ require_relative "alchemy/permissions"
54
54
  require_relative "alchemy/resource"
55
55
  require_relative "alchemy/tinymce"
56
56
  require_relative "alchemy/taggable"
57
+ require_relative "alchemy/version"
57
58
 
58
59
  # Require hacks
59
60
  require_relative "kaminari/scoped_pagination_url_helper"
@@ -14,6 +14,7 @@ module Alchemy
14
14
  @elements.each do |element|
15
15
  @element = element
16
16
  @contents = element["contents"] || []
17
+ @ingredients = element["ingredients"] || []
17
18
  @element_name = element_name(element)
18
19
  conditional_template "view.html.#{template_engine}", "#{elements_dir}/_#{@element_name}.html.#{template_engine}"
19
20
  end
@@ -9,6 +9,15 @@
9
9
  <%%= el.render :<%= content["name"] %> %>
10
10
  <%- end -%>
11
11
  <%- end -%>
12
+ <%- @ingredients.each do |ingredient| -%>
13
+ <%- if @ingredients.length > 1 -%>
14
+ <div class="<%= ingredient["role"] %>">
15
+ <%%= el.render(:<%= ingredient["role"] %>) %>
16
+ </div>
17
+ <%- else -%>
18
+ <%%= el.render(:<%= ingredient["role"] %>) %>
19
+ <%- end -%>
20
+ <%- end -%>
12
21
  <%- if @element['nestable_elements'].present? -%>
13
22
  <%%= render <%= @element_name %>.nested_elements.available %>
14
23
  <%- end -%>
@@ -8,6 +8,15 @@
8
8
  = el.render :<%= content["name"] %>
9
9
  <%- end -%>
10
10
  <%- end -%>
11
+ <%- @ingredients.each do |ingredient| -%>
12
+ <%- if @ingredients.length > 1 -%>
13
+ .<%= ingredient["role"] %>
14
+ = el.render(:<%= ingredient["role"] %>)
15
+ </div>
16
+ <%- else -%>
17
+ = el.render(:<%= ingredient["role"] %>)
18
+ <%- end -%>
19
+ <%- end -%>
11
20
  <%- if @element['nestable_elements'].present? -%>
12
21
  = render <%= @element_name -%>.nested_elements.available
13
22
  <%- end -%>
@@ -8,6 +8,15 @@
8
8
  = el.render :<%= content["name"] %>
9
9
  <%- end -%>
10
10
  <%- end -%>
11
+ <%- @ingredients.each do |ingredient| -%>
12
+ <%- if @ingredients.length > 1 -%>
13
+ .<%= ingredient["role"] %>
14
+ = el.render(:<%= ingredient["role"] %>)
15
+ </div>
16
+ <%- else -%>
17
+ = el.render(:<%= ingredient["role"] %>)
18
+ <%- end -%>
19
+ <%- end -%>
11
20
  <%- if @element['nestable_elements'].present? -%>
12
21
  = render <%= @element_name -%>.nested_elements.available
13
22
  <%- end -%>
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+ require "rails"
3
+
4
+ module Alchemy
5
+ module Generators
6
+ class IngredientGenerator < ::Rails::Generators::Base
7
+ desc "This generator generates an Alchemy ingredient class for you."
8
+ argument :class_name, banner: "ingredient_class_name"
9
+ source_root File.expand_path("templates", __dir__)
10
+
11
+ def init
12
+ @class_name = class_name.classify
13
+ @ingredients_view_path = "app/views/alchemy/ingredients"
14
+ end
15
+
16
+ def create_model
17
+ template "model.rb.tt", "app/models/alchemy/ingredients/#{file_name}.rb"
18
+ end
19
+
20
+ def copy_templates
21
+ @ingredient_editor_local = "#{file_name}_editor"
22
+ @ingredient_view_local = "#{file_name}_view"
23
+ template "view.html.erb", "#{@ingredients_view_path}/_#{file_name}_view.html.erb"
24
+ template "editor.html.erb", "#{@ingredients_view_path}/_#{file_name}_editor.html.erb"
25
+ end
26
+
27
+ def show_todo
28
+ say "\nPlease check the generated files and alter them to fit your needs."
29
+ end
30
+
31
+ private
32
+
33
+ def file_name
34
+ @_file_name ||= @class_name.classify.demodulize.underscore
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,14 @@
1
+ <%%#
2
+ Available locals:
3
+ * <%= @ingredient_editor_local %> - An Alchemy::IngredientEditor instance
4
+
5
+ Please consult Alchemy::IngredientEditor.rb docs for further methods on the ingredient object
6
+ %>
7
+ <%%= content_tag :div,
8
+ class: <%= @ingredient_editor_local %>.css_classes,
9
+ data: <%= @ingredient_editor_local %>.data_attributes do %>
10
+ <%%= element_form.fields_for(:ingredients, <%= @ingredient_editor_local %>.ingredient) do |f| %>
11
+ <%%= ingredient_label(<%= @ingredient_editor_local %>) %>
12
+ <%%= f.text_field :value %>
13
+ <%% end %>
14
+ <%% end %>
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ module Ingredients
5
+ class <%= @class_name %> < Alchemy::Ingredient
6
+ # Set additional attributes that get stored in the `data` JSON column
7
+ # store_accessor :data, :some, :attribute
8
+
9
+ # Set a related_object alias for convenience
10
+ # related_object_alias :some_association_name, class_name: "Some::Klass"
11
+ end
12
+ end
13
+ end
@@ -0,0 +1 @@
1
+ <%%= <%= @ingredient_view_local %>.value %>
@@ -7,7 +7,7 @@
7
7
  # http://markevans.github.io/dragonfly/cache/
8
8
  #
9
9
  # A complete reference can be found at
10
- # http://markevans.github.io/dragonfly/configuration/
10
+ # http://markevans.github.io/dragonfly/configuration
11
11
  #
12
12
  # Pictures
13
13
  #
@@ -1,4 +1,4 @@
1
- <%% cache node do %>
1
+ <%% cache [node, @page, @preview_mode] do %>
2
2
  <%%= content_tag :li, class: ['nav-item', node.children.any? ? 'dropdown' : nil].compact do %>
3
3
  <%%= link_to_if node.url,
4
4
  node.name,
@@ -1,4 +1,4 @@
1
- - cache node do
1
+ - cache [node, @page, @preview_mode] do
2
2
  = content_tag :li,
3
3
  class: ['nav-item', node.children.any? ? 'dropdown' : nil].compact do
4
4
  = link_to_if node.url,
@@ -1,4 +1,4 @@
1
- - cache node do
1
+ - cache [node, @page, @preview_mode] do
2
2
  = content_tag :li,
3
3
  class: ['nav-item', node.children.any? ? 'dropdown' : nil].compact do
4
4
  = link_to_if node.url,
@@ -1,4 +1,4 @@
1
- <%% cache menu do %>
1
+ <%% cache [menu, @page, @preview_mode] do %>
2
2
  <ul class="nav">
3
3
  <%%= render partial: menu.to_partial_path,
4
4
  collection: menu.children.includes(:page, :children),
@@ -1,4 +1,4 @@
1
- - cache menu do
1
+ - cache [menu, @page, @preview_mode] do
2
2
  %ul.nav
3
3
  = render partial: menu.to_partial_path,
4
4
  collection: menu.children.includes(:page, :children),