bootstrap-email 1.0.0.alpha1.2 → 1.0.0.alpha2
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/VERSION +1 -1
- data/core/bootstrap-email.scss +2 -0
- data/core/bootstrap-head.scss +1 -1
- data/core/scss/_selectors_for_utils.scss +24 -0
- data/core/scss/_utilities.scss +24 -4
- data/core/scss/components/_grid.scss +3 -0
- data/core/scss/components/_stack.scss +33 -0
- data/core/scss/utilities/_sizing.scss +17 -2
- data/core/scss/utilities/_typography.scss +4 -10
- data/core/scss/utilities/_valign.scss +5 -0
- data/core/templates/body.html.erb +1 -1
- data/core/templates/container.html.erb +3 -3
- data/core/templates/table-left.html.erb +1 -1
- data/core/templates/table-to-tbody.html.erb +5 -0
- data/core/templates/{row.html.erb → table-to-tr.html.erb} +1 -1
- data/core/templates/table.html.erb +1 -1
- data/core/templates/td.html.erb +3 -0
- data/core/templates/tr.html.erb +5 -0
- data/lib/{bootstrap_email.rb → bootstrap-email.rb} +1 -0
- data/lib/bootstrap-email/bootstrap_email_cli.rb +3 -3
- data/lib/bootstrap-email/compiler.rb +30 -50
- data/lib/bootstrap-email/components/align.rb +7 -6
- data/lib/bootstrap-email/components/base.rb +31 -3
- data/lib/bootstrap-email/components/block.rb +13 -0
- data/lib/bootstrap-email/components/body.rb +2 -14
- data/lib/bootstrap-email/components/card.rb +2 -2
- data/lib/bootstrap-email/components/force_encoding.rb +16 -0
- data/lib/bootstrap-email/components/grid.rb +2 -2
- data/lib/bootstrap-email/components/head_style.rb +33 -0
- data/lib/bootstrap-email/components/hr.rb +1 -1
- data/lib/bootstrap-email/components/paragraph.rb +2 -13
- data/lib/bootstrap-email/components/preview_text.rb +18 -0
- data/lib/bootstrap-email/components/spacing.rb +4 -5
- data/lib/bootstrap-email/components/stack.rb +30 -0
- data/lib/bootstrap-email/components/version_comment.rb +15 -0
- data/lib/bootstrap-email/erb.rb +9 -0
- data/lib/bootstrap-email/rails/action_mailer.rb +1 -0
- data/lib/bootstrap-email/sass_cache.rb +42 -22
- metadata +20 -9
- data/core/templates/col.html.erb +0 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 759e1882e8d2e275343ff2d3b6c92a358f350f35319c14208c26b7641ef989de
|
4
|
+
data.tar.gz: 6fb098dd28e51f2c962aa0c774b4948e1ad0499282d66927755a18539c1aa00e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d5f19d06827848b6a1cdc3f47ed99e66f46d0465748ac7c46d49654f7815e274c3462028442518b0cf6bd6cfb58e21becbc85204eab5abd45445e1dfe962594b
|
7
|
+
data.tar.gz: 4b9716bca2ccc400c22b30e12a6c9b7a03be72d9e595c854d99e92ffdf8d78d4b3dd8061fe914a9e60e3d3868df0e23d7383196a10f75d24514b73ed6ccaef95
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.0.0.
|
1
|
+
1.0.0.alpha2
|
data/core/bootstrap-email.scss
CHANGED
@@ -16,6 +16,7 @@
|
|
16
16
|
@import 'scss/components/image';
|
17
17
|
@import 'scss/components/preview';
|
18
18
|
@import 'scss/components/table';
|
19
|
+
@import 'scss/components/stack';
|
19
20
|
|
20
21
|
@import 'scss/utilities/background';
|
21
22
|
@import 'scss/utilities/border';
|
@@ -26,3 +27,4 @@
|
|
26
27
|
@import 'scss/utilities/spacing';
|
27
28
|
@import 'scss/utilities/text-decoration';
|
28
29
|
@import 'scss/utilities/typography';
|
30
|
+
@import 'scss/utilities/valign';
|
data/core/bootstrap-head.scss
CHANGED
@@ -98,3 +98,27 @@
|
|
98
98
|
}
|
99
99
|
}
|
100
100
|
}
|
101
|
+
|
102
|
+
//
|
103
|
+
// Text align
|
104
|
+
//
|
105
|
+
@mixin text-align-util($class) {
|
106
|
+
#{$class} {
|
107
|
+
&,
|
108
|
+
& > tbody > tr > td {
|
109
|
+
@content
|
110
|
+
}
|
111
|
+
}
|
112
|
+
}
|
113
|
+
|
114
|
+
//
|
115
|
+
// Vertical align
|
116
|
+
//
|
117
|
+
@mixin valign-util($class) {
|
118
|
+
#{$class} {
|
119
|
+
&,
|
120
|
+
& > tbody > tr > td {
|
121
|
+
@content
|
122
|
+
}
|
123
|
+
}
|
124
|
+
}
|
data/core/scss/_utilities.scss
CHANGED
@@ -117,9 +117,29 @@ $border-widths: (
|
|
117
117
|
) !default;
|
118
118
|
|
119
119
|
$border-radiuses: (
|
120
|
-
'-
|
121
|
-
'':
|
122
|
-
'
|
120
|
+
'-none': 0px,
|
121
|
+
'-sm': 2px,
|
122
|
+
'': 4px,
|
123
|
+
'-md': 6px,
|
124
|
+
'-lg': 8px,
|
123
125
|
'-xl': 12px,
|
124
|
-
'-
|
126
|
+
'-2xl': 16px,
|
127
|
+
'-3xl': 24px,
|
128
|
+
'-full': 9999px
|
129
|
+
) !default;
|
130
|
+
|
131
|
+
$vertical-align: (
|
132
|
+
top,
|
133
|
+
middle,
|
134
|
+
bottom,
|
135
|
+
baseline,
|
136
|
+
text-top,
|
137
|
+
text-bottom
|
138
|
+
) !default;
|
139
|
+
|
140
|
+
$text-align: (
|
141
|
+
left,
|
142
|
+
right,
|
143
|
+
center,
|
144
|
+
justify
|
125
145
|
) !default;
|
@@ -0,0 +1,33 @@
|
|
1
|
+
@each $space, $value in $spacers {
|
2
|
+
table.stack-x.gap-#{$space} > tbody > tr > td {
|
3
|
+
padding-right: $value;
|
4
|
+
&:last-child {
|
5
|
+
padding-right: 0;
|
6
|
+
}
|
7
|
+
}
|
8
|
+
table.stack-y.gap-#{$space} > tbody > tr {
|
9
|
+
& > td {
|
10
|
+
padding-bottom: $value;
|
11
|
+
}
|
12
|
+
&:last-child > td {
|
13
|
+
padding-bottom: 0;
|
14
|
+
}
|
15
|
+
}
|
16
|
+
}
|
17
|
+
|
18
|
+
.stack-cell {
|
19
|
+
vertical-align: top;
|
20
|
+
text-align: left;
|
21
|
+
}
|
22
|
+
|
23
|
+
@each $align in $vertical-align {
|
24
|
+
table.stack-valign-#{$align} > tbody > tr > td {
|
25
|
+
vertical-align: $align;
|
26
|
+
}
|
27
|
+
}
|
28
|
+
|
29
|
+
@each $align in $text-align {
|
30
|
+
table.stack-align-#{$align} > tbody > tr > td {
|
31
|
+
text-align: $align;
|
32
|
+
}
|
33
|
+
}
|
@@ -1,12 +1,12 @@
|
|
1
1
|
@each $prefix in $breakpoints {
|
2
2
|
@each $name, $property in $sizing-types {
|
3
|
-
@include sizing-util('
|
3
|
+
@include sizing-util('.max-#{$name}-#{$prefix}full') {
|
4
4
|
max-#{$property}: 100%;
|
5
5
|
-premailer-#{$property}: 100%;
|
6
6
|
#{$property}: 100%;
|
7
7
|
}
|
8
8
|
@each $size, $value in $sizing {
|
9
|
-
@include sizing-util('
|
9
|
+
@include sizing-util('.max-#{$name}-#{$prefix}#{$size}') {
|
10
10
|
max-#{$property}: $value;
|
11
11
|
-premailer-#{$property}: strip-unit($value);
|
12
12
|
#{$property}: 100%;
|
@@ -14,3 +14,18 @@
|
|
14
14
|
}
|
15
15
|
}
|
16
16
|
}
|
17
|
+
|
18
|
+
@each $prefix in $breakpoints {
|
19
|
+
@each $name, $property in $sizing-types {
|
20
|
+
@include sizing-util('.#{$name}-#{$prefix}full') {
|
21
|
+
-premailer-#{$property}: 100%;
|
22
|
+
#{$property}: 100%;
|
23
|
+
}
|
24
|
+
@each $size, $value in $sizing {
|
25
|
+
@include sizing-util('.#{$name}-#{$prefix}#{$size}') {
|
26
|
+
-premailer-#{$property}: strip-unit($value);
|
27
|
+
#{$property}: $value;
|
28
|
+
}
|
29
|
+
}
|
30
|
+
}
|
31
|
+
}
|
@@ -36,16 +36,10 @@ h1, h2, h3, h4, h5, h6,
|
|
36
36
|
}
|
37
37
|
}
|
38
38
|
|
39
|
-
|
40
|
-
text-align
|
41
|
-
|
42
|
-
|
43
|
-
.text-right {
|
44
|
-
text-align: right !important;
|
45
|
-
}
|
46
|
-
|
47
|
-
.text-center {
|
48
|
-
text-align: center !important;
|
39
|
+
@each $align in $text-align {
|
40
|
+
@include text-align-util('.text-#{$align}') {
|
41
|
+
text-align: $align !important;
|
42
|
+
}
|
49
43
|
}
|
50
44
|
|
51
45
|
p {
|
@@ -1,14 +1,14 @@
|
|
1
|
-
<table class="<%= classes %>">
|
1
|
+
<table class="<%= classes %>" role="presentation">
|
2
2
|
<tbody>
|
3
3
|
<tr>
|
4
4
|
<td align="center">
|
5
5
|
<!--[if (gte mso 9)|(IE)]>
|
6
|
-
<table align="center">
|
6
|
+
<table align="center" role="presentation">
|
7
7
|
<tbody>
|
8
8
|
<tr>
|
9
9
|
<td width="600">
|
10
10
|
<![endif]-->
|
11
|
-
<table align="center">
|
11
|
+
<table align="center" role="presentation">
|
12
12
|
<tbody>
|
13
13
|
<tr>
|
14
14
|
<td>
|
@@ -1,10 +1,10 @@
|
|
1
|
-
require_relative '../
|
1
|
+
require_relative '../bootstrap-email'
|
2
2
|
require 'optparse'
|
3
3
|
require 'fileutils'
|
4
4
|
|
5
5
|
input = nil
|
6
6
|
options = {
|
7
|
-
destination: '
|
7
|
+
destination: 'compiled',
|
8
8
|
type: :file
|
9
9
|
}
|
10
10
|
|
@@ -17,7 +17,7 @@ parser = OptionParser.new do |opts|
|
|
17
17
|
opts.separator ' bootstrap-email email.html > out.html'
|
18
18
|
opts.separator ' bootstrap-email ./public/index.html'
|
19
19
|
opts.separator ' bootstrap-email -s \'<a href="#" class="btn btn-primary">Some Button</a>\''
|
20
|
-
opts.separator ' bootstrap-email -p \'emails/*\' -d emails/
|
20
|
+
opts.separator ' bootstrap-email -p \'emails/*\' -d emails/compiled'
|
21
21
|
opts.separator ' bootstrap-email -p \'views/emails/*\' -d \'views/compiled_emails\''
|
22
22
|
opts.separator ' cat input.html | bootstrap-email'
|
23
23
|
opts.separator ''
|
@@ -13,21 +13,30 @@ module BootstrapEmail
|
|
13
13
|
when :file
|
14
14
|
html = File.read(input)
|
15
15
|
end
|
16
|
+
html = add_layout!(html)
|
16
17
|
build_premailer_doc(html, options)
|
17
18
|
end
|
18
19
|
|
19
20
|
def perform_full_compile
|
20
21
|
compile_html!
|
21
22
|
inline_css!
|
22
|
-
|
23
|
-
inject_comment!
|
23
|
+
configure_html!
|
24
24
|
finalize_document!
|
25
25
|
end
|
26
26
|
|
27
27
|
private
|
28
28
|
|
29
|
+
def add_layout!(html)
|
30
|
+
document = Nokogiri::HTML(html)
|
31
|
+
return html unless document.at_css('head').nil?
|
32
|
+
|
33
|
+
BootstrapEmail::Erb.template(
|
34
|
+
File.expand_path('../../core/layout.html.erb', __dir__),
|
35
|
+
contents: html
|
36
|
+
)
|
37
|
+
end
|
38
|
+
|
29
39
|
def build_premailer_doc(html, options)
|
30
|
-
html = add_layout!(html)
|
31
40
|
SassC.load_paths << File.expand_path('../../core', __dir__)
|
32
41
|
css_string = BootstrapEmail::SassCache.compile('bootstrap-email', config_path: options[:config_path], style: :expanded)
|
33
42
|
self.premailer = Premailer.new(
|
@@ -39,80 +48,51 @@ module BootstrapEmail
|
|
39
48
|
self.doc = premailer.doc
|
40
49
|
end
|
41
50
|
|
42
|
-
def add_layout!(html)
|
43
|
-
document = Nokogiri::HTML(html)
|
44
|
-
return document.to_html unless document.at_css('head').nil?
|
45
|
-
|
46
|
-
namespace = OpenStruct.new(contents: ERB.new(document.to_html).result)
|
47
|
-
template_html = File.read(File.expand_path('../../core/layout.html.erb', __dir__))
|
48
|
-
ERB.new(template_html).result(namespace.instance_eval { binding })
|
49
|
-
end
|
50
|
-
|
51
51
|
def compile_html!
|
52
|
+
BootstrapEmail::Component::Block.build(doc)
|
53
|
+
|
52
54
|
BootstrapEmail::Component::Button.build(doc)
|
53
55
|
BootstrapEmail::Component::Badge.build(doc)
|
54
56
|
BootstrapEmail::Component::Alert.build(doc)
|
55
57
|
BootstrapEmail::Component::Card.build(doc)
|
56
|
-
BootstrapEmail::Component::Paragraph.build(doc)
|
58
|
+
# BootstrapEmail::Component::Paragraph.build(doc) this might be too much
|
57
59
|
BootstrapEmail::Component::Hr.build(doc)
|
58
60
|
BootstrapEmail::Component::Container.build(doc)
|
59
61
|
BootstrapEmail::Component::Grid.build(doc)
|
60
|
-
BootstrapEmail::Component::
|
61
|
-
|
62
|
+
BootstrapEmail::Component::Stack.build(doc)
|
63
|
+
|
64
|
+
BootstrapEmail::Component::Spacing.build(doc)
|
62
65
|
BootstrapEmail::Component::Padding.build(doc)
|
63
66
|
BootstrapEmail::Component::Margin.build(doc)
|
64
|
-
BootstrapEmail::Component::Spacing.build(doc)
|
65
67
|
BootstrapEmail::Component::Spacer.build(doc)
|
68
|
+
|
66
69
|
BootstrapEmail::Component::Table.build(doc)
|
67
70
|
BootstrapEmail::Component::Body.build(doc)
|
71
|
+
BootstrapEmail::Component::Align.build(doc)
|
72
|
+
BootstrapEmail::Component::Color.build(doc)
|
73
|
+
|
74
|
+
BootstrapEmail::Component::PreviewText.build(doc)
|
68
75
|
end
|
69
76
|
|
70
77
|
def inline_css!
|
71
78
|
premailer.to_inline_css
|
72
79
|
end
|
73
80
|
|
74
|
-
def
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
def inject_comment!
|
79
|
-
doc.at_css('head').prepend_child(bootstrap_email_comment)
|
81
|
+
def configure_html!
|
82
|
+
BootstrapEmail::Component::ForceEncoding.build(doc)
|
83
|
+
BootstrapEmail::Component::HeadStyle.build(doc)
|
84
|
+
BootstrapEmail::Component::VersionComment.build(doc)
|
80
85
|
end
|
81
86
|
|
82
87
|
def finalize_document!
|
88
|
+
html = BootstrapEmail::Component::ForceEncoding.replace(doc.to_html)
|
83
89
|
case type
|
84
90
|
when :rails
|
85
|
-
(@mail.html_part || @mail).body =
|
91
|
+
(@mail.html_part || @mail).body = html
|
86
92
|
@mail
|
87
93
|
when :string, :file
|
88
|
-
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
def bootstrap_email_head
|
93
|
-
html_string = <<-INLINE
|
94
|
-
<style type="text/css">
|
95
|
-
#{purged_css_from_head}
|
96
|
-
</style>
|
97
|
-
INLINE
|
98
|
-
html_string
|
99
|
-
end
|
100
|
-
|
101
|
-
def bootstrap_email_comment
|
102
|
-
"\n <!-- Compiled with Bootstrap Email version: #{BootstrapEmail::VERSION} -->"
|
103
|
-
end
|
104
|
-
|
105
|
-
def purged_css_from_head
|
106
|
-
default, custom = BootstrapEmail::SassCache.compile('bootstrap-head').split('/*! allow_purge_after */')
|
107
|
-
# get each CSS declaration
|
108
|
-
custom.scan(/\w*\.[\w\-]*[\s\S\n]+?(?=})}{1}/).each do |group|
|
109
|
-
# get the first class for each comma separated CSS declaration
|
110
|
-
exist = group.scan(/(\.[\w\-]*).*?((,+?)|{+?)/).map(&:first).uniq.any? do |selector|
|
111
|
-
!doc.at_css(selector).nil?
|
112
|
-
end
|
113
|
-
custom.sub!(group, '') unless exist
|
94
|
+
html
|
114
95
|
end
|
115
|
-
(default + custom).gsub(/\n\s*\n+/, "\n")
|
116
96
|
end
|
117
97
|
end
|
118
98
|
end
|
@@ -3,16 +3,17 @@ module BootstrapEmail
|
|
3
3
|
class Align < Base
|
4
4
|
def build
|
5
5
|
['left', 'center', 'right'].each do |type|
|
6
|
-
|
7
|
-
|
6
|
+
full_type = "align-#{type}"
|
7
|
+
each_node(".#{full_type}") do |node|
|
8
|
+
align_helper(node, full_type, type)
|
8
9
|
end
|
9
10
|
end
|
10
11
|
end
|
11
12
|
|
12
|
-
def align_helper(node, type)
|
13
|
-
|
14
|
-
node['class'] = node['class'].sub(
|
15
|
-
node = node.replace(template('table', classes:
|
13
|
+
def align_helper(node, full_type, type)
|
14
|
+
unless table?(node) || td?(node)
|
15
|
+
node['class'] = node['class'].sub(full_type, '')
|
16
|
+
node = node.replace(template('table', classes: full_type, contents: node.to_html))[0]
|
16
17
|
end
|
17
18
|
node['align'] = type
|
18
19
|
end
|
@@ -10,17 +10,45 @@ module BootstrapEmail
|
|
10
10
|
new(doc).build
|
11
11
|
end
|
12
12
|
|
13
|
+
private
|
14
|
+
|
13
15
|
def template(file, locals_hash = {})
|
14
16
|
locals_hash[:classes] = locals_hash[:classes].split.join(' ') if locals_hash[:classes]
|
15
|
-
|
16
|
-
|
17
|
-
|
17
|
+
BootstrapEmail::Erb.template(
|
18
|
+
File.expand_path("../../../core/templates/#{file}.html.erb", __dir__),
|
19
|
+
locals_hash
|
20
|
+
)
|
18
21
|
end
|
19
22
|
|
20
23
|
def each_node(css_lookup, &blk)
|
21
24
|
# sort by youngest child and traverse backwards up the tree
|
22
25
|
doc.css(css_lookup).sort_by { |n| n.ancestors.size }.reverse!.each(&blk)
|
23
26
|
end
|
27
|
+
|
28
|
+
def add_class(node, class_name)
|
29
|
+
node['class'] ||= ''
|
30
|
+
node['class'] += class_name
|
31
|
+
end
|
32
|
+
|
33
|
+
def margin?(node)
|
34
|
+
margin_top?(node) || margin_bottom?(node)
|
35
|
+
end
|
36
|
+
|
37
|
+
def margin_top?(node)
|
38
|
+
node['class'].to_s.match?(/m[ty]{1}-(lg-)?\d+/)
|
39
|
+
end
|
40
|
+
|
41
|
+
def margin_bottom?(node)
|
42
|
+
node['class'].to_s.match?(/m[by]{1}-(lg-)?\d+/)
|
43
|
+
end
|
44
|
+
|
45
|
+
def table?(node)
|
46
|
+
node.name == 'table'
|
47
|
+
end
|
48
|
+
|
49
|
+
def td?(node)
|
50
|
+
node.name == 'td'
|
51
|
+
end
|
24
52
|
end
|
25
53
|
end
|
26
54
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module BootstrapEmail
|
2
|
+
module Component
|
3
|
+
class Block < Base
|
4
|
+
def build
|
5
|
+
each_node('block, .to-table') do |node|
|
6
|
+
# add .to-table if it's not already there
|
7
|
+
class_name = node['class'].to_s.split << 'to-table'
|
8
|
+
node.replace(template('table', classes: class_name.uniq.join(' '), contents: node.inner_html))
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -2,20 +2,8 @@ module BootstrapEmail
|
|
2
2
|
module Component
|
3
3
|
class Body < Base
|
4
4
|
def build
|
5
|
-
|
6
|
-
|
7
|
-
end
|
8
|
-
end
|
9
|
-
|
10
|
-
def preview_text
|
11
|
-
preview_node = doc.at_css('preview')
|
12
|
-
return if preview_node.nil?
|
13
|
-
|
14
|
-
# apply spacing after the text max of 100 characters so it doesn't show body text
|
15
|
-
preview_node.content += ' ' * [(100 - preview_node.content.length.to_i), 0].max
|
16
|
-
node = template('div', classes: 'preview', contents: preview_node.content)
|
17
|
-
preview_node.remove
|
18
|
-
node
|
5
|
+
body = doc.at_css('body')
|
6
|
+
body.inner_html = template('body', classes: "#{body['class']} body", contents: body.inner_html)
|
19
7
|
end
|
20
8
|
end
|
21
9
|
end
|
@@ -3,10 +3,10 @@ module BootstrapEmail
|
|
3
3
|
class Card < Base
|
4
4
|
def build
|
5
5
|
each_node('.card') do |node|
|
6
|
-
node.replace(template('table', classes: node['class'], contents: node.
|
6
|
+
node.replace(template('table', classes: node['class'], contents: node.inner_html))
|
7
7
|
end
|
8
8
|
each_node('.card-body') do |node|
|
9
|
-
node.replace(template('table', classes: node['class'], contents: node.
|
9
|
+
node.replace(template('table', classes: node['class'], contents: node.inner_html))
|
10
10
|
end
|
11
11
|
end
|
12
12
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module BootstrapEmail
|
2
|
+
module Component
|
3
|
+
class ForceEncoding < Base
|
4
|
+
def build
|
5
|
+
body = doc.at_css('body')
|
6
|
+
body.add_child('<force-encoding></force-encoding>')
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.replace(html)
|
10
|
+
# force utf-8 character encoded in iOS Mail: https://github.com/bootstrap-email/bootstrap-email/issues/50
|
11
|
+
# this needs to be done after the document has been outputted to a string so it doesn't get converted
|
12
|
+
html.sub('<force-encoding></force-encoding>', '<div id="force-encoding-to-utf-8" style="display: none;">➿</div>')
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -3,10 +3,10 @@ module BootstrapEmail
|
|
3
3
|
class Grid < Base
|
4
4
|
def build
|
5
5
|
each_node('.row') do |node|
|
6
|
-
node.replace(template('
|
6
|
+
node.replace(template('table-to-tr', classes: node['class'], contents: node.inner_html))
|
7
7
|
end
|
8
8
|
each_node('*[class*=col]') do |node|
|
9
|
-
node.replace(template('
|
9
|
+
node.replace(template('td', classes: node['class'], contents: node.inner_html))
|
10
10
|
end
|
11
11
|
end
|
12
12
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module BootstrapEmail
|
2
|
+
module Component
|
3
|
+
class HeadStyle < Base
|
4
|
+
def build
|
5
|
+
doc.at_css('head').add_child(bootstrap_email_head)
|
6
|
+
end
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def bootstrap_email_head
|
11
|
+
html_string = <<-INLINE
|
12
|
+
<style type="text/css">
|
13
|
+
#{purged_css_from_head}
|
14
|
+
</style>
|
15
|
+
INLINE
|
16
|
+
html_string
|
17
|
+
end
|
18
|
+
|
19
|
+
def purged_css_from_head
|
20
|
+
default, custom = BootstrapEmail::SassCache.compile('bootstrap-head').split('/*! allow_purge_after */')
|
21
|
+
# get each CSS declaration
|
22
|
+
custom.scan(/\w*\.[\w\-]*[\s\S\n]+?(?=})}{1}/).each do |group|
|
23
|
+
# get the first class for each comma separated CSS declaration
|
24
|
+
exist = group.scan(/(\.[\w\-]*).*?((,+?)|{+?)/).map(&:first).uniq.any? do |selector|
|
25
|
+
!doc.at_css(selector).nil?
|
26
|
+
end
|
27
|
+
custom.sub!(group, '') unless exist
|
28
|
+
end
|
29
|
+
(default + custom).gsub(/\n\s*\n+/, "\n")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -3,7 +3,7 @@ module BootstrapEmail
|
|
3
3
|
class Hr < Base
|
4
4
|
def build
|
5
5
|
each_node('hr') do |node|
|
6
|
-
default_margin =
|
6
|
+
default_margin = margin?(node) ? '' : 'my-5'
|
7
7
|
node.replace(template('table', classes: "#{default_margin} hr #{node['class']}", contents: ''))
|
8
8
|
end
|
9
9
|
end
|
@@ -3,22 +3,11 @@ module BootstrapEmail
|
|
3
3
|
class Paragraph < Base
|
4
4
|
def build
|
5
5
|
each_node('p') do |node|
|
6
|
-
next if margin?(node)
|
6
|
+
next if margin?(node)
|
7
7
|
|
8
|
-
node
|
9
|
-
node['class'] += 'mb-4'
|
8
|
+
add_class(node, 'mb-4')
|
10
9
|
end
|
11
10
|
end
|
12
|
-
|
13
|
-
private
|
14
|
-
|
15
|
-
def margin?(node)
|
16
|
-
node['class'].to_s.match?(/m[tby]{1}-(lg-)?\d+/)
|
17
|
-
end
|
18
|
-
|
19
|
-
def space_y?(node)
|
20
|
-
node.parent['class'].to_s.match?(/space-y-(lg-)?\d+/)
|
21
|
-
end
|
22
11
|
end
|
23
12
|
end
|
24
13
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module BootstrapEmail
|
2
|
+
module Component
|
3
|
+
class PreviewText < Base
|
4
|
+
def build
|
5
|
+
preview_node = doc.at_css('preview')
|
6
|
+
return if preview_node.nil?
|
7
|
+
|
8
|
+
# apply spacing after the text max of 100 characters so it doesn't show body text
|
9
|
+
preview_node.inner_html += ' ' * [(100 - preview_node.content.length.to_i), 0].max
|
10
|
+
node = template('div', classes: 'preview', contents: preview_node.content)
|
11
|
+
preview_node.remove
|
12
|
+
|
13
|
+
body = doc.at_css('body')
|
14
|
+
body.prepend_child(node)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -5,11 +5,10 @@ module BootstrapEmail
|
|
5
5
|
each_node('*[class*=space-y-]') do |node|
|
6
6
|
spacer = node['class'].scan(/space-y-((lg-)?\d+)/)[0][0]
|
7
7
|
# get all direct children except the first
|
8
|
-
node.xpath('./*[position()
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
child.replace(html)
|
8
|
+
node.xpath('./*[position() < last()] | ./tbody/tr/td/*[position() < last()]').each do |child|
|
9
|
+
next if margin_bottom?(child)
|
10
|
+
|
11
|
+
add_class(child, "mb-#{spacer}")
|
13
12
|
end
|
14
13
|
end
|
15
14
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module BootstrapEmail
|
2
|
+
module Component
|
3
|
+
class Stack < Base
|
4
|
+
def build
|
5
|
+
stack_x
|
6
|
+
stack_y
|
7
|
+
end
|
8
|
+
|
9
|
+
def stack_x
|
10
|
+
each_node('.stack-x') do |node|
|
11
|
+
html = ''
|
12
|
+
node.xpath('./*').each do |child|
|
13
|
+
html += template('td', classes: 'stack-cell', contents: child.to_html)
|
14
|
+
end
|
15
|
+
node.replace(template('table-to-tr', classes: node['class'], contents: html))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def stack_y
|
20
|
+
each_node('.stack-y') do |node|
|
21
|
+
html = ''
|
22
|
+
node.xpath('./*').each do |child|
|
23
|
+
html += template('tr', classes: 'stack-cell', contents: child.to_html)
|
24
|
+
end
|
25
|
+
node.replace(template('table-to-tbody', classes: node['class'], contents: html))
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module BootstrapEmail
|
2
|
+
module Component
|
3
|
+
class VersionComment < Base
|
4
|
+
def build
|
5
|
+
doc.at_css('head').prepend_child(bootstrap_email_comment)
|
6
|
+
end
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def bootstrap_email_comment
|
11
|
+
"\n <!-- Compiled with Bootstrap Email version: #{BootstrapEmail::VERSION} -->"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -5,6 +5,7 @@ ActiveSupport.on_load(:action_mailer, {yield: true}) do |action_mailer|
|
|
5
5
|
bootstrap = BootstrapEmail::Compiler.new(mail(*args, &block), type: :rails)
|
6
6
|
bootstrap.perform_full_compile
|
7
7
|
end
|
8
|
+
alias bootstrap_email bootstrap_mail
|
8
9
|
alias make_bootstrap_mail bootstrap_mail
|
9
10
|
end
|
10
11
|
end
|
@@ -3,36 +3,47 @@ module BootstrapEmail
|
|
3
3
|
CACHE_DIR = File.expand_path('../../.sass-cache', __dir__)
|
4
4
|
SASS_DIR = File.expand_path('../../core', __dir__)
|
5
5
|
|
6
|
-
def self.compile(
|
7
|
-
|
8
|
-
|
6
|
+
def self.compile(type, config_path: nil, style: :compressed)
|
7
|
+
new(type, config_path, style).compile
|
8
|
+
end
|
9
9
|
|
10
|
-
|
11
|
-
|
10
|
+
attr_accessor :type, :style, :file_path, :config_file, :checksum
|
11
|
+
|
12
|
+
def initialize(type, config_path, style)
|
13
|
+
self.type = type
|
14
|
+
self.style = style
|
15
|
+
self.file_path = "#{SASS_DIR}/#{type}"
|
16
|
+
self.config_file = load_config(config_path)
|
17
|
+
self.checksum = checksum_files
|
18
|
+
end
|
19
|
+
|
20
|
+
def compile
|
21
|
+
cache_path = "#{CACHE_DIR}/#{checksum}/#{type}.css"
|
22
|
+
unless cached?(cache_path)
|
23
|
+
compile_and_cache_scss(cache_path)
|
24
|
+
end
|
25
|
+
File.read(cache_path)
|
26
|
+
end
|
12
27
|
|
28
|
+
private
|
29
|
+
|
30
|
+
def load_config(config_path)
|
31
|
+
lookup_locations = ["#{type}.config.scss", "app/assets/stylesheets/#{type}.config.scss"]
|
32
|
+
locations = lookup_locations.select { |location| File.exist?(File.expand_path(location, Dir.pwd)) }
|
13
33
|
if config_path && File.exist?(config_path)
|
14
34
|
# check if custom config was passed in
|
15
|
-
|
35
|
+
replace_config(File.read(config_path))
|
16
36
|
elsif locations.any?
|
17
|
-
|
37
|
+
# look for common lookup locations of config
|
38
|
+
replace_config(File.read(File.expand_path(locations.first, Dir.pwd)))
|
18
39
|
end
|
40
|
+
end
|
19
41
|
|
20
|
-
|
21
|
-
|
22
|
-
if cached?(cache_path)
|
23
|
-
File.read(cache_path)
|
24
|
-
else
|
25
|
-
file = config_file.nil? ? File.read("#{path}.scss") : config_file
|
26
|
-
SassC::Engine.new(file, style: style).render.tap do |css|
|
27
|
-
Dir.mkdir(CACHE_DIR) unless File.directory?(CACHE_DIR)
|
28
|
-
Dir.mkdir("#{CACHE_DIR}/#{check}") unless File.directory?("#{CACHE_DIR}/#{check}")
|
29
|
-
File.write(cache_path, css)
|
30
|
-
puts "New css file cached for #{name}"
|
31
|
-
end
|
32
|
-
end
|
42
|
+
def replace_config(config_file)
|
43
|
+
config_file.gsub("//= @import #{type};", "@import '#{file_path}';")
|
33
44
|
end
|
34
45
|
|
35
|
-
def
|
46
|
+
def checksum_files
|
36
47
|
checksums = config_file.nil? ? [] : [Digest::SHA1.hexdigest(config_file)]
|
37
48
|
Dir.glob('../../core/**/*.scss', base: __dir__).each do |path|
|
38
49
|
checksums << Digest::SHA1.file(File.expand_path(path, __dir__)).hexdigest
|
@@ -40,8 +51,17 @@ module BootstrapEmail
|
|
40
51
|
Digest::SHA1.hexdigest(checksums.join)
|
41
52
|
end
|
42
53
|
|
43
|
-
def
|
54
|
+
def cached?(cache_path)
|
44
55
|
File.file?(cache_path)
|
45
56
|
end
|
57
|
+
|
58
|
+
def compile_and_cache_scss(cache_path)
|
59
|
+
file = config_file || File.read("#{file_path}.scss")
|
60
|
+
css = SassC::Engine.new(file, style: style).render
|
61
|
+
Dir.mkdir(CACHE_DIR) unless File.directory?(CACHE_DIR)
|
62
|
+
Dir.mkdir("#{CACHE_DIR}/#{checksum}") unless File.directory?("#{CACHE_DIR}/#{checksum}")
|
63
|
+
File.write(cache_path, css)
|
64
|
+
puts "New css file cached for #{type}"
|
65
|
+
end
|
46
66
|
end
|
47
67
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bootstrap-email
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.0.
|
4
|
+
version: 1.0.0.alpha2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stuart Yamartino
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-03-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: nokogiri
|
@@ -30,28 +30,28 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '1.
|
33
|
+
version: '1.7'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '1.
|
40
|
+
version: '1.7'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: sassc
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '2.
|
47
|
+
version: '2.0'
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '2.
|
54
|
+
version: '2.0'
|
55
55
|
description:
|
56
56
|
email: stu@stuyam.com
|
57
57
|
executables:
|
@@ -80,6 +80,7 @@ files:
|
|
80
80
|
- core/scss/components/_hr.scss
|
81
81
|
- core/scss/components/_image.scss
|
82
82
|
- core/scss/components/_preview.scss
|
83
|
+
- core/scss/components/_stack.scss
|
83
84
|
- core/scss/components/_table.scss
|
84
85
|
- core/scss/utilities/_background.scss
|
85
86
|
- core/scss/utilities/_border-radius.scss
|
@@ -90,38 +91,48 @@ files:
|
|
90
91
|
- core/scss/utilities/_spacing.scss
|
91
92
|
- core/scss/utilities/_text-decoration.scss
|
92
93
|
- core/scss/utilities/_typography.scss
|
94
|
+
- core/scss/utilities/_valign.scss
|
93
95
|
- core/templates/body.html.erb
|
94
|
-
- core/templates/col.html.erb
|
95
96
|
- core/templates/container.html.erb
|
96
97
|
- core/templates/div.html.erb
|
97
|
-
- core/templates/row.html.erb
|
98
98
|
- core/templates/table-left.html.erb
|
99
|
+
- core/templates/table-to-tbody.html.erb
|
100
|
+
- core/templates/table-to-tr.html.erb
|
99
101
|
- core/templates/table.html.erb
|
102
|
+
- core/templates/td.html.erb
|
103
|
+
- core/templates/tr.html.erb
|
104
|
+
- lib/bootstrap-email.rb
|
100
105
|
- lib/bootstrap-email/bootstrap_email_cli.rb
|
101
106
|
- lib/bootstrap-email/compiler.rb
|
102
107
|
- lib/bootstrap-email/components/alert.rb
|
103
108
|
- lib/bootstrap-email/components/align.rb
|
104
109
|
- lib/bootstrap-email/components/badge.rb
|
105
110
|
- lib/bootstrap-email/components/base.rb
|
111
|
+
- lib/bootstrap-email/components/block.rb
|
106
112
|
- lib/bootstrap-email/components/body.rb
|
107
113
|
- lib/bootstrap-email/components/button.rb
|
108
114
|
- lib/bootstrap-email/components/card.rb
|
109
115
|
- lib/bootstrap-email/components/color.rb
|
110
116
|
- lib/bootstrap-email/components/container.rb
|
117
|
+
- lib/bootstrap-email/components/force_encoding.rb
|
111
118
|
- lib/bootstrap-email/components/grid.rb
|
119
|
+
- lib/bootstrap-email/components/head_style.rb
|
112
120
|
- lib/bootstrap-email/components/hr.rb
|
113
121
|
- lib/bootstrap-email/components/margin.rb
|
114
122
|
- lib/bootstrap-email/components/padding.rb
|
115
123
|
- lib/bootstrap-email/components/paragraph.rb
|
124
|
+
- lib/bootstrap-email/components/preview_text.rb
|
116
125
|
- lib/bootstrap-email/components/spacer.rb
|
117
126
|
- lib/bootstrap-email/components/spacing.rb
|
127
|
+
- lib/bootstrap-email/components/stack.rb
|
118
128
|
- lib/bootstrap-email/components/table.rb
|
129
|
+
- lib/bootstrap-email/components/version_comment.rb
|
130
|
+
- lib/bootstrap-email/erb.rb
|
119
131
|
- lib/bootstrap-email/initialize.rb
|
120
132
|
- lib/bootstrap-email/rails/action_mailer.rb
|
121
133
|
- lib/bootstrap-email/rails/engine.rb
|
122
134
|
- lib/bootstrap-email/sass_cache.rb
|
123
135
|
- lib/bootstrap-email/version.rb
|
124
|
-
- lib/bootstrap_email.rb
|
125
136
|
homepage: https://bootstrapemail.com
|
126
137
|
licenses:
|
127
138
|
- MIT
|
data/core/templates/col.html.erb
DELETED