activemail 1.1.1 → 1.2.0
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/CHANGELOG.md +7 -0
- data/lib/activemail/components/base.rb +15 -3
- data/lib/activemail/components/button.rb +2 -2
- data/lib/activemail/components/cta.rb +1 -1
- data/lib/activemail/components/menu_item.rb +1 -1
- data/lib/activemail/configuration.rb +22 -18
- data/lib/activemail/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 45cf8d77d0e4e10e7669e5cfeaa843b174a495b900d6bc5ffc83b2af18bfb11d
|
|
4
|
+
data.tar.gz: 4b750b051e3354ffc5eeac01e71df54f9bc724b6d3c00ab2d160845a70396db7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: aebffed9184e53716057ec140ea66c71ebca81cac9d15ca3cd60b20da4f7fdfc0da8f5fa599e3aa18a08b7d75400295be8b56c1a776cc36465ac412646d515f0
|
|
7
|
+
data.tar.gz: a9975ff0585326d018de39b7d651515d788f792465ac49f8d147034df5e7abc6e9fbcd1a5e4fb717612e59969efac36a5799bc8d1e35e698146cee7afb59f287
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,13 @@ All notable changes to this project are documented here.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.2.0] - 2026-07-01
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `config.blank_link_rel` (default `"noopener"`): a `rel` is now emitted automatically on
|
|
13
|
+
`target="_blank"` anchors. Set `nil` to disable; an explicit `rel="…"` still wins.
|
|
14
|
+
|
|
8
15
|
## [1.1.1] - 2026-06-16
|
|
9
16
|
|
|
10
17
|
### Added
|
|
@@ -29,7 +29,7 @@ module ActiveMail
|
|
|
29
29
|
abstract!
|
|
30
30
|
|
|
31
31
|
IGNORED_ON_PASSTHROUGH = T.let(
|
|
32
|
-
%w[class id href size large no-expander small target up size-sm size-lg style].freeze,
|
|
32
|
+
%w[class id href size large no-expander small target rel up size-sm size-lg style].freeze,
|
|
33
33
|
T::Array[String]
|
|
34
34
|
)
|
|
35
35
|
|
|
@@ -103,8 +103,20 @@ module ActiveMail
|
|
|
103
103
|
end
|
|
104
104
|
|
|
105
105
|
sig { params(node: Nokogiri::XML::Node).returns(String) }
|
|
106
|
-
def
|
|
107
|
-
|
|
106
|
+
def link_attributes(node)
|
|
107
|
+
target = node.attributes['target']&.value
|
|
108
|
+
rel = resolve_rel(node, target)
|
|
109
|
+
[
|
|
110
|
+
target ? %( target="#{escape_attr(target)}") : '',
|
|
111
|
+
rel ? %( rel="#{escape_attr(rel)}") : ''
|
|
112
|
+
].join
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
sig { params(node: Nokogiri::XML::Node, target: T.nilable(String)).returns(T.nilable(String)) }
|
|
116
|
+
def resolve_rel(node, target)
|
|
117
|
+
rel = node.attributes['rel']&.value
|
|
118
|
+
rel = nil if rel&.strip&.empty?
|
|
119
|
+
rel || (ActiveMail.configuration.blank_link_rel if target == '_blank')
|
|
108
120
|
end
|
|
109
121
|
|
|
110
122
|
# Outlook-safe nested-table structure kept in one place for <button> and <cta>.
|
|
@@ -28,11 +28,11 @@ module ActiveMail
|
|
|
28
28
|
|
|
29
29
|
sig { params(node: Nokogiri::XML::Node, inner: String, expand: T::Boolean).returns(String) }
|
|
30
30
|
def anchor(node, inner, expand)
|
|
31
|
-
|
|
31
|
+
links = link_attributes(node)
|
|
32
32
|
extra = expand ? ' align="center" class="float-center"' : ''
|
|
33
33
|
# Padding on the <a> makes the whole button a clickable target.
|
|
34
34
|
link_style = "display:inline-block;text-decoration:none;#{BUTTON_PADDING}"
|
|
35
|
-
attrs = %(#{pass_through_attributes(node)}href="#{escape_attr(node.attr('href'))}"#{
|
|
35
|
+
attrs = %(#{pass_through_attributes(node)}href="#{escape_attr(node.attr('href'))}"#{links}#{extra})
|
|
36
36
|
%(<a #{attrs}#{style_attribute(node, link_style)}>#{inner}</a>)
|
|
37
37
|
end
|
|
38
38
|
end
|
|
@@ -16,7 +16,7 @@ module ActiveMail
|
|
|
16
16
|
raise ArgumentError, '<cta> requires an href attribute' if node.attr('href').to_s.strip.empty?
|
|
17
17
|
|
|
18
18
|
style = ActiveMail.tokens.button_style(class?(node, 'secondary') ? :secondary : :primary)
|
|
19
|
-
anchor = %(<a href="#{escape_attr(node.attr('href'))}"#{
|
|
19
|
+
anchor = %(<a href="#{escape_attr(node.attr('href'))}"#{link_attributes(node)} ) +
|
|
20
20
|
%(style="#{link_style(style)}">#{inner}</a>)
|
|
21
21
|
bulletproof_button_table(
|
|
22
22
|
outer_classes: combine_classes(node, 'cta'),
|
|
@@ -12,7 +12,7 @@ module ActiveMail
|
|
|
12
12
|
def transform(node, inner)
|
|
13
13
|
attributes = combine_attributes(node, 'menu-item')
|
|
14
14
|
# No href → a non-link item, not a broken <a href="">; mirrors <button>.
|
|
15
|
-
content = node.attr('href') ? %(<a href="#{escape_attr(node.attr('href'))}"#{
|
|
15
|
+
content = node.attr('href') ? %(<a href="#{escape_attr(node.attr('href'))}"#{link_attributes(node)}>#{inner}</a>) : inner
|
|
16
16
|
th = ::ActiveMail::Core::INTERIM_TH_TAG
|
|
17
17
|
%(<#{th} #{attributes}#{style_attribute(node)}>#{content}</#{th}>)
|
|
18
18
|
end
|
|
@@ -86,6 +86,13 @@ module ActiveMail
|
|
|
86
86
|
value
|
|
87
87
|
end
|
|
88
88
|
|
|
89
|
+
sig { params(name: Symbol, value: T.untyped).returns(Integer) }
|
|
90
|
+
def self.positive_integer!(name, value)
|
|
91
|
+
raise TypeError, "#{name} must be an Integer, got #{value.inspect} (#{value.class})" unless value.is_a?(Integer)
|
|
92
|
+
|
|
93
|
+
assert_positive_dimension!(name, value)
|
|
94
|
+
end
|
|
95
|
+
|
|
89
96
|
class Configuration
|
|
90
97
|
extend T::Sig
|
|
91
98
|
|
|
@@ -113,16 +120,13 @@ module ActiveMail
|
|
|
113
120
|
end
|
|
114
121
|
|
|
115
122
|
sig { returns(Symbol) }
|
|
116
|
-
attr_reader :template_engine
|
|
117
|
-
|
|
118
|
-
sig { returns(Symbol) }
|
|
119
|
-
attr_reader :on_parse_error
|
|
123
|
+
attr_reader :template_engine, :on_parse_error
|
|
120
124
|
|
|
121
125
|
sig { returns(Integer) }
|
|
122
|
-
attr_reader :column_count
|
|
126
|
+
attr_reader :column_count, :container_width
|
|
123
127
|
|
|
124
|
-
sig { returns(
|
|
125
|
-
attr_reader :
|
|
128
|
+
sig { returns(T.nilable(String)) }
|
|
129
|
+
attr_reader :blank_link_rel
|
|
126
130
|
|
|
127
131
|
# Mutating the returned hash would bypass validate_component!.
|
|
128
132
|
sig { returns(ActiveMail::ComponentMap) }
|
|
@@ -146,6 +150,7 @@ module ActiveMail
|
|
|
146
150
|
@inliner = T.let(:premailer, InlinerSetting)
|
|
147
151
|
@resolved_inliner = T.let(nil, T.nilable(ActiveMail::Inliner::Base))
|
|
148
152
|
@register_inline_interceptor = T.let(true, T::Boolean)
|
|
153
|
+
@blank_link_rel = T.let('noopener', T.nilable(String))
|
|
149
154
|
end
|
|
150
155
|
|
|
151
156
|
# Validates eagerly (like sibling setters): a typo fails at boot, not silently mid-delivery.
|
|
@@ -180,12 +185,20 @@ module ActiveMail
|
|
|
180
185
|
|
|
181
186
|
sig { params(value: T.untyped).returns(Integer) }
|
|
182
187
|
def column_count=(value)
|
|
183
|
-
@column_count = positive_integer!(:column_count, value)
|
|
188
|
+
@column_count = ActiveMail.positive_integer!(:column_count, value)
|
|
184
189
|
end
|
|
185
190
|
|
|
186
191
|
sig { params(value: T.untyped).returns(Integer) }
|
|
187
192
|
def container_width=(value)
|
|
188
|
-
@container_width = positive_integer!(:container_width, value)
|
|
193
|
+
@container_width = ActiveMail.positive_integer!(:container_width, value)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
sig { params(value: T.untyped).returns(T.nilable(String)) }
|
|
197
|
+
def blank_link_rel=(value)
|
|
198
|
+
raise TypeError, "blank_link_rel must be a String or nil, got #{value.inspect} (#{value.class})" unless value.nil? || value.is_a?(String)
|
|
199
|
+
|
|
200
|
+
normalized = value&.strip
|
|
201
|
+
@blank_link_rel = normalized&.empty? ? nil : normalized
|
|
189
202
|
end
|
|
190
203
|
|
|
191
204
|
sig { params(value: T.untyped).returns(ActiveMail::ComponentMap) }
|
|
@@ -216,14 +229,5 @@ module ActiveMail
|
|
|
216
229
|
|
|
217
230
|
raise ArgumentError, "unknown inliner #{value.inspect}, expected one of #{INLINERS.keys.inspect}, an Inliner::Base subclass, or an instance"
|
|
218
231
|
end
|
|
219
|
-
|
|
220
|
-
# Integer-only (no to_int): a Float would otherwise be silently truncated
|
|
221
|
-
# (12.9 -> 12), contradicting assert_positive_dimension!'s invariant.
|
|
222
|
-
sig { params(name: Symbol, value: T.untyped).returns(Integer) }
|
|
223
|
-
def positive_integer!(name, value)
|
|
224
|
-
raise TypeError, "#{name} must be an Integer, got #{value.inspect} (#{value.class})" unless value.is_a?(Integer)
|
|
225
|
-
|
|
226
|
-
ActiveMail.assert_positive_dimension!(name, value)
|
|
227
|
-
end
|
|
228
232
|
end
|
|
229
233
|
end
|
data/lib/activemail/version.rb
CHANGED