alchemy_cms 7.2.7 → 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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +97 -14
- data/Gemfile +4 -3
- data/Rakefile +1 -0
- data/alchemy_cms.gemspec +7 -7
- data/app/assets/builds/alchemy/admin/print.css +1 -0
- data/app/assets/builds/alchemy/admin/print.css.map +1 -0
- data/app/assets/builds/alchemy/admin.css +1 -0
- data/app/assets/builds/alchemy/admin.css.map +1 -0
- data/app/assets/builds/alchemy/welcome.css +1 -0
- data/app/assets/builds/alchemy/welcome.css.map +1 -0
- data/app/assets/builds/tinymce/skins/content/alchemy/content.css +1 -0
- data/app/assets/builds/tinymce/skins/content/alchemy/content.css.map +1 -0
- data/app/assets/builds/tinymce/skins/content/alchemy/content.min.css +1 -0
- data/app/assets/builds/tinymce/skins/content/alchemy/content.min.css.map +1 -0
- data/app/assets/builds/tinymce/skins/ui/alchemy/skin.css +1 -0
- data/app/assets/builds/tinymce/skins/ui/alchemy/skin.css.map +1 -0
- data/app/assets/builds/tinymce/skins/ui/alchemy/skin.min.css +1 -0
- data/app/assets/builds/tinymce/skins/ui/alchemy/skin.min.css.map +1 -0
- data/app/assets/config/alchemy_manifest.js +1 -5
- data/app/assets/javascripts/alchemy/alchemy.dialog.js.coffee +4 -0
- data/app/assets/stylesheets/alchemy/{_custom-properties.scss → _custom-properties.css} +28 -25
- data/app/assets/stylesheets/alchemy/_deprecated_variables.scss +41 -0
- data/app/assets/stylesheets/alchemy/_deprecation.scss +17 -0
- data/app/assets/stylesheets/alchemy/_extends.scss +1 -1
- data/app/assets/stylesheets/alchemy/_mixins.scss +20 -23
- data/app/assets/stylesheets/alchemy/_variables.scss +98 -94
- data/app/assets/stylesheets/alchemy/{archive.scss → admin/archive.scss} +23 -23
- data/app/assets/stylesheets/alchemy/{attachment-select.scss → admin/attachment-select.scss} +2 -2
- data/app/assets/stylesheets/alchemy/{attachments.scss → admin/attachments.scss} +4 -4
- data/app/assets/stylesheets/alchemy/{base.scss → admin/base.scss} +9 -9
- data/app/assets/stylesheets/alchemy/{buttons.scss → admin/buttons.scss} +3 -3
- data/app/assets/stylesheets/alchemy/{clipboard.scss → admin/clipboard.scss} +9 -6
- data/app/assets/stylesheets/alchemy/{dashboard.scss → admin/dashboard.scss} +8 -8
- data/app/assets/stylesheets/alchemy/{dialogs.scss → admin/dialogs.scss} +20 -20
- data/app/assets/stylesheets/alchemy/{elements.scss → admin/elements.scss} +128 -88
- data/app/assets/stylesheets/alchemy/{errors.scss → admin/errors.scss} +22 -6
- data/app/assets/stylesheets/alchemy/{flash.scss → admin/flash.scss} +3 -3
- data/app/assets/stylesheets/alchemy/{flatpickr.scss → admin/flatpickr.scss} +55 -35
- data/app/assets/stylesheets/alchemy/{form_fields.scss → admin/form_fields.scss} +8 -6
- data/app/assets/stylesheets/alchemy/{forms.scss → admin/forms.scss} +20 -16
- data/app/assets/stylesheets/alchemy/{frame.scss → admin/frame.scss} +9 -9
- data/app/assets/stylesheets/alchemy/{image_library.scss → admin/image_library.scss} +34 -33
- data/app/assets/stylesheets/alchemy/admin/labels.scss +3 -0
- data/app/assets/stylesheets/alchemy/{list_filter.scss → admin/list_filter.scss} +4 -4
- data/app/assets/stylesheets/alchemy/{lists.scss → admin/lists.scss} +9 -7
- data/app/assets/stylesheets/alchemy/{navigation.scss → admin/navigation.scss} +17 -17
- data/app/assets/stylesheets/alchemy/{node-select.scss → admin/node-select.scss} +5 -5
- data/app/assets/stylesheets/alchemy/{nodes.scss → admin/nodes.scss} +11 -11
- data/app/assets/stylesheets/alchemy/{notices.scss → admin/notices.scss} +11 -7
- data/app/assets/stylesheets/alchemy/{page-select.scss → admin/page-select.scss} +10 -10
- data/app/assets/stylesheets/alchemy/{pagination.scss → admin/pagination.scss} +10 -10
- data/app/assets/stylesheets/alchemy/{print.scss → admin/print.scss} +2 -6
- data/app/assets/stylesheets/alchemy/{resource_info.scss → admin/resource_info.scss} +6 -7
- data/app/assets/stylesheets/alchemy/{search.scss → admin/search.scss} +6 -6
- data/app/assets/stylesheets/alchemy/{selects.scss → admin/selects.scss} +46 -39
- data/app/assets/stylesheets/alchemy/{shoelace.scss → admin/shoelace.scss} +10 -10
- data/app/assets/stylesheets/alchemy/{sitemap.scss → admin/sitemap.scss} +18 -19
- data/app/assets/stylesheets/alchemy/{tables.scss → admin/tables.scss} +26 -22
- data/app/assets/stylesheets/alchemy/admin/tags.scss +158 -0
- data/app/assets/stylesheets/alchemy/{toolbar.scss → admin/toolbar.scss} +10 -10
- data/app/assets/stylesheets/alchemy/{typography.scss → admin/typography.scss} +3 -3
- data/app/assets/stylesheets/alchemy/{upload.scss → admin/upload.scss} +1 -1
- data/app/assets/stylesheets/alchemy/admin.scss +40 -45
- data/app/assets/stylesheets/alchemy/welcome.scss +57 -0
- data/app/assets/stylesheets/tinymce/skins/content/alchemy/{content.min.scss → content.scss} +5 -4
- data/app/assets/stylesheets/tinymce/skins/ui/alchemy/{skin.min.scss → skin.scss} +40 -40
- data/app/components/alchemy/admin/resource/action.rb +46 -0
- data/app/components/alchemy/admin/resource/cell.rb +34 -0
- data/app/components/alchemy/admin/resource/header.rb +46 -0
- data/app/components/alchemy/admin/resource/table.rb +153 -0
- data/app/components/alchemy/ingredients/datetime_view.rb +2 -2
- data/app/controllers/alchemy/admin/base_controller.rb +2 -1
- data/app/controllers/alchemy/admin/elements_controller.rb +7 -3
- data/app/controllers/alchemy/admin/legacy_page_urls_controller.rb +1 -1
- data/app/controllers/alchemy/admin/pages_controller.rb +1 -1
- data/app/controllers/alchemy/admin/pictures_controller.rb +2 -2
- data/app/controllers/alchemy/admin/resources_controller.rb +2 -2
- data/app/controllers/alchemy/base_controller.rb +2 -0
- data/app/controllers/concerns/alchemy/admin/uploader_responses.rb +2 -11
- data/app/decorators/alchemy/ingredient_editor.rb +17 -0
- data/app/helpers/alchemy/admin/pages_helper.rb +6 -10
- data/app/helpers/alchemy/base_helper.rb +2 -2
- data/app/helpers/alchemy/elements_block_helper.rb +13 -1
- data/app/helpers/alchemy/pages_helper.rb +2 -2
- data/app/javascript/alchemy_admin/components/element_editor.js +23 -31
- data/app/javascript/alchemy_admin/components/preview_window.js +2 -3
- data/app/javascript/alchemy_admin/picture_selector.js +38 -10
- data/app/models/alchemy/attachment.rb +0 -8
- data/app/models/alchemy/element/dom_id.rb +1 -0
- data/app/models/alchemy/element/element_ingredients.rb +0 -73
- data/app/models/alchemy/element/presenters.rb +4 -1
- data/app/models/alchemy/element.rb +6 -0
- data/app/models/alchemy/elements_repository.rb +2 -2
- data/app/models/alchemy/ingredient_validator.rb +10 -0
- data/app/models/alchemy/page/page_scopes.rb +1 -1
- data/app/models/alchemy/picture.rb +0 -10
- data/app/views/alchemy/admin/attachments/_files_list.html.erb +74 -16
- data/app/views/alchemy/admin/clipboard/index.html.erb +38 -33
- data/app/views/alchemy/admin/dashboard/_dashboard.html.erb +3 -0
- data/app/views/alchemy/admin/dashboard/_left_column.html.erb +4 -0
- data/app/views/alchemy/admin/dashboard/_right_column.html.erb +9 -0
- data/app/views/alchemy/admin/dashboard/_top.html.erb +12 -0
- data/app/views/alchemy/admin/dashboard/index.html.erb +1 -25
- data/app/views/alchemy/admin/elements/_element.html.erb +1 -2
- data/app/views/alchemy/admin/elements/_form.html.erb +1 -1
- data/app/views/alchemy/admin/ingredients/_picture_fields.html.erb +10 -3
- data/app/views/alchemy/admin/ingredients/update.turbo_stream.erb +7 -0
- data/app/views/alchemy/admin/languages/_table.html.erb +16 -42
- data/app/views/alchemy/admin/nodes/_form.html.erb +1 -1
- data/app/views/alchemy/admin/pages/_table.html.erb +92 -27
- data/app/views/alchemy/admin/pages/edit.html.erb +6 -8
- data/app/views/alchemy/admin/pages/index.html.erb +0 -4
- data/app/views/alchemy/admin/pictures/_form.html.erb +14 -12
- data/app/views/alchemy/admin/pictures/index.html.erb +1 -11
- data/app/views/alchemy/admin/pictures/update.turbo_stream.erb +6 -0
- data/app/views/alchemy/admin/resources/_resource_table.html.erb +3 -0
- data/app/views/alchemy/admin/resources/_table.html.erb +2 -0
- data/app/views/alchemy/admin/resources/index.html.erb +1 -1
- data/app/views/alchemy/admin/sites/index.html.erb +1 -1
- data/app/views/alchemy/admin/styleguide/index.html.erb +0 -4
- data/app/views/alchemy/admin/tags/index.html.erb +15 -14
- data/app/views/alchemy/base/403.html.erb +6 -0
- data/app/views/alchemy/base/500.html.erb +14 -12
- data/app/views/alchemy/ingredients/_datetime_editor.html.erb +13 -11
- data/app/views/alchemy/ingredients/_headline_editor.html.erb +29 -22
- data/app/views/alchemy/ingredients/_link_editor.html.erb +17 -11
- data/app/views/alchemy/ingredients/_page_editor.html.erb +1 -0
- data/app/views/alchemy/ingredients/_picture_editor.html.erb +3 -4
- data/app/views/alchemy/ingredients/_richtext_editor.html.erb +5 -1
- data/app/views/alchemy/ingredients/_select_editor.html.erb +2 -1
- data/app/views/alchemy/ingredients/_text_editor.html.erb +20 -14
- data/app/views/alchemy/ingredients/shared/_picture_css_class.html.erb +6 -0
- data/app/views/layouts/alchemy/admin.html.erb +4 -2
- data/bin/setup +2 -0
- data/bin/start +1 -1
- data/bun.lockb +0 -0
- data/config/alchemy/config.yml +9 -0
- data/config/locales/alchemy.en.yml +8 -29
- data/config/routes.rb +22 -22
- data/lib/alchemy/config.rb +3 -3
- data/lib/alchemy/install/tasks.rb +5 -2
- data/lib/alchemy/resources_helper.rb +3 -1
- data/lib/alchemy/test_support/capybara_helpers.rb +8 -5
- data/lib/alchemy/test_support/shared_uploader_examples.rb +0 -1
- data/lib/alchemy/upgrader/seven_point_three.rb +52 -0
- data/lib/alchemy/version.rb +1 -1
- data/lib/alchemy_cms.rb +1 -1
- data/lib/generators/alchemy/install/files/article.css +25 -0
- data/lib/generators/alchemy/install/files/custom.css +4 -0
- data/lib/generators/alchemy/install/install_generator.rb +6 -6
- data/lib/tasks/alchemy/upgrade.rake +29 -1
- data/vendor/assets/stylesheets/alchemy_admin/select2.css +1 -0
- data/vendor/assets/stylesheets/jquery.Jcrop.min.css +2 -0
- data/vendor/javascript/shoelace.min.js +62 -63
- data/vendor/javascript/tinymce.min.js +1 -1
- metadata +132 -105
- data/app/assets/images/alchemy/lupe.cur +0 -0
- data/app/assets/stylesheets/alchemy/labels.scss +0 -3
- data/app/assets/stylesheets/alchemy/tags.scss +0 -155
- data/app/assets/stylesheets/alchemy/welcome.sass +0 -49
- data/app/views/alchemy/admin/attachments/_attachment.html.erb +0 -81
- data/app/views/alchemy/admin/languages/_language.html.erb +0 -50
- data/app/views/alchemy/admin/pages/_table_row.html.erb +0 -111
- data/app/views/alchemy/admin/pages/list/_table.html.erb +0 -31
- data/app/views/alchemy/admin/pictures/update.js.erb +0 -6
- data/app/views/alchemy/admin/tags/_tag.html.erb +0 -32
- data/app/views/alchemy/base/update.js.erb +0 -5
- data/lib/generators/alchemy/install/files/all.css +0 -11
- data/lib/generators/alchemy/install/files/article.scss +0 -30
- data/package.json +0 -52
- data/vendor/assets/stylesheets/alchemy_admin/select2.scss +0 -741
- data/vendor/assets/stylesheets/jquery.Jcrop.min.scss +0 -2
- /data/app/assets/stylesheets/alchemy/{fonts.scss → _fonts.scss} +0 -0
- /data/app/assets/stylesheets/alchemy/{hints.scss → admin/hints.scss} +0 -0
- /data/app/assets/stylesheets/alchemy/{icons.scss → admin/icons.scss} +0 -0
- /data/app/assets/stylesheets/alchemy/{images.scss → admin/images.scss} +0 -0
- /data/app/assets/stylesheets/alchemy/{preview_window.scss → admin/preview_window.scss} +0 -0
- /data/app/assets/stylesheets/alchemy/{spinner.scss → admin/spinner.scss} +0 -0
- /data/app/views/alchemy/admin/dashboard/{_locked_pages.html.erb → widgets/_locked_pages.html.erb} +0 -0
- /data/app/views/alchemy/admin/dashboard/{_recent_pages.html.erb → widgets/_recent_pages.html.erb} +0 -0
- /data/app/views/alchemy/admin/dashboard/{_sites.html.erb → widgets/_sites.html.erb} +0 -0
- /data/app/views/alchemy/admin/dashboard/{_users.html.erb → widgets/_users.html.erb} +0 -0
|
@@ -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:
|
|
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
|
|
@@ -100,7 +100,8 @@ module Alchemy
|
|
|
100
100
|
flash[:notice] = Alchemy.t(flash_notice)
|
|
101
101
|
do_redirect_to redirect_url
|
|
102
102
|
else
|
|
103
|
-
render action: ((params[:action] == "update") ? "edit" : "new")
|
|
103
|
+
render action: ((params[:action] == "update") ? "edit" : "new"),
|
|
104
|
+
status: :unprocessable_entity
|
|
104
105
|
end
|
|
105
106
|
end
|
|
106
107
|
|
|
@@ -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
|
|
73
|
-
|
|
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
|
|
|
@@ -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
|
|
@@ -138,7 +138,7 @@ module Alchemy
|
|
|
138
138
|
end
|
|
139
139
|
|
|
140
140
|
def eligible_resource_filter_values
|
|
141
|
-
resource_filters.map(&:values).flatten
|
|
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:
|
|
173
|
+
@alchemy_module ||= module_definition_for(controller: controller_path, action: "index")
|
|
174
174
|
end
|
|
175
175
|
|
|
176
176
|
def load_resource
|
|
@@ -11,7 +11,7 @@ module Alchemy
|
|
|
11
11
|
name: file.name)
|
|
12
12
|
|
|
13
13
|
{
|
|
14
|
-
json:
|
|
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:
|
|
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
|
|
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
|
-
|
|
12
|
-
"
|
|
13
|
-
|
|
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, &
|
|
46
|
-
render Alchemy::Admin::Message.new(msg || capture(&
|
|
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(&
|
|
66
|
-
render
|
|
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
|
|
@@ -19,7 +19,7 @@ export class ElementEditor extends HTMLElement {
|
|
|
19
19
|
// Triggered by child elements
|
|
20
20
|
this.addEventListener("alchemy:element-update-title", this)
|
|
21
21
|
// We use of @rails/ujs for Rails remote forms
|
|
22
|
-
this.addEventListener("ajax:
|
|
22
|
+
this.addEventListener("ajax:complete", this)
|
|
23
23
|
// Dirty observer
|
|
24
24
|
this.addEventListener("change", this)
|
|
25
25
|
|
|
@@ -57,11 +57,11 @@ export class ElementEditor extends HTMLElement {
|
|
|
57
57
|
this.onClickElement()
|
|
58
58
|
}
|
|
59
59
|
break
|
|
60
|
-
case "ajax:
|
|
60
|
+
case "ajax:complete":
|
|
61
61
|
if (event.target === this.body) {
|
|
62
|
-
const
|
|
62
|
+
const xhr = event.detail[0]
|
|
63
63
|
event.stopPropagation()
|
|
64
|
-
this.onSaveElement(
|
|
64
|
+
this.onSaveElement(xhr)
|
|
65
65
|
}
|
|
66
66
|
break
|
|
67
67
|
case "alchemy:element-update-title":
|
|
@@ -115,30 +115,28 @@ export class ElementEditor extends HTMLElement {
|
|
|
115
115
|
/**
|
|
116
116
|
* Sets the element to saved state
|
|
117
117
|
* Updates title
|
|
118
|
+
* JS event bubbling will also update the parents element quote.
|
|
118
119
|
* Shows error messages if ingredient validations fail
|
|
119
|
-
* @argument {
|
|
120
|
+
* @argument {XMLHttpRequest} xhr
|
|
120
121
|
*/
|
|
121
|
-
onSaveElement(
|
|
122
|
-
|
|
123
|
-
this.setClean()
|
|
122
|
+
onSaveElement(xhr) {
|
|
123
|
+
const data = JSON.parse(xhr.responseText)
|
|
124
124
|
// Reset errors that might be visible from last save attempt
|
|
125
|
-
this.
|
|
126
|
-
this.elementErrors.classList.add("hidden")
|
|
127
|
-
this.body
|
|
128
|
-
.querySelectorAll(".ingredient-editor")
|
|
129
|
-
.forEach((el) => el.classList.remove("validation_failed"))
|
|
125
|
+
this.setClean()
|
|
130
126
|
// If validation failed
|
|
131
|
-
if (
|
|
127
|
+
if (xhr.status === 422) {
|
|
132
128
|
const warning = data.warning
|
|
133
129
|
// Create error messages
|
|
134
|
-
data.errors.forEach((message) => {
|
|
135
|
-
this.errorsDisplay.append(createHtmlElement(`<li>${message}</li>`))
|
|
136
|
-
})
|
|
137
130
|
// Mark ingredients as failed
|
|
138
|
-
data.ingredientsWithErrors.forEach((
|
|
139
|
-
this.querySelector(
|
|
140
|
-
"
|
|
131
|
+
data.ingredientsWithErrors.forEach((ingredient) => {
|
|
132
|
+
const ingredientEditor = this.querySelector(
|
|
133
|
+
`[data-ingredient-id="${ingredient.id}"]`
|
|
141
134
|
)
|
|
135
|
+
const errorDisplay = createHtmlElement(
|
|
136
|
+
`<small class="error">${ingredient.errorMessage}</small>`
|
|
137
|
+
)
|
|
138
|
+
ingredientEditor?.appendChild(errorDisplay)
|
|
139
|
+
ingredientEditor?.classList.add("validation_failed")
|
|
142
140
|
})
|
|
143
141
|
// Show message
|
|
144
142
|
growl(warning, "warn")
|
|
@@ -208,9 +206,12 @@ export class ElementEditor extends HTMLElement {
|
|
|
208
206
|
setClean() {
|
|
209
207
|
this.dirty = false
|
|
210
208
|
window.onbeforeunload = null
|
|
209
|
+
this.elementErrors.classList.add("hidden")
|
|
210
|
+
|
|
211
211
|
if (this.hasEditors) {
|
|
212
|
-
this.body.querySelectorAll(".
|
|
213
|
-
el.classList.remove("dirty")
|
|
212
|
+
this.body.querySelectorAll(".ingredient-editor").forEach((el) => {
|
|
213
|
+
el.classList.remove("dirty", "validation_failed")
|
|
214
|
+
el.querySelectorAll("small.error").forEach((e) => e.remove())
|
|
214
215
|
})
|
|
215
216
|
}
|
|
216
217
|
}
|
|
@@ -482,15 +483,6 @@ export class ElementEditor extends HTMLElement {
|
|
|
482
483
|
return this.toggleButton?.querySelector("alchemy-icon")
|
|
483
484
|
}
|
|
484
485
|
|
|
485
|
-
/**
|
|
486
|
-
* The error messages container
|
|
487
|
-
*
|
|
488
|
-
* @returns {HTMLElement}
|
|
489
|
-
*/
|
|
490
|
-
get errorsDisplay() {
|
|
491
|
-
return this.body.querySelector(".error-messages")
|
|
492
|
-
}
|
|
493
|
-
|
|
494
486
|
/**
|
|
495
487
|
* The validation messages list container
|
|
496
488
|
*
|
|
@@ -66,12 +66,11 @@ class PreviewWindow extends HTMLIFrameElement {
|
|
|
66
66
|
|
|
67
67
|
key("alt+r", () => this.refresh())
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
$(this.sizeSelect).on("change", (evt) => {
|
|
69
|
+
this.sizeSelect.addEventListener("change", (evt) => {
|
|
71
70
|
const select = evt.target
|
|
72
71
|
const width = select.value
|
|
73
72
|
|
|
74
|
-
if (width === "
|
|
73
|
+
if (width === "") {
|
|
75
74
|
this.style.width = null
|
|
76
75
|
} else {
|
|
77
76
|
this.resize(width)
|
|
@@ -1,34 +1,62 @@
|
|
|
1
1
|
import { on } from "alchemy_admin/utils/events"
|
|
2
2
|
|
|
3
|
+
function toggleCheckboxes(state) {
|
|
4
|
+
document
|
|
5
|
+
.querySelectorAll(".picture_tool.select input[type='checkbox']")
|
|
6
|
+
.forEach((checkbox) => {
|
|
7
|
+
checkbox.checked = state
|
|
8
|
+
checkbox.closest(".picture_thumbnail").classList.toggle("active", state)
|
|
9
|
+
})
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function checkedInputs() {
|
|
13
|
+
return document.querySelectorAll("#picture_archive input:checked")
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function editMultiplePicturesUrl(href) {
|
|
17
|
+
const searchParameters = new URLSearchParams()
|
|
18
|
+
checkedInputs().forEach((entry) =>
|
|
19
|
+
searchParameters.append(entry.name, entry.value)
|
|
20
|
+
)
|
|
21
|
+
const url = href + "?" + searchParameters.toString()
|
|
22
|
+
|
|
23
|
+
return url
|
|
24
|
+
}
|
|
25
|
+
|
|
3
26
|
/**
|
|
4
27
|
* Multiple picture select handler for the picture archive.
|
|
5
28
|
*/
|
|
6
29
|
export default function PictureSelector() {
|
|
30
|
+
const selectAllButton = document.querySelector("#select_all_pictures")
|
|
7
31
|
const selectedItemTools = document.querySelector(".selected_item_tools")
|
|
8
|
-
|
|
9
|
-
|
|
32
|
+
|
|
33
|
+
on("click", ".toolbar_buttons", "a#select_all_pictures", (event) => {
|
|
34
|
+
event.preventDefault()
|
|
35
|
+
|
|
36
|
+
selectAllButton.classList.toggle("active")
|
|
37
|
+
|
|
38
|
+
const state = selectAllButton.classList.contains("active")
|
|
39
|
+
|
|
40
|
+
toggleCheckboxes(state)
|
|
41
|
+
|
|
42
|
+
selectedItemTools.classList.toggle("hidden", !state)
|
|
43
|
+
})
|
|
10
44
|
|
|
11
45
|
// make the item toolbar visible and show the checkbox also if it is not hovered anymore
|
|
12
46
|
on("change", ".picture_tool.select", "input", (event) => {
|
|
13
|
-
selectedItemTools.
|
|
14
|
-
checkedInputs().length > 0 ? "block" : "none"
|
|
47
|
+
selectedItemTools.classList.toggle("hidden", checkedInputs().length === 0)
|
|
15
48
|
|
|
16
49
|
const parentElementClassList = event.target.parentElement.classList
|
|
17
50
|
const checked = event.target.checked
|
|
18
51
|
|
|
19
52
|
parentElementClassList.toggle("visible", checked)
|
|
20
|
-
parentElementClassList.toggle("hidden", !checked)
|
|
21
53
|
})
|
|
22
54
|
|
|
23
55
|
// open the edit view in a dialog modal
|
|
24
56
|
on("click", ".selected_item_tools", "a#edit_multiple_pictures", (event) => {
|
|
25
57
|
event.preventDefault()
|
|
26
58
|
|
|
27
|
-
const
|
|
28
|
-
checkedInputs().forEach((entry) =>
|
|
29
|
-
searchParameters.append(entry.name, entry.value)
|
|
30
|
-
)
|
|
31
|
-
const url = event.target.href + "?" + searchParameters.toString()
|
|
59
|
+
const url = editMultiplePicturesUrl(event.target.href)
|
|
32
60
|
|
|
33
61
|
Alchemy.openDialog(url, {
|
|
34
62
|
title: event.target.title,
|
|
@@ -102,14 +102,6 @@ module Alchemy
|
|
|
102
102
|
|
|
103
103
|
# Instance methods
|
|
104
104
|
|
|
105
|
-
def to_jq_upload
|
|
106
|
-
{
|
|
107
|
-
"name" => read_attribute(:file_name),
|
|
108
|
-
"size" => read_attribute(:file_size),
|
|
109
|
-
"error" => errors[:file].join
|
|
110
|
-
}
|
|
111
|
-
end
|
|
112
|
-
|
|
113
105
|
def url(options = {})
|
|
114
106
|
if file
|
|
115
107
|
self.class.url_class.new(self).call(options)
|