alchemy_cms 6.0.0 → 6.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +7 -0
  3. data/CHANGELOG.md +21 -0
  4. data/Gemfile +12 -0
  5. data/README.md +4 -3
  6. data/app/controllers/alchemy/admin/elements_controller.rb +18 -11
  7. data/app/controllers/alchemy/admin/pages_controller.rb +9 -4
  8. data/app/controllers/alchemy/api/base_controller.rb +4 -3
  9. data/app/controllers/alchemy/api/nodes_controller.rb +1 -0
  10. data/app/controllers/alchemy/base_controller.rb +7 -0
  11. data/app/controllers/alchemy/messages_controller.rb +0 -3
  12. data/app/controllers/alchemy/pages_controller.rb +0 -7
  13. data/app/helpers/alchemy/elements_helper.rb +17 -12
  14. data/app/models/alchemy/element.rb +13 -6
  15. data/app/models/alchemy/ingredient.rb +4 -0
  16. data/app/models/alchemy/ingredient_validator.rb +1 -1
  17. data/app/models/alchemy/language.rb +1 -1
  18. data/app/models/alchemy/page/page_elements.rb +2 -2
  19. data/app/models/alchemy/page/page_naming.rb +1 -1
  20. data/app/models/alchemy/page.rb +1 -1
  21. data/app/models/alchemy/picture/transformations.rb +2 -2
  22. data/app/models/alchemy/picture_variant.rb +3 -1
  23. data/app/models/alchemy/site.rb +1 -1
  24. data/app/views/alchemy/admin/clipboard/insert.js.erb +13 -0
  25. data/app/views/alchemy/admin/elements/_add_nested_element_form.html.erb +27 -0
  26. data/app/views/alchemy/admin/elements/_element.html.erb +1 -23
  27. data/app/views/alchemy/admin/elements/_form.html.erb +5 -1
  28. data/app/views/alchemy/essences/_essence_node_editor.html.erb +1 -1
  29. data/config/initializers/dragonfly.rb +2 -0
  30. data/lib/alchemy/config.rb +5 -1
  31. data/lib/alchemy/controller_actions.rb +2 -1
  32. data/lib/alchemy/dragonfly/processors/thumbnail.rb +27 -0
  33. data/lib/alchemy/element_definition.rb +2 -3
  34. data/lib/alchemy/elements_finder.rb +1 -2
  35. data/lib/alchemy/essence.rb +1 -1
  36. data/lib/alchemy/page_layout.rb +5 -1
  37. data/lib/alchemy/permissions.rb +2 -2
  38. data/lib/alchemy/version.rb +1 -1
  39. data/lib/alchemy.rb +2 -4
  40. data/lib/generators/alchemy/base.rb +7 -3
  41. data/lib/generators/alchemy/install/install_generator.rb +4 -1
  42. data/package.json +1 -1
  43. metadata +4 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f1db98328d1be1d96ee705e68f64b757f33d0997e4f622626ca86e4e42511079
4
- data.tar.gz: 563c9f02b864e170ac88a9d3f405c61b46da38fec41d0f4dfba0ff0c179407ba
3
+ metadata.gz: 0ccd47bae7272d0d8cb44b6d9c63cd8b72df01f086eff929819164d267af6bf1
4
+ data.tar.gz: d18a5f8105f242cef352e0c57d6d21743aedfdd75cc373bbf4205fd3d2cac5f1
5
5
  SHA512:
6
- metadata.gz: 524ab50512e3a7c377f3619f20c6ec812e1c12a933b94fb56d8bbc5b94b843cd9708451b77b96af8be7776bb0c733c90af549da76820109a0163a0338b8599dc
7
- data.tar.gz: 7c8346e5b07d35e2ef471cb90aff4fe8e62e2050ef13d03436cb2492e0cb5a6d9a2003eefa0f4f55d0a61cfdcbda48596788956908964c71333ca0ebdfa1565d
6
+ metadata.gz: 81b2e1e18d80a8cce6316286e91063e9363ded99c991de526f0155d98f9954a3986487bec834205847f364c883ecb8959777a55fbe14fdfdb9409f32818376fa
7
+ data.tar.gz: 39086e8836ff34ec6eb7abf76403b2f6a0515e6d46e6f77f76fe81b480f6a9cba65ae1ba3e0af390f79d8234d42f5ab218fd50ea14643e3ac8d89594de8bb1a2
@@ -16,10 +16,17 @@ jobs:
16
16
  - "2.6"
17
17
  - "2.7"
18
18
  - "3.0"
19
+ - "3.1"
19
20
  database:
20
21
  - mysql
21
22
  - postgresql
22
23
  exclude:
24
+ - rails: "6.0"
25
+ ruby: "3.1"
26
+ database: mysql
27
+ - rails: "6.0"
28
+ ruby: "3.1"
29
+ database: postgresql
23
30
  - rails: "7.0"
24
31
  ruby: "2.6"
25
32
  database: mysql
data/CHANGELOG.md CHANGED
@@ -1,3 +1,24 @@
1
+ ## 6.0.1 (2022-04-26)
2
+
3
+ - Allow passing a different partial to `render_element` [#2322](https://github.com/AlchemyCMS/alchemy_cms/pull/2322) ([mamhoff](https://github.com/mamhoff))
4
+ - Allow render_elements to take a block [#2321](https://github.com/AlchemyCMS/alchemy_cms/pull/2321) ([mamhoff](https://github.com/mamhoff))
5
+ - Remove old unused root_page ivar [#2320](https://github.com/AlchemyCMS/alchemy_cms/pull/2320) ([tvdeyen](https://github.com/tvdeyen))
6
+ - Raise on non-existing locale [#2319](https://github.com/AlchemyCMS/alchemy_cms/pull/2319) ([mamhoff](https://github.com/mamhoff))
7
+ - chore: Remove unnecessary puts from spec [#2318](https://github.com/AlchemyCMS/alchemy_cms/pull/2318) ([tvdeyen](https://github.com/tvdeyen))
8
+ - Fix gif resizing [#2315](https://github.com/AlchemyCMS/alchemy_cms/pull/2315) ([kulturbande](https://github.com/kulturbande))
9
+ - Refactor: Use page version's element repository rather than a new one [#2312](https://github.com/AlchemyCMS/alchemy_cms/pull/2312) ([mamhoff](https://github.com/mamhoff))
10
+ - Deprecate Alchemy::Element.available [#2309](https://github.com/AlchemyCMS/alchemy_cms/pull/2309) ([mamhoff](https://github.com/mamhoff))
11
+ - Explicitly set "store" for MariaDB [#2308](https://github.com/AlchemyCMS/alchemy_cms/pull/2308) ([mamhoff](https://github.com/mamhoff))
12
+ - Set preview mode earlier [#2307](https://github.com/AlchemyCMS/alchemy_cms/pull/2307) ([mamhoff](https://github.com/mamhoff))
13
+ - Fix updating the public_on date on persisted pages [#2305](https://github.com/AlchemyCMS/alchemy_cms/pull/2305) ([mamhoff](https://github.com/mamhoff))
14
+ - Allow opting out of Turbolinks in non-Alchemy controllers [#2302](https://github.com/AlchemyCMS/alchemy_cms/pull/2302) ([dssjoblom](https://github.com/dssjoblom))
15
+ - Explicitly validate uniqueness without case sensitivity [#2300](https://github.com/AlchemyCMS/alchemy_cms/pull/2300) ([mamhoff](https://github.com/mamhoff))
16
+ - Fix frozen string error when mixing template engines [#2299](https://github.com/AlchemyCMS/alchemy_cms/pull/2299) ([gr8bit](https://github.com/gr8bit))
17
+ - Do not flatten gifs if converted to webp [#2293](https://github.com/AlchemyCMS/alchemy_cms/pull/2293) ([tvdeyen](https://github.com/tvdeyen))
18
+ - Allow pasting into parent element with only one nested element type [#2292](https://github.com/AlchemyCMS/alchemy_cms/pull/2292) ([dbwinger](https://github.com/dbwinger))
19
+ - Restrict Node select to the site/language of the page being edited [#2277](https://github.com/AlchemyCMS/alchemy_cms/pull/2277) ([dbwinger](https://github.com/dbwinger))
20
+ - Add Ruby 3.1 support [#2229](https://github.com/AlchemyCMS/alchemy_cms/pull/2229) ([tvdeyen](https://github.com/tvdeyen))
21
+
1
22
  ## 6.0.0 (2022-04-11)
2
23
 
3
24
  - [ruby - main] Allow ransack version 3.0.1 [#2287](https://github.com/AlchemyCMS/alchemy_cms/pull/2287) ([depfu](https://github.com/apps/depfu))
data/Gemfile CHANGED
@@ -48,3 +48,15 @@ group :development, :test do
48
48
  gem "brakeman", require: false
49
49
  end
50
50
  end
51
+
52
+ # Ruby 3.1 split out the net-smtp gem
53
+ # Necessary until https://github.com/mikel/mail/pull/1439
54
+ # got merged and released.
55
+ if Gem.ruby_version >= Gem::Version.new("3.1.0")
56
+ if rails_version.to_s.match?(/6.1/)
57
+ # Rails 6.1 needs this as well
58
+ gem "net-pop", "~> 0.1.0", require: false
59
+ gem "net-imap", "~> 0.2.0", require: false
60
+ end
61
+ gem "net-smtp", "~> 0.3.0", require: false
62
+ end
data/README.md CHANGED
@@ -18,7 +18,7 @@ Alchemy is an open source CMS engine written in Ruby on Rails.
18
18
 
19
19
  Read more about Alchemy on the [website](https://alchemy-cms.com) and in the [guidelines](https://guides.alchemy-cms.com).
20
20
 
21
- **CAUTION: This main branch is a development branch that *can* contain bugs. For productive environments you should use the [current Ruby gem version](https://rubygems.org/gems/alchemy_cms), or the [latest stable branch (5.2-stable)](https://github.com/AlchemyCMS/alchemy_cms/tree/5.2-stable).**
21
+ **CAUTION: This main branch is a development branch that *can* contain bugs. For productive environments you should use the [current Ruby gem version](https://rubygems.org/gems/alchemy_cms), or the [latest stable branch (6.0-stable)](https://github.com/AlchemyCMS/alchemy_cms/tree/6.0-stable).**
22
22
 
23
23
 
24
24
  ## ✅ Features
@@ -27,6 +27,7 @@ Read more about Alchemy on the [website](https://alchemy-cms.com) and in the [gu
27
27
  - A rich RESTful API
28
28
  - Intuitive admin interface with live preview
29
29
  - Multi language and multi domain
30
+ - Page versioning
30
31
  - SEO friendly urls
31
32
  - User Access Control
32
33
  - Build in contact form mailer
@@ -51,9 +52,9 @@ or visit the existing demo at https://alchemy-demo.herokuapp.com
51
52
 
52
53
  ## 🚂 Rails Version
53
54
 
54
- **This version of AlchemyCMS runs with Rails 6.0**
55
+ **This version of AlchemyCMS runs with Rails 7.0, 6.1 and 6.0**
55
56
 
56
- * For a Rails 5.2 compatible version use the [`5.2-stable` branch](https://github.com/AlchemyCMS/alchemy_cms/tree/5.2-stable).
57
+ * For a Rails 5.2 compatible version use the [`5.3-stable` branch](https://github.com/AlchemyCMS/alchemy_cms/tree/5.3-stable).
57
58
  * For a Rails 5.0 or 5.1 compatible version use the [`4.5-stable` branch](https://github.com/AlchemyCMS/alchemy_cms/tree/4.5-stable).
58
59
  * For a Rails 4.2 compatible version use the [`3.6-stable` branch](https://github.com/AlchemyCMS/alchemy_cms/tree/3.6-stable).
59
60
  * For a Rails 4.0/4.1 compatible version use the [`3.1-stable` branch](https://github.com/AlchemyCMS/alchemy_cms/tree/3.1-stable).
@@ -12,6 +12,7 @@ module Alchemy
12
12
  elements = @page_version.elements.order(:position).includes(*element_includes)
13
13
  @elements = elements.not_nested.unfixed
14
14
  @fixed_elements = elements.not_nested.fixed
15
+ load_clipboard_items
15
16
  end
16
17
 
17
18
  def new
@@ -20,8 +21,7 @@ module Alchemy
20
21
  @parent_element = Element.find_by(id: params[:parent_element_id])
21
22
  @elements = @page.available_elements_within_current_scope(@parent_element)
22
23
  @element = @page_version.elements.build
23
- @clipboard = get_clipboard("elements")
24
- @clipboard_items = Element.all_from_clipboard_for_page(@clipboard, @page)
24
+ load_clipboard_items
25
25
  end
26
26
 
27
27
  # Creates a element as discribed in config/alchemy/elements.yml on page via AJAX.
@@ -44,8 +44,7 @@ module Alchemy
44
44
  else
45
45
  @element.page_version = @page_version
46
46
  @elements = @page.available_element_definitions
47
- @clipboard = get_clipboard("elements")
48
- @clipboard_items = Element.all_from_clipboard_for_page(@clipboard, @page)
47
+ load_clipboard_items
49
48
  render :new
50
49
  end
51
50
  end
@@ -126,19 +125,27 @@ module Alchemy
126
125
  @element = Element.find(params[:id])
127
126
  end
128
127
 
128
+ def load_clipboard_items
129
+ @clipboard = get_clipboard("elements")
130
+ @clipboard_items = Element.all_from_clipboard_for_page(@clipboard, @page)
131
+ end
132
+
129
133
  def element_from_clipboard
130
134
  @element_from_clipboard ||= begin
131
- @clipboard = get_clipboard("elements")
132
- @clipboard.detect { |item| item["id"].to_i == params[:paste_from_clipboard].to_i }
133
- end
135
+ @clipboard = get_clipboard("elements")
136
+ @clipboard.detect { |item| item["id"].to_i == params[:paste_from_clipboard].to_i }
137
+ end
134
138
  end
135
139
 
136
140
  def paste_element_from_clipboard
137
141
  @source_element = Element.find(element_from_clipboard["id"])
138
- element = Element.copy(@source_element, {
139
- parent_element_id: create_element_params[:parent_element_id],
140
- page_version_id: @page_version.id,
141
- })
142
+ element = Element.copy(
143
+ @source_element,
144
+ {
145
+ parent_element_id: create_element_params[:parent_element_id],
146
+ page_version_id: @page_version.id,
147
+ }
148
+ )
142
149
  if element_from_clipboard["action"] == "cut"
143
150
  @cut_element_id = @source_element.id
144
151
  @clipboard.delete_if { |item| item["id"] == @source_element.id.to_s }
@@ -23,9 +23,7 @@ module Alchemy
23
23
  before_action :set_root_page,
24
24
  only: [:index, :show, :order]
25
25
 
26
- before_action :run_on_page_layout_callbacks,
27
- if: :run_on_page_layout_callbacks?,
28
- only: [:show]
26
+ before_action :set_preview_mode, only: [:show]
29
27
 
30
28
  before_action :load_languages_and_layouts,
31
29
  unless: -> { @page_root },
@@ -35,6 +33,10 @@ module Alchemy
35
33
 
36
34
  before_action :set_page_version, only: [:show, :edit]
37
35
 
36
+ before_action :run_on_page_layout_callbacks,
37
+ if: :run_on_page_layout_callbacks?,
38
+ only: [:show]
39
+
38
40
  def index
39
41
  @query = @current_language.pages.contentpages.ransack(search_filter_params[:q])
40
42
 
@@ -64,7 +66,6 @@ module Alchemy
64
66
  # Used by page preview iframe in Page#edit view.
65
67
  #
66
68
  def show
67
- @preview_mode = true
68
69
  Page.current_preview = @page
69
70
  # Setting the locale to pages language, so the page content has it's correct translations.
70
71
  ::I18n.locale = @page.language.locale
@@ -397,6 +398,10 @@ module Alchemy
397
398
  @languages_with_page_tree = Language.on_current_site.with_root_page
398
399
  @page_layouts = PageLayout.layouts_for_select(@language.id)
399
400
  end
401
+
402
+ def set_preview_mode
403
+ @preview_mode = true
404
+ end
400
405
  end
401
406
  end
402
407
  end
@@ -5,17 +5,18 @@ module Alchemy
5
5
  layout false
6
6
  respond_to :json
7
7
 
8
- rescue_from CanCan::AccessDenied, with: :render_not_authorized
8
+ rescue_from CanCan::AccessDenied, with: :render_not_authorized
9
9
  rescue_from ActiveRecord::RecordNotFound, with: :render_not_found
10
+ rescue_from ActionController::RoutingError, with: :render_not_found
10
11
 
11
12
  private
12
13
 
13
14
  def render_not_authorized
14
- render json: {error: "Not authorized"}, status: 403
15
+ render json: { error: "Not authorized" }, status: 403
15
16
  end
16
17
 
17
18
  def render_not_found
18
- render json: {error: "Record not found"}, status: 404
19
+ render json: { error: "Record not found" }, status: 404
19
20
  end
20
21
  end
21
22
  end
@@ -8,6 +8,7 @@ module Alchemy
8
8
  def index
9
9
  @nodes = Node.all
10
10
  @nodes = @nodes.includes(:parent)
11
+ @nodes = @nodes.where(language_id: params[:language_id]) if params[:language_id]
11
12
  @nodes = @nodes.ransack(params[:filter]).result
12
13
 
13
14
  if params[:page]
@@ -9,6 +9,13 @@ module Alchemy
9
9
  include Alchemy::ControllerActions
10
10
  include Alchemy::Modules
11
11
 
12
+ # Include Turbolinks explicitly in case Alchemy is embedded into a
13
+ # larger application that doesn't work with Turbolinks. The app
14
+ # can then set config.turbolinks.auto_include = false so that
15
+ # Turbolinks is not included in the app controllers.
16
+ include Turbolinks::Controller
17
+ ::ActionDispatch::Assertions.include ::Turbolinks::Assertions
18
+
12
19
  protect_from_forgery
13
20
 
14
21
  before_action :mailer_set_url_options
@@ -62,7 +62,6 @@ module Alchemy
62
62
  end
63
63
 
64
64
  @page = @element.page
65
- @root_page = @page.get_language_root
66
65
  if @message.valid?
67
66
  MessagesMailer.contact_form_mail(@message, mail_to, mail_from, subject).deliver
68
67
  redirect_to_success_page
@@ -122,8 +121,6 @@ module Alchemy
122
121
  if @page.blank?
123
122
  raise "Page for page_layout #{mailer_config["page_layout_name"]} not found"
124
123
  end
125
-
126
- @root_page = @page.get_language_root
127
124
  end
128
125
 
129
126
  def message_params
@@ -32,9 +32,6 @@ module Alchemy
32
32
  if: :locale_prefix_missing?,
33
33
  only: [:index, :show]
34
34
 
35
- # We only need to set the +@root_page+ if we are sure that no more redirects happen.
36
- before_action :set_root_page, only: [:index, :show]
37
-
38
35
  # Page layout callbacks need to run after all other callbacks
39
36
  before_action :run_on_page_layout_callbacks,
40
37
  if: :run_on_page_layout_callbacks?,
@@ -199,10 +196,6 @@ module Alchemy
199
196
  end
200
197
  end
201
198
 
202
- def set_root_page
203
- @root_page ||= Language.current_root_page
204
- end
205
-
206
199
  def signup_required?
207
200
  if Alchemy.user_class.respond_to?(:admins)
208
201
  Alchemy.user_class.admins.empty? && @page.nil?
@@ -70,7 +70,7 @@ module Alchemy
70
70
  # A class instance that will return elements that get rendered.
71
71
  # Use this for your custom element loading logic in views.
72
72
  #
73
- def render_elements(options = {})
73
+ def render_elements(options = {}, &blk)
74
74
  options = {
75
75
  from_page: @page,
76
76
  render_format: "html",
@@ -86,11 +86,12 @@ module Alchemy
86
86
 
87
87
  elements = finder.elements(page_version: page_version)
88
88
 
89
- buff = []
90
- elements.each_with_index do |element, i|
91
- buff << render_element(element, options, i + 1)
92
- end
93
- buff.join(options[:separator]).html_safe
89
+ default_rendering = ->(element, i) { render_element(element, options, i + 1) }
90
+ if block_given?
91
+ elements.map.with_index(&blk)
92
+ else
93
+ elements.map.with_index(&default_rendering)
94
+ end.join(options[:separator]).html_safe
94
95
  end
95
96
 
96
97
  # This helper renders a {Alchemy::Element} view partial.
@@ -125,7 +126,7 @@ module Alchemy
125
126
  #
126
127
  # == Usage
127
128
  #
128
- # <%= render_element(Alchemy::Element.available.named(:headline).first) %>
129
+ # <%= render_element(Alchemy::Element.published.named(:headline).first) %>
129
130
  #
130
131
  # @param [Alchemy::Element] element
131
132
  # The element you want to render the view for
@@ -146,11 +147,15 @@ module Alchemy
146
147
 
147
148
  element.store_page(@page)
148
149
 
149
- render element, {
150
- element: element,
151
- counter: counter,
152
- options: options.except(:locals),
153
- }.merge(options[:locals] || {})
150
+ render(
151
+ partial: options[:partial] || element.to_partial_path,
152
+ object: element,
153
+ locals: {
154
+ element: element,
155
+ counter: counter,
156
+ options: options.except(:locals, :partial),
157
+ }.merge(options[:locals] || {}),
158
+ )
154
159
  rescue ActionView::MissingTemplate => e
155
160
  warning(%(
156
161
  Element view partial not found for #{element.name}.\n
@@ -70,7 +70,7 @@ module Alchemy
70
70
  dependent: :destroy
71
71
 
72
72
  has_many :nested_elements,
73
- -> { order(:position).available },
73
+ -> { order(:position).published },
74
74
  class_name: "Alchemy::Element",
75
75
  foreign_key: :parent_element_id,
76
76
  dependent: :destroy,
@@ -159,7 +159,7 @@ module Alchemy
159
159
  end
160
160
 
161
161
  def all_from_clipboard(clipboard)
162
- return [] if clipboard.nil?
162
+ return none if clipboard.nil?
163
163
 
164
164
  where(id: clipboard.collect { |e| e["id"] })
165
165
  end
@@ -167,12 +167,19 @@ module Alchemy
167
167
  # All elements in clipboard that could be placed on page
168
168
  #
169
169
  def all_from_clipboard_for_page(clipboard, page)
170
- return [] if clipboard.nil? || page.nil?
170
+ return none if clipboard.nil? || page.nil?
171
171
 
172
- all_from_clipboard(clipboard).select { |ce|
173
- page.available_element_names.include?(ce.name)
174
- }
172
+ all_from_clipboard(clipboard).where(name: page.available_element_names)
175
173
  end
174
+
175
+ # All elements in clipboard that could be placed as a child of `parent_element`
176
+ def all_from_clipboard_for_parent_element(clipboard, parent_element)
177
+ return none if clipboard.nil? || parent_element.nil?
178
+
179
+ all_from_clipboard(clipboard).where(name: parent_element.definition["nestable_elements"])
180
+ end
181
+
182
+ deprecate available: :published, deprecator: Alchemy::Deprecation
176
183
  end
177
184
 
178
185
  # Returns next public element from same page.
@@ -6,6 +6,10 @@ module Alchemy
6
6
 
7
7
  include Hints
8
8
 
9
+ # MariaDB needs to be told explicitly to use `data` as a JSON store. All other databases
10
+ # can do this natively.
11
+ store :data, coder: JSON
12
+
9
13
  self.table_name = "alchemy_ingredients"
10
14
 
11
15
  belongs_to :element, touch: true, class_name: "Alchemy::Element", inverse_of: :ingredients
@@ -88,7 +88,7 @@ module Alchemy
88
88
 
89
89
  def duplicates
90
90
  ingredient.class
91
- .joins(:element).merge(Alchemy::Element.available)
91
+ .joins(:element).merge(Alchemy::Element.published)
92
92
  .where(Alchemy::Element.table_name => { name: ingredient.element.name })
93
93
  .where(value: ingredient.value)
94
94
  .where.not(id: ingredient.id)
@@ -38,7 +38,7 @@ module Alchemy
38
38
 
39
39
  validates :language_code,
40
40
  presence: true,
41
- uniqueness: { scope: [:site_id, :country_code] },
41
+ uniqueness: { scope: [:site_id, :country_code], case_sensitive: false },
42
42
  format: { with: /\A[a-z]{2}\z/, if: -> { language_code.present? } }
43
43
 
44
44
  validates :country_code,
@@ -15,8 +15,8 @@ module Alchemy
15
15
  source: :elements,
16
16
  ) do
17
17
  has_many :all_elements
18
- has_many :elements, -> { not_nested.unfixed.available }
19
- has_many :fixed_elements, -> { fixed.available }
18
+ has_many :elements, -> { not_nested.unfixed.published }
19
+ has_many :fixed_elements, -> { fixed.published }
20
20
  end
21
21
 
22
22
  has_many :contents, through: :elements
@@ -15,7 +15,7 @@ module Alchemy
15
15
  validates :name,
16
16
  presence: true
17
17
  validates :urlname,
18
- uniqueness: { scope: [:language_id, :layoutpage], if: -> { urlname.present? } },
18
+ uniqueness: { scope: [:language_id, :layoutpage], if: -> { urlname.present? }, case_sensitive: false },
19
19
  exclusion: { in: RESERVED_URLNAMES },
20
20
  length: { minimum: 3, if: -> { urlname.present? } }
21
21
 
@@ -117,7 +117,7 @@ module Alchemy
117
117
  has_many :nodes, class_name: "Alchemy::Node", inverse_of: :page
118
118
  has_many :versions, class_name: "Alchemy::PageVersion", inverse_of: :page, dependent: :destroy
119
119
  has_one :draft_version, -> { drafts }, class_name: "Alchemy::PageVersion"
120
- has_one :public_version, -> { published }, class_name: "Alchemy::PageVersion"
120
+ has_one :public_version, -> { published }, class_name: "Alchemy::PageVersion", autosave: -> { persisted? }
121
121
 
122
122
  before_validation :set_language,
123
123
  if: -> { language.nil? }
@@ -30,7 +30,7 @@ module Alchemy
30
30
  # Returns the rendered resized image using imagemagick directly.
31
31
  #
32
32
  def resize(size, upsample = false)
33
- image_file.thumb(upsample ? size : "#{size}>")
33
+ image_file.thumbnail(upsample ? size : "#{size}>")
34
34
  end
35
35
 
36
36
  # Returns true if picture's width is greater than it's height
@@ -119,7 +119,7 @@ module Alchemy
119
119
  if is_smaller_than?(dimensions) && upsample == false
120
120
  dimensions = reduce_to_image(dimensions)
121
121
  end
122
- image_file.thumb("#{dimensions_to_string(dimensions)}#")
122
+ image_file.thumbnail("#{dimensions_to_string(dimensions)}#")
123
123
  end
124
124
 
125
125
  # Use imagemagick to custom crop an image. Uses -thumbnail for better performance when resizing.
@@ -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 -%>
@@ -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) %>
@@ -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]
@@ -41,7 +41,7 @@ module Alchemy #:nodoc:
41
41
  has_one :element, through: :content, class_name: "Alchemy::Element"
42
42
  has_one :page, through: :element, class_name: "Alchemy::Page"
43
43
 
44
- scope :available, -> { joins(:element).merge(Alchemy::Element.available) }
44
+ scope :available, -> { joins(:element).merge(Alchemy::Element.published) }
45
45
  scope :from_element, ->(name) { joins(:element).where(Element.table_name => { name: name }) }
46
46
 
47
47
  delegate :restricted?, to: :page, allow_nil: true
@@ -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
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Alchemy
4
- VERSION = "6.0.0"
4
+ VERSION = "6.0.1"
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
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alchemy_cms/admin",
3
- "version": "6.0.0",
3
+ "version": "6.0.1",
4
4
  "description": "AlchemyCMS",
5
5
  "browser": "package/admin.js",
6
6
  "files": [
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: alchemy_cms
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.0.0
4
+ version: 6.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thomas von Deyen
@@ -13,7 +13,7 @@ authors:
13
13
  autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
- date: 2022-04-11 00:00:00.000000000 Z
16
+ date: 2022-04-26 00:00:00.000000000 Z
17
17
  dependencies:
18
18
  - !ruby/object:Gem::Dependency
19
19
  name: actionmailer
@@ -1037,6 +1037,7 @@ files:
1037
1037
  - app/views/alchemy/admin/dashboard/help.html.erb
1038
1038
  - app/views/alchemy/admin/dashboard/index.html.erb
1039
1039
  - app/views/alchemy/admin/dashboard/info.html.erb
1040
+ - app/views/alchemy/admin/elements/_add_nested_element_form.html.erb
1040
1041
  - app/views/alchemy/admin/elements/_element.html.erb
1041
1042
  - app/views/alchemy/admin/elements/_footer.html.erb
1042
1043
  - app/views/alchemy/admin/elements/_form.html.erb
@@ -1303,6 +1304,7 @@ files:
1303
1304
  - lib/alchemy/deprecation.rb
1304
1305
  - lib/alchemy/dragonfly/processors/auto_orient.rb
1305
1306
  - lib/alchemy/dragonfly/processors/crop_resize.rb
1307
+ - lib/alchemy/dragonfly/processors/thumbnail.rb
1306
1308
  - lib/alchemy/element_definition.rb
1307
1309
  - lib/alchemy/elements_finder.rb
1308
1310
  - lib/alchemy/engine.rb