bootstrap-email 0.0.0.alpha.1 → 0.0.0.alpha.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/bootstrap-email.rb +143 -4
- data/lib/bootstrap-email/action_mailer.rb +15 -13
- data/lib/bootstrap-email/version.rb +1 -1
- metadata +40 -14
- data/lib/bootstrap-email/bootstrap_email.rb +0 -75
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d697ca73b6f0d8dd482151f4fefb5362100bf574
|
4
|
+
data.tar.gz: 98fdfe5fe8d602cf595750bdad3a123799667db6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6d2fc92efb6f6c04e261e3d306dc488c17ef0f17f40b686409b5a58c2d918acf3cd262352063c840880774285597f7222abfe17d178935247979d68258f3e4e4
|
7
|
+
data.tar.gz: 79aa08506d89efbc8856f15fde6e306e7c38ca5957065fa9a95373e467d3a8abe5097c253b0d11b438d8ce718236481a8433052198fac8aa6beac6ddffc8ed62
|
data/lib/bootstrap-email.rb
CHANGED
@@ -1,13 +1,152 @@
|
|
1
1
|
require 'nokogiri'
|
2
2
|
require 'erb'
|
3
3
|
require 'ostruct'
|
4
|
-
require '
|
4
|
+
require 'action_mailer'
|
5
5
|
require 'premailer'
|
6
|
+
require 'premailer/rails'
|
7
|
+
require 'rails'
|
6
8
|
|
7
|
-
|
9
|
+
module BootstrapEmail
|
10
|
+
class Compiler
|
8
11
|
|
12
|
+
def initialize mail
|
13
|
+
@mail = mail
|
14
|
+
@doc = Nokogiri::HTML(@mail.body.raw_source)
|
15
|
+
end
|
9
16
|
|
10
|
-
|
11
|
-
|
17
|
+
def compile_html!
|
18
|
+
button
|
19
|
+
badge
|
20
|
+
alert
|
21
|
+
align
|
22
|
+
card
|
23
|
+
hr
|
24
|
+
container
|
25
|
+
grid
|
26
|
+
padding
|
27
|
+
margin
|
28
|
+
table
|
29
|
+
end
|
30
|
+
|
31
|
+
def update_mailer!
|
32
|
+
@mail.body = @doc.to_html
|
33
|
+
@mail
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def build_from_template template, locals_hash = {}
|
39
|
+
namespace = OpenStruct.new(locals_hash)
|
40
|
+
template = File.open(File.expand_path("../core/templates/#{template}.html.erb", __dir__)).read
|
41
|
+
Nokogiri::HTML::DocumentFragment.parse(ERB.new(template).result(namespace.instance_eval { binding }))
|
42
|
+
end
|
43
|
+
|
44
|
+
def each_node css_lookup, &blk
|
45
|
+
# sort by youngest child and traverse backwards up the tree
|
46
|
+
@doc.css(css_lookup).sort_by{ |n| n.ancestors.size }.reverse!.each(&blk)
|
47
|
+
end
|
48
|
+
|
49
|
+
def button
|
50
|
+
each_node('.btn') do |node| # move all classes up and remove all classes from the element
|
51
|
+
node.replace(build_from_template('table-left', {classes: node['class'], contents: node.delete('class') && node.to_html}))
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def badge
|
56
|
+
each_node('.badge') do |node| # move all classes up and remove all classes from the element
|
57
|
+
node.replace(build_from_template('table-left', {classes: node['class'], contents: node.delete('class') && node.to_html}))
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def alert
|
62
|
+
each_node('.alert') do |node| # move all classes up and remove all classes from the element
|
63
|
+
node.replace(build_from_template('table', {classes: node['class'], contents: node.delete('class') && node.to_html}))
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def align
|
68
|
+
each_node('.align-left') do |node| # align table and move contents
|
69
|
+
node['class'] = node['class'].sub(/align-left/, '')
|
70
|
+
node.replace(build_from_template('align-left', {contents: node.to_html}))
|
71
|
+
end
|
72
|
+
each_node('.align-center') do |node| # align table and move contents
|
73
|
+
node['class'] = node['class'].sub(/align-center/, '')
|
74
|
+
node.replace(build_from_template('align-center', {contents: node.to_html}))
|
75
|
+
end
|
76
|
+
each_node('.align-right') do |node| # align table and move contents
|
77
|
+
node['class'] = node['class'].sub(/align-right/, '')
|
78
|
+
node.replace(build_from_template('align-right', {contents: node.to_html}))
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def card
|
83
|
+
each_node('.card') do |node| # move all classes up and remove all classes from element
|
84
|
+
node.replace(build_from_template('table', {classes: node['class'], contents: node.delete('class') && node.to_html}))
|
85
|
+
end
|
86
|
+
each_node('.card-body') do |node| # move all classes up and remove all classes from element
|
87
|
+
node.replace(build_from_template('table', {classes: node['class'], contents: node.delete('class') && node.to_html}))
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def hr
|
92
|
+
each_node('hr') do |node| # drop hr in place of current
|
93
|
+
node.replace(build_from_template('hr', {classes: "hr #{node['class']}"}))
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def container
|
98
|
+
each_node('.container') do |node|
|
99
|
+
node.replace(build_from_template('container', {classes: node['class'], contents: node.inner_html}))
|
100
|
+
end
|
101
|
+
each_node('.container-fluid') do |node|
|
102
|
+
node.replace(build_from_template('table', {classes: node['class'], contents: node.inner_html}))
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def grid
|
107
|
+
each_node('.row') do |node|
|
108
|
+
node.replace(build_from_template('row', {classes: node['class'], contents: node.inner_html}))
|
109
|
+
end
|
110
|
+
each_node('*[class*=col]') do |node|
|
111
|
+
node.replace(build_from_template('col', {classes: node['class'], contents: node.inner_html}))
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def padding
|
116
|
+
each_node('*[class*=p-], *[class*=pt-], *[class*=pr-], *[class*=pb-], *[class*=pl-], *[class*=px-], *[class*=py-]') do |node|
|
117
|
+
if node.name != 'table' # if it is already on a table, set the padding on the table, else wrap the content in a table
|
118
|
+
padding_regex = /(p[trblxy]?-\d)/
|
119
|
+
classes = node['class'].scan(padding_regex).join(' ')
|
120
|
+
node['class'] = node['class'].sub(padding_regex, '')
|
121
|
+
node.replace(build_from_template('table', {classes: classes, contents: node.to_html}))
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def margin
|
127
|
+
each_node('*[class*=m-], *[class*=mt-], *[class*=mr-], *[class*=mb-], *[class*=ml-], *[class*=mx-], *[class*=my-]') do |node|
|
128
|
+
if node.name != 'div' # if it is already on a div, set the margin on the div, else wrap the content in a div
|
129
|
+
margin_regex = /(m[trblxy]?-\d)/
|
130
|
+
classes = node['class'].scan(margin_regex).join(' ')
|
131
|
+
node['class'] = node['class'].sub(margin_regex, '')
|
132
|
+
node.replace(build_from_template('div', {classes: classes, contents: node.to_html}))
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def table
|
138
|
+
@doc.css('table').each do |node|
|
139
|
+
#border="0" cellpadding="0" cellspacing="0"
|
140
|
+
node['border'] = 0
|
141
|
+
node['cellpadding'] = 0
|
142
|
+
node['cellspacing'] = 0
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
require 'bootstrap-email/premailer_railtie'
|
150
|
+
require 'bootstrap-email/action_mailer'
|
12
151
|
require 'bootstrap-email/engine'
|
13
152
|
require 'bootstrap-email/version'
|
@@ -1,20 +1,22 @@
|
|
1
1
|
class ActionMailer::Base
|
2
|
-
helper BootstrapEmailHelper
|
3
2
|
|
3
|
+
# sit in the middle and compile the html
|
4
4
|
def bootstrap_mail *args
|
5
|
-
|
6
|
-
bootstrap
|
7
|
-
bootstrap.
|
5
|
+
bootstrap = BootstrapEmail::Compiler.new(mail(*args))
|
6
|
+
bootstrap.compile_html!
|
7
|
+
bootstrap.update_mailer!
|
8
8
|
end
|
9
|
-
end
|
10
9
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
10
|
+
# helper to inject style tags into head of the email template
|
11
|
+
module BootstrapEmailHelper
|
12
|
+
def bootstrap_email_head
|
13
|
+
html_string = <<-HEREDOC
|
14
|
+
<style type="text/css" data-premailer="ignore">
|
15
|
+
#{File.open(File.expand_path('../../core/head.css', __dir__)).read}
|
16
|
+
</style>
|
17
|
+
HEREDOC
|
18
|
+
html_string.html_safe
|
19
|
+
end
|
19
20
|
end
|
21
|
+
helper BootstrapEmailHelper
|
20
22
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bootstrap-email
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.0.alpha.
|
4
|
+
version: 0.0.0.alpha.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stuart Yamartino
|
@@ -26,33 +26,59 @@ dependencies:
|
|
26
26
|
version: '1.9'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: nokogiri
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.6'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.6'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: actionmailer
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
30
44
|
requirements:
|
31
45
|
- - ">="
|
32
46
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
34
|
-
|
47
|
+
version: '3'
|
48
|
+
- - "<"
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: '6'
|
51
|
+
type: :runtime
|
35
52
|
prerelease: false
|
36
53
|
version_requirements: !ruby/object:Gem::Requirement
|
37
54
|
requirements:
|
38
55
|
- - ">="
|
39
56
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
57
|
+
version: '3'
|
58
|
+
- - "<"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '6'
|
41
61
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
62
|
+
name: rails
|
43
63
|
requirement: !ruby/object:Gem::Requirement
|
44
64
|
requirements:
|
45
65
|
- - ">="
|
46
66
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
48
|
-
|
67
|
+
version: '3'
|
68
|
+
- - "<"
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '6'
|
71
|
+
type: :runtime
|
49
72
|
prerelease: false
|
50
73
|
version_requirements: !ruby/object:Gem::Requirement
|
51
74
|
requirements:
|
52
75
|
- - ">="
|
53
76
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
55
|
-
|
77
|
+
version: '3'
|
78
|
+
- - "<"
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '6'
|
81
|
+
description:
|
56
82
|
email: stuartyamartino@gmail.com
|
57
83
|
executables: []
|
58
84
|
extensions: []
|
@@ -60,10 +86,9 @@ extra_rdoc_files: []
|
|
60
86
|
files:
|
61
87
|
- lib/bootstrap-email.rb
|
62
88
|
- lib/bootstrap-email/action_mailer.rb
|
63
|
-
- lib/bootstrap-email/bootstrap_email.rb
|
64
89
|
- lib/bootstrap-email/engine.rb
|
65
90
|
- lib/bootstrap-email/version.rb
|
66
|
-
homepage:
|
91
|
+
homepage: https://github.com/stuyam/bootstrap-email
|
67
92
|
licenses:
|
68
93
|
- MIT
|
69
94
|
metadata: {}
|
@@ -73,9 +98,9 @@ require_paths:
|
|
73
98
|
- lib
|
74
99
|
required_ruby_version: !ruby/object:Gem::Requirement
|
75
100
|
requirements:
|
76
|
-
- - "
|
101
|
+
- - "~>"
|
77
102
|
- !ruby/object:Gem::Version
|
78
|
-
version: '0'
|
103
|
+
version: '2.0'
|
79
104
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
80
105
|
requirements:
|
81
106
|
- - ">"
|
@@ -86,5 +111,6 @@ rubyforge_project:
|
|
86
111
|
rubygems_version: 2.6.8
|
87
112
|
signing_key:
|
88
113
|
specification_version: 4
|
89
|
-
summary: Bootstrap 4
|
114
|
+
summary: Bootstrap 4 stylesheet, compiler, and inliner for responsive and consistent
|
115
|
+
emails with the Bootstrap syntax you know and love.
|
90
116
|
test_files: []
|
@@ -1,75 +0,0 @@
|
|
1
|
-
class BootstrapEmail
|
2
|
-
|
3
|
-
def constructor mail
|
4
|
-
@mail = mail
|
5
|
-
end
|
6
|
-
|
7
|
-
def compiled_html!
|
8
|
-
doc = Nokogiri::HTML(@mail.body.raw_source)
|
9
|
-
doc.css('.btn').each do |node| # move all classes up and remove all classes from the element
|
10
|
-
node.replace(build_from_template('table-left', {classes: node['class'], contents: node.delete('class') && node.to_html}))
|
11
|
-
end
|
12
|
-
doc.css('.badge').each do |node| # move all classes up and remove all classes from the element
|
13
|
-
node.replace(build_from_template('table-left', {classes: node['class'], contents: node.delete('class') && node.to_html}))
|
14
|
-
end
|
15
|
-
doc.css('.alert').each do |node| # move all classes up and remove all classes from the element
|
16
|
-
node.replace(build_from_template('table', {classes: node['class'], contents: node.delete('class') && node.to_html}))
|
17
|
-
end
|
18
|
-
doc.css('.align-left').each do |node| # align table and move contents
|
19
|
-
node.replace(build_from_template('align-left', {contents: node.to_html}))
|
20
|
-
end
|
21
|
-
doc.css('.align-center').each do |node| # align table and move contents
|
22
|
-
node.replace(build_from_template('align-center', {contents: node.to_html}))
|
23
|
-
end
|
24
|
-
doc.css('.align-right').each do |node| # align table and move contents
|
25
|
-
node.replace(build_from_template('align-right', {contents: node.to_html}))
|
26
|
-
end
|
27
|
-
doc.css('.card').each do |node| # move all classes up and remove all classes from element
|
28
|
-
node.replace(build_from_template('table', {classes: node['class'], contents: node.delete('class') && node.to_html}))
|
29
|
-
end
|
30
|
-
doc.css('.card-body').each do |node| # move all classes up and remove all classes from element
|
31
|
-
node.replace(build_from_template('table', {classes: node['class'], contents: node.delete('class') && node.to_html}))
|
32
|
-
end
|
33
|
-
doc.css('hr').each do |node| # drop hr in place of current
|
34
|
-
node.replace(build_from_template('hr'))
|
35
|
-
end
|
36
|
-
doc.css('.container').each do |node|
|
37
|
-
node.replace(build_from_template('container', {classes: node['class'], contents: node.inner_html}))
|
38
|
-
end
|
39
|
-
doc.css('.container-fluid').each do |node|
|
40
|
-
node.replace(build_from_template('table', {classes: node['class'], contents: node.inner_html}))
|
41
|
-
end
|
42
|
-
doc.css('.row').each do |node|
|
43
|
-
node.replace(build_from_template('row', {classes: node['class'], contents: node.inner_html}))
|
44
|
-
end
|
45
|
-
doc.css('*[class^=col]').each do |node|
|
46
|
-
node.replace(build_from_template('col', {classes: node['class'], contents: node.inner_html}))
|
47
|
-
end
|
48
|
-
padding = %w( p- pt- pr- pb- pl- px- py- ).map{ |padding| "contains(@class, '#{padding}')" }.join(' or ')
|
49
|
-
doc.xpath("//*[#{padding}]").each do |node|
|
50
|
-
if node.name != 'table' # if it is already on a table, set the padding on the table, else wrap the content in a table
|
51
|
-
node.replace(build_from_template('table', {classes: node['class'], contents: node.delete('class') && node.to_html}))
|
52
|
-
end
|
53
|
-
end
|
54
|
-
margin = %w( m- mt- mr- mb- ml- mx- my- ).map{ |margin| "contains(@class, '#{margin}')" }.join(' or ')
|
55
|
-
doc.xpath("//*[#{margin}]").each do |node|
|
56
|
-
if node.name != 'div' # if it is already on a div, set the margin on the div, else wrap the content in a div
|
57
|
-
node.replace(build_from_template('div', {classes: node['class'].scan(/(m[trblxy]?-\d)/).join(' '), contents: node.delete('class') && node.to_html}))
|
58
|
-
end
|
59
|
-
end
|
60
|
-
doc.css('table').each do |node|
|
61
|
-
#border="0" cellpadding="0" cellspacing="0"
|
62
|
-
node['border'] = 0
|
63
|
-
node['cellpadding'] = 0
|
64
|
-
node['cellspacing'] = 0
|
65
|
-
end
|
66
|
-
@mail.body = doc.to_html
|
67
|
-
@mail
|
68
|
-
end
|
69
|
-
|
70
|
-
def build_from_template template, locals_hash = {}
|
71
|
-
namespace = OpenStruct.new(locals_hash)
|
72
|
-
template = File.open("../../core/templates/#{template}.html.erb").read
|
73
|
-
Nokogiri::HTML::DocumentFragment.parse(ERB.new(template).result(namespace.instance_eval { binding }))
|
74
|
-
end
|
75
|
-
end
|