activemail 1.2.3 → 2.1.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 +107 -2
- data/README.md +141 -56
- data/UPGRADING.md +185 -0
- data/app/assets/stylesheets/activemail/_components.scss +33 -42
- data/app/assets/stylesheets/activemail/_dark.scss +44 -24
- data/app/assets/stylesheets/activemail/_grid.scss +40 -14
- data/app/assets/stylesheets/activemail/_settings.scss +23 -10
- data/app/assets/stylesheets/activemail/_utilities.scss +3 -1
- data/app/assets/stylesheets/activemail/activemail.scss +6 -5
- data/app/helpers/active_mail/styles_helper.rb +14 -15
- data/app/views/layouts/activemail/_document_head.html.erb +18 -0
- data/app/views/layouts/activemail/mailer.html.inky-erb +1 -22
- data/lib/activemail/asset_pipeline/base.rb +47 -0
- data/lib/activemail/asset_pipeline/null.rb +19 -0
- data/lib/activemail/asset_pipeline/propshaft.rb +19 -0
- data/lib/activemail/asset_pipeline/sprockets.rb +39 -0
- data/lib/activemail/asset_pipeline.rb +35 -0
- data/lib/activemail/components/base.rb +35 -39
- data/lib/activemail/components/block_grid.rb +11 -4
- data/lib/activemail/components/button.rb +5 -28
- data/lib/activemail/components/button_renderer.rb +69 -0
- data/lib/activemail/components/callout.rb +4 -5
- data/lib/activemail/components/center.rb +10 -10
- data/lib/activemail/components/columns.rb +58 -15
- data/lib/activemail/components/container.rb +13 -6
- data/lib/activemail/components/cta.rb +9 -33
- data/lib/activemail/components/h_line.rb +3 -5
- data/lib/activemail/components/info_box.rb +6 -12
- data/lib/activemail/components/menu.rb +3 -4
- data/lib/activemail/components/menu_item.rb +10 -3
- data/lib/activemail/components/row.rb +3 -4
- data/lib/activemail/components/spacer.rb +16 -12
- data/lib/activemail/components/table_component.rb +23 -0
- data/lib/activemail/components/wrapper.rb +10 -4
- data/lib/activemail/configuration.rb +43 -57
- data/lib/activemail/errors.rb +15 -0
- data/lib/activemail/inliner/base.rb +2 -10
- data/lib/activemail/inliner/null.rb +0 -5
- data/lib/activemail/inliner/premailer/document.rb +100 -0
- data/lib/activemail/inliner/premailer.rb +3 -2
- data/lib/activemail/inliner/roadie.rb +1 -1
- data/lib/activemail/inliner.rb +33 -0
- data/lib/activemail/parse_error_reporter.rb +5 -4
- data/lib/activemail/pipeline.rb +26 -0
- data/lib/activemail/quality/builtin_message_rules.rb +58 -0
- data/lib/activemail/quality/builtin_rules.rb +165 -0
- data/lib/activemail/quality/configuration.rb +103 -8
- data/lib/activemail/quality/guard.rb +61 -83
- data/lib/activemail/quality/minitest.rb +56 -27
- data/lib/activemail/quality/output_dir.rb +52 -0
- data/lib/activemail/quality/output_filter.rb +24 -0
- data/lib/activemail/quality/preview_renderer.rb +46 -33
- data/lib/activemail/quality/render_all.rb +95 -39
- data/lib/activemail/quality/rspec.rb +14 -13
- data/lib/activemail/quality/rule.rb +47 -0
- data/lib/activemail/quality/rule_exemptions.rb +49 -0
- data/lib/activemail/rails/engine.rb +13 -13
- data/lib/activemail/rails/rendering.rb +51 -0
- data/lib/activemail/rails/template_handler.rb +3 -1
- data/lib/activemail/tokens/button_style.rb +1 -2
- data/lib/activemail/tokens/defaults.rb +78 -0
- data/lib/activemail/tokens/scss_serializer.rb +4 -4
- data/lib/activemail/tokens.rb +19 -58
- data/lib/activemail/version.rb +1 -1
- data/lib/activemail.rb +89 -61
- data/lib/generators/activemail/templates/component.rb.tt +2 -3
- data/lib/generators/activemail/templates/initializer.rb +0 -3
- data/lib/generators/activemail/templates/mailer_layout.html.inky-erb +3 -19
- data/lib/generators/activemail/templates/mailer_layout.html.inky-haml +3 -11
- data/lib/generators/activemail/templates/mailer_layout.html.inky-slim +3 -10
- data/lib/tasks/activemail.rake +4 -5
- metadata +22 -4
- data/lib/activemail/components/inky.rb +0 -18
- data/lib/activemail/inliner/interceptor.rb +0 -51
- data/lib/activemail/rails/compiled_stylesheet.rb +0 -62
data/UPGRADING.md
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# Upgrading to ActiveMail 2.1
|
|
2
|
+
|
|
3
|
+
## Gemfile
|
|
4
|
+
|
|
5
|
+
```ruby
|
|
6
|
+
gem 'activemail', '~> 2.1'
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Outer gutter
|
|
10
|
+
|
|
11
|
+
Rows now keep content off the screen edge: the first and last column of a row
|
|
12
|
+
get a 16px (`container_gutter`) border-box padding on their outer side, inlined
|
|
13
|
+
like every other framework rule. Columns still sum to `container_width`; the
|
|
14
|
+
text area is 16px narrower on each side.
|
|
15
|
+
|
|
16
|
+
- To keep edge-to-edge columns: `config.tokens.spacing :container_gutter, '0px'`
|
|
17
|
+
(or `$am-container-gutter: 0`).
|
|
18
|
+
- Content you padded by hand to avoid the edge now has the padding twice:
|
|
19
|
+
remove it.
|
|
20
|
+
- A nested row does not repeat the outer gutter. The `.columns.first.last` SCSS
|
|
21
|
+
rule is gone; ejected `_grid.scss` copies (`activemail:styles`) must be
|
|
22
|
+
re-ejected.
|
|
23
|
+
|
|
24
|
+
## Outlook markup
|
|
25
|
+
|
|
26
|
+
- Conditional comments are `<!--[if mso]>` (`mso` alone targets Outlook
|
|
27
|
+
for Windows). Tests matching `[if mso | IE]` must change.
|
|
28
|
+
- Each column ghost cell wraps its column in a `<table><tr>`. A nested column
|
|
29
|
+
is sized against the content box of its enclosing column: `large="6"` in a
|
|
30
|
+
lone `large="6"` is 134px (300px minus two 16px gutters, halved), not 300px.
|
|
31
|
+
|
|
32
|
+
## Buttons
|
|
33
|
+
|
|
34
|
+
- The fill and the radius are on the cell, not on the `<a>`. The `<a>` carries
|
|
35
|
+
`display:inline-block`, `font-family` (the `body` font token) and
|
|
36
|
+
`line-height:1.25`; the cell gets `mso-padding-alt`.
|
|
37
|
+
- A dark-mode override of the button fill must target `table td`, not `a`.
|
|
38
|
+
|
|
39
|
+
## Center
|
|
40
|
+
|
|
41
|
+
`<center>` no longer adds `align="center"` and `.float-center` to its children
|
|
42
|
+
(nor to nested menu items): it renders one `<center align="center">` and leaves
|
|
43
|
+
its content as written. Put `class="float-center"` or `align="center"` on the
|
|
44
|
+
children that need it.
|
|
45
|
+
|
|
46
|
+
## Layouts
|
|
47
|
+
|
|
48
|
+
The `<head>` of the default layout moved to `layouts/activemail/_document_head`,
|
|
49
|
+
with `<title><%= message.subject %></title>`. The generated layouts
|
|
50
|
+
(`activemail:install`, `--haml`, `--slim`) render that partial and the `_head`
|
|
51
|
+
and `_footer` partials: regenerate yours or add the `<title>`. The partial reads
|
|
52
|
+
`message`, so it only renders inside a mailer view.
|
|
53
|
+
|
|
54
|
+
## Quality
|
|
55
|
+
|
|
56
|
+
New built-in rules flag emails that passed before: `title`, `anchor_href`,
|
|
57
|
+
`placeholder`, `unsupported_color`, `text_entities`. Disable any with
|
|
58
|
+
`Guard.new(disable: [...])`.
|
|
59
|
+
|
|
60
|
+
A `NullMail` (a mailer that returned without sending) is now a `declined`
|
|
61
|
+
violation, in `Guard`, `render_all`, `assert_email_quality`,
|
|
62
|
+
`assert_quality_for_all_previews` and `be_a_valid_email`. List the previews that
|
|
63
|
+
decline on purpose:
|
|
64
|
+
|
|
65
|
+
```ruby
|
|
66
|
+
ActiveMail::Quality.configure { |c| c.allow_declined = %w[reminder_mailer#maybe_skipped] }
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
or pass `allow_declined: true` to `Guard#violations`, `Guard#valid?`,
|
|
70
|
+
`assert_email_quality` and `be_a_valid_email`.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
# Upgrading to ActiveMail 2.0
|
|
75
|
+
|
|
76
|
+
## Gemfile
|
|
77
|
+
|
|
78
|
+
```ruby
|
|
79
|
+
gem 'activemail', '~> 2.0'
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Inlining
|
|
83
|
+
|
|
84
|
+
The delivery interceptor is gone: each email is transpiled and inlined once,
|
|
85
|
+
when the mailer renders an inky HTML template.
|
|
86
|
+
|
|
87
|
+
- Remove `config.register_inline_interceptor` from your initializer.
|
|
88
|
+
- If you ran another inliner (premailer-rails) next to ActiveMail with
|
|
89
|
+
`config.inliner = :null`, drop that inliner and the `:null` setting: ActiveMail
|
|
90
|
+
now inlines the whole body, layout included.
|
|
91
|
+
- Inliner failures still raise `ActiveMail::Inliner::Error` (now a subclass of
|
|
92
|
+
`ActiveMail::Error`).
|
|
93
|
+
|
|
94
|
+
## Layouts
|
|
95
|
+
|
|
96
|
+
Ejected or hand-written layouts must embed the stylesheets with the helper and
|
|
97
|
+
drop `stylesheet_link_tag` (Premailer strips it, so it never styled anything):
|
|
98
|
+
|
|
99
|
+
```erb
|
|
100
|
+
<%# before %>
|
|
101
|
+
<%= stylesheet_link_tag "emails/emails" %>
|
|
102
|
+
<%= activemail_inline_styles %>
|
|
103
|
+
|
|
104
|
+
<%# after %>
|
|
105
|
+
<%= activemail_inline_styles "emails/emails" %>
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
A missing or empty asset now raises `ActiveMail::StylesheetNotFound` instead of
|
|
109
|
+
logging a warning; inside a view it surfaces as the `#cause` of
|
|
110
|
+
`ActionView::Template::Error`.
|
|
111
|
+
|
|
112
|
+
## SCSS
|
|
113
|
+
|
|
114
|
+
The framework uses the Sass module system. Replace `@import` and variables
|
|
115
|
+
declared before it with `@use … with (…)`; a variable declared before the
|
|
116
|
+
`@use` is no longer read.
|
|
117
|
+
|
|
118
|
+
```scss
|
|
119
|
+
// before
|
|
120
|
+
$am-button-secondary-color: #264653;
|
|
121
|
+
@import "activemail/activemail";
|
|
122
|
+
|
|
123
|
+
// after
|
|
124
|
+
@use "activemail/activemail" with (
|
|
125
|
+
$am-button-secondary-color: #264653
|
|
126
|
+
);
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
- Renamed: `$am-button-color` → `$am-button-primary-color`,
|
|
130
|
+
`$am-info-box-bg` → `$am-info-box-background`.
|
|
131
|
+
- Prefer Ruby tokens (`secondary_text`, `secondary_border`, `info_box_*`, …):
|
|
132
|
+
they now reach both the inline styles and the stylesheet, so SCSS overrides of
|
|
133
|
+
the button/info-box variables are rarely needed.
|
|
134
|
+
- `$am-breakpoint` defaults to the container width (600px) instead of 596px.
|
|
135
|
+
|
|
136
|
+
## Buttons
|
|
137
|
+
|
|
138
|
+
`<button>` and `<cta>` share one rendering, styled inline from the tokens.
|
|
139
|
+
|
|
140
|
+
- CSS targeting the old markup (`<center>`, `.expander` cell inside
|
|
141
|
+
`.button.expand`) no longer matches; style buttons through tokens.
|
|
142
|
+
- A dark-mode override must use `!important` to beat the inline styles.
|
|
143
|
+
- A `<button>` without `href` renders its label in a `<span>`.
|
|
144
|
+
|
|
145
|
+
## Components
|
|
146
|
+
|
|
147
|
+
- The `<inky>` tag is removed.
|
|
148
|
+
- `inner` is an opaque slot: interpolate it exactly once and never parse or
|
|
149
|
+
alter it.
|
|
150
|
+
- `IGNORED_ON_PASSTHROUGH`, `combine_attributes`, `pass_through_attributes`,
|
|
151
|
+
`bulletproof_button_table` and `BUTTON_PADDING` are removed. Use
|
|
152
|
+
`root_attributes(node, classes:, layout_style:)` and override the private
|
|
153
|
+
`consumed_attributes` to list the attributes your component reads; a
|
|
154
|
+
component whose root is a `TABLE_RESET` table inherits from
|
|
155
|
+
`ActiveMail::Components::TableComponent`.
|
|
156
|
+
- A component must output `inner` exactly once (or have no children):
|
|
157
|
+
otherwise `ActiveMail::Error` is raised instead of silently dropping content.
|
|
158
|
+
Self-closing `<h-line/>`/`<spacer/>` are not valid HTML4 and swallow their
|
|
159
|
+
following siblings; write `<h-line></h-line>`.
|
|
160
|
+
- `id`, `aria-*`, `data-*` and other attributes now pass through on every
|
|
161
|
+
built-in component. On tables carrying `TABLE_RESET`, the component's
|
|
162
|
+
`role="presentation"` wins over the author's `role`.
|
|
163
|
+
- `ActiveMail::Core.new` only takes keywords (`config:`, `components:`,
|
|
164
|
+
`column_count:`, `container_width:`). For plain transpilation prefer the
|
|
165
|
+
memoized `ActiveMail.configuration.core`.
|
|
166
|
+
- Integer settings (`column_count`, `container_width`) raise `TypeError` for a
|
|
167
|
+
non-Integer and `ArgumentError` for a non-positive value. Numeric attributes
|
|
168
|
+
are parsed in strict base 10.
|
|
169
|
+
|
|
170
|
+
## Quality
|
|
171
|
+
|
|
172
|
+
- `output_dir` must be relative, inside `Rails.root` (symlinks resolved), and
|
|
173
|
+
neither `.` nor contain `..`. `render_all` marks it with an
|
|
174
|
+
`.activemail_previews` file and refuses to clean an existing, non-empty
|
|
175
|
+
directory without that marker: empty your previous output directory once.
|
|
176
|
+
- `PreviewRenderer.render` is removed.
|
|
177
|
+
- `RenderAll.new(root:, config:)` replaces `output_root:`.
|
|
178
|
+
- Rendered files and failures carry the locale when it differs from the
|
|
179
|
+
default (`welcome.fr.html`, `preview#email [fr]`).
|
|
180
|
+
- `Guard#violations` and `assert_email_quality` accept a `Mail::Message` (HTML
|
|
181
|
+
plus message rules) or an HTML string. New built-in rules may flag existing
|
|
182
|
+
emails: `translation_missing`, `translation_missing_text`, `single_root`,
|
|
183
|
+
`html_part_present`, `text_part_present`, `text_part_plain`. Disable any with
|
|
184
|
+
`Guard.new(disable: [...])`.
|
|
185
|
+
- Host-specific checks move to `Guard.new(rules: [Quality::Rule.html(...)])`.
|
|
@@ -1,62 +1,53 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
.button table td,
|
|
4
|
-
.button a {
|
|
5
|
-
background: $am-button-primary-bg;
|
|
6
|
-
border-radius: $am-button-radius;
|
|
7
|
-
}
|
|
1
|
+
@use "sass:math";
|
|
2
|
+
@use "settings" as *;
|
|
8
3
|
|
|
9
|
-
.
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
4
|
+
.expander {
|
|
5
|
+
padding: 0 !important;
|
|
6
|
+
visibility: hidden;
|
|
7
|
+
width: 0;
|
|
13
8
|
}
|
|
14
9
|
|
|
15
|
-
.
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
10
|
+
.info-box td {
|
|
11
|
+
background-color: $am-info-box-background;
|
|
12
|
+
border-left: 5px solid $am-info-box-border;
|
|
13
|
+
border-radius: $am-box-radius;
|
|
14
|
+
color: $am-info-box-text;
|
|
15
|
+
padding: $am-spacing-md;
|
|
19
16
|
}
|
|
20
17
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
.callout-inner {
|
|
19
|
+
background-color: $am-background;
|
|
20
|
+
color: $am-text;
|
|
21
|
+
padding: $am-spacing-md;
|
|
24
22
|
}
|
|
25
23
|
|
|
26
|
-
.
|
|
27
|
-
.
|
|
28
|
-
|
|
29
|
-
|
|
24
|
+
.h-line,
|
|
25
|
+
.h-line th {
|
|
26
|
+
font-size: 1px;
|
|
27
|
+
line-height: 1px;
|
|
30
28
|
}
|
|
31
29
|
|
|
32
|
-
.
|
|
33
|
-
|
|
34
|
-
font-family: $am-font-family;
|
|
35
|
-
font-weight: bold;
|
|
30
|
+
.h-line {
|
|
31
|
+
border-top: 1px solid $am-border;
|
|
36
32
|
}
|
|
37
33
|
|
|
38
|
-
.
|
|
39
|
-
|
|
40
|
-
background: $am-button-secondary-bg;
|
|
41
|
-
color: $am-button-secondary-color;
|
|
34
|
+
.menu {
|
|
35
|
+
width: auto !important;
|
|
42
36
|
}
|
|
43
37
|
|
|
44
|
-
.
|
|
45
|
-
|
|
38
|
+
.menu-item {
|
|
39
|
+
padding: $am-spacing-sm $am-spacing-md;
|
|
46
40
|
}
|
|
47
41
|
|
|
48
|
-
.
|
|
49
|
-
|
|
50
|
-
border-left: 5px solid $am-info-box-border;
|
|
51
|
-
border-radius: $am-box-radius;
|
|
52
|
-
color: $am-info-box-text;
|
|
53
|
-
padding: $am-spacing-md;
|
|
42
|
+
.menu-item a {
|
|
43
|
+
text-decoration: none;
|
|
54
44
|
}
|
|
55
45
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
46
|
+
@for $n from 1 through $am-column-count {
|
|
47
|
+
.block-grid.up-#{$n} > tbody > tr > th,
|
|
48
|
+
.block-grid.up-#{$n} > tbody > tr > td {
|
|
49
|
+
width: math.div(100%, $n);
|
|
50
|
+
}
|
|
60
51
|
}
|
|
61
52
|
|
|
62
53
|
// Height is set inline per instance; this rule is just a styling anchor.
|
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
// Dark mode: prefers-color-scheme (Apple Mail/iOS) + [data-ogsc] (Outlook
|
|
2
2
|
// Android). Colors derived from tokens — pure black/white crush contrast.
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
$am-dark-surface: mix($am-color-text, $am-color-background, 92%) !default;
|
|
6
|
-
$am-dark-panel: mix($am-color-text, $am-color-background, 82%) !default;
|
|
7
|
-
$am-dark-text: mix($am-color-background, $am-color-text, 88%) !default;
|
|
8
|
-
$am-dark-muted: $am-color-muted !default;
|
|
9
|
-
$am-dark-link: $am-color-primary !default;
|
|
4
|
+
@use "settings" as *;
|
|
10
5
|
|
|
11
6
|
@mixin am-dark-rules {
|
|
12
7
|
.body,
|
|
@@ -37,27 +32,40 @@ $am-dark-link: $am-color-primary !default;
|
|
|
37
32
|
color: $am-dark-link !important;
|
|
38
33
|
}
|
|
39
34
|
|
|
35
|
+
// The fill lives on the cell alone: a filled link would square off its rounded corners.
|
|
40
36
|
.button table td,
|
|
37
|
+
.cta table td {
|
|
38
|
+
background: $am-button-primary-bg !important;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
41
|
.button a,
|
|
42
|
-
.
|
|
43
|
-
.cta a
|
|
44
|
-
|
|
45
|
-
color: $am-
|
|
42
|
+
.button span,
|
|
43
|
+
.cta a,
|
|
44
|
+
.cta span {
|
|
45
|
+
color: $am-button-primary-color !important;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
// Restore the secondary fill AND text — the primary rule above
|
|
49
|
-
//
|
|
48
|
+
// Restore the secondary fill AND text — the primary rule above would be unreadable
|
|
49
|
+
// on a light secondary fill.
|
|
50
50
|
.button.secondary table td,
|
|
51
|
+
.cta.secondary table td {
|
|
52
|
+
background: $am-button-secondary-bg !important;
|
|
53
|
+
}
|
|
54
|
+
|
|
51
55
|
.button.secondary a,
|
|
52
|
-
.
|
|
53
|
-
.cta.secondary a
|
|
54
|
-
|
|
56
|
+
.button.secondary span,
|
|
57
|
+
.cta.secondary a,
|
|
58
|
+
.cta.secondary span {
|
|
55
59
|
color: $am-button-secondary-color !important;
|
|
56
60
|
}
|
|
57
61
|
|
|
58
62
|
// Panels keep their inline background unless overridden here, which would
|
|
59
63
|
// otherwise leave dark text on a dark background.
|
|
60
|
-
.callout-inner
|
|
64
|
+
.callout-inner {
|
|
65
|
+
background-color: $am-dark-panel !important;
|
|
66
|
+
color: $am-dark-text !important;
|
|
67
|
+
}
|
|
68
|
+
|
|
61
69
|
.info-box,
|
|
62
70
|
.info-box td {
|
|
63
71
|
background-color: $am-dark-panel !important;
|
|
@@ -101,22 +109,34 @@ $am-dark-link: $am-color-primary !default;
|
|
|
101
109
|
}
|
|
102
110
|
|
|
103
111
|
[data-ogsc] .button table td,
|
|
112
|
+
[data-ogsc] .cta table td {
|
|
113
|
+
background: $am-button-primary-bg !important;
|
|
114
|
+
}
|
|
115
|
+
|
|
104
116
|
[data-ogsc] .button a,
|
|
105
|
-
[data-ogsc] .
|
|
106
|
-
[data-ogsc] .cta a
|
|
107
|
-
|
|
108
|
-
color: $am-
|
|
117
|
+
[data-ogsc] .button span,
|
|
118
|
+
[data-ogsc] .cta a,
|
|
119
|
+
[data-ogsc] .cta span {
|
|
120
|
+
color: $am-button-primary-color !important;
|
|
109
121
|
}
|
|
110
122
|
|
|
111
123
|
[data-ogsc] .button.secondary table td,
|
|
124
|
+
[data-ogsc] .cta.secondary table td {
|
|
125
|
+
background: $am-button-secondary-bg !important;
|
|
126
|
+
}
|
|
127
|
+
|
|
112
128
|
[data-ogsc] .button.secondary a,
|
|
113
|
-
[data-ogsc] .
|
|
114
|
-
[data-ogsc] .cta.secondary a
|
|
115
|
-
|
|
129
|
+
[data-ogsc] .button.secondary span,
|
|
130
|
+
[data-ogsc] .cta.secondary a,
|
|
131
|
+
[data-ogsc] .cta.secondary span {
|
|
116
132
|
color: $am-button-secondary-color !important;
|
|
117
133
|
}
|
|
118
134
|
|
|
119
|
-
[data-ogsc] .callout-inner
|
|
135
|
+
[data-ogsc] .callout-inner {
|
|
136
|
+
background-color: $am-dark-panel !important;
|
|
137
|
+
color: $am-dark-text !important;
|
|
138
|
+
}
|
|
139
|
+
|
|
120
140
|
[data-ogsc] .info-box,
|
|
121
141
|
[data-ogsc] .info-box td {
|
|
122
142
|
background-color: $am-dark-panel !important;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
@use "settings" as *;
|
|
2
|
+
|
|
3
|
+
// Gutters are plain padding, inlined: Gmail mobile strips media queries.
|
|
3
4
|
|
|
4
5
|
.container {
|
|
5
6
|
width: 100%;
|
|
@@ -15,11 +16,8 @@
|
|
|
15
16
|
width: 100%;
|
|
16
17
|
}
|
|
17
18
|
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
// box-sizing keeps the gutter inside the inline max-width — without it a 300px
|
|
21
|
-
// 2-col cell + 8px padding becomes 316px and the row overflows ~616px, wrapping
|
|
22
|
-
// in clients that honor inline padding.
|
|
19
|
+
// border-box keeps each gutter inside the inline max-width, else the row overflows and wraps;
|
|
20
|
+
// the container takes no padding for the same reason.
|
|
23
21
|
.columns {
|
|
24
22
|
box-sizing: border-box;
|
|
25
23
|
padding-left: $am-gutter;
|
|
@@ -27,23 +25,51 @@
|
|
|
27
25
|
}
|
|
28
26
|
|
|
29
27
|
.columns.first {
|
|
30
|
-
padding-left:
|
|
28
|
+
padding-left: $am-container-gutter;
|
|
31
29
|
}
|
|
32
30
|
|
|
33
31
|
.columns.last {
|
|
34
|
-
padding-right:
|
|
32
|
+
padding-right: $am-container-gutter;
|
|
35
33
|
}
|
|
36
34
|
|
|
37
|
-
// A
|
|
38
|
-
.columns.first
|
|
35
|
+
// A nested row already sits inside its parent column's padding.
|
|
36
|
+
.columns .columns.first {
|
|
39
37
|
padding-left: 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.columns .columns.last {
|
|
40
41
|
padding-right: 0;
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
//
|
|
44
|
-
// direct children of .row — the real markup nests them as <th class="columns">
|
|
45
|
-
// under tbody > tr — so this must be a descendant selector, not a child one.
|
|
44
|
+
// Descendant selector: columns render as <th> under tbody > tr, not as children of .row.
|
|
46
45
|
.row.collapse .columns {
|
|
47
46
|
padding-left: 0;
|
|
48
47
|
padding-right: 0;
|
|
49
48
|
}
|
|
49
|
+
|
|
50
|
+
// Below the container width every full row stacks, so each top-level column edge is an outer one.
|
|
51
|
+
// Nested columns may still sit side by side and keep the regular gutters. Gmail strips this query.
|
|
52
|
+
@media only screen and (max-width: $am-breakpoint - 1px) {
|
|
53
|
+
.columns {
|
|
54
|
+
padding-left: $am-container-gutter !important;
|
|
55
|
+
padding-right: $am-container-gutter !important;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.columns .columns {
|
|
59
|
+
padding-left: $am-gutter !important;
|
|
60
|
+
padding-right: $am-gutter !important;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.columns .columns.first {
|
|
64
|
+
padding-left: 0 !important;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.columns .columns.last {
|
|
68
|
+
padding-right: 0 !important;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.row.collapse .columns {
|
|
72
|
+
padding-left: 0 !important;
|
|
73
|
+
padding-right: 0 !important;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
1
|
+
// Sass only lets `with (...)` configure !default variables of forwarded modules:
|
|
2
|
+
// every configurable variable must live here or in the forwarded token bridge.
|
|
3
|
+
@forward "activemail_tokens";
|
|
4
|
+
@use "sass:color";
|
|
5
|
+
@use "activemail_tokens" as *;
|
|
3
6
|
|
|
4
7
|
// Typography
|
|
5
8
|
$am-font-family: $am-font-body !default;
|
|
@@ -11,19 +14,19 @@ $am-background: $am-color-background !default;
|
|
|
11
14
|
$am-muted: $am-color-muted !default;
|
|
12
15
|
$am-border: $am-color-border !default;
|
|
13
16
|
|
|
14
|
-
// Buttons
|
|
17
|
+
// Buttons — a null border means no border at all.
|
|
15
18
|
$am-button-primary-bg: $am-color-primary !default;
|
|
19
|
+
$am-button-primary-color: $am-color-primary-text !default;
|
|
20
|
+
$am-button-primary-border: $am-color-primary-border !default;
|
|
16
21
|
$am-button-secondary-bg: $am-color-secondary !default;
|
|
17
|
-
$am-button-color: $am-color-
|
|
18
|
-
|
|
19
|
-
$am-button-secondary-color: $am-color-button-text !default;
|
|
20
|
-
$am-button-secondary-border: transparent !default;
|
|
22
|
+
$am-button-secondary-color: $am-color-secondary-text !default;
|
|
23
|
+
$am-button-secondary-border: $am-color-secondary-border !default;
|
|
21
24
|
$am-button-radius: $am-radius-button !default;
|
|
22
25
|
|
|
23
26
|
// Info box
|
|
24
|
-
$am-info-box-
|
|
25
|
-
$am-info-box-border: $am-border !default;
|
|
26
|
-
$am-info-box-text: $am-text !default;
|
|
27
|
+
$am-info-box-background: $am-color-info-box-background !default;
|
|
28
|
+
$am-info-box-border: $am-color-info-box-border !default;
|
|
29
|
+
$am-info-box-text: $am-color-info-box-text !default;
|
|
27
30
|
$am-box-radius: $am-radius-box !default;
|
|
28
31
|
|
|
29
32
|
// Grid — the gem emits no gutters; we provide them via column padding. Width
|
|
@@ -31,3 +34,13 @@ $am-box-radius: $am-radius-box !default;
|
|
|
31
34
|
// aligned with the transpiled markup (ghost tables, column max-width).
|
|
32
35
|
$am-container-width: $am-grid-container-width !default;
|
|
33
36
|
$am-gutter: $am-spacing-sm !default;
|
|
37
|
+
$am-container-gutter: $am-spacing-container-gutter !default;
|
|
38
|
+
$am-breakpoint: $am-container-width !default;
|
|
39
|
+
|
|
40
|
+
// Dark mode
|
|
41
|
+
$am-dark-bg: color.mix($am-color-text, $am-color-background, 96%) !default;
|
|
42
|
+
$am-dark-surface: color.mix($am-color-text, $am-color-background, 92%) !default;
|
|
43
|
+
$am-dark-panel: color.mix($am-color-text, $am-color-background, 82%) !default;
|
|
44
|
+
$am-dark-text: color.mix($am-color-background, $am-color-text, 88%) !default;
|
|
45
|
+
$am-dark-muted: $am-color-muted !default;
|
|
46
|
+
$am-dark-link: $am-button-primary-bg !default;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
@use "settings" as *;
|
|
2
|
+
|
|
1
3
|
// Alignment and visibility utilities.
|
|
2
4
|
|
|
3
5
|
.text-center {
|
|
@@ -32,7 +34,7 @@
|
|
|
32
34
|
overflow: hidden;
|
|
33
35
|
}
|
|
34
36
|
|
|
35
|
-
@media only screen and (min-width:
|
|
37
|
+
@media only screen and (min-width: $am-breakpoint) {
|
|
36
38
|
.show-for-large {
|
|
37
39
|
display: block !important;
|
|
38
40
|
font-size: inherit;
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
// ActiveMail email framework entry point. Critical styles are inlined by the
|
|
2
2
|
// configured CSS inliner (Gmail mobile strips <style>); only media-query and
|
|
3
3
|
// dark-mode enhancements stay in the <head>.
|
|
4
|
-
@
|
|
5
|
-
@
|
|
6
|
-
@
|
|
7
|
-
@
|
|
8
|
-
@
|
|
4
|
+
@forward "settings";
|
|
5
|
+
@use "settings" as *;
|
|
6
|
+
@use "grid";
|
|
7
|
+
@use "components";
|
|
8
|
+
@use "utilities";
|
|
9
|
+
@use "dark";
|
|
9
10
|
|
|
10
11
|
body,
|
|
11
12
|
table,
|
|
@@ -1,29 +1,28 @@
|
|
|
1
1
|
# typed: false
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
+
require 'activemail/asset_pipeline'
|
|
5
|
+
|
|
4
6
|
module ActiveMail
|
|
5
|
-
# Embeds the compiled
|
|
6
|
-
#
|
|
7
|
+
# Embeds the compiled CSS as a <style> block: the inliners take an HTML string
|
|
8
|
+
# and cannot fetch the digest URL a stylesheet_link_tag would emit.
|
|
7
9
|
module StylesHelper
|
|
8
|
-
FRAMEWORK_STYLESHEET = 'activemail/activemail
|
|
9
|
-
|
|
10
|
-
# '' (not raise) when the asset can't be read — degrades to the link fallback,
|
|
11
|
-
# but warns, since the email then ships unstyled.
|
|
12
|
-
def activemail_inline_styles
|
|
13
|
-
css = activemail_compiled_css
|
|
14
|
-
if css.blank?
|
|
15
|
-
ActiveMail.log_warning('[activemail] framework stylesheet could not be read from the asset pipeline; ' \
|
|
16
|
-
'email ships without inlined framework CSS')
|
|
17
|
-
return ''.html_safe
|
|
18
|
-
end
|
|
10
|
+
FRAMEWORK_STYLESHEET = 'activemail/activemail'
|
|
19
11
|
|
|
12
|
+
def activemail_inline_styles(*logical_paths)
|
|
13
|
+
paths = logical_paths.empty? ? [FRAMEWORK_STYLESHEET] : logical_paths
|
|
14
|
+
css = paths.map { |path| activemail_compiled_css(path.to_s) }.join("\n")
|
|
20
15
|
content_tag(:style, css.html_safe, type: 'text/css')
|
|
21
16
|
end
|
|
22
17
|
|
|
23
18
|
private
|
|
24
19
|
|
|
25
|
-
def activemail_compiled_css
|
|
26
|
-
|
|
20
|
+
def activemail_compiled_css(logical_path)
|
|
21
|
+
asset = logical_path.end_with?('.css') ? logical_path : "#{logical_path}.css"
|
|
22
|
+
css = ActiveMail::AssetPipeline.current.read(asset)
|
|
23
|
+
return css if css.present?
|
|
24
|
+
|
|
25
|
+
raise ActiveMail::StylesheetNotFound, "stylesheet #{asset.inspect} is missing or empty in the asset pipeline"
|
|
27
26
|
end
|
|
28
27
|
end
|
|
29
28
|
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<head>
|
|
2
|
+
<meta charset="utf-8">
|
|
3
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
4
|
+
<meta name="x-apple-disable-message-reformatting">
|
|
5
|
+
<meta name="color-scheme" content="light dark">
|
|
6
|
+
<meta name="supported-color-schemes" content="light dark">
|
|
7
|
+
<title><%= message.subject %></title>
|
|
8
|
+
<!--[if mso]>
|
|
9
|
+
<noscript>
|
|
10
|
+
<xml>
|
|
11
|
+
<o:OfficeDocumentSettings>
|
|
12
|
+
<o:PixelsPerInch>96</o:PixelsPerInch>
|
|
13
|
+
</o:OfficeDocumentSettings>
|
|
14
|
+
</xml>
|
|
15
|
+
</noscript>
|
|
16
|
+
<![endif]-->
|
|
17
|
+
<%= activemail_inline_styles %>
|
|
18
|
+
</head>
|
|
@@ -1,27 +1,6 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
2
|
<html lang="<%= I18n.locale %>" xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
|
3
|
-
|
|
4
|
-
<meta charset="utf-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
-
<meta name="x-apple-disable-message-reformatting">
|
|
7
|
-
<meta name="color-scheme" content="light dark">
|
|
8
|
-
<meta name="supported-color-schemes" content="light dark">
|
|
9
|
-
<!--[if mso]>
|
|
10
|
-
<noscript>
|
|
11
|
-
<xml>
|
|
12
|
-
<o:OfficeDocumentSettings>
|
|
13
|
-
<o:PixelsPerInch>96</o:PixelsPerInch>
|
|
14
|
-
</o:OfficeDocumentSettings>
|
|
15
|
-
</xml>
|
|
16
|
-
</noscript>
|
|
17
|
-
<![endif]-->
|
|
18
|
-
<%# Embedded so the Premailer adapter (HTML-string only) can inline the framework CSS. %>
|
|
19
|
-
<%= activemail_inline_styles %>
|
|
20
|
-
<%# Fallback for when activemail_inline_styles cannot read the compiled asset %>
|
|
21
|
-
<%# (CDN-only host, compile=false without precompile); redundant otherwise, as the %>
|
|
22
|
-
<%# embed's rules are idempotent once inlined. %>
|
|
23
|
-
<%= stylesheet_link_tag "activemail/activemail", media: "all" %>
|
|
24
|
-
</head>
|
|
3
|
+
<%= render "layouts/activemail/document_head" %>
|
|
25
4
|
|
|
26
5
|
<body style="margin:0;padding:0;word-spacing:normal;">
|
|
27
6
|
<div role="article" aria-roledescription="email" lang="<%= I18n.locale %>" style="width:100%;">
|