alchemy_cms 8.2.3 → 8.2.4

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.
@@ -14,6 +14,12 @@ export class RemoteSelect extends AlchemyHTMLElement {
14
14
  url: { default: "" }
15
15
  }
16
16
 
17
+ // Select2 manages its own DOM after initialization, so attribute changes
18
+ // must not trigger the default re-render which would destroy the widget.
19
+ static get observedAttributes() {
20
+ return []
21
+ }
22
+
17
23
  async connected() {
18
24
  await setupSelectLocale()
19
25
 
@@ -34,6 +40,11 @@ export class RemoteSelect extends AlchemyHTMLElement {
34
40
  * @param {Event} event
35
41
  */
36
42
  onChange(event) {
43
+ // Update selection attribute so re-attaching the select2 component to
44
+ // the same input (e.g. after dragndrop) does not reset the selection.
45
+ if (event.added) {
46
+ this.setAttribute("selection", JSON.stringify(event.added))
47
+ }
37
48
  this.dispatchCustomEvent("RemoteSelect.Change", {
38
49
  removed: event.removed,
39
50
  added: event.added
@@ -49,24 +49,16 @@ module Alchemy
49
49
  # not tracked via the polymorphic +related_object+ association, so the
50
50
  # base scope cannot see them.
51
51
  #
52
- # Uses a correlated +NOT EXISTS+ subquery that builds the per-row LIKE
53
- # pattern with +Arel::Nodes::Concat+, which compiles to +||+ on
54
- # SQLite/PostgreSQL and +CONCAT()+ on MySQL.
52
+ # Extracts referenced attachment IDs from ingredient values via Ruby
53
+ # regex to stay database-agnostic.
55
54
  scope :deletable, -> do
56
- ingredients = Alchemy::Ingredient.arel_table
57
- pattern = Arel::Nodes::Concat.new(
58
- Arel::Nodes::Concat.new(
59
- Arel::Nodes.build_quoted("%/attachment/"),
60
- arel_table[:id]
61
- ),
62
- Arel::Nodes.build_quoted("/download%")
63
- )
64
- referenced = ingredients
65
- .project(1)
66
- .where(ingredients[:value].matches(pattern))
67
-
68
- where("#{table_name}.id NOT IN (#{RelatableResource::RELATED_INGREDIENTS_SUBQUERY})", type: name)
69
- .where.not(referenced.exists)
55
+ referenced_ids = Alchemy::Ingredient
56
+ .where("value LIKE '%/attachment/%/download%'")
57
+ .pluck(:value)
58
+ .flat_map { |v| v.scan(%r{/attachment/(\d+)/download}).flatten.map(&:to_i) }
59
+
60
+ scope = where("#{table_name}.id NOT IN (#{RelatableResource::RELATED_INGREDIENTS_SUBQUERY})", type: name)
61
+ referenced_ids.any? ? scope.where.not(id: referenced_ids) : scope
70
62
  end
71
63
 
72
64
  # We need to define this method here to have it available in the validations below.
@@ -615,7 +615,17 @@ module Alchemy
615
615
  end
616
616
 
617
617
  def check_descendants_for_menu_nodes
618
- pages_with_nodes = descendants.joins(:nodes).reorder("alchemy_pages.lft").distinct
618
+ # awesome_nested_set's before_destroy runs first and closes the gap left
619
+ # by this page, shifting the following siblings' lft/rgt leftward. For a
620
+ # leaf the next sibling then lands exactly on this page's lft, so the
621
+ # inclusive comparison the `descendants` helper uses (lft >= self.lft)
622
+ # would match it. Restrict to true descendants with strict bounds. Build
623
+ # on nested_set_scope so the acts_as_nested_set scope stays in sync.
624
+ pages_with_nodes = nested_set_scope
625
+ .where("alchemy_pages.lft > ? AND alchemy_pages.rgt < ?", lft, rgt)
626
+ .joins(:nodes)
627
+ .reorder("alchemy_pages.lft")
628
+ .distinct
619
629
  if pages_with_nodes.exists?
620
630
  errors.add(:descendants, :still_attached_to_nodes, page_names: pages_with_nodes.map(&:name).to_sentence)
621
631
  throw :abort
@@ -29,32 +29,6 @@ module Alchemy
29
29
 
30
30
  before_destroy :delete_elements
31
31
 
32
- # Determines if this version is public
33
- #
34
- # Takes the two timestamps +public_on+ and +public_until+
35
- # and returns true if the time given (+Time.current+ per default)
36
- # is in this timespan.
37
- #
38
- # @param time [DateTime] (Time.current)
39
- # @returns Boolean
40
- def public?(time = Current.preview_time)
41
- already_public_for?(time) && still_public_for?(time)
42
- end
43
-
44
- # Determines if this version is already public for given time
45
- # @param time [DateTime] (Current.preview_time)
46
- # @returns Boolean
47
- def already_public_for?(time = Current.preview_time)
48
- !public_on.nil? && public_on <= time
49
- end
50
-
51
- # Determines if this version is still public for given time
52
- # @param time [DateTime] (Current.preview_time)
53
- # @returns Boolean
54
- def still_public_for?(time = Current.preview_time)
55
- public_until.nil? || public_until >= time
56
- end
57
-
58
32
  def element_repository
59
33
  ElementsRepository.new(elements)
60
34
  end
@@ -46,7 +46,7 @@ module Alchemy
46
46
  #
47
47
  # @returns Boolean
48
48
  def publishable?
49
- !public_on.nil? && still_public_for?
49
+ !public_on.nil? && still_public_for?(at: Time.current)
50
50
  end
51
51
 
52
52
  # Determines if this record is already public for given time
@@ -61,7 +61,7 @@ module Alchemy
61
61
 
62
62
  elements_by_id.each_value do |element|
63
63
  children = elements_by_parent[element.id] || []
64
- children = children.sort_by(&:position)
64
+ children = children.sort_by { |c| c.position.to_i }
65
65
 
66
66
  # Manually set the association target
67
67
  element.association(:all_nested_elements).target = children
@@ -70,6 +70,15 @@ alchemy-message {
70
70
  font-size: var(--font-size_medium);
71
71
  }
72
72
 
73
+ h1,
74
+ h2,
75
+ h3,
76
+ p {
77
+ &:last-child {
78
+ margin-bottom: 0;
79
+ }
80
+ }
81
+
73
82
  a[href] {
74
83
  text-decoration-color: inherit;
75
84
  text-decoration-thickness: 1px;
@@ -6,7 +6,7 @@
6
6
  <%= render_icon "file-edit", size: "xl" %>
7
7
  </sl-tooltip>
8
8
  <% else %>
9
- <%= render_icon "file-edit", size: "xl" %>
9
+ <%= render_icon "file", size: "xl" %>
10
10
  <% end %>
11
11
  <% else %>
12
12
  <sl-tooltip class="like-hint-tooltip" content="<%= Alchemy.t("Your user role does not allow you to edit this page") %>" placement="bottom-start">
@@ -1,11 +1,18 @@
1
1
  class ConvertSelectValueForMultiple < ActiveRecord::Migration[7.1]
2
2
  def up
3
3
  say_with_time "Converting Alchemy::Ingredients::Select values to multiple" do
4
- update <<-SQL.squish
5
- UPDATE alchemy_ingredients
6
- SET value = '["' || value || '"]'
7
- WHERE type = 'Alchemy::Ingredients::Select' AND value NOT LIKE '["%"]';
8
- SQL
4
+ Alchemy::Ingredients::Select
5
+ .where.not("value LIKE ?", '["%')
6
+ .update_all(
7
+ Arel.sql(
8
+ case ActiveRecord::Base.connection.adapter_name
9
+ when /mysql|mariadb/i
10
+ "value = CONCAT('[\"', value, '\"]')"
11
+ else
12
+ "value = '[\"' || value || '\"]'"
13
+ end
14
+ )
15
+ )
9
16
  end
10
17
  end
11
18
  end
@@ -212,4 +212,41 @@ RSpec.shared_examples_for "being publishable" do |factory_name|
212
212
  end
213
213
  end
214
214
  end
215
+
216
+ describe "#publishable?" do
217
+ context "when public_on is nil" do
218
+ let(:page_version) { build(factory_name, public_on: nil) }
219
+
220
+ it { expect(page_version.publishable?).to be(false) }
221
+ end
222
+
223
+ context "when public_on is set and public_until is nil" do
224
+ let(:page_version) { build(factory_name, public_on: Time.current) }
225
+
226
+ it { expect(page_version.publishable?).to be(true) }
227
+ end
228
+
229
+ context "when public_on is set and public_until is in the past" do
230
+ let(:page_version) do
231
+ build(factory_name,
232
+ public_on: Time.current - 2.days,
233
+ public_until: Time.current - 1.day)
234
+ end
235
+
236
+ it { expect(page_version.publishable?).to be(false) }
237
+ end
238
+
239
+ context "when Current.preview_time is set to a future time" do
240
+ let(:page_version) do
241
+ build(factory_name,
242
+ public_on: Time.current - 1.day,
243
+ public_until: Time.current + 1.day)
244
+ end
245
+
246
+ it "uses Time.current instead of the preview_time" do
247
+ Alchemy::Current.preview_time = Time.current + 1.week
248
+ expect(page_version.publishable?).to be(true)
249
+ end
250
+ end
251
+ end
215
252
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Alchemy
4
- VERSION = "8.2.3"
4
+ VERSION = "8.2.4"
5
5
 
6
6
  def self.version
7
7
  VERSION
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: 8.2.3
4
+ version: 8.2.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thomas von Deyen
@@ -1490,7 +1490,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
1490
1490
  version: '0'
1491
1491
  requirements:
1492
1492
  - ImageMagick (libmagick), v6.6 or greater.
1493
- rubygems_version: 4.0.6
1493
+ rubygems_version: 4.0.10
1494
1494
  specification_version: 4
1495
1495
  summary: A powerful, userfriendly and flexible CMS for Rails
1496
1496
  test_files: []