alchemy_cms 7.0.0.pre.a → 7.0.0.pre.b

Sign up to get free protection for your applications and to get access to all the features.
Files changed (199) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/brakeman-analysis.yml +2 -2
  3. data/.github/workflows/ci.yml +7 -7
  4. data/.github/workflows/lint.yml +17 -0
  5. data/.hound.yml +2 -3
  6. data/.rubocop.yml +4 -350
  7. data/.standard.yml +3 -0
  8. data/CHANGELOG.md +29 -0
  9. data/Gemfile +3 -1
  10. data/README.md +7 -9
  11. data/Rakefile +1 -1
  12. data/alchemy_cms.gemspec +2 -1
  13. data/app/assets/javascripts/alchemy/admin.js +0 -1
  14. data/app/assets/javascripts/alchemy/alchemy.link_dialog.js.coffee +6 -1
  15. data/app/components/alchemy/ingredients/audio_view.rb +37 -0
  16. data/app/components/alchemy/ingredients/base_view.rb +38 -0
  17. data/app/components/alchemy/ingredients/boolean_view.rb +13 -0
  18. data/app/components/alchemy/ingredients/datetime_view.rb +22 -0
  19. data/app/components/alchemy/ingredients/file_view.rb +40 -0
  20. data/app/components/alchemy/ingredients/headline_view.rb +20 -0
  21. data/app/components/alchemy/ingredients/html_view.rb +9 -0
  22. data/app/components/alchemy/ingredients/link_view.rb +25 -0
  23. data/app/components/alchemy/ingredients/node_view.rb +11 -0
  24. data/app/components/alchemy/ingredients/page_view.rb +15 -0
  25. data/app/components/alchemy/ingredients/picture_view.rb +108 -0
  26. data/app/components/alchemy/ingredients/richtext_view.rb +22 -0
  27. data/app/components/alchemy/ingredients/select_view.rb +6 -0
  28. data/app/components/alchemy/ingredients/text_view.rb +41 -0
  29. data/app/components/alchemy/ingredients/video_view.rb +39 -0
  30. data/app/controllers/alchemy/admin/attachments_controller.rb +3 -3
  31. data/app/controllers/alchemy/admin/base_controller.rb +7 -7
  32. data/app/controllers/alchemy/admin/clipboard_controller.rb +2 -2
  33. data/app/controllers/alchemy/admin/elements_controller.rb +26 -11
  34. data/app/controllers/alchemy/admin/languages_controller.rb +1 -1
  35. data/app/controllers/alchemy/admin/nodes_controller.rb +2 -2
  36. data/app/controllers/alchemy/admin/pages_controller.rb +10 -10
  37. data/app/controllers/alchemy/admin/pictures_controller.rb +14 -14
  38. data/app/controllers/alchemy/admin/resources_controller.rb +27 -28
  39. data/app/controllers/alchemy/admin/styleguide_controller.rb +1 -0
  40. data/app/controllers/alchemy/admin/tags_controller.rb +11 -11
  41. data/app/controllers/alchemy/api/base_controller.rb +2 -2
  42. data/app/controllers/alchemy/api/elements_controller.rb +11 -11
  43. data/app/controllers/alchemy/api/ingredients_controller.rb +1 -1
  44. data/app/controllers/alchemy/api/nodes_controller.rb +1 -1
  45. data/app/controllers/alchemy/api/pages_controller.rb +11 -11
  46. data/app/controllers/alchemy/attachments_controller.rb +3 -3
  47. data/app/controllers/alchemy/base_controller.rb +1 -1
  48. data/app/controllers/alchemy/messages_controller.rb +9 -9
  49. data/app/controllers/alchemy/pages_controller.rb +8 -19
  50. data/app/controllers/concerns/alchemy/admin/archive_overlay.rb +1 -0
  51. data/app/controllers/concerns/alchemy/admin/uploader_responses.rb +5 -7
  52. data/app/controllers/concerns/alchemy/legacy_page_redirects.rb +5 -5
  53. data/app/decorators/alchemy/element_editor.rb +4 -4
  54. data/app/decorators/alchemy/ingredient_editor.rb +6 -6
  55. data/app/helpers/alchemy/admin/attachments_helper.rb +1 -1
  56. data/app/helpers/alchemy/admin/base_helper.rb +21 -22
  57. data/app/helpers/alchemy/admin/elements_helper.rb +1 -1
  58. data/app/helpers/alchemy/admin/form_helper.rb +1 -1
  59. data/app/helpers/alchemy/admin/navigation_helper.rb +7 -7
  60. data/app/helpers/alchemy/admin/pages_helper.rb +2 -2
  61. data/app/helpers/alchemy/admin/tags_helper.rb +3 -3
  62. data/app/helpers/alchemy/base_helper.rb +2 -2
  63. data/app/helpers/alchemy/elements_block_helper.rb +9 -7
  64. data/app/helpers/alchemy/elements_helper.rb +12 -12
  65. data/app/helpers/alchemy/pages_helper.rb +11 -11
  66. data/app/helpers/alchemy/url_helper.rb +1 -1
  67. data/app/mailers/alchemy/messages_mailer.rb +1 -1
  68. data/app/models/alchemy/attachment.rb +6 -6
  69. data/app/models/alchemy/base_record.rb +1 -0
  70. data/app/models/alchemy/eager_loading.rb +6 -6
  71. data/app/models/alchemy/element/definitions.rb +1 -1
  72. data/app/models/alchemy/element/element_ingredients.rb +3 -3
  73. data/app/models/alchemy/element.rb +2 -2
  74. data/app/models/alchemy/elements_repository.rb +1 -1
  75. data/app/models/alchemy/image_cropper_settings.rb +2 -2
  76. data/app/models/alchemy/ingredient.rb +14 -12
  77. data/app/models/alchemy/ingredient_validator.rb +1 -1
  78. data/app/models/alchemy/ingredients/datetime.rb +1 -1
  79. data/app/models/alchemy/ingredients/file.rb +5 -5
  80. data/app/models/alchemy/ingredients/headline.rb +4 -4
  81. data/app/models/alchemy/ingredients/picture.rb +27 -9
  82. data/app/models/alchemy/ingredients/richtext.rb +15 -12
  83. data/app/models/alchemy/ingredients/text.rb +6 -6
  84. data/app/models/alchemy/language/code.rb +1 -1
  85. data/app/models/alchemy/language.rb +4 -4
  86. data/app/models/alchemy/legacy_page_url.rb +1 -1
  87. data/app/models/alchemy/node.rb +2 -2
  88. data/app/models/alchemy/page/page_elements.rb +14 -14
  89. data/app/models/alchemy/page/page_naming.rb +4 -4
  90. data/app/models/alchemy/page/page_natures.rb +1 -1
  91. data/app/models/alchemy/page/page_scopes.rb +5 -5
  92. data/app/models/alchemy/page.rb +11 -11
  93. data/app/models/alchemy/picture/calculations.rb +2 -2
  94. data/app/models/alchemy/picture/transformations.rb +2 -2
  95. data/app/models/alchemy/picture/url.rb +4 -4
  96. data/app/models/alchemy/picture.rb +11 -10
  97. data/app/models/alchemy/picture_thumb/create.rb +1 -1
  98. data/app/models/alchemy/picture_thumb.rb +1 -1
  99. data/app/models/alchemy/picture_variant.rb +2 -3
  100. data/app/models/alchemy/tag.rb +8 -0
  101. data/app/models/concerns/alchemy/picture_thumbnails.rb +6 -6
  102. data/app/serializers/alchemy/base_serializer.rb +1 -1
  103. data/app/serializers/alchemy/page_tree_serializer.rb +7 -7
  104. data/app/services/alchemy/duplicate_element.rb +3 -3
  105. data/app/services/alchemy/tag_validations.rb +1 -1
  106. data/app/views/alchemy/admin/elements/_element.html.erb +3 -0
  107. data/app/views/alchemy/admin/pages/edit.html.erb +0 -3
  108. data/app/views/alchemy/admin/pages/update.js.erb +10 -4
  109. data/app/views/alchemy/admin/pictures/_infos.html.erb +1 -1
  110. data/app/views/alchemy/ingredients/_audio_view.html.erb +1 -14
  111. data/app/views/alchemy/ingredients/_boolean_view.html.erb +1 -1
  112. data/app/views/alchemy/ingredients/_datetime_view.html.erb +3 -9
  113. data/app/views/alchemy/ingredients/_file_view.html.erb +3 -16
  114. data/app/views/alchemy/ingredients/_headline_view.html.erb +4 -10
  115. data/app/views/alchemy/ingredients/_html_view.html.erb +1 -1
  116. data/app/views/alchemy/ingredients/_link_view.html.erb +4 -9
  117. data/app/views/alchemy/ingredients/_node_view.html.erb +1 -1
  118. data/app/views/alchemy/ingredients/_page_view.html.erb +1 -4
  119. data/app/views/alchemy/ingredients/_picture_view.html.erb +4 -5
  120. data/app/views/alchemy/ingredients/_richtext_editor.html.erb +11 -2
  121. data/app/views/alchemy/ingredients/_richtext_view.html.erb +3 -3
  122. data/app/views/alchemy/ingredients/_select_view.html.erb +1 -1
  123. data/app/views/alchemy/ingredients/_text_view.html.erb +3 -19
  124. data/app/views/alchemy/ingredients/_video_view.html.erb +3 -18
  125. data/app/views/alchemy/ingredients/shared/_link_tools.html.erb +1 -0
  126. data/app/views/alchemy/ingredients/shared/_picture_tools.html.erb +1 -0
  127. data/app/views/layouts/alchemy/admin.html.erb +7 -9
  128. data/bin/setup +37 -0
  129. data/bin/start +17 -0
  130. data/config/initializers/assets.rb +1 -0
  131. data/config/initializers/dragonfly.rb +1 -0
  132. data/config/initializers/mime_types.rb +1 -0
  133. data/config/initializers/mini_profiler.rb +1 -0
  134. data/config/initializers/simple_form.rb +3 -2
  135. data/config/locales/alchemy.en.yml +1 -1
  136. data/config/routes.rb +21 -20
  137. data/config/spring.rb +1 -0
  138. data/db/migrate/20230121212637_alchemy_six_point_one.rb +8 -8
  139. data/db/migrate/20230505132743_add_indexes_to_alchemy_pictures.rb +6 -0
  140. data/lib/alchemy/admin/locale.rb +3 -3
  141. data/lib/alchemy/admin/preview_url.rb +2 -2
  142. data/lib/alchemy/auth_accessors.rb +1 -1
  143. data/lib/alchemy/config.rb +1 -1
  144. data/lib/alchemy/controller_actions.rb +4 -4
  145. data/lib/alchemy/deprecation.rb +1 -0
  146. data/lib/alchemy/dragonfly/processors/thumbnail.rb +1 -1
  147. data/lib/alchemy/element_definition.rb +2 -2
  148. data/lib/alchemy/engine.rb +2 -1
  149. data/lib/alchemy/filetypes.rb +7 -7
  150. data/lib/alchemy/forms/builder.rb +4 -4
  151. data/lib/alchemy/i18n.rb +6 -4
  152. data/lib/alchemy/install/tasks.rb +2 -1
  153. data/lib/alchemy/name_conversions.rb +1 -1
  154. data/lib/alchemy/page_layout.rb +1 -1
  155. data/lib/alchemy/permissions.rb +5 -4
  156. data/lib/alchemy/resource.rb +10 -10
  157. data/lib/alchemy/resources_helper.rb +7 -7
  158. data/lib/alchemy/routing_constraints.rb +2 -2
  159. data/lib/alchemy/seeder.rb +12 -5
  160. data/lib/alchemy/shell.rb +2 -1
  161. data/lib/alchemy/taggable.rb +3 -2
  162. data/lib/alchemy/tasks/tidy.rb +1 -0
  163. data/lib/alchemy/test_support/capybara_helpers.rb +1 -1
  164. data/lib/alchemy/test_support/config_stubbing.rb +1 -0
  165. data/lib/alchemy/test_support/factories/element_factory.rb +4 -0
  166. data/lib/alchemy/test_support/factories/page_factory.rb +2 -2
  167. data/lib/alchemy/test_support/having_crop_action_examples.rb +9 -9
  168. data/lib/alchemy/test_support/having_picture_thumbnails_examples.rb +33 -33
  169. data/lib/alchemy/test_support/integration_helpers.rb +4 -3
  170. data/lib/alchemy/test_support/shared_contexts.rb +2 -1
  171. data/lib/alchemy/test_support/shared_dom_ids_examples.rb +9 -9
  172. data/lib/alchemy/test_support/shared_ingredient_examples.rb +12 -6
  173. data/lib/alchemy/test_support/shared_uploader_examples.rb +1 -0
  174. data/lib/alchemy/tinymce.rb +3 -26
  175. data/lib/alchemy/upgrader.rb +1 -0
  176. data/lib/alchemy/version.rb +1 -1
  177. data/lib/alchemy_cms.rb +1 -0
  178. data/lib/generators/alchemy/base.rb +3 -2
  179. data/lib/generators/alchemy/elements/elements_generator.rb +2 -1
  180. data/lib/generators/alchemy/ingredient/ingredient_generator.rb +1 -0
  181. data/lib/generators/alchemy/install/files/application.html.erb +1 -1
  182. data/lib/generators/alchemy/install/install_generator.rb +2 -1
  183. data/lib/generators/alchemy/module/module_generator.rb +1 -0
  184. data/lib/generators/alchemy/page_layouts/page_layouts_generator.rb +1 -0
  185. data/lib/generators/alchemy/site_layouts/site_layouts_generator.rb +1 -0
  186. data/lib/generators/alchemy/views/views_generator.rb +2 -1
  187. data/lib/tasks/alchemy/thumbnails.rake +5 -5
  188. data/lib/tasks/alchemy/tidy.rake +1 -0
  189. data/lib/tasks/alchemy/upgrade.rake +6 -5
  190. data/package/admin.js +2 -0
  191. data/package/dist/admin.js +3 -3
  192. data/package/dist/admin.js.map +4 -4
  193. data/package/src/datepicker.js +1 -0
  194. data/package/src/tinymce.js +142 -0
  195. data/package.json +2 -2
  196. metadata +39 -7
  197. data/app/assets/javascripts/alchemy/alchemy.tinymce.js.coffee +0 -93
  198. data/app/presenters/alchemy/picture_view.rb +0 -88
  199. data/app/views/alchemy/admin/pages/_tinymce_custom_config.html.erb +0 -10
@@ -0,0 +1,37 @@
1
+ module Alchemy
2
+ module Ingredients
3
+ class AudioView < BaseView
4
+ def call
5
+ content_tag(:audio, **html_options) do
6
+ tag(:source, src: src, type: type)
7
+ end
8
+ end
9
+
10
+ def render?
11
+ !!ingredient.attachment
12
+ end
13
+
14
+ private
15
+
16
+ def src
17
+ alchemy.show_attachment_path(
18
+ ingredient.attachment,
19
+ format: ingredient.attachment.suffix
20
+ )
21
+ end
22
+
23
+ def type
24
+ ingredient.attachment.file_mime_type
25
+ end
26
+
27
+ def html_options
28
+ {
29
+ controls: ingredient.controls,
30
+ autoplay: ingredient.autoplay,
31
+ loop: ingredient.loop,
32
+ muted: ingredient.muted
33
+ }
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,38 @@
1
+ module Alchemy
2
+ module Ingredients
3
+ class BaseView < ViewComponent::Base
4
+ attr_reader :ingredient, :html_options
5
+
6
+ delegate :alchemy, to: :helpers
7
+ delegate :settings, :value, to: :ingredient
8
+
9
+ # @param ingredient [Alchemy::Ingredient]
10
+ # @param html_options [Hash] Options that will be passed to the wrapper tag.
11
+ def initialize(ingredient, html_options: {})
12
+ raise ArgumentError, "Ingredient missing!" if ingredient.nil?
13
+
14
+ @ingredient = ingredient
15
+ @html_options = html_options
16
+ end
17
+
18
+ def call
19
+ value
20
+ end
21
+
22
+ def render?
23
+ value.present?
24
+ end
25
+
26
+ private
27
+
28
+ # Fetches value from ingredient settings and allows to merge a value on top of it
29
+ #
30
+ # @param key [Symbol] - The settings key you want to fetch the value from
31
+ # @param value [Object] - A optional value that can override the setting.
32
+ # Normally passed as into the ingredient view.
33
+ def settings_value(key, value: nil, default: nil)
34
+ value.nil? ? settings.fetch(key, default) : value
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,13 @@
1
+ module Alchemy
2
+ module Ingredients
3
+ class BooleanView < BaseView
4
+ def call
5
+ Alchemy.t(value, scope: "ingredient_values.boolean")
6
+ end
7
+
8
+ def render?
9
+ !value.nil?
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,22 @@
1
+ module Alchemy
2
+ module Ingredients
3
+ class DatetimeView < BaseView
4
+ attr_reader :date_format
5
+
6
+ # @param ingredient [Alchemy::Ingredient]
7
+ # @param date_format [String] The date format to use. Use either a strftime format string, a I18n format symbol or "rfc822".
8
+ def initialize(ingredient, date_format: nil, html_options: {})
9
+ super(ingredient)
10
+ @date_format = settings_value(:date_format, value: date_format)
11
+ end
12
+
13
+ def call
14
+ if date_format == "rfc822"
15
+ ingredient.value.to_s(:rfc822)
16
+ else
17
+ ::I18n.l(ingredient.value, format: date_format)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,40 @@
1
+ module Alchemy
2
+ module Ingredients
3
+ class FileView < BaseView
4
+ delegate :attachment, to: :ingredient
5
+
6
+ # @param ingredient [Alchemy::Ingredient]
7
+ # @param link_text [String] The link text. If not given, the ingredients link_text setting or the attachments name will be used.
8
+ # @param html_options [Hash] Options that will be passed to the a tag.
9
+ def initialize(ingredient, link_text: nil, html_options: {})
10
+ super(ingredient, html_options: html_options)
11
+ @link_text = settings_value(:link_text, value: link_text, default: attachment&.name)
12
+ end
13
+
14
+ def call
15
+ link_to(
16
+ link_text,
17
+ attachment.url(
18
+ download: true,
19
+ name: attachment.slug,
20
+ format: attachment.suffix
21
+ ),
22
+ {
23
+ class: ingredient.css_class.presence,
24
+ title: ingredient.title.presence
25
+ }.merge(html_options)
26
+ )
27
+ end
28
+
29
+ def render?
30
+ !attachment.nil?
31
+ end
32
+
33
+ private
34
+
35
+ def link_text
36
+ ingredient.link_text.presence || @link_text
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,20 @@
1
+ module Alchemy
2
+ module Ingredients
3
+ class HeadlineView < BaseView
4
+ def initialize(ingredient, level: nil, html_options: {})
5
+ super(ingredient, html_options: html_options)
6
+ @level = level
7
+ end
8
+
9
+ def call
10
+ content_tag "h#{@level || ingredient.level}",
11
+ ingredient.value,
12
+ id: ingredient.dom_id.presence,
13
+ class: [
14
+ ingredient.size ? "h#{ingredient.size}" : nil,
15
+ html_options[:class]
16
+ ]
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,9 @@
1
+ module Alchemy
2
+ module Ingredients
3
+ class HtmlView < BaseView
4
+ def call
5
+ value.to_s.html_safe
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,25 @@
1
+ module Alchemy
2
+ module Ingredients
3
+ class LinkView < BaseView
4
+ attr_reader :link_text
5
+
6
+ # @param ingredient [Alchemy::Ingredient]
7
+ # @param text [String] The link text. If not given, the ingredient's text setting or the value will be used.
8
+ # @param html_options [Hash] Options that will be passed to the a tag.
9
+ def initialize(ingredient, text: nil, html_options: {})
10
+ super(ingredient, html_options: html_options)
11
+ @link_text = settings_value(:text, value: text, default: value)
12
+ end
13
+
14
+ def call
15
+ link_to(link_text, value, {target: link_target}.merge(html_options))
16
+ end
17
+
18
+ private
19
+
20
+ def link_target
21
+ (ingredient.link_target == "blank") ? "_blank" : nil
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,11 @@
1
+ module Alchemy
2
+ module Ingredients
3
+ class NodeView < BaseView
4
+ delegate :node, to: :ingredient
5
+
6
+ def call
7
+ render(node)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ module Alchemy
2
+ module Ingredients
3
+ class PageView < BaseView
4
+ delegate :page, to: :ingredient
5
+
6
+ def call
7
+ link_to page.name, alchemy.show_page_path(urlname: page.urlname)
8
+ end
9
+
10
+ def render?
11
+ !!page
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ module Ingredients
5
+ # Renders a picture ingredient view
6
+ class PictureView < BaseView
7
+ attr_reader :ingredient,
8
+ :show_caption,
9
+ :disable_link,
10
+ :srcset,
11
+ :sizes,
12
+ :html_options,
13
+ :picture_options,
14
+ :picture
15
+
16
+ # @param ingredient [Alchemy::Ingredient]
17
+ # @param show_caption [Boolean] (true) Whether to show a caption or not, even if present on the picture.
18
+ # @param disable_link [Boolean] (false) Whether to disable the link even if the picture has a link.
19
+ # @param srcset [Array<String>] An array of srcset sizes that will generate variants of the picture.
20
+ # @param sizes [Array<String>] An array of sizes that will be passed to the img tag.
21
+ # @param picture_options [Hash] Options that will be passed to the picture url. See {Alchemy::PictureVariant} for options.
22
+ # @param html_options [Hash] Options that will be passed to the img tag.
23
+ # @see Alchemy::PictureVariant
24
+ def initialize(
25
+ ingredient,
26
+ show_caption: nil,
27
+ disable_link: nil,
28
+ srcset: nil,
29
+ sizes: nil,
30
+ picture_options: {},
31
+ html_options: {}
32
+ )
33
+ super(ingredient)
34
+ @show_caption = settings_value(:show_caption, value: show_caption, default: true)
35
+ @disable_link = settings_value(:disable_link, value: disable_link, default: false)
36
+ @srcset = settings_value(:srcset, value: srcset, default: [])
37
+ @sizes = settings_value(:sizes, value: sizes, default: [])
38
+ @picture_options = picture_options || {}
39
+ @html_options = html_options || {}
40
+ @picture = ingredient.picture
41
+ end
42
+
43
+ def call
44
+ return if picture.blank?
45
+
46
+ output = caption ? img_tag + caption : img_tag
47
+
48
+ if is_linked?
49
+ output = link_to(output, url_for(ingredient.link), {
50
+ title: ingredient.link_title.presence,
51
+ target: (ingredient.link_target == "blank") ? "_blank" : nil,
52
+ data: {link_target: ingredient.link_target.presence}
53
+ })
54
+ end
55
+
56
+ if caption
57
+ content_tag(:figure, output, {class: ingredient.css_class.presence}.merge(html_options))
58
+ else
59
+ output
60
+ end
61
+ end
62
+
63
+ private
64
+
65
+ def caption
66
+ return unless show_caption?
67
+
68
+ @_caption ||= content_tag(:figcaption, ingredient.caption)
69
+ end
70
+
71
+ def src
72
+ ingredient.picture_url(picture_options)
73
+ end
74
+
75
+ def img_tag
76
+ @_img_tag ||= image_tag(
77
+ src, {
78
+ alt: alt_text,
79
+ title: ingredient.title.presence,
80
+ class: caption ? nil : ingredient.css_class.presence,
81
+ srcset: srcset_options.join(", ").presence,
82
+ sizes: sizes.join(", ").presence
83
+ }.merge(caption ? {} : html_options)
84
+ )
85
+ end
86
+
87
+ def show_caption?
88
+ show_caption && ingredient.caption.present?
89
+ end
90
+
91
+ def is_linked?
92
+ !disable_link && ingredient.link.present?
93
+ end
94
+
95
+ def srcset_options
96
+ srcset.map do |size|
97
+ url = ingredient.picture_url(size: size)
98
+ width, height = size.split("x")
99
+ width.present? ? "#{url} #{width}w" : "#{url} #{height}h"
100
+ end
101
+ end
102
+
103
+ def alt_text
104
+ ingredient.alt_tag.presence || html_options.delete(:alt) || ingredient.picture.name&.humanize
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,22 @@
1
+ module Alchemy
2
+ module Ingredients
3
+ class RichtextView < BaseView
4
+ attr_reader :plain_text
5
+
6
+ # @param ingredient [Alchemy::Ingredient]
7
+ # @param plain_text [Boolean] (false) Whether to show as plain text or with markup
8
+ def initialize(ingredient, plain_text: nil, html_options: {})
9
+ super(ingredient)
10
+ @plain_text = settings_value(:plain_text, value: plain_text, default: false)
11
+ end
12
+
13
+ def call
14
+ if plain_text
15
+ ingredient.stripped_body
16
+ else
17
+ value.to_s.html_safe
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,6 @@
1
+ module Alchemy
2
+ module Ingredients
3
+ class SelectView < BaseView
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,41 @@
1
+ module Alchemy
2
+ module Ingredients
3
+ class TextView < BaseView
4
+ attr_reader :disable_link
5
+
6
+ delegate :dom_id, :link, :link_title, :link_target,
7
+ to: :ingredient
8
+
9
+ # @param ingredient [Alchemy::Ingredient]
10
+ # @param disable_link [Boolean] (false) Whether to disable the link even if the picture has a link.
11
+ # @param html_options [Hash] Options that will be passed to the a tag.
12
+ def initialize(ingredient, disable_link: nil, html_options: {})
13
+ super(ingredient, html_options: html_options)
14
+ @disable_link = settings_value(:disable_link, value: disable_link, default: false)
15
+ end
16
+
17
+ def call
18
+ if disable_link?
19
+ dom_id.present? ? anchor : value
20
+ else
21
+ link_to(value, url_for(link), {
22
+ id: dom_id.presence,
23
+ title: link_title,
24
+ target: ((link_target == "blank") ? "_blank" : nil),
25
+ data: {link_target: link_target}
26
+ }.merge(html_options))
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def anchor
33
+ content_tag(:a, value, {id: dom_id}.merge(html_options))
34
+ end
35
+
36
+ def disable_link?
37
+ link.blank? || disable_link
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,39 @@
1
+ module Alchemy
2
+ module Ingredients
3
+ class VideoView < BaseView
4
+ delegate :attachment, to: :ingredient
5
+
6
+ def call
7
+ content_tag(:video, html_options) do
8
+ tag(:source, src: src, type: attachment.file_mime_type)
9
+ end
10
+ end
11
+
12
+ def render?
13
+ !attachment.nil?
14
+ end
15
+
16
+ private
17
+
18
+ def src
19
+ alchemy.show_attachment_path(
20
+ attachment,
21
+ format: attachment.suffix
22
+ )
23
+ end
24
+
25
+ def html_options
26
+ {
27
+ controls: ingredient.controls,
28
+ autoplay: ingredient.autoplay,
29
+ loop: ingredient.loop,
30
+ muted: ingredient.muted,
31
+ playsinline: ingredient.playsinline,
32
+ preload: ingredient.preload.presence,
33
+ width: ingredient.width.presence,
34
+ height: ingredient.height.presence
35
+ }.merge(@html_options)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -52,7 +52,7 @@ module Alchemy
52
52
  render_errors_or_redirect(
53
53
  @attachment,
54
54
  admin_attachments_path(search_filter_params),
55
- Alchemy.t("File successfully updated"),
55
+ Alchemy.t("File successfully updated")
56
56
  )
57
57
  end
58
58
  end
@@ -68,7 +68,7 @@ module Alchemy
68
68
  @attachment = Attachment.find(params[:id])
69
69
  send_file @attachment.file.path, {
70
70
  filename: @attachment.file_name,
71
- type: @attachment.file_mime_type,
71
+ type: @attachment.file_mime_type
72
72
  }
73
73
  end
74
74
 
@@ -77,7 +77,7 @@ module Alchemy
77
77
  def search_filter_params
78
78
  @_search_filter_params ||= params.except(*COMMON_SEARCH_FILTER_EXCLUDES + [:attachment]).permit(
79
79
  *common_search_filter_includes + [
80
- :form_field_id,
80
+ :form_field_id
81
81
  ]
82
82
  )
83
83
  end
@@ -54,7 +54,7 @@ module Alchemy
54
54
  else
55
55
  respond_to do |format|
56
56
  format.html { render "500", status: 500 }
57
- format.json { render json: { message: @notice }, status: 500 }
57
+ format.json { render json: {message: @notice}, status: 500 }
58
58
  end
59
59
  end
60
60
  end
@@ -101,7 +101,7 @@ module Alchemy
101
101
  flash[:notice] = Alchemy.t(flash_notice)
102
102
  do_redirect_to redirect_url
103
103
  else
104
- render action: (params[:action] == "update" ? "edit" : "new")
104
+ render action: ((params[:action] == "update") ? "edit" : "new")
105
105
  end
106
106
  end
107
107
 
@@ -138,11 +138,11 @@ module Alchemy
138
138
  #
139
139
  def current_alchemy_site
140
140
  @current_alchemy_site ||= begin
141
- site_id = params[:site_id] || session[:alchemy_site_id]
142
- site = Site.find_by(id: site_id) || super
143
- session[:alchemy_site_id] = site&.id
144
- site
145
- end
141
+ site_id = params[:site_id] || session[:alchemy_site_id]
142
+ site = Site.find_by(id: site_id) || super
143
+ session[:alchemy_site_id] = site&.id
144
+ site
145
+ end
146
146
  end
147
147
 
148
148
  def notify_error_tracker(exception)
@@ -3,7 +3,7 @@
3
3
  module Alchemy
4
4
  module Admin
5
5
  class ClipboardController < Alchemy::Admin::BaseController
6
- REMARKABLE_TYPES = %w(elements pages)
6
+ REMARKABLE_TYPES = %w[elements pages]
7
7
 
8
8
  authorize_resource class: :alchemy_admin_clipboard
9
9
  before_action :set_clipboard
@@ -20,7 +20,7 @@ module Alchemy
20
20
  unless @clipboard.detect { |item| item["id"] == remarkable_params[:remarkable_id] }
21
21
  @clipboard << {
22
22
  "id" => remarkable_params[:remarkable_id],
23
- "action" => params[:remove] ? "cut" : "copy",
23
+ "action" => params[:remove] ? "cut" : "copy"
24
24
  }
25
25
  end
26
26
  respond_to do |format|
@@ -29,10 +29,11 @@ module Alchemy
29
29
  @page_version = PageVersion.find(params[:element][:page_version_id])
30
30
  @page = @page_version.page
31
31
  Element.transaction do
32
- if @paste_from_clipboard = params[:paste_from_clipboard].present?
33
- @element = paste_element_from_clipboard
32
+ @paste_from_clipboard = params[:paste_from_clipboard].present?
33
+ @element = if @paste_from_clipboard
34
+ paste_element_from_clipboard
34
35
  else
35
- @element = Element.new(create_element_params)
36
+ Element.new(create_element_params)
36
37
  end
37
38
  if @page.definition["insert_elements_at"] == "top"
38
39
  @insert_at_top = true
@@ -65,7 +66,7 @@ module Alchemy
65
66
  def destroy
66
67
  @richtext_ids = @element.richtext_ingredients_ids
67
68
  @element.destroy
68
- @notice = Alchemy.t("Successfully deleted element") % { element: @element.display_name }
69
+ @notice = Alchemy.t("Successfully deleted element") % {element: @element.display_name}
69
70
  end
70
71
 
71
72
  def publish
@@ -80,7 +81,7 @@ module Alchemy
80
81
  # element over from another nestable element
81
82
  Element.find_by(id: element_id).update_columns(
82
83
  parent_element_id: params[:parent_element_id],
83
- position: position,
84
+ position: position
84
85
  )
85
86
  end
86
87
  # Need to manually touch the parent because Rails does not do it
@@ -95,24 +96,38 @@ module Alchemy
95
96
  @page = @element.page
96
97
  # We do not want to trigger the touch callback or any validations
97
98
  @element.update_columns(folded: !@element.folded)
99
+ # Fold all nested elements if folded
100
+ if @element.folded?
101
+ ids = collapse_nested_elements_ids(@element)
102
+ Alchemy::Element.where(id: ids).update_all(folded: true)
103
+ end
98
104
  end
99
105
 
100
106
  private
101
107
 
108
+ def collapse_nested_elements_ids(element)
109
+ ids = []
110
+ element.all_nested_elements.includes(:all_nested_elements).reject(&:compact?).each do |nested_element|
111
+ ids.push nested_element.id if nested_element.expanded?
112
+ ids.concat collapse_nested_elements_ids(nested_element) if nested_element.all_nested_elements.reject(&:compact?).any?
113
+ end
114
+ ids
115
+ end
116
+
102
117
  def element_includes
103
118
  [
104
119
  {
105
- ingredients: :related_object,
120
+ ingredients: :related_object
106
121
  },
107
122
  :tags,
108
123
  {
109
124
  all_nested_elements: [
110
125
  {
111
- ingredients: :related_object,
126
+ ingredients: :related_object
112
127
  },
113
- :tags,
114
- ],
115
- },
128
+ :tags
129
+ ]
130
+ }
116
131
  ]
117
132
  end
118
133
 
@@ -138,7 +153,7 @@ module Alchemy
138
153
  @source_element,
139
154
  {
140
155
  parent_element_id: create_element_params[:parent_element_id],
141
- page_version_id: @page_version.id,
156
+ page_version_id: @page_version.id
142
157
  }
143
158
  )
144
159
  if element_from_clipboard["action"] == "cut"
@@ -14,7 +14,7 @@ module Alchemy
14
14
  def new
15
15
  @language = Language.new(
16
16
  site: @current_site,
17
- page_layout: Config.get(:default_language)["page_layout"],
17
+ page_layout: Config.get(:default_language)["page_layout"]
18
18
  )
19
19
  end
20
20
 
@@ -12,7 +12,7 @@ module Alchemy
12
12
  def new
13
13
  @node = Node.new(
14
14
  parent_id: params[:parent_id],
15
- language: @current_language,
15
+ language: @current_language
16
16
  )
17
17
  end
18
18
 
@@ -28,7 +28,7 @@ module Alchemy
28
28
  :url,
29
29
  :title,
30
30
  :nofollow,
31
- :external,
31
+ :external
32
32
  )
33
33
  end
34
34
  end