alchemy_cms 8.4.1 → 8.4.2

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.
@@ -11,9 +11,26 @@ module Alchemy
11
11
  </div>
12
12
  ERB
13
13
 
14
+ # Entries without a position that were inserted between Alchemy's own
15
+ # entries are listed after the positioned entry preceding them, so
16
+ # +Alchemy.config.menu.insert+ can replace the deprecated position.
17
+ # Appended entries without a position are listed last.
14
18
  def items
15
- positioned, unpositioned = Alchemy.config.menu.partition(&:position)
16
- positioned.sort_by(&:position) + unpositioned
19
+ menu = Alchemy.config.menu.to_a
20
+ core_names = Alchemy::Configurations::Menu::DEFAULT.map { |entry| entry[:name] }
21
+ last_core_index = menu.rindex { |item| core_names.include?(item.name) } || -1
22
+ preceding_position = 0
23
+ menu.each_with_index.map do |item, index|
24
+ preceding_position = item.position if item.position
25
+ key = if item.position
26
+ [item.position, 0, index]
27
+ elsif index < last_core_index
28
+ [preceding_position, 1, index]
29
+ else
30
+ [Float::INFINITY, 0, index]
31
+ end
32
+ [item, key]
33
+ end.sort_by(&:last).map(&:first)
17
34
  end
18
35
  end
19
36
  end
@@ -32,6 +32,8 @@ class ElementsWindow extends HTMLElement {
32
32
  }
33
33
 
34
34
  #onWindowMessage = (event) => {
35
+ if (event.source !== this.previewWindow?.contentWindow) return
36
+
35
37
  const data = event.data
36
38
  if (data?.message == "Alchemy.focusElementEditor") {
37
39
  const element = document.getElementById(`element_${data.element_id}`)
@@ -5,6 +5,7 @@ class PreviewWindow extends HTMLIFrameElement {
5
5
  #afterLoad
6
6
  #reloadIcon
7
7
  #loadTimeout
8
+ #previewOrigin = null
8
9
 
9
10
  handleEvent(evt) {
10
11
  if (evt.type === "load") {
@@ -15,7 +16,10 @@ class PreviewWindow extends HTMLIFrameElement {
15
16
  }
16
17
 
17
18
  #onPreviewReady = (event) => {
19
+ if (event.source !== this.contentWindow) return
20
+
18
21
  if (event.data.message === "Alchemy.previewReady") {
22
+ this.#previewOrigin = event.origin
19
23
  this.#clearLoadTimeout()
20
24
  this.#stopSpinner()
21
25
  // Deliberately does not resolve #afterLoad: previewReady fires before the
@@ -47,7 +51,9 @@ class PreviewWindow extends HTMLIFrameElement {
47
51
  }
48
52
 
49
53
  postMessage(data) {
50
- this.contentWindow.postMessage(data, "*")
54
+ if (this.#previewOrigin) {
55
+ this.contentWindow.postMessage(data, this.#previewOrigin)
56
+ }
51
57
  }
52
58
 
53
59
  resize(width) {
@@ -55,6 +61,7 @@ class PreviewWindow extends HTMLIFrameElement {
55
61
  }
56
62
 
57
63
  refresh(url) {
64
+ this.#previewOrigin = null
58
65
  this.#startSpinner()
59
66
 
60
67
  if (url) {
@@ -24,14 +24,38 @@ module Alchemy
24
24
  option :nested_actions, :collection, item_type: :string
25
25
  option :nested, :collection, item_type: :configuration, config_class: MenuItem
26
26
 
27
+ alias_method :set_position, :position=
28
+ private :set_position
29
+
30
+ # Alchemy's own entries still need their positions for sorting,
31
+ # so only positions of other entries are deprecated.
32
+ def position=(value)
33
+ if value && Menu::DEFAULT.none? { |entry| entry[:name] == name }
34
+ Alchemy::Deprecation.warn(
35
+ "The `position` of menu entries is deprecated and will be removed in Alchemy 9.0, " \
36
+ "which lists the entries in the order of `Alchemy.config.menu`. " \
37
+ "Use `Alchemy.config.menu.insert` to place the #{name.inspect} entry instead."
38
+ )
39
+ end
40
+ set_position(value)
41
+ end
42
+
27
43
  def self.from_legacy_hash(hash)
28
44
  hash = hash.to_h.deep_stringify_keys
29
- new(
45
+ item = new(
30
46
  from_legacy_navigation(hash["navigation"] || {}, engine_name: hash["engine_name"]).merge(
31
- "name" => hash["name"],
32
- "position" => hash["position"]
47
+ "name" => hash["name"]
33
48
  ).compact
34
49
  )
50
+ # +register_module+ is deprecated already
51
+ item.send(:set_position, hash["position"]) if hash["position"]
52
+ item
53
+ end
54
+
55
+ # Positions sort entries around the default menu's entries, which use
56
+ # the positions 1 to 7, so +4.1+ maps to the index before Sites.
57
+ def self.index_for_legacy_position(position)
58
+ [position.ceil - 1, 0].max
35
59
  end
36
60
 
37
61
  def self.from_legacy_navigation(navigation, engine_name:)
@@ -82,8 +106,8 @@ module Alchemy
82
106
  current_path = view_context.request.path
83
107
  path_matches = if match_path
84
108
  match_path.match?(current_path)
85
- elsif (own_path = local_path(view_context))
86
- current_path == own_path || current_path.start_with?("#{own_path.chomp("/")}/")
109
+ elsif (own_path = local_path(view_context)&.chomp("/"))
110
+ current_path.chomp("/") == own_path || current_path.start_with?("#{own_path}/")
87
111
  end
88
112
 
89
113
  path_matches || [self, *nested].any? do |entry|
@@ -169,10 +169,11 @@ module Alchemy
169
169
  list = ->(files) { files.map { |file| " - #{Pathname.new(file).relative_path_from(Rails.root)}" }.join("\n") }
170
170
 
171
171
  if rewritten.any?
172
- todo(<<~TODO.strip, "`register_module` calls were rewritten to `Alchemy.config.menu.add`")
172
+ todo(<<~TODO.strip, "`register_module` calls were rewritten to `Alchemy.config.menu`")
173
173
  `Alchemy::Modules.register_module` is deprecated and will be removed in
174
174
  Alchemy 9.0. We rewrote its calls in the following initializers to
175
- `Alchemy.config.menu.add`:
175
+ `Alchemy.config.menu.add`, or to `Alchemy.config.menu.insert` if they
176
+ had a position between Alchemy's own entries:
176
177
 
177
178
  #{list.call(rewritten)}
178
179
 
@@ -212,7 +213,9 @@ module Alchemy
212
213
  the former `navigation` hash now live on the entry itself, its `name`
213
214
  is now `title`, `sub_navigation` is now `children`, `icon-style` is now
214
215
  `icon_style`, `data: {turbo: false}` is now `turbo: false`, and `params`
215
- is no longer supported.
216
+ is no longer supported. Instead of a `position`, use
217
+ `config.menu.insert(index, ...)` to place an entry between Alchemy's
218
+ own entries, e.g. `4` to place it between Languages and Sites.
216
219
 
217
220
  Call `config.menu.add` once at boot and not inside `to_prepare`, as it
218
221
  does not skip entries that are already present like `register_module`
@@ -3,7 +3,8 @@
3
3
  module Alchemy
4
4
  class Upgrader
5
5
  # Rewrites +Alchemy::Modules.register_module+ calls with a static literal
6
- # argument into +Alchemy.config.menu.add+ calls.
6
+ # argument into +Alchemy.config.menu.add+ calls, or +Alchemy.config.menu.insert+
7
+ # calls if they have a position within the default menu.
7
8
  #
8
9
  # Calls inside a +to_prepare+ block are guarded by the entry's name, because
9
10
  # +Alchemy.config.menu.add+ does not skip entries that are already present.
@@ -15,7 +16,7 @@ module Alchemy
15
16
  RECEIVERS = ["Alchemy::Modules", "::Alchemy::Modules"].freeze
16
17
 
17
18
  KEYS = %w[
18
- name title engine_name position icon icon_style image inline_image turbo
19
+ name title engine_name icon icon_style image inline_image turbo
19
20
  controller action nested_controllers nested_actions children nested
20
21
  ].freeze
21
22
 
@@ -70,24 +71,36 @@ module Alchemy
70
71
  return unless arguments.first.is_a?(Prism::HashNode) || arguments.first.is_a?(Prism::KeywordHashNode)
71
72
  return unless heredocs_inside?(arguments.first, call, source)
72
73
 
73
- item = Alchemy::Configurations::MenuItem.from_legacy_hash(literal(arguments.first))
74
+ definition = literal(arguments.first)
75
+ item = Alchemy::Configurations::MenuItem.from_legacy_hash(definition)
74
76
  return if in_to_prepare && item.name.blank?
75
77
 
78
+ index = menu_index(definition["position"])
79
+
76
80
  indent = " " * call.location.start_column
77
81
  if in_to_prepare
78
82
  <<~RUBY.chomp
79
83
  unless Alchemy.config.menu.any? { |item| item.name == #{item.name.inspect} }
80
- #{indent} #{menu_add(item, "#{indent} ")}
84
+ #{indent} #{menu_add(item, "#{indent} ", index)}
81
85
  #{indent}end
82
86
  RUBY
83
87
  else
84
- menu_add(item, indent)
88
+ menu_add(item, indent, index)
85
89
  end
86
90
  rescue NonLiteralError, Alchemy::Configuration::ConfigurationError
87
91
  nil
88
92
  end
89
93
 
90
- def menu_add(item, indent)
94
+ # @return [Integer, nil] The index to insert the entry at or +nil+ to append it
95
+ def menu_index(position)
96
+ return if position.nil?
97
+ raise NonLiteralError unless position.is_a?(Numeric)
98
+
99
+ index = Alchemy::Configurations::MenuItem.index_for_legacy_position(position)
100
+ index if index < Alchemy::Configurations::Menu::DEFAULT.size
101
+ end
102
+
103
+ def menu_add(item, indent, index)
91
104
  pairs = attributes(item).map do |key, value|
92
105
  if key == "children" || key == "nested"
93
106
  entries = value.map { |entry| "#{indent} #{inline_hash(entry)}" }
@@ -96,7 +109,11 @@ module Alchemy
96
109
  "#{indent} #{key}: #{value.inspect}"
97
110
  end
98
111
  end
99
- "Alchemy.config.menu.add(\n#{pairs.join(",\n")}\n#{indent})"
112
+ if index
113
+ "Alchemy.config.menu.insert(\n#{indent} #{index},\n#{pairs.join(",\n")}\n#{indent})"
114
+ else
115
+ "Alchemy.config.menu.add(\n#{pairs.join(",\n")}\n#{indent})"
116
+ end
100
117
  end
101
118
 
102
119
  def inline_hash(item)
@@ -3,7 +3,7 @@
3
3
  module Alchemy
4
4
  extend self
5
5
 
6
- VERSION = "8.4.1"
6
+ VERSION = "8.4.2"
7
7
 
8
8
  def version
9
9
  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.4.1
4
+ version: 8.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thomas von Deyen