alchemy_cms 6.0.0.pre.rc7 → 6.0.2

Sign up to get free protection for your applications and to get access to all the features.
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": [