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
@@ -8,7 +8,7 @@ module Alchemy
8
8
 
9
9
  before_action :load_locked_pages
10
10
 
11
- helper_method :clipboard_empty?, :trash_empty?, :get_clipboard, :is_admin?
11
+ helper_method :clipboard_empty?, :get_clipboard, :is_admin?
12
12
 
13
13
  check_authorization
14
14
 
@@ -54,7 +54,10 @@ module Alchemy
54
54
  if request.xhr?
55
55
  render action: "error_notice"
56
56
  else
57
- render "500", status: 500
57
+ respond_to do |format|
58
+ format.html { render "500", status: 500 }
59
+ format.json { render json: { message: @notice }, status: 500 }
60
+ end
58
61
  end
59
62
  end
60
63
 
@@ -69,11 +72,6 @@ module Alchemy
69
72
  get_clipboard(category).blank?
70
73
  end
71
74
 
72
- def trash_empty?(category)
73
- "alchemy/#{category.singularize}".classify.constantize.trashed.blank?
74
- end
75
- deprecate :trash_empty?, deprecator: Alchemy::Deprecation
76
-
77
75
  def set_stamper
78
76
  if Alchemy.user_class < ActiveRecord::Base
79
77
  Alchemy.user_class.stamper = current_alchemy_user
@@ -3,27 +3,31 @@
3
3
  module Alchemy
4
4
  module Admin
5
5
  class ElementsController < Alchemy::Admin::BaseController
6
- before_action :load_element, only: [:update, :trash, :fold, :publish]
6
+ before_action :load_element, only: [:update, :destroy, :fold, :publish]
7
7
  authorize_resource class: Alchemy::Element
8
8
 
9
9
  def index
10
- @page = Page.find(params[:page_id])
11
- @elements = @page.all_elements.not_nested.unfixed.not_trashed.includes(*element_includes)
12
- @fixed_elements = @page.all_elements.fixed.not_trashed.includes(*element_includes)
10
+ @page_version = PageVersion.find(params[:page_version_id])
11
+ @page = @page_version.page
12
+ elements = @page_version.elements.order(:position).includes(*element_includes)
13
+ @elements = elements.not_nested.unfixed
14
+ @fixed_elements = elements.not_nested.fixed
13
15
  end
14
16
 
15
17
  def new
16
- @page = Page.find(params[:page_id])
18
+ @page_version = PageVersion.find(params[:page_version_id])
19
+ @page = @page_version.page
17
20
  @parent_element = Element.find_by(id: params[:parent_element_id])
18
21
  @elements = @page.available_elements_within_current_scope(@parent_element)
19
- @element = @page.elements.build
22
+ @element = @page_version.elements.build
20
23
  @clipboard = get_clipboard("elements")
21
24
  @clipboard_items = Element.all_from_clipboard_for_page(@clipboard, @page)
22
25
  end
23
26
 
24
27
  # Creates a element as discribed in config/alchemy/elements.yml on page via AJAX.
25
28
  def create
26
- @page = Page.find(params[:element][:page_id])
29
+ @page_version = PageVersion.find(params[:element][:page_version_id])
30
+ @page = @page_version.page
27
31
  Element.transaction do
28
32
  if @paste_from_clipboard = params[:paste_from_clipboard].present?
29
33
  @element = paste_element_from_clipboard
@@ -38,7 +42,7 @@ module Alchemy
38
42
  if @element.valid?
39
43
  render :create
40
44
  else
41
- @element.page = @page
45
+ @element.page_version = @page_version
42
46
  @elements = @page.available_element_definitions
43
47
  @clipboard = get_clipboard("elements")
44
48
  @clipboard_items = Element.all_from_clipboard_for_page(@clipboard, @page)
@@ -51,40 +55,38 @@ module Alchemy
51
55
  # And update all contents in the elements by calling update_contents.
52
56
  #
53
57
  def update
54
- if @element.update_contents(contents_params)
55
- @page = @element.page
56
- @element_validated = @element.update(element_params)
58
+ @page = @element.page
59
+
60
+ if element_params.key?(:ingredients_attributes)
61
+ update_element_with_ingredients
57
62
  else
58
- @element_validated = false
59
- @notice = Alchemy.t("Validation failed")
60
- @error_message = "<h2>#{@notice}</h2><p>#{Alchemy.t(:content_validations_headline)}</p>".html_safe
63
+ update_element_with_contents
61
64
  end
62
65
  end
63
66
 
64
- def publish
65
- @element.update(public: !@element.public?)
67
+ def destroy
68
+ @element.destroy
69
+ @notice = Alchemy.t("Successfully deleted element") % { element: @element.display_name }
66
70
  end
67
71
 
68
- # Trashes the Element instead of deleting it.
69
- def trash
70
- @page = @element.page
71
- @element.trash!
72
+ def publish
73
+ @element.update(public: !@element.public?)
72
74
  end
73
75
 
74
76
  def order
75
- @trashed_element_ids = Element.trashed.where(id: params[:element_ids]).pluck(:id)
76
77
  @parent_element = Element.find_by(id: params[:parent_element_id])
77
78
  Element.transaction do
78
- params.fetch(:element_ids, []).each_with_index do |element_id, idx|
79
- # Ensure to set page_id and parent_element_id to the current
80
- # because of trashed elements could still have old values
81
- Element.where(id: element_id).update_all(
82
- page_id: params[:page_id],
79
+ params.fetch(:element_ids, []).each.with_index(1) do |element_id, position|
80
+ # We need to set the parent_element_id, because we might have dragged the
81
+ # element over from another nestable element
82
+ Element.find_by(id: element_id).update_columns(
83
83
  parent_element_id: params[:parent_element_id],
84
- position: idx + 1,
84
+ position: position,
85
85
  )
86
86
  end
87
- @parent_element.try!(:touch)
87
+ # Need to manually touch the parent because Rails does not do it
88
+ # with the update_columns above
89
+ @parent_element&.touch
88
90
  end
89
91
  end
90
92
 
@@ -102,6 +104,7 @@ module Alchemy
102
104
  contents: {
103
105
  essence: :ingredient_association,
104
106
  },
107
+ ingredients: :related_object,
105
108
  },
106
109
  :tags,
107
110
  {
@@ -110,6 +113,7 @@ module Alchemy
110
113
  contents: {
111
114
  essence: :ingredient_association,
112
115
  },
116
+ ingredients: :related_object,
113
117
  },
114
118
  :tags,
115
119
  ],
@@ -132,7 +136,7 @@ module Alchemy
132
136
  @source_element = Element.find(element_from_clipboard["id"])
133
137
  element = Element.copy(@source_element, {
134
138
  parent_element_id: create_element_params[:parent_element_id],
135
- page_id: @page.id,
139
+ page_version_id: @page_version.id,
136
140
  })
137
141
  if element_from_clipboard["action"] == "cut"
138
142
  @cut_element_id = @source_element.id
@@ -147,15 +151,35 @@ module Alchemy
147
151
  end
148
152
 
149
153
  def element_params
150
- if @element.taggable?
151
- params.fetch(:element, {}).permit(:tag_list)
154
+ params.fetch(:element, {}).permit(:tag_list, ingredients_attributes: {})
155
+ end
156
+
157
+ def create_element_params
158
+ params.require(:element).permit(:name, :page_version_id, :parent_element_id)
159
+ end
160
+
161
+ def update_element_with_ingredients
162
+ if @element.update(element_params)
163
+ @element_validated = true
152
164
  else
153
- params.fetch(:element, {})
165
+ element_update_error
166
+ @error_messages = @element.ingredient_error_messages
154
167
  end
155
168
  end
156
169
 
157
- def create_element_params
158
- params.require(:element).permit(:name, :page_id, :parent_element_id)
170
+ def update_element_with_contents
171
+ if @element.update_contents(contents_params)
172
+ @element_validated = @element.update(element_params)
173
+ else
174
+ element_update_error
175
+ @error_messages = @element.essence_error_messages
176
+ end
177
+ end
178
+
179
+ def element_update_error
180
+ @element_validated = false
181
+ @notice = Alchemy.t("Validation failed")
182
+ @error_message = "<h2>#{@notice}</h2><p>#{Alchemy.t(:content_validations_headline)}</p>".html_safe
159
183
  end
160
184
  end
161
185
  end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ module Admin
5
+ class EssenceAudiosController < Alchemy::Admin::BaseController
6
+ authorize_resource class: Alchemy::EssenceAudio
7
+ before_action :load_essence
8
+
9
+ def update
10
+ @essence_audio.update(essence_audio_params)
11
+ end
12
+
13
+ private
14
+
15
+ def load_essence
16
+ @essence_audio = EssenceAudio.find(params[:id])
17
+ end
18
+
19
+ def essence_audio_params
20
+ params.require(:essence_audio).permit(
21
+ :autoplay,
22
+ :controls,
23
+ :loop,
24
+ :muted,
25
+ :attachment_id
26
+ )
27
+ end
28
+ end
29
+ end
30
+ end
@@ -17,20 +17,6 @@ module Alchemy
17
17
  @essence_file.update(essence_file_params)
18
18
  end
19
19
 
20
- # Assigns file, but does not saves it.
21
- #
22
- # When the user saves the element the content gets updated as well.
23
- #
24
- def assign
25
- @content = Content.find_by(id: params[:content_id])
26
- @attachment = Attachment.find_by(id: params[:attachment_id])
27
- @content.essence.attachment = @attachment
28
-
29
- # We need to update timestamp here because we don't save yet,
30
- # but the cache needs to be get invalid.
31
- @content.touch
32
- end
33
-
34
20
  private
35
21
 
36
22
  def essence_file_params
@@ -3,11 +3,12 @@
3
3
  module Alchemy
4
4
  module Admin
5
5
  class EssencePicturesController < Alchemy::Admin::BaseController
6
- FLOAT_REGEX = /\A\d+(\.\d+)?\z/
6
+ include CropAction
7
+
7
8
  authorize_resource class: Alchemy::EssencePicture
8
9
 
9
- before_action :load_essence_picture, only: [:edit, :crop, :update]
10
- before_action :load_content, only: [:edit, :update, :assign]
10
+ before_action :load_essence_picture, only: [:edit, :update]
11
+ before_action :load_content, only: [:edit, :update]
11
12
 
12
13
  helper "alchemy/admin/contents"
13
14
  helper "alchemy/admin/essences"
@@ -16,94 +17,22 @@ module Alchemy
16
17
  def edit
17
18
  end
18
19
 
19
- def crop
20
- if @picture = @essence_picture.picture
21
- @content = @essence_picture.content
22
- @min_size = sizes_from_essence_or_params
23
- @ratio = ratio_from_size_or_settings
24
- infer_width_or_height_from_ratio
25
-
26
- @default_box = @essence_picture.default_mask(@min_size)
27
- @initial_box = @essence_picture.cropping_mask || @default_box
28
- else
29
- @no_image_notice = Alchemy.t(:no_image_for_cropper_found)
30
- end
31
- end
32
-
33
20
  def update
34
21
  @essence_picture.update(essence_picture_params)
35
22
  end
36
23
 
37
- # Assigns picture, but does not save it.
38
- #
39
- # When the user saves the element the content gets updated as well.
40
- #
41
- def assign
42
- @picture = Picture.find_by(id: params[:picture_id])
43
- @content.essence.picture = @picture
44
- @element = @content.element
45
-
46
- # We need to update timestamp here because we don't save yet,
47
- # but the cache needs to be get invalid.
48
- @content.touch
49
- end
50
-
51
- def destroy
52
- @content = Content.find_by(id: params[:id])
53
- @element = @content.element
54
- @content_id = @content.id
55
- @content.destroy
56
- @essence_pictures = @element.contents.essence_pictures
57
- end
58
-
59
24
  private
60
25
 
61
26
  def load_essence_picture
62
27
  @essence_picture = EssencePicture.find(params[:id])
63
28
  end
64
29
 
65
- def load_content
66
- @content = Content.find(params[:content_id])
30
+ def load_croppable_resource
31
+ @croppable_resource = EssencePicture.find(params[:id])
67
32
  end
68
33
 
69
- # Gets the minimum size of the image to be rendered.
70
- #
71
- # The +render_size+ attribute has preference over the contents +size+ setting.
72
- #
73
- def sizes_from_essence_or_params
74
- if @essence_picture.render_size?
75
- @essence_picture.sizes_from_string(@essence_picture.render_size)
76
- elsif @essence_picture.content.settings[:size]
77
- @essence_picture.sizes_from_string(@essence_picture.content.settings[:size])
78
- else
79
- { width: 0, height: 0 }
80
- end
81
- end
82
-
83
- # Infers the aspect ratio from size or contents settings. If you don't want a fixed
84
- # aspect ratio, don't specify a size or only width or height.
85
- #
86
- def ratio_from_size_or_settings
87
- if @min_size.value?(0) && @essence_picture.content.settings[:fixed_ratio].to_s =~ FLOAT_REGEX
88
- @essence_picture.content.settings[:fixed_ratio].to_f
89
- elsif !@min_size[:width].zero? && !@min_size[:height].zero?
90
- @min_size[:width].to_f / @min_size[:height]
91
- else
92
- false
93
- end
94
- end
95
-
96
- # Infers the minimum width or height
97
- # if the aspect ratio and one dimension is specified.
98
- #
99
- def infer_width_or_height_from_ratio
100
- return unless @ratio
101
-
102
- if @min_size[:height].zero?
103
- @min_size[:height] = (@min_size[:width] / @ratio).to_i
104
- else
105
- @min_size[:width] = (@min_size[:height] * @ratio).to_i
106
- end
34
+ def load_content
35
+ @content = Content.find(params[:content_id])
107
36
  end
108
37
 
109
38
  def essence_picture_params
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ module Admin
5
+ class EssenceVideosController < Alchemy::Admin::BaseController
6
+ authorize_resource class: Alchemy::EssenceVideo
7
+ before_action :load_essence
8
+
9
+ def update
10
+ @essence_video.update(essence_video_params)
11
+ end
12
+
13
+ private
14
+
15
+ def load_essence
16
+ @essence_video = EssenceVideo.find(params[:id])
17
+ end
18
+
19
+ def essence_video_params
20
+ params.require(:essence_video).permit(
21
+ :width,
22
+ :height,
23
+ :autoplay,
24
+ :controls,
25
+ :loop,
26
+ :muted,
27
+ :preload,
28
+ :attachment_id
29
+ )
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ module Admin
5
+ class IngredientsController < Alchemy::Admin::BaseController
6
+ load_and_authorize_resource class: Alchemy::Ingredient
7
+
8
+ include CropAction
9
+
10
+ helper "Alchemy::Admin::Ingredients"
11
+
12
+ def edit
13
+ end
14
+
15
+ def update
16
+ @ingredient.update(ingredient_params)
17
+ end
18
+
19
+ private
20
+
21
+ def ingredient_params
22
+ params.require(:ingredient).permit(@ingredient.class.stored_attributes[:data])
23
+ end
24
+
25
+ def load_croppable_resource
26
+ @croppable_resource = @ingredient
27
+ end
28
+ end
29
+ end
30
+ end
@@ -16,7 +16,6 @@ module Alchemy
16
16
 
17
17
  def edit
18
18
  @page = Page.find(params[:id])
19
- @page_layouts = PageLayout.layouts_with_own_for_select(@page.page_layout, @current_language.id, true)
20
19
  end
21
20
  end
22
21
  end
@@ -33,6 +33,8 @@ module Alchemy
33
33
 
34
34
  before_action :set_view, only: [:index]
35
35
 
36
+ before_action :set_page_version, only: [:show, :edit]
37
+
36
38
  def index
37
39
  @query = @current_language.pages.contentpages.ransack(search_filter_params[:q])
38
40
 
@@ -118,7 +120,6 @@ module Alchemy
118
120
 
119
121
  # Set page configuration like page names, meta tags and states.
120
122
  def configure
121
- @page_layouts = PageLayout.layouts_with_own_for_select(@page.page_layout, @current_language.id, @page.layoutpage?)
122
123
  end
123
124
 
124
125
  # Updates page
@@ -191,24 +192,12 @@ module Alchemy
191
192
  end
192
193
  end
193
194
 
194
- def visit
195
- @page.unlock!
196
- redirect_to show_page_url(
197
- urlname: @page.urlname,
198
- locale: prefix_locale? ? @page.language_code : nil,
199
- host: @page.site.host == "*" ? request.host : @page.site.host,
200
- )
201
- end
202
-
203
195
  # Sets the page public and updates the published_at attribute that is used as cache_key
204
196
  #
205
197
  def publish
206
198
  # fetching page via before filter
207
199
  @page.publish!
208
200
 
209
- # Send publish notification to all registered publish targets
210
- Alchemy.publish_targets.each { |p| p.perform_later(@page) }
211
-
212
201
  flash[:notice] = Alchemy.t(:page_published, name: @page.name)
213
202
  redirect_back(fallback_location: admin_pages_path)
214
203
  end
@@ -403,6 +392,10 @@ module Alchemy
403
392
  @page_root = @current_language.root_page
404
393
  end
405
394
 
395
+ def set_page_version
396
+ @page_version = @page.draft_version
397
+ end
398
+
406
399
  def serialized_page_tree
407
400
  PageTreeSerializer.new(@page, ability: current_ability,
408
401
  user: current_alchemy_user,
@@ -9,12 +9,16 @@ module Alchemy
9
9
  helper "alchemy/admin/tags"
10
10
 
11
11
  before_action :load_resource,
12
- only: [:show, :edit, :update, :destroy, :info]
12
+ only: [:show, :edit, :update, :url, :destroy, :info]
13
13
 
14
14
  before_action :set_size, only: [:index, :show, :edit_multiple]
15
15
 
16
16
  authorize_resource class: Alchemy::Picture
17
17
 
18
+ before_action(only: :assign) do
19
+ @picture = Picture.find(params[:id])
20
+ end
21
+
18
22
  def index
19
23
  @query = Picture.ransack(search_filter_params[:q])
20
24
  @pictures = Picture.search_by(
@@ -32,10 +36,21 @@ module Alchemy
32
36
  def show
33
37
  @previous = @picture.previous(params)
34
38
  @next = @picture.next(params)
35
- @assignments = @picture.essence_pictures.joins(content: {element: :page})
39
+ @assignments = @picture.essence_pictures.joins(content: { element: :page })
36
40
  render action: "show"
37
41
  end
38
42
 
43
+ def url
44
+ options = picture_url_params.to_h.symbolize_keys.transform_values! do |value|
45
+ value.in?(%w[true false]) ? value == "true" : value
46
+ end
47
+ render json: {
48
+ url: @picture.url(options),
49
+ alt: @picture.name,
50
+ title: Alchemy.t(:image_name, name: @picture.name),
51
+ }
52
+ end
53
+
39
54
  def create
40
55
  @picture = Picture.new(picture_params)
41
56
  @picture.name = @picture.humanized_name
@@ -125,8 +140,8 @@ module Alchemy
125
140
  end
126
141
  else
127
142
  cookies[:alchemy_pictures_per_page] = params[:per_page] ||
128
- cookies[:alchemy_pictures_per_page] ||
129
- pictures_per_page_for_size
143
+ cookies[:alchemy_pictures_per_page] ||
144
+ pictures_per_page_for_size
130
145
  end
131
146
  end
132
147
 
@@ -158,17 +173,28 @@ module Alchemy
158
173
  def search_filter_params
159
174
  @_search_filter_params ||= params.except(*COMMON_SEARCH_FILTER_EXCLUDES + [:picture_ids]).permit(
160
175
  *common_search_filter_includes + [
161
- :size,
162
- :element_id,
163
- :swap,
164
- :content_id,
165
- ],
176
+ :size,
177
+ :form_field_id,
178
+ ],
166
179
  )
167
180
  end
168
181
 
169
182
  def picture_params
170
183
  params.require(:picture).permit(:image_file, :upload_hash, :name, :tag_list)
171
184
  end
185
+
186
+ def picture_url_params
187
+ params.permit(
188
+ :crop_from,
189
+ :crop_size,
190
+ :crop,
191
+ :flatten,
192
+ :format,
193
+ :quality,
194
+ :size,
195
+ :upsample
196
+ )
197
+ end
172
198
  end
173
199
  end
174
200
  end
@@ -9,18 +9,21 @@ module Alchemy
9
9
  # If you want to only load a specific type of element pass ?named=an_element_name
10
10
  #
11
11
  def index
12
- @elements = Element.not_nested
12
+ if params[:page_id].present?
13
+ @page = Page.find(params[:page_id])
14
+ @elements = @page.elements.not_nested
15
+ else
16
+ @elements = Element.not_nested.joins(:page_version).merge(PageVersion.published)
17
+ end
18
+
13
19
  # Fix for cancancan not able to merge multiple AR scopes for logged in users
14
20
  if cannot? :manage, Alchemy::Element
15
21
  @elements = @elements.accessible_by(current_ability, :index)
16
22
  end
17
- if params[:page_id].present?
18
- @elements = @elements.where(page_id: params[:page_id])
19
- end
20
23
  if params[:named].present?
21
24
  @elements = @elements.named(params[:named])
22
25
  end
23
- @elements = @elements.includes(*element_includes)
26
+ @elements = @elements.includes(*element_includes).order(:position)
24
27
 
25
28
  render json: @elements, adapter: :json, root: "elements"
26
29
  end
@@ -43,6 +46,7 @@ module Alchemy
43
46
  contents: {
44
47
  essence: :ingredient_association,
45
48
  },
49
+ ingredients: :related_object,
46
50
  },
47
51
  :tags,
48
52
  ],
@@ -51,6 +55,7 @@ module Alchemy
51
55
  contents: {
52
56
  essence: :ingredient_association,
53
57
  },
58
+ ingredients: :related_object,
54
59
  },
55
60
  :tags,
56
61
  ]
@@ -7,12 +7,10 @@ module Alchemy
7
7
  # Returns all pages as json object
8
8
  #
9
9
  def index
10
+ @pages = Language.current&.pages.presence || Alchemy::Page.none
10
11
  # Fix for cancancan not able to merge multiple AR scopes for logged in users
11
12
  if cannot? :edit_content, Alchemy::Page
12
- @pages = Alchemy::Page.accessible_by(current_ability, :index)
13
- @pages = @pages.where(language: Language.current)
14
- else
15
- @pages = Language.current&.pages.presence || Alchemy::Page.none
13
+ @pages = @pages.accessible_by(current_ability, :index)
16
14
  end
17
15
  @pages = @pages.includes(*page_includes)
18
16
  @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