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.

Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +15 -0
  3. data/CHANGELOG.md +38 -0
  4. data/Gemfile +15 -1
  5. data/README.md +4 -3
  6. data/alchemy_cms.gemspec +2 -2
  7. data/app/assets/javascripts/alchemy/alchemy.link_dialog.js.coffee +2 -0
  8. data/app/assets/stylesheets/alchemy/archive.scss +5 -0
  9. data/app/assets/stylesheets/alchemy/elements.scss +4 -0
  10. data/app/controllers/alchemy/admin/elements_controller.rb +20 -17
  11. data/app/controllers/alchemy/admin/pages_controller.rb +15 -7
  12. data/app/controllers/alchemy/api/base_controller.rb +4 -3
  13. data/app/controllers/alchemy/api/contents_controller.rb +1 -5
  14. data/app/controllers/alchemy/api/elements_controller.rb +2 -6
  15. data/app/controllers/alchemy/api/nodes_controller.rb +1 -0
  16. data/app/controllers/alchemy/api/pages_controller.rb +2 -6
  17. data/app/controllers/alchemy/base_controller.rb +7 -0
  18. data/app/controllers/alchemy/messages_controller.rb +0 -3
  19. data/app/controllers/alchemy/pages_controller.rb +0 -7
  20. data/app/helpers/alchemy/elements_helper.rb +17 -12
  21. data/app/models/alchemy/element.rb +13 -6
  22. data/app/models/alchemy/ingredient_validator.rb +1 -1
  23. data/app/models/alchemy/language.rb +1 -1
  24. data/app/models/alchemy/page/page_elements.rb +2 -2
  25. data/app/models/alchemy/page/page_naming.rb +1 -1
  26. data/app/models/alchemy/page.rb +1 -1
  27. data/app/models/alchemy/picture/transformations.rb +2 -2
  28. data/app/models/alchemy/picture.rb +1 -1
  29. data/app/models/alchemy/picture_variant.rb +3 -1
  30. data/app/models/alchemy/site.rb +1 -1
  31. data/app/views/alchemy/admin/clipboard/insert.js.erb +13 -0
  32. data/app/views/alchemy/admin/elements/_add_nested_element_form.html.erb +27 -0
  33. data/app/views/alchemy/admin/elements/_element.html.erb +1 -23
  34. data/app/views/alchemy/admin/elements/_form.html.erb +5 -1
  35. data/app/views/alchemy/admin/partials/_routes.html.erb +4 -0
  36. data/app/views/alchemy/admin/resources/_form.html.erb +5 -0
  37. data/app/views/alchemy/essences/_essence_node_editor.html.erb +1 -1
  38. data/config/alchemy/config.yml +1 -0
  39. data/config/initializers/dragonfly.rb +2 -0
  40. data/lib/alchemy/config.rb +5 -1
  41. data/lib/alchemy/controller_actions.rb +2 -1
  42. data/lib/alchemy/dragonfly/processors/thumbnail.rb +27 -0
  43. data/lib/alchemy/element_definition.rb +2 -3
  44. data/lib/alchemy/elements_finder.rb +1 -2
  45. data/lib/alchemy/engine.rb +12 -1
  46. data/lib/alchemy/essence.rb +1 -27
  47. data/lib/alchemy/page_layout.rb +5 -1
  48. data/lib/alchemy/permissions.rb +2 -2
  49. data/lib/alchemy/resource.rb +16 -1
  50. data/lib/alchemy/test_support/essence_shared_examples.rb +0 -12
  51. data/lib/alchemy/upgrader/tasks/ingredients_migrator.rb +1 -1
  52. data/lib/alchemy/version.rb +1 -1
  53. data/lib/alchemy.rb +2 -4
  54. data/lib/generators/alchemy/base.rb +7 -3
  55. data/lib/generators/alchemy/install/install_generator.rb +4 -1
  56. data/package/src/image_loader.js +2 -2
  57. data/package/src/picture_editors.js +5 -5
  58. data/package.json +1 -1
  59. metadata +26 -24
@@ -28,7 +28,7 @@ module Alchemy
28
28
  large: "240x180",
29
29
  }.with_indifferent_access.freeze
30
30
 
31
- CONVERTIBLE_FILE_FORMATS = %w(gif jpg jpeg png).freeze
31
+ CONVERTIBLE_FILE_FORMATS = %w[gif jpg jpeg png webp].freeze
32
32
 
33
33
  TRANSFORMATION_OPTIONS = [
34
34
  :crop,
@@ -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 != "gif" && picture.image_file_format == "gif",
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 = []
@@ -18,7 +18,7 @@ module Alchemy
18
18
  class Site < BaseRecord
19
19
  # validations
20
20
  validates_presence_of :host
21
- validates_uniqueness_of :host
21
+ validates_uniqueness_of :host, case_sensitive: false
22
22
 
23
23
  # associations
24
24
  has_many :languages
@@ -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
- <% if element.expanded? || element.fixed? %>
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
- input_html: {class: 'alchemy_selectbox', autofocus: true} %>
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) %>
@@ -173,6 +173,7 @@ uploader:
173
173
  - gif
174
174
  - png
175
175
  - svg
176
+ - webp
176
177
 
177
178
  # === Link Target Options
178
179
  #
@@ -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
@@ -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(ERB.new(File.read(file)).result, YAML_WHITELIST_CLASSES, [], true) || {}
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
@@ -75,7 +75,8 @@ module Alchemy
75
75
 
76
76
  def load_alchemy_language_from_params
77
77
  if params[:locale].present?
78
- Language.find_by_code(params[:locale])
78
+ Language.find_by_code(params[:locale]) ||
79
+ raise(ActionController::RoutingError, "Language not found")
79
80
  end
80
81
  end
81
82
 
@@ -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
- YAML_WHITELIST_CLASSES,
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 = Alchemy::ElementsRepository.new(page_version.elements.available)
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]
@@ -37,7 +37,7 @@ module Alchemy
37
37
  end
38
38
  end
39
39
 
40
- initializer "alchemy.userstamp" do
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
@@ -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.available) }
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
 
@@ -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(ERB.new(File.read(layouts_file_path)).result, YAML_WHITELIST_CLASSES, [], true) || []
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
@@ -41,7 +41,7 @@ module Alchemy
41
41
  c.public? && !c.restricted?
42
42
  end
43
43
 
44
- can :read, Alchemy::Element, Alchemy::Element.available.not_restricted do |e|
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.available do |e|
71
+ can :read, Alchemy::Element, Alchemy::Element.published do |e|
72
72
  e.public?
73
73
  end
74
74
 
@@ -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
- }.delete_if { |_k, v| v.nil? }
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: { essence: :ingredient_association })
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|
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Alchemy
4
- VERSION = "6.0.0-rc7"
4
+ VERSION = "6.0.2"
5
5
 
6
6
  def self.version
7
7
  VERSION
data/lib/alchemy.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  require "alchemy/admin/preview_url"
4
4
 
5
5
  module Alchemy
6
- YAML_WHITELIST_CLASSES = %w(Symbol Date Regexp)
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 ||= begin
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!(/#{template_engine}$/, ext)
21
- source.gsub!(/#{template_engine}$/, ext)
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(ERB.new(File.read("#{Rails.root}/config/alchemy/#{name}")).result, YAML_WHITELIST_CLASSES, [], true)
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.load_file(app_root.join("config", "webpacker.yml"))[Rails.env]
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
@@ -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(`/admin/pictures/${this.pictureId}/url`, {
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
- this.thumbnailBackground.innerHTML = EMPTY_IMAGE
87
- this.image = this.container.querySelector("img")
88
- this.imageLoader = new ImageLoader(this.image)
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() {
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alchemy_cms/admin",
3
- "version": "6.0.0-rc7",
3
+ "version": "6.0.2",
4
4
  "description": "AlchemyCMS",
5
5
  "browser": "package/admin.js",
6
6
  "files": [