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
@@ -8,10 +8,11 @@ module Alchemy
8
8
  #
9
9
  def index
10
10
  # Fix for cancancan not able to merge multiple AR scopes for logged in users
11
- if can? :edit_content, Alchemy::Page
12
- @pages = Page.all
11
+ if cannot? :edit_content, Alchemy::Page
12
+ @pages = Alchemy::Page.accessible_by(current_ability, :index)
13
+ @pages = @pages.where(language: Language.current)
13
14
  else
14
- @pages = Page.accessible_by(current_ability, :index)
15
+ @pages = Language.current&.pages.presence || Alchemy::Page.none
15
16
  end
16
17
  @pages = @pages.includes(*page_includes)
17
18
  @pages = @pages.ransack(params[:q]).result
@@ -2,18 +2,28 @@
2
2
  module Alchemy
3
3
  module Admin
4
4
  module ArchiveOverlay
5
+ # Sets assignable id on given form field via JS.
6
+ #
7
+ # When the user saves the model the assignable
8
+ # gets persisted with the model as well.
9
+ #
10
+ def assign
11
+ @assignable_id = params[:id]
12
+ @form_field_id = params[:form_field_id]
13
+ end
14
+
5
15
  private
6
16
 
7
17
  def in_overlay?
8
- params[:content_id].present?
18
+ params[:form_field_id].present?
9
19
  end
10
20
 
11
21
  def archive_overlay
12
- @content = Content.find_by(id: params[:content_id])
22
+ @form_field_id = params[:form_field_id]
13
23
 
14
24
  respond_to do |format|
15
25
  format.html { render partial: "archive_overlay" }
16
- format.js { render action: "archive_overlay" }
26
+ format.js { render action: "archive_overlay" }
17
27
  end
18
28
  end
19
29
  end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ module Admin
5
+ module CropAction
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ before_action :load_croppable_resource, only: [:crop]
10
+ end
11
+
12
+ def crop
13
+ @picture = Alchemy::Picture.find_by(id: params[:picture_id])
14
+ if @picture
15
+ @croppable_resource.picture = @picture
16
+ @settings = @croppable_resource.image_cropper_settings
17
+ @element = @croppable_resource.element
18
+ else
19
+ @no_image_notice = Alchemy.t(:no_image_for_cropper_found)
20
+ end
21
+
22
+ render template: "alchemy/admin/crop"
23
+ end
24
+ end
25
+ end
26
+ end
@@ -19,6 +19,23 @@ module Alchemy
19
19
  end
20
20
  end
21
21
 
22
+ # Returns ingredient editor instances for defined ingredients
23
+ #
24
+ # Creates ingredient on demand if the ingredient is not yet present on the element
25
+ #
26
+ # @return Array<Alchemy::IngredientEditor>
27
+ def ingredients
28
+ element.definition.fetch(:ingredients, []).map do |ingredient|
29
+ Alchemy::IngredientEditor.new(find_or_create_ingredient(ingredient))
30
+ end
31
+ end
32
+
33
+ # Are any ingredients defined?
34
+ # @return [Boolean]
35
+ def has_ingredients_defined?
36
+ element.definition.fetch(:ingredients, []).any?
37
+ end
38
+
22
39
  # CSS classes for the element editor partial.
23
40
  def css_classes
24
41
  [
@@ -38,7 +55,7 @@ module Alchemy
38
55
  def editable?
39
56
  return false if folded?
40
57
 
41
- content_definitions.present? || taggable?
58
+ content_definitions.present? || ingredient_definitions.any? || taggable?
42
59
  end
43
60
 
44
61
  # Fixes Rails partial renderer calling to_model on the object
@@ -103,5 +120,13 @@ module Alchemy
103
120
  def create_content(name)
104
121
  Alchemy::Content.create(element: element, name: name)
105
122
  end
123
+
124
+ def find_or_create_ingredient(definition)
125
+ element.ingredients.detect { |i| i.role == definition[:role] } ||
126
+ element.ingredients.create!(
127
+ role: definition[:role],
128
+ type: Alchemy::Ingredient.normalize_type(definition[:type]),
129
+ )
130
+ end
106
131
  end
107
132
  end
@@ -0,0 +1,158 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ class IngredientEditor < SimpleDelegator
5
+ alias_method :ingredient, :__getobj__
6
+
7
+ def to_partial_path
8
+ "alchemy/ingredients/#{partial_name}_editor"
9
+ end
10
+
11
+ # Returns the translated role for displaying in labels
12
+ #
13
+ # Translate it in your locale yml file:
14
+ #
15
+ # alchemy:
16
+ # ingredient_roles:
17
+ # foo: Bar
18
+ #
19
+ # Optionally you can scope your ingredient role to an element:
20
+ #
21
+ # alchemy:
22
+ # ingredient_roles:
23
+ # article:
24
+ # foo: Baz
25
+ #
26
+ def translated_role
27
+ Alchemy.t(
28
+ role,
29
+ scope: "ingredient_roles.#{element.name}",
30
+ default: Alchemy.t("ingredient_roles.#{role}", default: role.humanize),
31
+ )
32
+ end
33
+
34
+ def css_classes
35
+ [
36
+ "ingredient-editor",
37
+ partial_name,
38
+ deprecated? ? "deprecated" : nil,
39
+ ].compact
40
+ end
41
+
42
+ def data_attributes
43
+ {
44
+ ingredient_id: id,
45
+ ingredient_role: role,
46
+ }
47
+ end
48
+
49
+ # Returns a string to be passed to Rails form field tags to ensure it can be used with Rails' nested attributes.
50
+ #
51
+ # === Example:
52
+ #
53
+ # <%= text_field_tag text_editor.form_field_name, text_editor.value %>
54
+ #
55
+ # === Options:
56
+ #
57
+ # You can pass an Ingredient column_name. Default is 'value'
58
+ #
59
+ # ==== Example:
60
+ #
61
+ # <%= text_field_tag text_editor.form_field_name(:link), text_editor.value %>
62
+ #
63
+ def form_field_name(column = "value")
64
+ "element[ingredients_attributes][#{form_field_counter}][#{column}]"
65
+ end
66
+
67
+ # Returns a unique string to be passed to a form field id.
68
+ #
69
+ # @param column [String] A Ingredient column_name. Default is 'value'
70
+ #
71
+ def form_field_id(column = "value")
72
+ "element_#{element.id}_ingredient_#{id}_#{column}"
73
+ end
74
+
75
+ # Fixes Rails partial renderer calling to_model on the object
76
+ # which reveals the delegated ingredient instead of this decorator.
77
+ def respond_to?(method_name)
78
+ return false if method_name == :to_model
79
+
80
+ super
81
+ end
82
+
83
+ def has_warnings?
84
+ definition.blank? || deprecated?
85
+ end
86
+
87
+ def linked?
88
+ link.try(:present?)
89
+ end
90
+
91
+ def warnings
92
+ return unless has_warnings?
93
+
94
+ if definition.blank?
95
+ Logger.warn("ingredient #{role} is missing its definition", caller(1..1))
96
+ Alchemy.t(:ingredient_definition_missing)
97
+ else
98
+ deprecation_notice
99
+ end
100
+ end
101
+
102
+ # Returns a deprecation notice for ingredients marked deprecated
103
+ #
104
+ # You can either use localizations or pass a String as notice
105
+ # in the ingredient definition.
106
+ #
107
+ # == Custom deprecation notices
108
+ #
109
+ # Use general ingredient deprecation notice
110
+ #
111
+ # - name: element_name
112
+ # ingredients:
113
+ # - role: old_ingredient
114
+ # type: Text
115
+ # deprecated: true
116
+ #
117
+ # Add a translation to your locale file for a per ingredient notice.
118
+ #
119
+ # en:
120
+ # alchemy:
121
+ # ingredient_deprecation_notices:
122
+ # element_name:
123
+ # old_ingredient: Foo baz widget is deprecated
124
+ #
125
+ # or use the global translation that apply to all deprecated ingredients.
126
+ #
127
+ # en:
128
+ # alchemy:
129
+ # ingredient_deprecation_notice: Foo baz widget is deprecated
130
+ #
131
+ # or pass string as deprecation notice.
132
+ #
133
+ # - name: element_name
134
+ # ingredients:
135
+ # - role: old_ingredient
136
+ # type: Text
137
+ # deprecated: This ingredient will be removed soon.
138
+ #
139
+ def deprecation_notice
140
+ case definition[:deprecated]
141
+ when String
142
+ definition[:deprecated]
143
+ when TrueClass
144
+ Alchemy.t(
145
+ role,
146
+ scope: [:ingredient_deprecation_notices, element.name],
147
+ default: Alchemy.t(:ingredient_deprecated),
148
+ )
149
+ end
150
+ end
151
+
152
+ private
153
+
154
+ def form_field_counter
155
+ element.definition.fetch(:ingredients, []).index { |i| i[:role] == role }
156
+ end
157
+ end
158
+ end
@@ -3,6 +3,7 @@
3
3
  module Alchemy
4
4
  module Admin
5
5
  module ElementsHelper
6
+ include Alchemy::Admin::IngredientsHelper
6
7
  include Alchemy::Admin::ContentsHelper
7
8
  include Alchemy::Admin::EssencesHelper
8
9
 
@@ -14,7 +14,7 @@ module Alchemy
14
14
  essence.thumbnail_url,
15
15
  alt: picture.name,
16
16
  class: "img_paddingtop",
17
- title: Alchemy.t(:image_name) + ": #{picture.name}",
17
+ title: Alchemy.t(:image_name, name: picture.name),
18
18
  )
19
19
  end
20
20
 
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ module Admin
5
+ module IngredientsHelper
6
+ include Alchemy::Admin::BaseHelper
7
+
8
+ # Renders the translated role of ingredient.
9
+ #
10
+ # Displays a warning icon if ingredient is missing its definition.
11
+ #
12
+ # Displays a mandatory field indicator, if the ingredient has validations.
13
+ #
14
+ def render_ingredient_role(ingredient)
15
+ if ingredient.blank?
16
+ warning("Ingredient is nil")
17
+ return
18
+ end
19
+
20
+ content = ingredient.translated_role
21
+
22
+ if ingredient.has_warnings?
23
+ icon = hint_with_tooltip(ingredient.warnings)
24
+ content = "#{icon} #{content}".html_safe
25
+ end
26
+
27
+ if ingredient.has_validations?
28
+ "#{content}<span class='validation_indicator'>*</span>".html_safe
29
+ else
30
+ content
31
+ end
32
+ end
33
+
34
+ # Renders the label and hint for a ingredient.
35
+ def ingredient_label(ingredient, column = :value)
36
+ label_tag ingredient.form_field_id(column) do
37
+ [render_ingredient_role(ingredient), render_hint_for(ingredient)].compact.join("&nbsp;").html_safe
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -26,12 +26,13 @@ module Alchemy
26
26
  class ElementViewHelper < BlockHelper
27
27
  # Renders one of the element's contents.
28
28
  #
29
+ # If the element uses +ingredients+ it renders the ingredient record.
30
+ #
29
31
  def render(name, options = {}, html_options = {})
30
- content = element.content_by_name(name)
31
- return if content.nil?
32
+ renderable = element.ingredient_by_role(name) || content(name)
33
+ return if renderable.nil?
32
34
 
33
- helpers.render(content, {
34
- content: content,
35
+ helpers.render(renderable, {
35
36
  options: options,
36
37
  html_options: html_options,
37
38
  })
@@ -43,16 +44,30 @@ module Alchemy
43
44
  element.content_by_name(name)
44
45
  end
45
46
 
47
+ deprecate content: "Use `ingredient_by_role` instead", deprecator: Alchemy::Deprecation
48
+
46
49
  # Returns the ingredient of one of the element's contents.
47
50
  #
51
+ # If the element uses +ingredients+ it returns the +value+ of the ingredient record.
52
+ #
48
53
  def ingredient(name)
49
54
  element.ingredient(name)
50
55
  end
51
56
 
52
- # Returns true if the given content has been filled by the user.
57
+ # Returns the value of one of the element's ingredients.
58
+ #
59
+ def value(name)
60
+ element.value_for(name)
61
+ end
62
+
63
+ # Returns true if the given content or ingredient has a value.
53
64
  #
54
65
  def has?(name)
55
- element.has_ingredient?(name)
66
+ if element.ingredient_definitions.any?
67
+ element.has_value_for?(name)
68
+ else
69
+ element.has_ingredient?(name)
70
+ end
56
71
  end
57
72
 
58
73
  # Return's the given content's essence.
@@ -60,6 +75,8 @@ module Alchemy
60
75
  def essence(name)
61
76
  content(name).try(:essence)
62
77
  end
78
+
79
+ deprecate essence: "Use `ingredient_by_role` instead", deprecator: Alchemy::Deprecation
63
80
  end
64
81
 
65
82
  # Block-level helper for element views. Constructs a DOM element wrapping
@@ -25,7 +25,7 @@ module Alchemy
25
25
  # === Render elements from global page:
26
26
  #
27
27
  # <footer>
28
- # <%= render_elements from_page: 'footer' %>
28
+ # <%= render_elements from_page: Alchemy::Page.find_by(page_layout: 'footer') %>
29
29
  # </footer>
30
30
  #
31
31
  # === Custom elements finder:
@@ -50,8 +50,8 @@ module Alchemy
50
50
  # <%= render_elements finder: MyCustomNewsArchive.new %>
51
51
  # </div>
52
52
  #
53
- # @option options [Alchemy::Page|String] :from_page (@page)
54
- # The page the elements are rendered from. You can pass a page_layout String or a {Alchemy::Page} object.
53
+ # @option options [Alchemy::Page] :from_page (@page)
54
+ # The page the elements are rendered from.
55
55
  # @option options [Array<String>|String] :only
56
56
  # A list of element names only to be rendered.
57
57
  # @option options [Array<String>|String] :except
@@ -77,7 +77,14 @@ module Alchemy
77
77
  }.update(options)
78
78
 
79
79
  finder = options[:finder] || Alchemy::ElementsFinder.new(options)
80
- elements = finder.elements(page: options[:from_page])
80
+
81
+ page_version = if @preview_mode
82
+ options[:from_page]&.draft_version
83
+ else
84
+ options[:from_page]&.public_version
85
+ end
86
+
87
+ elements = finder.elements(page_version: page_version)
81
88
 
82
89
  buff = []
83
90
  elements.each_with_index do |element, i|
@@ -133,7 +140,7 @@ module Alchemy
133
140
  def render_element(element, options = {}, counter = 1)
134
141
  if element.nil?
135
142
  warning("Element is nil")
136
- render "alchemy/elements/view_not_found", {name: "nil"}
143
+ render "alchemy/elements/view_not_found", { name: "nil" }
137
144
  return
138
145
  end
139
146