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
@@ -66,8 +66,8 @@ module Alchemy
66
66
  #
67
67
  # renders +app/views/alchemy/site_layouts/_default_site.html.erb+ for the site named "Default Site".
68
68
  #
69
- def render_site_layout
70
- render current_alchemy_site
69
+ def render_site_layout(&block)
70
+ render current_alchemy_site, &block
71
71
  rescue ActionView::MissingTemplate
72
72
  warning("Site layout for #{current_alchemy_site.try(:name)} not found. Please run `rails g alchemy:site_layouts`")
73
73
  ""
@@ -98,14 +98,6 @@ module Alchemy
98
98
  WARN
99
99
  end
100
100
 
101
- # Returns true if page is in the active branch
102
- def page_active?(page)
103
- Alchemy::Deprecation.warn("page_active? helper is deprecated and will be removed from Alchemy 6.0")
104
-
105
- @_page_ancestors ||= @page.self_and_ancestors.contentpages
106
- @_page_ancestors.include?(page)
107
- end
108
-
109
101
  # Returns page links in a breadcrumb beginning from root to current page.
110
102
  #
111
103
  # === Options:
@@ -127,7 +119,7 @@ module Alchemy
127
119
 
128
120
  pages = options[:page].
129
121
  self_and_ancestors.contentpages.
130
- accessible_by(current_ability, :see)
122
+ published
131
123
 
132
124
  if options.delete(:restricted_only)
133
125
  pages = pages.restricted
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ class BaseJob < ActiveJob::Base
5
+ # Automatically retry jobs that encountered a deadlock
6
+ # retry_on ActiveRecord::Deadlocked
7
+
8
+ # Most jobs are safe to ignore if the underlying records are no longer available
9
+ # discard_on ActiveJob::DeserializationError
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ class PublishPageJob < BaseJob
5
+ queue_as :default
6
+
7
+ def perform(page, public_on:)
8
+ Alchemy::Page::Publisher.new(page).publish!(public_on: public_on)
9
+ end
10
+ end
11
+ end
@@ -35,6 +35,10 @@ module Alchemy
35
35
  has_many :elements, through: :contents
36
36
  has_many :pages, through: :elements
37
37
 
38
+ scope :by_file_type, ->(file_type) { where(file_mime_type: file_type) }
39
+ scope :recent, -> { where("#{table_name}.created_at > ?", Time.current - 24.hours).order(:created_at) }
40
+ scope :without_tag, -> { left_outer_joins(:taggings).where(gutentag_taggings: { id: nil }) }
41
+
38
42
  # We need to define this method here to have it available in the validations below.
39
43
  class << self
40
44
  # The class used to generate URLs for attachments
@@ -51,6 +55,26 @@ module Alchemy
51
55
  @_url_class = klass
52
56
  end
53
57
 
58
+ def alchemy_resource_filters
59
+ [
60
+ {
61
+ name: :by_file_type,
62
+ values: distinct.pluck(:file_mime_type).map { |type| [Alchemy.t(type, scope: "mime_types"), type] }.sort_by(&:first),
63
+ },
64
+ {
65
+ name: :misc,
66
+ values: %w(recent last_upload without_tag),
67
+ },
68
+ ]
69
+ end
70
+
71
+ def last_upload
72
+ last_id = Attachment.maximum(:id)
73
+ return Attachment.all unless last_id
74
+
75
+ where(id: last_id)
76
+ end
77
+
54
78
  def searchable_alchemy_resource_attributes
55
79
  %w(name file_name)
56
80
  end
@@ -58,13 +82,6 @@ module Alchemy
58
82
  def allowed_filetypes
59
83
  Config.get(:uploader).fetch("allowed_filetypes", {}).fetch("alchemy/attachments", [])
60
84
  end
61
-
62
- def file_types_for_select
63
- file_types = Alchemy::Attachment.pluck(:file_mime_type).uniq.map do |type|
64
- [Alchemy.t(type, scope: "mime_types"), type]
65
- end
66
- file_types.sort_by(&:first)
67
- end
68
85
  end
69
86
 
70
87
  validates_presence_of :file
@@ -39,19 +39,14 @@ module Alchemy
39
39
  scope :essence_selects, -> { where(essence_type: "Alchemy::EssenceSelect") }
40
40
  scope :essence_texts, -> { where(essence_type: "Alchemy::EssenceText") }
41
41
  scope :named, ->(name) { where(name: name) }
42
- scope :available, -> { published.not_trashed }
42
+ scope :available, -> { published }
43
43
  scope :published, -> { joins(:element).merge(Element.published) }
44
- scope :not_trashed, -> { joins(:element).merge(Element.not_trashed) }
45
44
  scope :not_restricted, -> { joins(:element).merge(Element.not_restricted) }
46
45
 
47
46
  delegate :restricted?, to: :page, allow_nil: true
48
- delegate :trashed?, to: :element, allow_nil: true
49
- deprecate :trashed?, deprecator: Alchemy::Deprecation
50
47
  delegate :public?, to: :element, allow_nil: true
51
48
 
52
49
  class << self
53
- deprecate :not_trashed, deprecator: Alchemy::Deprecation
54
-
55
50
  # Returns the translated label for a content name.
56
51
  #
57
52
  # Translate it in your locale yml file:
@@ -7,7 +7,7 @@ module Alchemy
7
7
  extend ActiveSupport::Concern
8
8
 
9
9
  module ClassMethods
10
- SKIPPED_ATTRIBUTES_ON_COPY = %w(position created_at updated_at creator_id updater_id id)
10
+ SKIPPED_ATTRIBUTES_ON_COPY = %w(position created_at updated_at creator_id updater_id element_id id)
11
11
 
12
12
  # Builds a new content as descriped in the elements.yml file.
13
13
  #
@@ -19,14 +19,14 @@ module Alchemy
19
19
  return super if attributes.empty? || element.nil?
20
20
 
21
21
  definition = element.content_definition_for(attributes[:name])
22
- if definition.blank?
22
+ if definition.blank? && attributes[:essence_type].nil?
23
23
  raise ContentDefinitionError, "No definition found in elements.yml for #{attributes.inspect} and #{element.inspect}"
24
24
  end
25
25
 
26
26
  super(
27
- name: definition[:name],
28
- essence_type: normalize_essence_type(definition[:type]),
29
- element_id: element.id
27
+ name: attributes[:name],
28
+ essence_type: attributes[:essence_type] || normalize_essence_type(definition[:type]),
29
+ element: element
30
30
  ).tap(&:build_essence)
31
31
  end
32
32
 
@@ -53,18 +53,16 @@ module Alchemy
53
53
  # @copy.element_id # => 3
54
54
  #
55
55
  def copy(source, differences = {})
56
- new_content = Content.new(
57
- source.attributes.
56
+ Content.new(
57
+ source.attributes.with_indifferent_access.
58
58
  except(*SKIPPED_ATTRIBUTES_ON_COPY).
59
- merge(differences.with_indifferent_access),
60
- )
61
- new_essence = source.essence.class.create!(
62
- source.essence.attributes.
63
- except(*SKIPPED_ATTRIBUTES_ON_COPY),
64
- )
65
- new_content.tap do |content|
66
- content.essence = new_essence
67
- content.save
59
+ merge(differences.with_indifferent_access)
60
+ ).tap do |new_content|
61
+ new_content.build_essence(
62
+ source.essence.attributes.
63
+ except(*SKIPPED_ATTRIBUTES_ON_COPY)
64
+ )
65
+ new_content.save
68
66
  end
69
67
  end
70
68
 
@@ -117,29 +115,27 @@ module Alchemy
117
115
  #
118
116
  # If an optional type is passed, this type of essence gets created.
119
117
  #
120
- def build_essence(type = essence_type)
121
- self.essence = essence_class(type).new({
122
- ingredient: default_value,
123
- })
118
+ def build_essence(attributes = {})
119
+ self.essence = essence_class.new(
120
+ { content: self, ingredient: default_value }.merge(attributes)
121
+ )
124
122
  end
125
123
 
126
124
  # Creates essence from definition.
127
125
  #
128
126
  # If an optional type is passed, this type of essence gets created.
129
127
  #
130
- def create_essence!(type = nil)
131
- build_essence(type).save!
128
+ def create_essence!(attrs = {})
129
+ build_essence(attrs).save!
132
130
  save!
133
131
  end
134
132
 
135
133
  private
136
134
 
137
- # Returns a class constant from definition's type field.
135
+ # Returns a class constant from definition's type field or the essence_type column
138
136
  #
139
- # If an optional type is passed, this type of essence gets constantized.
140
- #
141
- def essence_class(type = nil)
142
- Content.normalize_essence_type(type || definition["type"]).constantize
137
+ def essence_class
138
+ (essence_type || Content.normalize_essence_type(definition["type"])).constantize
143
139
  end
144
140
  end
145
141
  end
@@ -7,7 +7,7 @@
7
7
  # id :integer not null, primary key
8
8
  # name :string
9
9
  # position :integer
10
- # page_id :integer not null
10
+ # page_version_id :integer not null
11
11
  # public :boolean default(TRUE)
12
12
  # fixed :boolean default(FALSE)
13
13
  # folded :boolean default(FALSE)
@@ -20,6 +20,12 @@
20
20
  # parent_element_id :integer
21
21
  #
22
22
 
23
+ require_dependency "alchemy/element/definitions"
24
+ require_dependency "alchemy/element/element_contents"
25
+ require_dependency "alchemy/element/element_ingredients"
26
+ require_dependency "alchemy/element/element_essences"
27
+ require_dependency "alchemy/element/presenters"
28
+
23
29
  module Alchemy
24
30
  class Element < BaseRecord
25
31
  NAME_REGEXP = /\A[a-z0-9_-]+\z/
@@ -34,39 +40,31 @@ module Alchemy
34
40
  "nestable_elements",
35
41
  "contents",
36
42
  "hint",
43
+ "ingredients",
37
44
  "taggable",
38
45
  "compact",
39
46
  "message",
40
47
  "deprecated",
41
48
  ].freeze
42
49
 
43
- SKIPPED_ATTRIBUTES_ON_COPY = [
44
- "cached_tag_list",
45
- "created_at",
46
- "creator_id",
47
- "id",
48
- "folded",
49
- "position",
50
- "updated_at",
51
- "updater_id",
52
- ].freeze
53
-
54
- # All Elements that share the same page id and parent element id and are fixed or not are considered a list.
50
+ # All Elements that share the same page version and parent element and are fixed or not are considered a list.
55
51
  #
56
- # If parent element id is nil (typical case for a simple page),
52
+ # If parent_element_id is nil (typical case for a simple page),
57
53
  # then all elements on that page are still in one list,
58
54
  # because acts_as_list correctly creates this statement:
59
55
  #
60
- # WHERE page_id = 1 and fixed = FALSE AND parent_element_id = NULL
56
+ # WHERE page_version_id = 1 and fixed = FALSE AND parent_element_id = NULL
61
57
  #
62
- acts_as_list scope: [:page_id, :fixed, :parent_element_id]
58
+ acts_as_list scope: [:page_version_id, :fixed, :parent_element_id]
63
59
 
64
60
  stampable stamper_class_name: Alchemy.user_class_name
65
61
 
66
62
  has_many :contents, dependent: :destroy, inverse_of: :element
67
63
 
64
+ before_destroy :delete_all_nested_elements
65
+
68
66
  has_many :all_nested_elements,
69
- -> { order(:position).not_trashed },
67
+ -> { order(:position) },
70
68
  class_name: "Alchemy::Element",
71
69
  foreign_key: :parent_element_id,
72
70
  dependent: :destroy
@@ -78,7 +76,8 @@ module Alchemy
78
76
  dependent: :destroy,
79
77
  inverse_of: :parent_element
80
78
 
81
- belongs_to :page, touch: true, inverse_of: :elements
79
+ belongs_to :page_version, touch: true, inverse_of: :elements
80
+ has_one :page, through: :page_version
82
81
 
83
82
  # A nested element belongs to a parent element.
84
83
  belongs_to :parent_element,
@@ -101,11 +100,10 @@ module Alchemy
101
100
 
102
101
  after_update :touch_touchable_pages
103
102
 
104
- scope :trashed, -> { where(position: nil).order("updated_at DESC") }
105
- scope :not_trashed, -> { where.not(position: nil) }
106
103
  scope :published, -> { where(public: true) }
104
+ scope :hidden, -> { where(public: false) }
107
105
  scope :not_restricted, -> { joins(:page).merge(Page.not_restricted) }
108
- scope :available, -> { published.not_trashed }
106
+ scope :available, -> { published }
109
107
  scope :named, ->(names) { where(name: names) }
110
108
  scope :excluded, ->(names) { where.not(name: names) }
111
109
  scope :fixed, -> { where(fixed: true) }
@@ -118,16 +116,14 @@ module Alchemy
118
116
  delegate :restricted?, to: :page, allow_nil: true
119
117
 
120
118
  # Concerns
121
- include Alchemy::Element::Definitions
122
- include Alchemy::Element::ElementContents
123
- include Alchemy::Element::ElementEssences
124
- include Alchemy::Element::Presenters
119
+ include Definitions
120
+ include ElementContents
121
+ include ElementEssences
122
+ include ElementIngredients
123
+ include Presenters
125
124
 
126
125
  # class methods
127
126
  class << self
128
- deprecate :trashed, deprecator: Alchemy::Deprecation
129
- deprecate :not_trashed, deprecator: Alchemy::Deprecation
130
-
131
127
  # Builds a new element as described in +/config/alchemy/elements.yml+
132
128
  #
133
129
  # - Returns a new Alchemy::Element object if no name is given in attributes,
@@ -159,26 +155,7 @@ module Alchemy
159
155
  # @copy.public? # => false
160
156
  #
161
157
  def copy(source_element, differences = {})
162
- attributes = source_element.attributes.with_indifferent_access
163
- .except(*SKIPPED_ATTRIBUTES_ON_COPY)
164
- .merge(differences)
165
- .merge({
166
- autogenerate_contents: false,
167
- autogenerate_nested_elements: false,
168
- tag_list: source_element.tag_list,
169
- })
170
-
171
- new_element = create!(attributes)
172
-
173
- if source_element.contents.any?
174
- source_element.copy_contents_to(new_element)
175
- end
176
-
177
- if source_element.nested_elements.any?
178
- source_element.copy_nested_elements_to(new_element)
179
- end
180
-
181
- new_element
158
+ Alchemy::DuplicateElement.new(source_element).call(differences)
182
159
  end
183
160
 
184
161
  def all_from_clipboard(clipboard)
@@ -225,19 +202,6 @@ module Alchemy
225
202
  end
226
203
  end
227
204
 
228
- # Trashing an element means nullifying its position, folding and unpublishing it.
229
- def trash!
230
- self.public = false
231
- self.folded = true
232
- remove_from_list
233
- end
234
- deprecate :trash!, deprecator: Alchemy::Deprecation
235
-
236
- def trashed?
237
- position.nil?
238
- end
239
- deprecate :trashed?, deprecator: Alchemy::Deprecation
240
-
241
205
  # Returns true if the definition of this element has a taggable true value.
242
206
  def taggable?
243
207
  definition["taggable"] == true
@@ -322,22 +286,12 @@ module Alchemy
322
286
  definition.fetch("nestable_elements", [])
323
287
  end
324
288
 
325
- # Copy all nested elements from current element to given target element.
326
- def copy_nested_elements_to(target_element)
327
- nested_elements.map do |nested_element|
328
- Element.copy(nested_element, {
329
- parent_element_id: target_element.id,
330
- page_id: target_element.page_id,
331
- })
332
- end
333
- end
334
-
335
289
  private
336
290
 
337
291
  def generate_nested_elements
338
292
  definition.fetch("autogenerate", []).each do |nestable_element|
339
293
  if nestable_elements.include?(nestable_element)
340
- Element.create(page: page, parent_element_id: id, name: nestable_element)
294
+ Element.create(page_version: page_version, parent_element_id: id, name: nestable_element)
341
295
  else
342
296
  log_warning("Element '#{nestable_element}' not a nestable element for '#{name}'. Skipping!")
343
297
  end
@@ -358,5 +312,18 @@ module Alchemy
358
312
 
359
313
  touchable_pages.each(&:touch)
360
314
  end
315
+
316
+ def delete_all_nested_elements
317
+ deeply_nested_elements = descendent_elements(self).flatten
318
+ DeleteElements.new(deeply_nested_elements).call
319
+ nested_elements.reset
320
+ all_nested_elements.reset
321
+ end
322
+
323
+ def descendent_elements(element)
324
+ element.all_nested_elements + element.all_nested_elements.map do |nested_element|
325
+ descendent_elements(nested_element)
326
+ end
327
+ end
361
328
  end
362
329
  end
@@ -1,37 +1,39 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Alchemy
4
- # Module concerning element definitions
5
- #
6
- module Element::Definitions
7
- extend ActiveSupport::Concern
4
+ class Element < BaseRecord
5
+ # Module concerning element definitions
6
+ #
7
+ module Definitions
8
+ extend ActiveSupport::Concern
8
9
 
9
- module ClassMethods
10
- # Returns the definitions from elements.yml file.
11
- #
12
- # Place a +elements.yml+ file inside your apps +config/alchemy+ folder to define
13
- # your own set of elements
14
- #
15
- def definitions
16
- ElementDefinition.all
17
- end
10
+ module ClassMethods
11
+ # Returns the definitions from elements.yml file.
12
+ #
13
+ # Place a +elements.yml+ file inside your apps +config/alchemy+ folder to define
14
+ # your own set of elements
15
+ #
16
+ def definitions
17
+ ElementDefinition.all
18
+ end
18
19
 
19
- # Returns one element definition by given name.
20
- #
21
- def definition_by_name(name)
22
- ElementDefinition.get(name)
20
+ # Returns one element definition by given name.
21
+ #
22
+ def definition_by_name(name)
23
+ ElementDefinition.get(name)
24
+ end
23
25
  end
24
- end
25
26
 
26
- # The definition of this element.
27
- #
28
- def definition
29
- if definition = self.class.definition_by_name(name)
30
- definition
31
- else
32
- log_warning "Could not find element definition for #{name}. " \
33
- "Please check your elements.yml file!"
34
- {}
27
+ # The definition of this element.
28
+ #
29
+ def definition
30
+ if definition = self.class.definition_by_name(name)
31
+ definition
32
+ else
33
+ log_warning "Could not find element definition for #{name}. " \
34
+ "Please check your elements.yml file!"
35
+ {}
36
+ end
35
37
  end
36
38
  end
37
39
  end