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.
- checksums.yaml +4 -4
- data/app/assets/builds/alchemy/admin.css +1 -1
- data/app/assets/builds/alchemy/alchemy_admin.min.js +1 -1
- data/app/assets/builds/alchemy/alchemy_admin.min.js.map +1 -1
- data/app/javascript/alchemy_admin/components/remote_select.js +11 -0
- data/app/models/alchemy/attachment.rb +9 -17
- data/app/models/alchemy/page.rb +11 -1
- data/app/models/alchemy/page_version.rb +0 -26
- data/app/models/concerns/alchemy/publishable.rb +1 -1
- data/app/services/alchemy/element_preloader.rb +1 -1
- data/app/stylesheets/alchemy/admin/notices.scss +9 -0
- data/app/views/alchemy/admin/pages/_table.html.erb +1 -1
- data/db/migrate/20251106150010_convert_select_value_for_multiple.rb +12 -5
- data/lib/alchemy/test_support/shared_publishable_examples.rb +37 -0
- data/lib/alchemy/version.rb +1 -1
- metadata +2 -2
|
@@ -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
|
-
#
|
|
53
|
-
#
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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.
|
data/app/models/alchemy/page.rb
CHANGED
|
@@ -615,7 +615,17 @@ module Alchemy
|
|
|
615
615
|
end
|
|
616
616
|
|
|
617
617
|
def check_descendants_for_menu_nodes
|
|
618
|
-
|
|
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
|
|
@@ -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
|
|
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
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
<%= render_icon "file-edit", size: "xl" %>
|
|
7
7
|
</sl-tooltip>
|
|
8
8
|
<% else %>
|
|
9
|
-
<%= render_icon "file
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
data/lib/alchemy/version.rb
CHANGED
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.
|
|
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.
|
|
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: []
|