activemail 1.1.1 → 1.2.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d7cc246dce0922db6f211036614c9c09e61f07ec31be8a8f080ec5367b639162
4
- data.tar.gz: f73779825c12264751b039b6fdac9bce76db075d26f058f432d9b85f79529f6a
3
+ metadata.gz: 3ad6c37435b356eecce625c0d03128d400a0684893d9fe74794038bd58d93e6e
4
+ data.tar.gz: 2bff13d848732be8df3643cc386c64b72dc0bde713a0b7df6873e97d55a41877
5
5
  SHA512:
6
- metadata.gz: 6edee3036ed2af1fa135508c8b936a61a9045a48f8b44ada6e762dead35510f6a8fd0b8c623f82132d72b6726c4cb3a7adfa96c58f93eebb0eb239340cd69118
7
- data.tar.gz: 1d49cbdcafea296842256ec928e626513d3fefe6f0d81d6ce1a2ea9229b9173735269c80123e94c832ba2bd691d8ce08468fba33cb65839cceb162de50af6c99
6
+ metadata.gz: b93e6c7efbeba96471a2989e74abf7308896255fa6338a25adadaaa1bdbde68625c4adb210b13cabf1752cd596d91168d19fe86ff77b4de00cb7e2d144aba4aa
7
+ data.tar.gz: 55fafc3be6f04051d719de0735bc3f89a7689357913da9da15242130a7e80fbd54ee65574397bcf509c873a2a1ef21000a951b00dafd8353c3ff2734941001dd
data/CHANGELOG.md CHANGED
@@ -5,6 +5,20 @@ 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.1] - 2026-07-01
9
+
10
+ ### Changed
11
+
12
+ - Renamed the internal `ActiveMail::Core#release_the_kraken` to `#transpile`.
13
+ - Neutralised test fixtures (example URLs) and severed the upstream fork link.
14
+
15
+ ## [1.2.0] - 2026-07-01
16
+
17
+ ### Added
18
+
19
+ - `config.blank_link_rel` (default `"noopener"`): a `rel` is now emitted automatically on
20
+ `target="_blank"` anchors. Set `nil` to disable; an explicit `rel="…"` still wins.
21
+
8
22
  ## [1.1.1] - 2026-06-16
9
23
 
10
24
  ### Added
data/README.md CHANGED
@@ -7,7 +7,7 @@ tokens, automatic CSS inlining, generators, and test-time quality guards — so
7
7
  responsive, accessible email renders **out of the box**, with every default
8
8
  overridable.
9
9
 
10
- A fork and modernization of the now-unmaintained `inky-rb`. Not affiliated with Rails core.
10
+ Not affiliated with Rails core.
11
11
 
12
12
  Write this:
13
13
 
@@ -289,7 +289,7 @@ Helpers available from `Base`: `combine_classes`, `combine_attributes`,
289
289
  Per-instance overrides (including replacing a built-in tag) are also possible:
290
290
 
291
291
  ```ruby
292
- ActiveMail::Core.new(components: { 'button' => MyButton }).release_the_kraken(source)
292
+ ActiveMail::Core.new(components: { 'button' => MyButton }).transpile(source)
293
293
  ```
294
294
 
295
295
  ## Generators
@@ -356,8 +356,8 @@ Targets the real-world client landscape as of 2026:
356
356
  ## Programmatic use
357
357
 
358
358
  ```ruby
359
- ActiveMail::Core.new.release_the_kraken('<container><row><columns>Hi</columns></row></container>')
360
- ActiveMail::Core.new(column_count: 24, container_width: 480).release_the_kraken(source)
359
+ ActiveMail::Core.new.transpile('<container><row><columns>Hi</columns></row></container>')
360
+ ActiveMail::Core.new(column_count: 24, container_width: 480).transpile(source)
361
361
  ```
362
362
 
363
363
  ## Development
@@ -371,4 +371,4 @@ bundle exec srb tc # Sorbet
371
371
 
372
372
  ## License
373
373
 
374
- MIT. See [`LICENSE.txt`](LICENSE.txt).
374
+ MIT. See [`LICENSE.txt`](LICENSE.txt), which retains the original ZURB copyright.
@@ -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 target_attribute(node)
107
- node.attributes['target'] ? %( target="#{escape_attr(node.attributes['target'])}") : ''
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
- target = target_attribute(node)
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'))}"#{target}#{extra})
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'))}"#{target_attribute(node)} ) +
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'))}"#{target_attribute(node)}>#{inner}</a>) : inner
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(Integer) }
125
- attr_reader :container_width
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
@@ -37,7 +37,7 @@ module ActiveMail
37
37
  else
38
38
  engine_handler.call(template)
39
39
  end
40
- "ActiveMail::Core.new.release_the_kraken(begin; #{compiled_source};end)"
40
+ "ActiveMail::Core.new.transpile(begin; #{compiled_source};end)"
41
41
  end
42
42
 
43
43
  module Composer
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module ActiveMail
5
- VERSION = '1.1.1'
5
+ VERSION = '1.2.1'
6
6
  end
data/lib/activemail.rb CHANGED
@@ -73,7 +73,7 @@ module ActiveMail
73
73
 
74
74
  # Object, not String: ActionView::OutputBuffer is no longer a String since Rails 7.1.
75
75
  sig { params(html_string: Object).returns(String) }
76
- def release_the_kraken(html_string)
76
+ def transpile(html_string)
77
77
  raws, str = extract_raws(normalize_input(html_string))
78
78
  parse_cmd = ::ActiveMail.full_document?(str) ? :parse : :fragment
79
79
  html = Nokogiri::HTML.public_send(parse_cmd, str)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activemail
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.1
4
+ version: 1.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Advitam