bootstrap-email 1.0.0.alpha1.1 → 1.0.0.alpha3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) 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 -2
  22. data/lib/bootstrap-email/bootstrap_email_cli.rb +9 -9
  23. data/lib/bootstrap-email/compiler.rb +48 -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 +16 -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 -31
  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
@@ -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'
@@ -15,11 +16,14 @@ if defined?(Rails)
15
16
  end
16
17
 
17
18
  require_relative 'bootstrap-email/initialize'
19
+ require_relative 'bootstrap-email/config'
20
+ require_relative 'bootstrap-email/setup'
21
+ require_relative 'bootstrap-email/erb'
18
22
  require_relative 'bootstrap-email/compiler'
19
23
  require_relative 'bootstrap-email/sass_cache'
20
24
  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 }
25
+ require_relative 'bootstrap-email/converters/base'
26
+ Dir[File.join(__dir__, 'bootstrap-email/converters', '*.rb')].each { |file| require_relative file }
23
27
 
24
28
  if defined?(Rails)
25
29
  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
 
@@ -76,14 +75,15 @@ if input
76
75
 
77
76
  puts "Compiling file #{path}"
78
77
  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)
78
+ destination = options[:destination].chomp('/*')
79
+ FileUtils.mkdir_p("#{Dir.pwd}/#{destination}")
80
+ File.write(File.expand_path("#{destination}/#{path.split('/').last}", Dir.pwd), compiled)
81
81
  end
82
82
  when :file
83
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
84
+ puts BootstrapEmail::Compiler.new(path, type: :file, options: {config_path: options[:config], sass_log_enabled: false}).perform_full_compile
85
85
  when :string
86
- 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
87
87
  end
88
88
  else
89
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,23 +14,36 @@ 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,
@@ -39,80 +53,49 @@ module BootstrapEmail
39
53
  self.doc = premailer.doc
40
54
  end
41
55
 
42
- def add_layout!(html)
43
- document = Nokogiri::HTML(html)
44
- return document.to_html unless document.at_css('head').nil?
56
+ def compile_html!
57
+ BootstrapEmail::Converter::Body.build(doc)
58
+ BootstrapEmail::Converter::Block.build(doc)
45
59
 
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
60
+ BootstrapEmail::Converter::Button.build(doc)
61
+ BootstrapEmail::Converter::Badge.build(doc)
62
+ BootstrapEmail::Converter::Alert.build(doc)
63
+ BootstrapEmail::Converter::Card.build(doc)
64
+ BootstrapEmail::Converter::Hr.build(doc)
65
+ BootstrapEmail::Converter::Container.build(doc)
66
+ BootstrapEmail::Converter::Grid.build(doc)
67
+ BootstrapEmail::Converter::Stack.build(doc)
50
68
 
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)
69
+ BootstrapEmail::Converter::Color.build(doc)
70
+ BootstrapEmail::Converter::Spacing.build(doc)
71
+ BootstrapEmail::Converter::Margin.build(doc)
72
+ BootstrapEmail::Converter::Spacer.build(doc)
73
+ BootstrapEmail::Converter::Align.build(doc)
74
+ BootstrapEmail::Converter::Padding.build(doc)
75
+
76
+ BootstrapEmail::Converter::PreviewText.build(doc)
77
+ BootstrapEmail::Converter::Table.build(doc)
68
78
  end
69
79
 
70
80
  def inline_css!
71
81
  premailer.to_inline_css
72
82
  end
73
83
 
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)
84
+ def configure_html!
85
+ BootstrapEmail::Converter::ForceEncoding.build(doc)
86
+ BootstrapEmail::Converter::HeadStyle.build(doc)
87
+ BootstrapEmail::Converter::VersionComment.build(doc)
80
88
  end
81
89
 
82
90
  def finalize_document!
91
+ html = BootstrapEmail::Converter::ForceEncoding.replace(doc.to_html)
83
92
  case type
84
93
  when :rails
85
- (@mail.html_part || @mail).body = doc.to_html
94
+ (@mail.html_part || @mail).body = html
86
95
  @mail
87
96
  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
97
+ html
114
98
  end
115
- (default + custom).gsub(/\n\s*\n+/, "\n")
116
99
  end
117
100
  end
118
101
  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 = "align-#{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