bootstrap-email 0.3.0 → 1.0.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/bin/bootstrap-email +2 -0
  4. data/core/bootstrap-email.scss +27 -18
  5. data/core/bootstrap-head.scss +143 -0
  6. data/core/layout.html.erb +11 -0
  7. data/core/scss/_colors.scss +161 -0
  8. data/core/scss/_functions.scss +29 -0
  9. data/core/scss/_helper_groups.scss +21 -0
  10. data/core/{sass → scss}/_reboot_email.scss +7 -22
  11. data/core/scss/_selectors_for_utils.scss +100 -0
  12. data/core/scss/_utilities.scss +125 -0
  13. data/core/scss/_variables.scss +52 -0
  14. data/core/{sass → scss/components}/_alert.scss +0 -1
  15. data/core/{sass → scss/components}/_badge.scss +8 -15
  16. data/core/{sass → scss/components}/_button.scss +21 -17
  17. data/core/{sass → scss/components}/_card.scss +3 -2
  18. data/core/{sass → scss/components}/_container.scss +0 -0
  19. data/core/scss/components/_grid.scss +35 -0
  20. data/core/scss/components/_hr.scss +8 -0
  21. data/core/{sass → scss/components}/_image.scss +2 -1
  22. data/core/{sass → scss/components}/_preview.scss +0 -0
  23. data/core/{sass → scss/components}/_table.scss +0 -0
  24. data/core/scss/utilities/_background.scss +5 -0
  25. data/core/scss/utilities/_border-radius.scss +21 -0
  26. data/core/scss/utilities/_border.scss +13 -0
  27. data/core/scss/utilities/_color.scss +9 -0
  28. data/core/scss/utilities/_display.scss +7 -0
  29. data/core/scss/utilities/_sizing.scss +16 -0
  30. data/core/scss/utilities/_spacing.scss +18 -0
  31. data/core/scss/utilities/_text-decoration.scss +9 -0
  32. data/core/scss/utilities/_typography.scss +54 -0
  33. data/core/templates/col.html.erb +2 -2
  34. data/core/templates/row.html.erb +2 -2
  35. data/lib/bootstrap-email/bootstrap_email_cli.rb +91 -0
  36. data/lib/bootstrap-email/compiler.rb +118 -0
  37. data/lib/bootstrap-email/components/alert.rb +11 -0
  38. data/lib/bootstrap-email/components/align.rb +21 -0
  39. data/lib/bootstrap-email/components/badge.rb +11 -0
  40. data/lib/bootstrap-email/components/base.rb +26 -0
  41. data/lib/bootstrap-email/components/body.rb +22 -0
  42. data/lib/bootstrap-email/components/button.rb +11 -0
  43. data/lib/bootstrap-email/components/card.rb +14 -0
  44. data/lib/bootstrap-email/components/color.rb +13 -0
  45. data/lib/bootstrap-email/components/container.rb +14 -0
  46. data/lib/bootstrap-email/components/grid.rb +14 -0
  47. data/lib/bootstrap-email/components/hr.rb +12 -0
  48. data/lib/bootstrap-email/components/margin.rb +22 -0
  49. data/lib/bootstrap-email/components/padding.rb +16 -0
  50. data/lib/bootstrap-email/components/paragraph.rb +24 -0
  51. data/lib/bootstrap-email/components/spacer.rb +11 -0
  52. data/lib/bootstrap-email/components/spacing.rb +18 -0
  53. data/lib/bootstrap-email/components/table.rb +13 -0
  54. data/lib/bootstrap-email/initialize.rb +1 -0
  55. data/lib/bootstrap-email/rails/action_mailer.rb +10 -0
  56. data/lib/bootstrap-email/{engine.rb → rails/engine.rb} +0 -0
  57. data/lib/bootstrap-email/sass_cache.rb +47 -0
  58. data/lib/bootstrap-email/version.rb +3 -5
  59. data/lib/bootstrap_email.rb +26 -0
  60. metadata +76 -64
  61. data/core/head.scss +0 -269
  62. data/core/sass/_border.scss +0 -73
  63. data/core/sass/_color.scss +0 -33
  64. data/core/sass/_display.scss +0 -7
  65. data/core/sass/_functions.scss +0 -14
  66. data/core/sass/_grid.scss +0 -131
  67. data/core/sass/_hr.scss +0 -14
  68. data/core/sass/_spacing.scss +0 -100
  69. data/core/sass/_typography.scss +0 -50
  70. data/core/sass/_variables.scss +0 -150
  71. data/core/template.html.erb +0 -11
  72. data/core/templates/align-center.html.erb +0 -9
  73. data/core/templates/align-left.html.erb +0 -9
  74. data/core/templates/align-right.html.erb +0 -9
  75. data/core/templates/hr.html.erb +0 -9
  76. data/lib/assets/stylesheets/bootstrap-email.scss +0 -1
  77. data/lib/bootstrap-email.rb +0 -230
  78. data/lib/bootstrap-email/action_mailer.rb +0 -12
  79. data/lib/bootstrap-email/premailer_railtie.rb +0 -5
@@ -1,5 +1,6 @@
1
1
  .img-fluid {
2
2
  height: auto;
3
- width: 100%;
4
3
  max-width: 100%;
4
+ -premailer-width: 100%;
5
+ width: 100%;
5
6
  }
@@ -0,0 +1,5 @@
1
+ @each $name, $color in $all-colors {
2
+ @include background-color-util('.bg-#{$name}') {
3
+ background-color: $color;
4
+ }
5
+ }
@@ -0,0 +1,21 @@
1
+ @each $name, $radius in $border-radiuses {
2
+ @include border-radius-util('.rounded#{$name}') {
3
+ border-radius: $radius;
4
+ }
5
+ @include border-radius-util('.rounded-top#{$name}') {
6
+ border-top-left-radius: $radius;
7
+ border-top-right-radius: $radius;
8
+ }
9
+ @include border-radius-util('.rounded-right#{$name}') {
10
+ border-top-right-radius: $radius;
11
+ border-bottom-right-radius: $radius;
12
+ }
13
+ @include border-radius-util('.rounded-bottom#{$name}') {
14
+ border-bottom-right-radius: $radius;
15
+ border-bottom-left-radius: $radius;
16
+ }
17
+ @include border-radius-util('.rounded-left#{$name}') {
18
+ border-top-left-radius: $radius;
19
+ border-bottom-left-radius: $radius;
20
+ }
21
+ }
@@ -0,0 +1,13 @@
1
+ @each $name, $width in $border-widths {
2
+ .border#{$name} { border: $width solid $border-color !important; }
3
+ .border-top#{$name} { border-top: $width solid $border-color !important; }
4
+ .border-right#{$name} { border-right: $width solid $border-color !important; }
5
+ .border-bottom#{$name} { border-bottom: $width solid $border-color !important; }
6
+ .border-left#{$name} { border-left: $width solid $border-color !important; }
7
+ }
8
+
9
+ @each $name, $color in $all-colors {
10
+ @include border-color-util('.border-#{$name}') {
11
+ border-color: $color !important;
12
+ }
13
+ }
@@ -0,0 +1,9 @@
1
+ @each $name, $color in $all-colors {
2
+ @include color-util('.text-#{$name}') {
3
+ color: $color;
4
+ }
5
+ }
6
+
7
+ @include color-util('.text-muted') {
8
+ color: $gray-600;
9
+ }
@@ -0,0 +1,7 @@
1
+ @each $prefix in $breakpoints {
2
+ @each $display in $display-type {
3
+ .d-#{$prefix}#{$display} {
4
+ display: $display;
5
+ }
6
+ }
7
+ }
@@ -0,0 +1,16 @@
1
+ @each $prefix in $breakpoints {
2
+ @each $name, $property in $sizing-types {
3
+ @include sizing-util('.#{$name}-#{$prefix}full') {
4
+ max-#{$property}: 100%;
5
+ -premailer-#{$property}: 100%;
6
+ #{$property}: 100%;
7
+ }
8
+ @each $size, $value in $sizing {
9
+ @include sizing-util('.#{$name}-#{$prefix}#{$size}') {
10
+ max-#{$property}: $value;
11
+ -premailer-#{$property}: strip-unit($value);
12
+ #{$property}: 100%;
13
+ }
14
+ }
15
+ }
16
+ }
@@ -0,0 +1,18 @@
1
+ // Padding Util
2
+ @each $prefix in $breakpoints {
3
+ @each $size, $value in $spacers {
4
+ @include padding-group($prefix, $size, $value);
5
+ }
6
+ }
7
+
8
+ // Spacing Util
9
+ @each $prefix in $breakpoints {
10
+ @each $size, $value in $spacers {
11
+ @include spacer-util('.s-#{$prefix}#{$size}') {
12
+ font-size: $value;
13
+ line-height: $value;
14
+ height: $value;
15
+ -premailer-height: strip-unit($value);
16
+ }
17
+ }
18
+ }
@@ -0,0 +1,9 @@
1
+ .underline {
2
+ text-decoration: underline;
3
+ }
4
+ .line-through {
5
+ text-decoration: line-through;
6
+ }
7
+ .no-underline {
8
+ text-decoration: none;
9
+ }
@@ -0,0 +1,54 @@
1
+ h1, h2, h3, h4, h5, h6,
2
+ .h1, .h2, .h3, .h4, .h5, .h6 {
3
+ margin: 0;
4
+ padding-top: $headings-margin-top;
5
+ padding-bottom: $headings-margin-bottom;
6
+ font-family: $headings-font-family;
7
+ font-weight: $headings-font-weight;
8
+ color: $headings-color;
9
+ text-align: left;
10
+ vertical-align: baseline;
11
+ }
12
+
13
+ @each $name, $size in $headings {
14
+ h#{$name}, .h#{$name} {
15
+ font-size: $size;
16
+ line-height: $size * $headings-line-height;
17
+ }
18
+ }
19
+
20
+ @each $name, $size in $font-size {
21
+ .text-#{$name} {
22
+ font-size: $size;
23
+ line-height: $size * $headings-line-height;
24
+ }
25
+ }
26
+
27
+ @each $name, $size in $line-height {
28
+ .lh-#{$name} {
29
+ line-height: $size;
30
+ }
31
+ }
32
+
33
+ @for $n from 1 through 9 {
34
+ @include font-weight-util('.fw-#{$n * 100}') {
35
+ font-weight: #{$n * 100} !important;
36
+ }
37
+ }
38
+
39
+ .text-left {
40
+ text-align: left !important;
41
+ }
42
+
43
+ .text-right {
44
+ text-align: right !important;
45
+ }
46
+
47
+ .text-center {
48
+ text-align: center !important;
49
+ }
50
+
51
+ p {
52
+ width: 100%;
53
+ margin: 0;
54
+ }
@@ -1,3 +1,3 @@
1
- <th class="<%= classes %>" align="left" valign="top">
1
+ <td class="<%= classes %>" align="left" valign="top">
2
2
  <%= contents %>
3
- </th>
3
+ </td>
@@ -1,7 +1,7 @@
1
1
  <table class="<%= classes %>">
2
- <thead>
2
+ <tbody>
3
3
  <tr>
4
4
  <%= contents %>
5
5
  </tr>
6
- </thead>
6
+ </tbody>
7
7
  </table>
@@ -0,0 +1,91 @@
1
+ require_relative '../bootstrap_email'
2
+ require 'optparse'
3
+ require 'fileutils'
4
+
5
+ input = nil
6
+ options = {
7
+ destination: 'output',
8
+ type: :file
9
+ }
10
+
11
+ parser = OptionParser.new do |opts|
12
+ opts.banner = "Bootstrap 5 stylesheet, compiler, and inliner for responsive and consistent emails with the Bootstrap syntax you know and love.\n\n"
13
+ opts.define_head 'Usage: bootstrap-email <path> [options]'
14
+ opts.separator ''
15
+ opts.separator 'Examples:'
16
+ opts.separator ' bootstrap-email'
17
+ opts.separator ' bootstrap-email email.html > out.html'
18
+ opts.separator ' bootstrap-email ./public/index.html'
19
+ opts.separator ' bootstrap-email -s \'<a href="#" class="btn btn-primary">Some Button</a>\''
20
+ opts.separator ' bootstrap-email -p \'emails/*\' -d emails/output'
21
+ opts.separator ' bootstrap-email -p \'views/emails/*\' -d \'views/compiled_emails\''
22
+ opts.separator ' cat input.html | bootstrap-email'
23
+ opts.separator ''
24
+ opts.separator 'Options:'
25
+
26
+ opts.on('-s', '--string STRING', String, 'HTML string to be to be compiled rather than a file.') do |v|
27
+ input = v
28
+ options[:type] = :string
29
+ end
30
+
31
+ opts.on('-p', '--pattern STRING', String, 'Specify a pattern of files to compile and save multiple files at once.') do |v|
32
+ input = v
33
+ options[:type] = :pattern
34
+ end
35
+
36
+ opts.on('-d', '--destination STRING', String, 'Destination for compiled files (used with the --pattern option).') do |v|
37
+ options[:destination] = v
38
+ end
39
+
40
+ opts.on('-c', '--config STRING', String, 'Relative path to SCSS config config file to customize Bootstrap Email.') do |v|
41
+ options[:config] = File.expand_path(v, Dir.pwd)
42
+ end
43
+
44
+ opts.on('-h', '--help', 'Prints this help') do
45
+ puts opts
46
+ exit
47
+ end
48
+
49
+ opts.on('-v', '--version', 'Show version') do
50
+ puts BootstrapEmail::VERSION
51
+ exit
52
+ end
53
+ end
54
+ parser.parse!
55
+
56
+ if input
57
+ # input already set by pattern
58
+ elsif ARGV.any?
59
+ # Executed via command line or shell script
60
+ input = ARGV.shift
61
+ elsif !STDIN.tty?
62
+ # Called in piped command
63
+ input = STDIN.read
64
+ options[:type] = :string
65
+ else
66
+ # Running just the blank command to compile all files in directory containing .html
67
+ input = '*.html*'
68
+ options[:type] = :pattern
69
+ end
70
+
71
+ if input
72
+ case options[:type]
73
+ when :pattern
74
+ Dir.glob(input, base: Dir.pwd).each do |path|
75
+ next unless File.file?(path)
76
+
77
+ puts "Compiling file #{path}"
78
+ compiled = BootstrapEmail::Compiler.new(path, type: :file, options: {config_path: options[:config]}).perform_full_compile
79
+ FileUtils.mkdir_p("#{Dir.pwd}/#{options[:destination]}")
80
+ File.write(File.expand_path("#{options[:destination]}/#{path.split('/').last}", Dir.pwd), compiled)
81
+ end
82
+ when :file
83
+ path = File.expand_path(input, Dir.pwd)
84
+ puts BootstrapEmail::Compiler.new(File.join(Dir.pwd, path), type: :file, options: {config_path: options[:config]}).perform_full_compile
85
+ when :string
86
+ puts BootstrapEmail::Compiler.new(input, options: {config_path: options[:config]}).perform_full_compile
87
+ end
88
+ else
89
+ puts opts
90
+ exit 1
91
+ end
@@ -0,0 +1,118 @@
1
+ module BootstrapEmail
2
+ class Compiler
3
+ attr_accessor :type, :doc, :premailer
4
+
5
+ def initialize(input, type: :string, options: {})
6
+ self.type = type
7
+ case type
8
+ when :rails
9
+ @mail = input
10
+ html = (@mail.html_part || @mail).body.raw_source
11
+ when :string
12
+ html = input
13
+ when :file
14
+ html = File.read(input)
15
+ end
16
+ build_premailer_doc(html, options)
17
+ end
18
+
19
+ def perform_full_compile
20
+ compile_html!
21
+ inline_css!
22
+ inject_head!
23
+ inject_comment!
24
+ finalize_document!
25
+ end
26
+
27
+ private
28
+
29
+ def build_premailer_doc(html, options)
30
+ html = add_layout!(html)
31
+ SassC.load_paths << File.expand_path('../../core', __dir__)
32
+ css_string = BootstrapEmail::SassCache.compile('bootstrap-email', config_path: options[:config_path], style: :expanded)
33
+ self.premailer = Premailer.new(
34
+ html,
35
+ with_html_string: true,
36
+ preserve_reset: false,
37
+ css_string: css_string
38
+ )
39
+ self.doc = premailer.doc
40
+ end
41
+
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
+ def compile_html!
52
+ BootstrapEmail::Component::Button.build(doc)
53
+ BootstrapEmail::Component::Badge.build(doc)
54
+ BootstrapEmail::Component::Alert.build(doc)
55
+ BootstrapEmail::Component::Card.build(doc)
56
+ BootstrapEmail::Component::Paragraph.build(doc)
57
+ BootstrapEmail::Component::Hr.build(doc)
58
+ BootstrapEmail::Component::Container.build(doc)
59
+ BootstrapEmail::Component::Grid.build(doc)
60
+ BootstrapEmail::Component::Align.build(doc)
61
+ BootstrapEmail::Component::Color.build(doc)
62
+ BootstrapEmail::Component::Padding.build(doc)
63
+ BootstrapEmail::Component::Margin.build(doc)
64
+ BootstrapEmail::Component::Spacing.build(doc)
65
+ BootstrapEmail::Component::Spacer.build(doc)
66
+ BootstrapEmail::Component::Table.build(doc)
67
+ BootstrapEmail::Component::Body.build(doc)
68
+ end
69
+
70
+ def inline_css!
71
+ premailer.to_inline_css
72
+ end
73
+
74
+ def inject_head!
75
+ doc.at_css('head').add_child(bootstrap_email_head)
76
+ end
77
+
78
+ def inject_comment!
79
+ doc.at_css('head').prepend_child(bootstrap_email_comment)
80
+ end
81
+
82
+ def finalize_document!
83
+ case type
84
+ when :rails
85
+ (@mail.html_part || @mail).body = doc.to_html
86
+ @mail
87
+ when :string, :file
88
+ doc.to_html
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
114
+ end
115
+ (default + custom).gsub(/\n\s*\n+/, "\n")
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,11 @@
1
+ module BootstrapEmail
2
+ module Component
3
+ class Alert < Base
4
+ def build
5
+ each_node('.alert') do |node|
6
+ node.replace(template('table', classes: node['class'], contents: node.delete('class') && node.to_html))
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,21 @@
1
+ module BootstrapEmail
2
+ module Component
3
+ class Align < Base
4
+ def build
5
+ ['left', 'center', 'right'].each do |type|
6
+ each_node(".align-#{type}") do |node|
7
+ align_helper(node, type)
8
+ end
9
+ end
10
+ end
11
+
12
+ def align_helper(node, type)
13
+ if node.name != 'table'
14
+ node['class'] = node['class'].sub("align-#{type}", '')
15
+ node = node.replace(template('table', classes: "align-#{type}", contents: node.to_html))[0]
16
+ end
17
+ node['align'] = type
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,11 @@
1
+ module BootstrapEmail
2
+ module Component
3
+ class Badge < Base
4
+ def build
5
+ each_node('.badge') do |node|
6
+ node.replace(template('table-left', classes: node['class'], contents: node.delete('class') && node.to_html))
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end