bootstrap-email 1.0.0.alpha1.2 → 1.0.0.alpha4

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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/core/bootstrap-email.scss +2 -0
  4. data/core/bootstrap-head.scss +89 -16
  5. data/core/scss/_selectors_for_utils.scss +24 -0
  6. data/core/scss/_utilities.scss +24 -4
  7. data/core/scss/components/_grid.scss +25 -17
  8. data/core/scss/components/_stack.scss +39 -0
  9. data/core/scss/utilities/_sizing.scss +21 -2
  10. data/core/scss/utilities/_typography.scss +4 -10
  11. data/core/scss/utilities/_valign.scss +5 -0
  12. data/core/templates/body.html +9 -0
  13. data/core/templates/{container.html.erb → container.html} +4 -4
  14. data/core/templates/div.html +3 -0
  15. data/core/templates/table-left.html +9 -0
  16. data/core/templates/table-to-tbody.html +5 -0
  17. data/core/templates/table-to-tr.html +7 -0
  18. data/core/templates/table.html +9 -0
  19. data/core/templates/td.html +3 -0
  20. data/core/templates/tr.html +5 -0
  21. data/lib/{bootstrap_email.rb → bootstrap-email.rb} +6 -3
  22. data/lib/bootstrap-email/bootstrap_email_cli.rb +6 -7
  23. data/lib/bootstrap-email/compiler.rb +50 -65
  24. data/lib/bootstrap-email/config.rb +50 -0
  25. data/lib/bootstrap-email/{components → converters}/alert.rb +1 -1
  26. data/lib/bootstrap-email/converters/align.rb +22 -0
  27. data/lib/bootstrap-email/{components → converters}/badge.rb +1 -1
  28. data/lib/bootstrap-email/converters/base.rb +63 -0
  29. data/lib/bootstrap-email/converters/block.rb +13 -0
  30. data/lib/bootstrap-email/converters/body.rb +10 -0
  31. data/lib/bootstrap-email/{components → converters}/button.rb +1 -1
  32. data/lib/bootstrap-email/{components → converters}/card.rb +3 -3
  33. data/lib/bootstrap-email/{components → converters}/color.rb +1 -1
  34. data/lib/bootstrap-email/{components → converters}/container.rb +1 -1
  35. data/lib/bootstrap-email/converters/force_encoding.rb +14 -0
  36. data/lib/bootstrap-email/converters/grid.rb +14 -0
  37. data/lib/bootstrap-email/converters/head_style.rb +33 -0
  38. data/lib/bootstrap-email/{components → converters}/hr.rb +2 -2
  39. data/lib/bootstrap-email/{components → converters}/margin.rb +3 -3
  40. data/lib/bootstrap-email/converters/padding.rb +16 -0
  41. data/lib/bootstrap-email/converters/paragraph.rb +13 -0
  42. data/lib/bootstrap-email/converters/preview_text.rb +18 -0
  43. data/lib/bootstrap-email/{components → converters}/spacer.rb +1 -1
  44. data/lib/bootstrap-email/{components → converters}/spacing.rb +5 -6
  45. data/lib/bootstrap-email/converters/stack.rb +30 -0
  46. data/lib/bootstrap-email/{components → converters}/table.rb +1 -1
  47. data/lib/bootstrap-email/converters/version_comment.rb +15 -0
  48. data/lib/bootstrap-email/erb.rb +9 -0
  49. data/lib/bootstrap-email/rails/action_mailer.rb +7 -2
  50. data/lib/bootstrap-email/sass_cache.rb +43 -26
  51. data/lib/bootstrap-email/setup.rb +27 -0
  52. metadata +44 -32
  53. data/core/templates/body.html.erb +0 -9
  54. data/core/templates/col.html.erb +0 -3
  55. data/core/templates/div.html.erb +0 -3
  56. data/core/templates/row.html.erb +0 -7
  57. data/core/templates/table-left.html.erb +0 -9
  58. data/core/templates/table.html.erb +0 -9
  59. data/lib/bootstrap-email/components/align.rb +0 -21
  60. data/lib/bootstrap-email/components/base.rb +0 -26
  61. data/lib/bootstrap-email/components/body.rb +0 -22
  62. data/lib/bootstrap-email/components/grid.rb +0 -14
  63. data/lib/bootstrap-email/components/padding.rb +0 -16
  64. data/lib/bootstrap-email/components/paragraph.rb +0 -24
  65. data/lib/bootstrap-email/initialize.rb +0 -1
@@ -0,0 +1,3 @@
1
+ <div class="{{ classes }}">
2
+ {{ contents }}
3
+ </div>
@@ -0,0 +1,9 @@
1
+ <table class="{{ classes }}" align="left" role="presentation">
2
+ <tbody>
3
+ <tr>
4
+ <td>
5
+ {{ contents }}
6
+ </td>
7
+ </tr>
8
+ </tbody>
9
+ </table>
@@ -0,0 +1,5 @@
1
+ <table class="{{ classes }}" role="presentation">
2
+ <tbody>
3
+ {{ contents }}
4
+ </tbody>
5
+ </table>
@@ -0,0 +1,7 @@
1
+ <table class="{{ classes }}" role="presentation">
2
+ <tbody>
3
+ <tr>
4
+ {{ contents }}
5
+ </tr>
6
+ </tbody>
7
+ </table>
@@ -0,0 +1,9 @@
1
+ <table class="{{ classes }}" role="presentation">
2
+ <tbody>
3
+ <tr>
4
+ <td>
5
+ {{ contents }}
6
+ </td>
7
+ </tr>
8
+ </tbody>
9
+ </table>
@@ -0,0 +1,3 @@
1
+ <td class="{{ classes }}">
2
+ {{ contents }}
3
+ </td>
@@ -0,0 +1,5 @@
1
+ <tr>
2
+ <td>
3
+ {{ contents }}
4
+ </td>
5
+ </tr>
@@ -5,6 +5,7 @@ require 'premailer'
5
5
  require 'sassc'
6
6
  require 'digest/sha1'
7
7
  require 'css_parser'
8
+ require 'fileutils'
8
9
 
9
10
  begin
10
11
  require 'rails'
@@ -14,12 +15,14 @@ if defined?(Rails)
14
15
  require 'action_mailer'
15
16
  end
16
17
 
17
- require_relative 'bootstrap-email/initialize'
18
+ require_relative 'bootstrap-email/config'
19
+ require_relative 'bootstrap-email/setup'
20
+ require_relative 'bootstrap-email/erb'
18
21
  require_relative 'bootstrap-email/compiler'
19
22
  require_relative 'bootstrap-email/sass_cache'
20
23
  require_relative 'bootstrap-email/version'
21
- require_relative 'bootstrap-email/components/base'
22
- Dir[File.join(__dir__, 'bootstrap-email/components', '*.rb')].each { |file| require_relative file }
24
+ require_relative 'bootstrap-email/converters/base'
25
+ Dir[File.join(__dir__, 'bootstrap-email/converters', '*.rb')].each { |file| require_relative file }
23
26
 
24
27
  if defined?(Rails)
25
28
  require_relative 'bootstrap-email/rails/action_mailer'
@@ -1,10 +1,9 @@
1
- require_relative '../bootstrap_email'
1
+ require_relative '../bootstrap-email'
2
2
  require 'optparse'
3
- require 'fileutils'
4
3
 
5
4
  input = nil
6
5
  options = {
7
- destination: 'output',
6
+ destination: 'compiled',
8
7
  type: :file
9
8
  }
10
9
 
@@ -17,7 +16,7 @@ parser = OptionParser.new do |opts|
17
16
  opts.separator ' bootstrap-email email.html > out.html'
18
17
  opts.separator ' bootstrap-email ./public/index.html'
19
18
  opts.separator ' bootstrap-email -s \'<a href="#" class="btn btn-primary">Some Button</a>\''
20
- opts.separator ' bootstrap-email -p \'emails/*\' -d emails/output'
19
+ opts.separator ' bootstrap-email -p \'emails/*\' -d emails/compiled'
21
20
  opts.separator ' bootstrap-email -p \'views/emails/*\' -d \'views/compiled_emails\''
22
21
  opts.separator ' cat input.html | bootstrap-email'
23
22
  opts.separator ''
@@ -37,7 +36,7 @@ parser = OptionParser.new do |opts|
37
36
  options[:destination] = v
38
37
  end
39
38
 
40
- opts.on('-c', '--config STRING', String, 'Relative path to SCSS config config file to customize Bootstrap Email.') do |v|
39
+ opts.on('-c', '--config STRING', String, 'Relative path to ruby config file to customize Bootstrap Email.') do |v|
41
40
  options[:config] = File.expand_path(v, Dir.pwd)
42
41
  end
43
42
 
@@ -82,9 +81,9 @@ if input
82
81
  end
83
82
  when :file
84
83
  path = File.expand_path(input, Dir.pwd)
85
- puts BootstrapEmail::Compiler.new(path, type: :file, options: {config_path: options[:config]}).perform_full_compile
84
+ puts BootstrapEmail::Compiler.new(path, type: :file, options: {config_path: options[:config], sass_log_enabled: false}).perform_full_compile
86
85
  when :string
87
- puts BootstrapEmail::Compiler.new(input, options: {config_path: options[:config]}).perform_full_compile
86
+ puts BootstrapEmail::Compiler.new(input, options: {config_path: options[:config], sass_log_enabled: false}).perform_full_compile
88
87
  end
89
88
  else
90
89
  puts opts
@@ -3,6 +3,7 @@ module BootstrapEmail
3
3
  attr_accessor :type, :doc, :premailer
4
4
 
5
5
  def initialize(input, type: :string, options: {})
6
+ BootstrapEmail.load_options(options)
6
7
  self.type = type
7
8
  case type
8
9
  when :rails
@@ -13,106 +14,90 @@ module BootstrapEmail
13
14
  when :file
14
15
  html = File.read(input)
15
16
  end
16
- build_premailer_doc(html, options)
17
+ html = add_layout!(html)
18
+ sass_load_paths
19
+ build_premailer_doc(html)
17
20
  end
18
21
 
19
22
  def perform_full_compile
20
23
  compile_html!
21
24
  inline_css!
22
- inject_head!
23
- inject_comment!
25
+ configure_html!
24
26
  finalize_document!
25
27
  end
26
28
 
27
29
  private
28
30
 
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)
31
+ def add_layout!(html)
32
+ document = Nokogiri::HTML(html)
33
+ return html unless document.at_css('head').nil?
34
+
35
+ BootstrapEmail::Erb.template(
36
+ File.expand_path('../../core/layout.html.erb', __dir__),
37
+ contents: html
38
+ )
39
+ end
40
+
41
+ def sass_load_paths
42
+ SassC.load_paths << BootstrapEmail.config.sass_load_paths
43
+ end
44
+
45
+ def build_premailer_doc(html)
46
+ css_string = BootstrapEmail::SassCache.compile('bootstrap-email', style: :expanded)
33
47
  self.premailer = Premailer.new(
34
48
  html,
35
49
  with_html_string: true,
36
50
  preserve_reset: false,
51
+ adapter: :nokogiri_fast,
52
+ output_encoding: 'US-ASCII',
37
53
  css_string: css_string
38
54
  )
39
55
  self.doc = premailer.doc
40
56
  end
41
57
 
42
- def add_layout!(html)
43
- document = Nokogiri::HTML(html)
44
- return document.to_html unless document.at_css('head').nil?
58
+ def compile_html!
59
+ BootstrapEmail::Converter::Body.build(doc)
60
+ BootstrapEmail::Converter::Block.build(doc)
45
61
 
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
62
+ BootstrapEmail::Converter::Button.build(doc)
63
+ BootstrapEmail::Converter::Badge.build(doc)
64
+ BootstrapEmail::Converter::Alert.build(doc)
65
+ BootstrapEmail::Converter::Card.build(doc)
66
+ BootstrapEmail::Converter::Hr.build(doc)
67
+ BootstrapEmail::Converter::Container.build(doc)
68
+ BootstrapEmail::Converter::Grid.build(doc)
69
+ BootstrapEmail::Converter::Stack.build(doc)
50
70
 
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)
71
+ BootstrapEmail::Converter::Color.build(doc)
72
+ BootstrapEmail::Converter::Spacing.build(doc)
73
+ BootstrapEmail::Converter::Margin.build(doc)
74
+ BootstrapEmail::Converter::Spacer.build(doc)
75
+ BootstrapEmail::Converter::Align.build(doc)
76
+ BootstrapEmail::Converter::Padding.build(doc)
77
+
78
+ BootstrapEmail::Converter::PreviewText.build(doc)
79
+ BootstrapEmail::Converter::Table.build(doc)
68
80
  end
69
81
 
70
82
  def inline_css!
71
83
  premailer.to_inline_css
72
84
  end
73
85
 
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)
86
+ def configure_html!
87
+ BootstrapEmail::Converter::HeadStyle.build(doc)
88
+ BootstrapEmail::Converter::VersionComment.build(doc)
80
89
  end
81
90
 
82
91
  def finalize_document!
92
+ html = doc.to_html(encoding: 'US-ASCII')
93
+ html = BootstrapEmail::Converter::ForceEncoding.replace(html)
83
94
  case type
84
95
  when :rails
85
- (@mail.html_part || @mail).body = doc.to_html
96
+ (@mail.html_part || @mail).body = html
86
97
  @mail
87
98
  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
99
+ html
114
100
  end
115
- (default + custom).gsub(/\n\s*\n+/, "\n")
116
101
  end
117
102
  end
118
103
  end
@@ -0,0 +1,50 @@
1
+ module BootstrapEmail
2
+ class Config
3
+ attr_writer :sass_email_location # path to main sass file
4
+ attr_writer :sass_head_location # path to head sass file
5
+ attr_writer :sass_load_paths # array of directories for loading sass imports
6
+ attr_writer :sass_cache_location # path to tmp folder for sass cache
7
+ attr_writer :sass_log_enabled # turn on or off sass log when caching new sass
8
+
9
+ def load_options(options)
10
+ file = File.expand_path('bootstrap-email.config.rb', Dir.pwd)
11
+ if options[:config_path]
12
+ require_relative options[:config_path]
13
+ elsif File.exist?(file)
14
+ require_relative file
15
+ end
16
+ options.each { |name, value| instance_variable_set("@#{name}", value) }
17
+ end
18
+
19
+ def sass_location_for(type:)
20
+ ivar = instance_variable_get("@sass_#{type.sub('bootstrap-', '')}_location")
21
+ return ivar if ivar
22
+
23
+ lookup_locations = ["#{type}.scss", "app/assets/stylesheets/#{type}.scss"]
24
+ locations = lookup_locations.map { |location| File.expand_path(location, Dir.pwd) }.select { |location| File.exist?(location) }
25
+ locations.first if locations.any?
26
+ end
27
+
28
+ def sass_load_paths
29
+ paths_array = [SassCache::SASS_DIR]
30
+ @sass_load_paths ||= []
31
+ paths_array.concat(@sass_load_paths)
32
+ end
33
+
34
+ def sass_cache_location
35
+ @sass_cache_location ||= begin
36
+ if defined?(::Rails) && ::Rails.root
37
+ ::Rails.root.join('tmp', 'cache', 'bootstrap-email', '.sass-cache')
38
+ elsif File.writable?(Dir.pwd)
39
+ File.join(Dir.pwd, '.sass-cache', 'bootstrap-email')
40
+ else
41
+ File.join(Dir.tmpdir, '.sass-cache', 'bootstrap-email')
42
+ end
43
+ end
44
+ end
45
+
46
+ def sass_log_enabled?
47
+ defined?(@sass_log_enabled) ? @sass_log_enabled : true
48
+ end
49
+ end
50
+ end
@@ -1,5 +1,5 @@
1
1
  module BootstrapEmail
2
- module Component
2
+ module Converter
3
3
  class Alert < Base
4
4
  def build
5
5
  each_node('.alert') do |node|
@@ -0,0 +1,22 @@
1
+ module BootstrapEmail
2
+ module Converter
3
+ class Align < Base
4
+ def build
5
+ ['left', 'center', 'right'].each do |type|
6
+ full_type = "ax-#{type}"
7
+ each_node(".#{full_type}") do |node|
8
+ align_helper(node, full_type, type)
9
+ end
10
+ end
11
+ end
12
+
13
+ def align_helper(node, full_type, type)
14
+ unless table?(node) || td?(node)
15
+ node['class'] = node['class'].sub(full_type, '').strip
16
+ node = node.replace(template('table', classes: full_type, contents: node.to_html))[0]
17
+ end
18
+ node['align'] = type
19
+ end
20
+ end
21
+ end
22
+ end
@@ -1,5 +1,5 @@
1
1
  module BootstrapEmail
2
- module Component
2
+ module Converter
3
3
  class Badge < Base
4
4
  def build
5
5
  each_node('.badge') do |node|
@@ -0,0 +1,63 @@
1
+ module BootstrapEmail
2
+ module Converter
3
+ class Base
4
+ attr_reader :doc
5
+ def initialize(doc)
6
+ @doc = doc
7
+ @cached_templates = {}
8
+ end
9
+
10
+ def self.build(doc)
11
+ new(doc).build
12
+ end
13
+
14
+ private
15
+
16
+ def template(file, locals_hash = {})
17
+ locals_hash[:classes] = locals_hash[:classes].to_s.split.join(' ')
18
+ locals_hash[:content] ||= nil
19
+ if @cached_templates[file]
20
+ string = @cached_templates[file]
21
+ else
22
+ path = File.expand_path("../../../core/templates/#{file}.html", __dir__)
23
+ string = File.read(path).chop # read and remove trailing newline
24
+ @cached_templates[file] = string
25
+ end
26
+ locals_hash.each do |key, value|
27
+ string = string.sub("{{ #{key} }}", value.to_s)
28
+ end
29
+ string
30
+ end
31
+
32
+ def each_node(css_lookup, &blk)
33
+ # sort by youngest child and traverse backwards up the tree
34
+ doc.css(css_lookup).sort_by { |n| n.ancestors.size }.reverse!.each(&blk)
35
+ end
36
+
37
+ def add_class(node, class_name)
38
+ node['class'] ||= ''
39
+ node['class'] += node['class'].length.zero? ? class_name : " #{class_name}"
40
+ end
41
+
42
+ def margin?(node)
43
+ margin_top?(node) || margin_bottom?(node)
44
+ end
45
+
46
+ def margin_top?(node)
47
+ node['class'].to_s.match?(/m[ty]{1}-(lg-)?\d+/)
48
+ end
49
+
50
+ def margin_bottom?(node)
51
+ node['class'].to_s.match?(/m[by]{1}-(lg-)?\d+/)
52
+ end
53
+
54
+ def table?(node)
55
+ node.name == 'table'
56
+ end
57
+
58
+ def td?(node)
59
+ node.name == 'td'
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,13 @@
1
+ module BootstrapEmail
2
+ module Converter
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