inkcite 1.7.0 → 1.8.0
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.
- 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
|