inkcite 1.7.0 → 1.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +7 -6
- data/inkcite.gemspec +1 -0
- data/lib/inkcite/cli/base.rb +5 -4
- data/lib/inkcite/cli/build.rb +10 -10
- data/lib/inkcite/cli/init.rb +2 -2
- data/lib/inkcite/cli/server.rb +5 -0
- data/lib/inkcite/email.rb +10 -6
- data/lib/inkcite/renderer/base.rb +20 -0
- data/lib/inkcite/renderer/div.rb +10 -2
- data/lib/inkcite/renderer/element.rb +13 -1
- data/lib/inkcite/renderer/footnote.rb +3 -2
- data/lib/inkcite/renderer/image.rb +26 -3
- data/lib/inkcite/renderer/responsive.rb +23 -10
- data/lib/inkcite/renderer/table.rb +118 -34
- data/lib/inkcite/renderer/table_base.rb +7 -16
- data/lib/inkcite/renderer/td.rb +149 -57
- data/lib/inkcite/uploader.rb +28 -22
- data/lib/inkcite/version.rb +1 -1
- data/lib/inkcite/view.rb +24 -5
- data/test/renderer/image_spec.rb +4 -0
- data/test/renderer/table_spec.rb +20 -0
- metadata +16 -5
- data/bin/release-major +0 -1
- data/bin/release-minor +0 -1
- data/bin/release-patch +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d4d3044cbc8db45ebd662ab451d759ef1f0aa25d
|
4
|
+
data.tar.gz: e87b26d745c307427fd53c7f1e55c19e242f3aaa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: efb49479b68f7745f54902ea4d488c80532345baf772f74f6c939c22a77247c8f9635f4ac73cbf765cab9b3b96319c5af832f06f53c072e5ffd3541cda5a7460
|
7
|
+
data.tar.gz: 40b7fcc6f1bf820be7bfe5b890730a831b08f48fdd390061584c6808164b19998f1f038a1ad33740365f38689498e192d61f2f4d8719cb6280a235e9fd68b66a
|
data/README.md
CHANGED
@@ -5,12 +5,13 @@ Like [Middleman] is to static web sites, Inkcite makes it easy for email
|
|
5
5
|
developers to keep their code DRY (don’t repeat yourself) and integrate
|
6
6
|
versioning, testing and minification into their workflow.
|
7
7
|
|
8
|
-
* Easy, flexible templates, variables and
|
9
|
-
* ERB for easy A/B
|
8
|
+
* Easy, flexible templates, variables and Helpers
|
9
|
+
* ERB for dynamic content and easy A/B Testing and Versioning
|
10
10
|
* Automatic link tagging and tracking
|
11
|
-
* [Litmus]
|
12
|
-
*
|
13
|
-
*
|
11
|
+
* [Litmus]-integrated compatibility testing and analytics
|
12
|
+
* Email preview distribution lists
|
13
|
+
* Automatic image optimization using ImageOptim
|
14
|
+
* Failsafe rules to double-check your work
|
14
15
|
|
15
16
|
## Installation
|
16
17
|
|
@@ -106,7 +107,7 @@ developer questions in a timely manner.
|
|
106
107
|
|
107
108
|
## License
|
108
109
|
|
109
|
-
Copyright (c) 2014 Jeffrey D. Hoffman. MIT Licensed, see [LICENSE] for
|
110
|
+
Copyright (c) 2014-2015 Jeffrey D. Hoffman. MIT Licensed, see [LICENSE] for
|
110
111
|
details.
|
111
112
|
|
112
113
|
[Middleman]: http://middlemanapp.com
|
data/inkcite.gemspec
CHANGED
@@ -29,6 +29,7 @@ Gem::Specification.new do |spec|
|
|
29
29
|
spec.add_dependency 'faker'
|
30
30
|
spec.add_dependency 'guard'
|
31
31
|
spec.add_dependency 'guard-livereload'
|
32
|
+
spec.add_dependency 'htmlbeautifier'
|
32
33
|
spec.add_dependency 'image_optim'
|
33
34
|
spec.add_dependency 'image_optim_pack'
|
34
35
|
spec.add_dependency 'litmus'
|
data/lib/inkcite/cli/base.rb
CHANGED
@@ -9,6 +9,10 @@ module Inkcite
|
|
9
9
|
option :archive,
|
10
10
|
:aliases => '-a',
|
11
11
|
:desc => 'The name of the archive to compress final assets into'
|
12
|
+
option :environment,
|
13
|
+
:aliases => '-e',
|
14
|
+
:desc => 'The environment (development, preview or production) to build',
|
15
|
+
:default => :production
|
12
16
|
option :force,
|
13
17
|
:aliases => '-f',
|
14
18
|
:desc => 'Build even if there are errors (not recommended)',
|
@@ -16,10 +20,7 @@ module Inkcite
|
|
16
20
|
|
17
21
|
def build
|
18
22
|
require_relative 'build'
|
19
|
-
Cli::Build.invoke(email,
|
20
|
-
:archive => options['archive'],
|
21
|
-
:force => options['force']
|
22
|
-
})
|
23
|
+
Cli::Build.invoke(email, options)
|
23
24
|
end
|
24
25
|
|
25
26
|
desc 'init NAME [options]', 'Initialize a new email project in the NAME directory'
|
data/lib/inkcite/cli/build.rb
CHANGED
@@ -7,24 +7,23 @@ module Inkcite
|
|
7
7
|
errors = false
|
8
8
|
|
9
9
|
# Don't allow production files to be built if there are errors.
|
10
|
-
email.views(:
|
10
|
+
email.views(opts[:environment]) do |ev|
|
11
11
|
|
12
12
|
ev.render!
|
13
|
+
next if ev.errors.blank?
|
13
14
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
errors = true
|
18
|
-
end
|
15
|
+
puts "The #{ev.version} version (#{ev.format}) has #{ev.errors.size} errors:"
|
16
|
+
puts " - #{ev.errors.join("\n - ")}"
|
17
|
+
errors = true
|
19
18
|
|
20
19
|
end
|
21
20
|
|
22
|
-
abort(
|
21
|
+
abort('Fix errors or use --force to build') if errors && !opts[:force]
|
23
22
|
|
24
23
|
# First, compile all assets to the build directory.
|
25
24
|
build_to_dir email, opts
|
26
25
|
|
27
|
-
#
|
26
|
+
# Compress the directory into an archive if so desired.
|
28
27
|
archive = opts[:archive]
|
29
28
|
build_archive(email, opts) unless archive.blank?
|
30
29
|
|
@@ -102,7 +101,7 @@ module Inkcite
|
|
102
101
|
email.optimize_images
|
103
102
|
|
104
103
|
# For each of the production views, build the HTML and links files.
|
105
|
-
email.views(:
|
104
|
+
email.views(opts[:environment]) do |ev|
|
106
105
|
|
107
106
|
File.open(File.join(build_html_to, ev.file_name), 'w') { |f| ev.write(f) }
|
108
107
|
|
@@ -113,7 +112,8 @@ module Inkcite
|
|
113
112
|
|
114
113
|
# Copy all of the source images into the build directory in preparation
|
115
114
|
# for optimization
|
116
|
-
|
115
|
+
build_images_from = email.optimized_image_dir
|
116
|
+
FileUtils.cp_r(File.join(build_images_from, '.'), build_images_to) if File.exists?(build_images_from)
|
117
117
|
|
118
118
|
end
|
119
119
|
|
data/lib/inkcite/cli/init.rb
CHANGED
@@ -19,7 +19,7 @@ module Inkcite
|
|
19
19
|
is_empty = opts[:empty]
|
20
20
|
|
21
21
|
# True if we're initializing a project from the built-in files.
|
22
|
-
is_new =
|
22
|
+
is_new = from_path.blank?
|
23
23
|
if is_new
|
24
24
|
|
25
25
|
# Use the default, bundled path if a from-path wasn't specified.
|
@@ -51,7 +51,7 @@ module Inkcite
|
|
51
51
|
|
52
52
|
# If the example email is required, switch to the example root and
|
53
53
|
# copy the files within over the existing files.
|
54
|
-
|
54
|
+
if is_new && !is_empty
|
55
55
|
from_path = File.join(Inkcite.asset_path, 'example')
|
56
56
|
FileUtils.cp_r(File.join(from_path, '.'), full_init_path)
|
57
57
|
puts 'Copied example email files'
|
data/lib/inkcite/cli/server.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'guard'
|
2
2
|
require 'guard/commander'
|
3
|
+
require 'htmlbeautifier'
|
3
4
|
require 'rack'
|
4
5
|
require 'rack-livereload'
|
5
6
|
require 'webrick'
|
@@ -123,6 +124,10 @@ module Inkcite
|
|
123
124
|
|
124
125
|
html = view.render!
|
125
126
|
|
127
|
+
# If we're rendering the development version of the email, beautify the
|
128
|
+
# output so that
|
129
|
+
html = HtmlBeautifier.beautify(html) if view.development?
|
130
|
+
|
126
131
|
unless view.errors.blank?
|
127
132
|
error_count = view.errors.count
|
128
133
|
puts "#{ts} #{error_count} error#{'s' if error_count > 1} or warning#{'s' if error_count > 1}:"
|
data/lib/inkcite/email.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
module Inkcite
|
2
2
|
class Email
|
3
3
|
|
4
|
+
BROWSER_VERSION = :'browser-version'
|
4
5
|
CACHE_BUST = :'cache-bust'
|
5
6
|
IMAGE_HOST = :'image-host'
|
6
7
|
IMAGE_PLACEHOLDERS = :'image-placeholders'
|
@@ -28,7 +29,11 @@ module Inkcite
|
|
28
29
|
|
29
30
|
def formats env
|
30
31
|
|
31
|
-
|
32
|
+
# Inkcite is always capable of producing an email version of
|
33
|
+
# the project.
|
34
|
+
f = [ :email ]
|
35
|
+
|
36
|
+
f << :browser if config[BROWSER_VERSION] == true
|
32
37
|
|
33
38
|
# Need to make sure a source.txt exists before we can include
|
34
39
|
# it in the list of known formats.
|
@@ -61,7 +66,7 @@ module Inkcite
|
|
61
66
|
end
|
62
67
|
|
63
68
|
def optimize_images?
|
64
|
-
config[
|
69
|
+
config[OPTIMIZE_IMAGES] == true
|
65
70
|
end
|
66
71
|
|
67
72
|
# Returns the directory that optimized, compressed images
|
@@ -102,7 +107,9 @@ module Inkcite
|
|
102
107
|
version = (version || versions.first).to_sym
|
103
108
|
|
104
109
|
raise "Unknown environment \"#{environment}\" - must be one of #{ENVIRONMENTS.join(',')}" unless ENVIRONMENTS.include?(environment)
|
105
|
-
|
110
|
+
|
111
|
+
_formats = formats(environment)
|
112
|
+
raise "Unknown format \"#{format}\" - must be one of #{_formats.join(',')}" unless _formats.include?(format)
|
106
113
|
raise "Unknown version: \"#{version}\" - must be one of #{versions.join(',')}" unless versions.include?(version)
|
107
114
|
|
108
115
|
# Instantiate a new view of this email with the desired view and
|
@@ -130,9 +137,6 @@ module Inkcite
|
|
130
137
|
|
131
138
|
private
|
132
139
|
|
133
|
-
# Allowed formats.
|
134
|
-
FORMATS = [ :browser, :email, :text ].freeze
|
135
|
-
|
136
140
|
# Name of the property controlling the meta data file name and
|
137
141
|
# the default file name.
|
138
142
|
META_FILE_NAME = :'meta-file'
|
@@ -24,6 +24,7 @@ module Inkcite
|
|
24
24
|
MARGIN_LEFT = :'margin-left'
|
25
25
|
MARGIN_RIGHT = :'margin-right'
|
26
26
|
MARGIN_TOP = :'margin-top'
|
27
|
+
MAX_WIDTH = :'max-width'
|
27
28
|
PADDING_X = :'padding-x'
|
28
29
|
PADDING_Y = :'padding-y'
|
29
30
|
TEXT_ALIGN = :'text-align'
|
@@ -78,6 +79,10 @@ module Inkcite
|
|
78
79
|
Renderer.hex(color)
|
79
80
|
end
|
80
81
|
|
82
|
+
def if_mso html
|
83
|
+
%Q(<!--[if mso]>#{html.to_s}<![endif]-->)
|
84
|
+
end
|
85
|
+
|
81
86
|
def none? val
|
82
87
|
val.blank? || val == NONE
|
83
88
|
end
|
@@ -92,6 +97,21 @@ module Inkcite
|
|
92
97
|
|
93
98
|
end
|
94
99
|
|
100
|
+
def mix_border element, opt
|
101
|
+
|
102
|
+
border = opt[:border]
|
103
|
+
element.style[:border] = border unless border.blank?
|
104
|
+
|
105
|
+
# Iterate through each of the possible borders and apply them individually
|
106
|
+
# to the style if they are defined.
|
107
|
+
DIRECTIONS.each do |dir|
|
108
|
+
key = :"border-#{dir}"
|
109
|
+
border = opt[key]
|
110
|
+
element.style[key] = border unless border.blank? || border == NONE
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
|
95
115
|
def mix_font element, opt, ctx, parent=nil
|
96
116
|
|
97
117
|
# Always ensure we have a parent to inherit from.
|
data/lib/inkcite/renderer/div.rb
CHANGED
@@ -8,16 +8,24 @@ module Inkcite
|
|
8
8
|
|
9
9
|
div = Element.new('div')
|
10
10
|
|
11
|
+
width = opt[:width]
|
12
|
+
div.style[:width] = px(width) unless width.blank?
|
13
|
+
|
11
14
|
height = opt[:height].to_i
|
12
15
|
div.style[:height] = px(height) if height > 0
|
13
16
|
|
17
|
+
mix_background div, opt
|
18
|
+
mix_font div, opt, ctx
|
19
|
+
|
14
20
|
# Text alignment - left, right, center.
|
15
21
|
align = opt[:align]
|
16
22
|
div.style[TEXT_ALIGN] = align unless none?(align)
|
17
23
|
|
18
|
-
|
24
|
+
valign = opt[:valign]
|
25
|
+
div.style[VERTICAL_ALIGN] = valign unless valign.blank?
|
19
26
|
|
20
|
-
|
27
|
+
display = opt[:display]
|
28
|
+
div.style[:display] = display unless display.blank?
|
21
29
|
|
22
30
|
mix_responsive div, opt, ctx
|
23
31
|
|
@@ -13,7 +13,19 @@ module Inkcite
|
|
13
13
|
# True if the tag self-closes as in "<img .../>"
|
14
14
|
@self_close = att.delete(:self_close) == true
|
15
15
|
|
16
|
+
# For caller convenience, accept a style hash from the attributes
|
17
|
+
# or initialize it here.
|
18
|
+
@styles = att.delete(:style) || {}
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
# I found myself doing a lot of Element.new('tag', { }).to_s + 'more html'
|
23
|
+
# so this method makes it easier by allowing elements to be added to
|
24
|
+
# strings.
|
25
|
+
def + html
|
26
|
+
to_s << html.to_s
|
16
27
|
end
|
28
|
+
alias_method :concat, :+
|
17
29
|
|
18
30
|
def [] key
|
19
31
|
@att[key]
|
@@ -52,7 +64,7 @@ module Inkcite
|
|
52
64
|
end
|
53
65
|
|
54
66
|
def style
|
55
|
-
@styles
|
67
|
+
@styles
|
56
68
|
end
|
57
69
|
|
58
70
|
def to_s
|
@@ -21,6 +21,7 @@ module Inkcite
|
|
21
21
|
# activate but those read from footnotes.tsv are inactive until
|
22
22
|
# referenced in the source.
|
23
23
|
attr_accessor :active
|
24
|
+
alias_method :active?, :active
|
24
25
|
|
25
26
|
def initialize id, symbol, text, active=true
|
26
27
|
@id = id
|
@@ -94,8 +95,8 @@ module Inkcite
|
|
94
95
|
# Grab the last numeric footnote that was specified and, assuming
|
95
96
|
# there is one, increment the count. Otherwise, start the count
|
96
97
|
# off at one.
|
97
|
-
last_instance = ctx.footnotes.select(&:
|
98
|
-
instance.symbol = last_instance
|
98
|
+
last_instance = ctx.footnotes.select { |fn| fn.numeric? && fn.active? }.collect(&:number).max.to_i
|
99
|
+
instance.symbol = last_instance + 1
|
99
100
|
|
100
101
|
end
|
101
102
|
|
@@ -14,6 +14,7 @@ module Inkcite
|
|
14
14
|
img[:src] = image_url(opt[:src], opt, ctx)
|
15
15
|
|
16
16
|
mix_background img, opt
|
17
|
+
mix_border img, opt
|
17
18
|
|
18
19
|
# Check to see if there is alt text specified for this image. We are
|
19
20
|
# testing against nil because sometimes the author desires an empty
|
@@ -36,6 +37,9 @@ module Inkcite
|
|
36
37
|
|
37
38
|
mix_font img, opt, ctx
|
38
39
|
|
40
|
+
text_align = opt[TEXT_ALIGN]
|
41
|
+
img.style[TEXT_ALIGN] = text_align unless text_align.blank?
|
42
|
+
|
39
43
|
end
|
40
44
|
|
41
45
|
end
|
@@ -75,9 +79,28 @@ module Inkcite
|
|
75
79
|
|
76
80
|
mobile = opt[:mobile]
|
77
81
|
|
78
|
-
#
|
79
|
-
#
|
80
|
-
|
82
|
+
# Fluid-Hybrid responsive images courtesy of @moonstrips and @CourtFantinato.
|
83
|
+
# http://webdesign.tutsplus.com/tutorials/creating-a-future-proof-responsive-email-without-media-queries--cms-23919#comment-2074740905
|
84
|
+
if mobile == FLUID
|
85
|
+
|
86
|
+
# Set the inline styles of the image to scale with aspect ratio
|
87
|
+
# intact up to the maximum width of the image itself.
|
88
|
+
img.style[MAX_WIDTH] = px(opt[:width])
|
89
|
+
img.style[:width] = '100%'
|
90
|
+
img.style[:height] = 'auto'
|
91
|
+
|
92
|
+
# Leave the explicit width attribute set (this prevents Outlook from
|
93
|
+
# blowing up) but clear the height attribute as Gmail images will not
|
94
|
+
# maintain aspect ratio if present.
|
95
|
+
img[:height] = nil
|
96
|
+
|
97
|
+
else
|
98
|
+
|
99
|
+
# Check to see if this image is inside of a mobile-image declaration.
|
100
|
+
# If so, the image defaults to hide on mobile.
|
101
|
+
mobile = HIDE if mobile.blank? && !ctx.parent_opts(:mobile_image).blank?
|
102
|
+
|
103
|
+
end
|
81
104
|
|
82
105
|
mix_responsive img, opt, ctx, mobile
|
83
106
|
|
@@ -2,15 +2,17 @@ module Inkcite
|
|
2
2
|
module Renderer
|
3
3
|
class Responsive < Base
|
4
4
|
|
5
|
-
BUTTON
|
6
|
-
DROP
|
7
|
-
FILL
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
5
|
+
BUTTON = 'button'
|
6
|
+
DROP = 'drop'
|
7
|
+
FILL = 'fill'
|
8
|
+
FLUID = 'fluid'
|
9
|
+
FLUID_DROP = 'fluid-drop'
|
10
|
+
HIDE = 'hide'
|
11
|
+
IMAGE = 'img'
|
12
|
+
SHOW = 'show'
|
13
|
+
SWITCH = 'switch'
|
14
|
+
SWITCH_UP = 'switch-up'
|
15
|
+
TOGGLE = 'toggle'
|
14
16
|
|
15
17
|
# For elements that take on different background properties
|
16
18
|
# when they go responsive
|
@@ -180,6 +182,12 @@ module Inkcite
|
|
180
182
|
|
181
183
|
protected
|
182
184
|
|
185
|
+
# Returns true if the mobile klass provided matches any of the
|
186
|
+
# Fluid-Hybrid classes.
|
187
|
+
def is_fluid? mobile
|
188
|
+
mobile == FLUID || mobile == FLUID_DROP
|
189
|
+
end
|
190
|
+
|
183
191
|
def mix_font element, opt, ctx, parent=nil
|
184
192
|
|
185
193
|
# Let the super class do its thing and grab the name of the font
|
@@ -215,6 +223,11 @@ module Inkcite
|
|
215
223
|
# Nothing to do if there is no class specified.s
|
216
224
|
return nil if klass.blank?
|
217
225
|
|
226
|
+
# The Fluid-Hybrid klass is also ignored because it doesn't involve
|
227
|
+
# media queries - aborting early to avoid the "missing mobile class"
|
228
|
+
# warning normally generated.
|
229
|
+
return nil if is_fluid?(klass)
|
230
|
+
|
218
231
|
mq = ctx.media_query
|
219
232
|
|
220
233
|
# The element's tag - e.g. table, td, etc.
|
@@ -226,7 +239,7 @@ module Inkcite
|
|
226
239
|
|
227
240
|
id = opt[:id]
|
228
241
|
if id.blank?
|
229
|
-
ctx.errors 'Mobile elements with toggle behavior require an ID attribute', { :tag => tag} if id.blank?
|
242
|
+
ctx.errors 'Mobile elements with toggle behavior require an ID attribute', { :tag => tag } if id.blank?
|
230
243
|
|
231
244
|
else
|
232
245
|
|
@@ -4,60 +4,144 @@ module Inkcite
|
|
4
4
|
|
5
5
|
def render tag, opt, ctx
|
6
6
|
|
7
|
+
html = ''
|
8
|
+
|
9
|
+
# We're either going to be pushing a newly opened table onto this stack
|
10
|
+
# or we're popping the open opts off of it.
|
7
11
|
tag_stack = ctx.tag_stack(:table)
|
8
12
|
|
9
13
|
if tag == CLOSE_TABLE
|
10
14
|
|
11
|
-
#
|
12
|
-
|
15
|
+
# Pop the opts used when the table was originally opened. Then grab the
|
16
|
+
# mobile attribute. If this is a fluid table, we'll need to do close some
|
17
|
+
# of the additional tags injected when it was opened.
|
18
|
+
open_opt = tag_stack.pop
|
19
|
+
open_mobile = open_opt[:mobile]
|
13
20
|
|
14
|
-
|
21
|
+
# If the table was declared as Fluid-Hybrid Drop, then there are some additional
|
22
|
+
# elements that need to be closed before the regular row-table closure that
|
23
|
+
# the Table helper normally produces.
|
24
|
+
if open_mobile == FLUID_DROP
|
15
25
|
|
16
|
-
|
26
|
+
# Close the interior conditional table for Outlook that contains the floating blocks.
|
27
|
+
html << if_mso('</tr></table>')
|
17
28
|
|
18
|
-
|
19
|
-
|
20
|
-
|
29
|
+
# Close what @campaignmonitor calls the "secret weapon" cell that typically aligns
|
30
|
+
# the text horizontally and vertically aligns the floating elements.
|
31
|
+
html << '</td>' # Styled
|
32
|
+
end
|
21
33
|
|
22
|
-
|
34
|
+
# Normal Inkcite Helper close HTML.
|
35
|
+
html << '</tr></table>'
|
23
36
|
|
24
|
-
|
25
|
-
|
37
|
+
# Close the conditional table for Output that contains the entire fluid layout.
|
38
|
+
html << if_mso('</td></tr></table>') if is_fluid?(open_mobile)
|
26
39
|
|
27
|
-
|
28
|
-
mix_text_shadow table, opt, ctx
|
40
|
+
else
|
29
41
|
|
30
|
-
|
31
|
-
|
32
|
-
|
42
|
+
# Push this table onto the stack which will make it's declaration
|
43
|
+
# available to its child TDs.
|
44
|
+
tag_stack << opt
|
33
45
|
|
34
|
-
|
35
|
-
align = opt[:align] || opt[:float]
|
36
|
-
table[:align] = align unless align.blank?
|
46
|
+
table = Element.new(tag, { :border => 0, :cellspacing => 0 })
|
37
47
|
|
38
|
-
|
39
|
-
|
48
|
+
# Grab the responsive mobile klass that is assigned to this table, if any.
|
49
|
+
mobile = opt[:mobile]
|
40
50
|
|
41
|
-
|
42
|
-
|
51
|
+
# Check if fluid-drop has been specified. This will force a lot more HTML to
|
52
|
+
# be produced for this table and its child TDs.
|
53
|
+
is_fluid_drop = mobile == FLUID_DROP
|
43
54
|
|
44
|
-
|
45
|
-
|
55
|
+
# Inherit base cell attributes - border, background color and image, etc.
|
56
|
+
mix_all table, opt, ctx
|
57
|
+
mix_margins table, opt, ctx
|
58
|
+
mix_text_shadow table, opt, ctx
|
46
59
|
|
47
|
-
|
60
|
+
# Conveniently accept padding (easier to type and consistent with CSS) or
|
61
|
+
# cellpadding which must always be declared.
|
62
|
+
#
|
63
|
+
# If Fluid-Drop is enabled, padding is always zero at this top-level table
|
64
|
+
# and will be applied in the TD renderer when it creates a new table to
|
65
|
+
# wrap itself in.
|
66
|
+
table[:cellpadding] = is_fluid_drop ? 0 : get_padding(opt)
|
48
67
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
# see the parent as DROP.
|
53
|
-
mobile = FILL if mobile == DROP || mobile == SWITCH
|
68
|
+
# Conveniently accept both float and align to mean the same thing.
|
69
|
+
align = opt[:align] || opt[:float]
|
70
|
+
table[:align] = align unless align.blank?
|
54
71
|
|
55
|
-
|
72
|
+
border_radius = opt[BORDER_RADIUS].to_i
|
73
|
+
table.style[BORDER_RADIUS] = px(border_radius) if border_radius > 0
|
56
74
|
|
57
|
-
|
58
|
-
|
75
|
+
border_collapse = opt[BORDER_COLLAPSE]
|
76
|
+
table.style[BORDER_COLLAPSE] = border_collapse unless border_collapse.blank?
|
77
|
+
|
78
|
+
|
79
|
+
# For both fluid and fluid-drop share certain setup which is performed here.
|
80
|
+
if is_fluid?(mobile)
|
81
|
+
|
82
|
+
# Width must always be specified on fluid tables, like it is for images.
|
83
|
+
# Warn the designer if a width has not been supplied in pixels.
|
84
|
+
width = opt[:width].to_i
|
85
|
+
ctx.error("Width is a required attribute when '#{mobile}' is applied to a {table}", opt) unless width > 0
|
86
|
+
|
87
|
+
# Fluid table method courtesy of @campaignmonitor - assign the max-width in
|
88
|
+
# pixels and set the normal width to 100%.
|
89
|
+
# https://www.campaignmonitor.com/blog/email-marketing/2014/07/creating-a-centred-responsive-design-without-media-queries/
|
90
|
+
table.style[MAX_WIDTH] = px(width)
|
91
|
+
table[:width] = '100%'
|
92
|
+
|
93
|
+
# Outlook (and Lotus Notes, if you can believe it) doesn't support max-width
|
94
|
+
# so we need to wrap the entire fluid table in a conditional table that
|
95
|
+
# ensures layout displays within the actual maximum pixels width.
|
96
|
+
html << if_mso(Element.new('table', {
|
97
|
+
:align => opt[:align], :border => 0, :cellspacing => 0, :cellpadding => 0, :width => opt[:width].to_i
|
98
|
+
}) + '<tr><td>')
|
99
|
+
|
100
|
+
elsif mobile == DROP || mobile == SWITCH
|
101
|
+
|
102
|
+
# When a Table is configured to have it's cells DROP then it
|
103
|
+
# actually needs to FILL on mobile and it's child Tds will
|
104
|
+
# be DROP'd. Override the local mobile klass so the child Tds
|
105
|
+
# see the parent as DROP.
|
106
|
+
mobile = FILL
|
107
|
+
|
108
|
+
end
|
59
109
|
|
60
|
-
|
110
|
+
mix_responsive table, opt, ctx, mobile
|
111
|
+
|
112
|
+
html << table.to_s
|
113
|
+
html << '<tr>'
|
114
|
+
|
115
|
+
if is_fluid_drop
|
116
|
+
|
117
|
+
# Fluid-Drop tables need a default alignment specified which is inherited
|
118
|
+
# by the child TD elements if not otherwise specified.
|
119
|
+
#
|
120
|
+
# 11/8/2015: For reasons I don't understand, if the table is not valigned
|
121
|
+
# middle by default, then we lose the ability to valign-middle individual
|
122
|
+
# TD children. So, if we force 'middle' here, then the TDs can override
|
123
|
+
# with 'top' or 'bottom' alignment when desired.
|
124
|
+
valign = opt[:valign] ||= 'middle'
|
125
|
+
|
126
|
+
# According to @campaignmonitor this is the secret weapon of Fluid-Hyrbid
|
127
|
+
# drop which wraps the floating elements and centers them appropriately.
|
128
|
+
# https://www.campaignmonitor.com/blog/email-marketing/2014/07/creating-a-centred-responsive-design-without-media-queries/
|
129
|
+
#
|
130
|
+
# The zero-size font addresses a rendering problem in Outlook:
|
131
|
+
# https://css-tricks.com/fighting-the-space-between-inline-block-elements/
|
132
|
+
html << Element.new('td', :style => { TEXT_ALIGN => :center, VERTICAL_ALIGN => opt[:valign], FONT_SIZE => 0 }).to_s
|
133
|
+
|
134
|
+
# Lastly, Outlook needs yet another conditional table that will be used
|
135
|
+
# to contain the floating blocks. The TD elements are generated by
|
136
|
+
# each of the columns within this Fluid-Drop table.
|
137
|
+
html << if_mso(Element.new('table', :width => '100%', :align => :center, :cellpadding => 0, :cellspacing => 0, :border => 0) + '<tr>')
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
|
143
|
+
html
|
144
|
+
end
|
61
145
|
|
62
146
|
CLOSE_TABLE = '/table'
|
63
147
|
|
@@ -4,10 +4,16 @@ module Inkcite
|
|
4
4
|
|
5
5
|
protected
|
6
6
|
|
7
|
+
# Returns cellpadding specified from the provided attributes by
|
8
|
+
# Checking both :padding and :cellpadding.
|
9
|
+
def get_padding opt
|
10
|
+
(opt[:padding] || opt[:cellpadding]).to_i
|
11
|
+
end
|
12
|
+
|
7
13
|
def mix_all element, opt, ctx
|
8
14
|
|
9
15
|
mix_background element, opt, ctx
|
10
|
-
mix_border element, opt
|
16
|
+
mix_border element, opt
|
11
17
|
mix_dimensions element, opt, ctx
|
12
18
|
|
13
19
|
end
|
@@ -67,21 +73,6 @@ module Inkcite
|
|
67
73
|
|
68
74
|
end
|
69
75
|
|
70
|
-
def mix_border element, opt, ctx
|
71
|
-
|
72
|
-
border = opt[:border]
|
73
|
-
element.style[:border] = border unless border.blank?
|
74
|
-
|
75
|
-
# Iterate through each of the possible borders and apply them individually
|
76
|
-
# to the style if they are defined.
|
77
|
-
DIRECTIONS.each do |dir|
|
78
|
-
key = :"border-#{dir}"
|
79
|
-
border = opt[key]
|
80
|
-
element.style[key] = border unless border.blank? || border == NONE
|
81
|
-
end
|
82
|
-
|
83
|
-
end
|
84
|
-
|
85
76
|
def mix_dimensions element, opt, ctx
|
86
77
|
|
87
78
|
# Not taking .to_i because we want to accept both integer values
|
data/lib/inkcite/renderer/td.rb
CHANGED
@@ -4,79 +4,171 @@ module Inkcite
|
|
4
4
|
|
5
5
|
def render tag, opt, ctx
|
6
6
|
|
7
|
+
html = ''
|
8
|
+
|
9
|
+
# Tracks the depth of currently open TD elements.
|
7
10
|
tag_stack = ctx.tag_stack(:td)
|
8
11
|
|
12
|
+
# Grab the attributes of the parent table so that the TD can inherit
|
13
|
+
# specific values like padding, valign, responsiveness, etc.
|
14
|
+
table_opt = ctx.parent_opts(:table)
|
15
|
+
|
16
|
+
# Check to see if the parent table was set to fluid-drop which causes
|
17
|
+
# the table cells to be wrapped in <div> elements and floated to
|
18
|
+
# cause them to display more responsively on Android Mail and Gmail apps.
|
19
|
+
#
|
20
|
+
# Fluid-Hybrid TD courtesy of @moonstrips and our friends at Campaign Monitor
|
21
|
+
# https://www.campaignmonitor.com/blog/email-marketing/2014/07/creating-a-centred-responsive-design-without-media-queries/
|
22
|
+
is_fluid_drop = table_opt[:mobile] == FLUID_DROP
|
23
|
+
|
9
24
|
if tag == CLOSE_TD
|
25
|
+
|
26
|
+
# Retrieve the opts that were used to open this TD. We'll need them to
|
27
|
+
# check for the private _fluid_drop attribute.
|
10
28
|
tag_stack.pop
|
11
|
-
return '</td>'
|
12
|
-
end
|
13
29
|
|
14
|
-
|
15
|
-
|
16
|
-
tag_stack << opt
|
30
|
+
# Normal HTML produced by the Helper to close the cell.
|
31
|
+
html << '</td>'
|
17
32
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
td = Element.new('td')
|
23
|
-
|
24
|
-
# Inherit base cell attributes - border, background color and image, etc.
|
25
|
-
mix_all td, opt, ctx
|
26
|
-
|
27
|
-
# Force the td to collapse to a single pixel to support images that
|
28
|
-
# are less than 15 pixels.
|
29
|
-
opt.merge!({
|
30
|
-
:font => NONE,
|
31
|
-
:color => NONE,
|
32
|
-
FONT_SIZE => 1,
|
33
|
-
LINE_HEIGHT => 1
|
34
|
-
}) unless opt[:flush].blank?
|
35
|
-
|
36
|
-
# It is a best-practice to declare the same padding on all cells in a
|
37
|
-
# table. Check to see if padding was declared on the parent.
|
38
|
-
padding = parent[:padding].to_i
|
39
|
-
td.style[:padding] = px(padding) if padding > 0
|
40
|
-
|
41
|
-
# Custom handling for text align on TDs rather than Base's mix_text_align
|
42
|
-
# because if possible, using align= rather than a style keeps emails
|
43
|
-
# smaller. But for left-aligned text, you gotta use a style because
|
44
|
-
# you know, Outlook.
|
45
|
-
align = opt[:align]
|
46
|
-
unless align.blank?
|
47
|
-
td[:align] = align
|
48
|
-
|
49
|
-
# Must use style to reinforce left-align text in certain email clients.
|
50
|
-
# All other alignments are accepted naturally.
|
51
|
-
td.style[TEXT_ALIGN] = align if align == LEFT
|
33
|
+
# If the td was originally opened with fluid-drop, we need to do a fair
|
34
|
+
# bit of cleanup...
|
35
|
+
if is_fluid_drop
|
52
36
|
|
53
|
-
|
37
|
+
# Close the
|
38
|
+
html << '</tr></table>'
|
54
39
|
|
55
|
-
|
56
|
-
|
40
|
+
# Close the floating, responsive div.
|
41
|
+
html << '{/div}'
|
57
42
|
|
58
|
-
|
59
|
-
|
43
|
+
# Close the conditional cell
|
44
|
+
html << if_mso('</td>')
|
60
45
|
|
61
|
-
|
46
|
+
end
|
62
47
|
|
63
|
-
|
64
|
-
if mobile.blank?
|
48
|
+
else
|
65
49
|
|
66
|
-
#
|
67
|
-
#
|
68
|
-
|
69
|
-
pm = parent[:mobile]
|
70
|
-
mobile = pm if pm == DROP || pm == SWITCH
|
50
|
+
# Push this tag onto the stack so that child elements (e.g. links)
|
51
|
+
# can have access to its attributes.
|
52
|
+
tag_stack << opt
|
71
53
|
|
72
|
-
|
54
|
+
td = Element.new('td')
|
55
|
+
|
56
|
+
# Check to see if a width has been specified for this element. The
|
57
|
+
# width is critical to Fluid-Hybrid drop.
|
58
|
+
width = opt[:width].to_i
|
59
|
+
|
60
|
+
# Check for vertical alignment applied to either the TD or to the
|
61
|
+
# parent Table.
|
62
|
+
valign = detect(opt[:valign], table_opt[:valign])
|
63
|
+
td[:valign] = valign unless valign.blank?
|
64
|
+
|
65
|
+
# It is a best-practice to declare the same padding on all cells in a
|
66
|
+
# table. Check to see if padding was declared on the parent.
|
67
|
+
padding = get_padding(table_opt)
|
68
|
+
td.style[:padding] = px(padding) if padding > 0
|
69
|
+
|
70
|
+
mobile = opt[:mobile]
|
71
|
+
|
72
|
+
# Need to handle Fluid-Drop HTML injection here before the rest of the
|
73
|
+
# TD is formalized. Fluid-Drop removes the width attribute of the cell
|
74
|
+
# as it is wrapped in a 100%-width table.
|
75
|
+
if is_fluid_drop
|
76
|
+
|
77
|
+
# Width must be specified for Fluid-Drop cells. Vertical-alignment is
|
78
|
+
# also important but should have been preset by the Table Helper if it
|
79
|
+
# was omitted by the designer.
|
80
|
+
ctx.error("Width is a required attribute when #{FLUID_DROP} is specified", opt) unless width > 0
|
81
|
+
ctx.error("Vertical alignment should be specified when #{FLUID_DROP} is specified", opt) if valign.blank?
|
82
|
+
|
83
|
+
# Conditional Outlook cell to prevent the 100%-wide table within from
|
84
|
+
# stretching beyond the max-width. Also, valign necessary to get float
|
85
|
+
# elements to align properly.
|
86
|
+
html << if_mso(Element.new('td', :width => width, :valign => valign))
|
87
|
+
|
88
|
+
# Per @campaignmonitor, the secret to the Fluid-Drop trick is to wrap the
|
89
|
+
# floating table in a div with "display: inline-block" - which means that
|
90
|
+
# they'll obey the text-align property on the parent cell (text-align affects
|
91
|
+
# all inline or inline-block elements in a container).
|
92
|
+
# https://www.campaignmonitor.com/blog/email-marketing/2014/07/creating-a-centred-responsive-design-without-media-queries/
|
93
|
+
|
94
|
+
div_mobile = mobile == HIDE ? HIDE : FILL
|
95
|
+
html << %Q({div width=#{width} display=inline-block valign=#{valign} mobile="#{div_mobile}"})
|
73
96
|
|
74
|
-
|
97
|
+
# One last wrapper table within the div. This 100%-wide table is also where any
|
98
|
+
# padding applied to the elements belongs.
|
99
|
+
html << Element.new('table', :cellpadding => padding, :cellspacing => 0, :border => 0, :width => '100%').to_s
|
100
|
+
html << '<tr>'
|
75
101
|
|
76
|
-
|
77
|
-
|
102
|
+
# Remove the width attribute from the TDs declaration.
|
103
|
+
opt.delete(:width)
|
104
|
+
|
105
|
+
# The TD nested within the floating div and additional table will inherit center-aligned
|
106
|
+
# text which means fluid-drop cells would have a default layout inconsistent with a regular
|
107
|
+
# TD - which will typically be left-aligned. So, unless otherwise specified, presume that
|
108
|
+
# the TD should have left-aligned text.
|
109
|
+
opt[:align] = 'left' if opt[:align].blank?
|
110
|
+
|
111
|
+
mobile = ''
|
112
|
+
|
113
|
+
end
|
114
|
+
|
115
|
+
# Inherit base cell attributes - border, background color and image, etc.
|
116
|
+
mix_all td, opt, ctx
|
117
|
+
|
118
|
+
# Force the td to collapse to a single pixel to support images that
|
119
|
+
# are less than 15 pixels.
|
120
|
+
opt.merge!({
|
121
|
+
:font => NONE,
|
122
|
+
:color => NONE,
|
123
|
+
FONT_SIZE => 1,
|
124
|
+
LINE_HEIGHT => 1
|
125
|
+
}) unless opt[:flush].blank?
|
126
|
+
|
127
|
+
# Custom handling for text align on TDs rather than Base's mix_text_align
|
128
|
+
# because if possible, using align= rather than a style keeps emails
|
129
|
+
# smaller. But for left-aligned text, you gotta use a style because
|
130
|
+
# you know, Outlook.
|
131
|
+
align = opt[:align]
|
132
|
+
unless align.blank?
|
133
|
+
td[:align] = align
|
134
|
+
|
135
|
+
# Must use style to reinforce left-align text in certain email clients.
|
136
|
+
# All other alignments are accepted naturally.
|
137
|
+
td.style[TEXT_ALIGN] = align if align == LEFT
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
rowspan = opt[:rowspan].to_i
|
142
|
+
td[:rowspan] = rowspan if rowspan > 0
|
143
|
+
|
144
|
+
mix_font td, opt, ctx, table_opt
|
145
|
+
|
146
|
+
# In Fluid-Drop, the font-size is set to zero to overcome Outlook rendering
|
147
|
+
# problems so it is important to warn the designer that they need to set
|
148
|
+
# it back to a reasonable size on the TD element.
|
149
|
+
# TODO [JDH 11/14/2015] Decide if the warning re: font-size should ever
|
150
|
+
# be restored based on whether or not users are finding it confusing.
|
151
|
+
|
152
|
+
if mobile.blank?
|
153
|
+
|
154
|
+
# If the cell doesn't define it's own responsive behavior, check to
|
155
|
+
# see if it inherits from its parent table. DROP and SWITCH declared
|
156
|
+
# at the table-level descend to their tds.
|
157
|
+
pm = table_opt[:mobile]
|
158
|
+
mobile = pm if pm == DROP || pm == SWITCH
|
159
|
+
|
160
|
+
end
|
161
|
+
|
162
|
+
mix_responsive td, opt, ctx, mobile
|
163
|
+
|
164
|
+
#outlook-bg <!--[if gte mso 9]>[n]<v:rect style="width:%width%px;height:%height%px;" strokecolor="none"><v:fill type="tile" src="%src%" /></v:fill></v:rect><v:shape id="theText[rnd]" style="position:absolute;width:%width%px;height:%height%px;margin:0;padding:0;%style%">[n]<![endif]-->
|
165
|
+
#/outlook-bg <!--[if gte mso 9]></v:shape><![endif]-->
|
166
|
+
|
167
|
+
html << td.to_s
|
168
|
+
|
169
|
+
end
|
78
170
|
|
79
|
-
|
171
|
+
html
|
80
172
|
end
|
81
173
|
|
82
174
|
private
|
data/lib/inkcite/uploader.rb
CHANGED
@@ -8,7 +8,7 @@ module Inkcite
|
|
8
8
|
|
9
9
|
times = []
|
10
10
|
|
11
|
-
[
|
11
|
+
['source.html', 'source.txt', 'helpers.tsv'].each do |file|
|
12
12
|
file = email.project_file(file)
|
13
13
|
times << File.mtime(file).to_i if File.exists?(file)
|
14
14
|
end
|
@@ -81,8 +81,8 @@ module Inkcite
|
|
81
81
|
end
|
82
82
|
|
83
83
|
# TODO: Verify SFTP configuration
|
84
|
-
host
|
85
|
-
path
|
84
|
+
host = config[:host]
|
85
|
+
path = config[:path]
|
86
86
|
username = config[:username]
|
87
87
|
password = config[:password]
|
88
88
|
|
@@ -107,7 +107,7 @@ module Inkcite
|
|
107
107
|
# Upload each version of the email.
|
108
108
|
email.versions.each do |version|
|
109
109
|
|
110
|
-
view = email.view(:preview, :
|
110
|
+
view = email.view(:preview, :email, version)
|
111
111
|
|
112
112
|
# Need to pass the upload path through the renderer to ensure
|
113
113
|
# that embedded tags will be converted into data.
|
@@ -117,28 +117,34 @@ module Inkcite
|
|
117
117
|
# the content and images is present.
|
118
118
|
mkdir! sftp, remote_root
|
119
119
|
|
120
|
+
# Upload the images to the remote directory. We use the last_remote_root
|
121
|
+
# to ensure that we're not repeatedly uploading the same images over and
|
122
|
+
# over when force is enabled -- but will re-upload images to distinct
|
123
|
+
# remote roots.
|
124
|
+
copy! sftp, local_images, remote_root, force && last_remote_root != remote_root
|
125
|
+
last_remote_root = remote_root
|
126
|
+
|
127
|
+
# Check to see if we're creating an in-browser version of the email.
|
128
|
+
next unless email.formats.include?(:browser)
|
129
|
+
|
130
|
+
browser_view = email.view(:preview, :browser, version)
|
131
|
+
|
120
132
|
# Check to see if there is a HTML version of this preview. Some emails
|
121
133
|
# do not have a hosted version and so it is not necessary to upload the
|
122
134
|
# HTML version of the email - but this is a bad practice.
|
123
135
|
file_name = view.file_name
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
end
|
138
|
-
|
139
|
-
# Upload the images to the remote directory
|
140
|
-
copy! sftp, local_images, remote_root, force && last_remote_root != remote_root
|
141
|
-
last_remote_root = remote_root
|
136
|
+
next if file_name.blank?
|
137
|
+
|
138
|
+
remote_file_name = File.join(remote_root, file_name)
|
139
|
+
puts "Uploading #{remote_file_name}"
|
140
|
+
|
141
|
+
# We need to use StringIO to write the email to a buffer in order to upload
|
142
|
+
# the email's content in binary so that its encoding is honored. SFTP defaults
|
143
|
+
# to ASCII-8bit in non-binary mode, so it was blowing up on UTF-8 special
|
144
|
+
# characters (e.g. "Mäkinen").
|
145
|
+
# http://stackoverflow.com/questions/9439289/netsftp-transfer-mode-binary-vs-text
|
146
|
+
io = StringIO.new(browser_view.render!)
|
147
|
+
sftp.upload!(io, remote_file_name)
|
142
148
|
|
143
149
|
end
|
144
150
|
|
data/lib/inkcite/version.rb
CHANGED
data/lib/inkcite/view.rb
CHANGED
@@ -61,8 +61,15 @@ module Inkcite
|
|
61
61
|
@config[FILE_NAME] = file_name
|
62
62
|
|
63
63
|
# The MediaQuery object manages the responsive styles that are applied to
|
64
|
-
# the email during rendering.
|
65
|
-
|
64
|
+
# the email during rendering. Check to see if a breakwidth has been supplied
|
65
|
+
# in helpers.tsv so the designer can control the primary breakpoint.
|
66
|
+
breakpoint = @config[:'mobile-breakpoint'].to_i
|
67
|
+
if breakpoint <= 0
|
68
|
+
breakpoint = @config[:width].to_i - 1
|
69
|
+
breakpoint = 480 if breakpoint <= 0
|
70
|
+
end
|
71
|
+
|
72
|
+
@media_query = MediaQuery.new(self, breakpoint)
|
66
73
|
|
67
74
|
# Set the version index based on the position of this
|
68
75
|
# version in the list of those defined.
|
@@ -218,7 +225,11 @@ module Inkcite
|
|
218
225
|
image_host = if development?
|
219
226
|
(@email.optimize_images?? Minifier::IMAGE_CACHE : Email::IMAGES) + '/'
|
220
227
|
else
|
221
|
-
|
228
|
+
|
229
|
+
# Use the image host defined in config.yml or, out-of-the-box refer to images/
|
230
|
+
# in the build directory.
|
231
|
+
self[Email::IMAGE_HOST] || (Email::IMAGES + '/')
|
232
|
+
|
222
233
|
end
|
223
234
|
|
224
235
|
src_url << image_host unless image_host.blank?
|
@@ -227,7 +238,7 @@ module Inkcite
|
|
227
238
|
src_url << src
|
228
239
|
|
229
240
|
# Cache-bust the image if the caller is expecting it to be there.
|
230
|
-
src_url << "?#{Time.now.to_i}" if is_enabled?(Email::CACHE_BUST)
|
241
|
+
src_url << "?#{Time.now.to_i}" if !production? && is_enabled?(Email::CACHE_BUST)
|
231
242
|
|
232
243
|
# Transpose any embedded tags into actual values.
|
233
244
|
Renderer.render(src_url, self)
|
@@ -361,7 +372,15 @@ module Inkcite
|
|
361
372
|
html << '<meta name="viewport" content="width=device-width"/>'
|
362
373
|
html << "<meta name=\"generator\" content=\"Inkcite #{Inkcite::VERSION}\"/>"
|
363
374
|
|
364
|
-
|
375
|
+
# Enable responsive media queries on Windows phones courtesy of @jamesmacwhite
|
376
|
+
# https://blog.jmwhite.co.uk/2014/03/01/windows-phone-does-support-css3-media-queries-in-html-email/
|
377
|
+
html << '<!--[if !mso]><!-->'
|
378
|
+
html << '<meta http-equiv="X-UA-Compatible" content="IE=edge" />'
|
379
|
+
html << '<!--<![endif]-->'
|
380
|
+
|
381
|
+
# Some native Android clients display the title before the preheader so
|
382
|
+
# don't include it in non-development or email rendering per @moonstrips
|
383
|
+
html << "<title>#{self.title if (development? || browser?)}</title>"
|
365
384
|
|
366
385
|
# Add external script sources.
|
367
386
|
html += external_scripts
|
data/test/renderer/image_spec.rb
CHANGED
@@ -79,4 +79,8 @@ describe Inkcite::Renderer::Image do
|
|
79
79
|
@view.media_query.find_by_klass('i01').to_css.must_equal('img[class~="i01"] { content: url("images/inkcite-mobile.jpg") !important; }')
|
80
80
|
end
|
81
81
|
|
82
|
+
it 'supports fluid-hybrid desktop and style' do
|
83
|
+
Inkcite::Renderer.render('{img src=inkcite.jpg height=200 width=325 mobile=fluid}', @view).must_equal('<img border=0 src="images/inkcite.jpg" style="display:block;height:auto;max-width:325px;width:100%" width=325>')
|
84
|
+
end
|
85
|
+
|
82
86
|
end
|
data/test/renderer/table_spec.rb
CHANGED
@@ -31,4 +31,24 @@ describe Inkcite::Renderer::Table do
|
|
31
31
|
Inkcite::Renderer.render('{table margin=15 margin-left=8}', @view).must_equal('<table border=0 cellpadding=0 cellspacing=0 style="margin-bottom:15px;margin-left:8px;margin-right:15px;margin-top:15px"><tr>')
|
32
32
|
end
|
33
33
|
|
34
|
+
it 'supports fluid-hybrid desktop and style' do
|
35
|
+
Inkcite::Renderer.render('{table width=500 mobile=fluid}', @view).must_equal(%Q(<!--[if mso]><table border=0 cellpadding=0 cellspacing=0 width=500><tr><td><![endif]--><table border=0 cellpadding=0 cellspacing=0 style="max-width:500px" width=100%><tr>))
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'carries table alignment into the Outlook wrap table in fluid-hybrid' do
|
39
|
+
Inkcite::Renderer.render('{table align=center width=500 mobile=fluid}', @view).must_equal(%Q(<!--[if mso]><table align=center border=0 cellpadding=0 cellspacing=0 width=500><tr><td><![endif]--><table align=center border=0 cellpadding=0 cellspacing=0 style="max-width:500px" width=100%><tr>))
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'supports fluid-drop desktop and style' do
|
43
|
+
|
44
|
+
markup = ''
|
45
|
+
markup << %Q({table font-size=25 bgcolor=#090 border="5px solid #f0f" padding=15 width=600 mobile="fluid-drop"})
|
46
|
+
markup << %Q({td width=195 bgcolor=#009 color=#fff valign=top}left{/td})
|
47
|
+
markup << %Q({td width=195 align=center}centered two-line{/td})
|
48
|
+
markup << %Q({td width=195 bgcolor=#900 color=#fff align=right font-size=30}right<br>three<br>lines{/td})
|
49
|
+
markup << %Q({/table})
|
50
|
+
|
51
|
+
Inkcite::Renderer.render(markup, @view).must_equal(%Q(<!--[if mso]><table border=0 cellpadding=0 cellspacing=0 width=600><tr><td><![endif]--><table bgcolor=#009900 border=0 cellpadding=0 cellspacing=0 style="border:5px solid #f0f;max-width:600px" width=100%><tr><td style="font-size:0;text-align:center;vertical-align:middle"><!--[if mso]><table align=center border=0 cellpadding=0 cellspacing=0 width=100%><tr><![endif]--><!--[if mso]><td valign=top width=195><![endif]--><div class="fill" style="display:inline-block;vertical-align:top;width:195px"><table border=0 cellpadding=15 cellspacing=0 width=100%><tr><td align=left bgcolor=#000099 style="color:#ffffff;font-size:25px;padding:15px;text-align:left" valign=top>left</td></tr></table></div><!--[if mso]></td><![endif]--><!--[if mso]><td valign=middle width=195><![endif]--><div class="fill" style="display:inline-block;vertical-align:middle;width:195px"><table border=0 cellpadding=15 cellspacing=0 width=100%><tr><td align=center style="font-size:25px;padding:15px" valign=middle>centered two-line</td></tr></table></div><!--[if mso]></td><![endif]--><!--[if mso]><td valign=middle width=195><![endif]--><div class="fill" style="display:inline-block;vertical-align:middle;width:195px"><table border=0 cellpadding=15 cellspacing=0 width=100%><tr><td align=right bgcolor=#990000 style="color:#ffffff;font-size:30px;padding:15px" valign=middle>right<br>three<br>lines</td></tr></table></div><!--[if mso]></td><![endif]--><!--[if mso]></tr></table><![endif]--></td></tr></table><!--[if mso]></td></tr></table><![endif]-->))
|
52
|
+
end
|
53
|
+
|
34
54
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: inkcite
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeffrey D. Hoffman
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-11-
|
11
|
+
date: 2015-11-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -94,6 +94,20 @@ dependencies:
|
|
94
94
|
- - '>='
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: htmlbeautifier
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - '>='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
97
111
|
- !ruby/object:Gem::Dependency
|
98
112
|
name: image_optim
|
99
113
|
requirement: !ruby/object:Gem::Requirement
|
@@ -313,9 +327,6 @@ files:
|
|
313
327
|
- assets/init/image_optim.yml
|
314
328
|
- assets/init/source.html
|
315
329
|
- bin/inkcite
|
316
|
-
- bin/release-major
|
317
|
-
- bin/release-minor
|
318
|
-
- bin/release-patch
|
319
330
|
- inkcite.gemspec
|
320
331
|
- lib/inkcite.rb
|
321
332
|
- lib/inkcite/cli/base.rb
|
data/bin/release-major
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
gem bump --version major --push --release
|
data/bin/release-minor
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
gem bump --version minor --push --release
|
data/bin/release-patch
DELETED
File without changes
|