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.
@@ -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-0);
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
- on("click", "#picture_archive", ".thumbnail_background", (event) => {
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 Boxes</label>
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
- pin "alchemy_admin/components/remote_select", to: "alchemy/alchemy_admin.min.js"
19
- pin "alchemy_admin/components/page_select", to: "alchemy/alchemy_admin.min.js"
20
- pin "alchemy_admin/components/attachment_select", to: "alchemy/alchemy_admin.min.js"
21
- pin "alchemy_admin/components/element_select", to: "alchemy/alchemy_admin.min.js"
22
- pin "alchemy_admin/components/tags_autocomplete", to: "alchemy/alchemy_admin.min.js"
23
- pin "alchemy_admin/components/tinymce", to: "alchemy/alchemy_admin.min.js"
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
@@ -3,7 +3,7 @@
3
3
  module Alchemy
4
4
  extend self
5
5
 
6
- VERSION = "8.3.2"
6
+ VERSION = "8.3.4"
7
7
 
8
8
  def version
9
9
  VERSION
@@ -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.2
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.10
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: []