alchemy_cms 7.2.9 → 7.3.0

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 (191) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +97 -29
  3. data/Gemfile +3 -11
  4. data/Rakefile +1 -0
  5. data/alchemy_cms.gemspec +7 -7
  6. data/app/assets/builds/alchemy/admin/print.css +1 -0
  7. data/app/assets/builds/alchemy/admin/print.css.map +1 -0
  8. data/app/assets/builds/alchemy/admin.css +1 -0
  9. data/app/assets/builds/alchemy/admin.css.map +1 -0
  10. data/app/assets/builds/alchemy/welcome.css +1 -0
  11. data/app/assets/builds/alchemy/welcome.css.map +1 -0
  12. data/app/assets/builds/tinymce/skins/content/alchemy/content.css +1 -0
  13. data/app/assets/builds/tinymce/skins/content/alchemy/content.css.map +1 -0
  14. data/app/assets/builds/tinymce/skins/content/alchemy/content.min.css +1 -0
  15. data/app/assets/builds/tinymce/skins/content/alchemy/content.min.css.map +1 -0
  16. data/app/assets/builds/tinymce/skins/ui/alchemy/skin.css +1 -0
  17. data/app/assets/builds/tinymce/skins/ui/alchemy/skin.css.map +1 -0
  18. data/app/assets/builds/tinymce/skins/ui/alchemy/skin.min.css +1 -0
  19. data/app/assets/builds/tinymce/skins/ui/alchemy/skin.min.css.map +1 -0
  20. data/app/assets/config/alchemy_manifest.js +1 -5
  21. data/app/assets/javascripts/alchemy/alchemy.dialog.js.coffee +4 -0
  22. data/app/assets/stylesheets/alchemy/{_custom-properties.scss → _custom-properties.css} +28 -25
  23. data/app/assets/stylesheets/alchemy/_deprecated_variables.scss +41 -0
  24. data/app/assets/stylesheets/alchemy/_deprecation.scss +17 -0
  25. data/app/assets/stylesheets/alchemy/_extends.scss +1 -1
  26. data/app/assets/stylesheets/alchemy/_mixins.scss +20 -23
  27. data/app/assets/stylesheets/alchemy/_variables.scss +98 -94
  28. data/app/assets/stylesheets/alchemy/{archive.scss → admin/archive.scss} +23 -23
  29. data/app/assets/stylesheets/alchemy/{attachment-select.scss → admin/attachment-select.scss} +2 -2
  30. data/app/assets/stylesheets/alchemy/{attachments.scss → admin/attachments.scss} +4 -4
  31. data/app/assets/stylesheets/alchemy/{base.scss → admin/base.scss} +9 -9
  32. data/app/assets/stylesheets/alchemy/{buttons.scss → admin/buttons.scss} +3 -3
  33. data/app/assets/stylesheets/alchemy/{clipboard.scss → admin/clipboard.scss} +9 -6
  34. data/app/assets/stylesheets/alchemy/{dashboard.scss → admin/dashboard.scss} +8 -8
  35. data/app/assets/stylesheets/alchemy/{dialogs.scss → admin/dialogs.scss} +20 -20
  36. data/app/assets/stylesheets/alchemy/{elements.scss → admin/elements.scss} +129 -89
  37. data/app/assets/stylesheets/alchemy/{errors.scss → admin/errors.scss} +22 -6
  38. data/app/assets/stylesheets/alchemy/{flash.scss → admin/flash.scss} +3 -3
  39. data/app/assets/stylesheets/alchemy/{flatpickr.scss → admin/flatpickr.scss} +55 -35
  40. data/app/assets/stylesheets/alchemy/{form_fields.scss → admin/form_fields.scss} +8 -6
  41. data/app/assets/stylesheets/alchemy/{forms.scss → admin/forms.scss} +20 -16
  42. data/app/assets/stylesheets/alchemy/{frame.scss → admin/frame.scss} +9 -9
  43. data/app/assets/stylesheets/alchemy/{image_library.scss → admin/image_library.scss} +34 -33
  44. data/app/assets/stylesheets/alchemy/admin/labels.scss +3 -0
  45. data/app/assets/stylesheets/alchemy/{list_filter.scss → admin/list_filter.scss} +4 -4
  46. data/app/assets/stylesheets/alchemy/{lists.scss → admin/lists.scss} +9 -7
  47. data/app/assets/stylesheets/alchemy/{navigation.scss → admin/navigation.scss} +17 -17
  48. data/app/assets/stylesheets/alchemy/{node-select.scss → admin/node-select.scss} +5 -5
  49. data/app/assets/stylesheets/alchemy/{nodes.scss → admin/nodes.scss} +11 -11
  50. data/app/assets/stylesheets/alchemy/{notices.scss → admin/notices.scss} +11 -7
  51. data/app/assets/stylesheets/alchemy/{page-select.scss → admin/page-select.scss} +10 -10
  52. data/app/assets/stylesheets/alchemy/{pagination.scss → admin/pagination.scss} +10 -10
  53. data/app/assets/stylesheets/alchemy/{print.scss → admin/print.scss} +2 -6
  54. data/app/assets/stylesheets/alchemy/{resource_info.scss → admin/resource_info.scss} +6 -7
  55. data/app/assets/stylesheets/alchemy/{search.scss → admin/search.scss} +6 -6
  56. data/app/assets/stylesheets/alchemy/{selects.scss → admin/selects.scss} +46 -39
  57. data/app/assets/stylesheets/alchemy/{shoelace.scss → admin/shoelace.scss} +10 -10
  58. data/app/assets/stylesheets/alchemy/{sitemap.scss → admin/sitemap.scss} +18 -19
  59. data/app/assets/stylesheets/alchemy/{tables.scss → admin/tables.scss} +26 -22
  60. data/app/assets/stylesheets/alchemy/admin/tags.scss +158 -0
  61. data/app/assets/stylesheets/alchemy/{toolbar.scss → admin/toolbar.scss} +10 -10
  62. data/app/assets/stylesheets/alchemy/{typography.scss → admin/typography.scss} +3 -3
  63. data/app/assets/stylesheets/alchemy/{upload.scss → admin/upload.scss} +1 -1
  64. data/app/assets/stylesheets/alchemy/admin.scss +40 -45
  65. data/app/assets/stylesheets/alchemy/welcome.scss +57 -0
  66. data/app/assets/stylesheets/tinymce/skins/content/alchemy/{content.min.scss → content.scss} +5 -4
  67. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/{skin.min.scss → skin.scss} +40 -40
  68. data/app/components/alchemy/admin/link_dialog/internal_tab.rb +1 -2
  69. data/app/components/alchemy/admin/resource/action.rb +46 -0
  70. data/app/components/alchemy/admin/resource/cell.rb +34 -0
  71. data/app/components/alchemy/admin/resource/header.rb +46 -0
  72. data/app/components/alchemy/admin/resource/table.rb +153 -0
  73. data/app/components/alchemy/ingredients/datetime_view.rb +2 -2
  74. data/app/components/alchemy/ingredients/link_view.rb +1 -7
  75. data/app/components/alchemy/ingredients/picture_view.rb +2 -5
  76. data/app/components/alchemy/ingredients/text_view.rb +1 -4
  77. data/app/controllers/alchemy/admin/base_controller.rb +4 -27
  78. data/app/controllers/alchemy/admin/elements_controller.rb +7 -3
  79. data/app/controllers/alchemy/admin/languages_controller.rb +1 -1
  80. data/app/controllers/alchemy/admin/legacy_page_urls_controller.rb +1 -1
  81. data/app/controllers/alchemy/admin/pages_controller.rb +6 -2
  82. data/app/controllers/alchemy/admin/pictures_controller.rb +2 -2
  83. data/app/controllers/alchemy/admin/resources_controller.rb +3 -3
  84. data/app/controllers/alchemy/base_controller.rb +2 -0
  85. data/app/controllers/concerns/alchemy/admin/uploader_responses.rb +2 -11
  86. data/app/decorators/alchemy/ingredient_editor.rb +17 -0
  87. data/app/helpers/alchemy/admin/pages_helper.rb +6 -10
  88. data/app/helpers/alchemy/base_helper.rb +2 -2
  89. data/app/helpers/alchemy/elements_block_helper.rb +13 -1
  90. data/app/helpers/alchemy/pages_helper.rb +2 -2
  91. data/app/javascript/alchemy_admin/components/element_editor.js +23 -31
  92. data/app/javascript/alchemy_admin/components/preview_window.js +2 -3
  93. data/app/javascript/alchemy_admin/picture_selector.js +38 -10
  94. data/app/models/alchemy/attachment.rb +0 -8
  95. data/app/models/alchemy/element/dom_id.rb +1 -0
  96. data/app/models/alchemy/element/element_ingredients.rb +0 -73
  97. data/app/models/alchemy/element/presenters.rb +4 -1
  98. data/app/models/alchemy/element.rb +6 -0
  99. data/app/models/alchemy/elements_repository.rb +2 -2
  100. data/app/models/alchemy/ingredient_validator.rb +10 -0
  101. data/app/models/alchemy/page/page_scopes.rb +1 -1
  102. data/app/models/alchemy/picture.rb +0 -10
  103. data/app/models/concerns/alchemy/picture_thumbnails.rb +5 -4
  104. data/app/views/alchemy/admin/attachments/_files_list.html.erb +74 -16
  105. data/app/views/alchemy/admin/clipboard/index.html.erb +38 -33
  106. data/app/views/alchemy/admin/dashboard/_dashboard.html.erb +3 -0
  107. data/app/views/alchemy/admin/dashboard/_left_column.html.erb +4 -0
  108. data/app/views/alchemy/admin/dashboard/_right_column.html.erb +9 -0
  109. data/app/views/alchemy/admin/dashboard/_top.html.erb +12 -0
  110. data/app/views/alchemy/admin/dashboard/index.html.erb +1 -25
  111. data/app/views/alchemy/admin/elements/_element.html.erb +1 -2
  112. data/app/views/alchemy/admin/elements/_form.html.erb +1 -1
  113. data/app/views/alchemy/admin/ingredients/_picture_fields.html.erb +10 -3
  114. data/app/views/alchemy/admin/ingredients/update.turbo_stream.erb +7 -0
  115. data/app/views/alchemy/admin/languages/_table.html.erb +16 -42
  116. data/app/views/alchemy/admin/nodes/_form.html.erb +1 -1
  117. data/app/views/alchemy/admin/pages/_table.html.erb +92 -27
  118. data/app/views/alchemy/admin/pages/edit.html.erb +6 -8
  119. data/app/views/alchemy/admin/pages/index.html.erb +0 -4
  120. data/app/views/alchemy/admin/pictures/_form.html.erb +14 -12
  121. data/app/views/alchemy/admin/pictures/index.html.erb +1 -11
  122. data/app/views/alchemy/admin/pictures/update.turbo_stream.erb +6 -0
  123. data/app/views/alchemy/admin/resources/_resource_table.html.erb +3 -0
  124. data/app/views/alchemy/admin/resources/_table.html.erb +2 -0
  125. data/app/views/alchemy/admin/resources/index.html.erb +1 -1
  126. data/app/views/alchemy/admin/sites/index.html.erb +1 -1
  127. data/app/views/alchemy/admin/styleguide/index.html.erb +0 -4
  128. data/app/views/alchemy/admin/tags/index.html.erb +15 -14
  129. data/app/views/alchemy/base/403.html.erb +6 -0
  130. data/app/views/alchemy/base/500.html.erb +14 -12
  131. data/app/views/alchemy/ingredients/_datetime_editor.html.erb +13 -11
  132. data/app/views/alchemy/ingredients/_headline_editor.html.erb +29 -22
  133. data/app/views/alchemy/ingredients/_link_editor.html.erb +17 -11
  134. data/app/views/alchemy/ingredients/_page_editor.html.erb +1 -0
  135. data/app/views/alchemy/ingredients/_picture_editor.html.erb +3 -4
  136. data/app/views/alchemy/ingredients/_richtext_editor.html.erb +5 -1
  137. data/app/views/alchemy/ingredients/_select_editor.html.erb +2 -1
  138. data/app/views/alchemy/ingredients/_text_editor.html.erb +20 -14
  139. data/app/views/alchemy/ingredients/shared/_picture_css_class.html.erb +6 -0
  140. data/app/views/layouts/alchemy/admin.html.erb +4 -2
  141. data/bin/setup +2 -0
  142. data/bin/start +1 -1
  143. data/bun.lockb +0 -0
  144. data/config/alchemy/config.yml +9 -0
  145. data/config/locales/alchemy.en.yml +8 -29
  146. data/config/routes.rb +22 -22
  147. data/lib/alchemy/config.rb +3 -3
  148. data/lib/alchemy/install/tasks.rb +5 -2
  149. data/lib/alchemy/resource.rb +4 -14
  150. data/lib/alchemy/resources_helper.rb +3 -1
  151. data/lib/alchemy/test_support/capybara_helpers.rb +8 -5
  152. data/lib/alchemy/test_support/shared_uploader_examples.rb +0 -1
  153. data/lib/alchemy/upgrader/seven_point_three.rb +52 -0
  154. data/lib/alchemy/version.rb +1 -1
  155. data/lib/alchemy_cms.rb +1 -1
  156. data/lib/generators/alchemy/install/files/article.css +25 -0
  157. data/lib/generators/alchemy/install/files/custom.css +4 -0
  158. data/lib/generators/alchemy/install/install_generator.rb +6 -6
  159. data/lib/tasks/alchemy/upgrade.rake +29 -1
  160. data/vendor/assets/stylesheets/alchemy_admin/select2.css +1 -0
  161. data/vendor/assets/stylesheets/jquery.Jcrop.min.css +2 -0
  162. data/vendor/javascript/shoelace.min.js +62 -63
  163. data/vendor/javascript/tinymce.min.js +1 -1
  164. metadata +135 -107
  165. data/app/assets/images/alchemy/lupe.cur +0 -0
  166. data/app/assets/stylesheets/alchemy/labels.scss +0 -3
  167. data/app/assets/stylesheets/alchemy/tags.scss +0 -155
  168. data/app/assets/stylesheets/alchemy/welcome.sass +0 -49
  169. data/app/components/concerns/alchemy/ingredients/link_target.rb +0 -18
  170. data/app/views/alchemy/admin/attachments/_attachment.html.erb +0 -81
  171. data/app/views/alchemy/admin/languages/_language.html.erb +0 -50
  172. data/app/views/alchemy/admin/pages/_table_row.html.erb +0 -111
  173. data/app/views/alchemy/admin/pages/list/_table.html.erb +0 -31
  174. data/app/views/alchemy/admin/pictures/update.js.erb +0 -6
  175. data/app/views/alchemy/admin/tags/_tag.html.erb +0 -32
  176. data/app/views/alchemy/base/update.js.erb +0 -5
  177. data/lib/generators/alchemy/install/files/all.css +0 -11
  178. data/lib/generators/alchemy/install/files/article.scss +0 -30
  179. data/package.json +0 -52
  180. data/vendor/assets/stylesheets/alchemy_admin/select2.scss +0 -741
  181. data/vendor/assets/stylesheets/jquery.Jcrop.min.scss +0 -2
  182. /data/app/assets/stylesheets/alchemy/{fonts.scss → _fonts.scss} +0 -0
  183. /data/app/assets/stylesheets/alchemy/{hints.scss → admin/hints.scss} +0 -0
  184. /data/app/assets/stylesheets/alchemy/{icons.scss → admin/icons.scss} +0 -0
  185. /data/app/assets/stylesheets/alchemy/{images.scss → admin/images.scss} +0 -0
  186. /data/app/assets/stylesheets/alchemy/{preview_window.scss → admin/preview_window.scss} +0 -0
  187. /data/app/assets/stylesheets/alchemy/{spinner.scss → admin/spinner.scss} +0 -0
  188. /data/app/views/alchemy/admin/dashboard/{_locked_pages.html.erb → widgets/_locked_pages.html.erb} +0 -0
  189. /data/app/views/alchemy/admin/dashboard/{_recent_pages.html.erb → widgets/_recent_pages.html.erb} +0 -0
  190. /data/app/views/alchemy/admin/dashboard/{_sites.html.erb → widgets/_sites.html.erb} +0 -0
  191. /data/app/views/alchemy/admin/dashboard/{_users.html.erb → widgets/_users.html.erb} +0 -0
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ module Admin
5
+ module Resource
6
+ # Renders a table cell with the given css classes
7
+ #
8
+ # @param [String, nil] :css_classes
9
+ # css classes that are show at the table cell
10
+ # @param [Lambda] :block
11
+ # a block to include a button or a link
12
+ #
13
+ class Cell < ViewComponent::Base
14
+ attr_reader :block, :css_classes
15
+
16
+ erb_template <<~ERB
17
+ <td class="<%= css_classes %>">
18
+ <%= view_context.capture(@resource, &block) %>
19
+ </td>
20
+ ERB
21
+
22
+ def initialize(css_classes, &block)
23
+ @css_classes = css_classes
24
+ @block = block
25
+ end
26
+
27
+ def with_resource(resource)
28
+ @resource = resource
29
+ self
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ module Admin
5
+ module Resource
6
+ # Renders a table header tag
7
+ # the component is an internal component of the Table component
8
+ #
9
+ # @param [String] :name
10
+ # name of the sortable link or the text if not additional text is given
11
+ # @param [String] :query
12
+ # Ransack query
13
+ # @param [String] :css_classes ("")
14
+ # css class of the th - tag
15
+ # @param [String, nil] :text (nil)
16
+ # optional text of the header
17
+ # @param [Symbol] :type (:string)
18
+ # type of the column will be used to inverse the sorting order for data/time - objects
19
+ # @param [Boolean] :sortable (false)
20
+ # enable a sortable link
21
+ #
22
+ class Header < ViewComponent::Base
23
+ delegate :sort_link, to: :helpers
24
+
25
+ erb_template <<~ERB
26
+ <th class="<%= @css_classes %>">
27
+ <% if @sortable %>
28
+ <%= sort_link @query, @name, @text, default_order: @default_order %>
29
+ <% else %>
30
+ <%= @text %>
31
+ <% end %>
32
+ </th>
33
+ ERB
34
+
35
+ def initialize(name, query, css_classes: "", text: nil, type: :string, sortable: false)
36
+ @name = name
37
+ @query = query
38
+ @text = text || name
39
+ @css_classes = css_classes
40
+ @default_order = /date|time/.match?(type.to_s) ? "desc" : "asc"
41
+ @sortable = sortable
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,153 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ module Admin
5
+ module Resource
6
+ # Renders a resource table with columns and buttons
7
+ #
8
+ # == Example
9
+ #
10
+ # <%= render Alchemy::Admin::Resource::Table.new(@languages, query: @query) do |table| %>
11
+ # <% table.icon_column "translate-2", style: false %>
12
+ # <% table.column :name, sortable: true %>
13
+ # <% table.column :language_code, sortable: true %>
14
+ # <% table.column :page_layout do |language| %>
15
+ # <%= Alchemy::Page.human_layout_name(language.page_layout) %>
16
+ # <% end %>
17
+ # <% table.delete_button %>
18
+ # <% table.edit_button %>
19
+ # <% end %>
20
+ #
21
+ # @param [ActiveRecord::Relation] :collection
22
+ # a collection of Alchemy::Resource objects that are shown in the table
23
+ # @param [Ransack::Search] :query
24
+ # The ransack search object to allow sortable table columns
25
+ # @param [String] :nothing_found_label (Alchemy.t("Nothing found"))
26
+ # The message that will be shown, if the collection is empty
27
+ # @param [Hash] :search_filter_params ({})
28
+ # An additional hash that will attached to the delete and edit button to redirect back to
29
+ # the same page of the table
30
+ # @param [String] :icon (nil)
31
+ # a default icon, if the table is auto generated
32
+ class Table < ViewComponent::Base
33
+ delegate :render_attribute,
34
+ :resource_path,
35
+ :render_icon,
36
+ :edit_resource_path,
37
+ :resource_handler,
38
+ :resource_window_size,
39
+ to: :helpers
40
+
41
+ attr_reader :collection,
42
+ :nothing_found_label,
43
+ :search_filter_params
44
+
45
+ renders_many :headers, Header
46
+
47
+ renders_many :cells, ->(css_classes, &block) do
48
+ Cell.new(css_classes, &block)
49
+ end
50
+
51
+ renders_many :actions, ->(name, tooltip = nil, &block) do
52
+ Action.new(name, tooltip, &block)
53
+ end
54
+
55
+ erb_template <<~ERB
56
+ <% if collection.any? %>
57
+ <table class="list">
58
+ <thead>
59
+ <tr>
60
+ <% headers.each do |header| %>
61
+ <%= header %>
62
+ <% end %>
63
+ <% if actions? %>
64
+ <th class="tools"></th>
65
+ <% end %>
66
+ </tr>
67
+ </thead>
68
+ <tbody>
69
+ <% collection.each do |resource| %>
70
+ <tr class="<%= cycle('even', 'odd') %>">
71
+ <% cells.each do |cell| %>
72
+ <%= render cell.with_resource(resource) %>
73
+ <% end %>
74
+ <% if actions? %>
75
+ <td class="tools">
76
+ <% actions.each do |action| %>
77
+ <%= render action.with_resource(resource) %>
78
+ <% end %>
79
+ </td>
80
+ <% end %>
81
+ </tr>
82
+ <% end %>
83
+ </tbody>
84
+ </table>
85
+ <% else %>
86
+ <alchemy-message type="info">
87
+ <%= nothing_found_label %>
88
+ </alchemy-message>
89
+ <% end %>
90
+ ERB
91
+
92
+ def initialize(collection, query: nil, nothing_found_label: Alchemy.t("Nothing found"), search_filter_params: {}, icon: nil)
93
+ @collection = collection
94
+ @query = query
95
+ @nothing_found_label = nothing_found_label
96
+ @search_filter_params = search_filter_params
97
+ @icon = icon
98
+ end
99
+
100
+ def column(name, header: nil, sortable: false, type: nil, class_name: nil, &block)
101
+ header ||= resource_handler.model.human_attribute_name(name)
102
+ type ||= resource_handler.model.columns_hash[name.to_s]&.type
103
+ attribute = resource_handler.attributes.find { |item| item[:name] == name.to_s } || {name: name, type: type}
104
+ block ||= lambda { |item| render_attribute(item, attribute) }
105
+
106
+ css_classes = [name, type, class_name].compact.join(" ")
107
+ with_header(name, @query, css_classes: css_classes, text: header, type: type, sortable: sortable)
108
+ with_cell(css_classes, &block)
109
+ end
110
+
111
+ def icon_column(icon = nil, style: nil)
112
+ column(:icon, header: "") do |resource|
113
+ render_icon(icon || yield(resource), size: "xl", style: style)
114
+ end
115
+ end
116
+
117
+ def delete_button(tooltip: Alchemy.t("Delete"), confirm_message: Alchemy.t("Are you sure?"))
118
+ with_action(:destroy, tooltip) do |row|
119
+ helpers.delete_button(resource_path(row, search_filter_params), {message: confirm_message})
120
+ end
121
+ end
122
+
123
+ def edit_button(tooltip: Alchemy.t("Edit"), dialog_title: tooltip, dialog_size: resource_window_size)
124
+ with_action(:edit, tooltip) do |row|
125
+ helpers.link_to_dialog render_icon(:edit),
126
+ edit_resource_path(row, search_filter_params),
127
+ {
128
+ size: dialog_size,
129
+ title: dialog_title
130
+ },
131
+ class: "icon_button"
132
+ end
133
+ end
134
+
135
+ private
136
+
137
+ ##
138
+ # if no cells are available the resource_helper will be used, to generate the
139
+ # default attributes of the given resource
140
+ def before_render
141
+ unless cells?
142
+ icon_column(@icon) if @icon.present?
143
+ resource_handler.sorted_attributes.each do |attribute|
144
+ column(attribute[:name], sortable: true)
145
+ end
146
+ delete_button
147
+ edit_button
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
@@ -4,8 +4,8 @@ module Alchemy
4
4
  attr_reader :date_format
5
5
 
6
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: {})
7
+ # @param date_format [String] The date format to use. Use either a strftime format string, a I18n format symbol or "rfc822". Defaults to "time.formats.alchemy.default".
8
+ def initialize(ingredient, date_format: :"alchemy.default", html_options: {})
9
9
  super(ingredient)
10
10
  @date_format = settings_value(:date_format, value: date_format)
11
11
  end
@@ -1,8 +1,6 @@
1
1
  module Alchemy
2
2
  module Ingredients
3
3
  class LinkView < BaseView
4
- include LinkTarget
5
-
6
4
  attr_reader :link_text
7
5
 
8
6
  # @param ingredient [Alchemy::Ingredient]
@@ -14,11 +12,7 @@ module Alchemy
14
12
  end
15
13
 
16
14
  def call
17
- target = ingredient.link_target.presence
18
- link_to(link_text, value, {
19
- target: link_target_value(target),
20
- rel: link_rel_value(target)
21
- }.merge(html_options)).html_safe
15
+ link_to(link_text, value, {target: ingredient.link_target.presence}.merge(html_options)).html_safe
22
16
  end
23
17
  end
24
18
  end
@@ -4,8 +4,6 @@ module Alchemy
4
4
  module Ingredients
5
5
  # Renders a picture ingredient view
6
6
  class PictureView < BaseView
7
- include LinkTarget
8
-
9
7
  attr_reader :ingredient,
10
8
  :show_caption,
11
9
  :disable_link,
@@ -48,11 +46,10 @@ module Alchemy
48
46
  output = caption ? img_tag + caption : img_tag
49
47
 
50
48
  if is_linked?
51
- target = ingredient.link_target.presence
52
49
  output = link_to(output, url_for(ingredient.link), {
53
50
  title: ingredient.link_title.presence,
54
- rel: link_rel_value(target),
55
- target: link_target_value(target)
51
+ target: (ingredient.link_target == "blank") ? "_blank" : nil,
52
+ data: {link_target: ingredient.link_target.presence}
56
53
  })
57
54
  end
58
55
 
@@ -1,8 +1,6 @@
1
1
  module Alchemy
2
2
  module Ingredients
3
3
  class TextView < BaseView
4
- include LinkTarget
5
-
6
4
  attr_reader :disable_link
7
5
 
8
6
  delegate :dom_id, :link, :link_title, :link_target,
@@ -23,8 +21,7 @@ module Alchemy
23
21
  link_to(value, url_for(link), {
24
22
  id: dom_id.presence,
25
23
  title: link_title,
26
- target: link_target_value(link_target),
27
- rel: link_rel_value(link_target)
24
+ target: link_target
28
25
  }.merge(html_options))
29
26
  end.html_safe
30
27
  end
@@ -31,27 +31,6 @@ module Alchemy
31
31
 
32
32
  private
33
33
 
34
- def safe_redirect_path(path = params[:redirect_to], fallback: admin_path)
35
- if is_safe_redirect_path?(path)
36
- path
37
- elsif is_safe_redirect_path?(fallback)
38
- fallback
39
- else
40
- admin_path
41
- end
42
- end
43
-
44
- def is_safe_redirect_path?(path)
45
- mount_path = alchemy.root_path
46
- path.to_s.match? %r{^#{mount_path}admin/}
47
- end
48
-
49
- def relative_referer_path(referer = request.referer)
50
- return unless referer
51
-
52
- URI(referer).path
53
- end
54
-
55
34
  # Disable layout rendering for xhr requests.
56
35
  def set_layout
57
36
  (request.xhr? || turbo_frame_request?) ? false : "alchemy/admin"
@@ -121,22 +100,20 @@ module Alchemy
121
100
  flash[:notice] = Alchemy.t(flash_notice)
122
101
  do_redirect_to redirect_url
123
102
  else
124
- render action: ((params[:action] == "update") ? "edit" : "new")
103
+ render action: ((params[:action] == "update") ? "edit" : "new"),
104
+ status: :unprocessable_entity
125
105
  end
126
106
  end
127
107
 
128
108
  # Does redirects for html and js requests
129
109
  #
130
- # Makes sure that the redirect path is safe.
131
- #
132
110
  def do_redirect_to(url_or_path)
133
- redirect_path = safe_redirect_path(url_or_path)
134
111
  respond_to do |format|
135
112
  format.js {
136
- @redirect_url = redirect_path
113
+ @redirect_url = url_or_path
137
114
  render :redirect
138
115
  }
139
- format.html { redirect_to redirect_path }
116
+ format.html { redirect_to url_or_path }
140
117
  end
141
118
  end
142
119
 
@@ -69,9 +69,13 @@ module Alchemy
69
69
  render json: {
70
70
  warning: @warning,
71
71
  errorMessage: Alchemy.t(:ingredient_validations_headline),
72
- ingredientsWithErrors: @element.ingredients_with_errors.map(&:id),
73
- errors: @element.ingredient_error_messages
74
- }
72
+ ingredientsWithErrors: @element.ingredients_with_errors.map do |ingredient|
73
+ {
74
+ id: ingredient.id,
75
+ errorMessage: ingredient.errors.messages[:value].to_sentence
76
+ }
77
+ end
78
+ }, status: :unprocessable_entity
75
79
  end
76
80
  end
77
81
 
@@ -40,7 +40,7 @@ module Alchemy
40
40
  def switch
41
41
  @language = set_alchemy_language(params[:language_id])
42
42
  session[:alchemy_language_id] = @language.id
43
- do_redirect_to relative_referer_path || alchemy.admin_dashboard_path
43
+ do_redirect_to request.referer || alchemy.admin_dashboard_path
44
44
  end
45
45
 
46
46
  private
@@ -22,7 +22,7 @@ module Alchemy
22
22
  @message = message_for_resource_action
23
23
  render :update
24
24
  else
25
- render :edit
25
+ render :edit, status: :unprocessable_entity
26
26
  end
27
27
  end
28
28
 
@@ -135,7 +135,7 @@ module Alchemy
135
135
  @tree = serialized_page_tree
136
136
  end
137
137
  else
138
- render :configure
138
+ render :configure, status: :unprocessable_entity
139
139
  end
140
140
  end
141
141
 
@@ -189,7 +189,11 @@ module Alchemy
189
189
  end
190
190
 
191
191
  def unlock_redirect_path
192
- safe_redirect_path(fallback: admin_pages_path)
192
+ if params[:redirect_to].to_s.match?(/\A\/admin\/(layout_)?pages/)
193
+ params[:redirect_to]
194
+ else
195
+ admin_pages_path
196
+ end
193
197
  end
194
198
 
195
199
  # Sets the page public and updates the published_at attribute that is used as cache_key
@@ -12,7 +12,7 @@ module Alchemy
12
12
  before_action :load_resource,
13
13
  only: [:show, :edit, :update, :url, :destroy]
14
14
 
15
- before_action :set_size, only: [:index, :show, :edit_multiple]
15
+ before_action :set_size, only: [:index, :show, :edit_multiple, :update]
16
16
 
17
17
  authorize_resource class: Alchemy::Picture
18
18
 
@@ -79,7 +79,7 @@ module Alchemy
79
79
  type: "error"
80
80
  }
81
81
  end
82
- render :update
82
+ render :update, status: (@message[:type] == "notice") ? :ok : :unprocessable_entity
83
83
  end
84
84
 
85
85
  def update_multiple
@@ -78,7 +78,7 @@ module Alchemy
78
78
  flash[:error] = resource_instance_variable.errors.full_messages.join(", ")
79
79
  end
80
80
  flash_notice_for_resource_action
81
- do_redirect_to resource_url_proxy.url_for(search_filter_params.merge(action: "index", only_path: true))
81
+ do_redirect_to resource_url_proxy.url_for(search_filter_params.merge(action: "index"))
82
82
  end
83
83
 
84
84
  def resource_handler
@@ -138,7 +138,7 @@ module Alchemy
138
138
  end
139
139
 
140
140
  def eligible_resource_filter_values
141
- resource_filters.map(&:values).flatten!.map!(&:to_s)
141
+ resource_filters.map(&:values).flatten
142
142
  end
143
143
 
144
144
  # Returns a translated +flash[:notice]+ for current controller action.
@@ -170,7 +170,7 @@ module Alchemy
170
170
  end
171
171
 
172
172
  def alchemy_module
173
- @alchemy_module ||= module_definition_for(controller: params[:controller], action: "index")
173
+ @alchemy_module ||= module_definition_for(controller: controller_path, action: "index")
174
174
  end
175
175
 
176
176
  def load_resource
@@ -82,6 +82,8 @@ module Alchemy
82
82
  locals: {message: flash[:warning], flash_type: "warning"}
83
83
  end
84
84
  end
85
+ elsif turbo_frame_request?
86
+ render "403", status: 403
85
87
  else
86
88
  redirect_to(alchemy.admin_dashboard_path)
87
89
  end
@@ -11,7 +11,7 @@ module Alchemy
11
11
  name: file.name)
12
12
 
13
13
  {
14
- json: uploader_response(file: file, message: message),
14
+ json: {message: message},
15
15
  status: status
16
16
  }
17
17
  end
@@ -23,19 +23,10 @@ module Alchemy
23
23
  name: file.name)
24
24
 
25
25
  {
26
- json: uploader_response(file: file, message: message),
26
+ json: {message: message},
27
27
  status: :unprocessable_entity
28
28
  }
29
29
  end
30
-
31
- private
32
-
33
- def uploader_response(file:, message:)
34
- {
35
- files: [file.to_jq_upload],
36
- message: message
37
- }
38
- end
39
30
  end
40
31
  end
41
32
  end
@@ -153,6 +153,23 @@ module Alchemy
153
153
  end
154
154
  end
155
155
 
156
+ def validations
157
+ definition.fetch(:validate, [])
158
+ end
159
+
160
+ def format_validation
161
+ validations.select { _1.is_a?(Hash) }.find { _1[:format] }&.fetch(:format)
162
+ end
163
+
164
+ def length_validation
165
+ validations.select { _1.is_a?(Hash) }.find { _1[:length] }&.fetch(:length)
166
+ end
167
+
168
+ def presence_validation?
169
+ validations.include?("presence") ||
170
+ validations.any? { _1.is_a?(Hash) && _1[:presence] == true }
171
+ end
172
+
156
173
  private
157
174
 
158
175
  def form_field_counter
@@ -5,18 +5,14 @@ module Alchemy
5
5
  module PagesHelper
6
6
  include Alchemy::Admin::BaseHelper
7
7
 
8
- # Returns options tags for the screen sizes select in page edit view.
8
+ # Returns screen sizes for the preview size select in page edit view.
9
+ #
10
+ # You can configure the screen sizes in your +config/alchemy/config.yml+.
9
11
  #
10
12
  def preview_sizes_for_select
11
- options_for_select([
12
- "auto",
13
- [Alchemy.t("240", scope: "preview_sizes"), 240],
14
- [Alchemy.t("320", scope: "preview_sizes"), 320],
15
- [Alchemy.t("480", scope: "preview_sizes"), 480],
16
- [Alchemy.t("768", scope: "preview_sizes"), 768],
17
- [Alchemy.t("1024", scope: "preview_sizes"), 1024],
18
- [Alchemy.t("1280", scope: "preview_sizes"), 1280]
19
- ])
13
+ Alchemy::Config.get(:page_preview_sizes).map do |size|
14
+ [Alchemy.t(size, scope: "preview_sizes"), size]
15
+ end
20
16
  end
21
17
 
22
18
  # Renders a label for page's page layout
@@ -42,8 +42,8 @@ module Alchemy
42
42
  # <p>Caution! This is a warning!</p>
43
43
  # <% end %>
44
44
  #
45
- def render_message(type = :info, msg = nil, &blk)
46
- render Alchemy::Admin::Message.new(msg || capture(&blk), type: type)
45
+ def render_message(type = :info, msg = nil, &)
46
+ render Alchemy::Admin::Message.new(msg || capture(&), type: type)
47
47
  end
48
48
 
49
49
  # Checks if the given argument is a String or a Page object.
@@ -100,9 +100,21 @@ module Alchemy
100
100
  # A lambda used for formatting the element's tags (see Alchemy::ElementsHelper::element_tags_attributes). Set to +false+ to not include tags in the wrapper element.
101
101
  #
102
102
  def element_view_for(element, options = {})
103
+ if options[:id].nil?
104
+ Alchemy::Deprecation.warn <<~WARN
105
+ Relying on an implicit DOM id in `element_view_for` is deprecated. Please provide an explicit `id` if you actually want to render an `id` attribute on the #{element.name} element wrapper tag.
106
+ WARN
107
+ end
108
+
109
+ if options[:class].nil?
110
+ Alchemy::Deprecation.warn <<~WARN
111
+ Relying on an implicit CSS class in `element_view_for` is deprecated. Please provide an explicit `class` for the #{element.name} element wrapper tag.
112
+ WARN
113
+ end
114
+
103
115
  options = {
104
116
  tag: :div,
105
- id: element.dom_id,
117
+ id: (!!options[:id]) ? options[:id] : element.dom_id,
106
118
  class: element.name,
107
119
  tags_formatter: ->(tags) { tags.join(" ") }
108
120
  }.merge(options)
@@ -62,8 +62,8 @@ module Alchemy
62
62
  #
63
63
  # renders +app/views/alchemy/site_layouts/_default_site.html.erb+ for the site named "Default Site".
64
64
  #
65
- def render_site_layout(&block)
66
- render current_alchemy_site, &block
65
+ def render_site_layout(&)
66
+ render(current_alchemy_site, &)
67
67
  rescue ActionView::MissingTemplate => error
68
68
  error_or_warning(error, "Site layout for #{current_alchemy_site.try(:name)} not found. Please run `rails g alchemy:site_layouts`")
69
69
  end