alchemy_cms 6.0.0.pre.rc7 → 6.0.2
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.
Potentially problematic release.
This version of alchemy_cms might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +15 -0
- data/CHANGELOG.md +38 -0
- data/Gemfile +15 -1
- data/README.md +4 -3
- data/alchemy_cms.gemspec +2 -2
- data/app/assets/javascripts/alchemy/alchemy.link_dialog.js.coffee +2 -0
- data/app/assets/stylesheets/alchemy/archive.scss +5 -0
- data/app/assets/stylesheets/alchemy/elements.scss +4 -0
- data/app/controllers/alchemy/admin/elements_controller.rb +20 -17
- data/app/controllers/alchemy/admin/pages_controller.rb +15 -7
- data/app/controllers/alchemy/api/base_controller.rb +4 -3
- data/app/controllers/alchemy/api/contents_controller.rb +1 -5
- data/app/controllers/alchemy/api/elements_controller.rb +2 -6
- data/app/controllers/alchemy/api/nodes_controller.rb +1 -0
- data/app/controllers/alchemy/api/pages_controller.rb +2 -6
- data/app/controllers/alchemy/base_controller.rb +7 -0
- data/app/controllers/alchemy/messages_controller.rb +0 -3
- data/app/controllers/alchemy/pages_controller.rb +0 -7
- data/app/helpers/alchemy/elements_helper.rb +17 -12
- data/app/models/alchemy/element.rb +13 -6
- data/app/models/alchemy/ingredient_validator.rb +1 -1
- data/app/models/alchemy/language.rb +1 -1
- data/app/models/alchemy/page/page_elements.rb +2 -2
- data/app/models/alchemy/page/page_naming.rb +1 -1
- data/app/models/alchemy/page.rb +1 -1
- data/app/models/alchemy/picture/transformations.rb +2 -2
- data/app/models/alchemy/picture.rb +1 -1
- data/app/models/alchemy/picture_variant.rb +3 -1
- data/app/models/alchemy/site.rb +1 -1
- data/app/views/alchemy/admin/clipboard/insert.js.erb +13 -0
- data/app/views/alchemy/admin/elements/_add_nested_element_form.html.erb +27 -0
- data/app/views/alchemy/admin/elements/_element.html.erb +1 -23
- data/app/views/alchemy/admin/elements/_form.html.erb +5 -1
- data/app/views/alchemy/admin/partials/_routes.html.erb +4 -0
- data/app/views/alchemy/admin/resources/_form.html.erb +5 -0
- data/app/views/alchemy/essences/_essence_node_editor.html.erb +1 -1
- data/config/alchemy/config.yml +1 -0
- data/config/initializers/dragonfly.rb +2 -0
- data/lib/alchemy/config.rb +5 -1
- data/lib/alchemy/controller_actions.rb +2 -1
- data/lib/alchemy/dragonfly/processors/thumbnail.rb +27 -0
- data/lib/alchemy/element_definition.rb +2 -3
- data/lib/alchemy/elements_finder.rb +1 -2
- data/lib/alchemy/engine.rb +12 -1
- data/lib/alchemy/essence.rb +1 -27
- data/lib/alchemy/page_layout.rb +5 -1
- data/lib/alchemy/permissions.rb +2 -2
- data/lib/alchemy/resource.rb +16 -1
- data/lib/alchemy/test_support/essence_shared_examples.rb +0 -12
- data/lib/alchemy/upgrader/tasks/ingredients_migrator.rb +1 -1
- data/lib/alchemy/version.rb +1 -1
- data/lib/alchemy.rb +2 -4
- data/lib/generators/alchemy/base.rb +7 -3
- data/lib/generators/alchemy/install/install_generator.rb +4 -1
- data/package/src/image_loader.js +2 -2
- data/package/src/picture_editors.js +5 -5
- data/package.json +1 -1
- metadata +26 -24
@@ -13,6 +13,8 @@ module Alchemy
|
|
13
13
|
include Alchemy::Logger
|
14
14
|
include Alchemy::Picture::Transformations
|
15
15
|
|
16
|
+
ANIMATED_IMAGE_FORMATS = %w[gif webp]
|
17
|
+
|
16
18
|
attr_reader :picture, :render_format
|
17
19
|
|
18
20
|
def_delegators :@picture,
|
@@ -86,7 +88,7 @@ module Alchemy
|
|
86
88
|
end
|
87
89
|
|
88
90
|
options = {
|
89
|
-
flatten: render_format
|
91
|
+
flatten: !render_format.in?(ANIMATED_IMAGE_FORMATS) && picture.image_file_format == "gif",
|
90
92
|
}.with_indifferent_access.merge(options)
|
91
93
|
|
92
94
|
encoding_options = []
|
data/app/models/alchemy/site.rb
CHANGED
@@ -14,3 +14,16 @@
|
|
14
14
|
Alchemy.growl('<%= j Alchemy.t("item copied to clipboard", name: @item.class.name == "Alchemy::Element" ? @item.display_name_with_preview_text : @item.name) %>')
|
15
15
|
<% end -%>
|
16
16
|
$('#clipboard_button .icon').removeClass('fa-clipboard').addClass('fa-paste');
|
17
|
+
|
18
|
+
<%# Update add nested element forms for any elements that accept ONLY this as a nested element %>
|
19
|
+
<% if @item.class == Alchemy::Element %>
|
20
|
+
if (window.location.pathname == "<%= edit_admin_page_path(@item.page.id) %>") {
|
21
|
+
<%
|
22
|
+
@item.page.draft_version.elements.expanded.select do |element|
|
23
|
+
element.definition["nestable_elements"] == [@item.name]
|
24
|
+
end.each do |element|
|
25
|
+
%>
|
26
|
+
$(".add-nested-element[data-element-id='<%= element.id %>']").replaceWith('<%= j render "alchemy/admin/elements/add_nested_element_form", element: element %>')
|
27
|
+
<% end %>
|
28
|
+
}
|
29
|
+
<% end %>
|
@@ -0,0 +1,27 @@
|
|
1
|
+
<%= content_tag :div, class: 'add-nested-element', data: { element_id: element.id } do %>
|
2
|
+
<% if element.expanded? || element.fixed? %>
|
3
|
+
<% if element.nestable_elements.length == 1 &&
|
4
|
+
(nestable_element = element.nestable_elements.first) &&
|
5
|
+
Alchemy::Element.all_from_clipboard_for_parent_element(get_clipboard("elements"), element).none?
|
6
|
+
%>
|
7
|
+
<%= form_for [:admin, Alchemy::Element.new(name: nestable_element)],
|
8
|
+
remote: true, html: { class: 'add-nested-element-form', id: nil } do |f| %>
|
9
|
+
<%= f.hidden_field :name %>
|
10
|
+
<%= f.hidden_field :page_version_id, value: element.page_version_id %>
|
11
|
+
<%= f.hidden_field :parent_element_id, value: element.id %>
|
12
|
+
<button class="button add-nestable-element-button" data-alchemy-button>
|
13
|
+
<%= Alchemy.t(:add_nested_element, name: Alchemy.t(nestable_element, scope: 'element_names')) %>
|
14
|
+
</button>
|
15
|
+
<% end %>
|
16
|
+
<% else %>
|
17
|
+
<%= link_to_dialog (nestable_element ? Alchemy.t(:add_nested_element, name: Alchemy.t(nestable_element, scope: 'element_names')) : Alchemy.t("New Element")),
|
18
|
+
alchemy.new_admin_element_path(
|
19
|
+
parent_element_id: element.id,
|
20
|
+
page_version_id: element.page_version_id
|
21
|
+
), {
|
22
|
+
size: "320x125",
|
23
|
+
title: Alchemy.t("New Element")
|
24
|
+
}, class: "button add-nestable-element-button" %>
|
25
|
+
<% end %>
|
26
|
+
<% end %>
|
27
|
+
<% end %>
|
@@ -55,29 +55,7 @@
|
|
55
55
|
} %>
|
56
56
|
<% end %>
|
57
57
|
|
58
|
-
|
59
|
-
<% if element.nestable_elements.length == 1 %>
|
60
|
-
<% nestable_element = element.nestable_elements.first %>
|
61
|
-
<%= form_for [:admin, Alchemy::Element.new(name: nestable_element)],
|
62
|
-
remote: true, html: { class: 'add-nested-element-form', id: nil } do |f| %>
|
63
|
-
<%= f.hidden_field :name %>
|
64
|
-
<%= f.hidden_field :page_version_id, value: element.page_version_id %>
|
65
|
-
<%= f.hidden_field :parent_element_id, value: element.id %>
|
66
|
-
<button class="button add-nestable-element-button" data-alchemy-button>
|
67
|
-
<%= Alchemy.t(:add_nested_element) % { name: Alchemy.t(nestable_element, scope: 'element_names') } %>
|
68
|
-
</button>
|
69
|
-
<% end %>
|
70
|
-
<% else %>
|
71
|
-
<%= link_to_dialog Alchemy.t("New Element"),
|
72
|
-
alchemy.new_admin_element_path(
|
73
|
-
parent_element_id: element.id,
|
74
|
-
page_version_id: element.page_version_id
|
75
|
-
), {
|
76
|
-
size: "320x125",
|
77
|
-
title: Alchemy.t("New Element")
|
78
|
-
}, class: "button add-nestable-element-button" %>
|
79
|
-
<% end %>
|
80
|
-
<% end %>
|
58
|
+
<%= render "alchemy/admin/elements/add_nested_element_form", element: element %>
|
81
59
|
</div>
|
82
60
|
<% end %>
|
83
61
|
<% end %>
|
@@ -9,7 +9,11 @@
|
|
9
9
|
label: Alchemy.t(:element_of_type),
|
10
10
|
collection: elements_for_select(@elements),
|
11
11
|
prompt: Alchemy.t(:select_element),
|
12
|
-
|
12
|
+
selected: (@elements.first if @elements.count == 1),
|
13
|
+
input_html: {class: 'alchemy_selectbox', autofocus: true, disabled: @elements.count == 1} %>
|
14
|
+
<% if @elements.count == 1 %>
|
15
|
+
<%= form.hidden_field :name, value: @elements.first[:name] %>
|
16
|
+
<% end %>
|
13
17
|
<%= form.hidden_field :parent_element_id, value: @parent_element.try(:id) %>
|
14
18
|
<%= form.submit Alchemy.t(:add) %>
|
15
19
|
<%- end -%>
|
@@ -5,6 +5,10 @@
|
|
5
5
|
return '<%= alchemy.admin_picture_path(id: 1) %>'.replace(/1/, id);
|
6
6
|
},
|
7
7
|
|
8
|
+
url_admin_picture_path: function(id) {
|
9
|
+
return '<%= alchemy.url_admin_picture_path(id: 1) %>'.replace(/1/, id);
|
10
|
+
},
|
11
|
+
|
8
12
|
fold_admin_element_path: function(id) {
|
9
13
|
return '<%= alchemy.fold_admin_element_path(id: 1) %>'.replace(/1/, id);
|
10
14
|
},
|
@@ -8,6 +8,11 @@
|
|
8
8
|
input_html: {class: 'alchemy_selectbox'} %>
|
9
9
|
<% elsif attribute[:type].in? %i[date time datetime] %>
|
10
10
|
<%= f.datepicker attribute[:name], resource_attribute_field_options(attribute) %>
|
11
|
+
<% elsif attribute[:enum].present? %>
|
12
|
+
<%= f.input attribute[:name],
|
13
|
+
collection: attribute[:enum],
|
14
|
+
include_blank: Alchemy.t(:blank, scope: 'resources.relation_select'),
|
15
|
+
input_html: {class: 'alchemy_selectbox'} %>
|
11
16
|
<% else %>
|
12
17
|
<%= f.input attribute[:name], resource_attribute_field_options(attribute) %>
|
13
18
|
<% end %>
|
@@ -17,7 +17,7 @@
|
|
17
17
|
}) %>
|
18
18
|
$('#<%= essence_node_editor.form_field_id %>').alchemyNodeSelect({
|
19
19
|
placeholder: "<%= Alchemy.t(:search_node) %>",
|
20
|
-
url: "<%= alchemy.api_nodes_path %>",
|
20
|
+
url: "<%= alchemy.api_nodes_path(language_id: essence_node_editor.page&.language_id) %>",
|
21
21
|
query_params: <%== query_params.to_json %>,
|
22
22
|
<% if essence_node_editor.essence.node %>
|
23
23
|
<% serialized_node = ActiveModelSerializers::SerializableResource.new(essence_node_editor.essence.node, include: :ancestors) %>
|
data/config/alchemy/config.yml
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
require "dragonfly_svg"
|
3
3
|
require "alchemy/dragonfly/processors/crop_resize"
|
4
4
|
require "alchemy/dragonfly/processors/auto_orient"
|
5
|
+
require "alchemy/dragonfly/processors/thumbnail"
|
5
6
|
|
6
7
|
# Logger
|
7
8
|
Dragonfly.logger = Rails.logger
|
@@ -18,4 +19,5 @@ Dragonfly::ImageMagick::Processors::Encode::WHITELISTED_ARGS << "flatten"
|
|
18
19
|
Rails.application.config.after_initialize do
|
19
20
|
Dragonfly.app(:alchemy_pictures).add_processor(:crop_resize, Alchemy::Dragonfly::Processors::CropResize.new)
|
20
21
|
Dragonfly.app(:alchemy_pictures).add_processor(:auto_orient, Alchemy::Dragonfly::Processors::AutoOrient.new)
|
22
|
+
Dragonfly.app(:alchemy_pictures).add_processor(:thumbnail, Alchemy::Dragonfly::Processors::Thumbnail.new)
|
21
23
|
end
|
data/lib/alchemy/config.rb
CHANGED
@@ -55,7 +55,11 @@ module Alchemy
|
|
55
55
|
# If it does not exist, or its empty, it returns an empty Hash.
|
56
56
|
#
|
57
57
|
def read_file(file)
|
58
|
-
YAML.safe_load(
|
58
|
+
YAML.safe_load(
|
59
|
+
ERB.new(File.read(file)).result,
|
60
|
+
permitted_classes: YAML_PERMITTED_CLASSES,
|
61
|
+
aliases: true,
|
62
|
+
) || {}
|
59
63
|
rescue Errno::ENOENT
|
60
64
|
{}
|
61
65
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dragonfly/image_magick/processors/thumb"
|
4
|
+
|
5
|
+
module Alchemy
|
6
|
+
module Dragonfly
|
7
|
+
module Processors
|
8
|
+
class Thumbnail < ::Dragonfly::ImageMagick::Processors::Thumb
|
9
|
+
def call(content, geometry, opts = {})
|
10
|
+
# store content into an instance variable to use it in args_for_geometry - method
|
11
|
+
@content = content
|
12
|
+
super
|
13
|
+
end
|
14
|
+
|
15
|
+
##
|
16
|
+
# due to a missing ImageMagick parameter animated GIFs were broken with the default
|
17
|
+
# Dragonfly Thumb processor
|
18
|
+
def args_for_geometry(geometry)
|
19
|
+
# resize all frames in a GIF
|
20
|
+
# @link https://imagemagick.org/script/command-line-options.php#coalesce
|
21
|
+
# @link https://imagemagick.org/script/command-line-options.php#deconstruct
|
22
|
+
@content&.mime_type == "image/gif" ? "-coalesce #{super} -deconstruct" : super
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -50,9 +50,8 @@ module Alchemy
|
|
50
50
|
if File.exist?(definitions_file_path)
|
51
51
|
YAML.safe_load(
|
52
52
|
ERB.new(File.read(definitions_file_path)).result,
|
53
|
-
|
54
|
-
|
55
|
-
true
|
53
|
+
permitted_classes: YAML_PERMITTED_CLASSES,
|
54
|
+
aliases: true,
|
56
55
|
) || []
|
57
56
|
else
|
58
57
|
raise LoadError,
|
@@ -54,8 +54,7 @@ module Alchemy
|
|
54
54
|
def find_elements(page_version)
|
55
55
|
return Alchemy::ElementsRepository.none unless page_version
|
56
56
|
|
57
|
-
elements =
|
58
|
-
elements = elements.not_nested
|
57
|
+
elements = page_version.element_repository.visible.not_nested
|
59
58
|
elements = options[:fixed] ? elements.fixed : elements.unfixed
|
60
59
|
|
61
60
|
if options[:only]
|
data/lib/alchemy/engine.rb
CHANGED
@@ -37,7 +37,7 @@ module Alchemy
|
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
40
|
-
|
40
|
+
config.after_initialize do
|
41
41
|
if Alchemy.user_class
|
42
42
|
ActiveSupport.on_load(:active_record) do
|
43
43
|
Alchemy.user_class.model_stamper
|
@@ -45,5 +45,16 @@ module Alchemy
|
|
45
45
|
end
|
46
46
|
end
|
47
47
|
end
|
48
|
+
|
49
|
+
initializer "alchemy.webp-mime_type" do
|
50
|
+
# Rails does not know anything about webp even in 2022
|
51
|
+
unless Mime::Type.lookup_by_extension(:webp)
|
52
|
+
Mime::Type.register("image/webp", :webp)
|
53
|
+
end
|
54
|
+
# Dragonfly uses Rack to read the mime type and guess what
|
55
|
+
unless Rack::Mime::MIME_TYPES[".webp"]
|
56
|
+
Rack::Mime::MIME_TYPES[".webp"] = "image/webp"
|
57
|
+
end
|
58
|
+
end
|
48
59
|
end
|
49
60
|
end
|
data/lib/alchemy/essence.rb
CHANGED
@@ -3,18 +3,6 @@
|
|
3
3
|
require "active_record"
|
4
4
|
|
5
5
|
module Alchemy #:nodoc:
|
6
|
-
# A bogus association that skips eager loading for essences not having an ingredient association
|
7
|
-
class IngredientAssociation < ActiveRecord::Associations::BelongsToAssociation
|
8
|
-
# Skip eager loading if called by Rails' preloader
|
9
|
-
def klass
|
10
|
-
if caller.any? { |line| line =~ /preloader\.rb/ }
|
11
|
-
nil
|
12
|
-
else
|
13
|
-
super
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
6
|
module Essence #:nodoc:
|
19
7
|
def self.included(base)
|
20
8
|
base.extend(ClassMethods)
|
@@ -43,8 +31,6 @@ module Alchemy #:nodoc:
|
|
43
31
|
ingredient_column: "body",
|
44
32
|
}.update(options)
|
45
33
|
|
46
|
-
@_classes_with_ingredient_association ||= []
|
47
|
-
|
48
34
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
49
35
|
attr_writer :validation_errors
|
50
36
|
include Alchemy::Essence::InstanceMethods
|
@@ -55,7 +41,7 @@ module Alchemy #:nodoc:
|
|
55
41
|
has_one :element, through: :content, class_name: "Alchemy::Element"
|
56
42
|
has_one :page, through: :element, class_name: "Alchemy::Page"
|
57
43
|
|
58
|
-
scope :available, -> { joins(:element).merge(Alchemy::Element.
|
44
|
+
scope :available, -> { joins(:element).merge(Alchemy::Element.published) }
|
59
45
|
scope :from_element, ->(name) { joins(:element).where(Element.table_name => { name: name }) }
|
60
46
|
|
61
47
|
delegate :restricted?, to: :page, allow_nil: true
|
@@ -87,18 +73,6 @@ module Alchemy #:nodoc:
|
|
87
73
|
alias_method :#{configuration[:ingredient_column]}, :ingredient_association
|
88
74
|
alias_method :#{configuration[:ingredient_column]}=, :ingredient_association=
|
89
75
|
RUBY
|
90
|
-
|
91
|
-
@_classes_with_ingredient_association << self
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
# Overwrite ActiveRecords method to return a bogus association class that skips eager loading
|
96
|
-
# for essence classes that do not have an ingredient association
|
97
|
-
def _reflect_on_association(name)
|
98
|
-
if name == :ingredient_association && !in?(@_classes_with_ingredient_association)
|
99
|
-
OpenStruct.new(association_class: Alchemy::IngredientAssociation)
|
100
|
-
else
|
101
|
-
super
|
102
76
|
end
|
103
77
|
end
|
104
78
|
|
data/lib/alchemy/page_layout.rb
CHANGED
@@ -151,7 +151,11 @@ module Alchemy
|
|
151
151
|
#
|
152
152
|
def read_definitions_file
|
153
153
|
if File.exist?(layouts_file_path)
|
154
|
-
YAML.safe_load(
|
154
|
+
YAML.safe_load(
|
155
|
+
ERB.new(File.read(layouts_file_path)).result,
|
156
|
+
permitted_classes: YAML_PERMITTED_CLASSES,
|
157
|
+
aliases: true,
|
158
|
+
) || []
|
155
159
|
else
|
156
160
|
raise LoadError, "Could not find page_layouts.yml file! Please run `rails generate alchemy:install`"
|
157
161
|
end
|
data/lib/alchemy/permissions.rb
CHANGED
@@ -41,7 +41,7 @@ module Alchemy
|
|
41
41
|
c.public? && !c.restricted?
|
42
42
|
end
|
43
43
|
|
44
|
-
can :read, Alchemy::Element, Alchemy::Element.
|
44
|
+
can :read, Alchemy::Element, Alchemy::Element.published.not_restricted do |e|
|
45
45
|
e.public? && !e.restricted?
|
46
46
|
end
|
47
47
|
|
@@ -68,7 +68,7 @@ module Alchemy
|
|
68
68
|
c.public?
|
69
69
|
end
|
70
70
|
|
71
|
-
can :read, Alchemy::Element, Alchemy::Element.
|
71
|
+
can :read, Alchemy::Element, Alchemy::Element.published do |e|
|
72
72
|
e.public?
|
73
73
|
end
|
74
74
|
|
data/lib/alchemy/resource.rb
CHANGED
@@ -169,10 +169,25 @@ module Alchemy
|
|
169
169
|
name: col.name,
|
170
170
|
type: resource_column_type(col),
|
171
171
|
relation: resource_relation(col.name),
|
172
|
-
|
172
|
+
enum: enum_values_collection_for_select(col.name),
|
173
|
+
}.delete_if { |_k, v| v.blank? }
|
173
174
|
end.compact
|
174
175
|
end
|
175
176
|
|
177
|
+
def enum_values_collection_for_select(column_name)
|
178
|
+
enum = model.defined_enums[column_name]
|
179
|
+
return if enum.blank?
|
180
|
+
|
181
|
+
enum.keys.map do |key|
|
182
|
+
[
|
183
|
+
::I18n.t(key, scope: [
|
184
|
+
:activerecord, :attributes, model.model_name.i18n_key, "#{column_name}_values"
|
185
|
+
], default: key.humanize),
|
186
|
+
key,
|
187
|
+
]
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
176
191
|
def sorted_attributes
|
177
192
|
@_sorted_attributes ||= attributes.
|
178
193
|
sort_by { |attr| attr[:name] == "name" ? 0 : 1 }.
|
@@ -7,18 +7,6 @@ RSpec.shared_examples_for "an essence" do
|
|
7
7
|
let(:content) { Alchemy::Content.new(name: "foo") }
|
8
8
|
let(:content_definition) { { "name" => "foo" } }
|
9
9
|
|
10
|
-
describe "eager loading" do
|
11
|
-
before do
|
12
|
-
2.times { described_class.create! }
|
13
|
-
end
|
14
|
-
|
15
|
-
it "does not throw error if eager loaded" do
|
16
|
-
expect {
|
17
|
-
described_class.all.includes(:ingredient_association).to_a
|
18
|
-
}.to_not raise_error
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
10
|
it "touches the element after save" do
|
23
11
|
element = FactoryBot.create(:alchemy_element)
|
24
12
|
content = FactoryBot.create(:alchemy_content, element: element, essence: essence, essence_type: essence.class.name)
|
@@ -16,7 +16,7 @@ module Alchemy::Upgrader::Tasks
|
|
16
16
|
# eager load all elements that have ingredients defined but no ingredient records yet.
|
17
17
|
all_elements = Alchemy::Element
|
18
18
|
.named(elements_with_ingredients.map { |d| d[:name] })
|
19
|
-
.includes(contents:
|
19
|
+
.includes(contents: :essence)
|
20
20
|
.left_outer_joins(:ingredients).where(alchemy_ingredients: { id: nil })
|
21
21
|
.to_a
|
22
22
|
elements_with_ingredients.map do |element_definition|
|
data/lib/alchemy/version.rb
CHANGED
data/lib/alchemy.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
require "alchemy/admin/preview_url"
|
4
4
|
|
5
5
|
module Alchemy
|
6
|
-
|
6
|
+
YAML_PERMITTED_CLASSES = %w[Symbol Date Regexp]
|
7
7
|
|
8
8
|
# Define page preview sources
|
9
9
|
#
|
@@ -35,9 +35,7 @@ module Alchemy
|
|
35
35
|
# acme/preview_source: Acme Vorschau
|
36
36
|
#
|
37
37
|
def self.preview_sources
|
38
|
-
@_preview_sources ||=
|
39
|
-
Set.new << Alchemy::Admin::PreviewUrl
|
40
|
-
end
|
38
|
+
@_preview_sources ||= Set.new << Alchemy::Admin::PreviewUrl
|
41
39
|
end
|
42
40
|
|
43
41
|
# Define page publish targets
|
@@ -17,8 +17,8 @@ module Alchemy
|
|
17
17
|
# source and destination file names to use that engine.
|
18
18
|
if ext != template_engine.to_s
|
19
19
|
say_status :warning, "View uses unexpected template engine '#{ext}'.", :cyan
|
20
|
-
destination.gsub
|
21
|
-
source.gsub
|
20
|
+
destination = destination.gsub(/#{template_engine}$/, ext)
|
21
|
+
source = source.gsub(/#{template_engine}$/, ext)
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
@@ -33,7 +33,11 @@ module Alchemy
|
|
33
33
|
end
|
34
34
|
|
35
35
|
def load_alchemy_yaml(name)
|
36
|
-
YAML.safe_load(
|
36
|
+
YAML.safe_load(
|
37
|
+
ERB.new(File.read(Rails.root.join("config", "alchemy", name))).result,
|
38
|
+
permitted_classes: YAML_PERMITTED_CLASSES,
|
39
|
+
aliases: true,
|
40
|
+
)
|
37
41
|
rescue Errno::ENOENT
|
38
42
|
puts "\nERROR: Could not read config/alchemy/#{name} file. Please run: `rails generate alchemy:install`"
|
39
43
|
end
|
@@ -98,7 +98,10 @@ module Alchemy
|
|
98
98
|
end
|
99
99
|
|
100
100
|
def copy_alchemy_entry_point
|
101
|
-
webpack_config = YAML.
|
101
|
+
webpack_config = YAML.safe_load(
|
102
|
+
File.read(app_root.join("config", "webpacker.yml")),
|
103
|
+
aliases: true
|
104
|
+
)[Rails.env]
|
102
105
|
copy_file "alchemy_admin.js",
|
103
106
|
app_root.join(webpack_config["source_path"], webpack_config["source_entry_path"], "alchemy/admin.js")
|
104
107
|
end
|
data/package/src/image_loader.js
CHANGED
@@ -40,9 +40,9 @@ export default class ImageLoader {
|
|
40
40
|
}
|
41
41
|
|
42
42
|
onError(evt) {
|
43
|
-
const message = `Could not load ${this.image.src}`
|
43
|
+
const message = `Could not load "${this.image.src}"`
|
44
44
|
this.removeSpinner()
|
45
|
-
this.parent.innerHTML = `<span class="icon fas fa-exclamation-triangle" title="${message}" />`
|
45
|
+
this.parent.innerHTML = `<span class="icon error fas fa-exclamation-triangle" title="${message}" />`
|
46
46
|
console.error(message, evt)
|
47
47
|
this.unbind()
|
48
48
|
}
|
@@ -5,7 +5,6 @@ import ImageLoader from "./image_loader"
|
|
5
5
|
|
6
6
|
const UPDATE_DELAY = 125
|
7
7
|
const IMAGE_PLACEHOLDER = '<i class="icon far fa-image fa-fw"></i>'
|
8
|
-
const EMPTY_IMAGE = '<img src="" class="img_paddingtop" />'
|
9
8
|
const THUMBNAIL_SIZE = "160x120"
|
10
9
|
|
11
10
|
class PictureEditor {
|
@@ -62,7 +61,7 @@ class PictureEditor {
|
|
62
61
|
this.image.removeAttribute("alt")
|
63
62
|
this.image.removeAttribute("src")
|
64
63
|
this.imageLoader.load(true)
|
65
|
-
get(
|
64
|
+
get(Alchemy.routes.url_admin_picture_path(this.pictureId), {
|
66
65
|
crop: this.imageCropperEnabled,
|
67
66
|
crop_from: this.cropFrom,
|
68
67
|
crop_size: this.cropSize,
|
@@ -83,9 +82,10 @@ class PictureEditor {
|
|
83
82
|
ensureImage() {
|
84
83
|
if (this.image) return
|
85
84
|
|
86
|
-
|
87
|
-
this.
|
88
|
-
this.
|
85
|
+
const img = new Image()
|
86
|
+
this.thumbnailBackground.replaceChildren(img)
|
87
|
+
this.image = img
|
88
|
+
this.imageLoader = new ImageLoader(img)
|
89
89
|
}
|
90
90
|
|
91
91
|
removeImage() {
|