bootstrap-email 0.3.0 → 1.0.0.alpha1

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.
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