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
@@ -2,17 +2,8 @@
2
2
 
3
3
  module Alchemy
4
4
  module TestSupport
5
- class << self
6
- def factory_paths
7
- Dir[
8
- ::Alchemy::Engine.root.join("lib", "alchemy", "test_support", "factories", "*_factory.rb")
9
- ].map { |path| path.sub(/.rb\z/, "") }
10
- end
11
- deprecate factory_paths: :factories_path, deprecator: Alchemy::Deprecation
12
-
13
- def factories_path
14
- ::Alchemy::Engine.root.join("lib", "alchemy", "test_support", "factories")
15
- end
5
+ def self.factories_path
6
+ ::Alchemy::Engine.root.join("lib", "alchemy", "test_support", "factories")
16
7
  end
17
8
  end
18
9
  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
@@ -1,19 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "tasks/harden_gutentag_migrations"
4
- require "rails/generators"
5
- require "thor"
6
- require "alchemy/install/tasks"
7
- require "alchemy/version"
8
4
 
9
5
  module Alchemy
10
6
  class Upgrader::FivePointZero < Upgrader
11
- include Rails::Generators::Actions
12
- include Thor::Base
13
- include Thor::Actions
14
-
15
- source_root File.expand_path("../../generators/alchemy/install/files", __dir__)
16
-
17
7
  class << self
18
8
  def install_gutentag_migrations
19
9
  desc "Install Gutentag migrations"
@@ -46,28 +36,6 @@ module Alchemy
46
36
  log "Root page not found.", :skip
47
37
  end
48
38
  end
49
-
50
- def run_webpacker_installer
51
- # Webpacker does not create a package.json, but we need one
52
- unless File.exist? app_root.join("package.json")
53
- in_root { run "echo '{}' > package.json" }
54
- end
55
- new.rake("webpacker:install", abort_on_failure: true)
56
- end
57
-
58
- def add_npm_package
59
- new.run "yarn add @alchemy_cms/admin@~#{Alchemy.version}"
60
- end
61
-
62
- def copy_alchemy_entry_point
63
- webpack_config = YAML.load_file(app_root.join("config", "webpacker.yml"))[Rails.env]
64
- new.copy_file "alchemy_admin.js",
65
- app_root.join(webpack_config["source_path"], webpack_config["source_entry_path"], "alchemy/admin.js")
66
- end
67
-
68
- def app_root
69
- @_app_root ||= Rails.root
70
- end
71
39
  end
72
40
  end
73
41
  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,51 @@
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
+ # eager load all elements that have ingredients defined but no ingredient records yet.
14
+ all_elements = Alchemy::Element
15
+ .named(elements_with_ingredients.map { |d| d[:name] })
16
+ .includes(contents: { essence: :ingredient_association })
17
+ .left_outer_joins(:ingredients).where(alchemy_ingredients: { id: nil })
18
+ .to_a
19
+ elements_with_ingredients.map do |element_definition|
20
+ elements = all_elements.select { |e| e.name == element_definition[:name] }
21
+ if elements.any?
22
+ puts "-- Creating ingredients for #{elements.count} #{element_definition[:name]}(s)"
23
+ elements.each do |element|
24
+ Alchemy::Element.transaction do
25
+ element.ingredients = element_definition[:ingredients].map do |ingredient_definition|
26
+ content = element.content_by_name(ingredient_definition[:role])
27
+ next unless content
28
+
29
+ ingredient = Alchemy::Ingredient.build(role: ingredient_definition[:role], element: element)
30
+ belongs_to_associations = content.essence.class.reflect_on_all_associations(:belongs_to)
31
+ if belongs_to_associations.any?
32
+ ingredient.related_object = content.essence.public_send(belongs_to_associations.first.name)
33
+ else
34
+ ingredient.value = content.ingredient
35
+ end
36
+ content.destroy!
37
+ print "."
38
+ ingredient
39
+ end.compact
40
+ end
41
+ end
42
+ puts "\n"
43
+ else
44
+ puts "-- No #{element_definition[:name]} elements found for migration."
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Alchemy
4
- VERSION = "5.2.4"
4
+ VERSION = "6.0.0.b1"
5
5
 
6
6
  def self.version
7
7
  VERSION
@@ -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 %>
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  require "rails/generators"
3
3
  require "alchemy/install/tasks"
4
- require "alchemy/version"
5
4
 
6
5
  module Alchemy
7
6
  module Generators
@@ -89,7 +88,7 @@ module Alchemy
89
88
  end
90
89
 
91
90
  def add_npm_package
92
- run "yarn add @alchemy_cms/admin@~#{Alchemy.version}"
91
+ run "yarn add @alchemy_cms/admin"
93
92
  end
94
93
 
95
94
  def copy_alchemy_entry_point
@@ -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, @page, @preview_mode] do %>
1
+ <%% cache node 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, @page, @preview_mode] do
1
+ - cache node 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, @page, @preview_mode] do
1
+ - cache node 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, @page, @preview_mode] do %>
1
+ <%% cache menu 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, @page, @preview_mode] do
1
+ - cache menu do
2
2
  %ul.nav
3
3
  = render partial: menu.to_partial_path,
4
4
  collection: menu.children.includes(:page, :children),
@@ -1,4 +1,4 @@
1
- - cache [menu, @page, @preview_mode] do
1
+ - cache menu do
2
2
  ul.nav
3
3
  = render partial: menu.to_partial_path,
4
4
  collection: menu.children.includes(:page, :children),
@@ -8,6 +8,8 @@ namespace :alchemy do
8
8
  Rake::Task["alchemy:tidy:element_positions"].invoke
9
9
  Rake::Task["alchemy:tidy:content_positions"].invoke
10
10
  Rake::Task["alchemy:tidy:remove_orphaned_records"].invoke
11
+ Rake::Task["alchemy:tidy:remove_trashed_elements"].invoke
12
+ Rake::Task["alchemy:tidy:remove_duplicate_legacy_urls"].invoke
11
13
  end
12
14
 
13
15
  desc "Fixes element positions."
@@ -36,6 +38,16 @@ namespace :alchemy do
36
38
  Alchemy::Tidy.remove_orphaned_contents
37
39
  end
38
40
 
41
+ desc "Remove trashed elements."
42
+ task remove_trashed_elements: [:environment] do
43
+ Alchemy::Tidy.remove_trashed_elements
44
+ end
45
+
46
+ desc "Remove duplicate legacy URLs"
47
+ task remove_duplicate_legacy_urls: [:environment] do
48
+ Alchemy::Tidy.remove_duplicate_legacy_urls
49
+ end
50
+
39
51
  desc "List Alchemy elements usage"
40
52
  task elements_usage: :environment do
41
53
  puts "\n"
@@ -7,6 +7,7 @@ namespace :alchemy do
7
7
  task upgrade: [
8
8
  "alchemy:upgrade:prepare",
9
9
  "alchemy:upgrade:5.0:run",
10
+ "alchemy:upgrade:6.0:run",
10
11
  ] do
11
12
  Alchemy::Upgrader.display_todos
12
13
  end
@@ -42,9 +43,6 @@ namespace :alchemy do
42
43
  "alchemy:upgrade:5.0:install_gutentag_migrations",
43
44
  "alchemy:upgrade:5.0:remove_layout_roots",
44
45
  "alchemy:upgrade:5.0:remove_root_page",
45
- "alchemy:upgrade:5.0:run_webpacker_installer",
46
- "alchemy:upgrade:5.0:add_npm_package",
47
- "alchemy:upgrade:5.0:copy_alchemy_entry_point",
48
46
  ]
49
47
 
50
48
  desc "Install Gutentag migrations"
@@ -61,22 +59,30 @@ namespace :alchemy do
61
59
  task remove_root_page: [:environment] do
62
60
  Alchemy::Upgrader::FivePointZero.remove_root_page
63
61
  end
62
+ end
64
63
 
65
- desc "Run webpacker installer"
66
- task run_webpacker_installer: [:environment] do
67
- Alchemy::Upgrader::FivePointZero.run_webpacker_installer
68
- end
64
+ desc "Upgrade Alchemy to v6.0"
65
+ task "6.0" => [
66
+ "alchemy:upgrade:prepare",
67
+ "alchemy:upgrade:6.0:run",
68
+ ] do
69
+ Alchemy::Upgrader.display_todos
70
+ end
71
+
72
+ namespace "6.0" do
73
+ task "run" => [
74
+ "alchemy:upgrade:6.0:create_public_page_versions",
75
+ "alchemy:upgrade:6.0:create_ingredients",
76
+ ]
69
77
 
70
- desc "Add NPM package"
71
- task add_npm_package: [:environment] do
72
- puts "adding npm_package..."
73
- Alchemy::Upgrader::FivePointZero.add_npm_package
78
+ desc "Create public page versions"
79
+ task create_public_page_versions: [:environment] do
80
+ Alchemy::Upgrader::SixPointZero.create_public_page_versions
74
81
  end
75
82
 
76
- desc "Copy alchemy entry point"
77
- task copy_alchemy_entry_point: [:environment] do
78
- puts "copying alchemy entry point"
79
- Alchemy::Upgrader::FivePointZero.copy_alchemy_entry_point
83
+ desc "Create ingredients for elements with ingredients defined"
84
+ task create_ingredients: [:environment] do
85
+ Alchemy::Upgrader::SixPointZero.create_ingredients
80
86
  end
81
87
  end
82
88
  end
data/package/admin.js CHANGED
@@ -1,6 +1,10 @@
1
1
  import translate from "./src/i18n"
2
2
  import translationData from "./src/translations"
3
3
  import NodeTree from "./src/node_tree"
4
+ import fileEditors from "./src/file_editors"
5
+ import pictureEditors from "./src/picture_editors"
6
+ import ImageLoader from "./src/image_loader"
7
+ import ImageCropper from "./src/image_cropper"
4
8
 
5
9
  // Global Alchemy object
6
10
  if (typeof window.Alchemy === "undefined") {
@@ -12,5 +16,9 @@ Object.assign(Alchemy, {
12
16
  // Global utility method for translating a given string
13
17
  t: translate,
14
18
  translations: Object.assign(Alchemy.translations || {}, translationData),
15
- NodeTree
19
+ NodeTree,
20
+ fileEditors,
21
+ pictureEditors,
22
+ ImageLoader: ImageLoader.init,
23
+ ImageCropper
16
24
  })
@@ -0,0 +1,28 @@
1
+ class FileEditor {
2
+ constructor(container) {
3
+ this.container = container
4
+ this.deleteLink = container.querySelector(".remove_file_link")
5
+ this.fileIcon = container.querySelector(".file_icon")
6
+ this.fileName = container.querySelector(".file_name")
7
+ this.deleteLink.addEventListener("click", this.removeFile.bind(this))
8
+ this.formFieldId = this.deleteLink.dataset.formFieldId
9
+ this.formField = container.querySelector(`#${this.formFieldId}`)
10
+ this.assignFileText = this.deleteLink.dataset.assignFileText
11
+ }
12
+
13
+ removeFile(event) {
14
+ event.stopPropagation()
15
+ this.formField.value = ""
16
+ this.fileIcon.innerHTML = ""
17
+ this.fileName.innerHTML = ""
18
+ this.deleteLink.classList.add("hidden")
19
+ Alchemy.setElementDirty(this.container.closest(".element-editor"))
20
+ return false
21
+ }
22
+ }
23
+
24
+ export default function init(selector) {
25
+ document.querySelectorAll(selector).forEach((node) => {
26
+ new FileEditor(node)
27
+ })
28
+ }
@@ -0,0 +1,103 @@
1
+ export default class ImageCropper {
2
+ constructor(
3
+ minSize,
4
+ defaultBox,
5
+ aspectRatio,
6
+ trueSize,
7
+ formFieldIds,
8
+ elementId
9
+ ) {
10
+ this.initialized = false
11
+
12
+ this.minSize = minSize
13
+ this.defaultBox = defaultBox
14
+ this.aspectRatio = aspectRatio
15
+ this.trueSize = trueSize
16
+ this.cropFromField = document.getElementById(formFieldIds[0])
17
+ this.cropSizeField = document.getElementById(formFieldIds[1])
18
+ this.elementId = elementId
19
+ this.dialog = Alchemy.currentDialog()
20
+ this.dialog.options.closed = this.destroy
21
+
22
+ this.init()
23
+ this.bind()
24
+ }
25
+
26
+ get jcropOptions() {
27
+ return {
28
+ onSelect: this.update.bind(this),
29
+ setSelect: this.box,
30
+ aspectRatio: this.aspectRatio,
31
+ minSize: this.minSize,
32
+ boxWidth: 800,
33
+ boxHeight: 600,
34
+ trueSize: this.trueSize,
35
+ closed: this.destroy.bind(this)
36
+ }
37
+ }
38
+
39
+ get cropFrom() {
40
+ if (this.cropFromField.value) {
41
+ return this.cropFromField.value.split("x").map((v) => parseInt(v))
42
+ }
43
+ }
44
+
45
+ get cropSize() {
46
+ if (this.cropSizeField.value) {
47
+ return this.cropSizeField.value.split("x").map((v) => parseInt(v))
48
+ }
49
+ }
50
+
51
+ get box() {
52
+ if (this.cropFrom && this.cropSize) {
53
+ return [
54
+ this.cropFrom[0],
55
+ this.cropFrom[1],
56
+ this.cropFrom[0] + this.cropSize[0],
57
+ this.cropFrom[1] + this.cropSize[1]
58
+ ]
59
+ } else {
60
+ return this.defaultBox
61
+ }
62
+ }
63
+
64
+ init() {
65
+ if (!this.initialized) {
66
+ this.api = $.Jcrop("#imageToCrop", this.jcropOptions)
67
+ this.initialized = true
68
+ }
69
+ }
70
+
71
+ update(coords) {
72
+ this.cropFromField.value = Math.round(coords.x) + "x" + Math.round(coords.y)
73
+ this.cropFromField.dispatchEvent(new Event("change"))
74
+ this.cropSizeField.value = Math.round(coords.w) + "x" + Math.round(coords.h)
75
+ this.cropFromField.dispatchEvent(new Event("change"))
76
+ }
77
+
78
+ reset() {
79
+ this.api.setSelect(this.defaultBox)
80
+ this.cropFromField.value = `${this.box[0]}x${this.box[1]}`
81
+ this.cropSizeField.value = `${this.box[2]}x${this.box[3] - this.box[1]}`
82
+ }
83
+
84
+ destroy() {
85
+ if (this.api) {
86
+ this.api.destroy()
87
+ }
88
+ this.initialized = false
89
+ return true
90
+ }
91
+
92
+ bind() {
93
+ this.dialog.dialog_body.find('button[type="submit"]').click(() => {
94
+ Alchemy.setElementDirty(`[data-element-id='${this.elementId}']`)
95
+ this.dialog.close()
96
+ return false
97
+ })
98
+ this.dialog.dialog_body.find('button[type="reset"]').click(() => {
99
+ this.reset()
100
+ return false
101
+ })
102
+ }
103
+ }