alchemy_cms 8.3.2 → 8.3.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/stylesheets/alchemy/admin/forms.scss +8 -7
- data/app/views/alchemy/admin/pictures/index.html.erb +2 -1
- data/app/views/alchemy/admin/styleguide/index.html.erb +10 -1
- data/config/importmap.rb +12 -6
- data/db/migrate/20260702090038_add_index_to_elements_pages_join_table.rb +14 -0
- data/lib/alchemy/tasks/reorient_pictures.rb +119 -0
- data/lib/alchemy/version.rb +1 -1
- data/lib/tasks/alchemy/pictures.rake +23 -0
- metadata +5 -2
|
@@ -66,7 +66,7 @@ form {
|
|
|
66
66
|
&.boolean {
|
|
67
67
|
height: var(--form-field-height);
|
|
68
68
|
|
|
69
|
-
input,
|
|
69
|
+
input[type="checkbox"],
|
|
70
70
|
label {
|
|
71
71
|
grid-column: 2;
|
|
72
72
|
grid-row: 1;
|
|
@@ -75,23 +75,24 @@ form {
|
|
|
75
75
|
margin: var(--form-field-margin);
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
input {
|
|
78
|
+
input[type="checkbox"] {
|
|
79
79
|
margin-left: var(--spacing-0);
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
label {
|
|
82
|
+
input[type="checkbox"] + label {
|
|
83
83
|
margin-left: var(--spacing-6);
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
label.checkbox
|
|
87
|
+
label.checkbox,
|
|
88
|
+
label.boolean {
|
|
88
89
|
display: flex;
|
|
89
90
|
align-items: baseline;
|
|
90
91
|
gap: var(--spacing-1);
|
|
91
92
|
margin: var(--spacing-1) 0;
|
|
92
93
|
width: max-content;
|
|
93
94
|
|
|
94
|
-
input {
|
|
95
|
+
input[type="checkbox"] {
|
|
95
96
|
align-self: center;
|
|
96
97
|
margin: 0 var(--spacing-0) 0 1px;
|
|
97
98
|
}
|
|
@@ -278,12 +279,12 @@ form {
|
|
|
278
279
|
label.checkbox {
|
|
279
280
|
display: flex;
|
|
280
281
|
align-items: center;
|
|
281
|
-
gap: var(--spacing-
|
|
282
|
+
gap: var(--spacing-1);
|
|
282
283
|
width: max-content;
|
|
283
284
|
grid-column: 2;
|
|
284
285
|
grid-row: 1;
|
|
285
286
|
|
|
286
|
-
input {
|
|
287
|
+
input[type="checkbox"] {
|
|
287
288
|
margin-left: var(--spacing-0);
|
|
288
289
|
}
|
|
289
290
|
}
|
|
@@ -77,7 +77,8 @@
|
|
|
77
77
|
import { on } from "alchemy_admin/utils/events";
|
|
78
78
|
|
|
79
79
|
pictureSelector();
|
|
80
|
-
|
|
80
|
+
// A keyboard activated click targets the link, not the thumbnail inside it.
|
|
81
|
+
on("click", "#picture_archive", ".picture_thumbnail > a", (event) => {
|
|
81
82
|
const url = event.target.closest("a")?.href;
|
|
82
83
|
const overlay = new ImageOverlay(url, {
|
|
83
84
|
size: `${window.clientWidth}x${window.clientHeight}`,
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
</alchemy-char-counter>
|
|
90
90
|
|
|
91
91
|
<div class="input check_boxes">
|
|
92
|
-
<label class="control-label">Check
|
|
92
|
+
<label class="control-label">Check Box Group</label>
|
|
93
93
|
<div class="control_group">
|
|
94
94
|
<label class="checkbox">
|
|
95
95
|
<input type="checkbox" value="1" checked>
|
|
@@ -101,6 +101,15 @@
|
|
|
101
101
|
</label>
|
|
102
102
|
</div>
|
|
103
103
|
</div>
|
|
104
|
+
|
|
105
|
+
<div class="input boolean">
|
|
106
|
+
<input type="hidden" value="0">
|
|
107
|
+
<label class="boolean control-label">
|
|
108
|
+
<input class="boolean" type="checkbox" value="1">
|
|
109
|
+
Single Checkbox
|
|
110
|
+
</label>
|
|
111
|
+
</div>
|
|
112
|
+
|
|
104
113
|
<alchemy-char-counter max-chars="160">
|
|
105
114
|
<div class="input text">
|
|
106
115
|
<label class="text control-label" for="textarea_with_counter">Textarea with char counter</label>
|
data/config/importmap.rb
CHANGED
|
@@ -15,12 +15,18 @@ pin "tinymce", to: "tinymce.min.js", preload: true
|
|
|
15
15
|
pin "tom-select", to: "tom-select.min.js", preload: true
|
|
16
16
|
|
|
17
17
|
pin "alchemy_admin", to: "alchemy/alchemy_admin.min.js", preload: true
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
pin "alchemy_admin/components/
|
|
23
|
-
pin "alchemy_admin/components/
|
|
18
|
+
# Individually importable admin components. These resolve to their source
|
|
19
|
+
# files (not the bundle) so host engines can import a single component without
|
|
20
|
+
# loading and executing the whole admin bundle. Not preloaded, so they add no
|
|
21
|
+
# fetches to normal admin page loads (which import the bundle instead).
|
|
22
|
+
pin "alchemy_admin/components/page_select"
|
|
23
|
+
pin "alchemy_admin/components/attachment_select"
|
|
24
|
+
pin "alchemy_admin/components/element_select"
|
|
25
|
+
pin "alchemy_admin/components/tags_autocomplete"
|
|
26
|
+
pin "alchemy_admin/components/tinymce"
|
|
27
|
+
pin "alchemy_admin/components/remote_select"
|
|
28
|
+
pin "alchemy_admin/components/select"
|
|
29
|
+
pin "alchemy_admin/i18n"
|
|
24
30
|
pin "alchemy_admin/image_cropper", to: "alchemy/alchemy_admin.min.js"
|
|
25
31
|
pin "alchemy_admin/image_overlay", to: "alchemy/alchemy_admin.min.js"
|
|
26
32
|
pin "alchemy_admin/picture_selector", to: "alchemy/alchemy_admin.min.js"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
class AddIndexToElementsPagesJoinTable < ActiveRecord::Migration[7.2]
|
|
2
|
+
disable_ddl_transaction! if connection.adapter_name.match?(/postgres/i)
|
|
3
|
+
|
|
4
|
+
def change
|
|
5
|
+
add_index :alchemy_elements_alchemy_pages, :element_id, if_not_exists: true, algorithm: algorithm
|
|
6
|
+
add_index :alchemy_elements_alchemy_pages, :page_id, if_not_exists: true, algorithm: algorithm
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
def algorithm
|
|
12
|
+
connection.adapter_name.match?(/postgres/i) ? :concurrently : nil
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "alchemy/shell"
|
|
4
|
+
|
|
5
|
+
module Alchemy
|
|
6
|
+
# Bakes the EXIF orientation into legacy picture masters.
|
|
7
|
+
#
|
|
8
|
+
# Masters uploaded before auto orientation landed still carry an EXIF
|
|
9
|
+
# orientation tag. Clients honor it for the original image, but not once it is
|
|
10
|
+
# converted to WebP, which renders such variants rotated in Chrome and Firefox.
|
|
11
|
+
class ReorientPictures
|
|
12
|
+
extend Shell
|
|
13
|
+
|
|
14
|
+
# Also covers "" and "Undefined", i.e. no orientation tag at all.
|
|
15
|
+
UPRIGHT_ORIENTATIONS = ["", "Undefined", "TopLeft"].freeze
|
|
16
|
+
|
|
17
|
+
class << self
|
|
18
|
+
def call(dry_run: false, picture_ids: nil)
|
|
19
|
+
unless Alchemy.storage_adapter.dragonfly?
|
|
20
|
+
log "Auto orientation only applies to the Dragonfly storage adapter", :skip
|
|
21
|
+
return []
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
log "Dry run - no changes will be made", :message if dry_run
|
|
25
|
+
|
|
26
|
+
affected = []
|
|
27
|
+
skipped = 0
|
|
28
|
+
# Dragonfly and ActiveJob log every shell command and enqueued job, which
|
|
29
|
+
# floods the output. Silence them for the duration of the run.
|
|
30
|
+
with_quiet_logging do
|
|
31
|
+
# "." upright, "f" needs reorienting (report), "o" reoriented,
|
|
32
|
+
# "s" master file missing.
|
|
33
|
+
scope(picture_ids).find_each do |picture|
|
|
34
|
+
next unless picture.has_convertible_format?
|
|
35
|
+
|
|
36
|
+
if UPRIGHT_ORIENTATIONS.include?(orientation_of(picture))
|
|
37
|
+
print_progress "."
|
|
38
|
+
next
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
affected << picture.id
|
|
42
|
+
reorient!(picture) unless dry_run
|
|
43
|
+
print_progress(dry_run ? "f" : "o")
|
|
44
|
+
rescue *Array(Alchemy.storage_adapter.rescuable_errors)
|
|
45
|
+
affected.delete(picture.id)
|
|
46
|
+
skipped += 1
|
|
47
|
+
print_progress "s"
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
finish_progress
|
|
51
|
+
|
|
52
|
+
log "#{dry_run ? "Found" : "Reoriented"} #{affected.size} picture(s) that need reorienting"
|
|
53
|
+
log "Skipped #{skipped} picture(s) whose master file is missing", :error if skipped.positive?
|
|
54
|
+
if dry_run && affected.any?
|
|
55
|
+
log "Reorient them with: rake alchemy:pictures:reorient PICTURE_IDS=#{affected.join(",")}"
|
|
56
|
+
end
|
|
57
|
+
affected
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def report(picture_ids: nil)
|
|
61
|
+
call(dry_run: true, picture_ids:)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def scope(picture_ids)
|
|
67
|
+
picture_ids.present? ? Alchemy::Picture.where(id: picture_ids) : Alchemy::Picture.all
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def reorient!(picture)
|
|
71
|
+
picture.image_file.auto_orient!
|
|
72
|
+
# Saving the baked master rewrites the element and page caches that embed it.
|
|
73
|
+
picture.save!
|
|
74
|
+
# The variant signature does not change, so drop the stale variants
|
|
75
|
+
# before regenerating them from the now upright master.
|
|
76
|
+
picture.thumbs.destroy_all
|
|
77
|
+
regenerate_variants(picture)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Rerenders the frontend variants in the sizes the ingredients use, so the
|
|
81
|
+
# corrected images are served right away instead of on first request.
|
|
82
|
+
def regenerate_variants(picture)
|
|
83
|
+
picture.related_ingredients.each do |ingredient|
|
|
84
|
+
ingredient.picture_url
|
|
85
|
+
ingredient.settings.fetch(:srcset, []).each { |src| ingredient.picture_url(src) }
|
|
86
|
+
rescue => e
|
|
87
|
+
log "Could not regenerate variants for ingredient ##{ingredient.id}: #{e.message}", :error
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def orientation_of(picture)
|
|
92
|
+
picture.image_file.identify("-format '%[orientation]'").to_s.strip
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def with_quiet_logging
|
|
96
|
+
null_logger = ::Logger.new(IO::NULL)
|
|
97
|
+
dragonfly_logger = ::Dragonfly.logger
|
|
98
|
+
active_job_logger = ActiveJob::Base.logger
|
|
99
|
+
::Dragonfly.logger = null_logger
|
|
100
|
+
ActiveJob::Base.logger = null_logger
|
|
101
|
+
yield
|
|
102
|
+
ensure
|
|
103
|
+
::Dragonfly.logger = dragonfly_logger
|
|
104
|
+
ActiveJob::Base.logger = active_job_logger
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def print_progress(char)
|
|
108
|
+
return if Alchemy::Shell.silenced?
|
|
109
|
+
|
|
110
|
+
print char
|
|
111
|
+
$stdout.flush
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def finish_progress
|
|
115
|
+
puts unless Alchemy::Shell.silenced?
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
data/lib/alchemy/version.rb
CHANGED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "alchemy/tasks/reorient_pictures"
|
|
4
|
+
|
|
5
|
+
namespace :alchemy do
|
|
6
|
+
namespace :pictures do
|
|
7
|
+
# Limit any of the tasks below to a subset of pictures:
|
|
8
|
+
# rake alchemy:pictures:report PICTURE_IDS=1,2,3
|
|
9
|
+
def alchemy_picture_ids
|
|
10
|
+
ENV["PICTURE_IDS"]&.split(",")&.map(&:strip).presence
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
desc "Reports picture masters that still carry a non upright EXIF orientation (Dragonfly only)."
|
|
14
|
+
task report: [:environment] do
|
|
15
|
+
Alchemy::ReorientPictures.report(picture_ids: alchemy_picture_ids)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
desc "Bakes the EXIF orientation into existing picture masters (Dragonfly only)."
|
|
19
|
+
task reorient: [:environment] do
|
|
20
|
+
Alchemy::ReorientPictures.call(picture_ids: alchemy_picture_ids)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
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.3.
|
|
4
|
+
version: 8.3.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Thomas von Deyen
|
|
@@ -1320,6 +1320,7 @@ files:
|
|
|
1320
1320
|
- db/migrate/20260102121232_add_metadata_to_page_versions.rb
|
|
1321
1321
|
- db/migrate/20260115164704_add_publication_timestamps_to_alchemy_elements.rb
|
|
1322
1322
|
- db/migrate/20260115164705_add_index_to_element_publication_timestamps.rb
|
|
1323
|
+
- db/migrate/20260702090038_add_index_to_elements_pages_join_table.rb
|
|
1323
1324
|
- lib/alchemy.rb
|
|
1324
1325
|
- lib/alchemy/ability_helper.rb
|
|
1325
1326
|
- lib/alchemy/admin/locale.rb
|
|
@@ -1381,6 +1382,7 @@ files:
|
|
|
1381
1382
|
- lib/alchemy/sprockets/skip_builds_compression.rb
|
|
1382
1383
|
- lib/alchemy/svg_scrubber.rb
|
|
1383
1384
|
- lib/alchemy/taggable.rb
|
|
1385
|
+
- lib/alchemy/tasks/reorient_pictures.rb
|
|
1384
1386
|
- lib/alchemy/tasks/tidy.rb
|
|
1385
1387
|
- lib/alchemy/tasks/usage.rb
|
|
1386
1388
|
- lib/alchemy/test_support.rb
|
|
@@ -1461,6 +1463,7 @@ files:
|
|
|
1461
1463
|
- lib/tasks/alchemy/assets.rake
|
|
1462
1464
|
- lib/tasks/alchemy/db.rake
|
|
1463
1465
|
- lib/tasks/alchemy/install.rake
|
|
1466
|
+
- lib/tasks/alchemy/pictures.rake
|
|
1464
1467
|
- lib/tasks/alchemy/sitemap.rake
|
|
1465
1468
|
- lib/tasks/alchemy/thumbnails.rake
|
|
1466
1469
|
- lib/tasks/alchemy/tidy.rake
|
|
@@ -1529,7 +1532,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
1529
1532
|
version: '0'
|
|
1530
1533
|
requirements:
|
|
1531
1534
|
- ImageMagick (libmagick), v6.6 or greater.
|
|
1532
|
-
rubygems_version: 4.0.
|
|
1535
|
+
rubygems_version: 4.0.16
|
|
1533
1536
|
specification_version: 4
|
|
1534
1537
|
summary: A powerful, userfriendly and flexible CMS for Rails
|
|
1535
1538
|
test_files: []
|