inkcite 1.2.0 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/assets/blueprint.png +0 -0
- data/assets/example/helpers.tsv +37 -0
- data/assets/example/source.html +32 -0
- data/assets/{init → example}/source.txt +0 -0
- data/assets/init/config.yml +42 -4
- data/assets/init/helpers.tsv +12 -26
- data/assets/init/image_optim.yml +37 -0
- data/assets/init/source.html +0 -60
- data/inkcite.gemspec +2 -0
- data/lib/inkcite/cli/base.rb +5 -1
- data/lib/inkcite/cli/build.rb +1 -1
- data/lib/inkcite/cli/init.rb +31 -16
- data/lib/inkcite/cli/server.rb +5 -0
- data/lib/inkcite/cli/test.rb +16 -31
- data/lib/inkcite/email.rb +2 -2
- data/lib/inkcite/minifier.rb +46 -31
- data/lib/inkcite/renderer/button.rb +17 -8
- data/lib/inkcite/renderer/footnote.rb +39 -13
- data/lib/inkcite/renderer/litmus_analytics.rb +79 -0
- data/lib/inkcite/renderer/span.rb +5 -0
- data/lib/inkcite/renderer/table_base.rb +43 -52
- data/lib/inkcite/renderer/td.rb +0 -3
- data/lib/inkcite/renderer.rb +2 -2
- data/lib/inkcite/uploader.rb +4 -0
- data/lib/inkcite/util.rb +4 -0
- data/lib/inkcite/version.rb +1 -1
- data/lib/inkcite/view.rb +50 -8
- data/lib/inkcite.rb +10 -1
- data/test/renderer/span_spec.rb +97 -0
- data/test/renderer/td_spec.rb +10 -10
- metadata +38 -4
- data/lib/inkcite/renderer/litmus.rb +0 -33
data/lib/inkcite/minifier.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
|
+
require 'image_optim'
|
1
2
|
require 'yui/compressor'
|
2
3
|
|
3
4
|
module Inkcite
|
4
5
|
class Minifier
|
5
6
|
|
6
7
|
# Directory of optimized images
|
7
|
-
IMAGE_CACHE = "
|
8
|
+
IMAGE_CACHE = "images-optim"
|
8
9
|
|
9
10
|
def self.css code, ctx
|
10
11
|
minify?(ctx) ? css_compressor(ctx).compress(code) : code
|
@@ -60,15 +61,15 @@ module Inkcite
|
|
60
61
|
|
61
62
|
end
|
62
63
|
|
63
|
-
def self.images email
|
64
|
-
|
65
|
-
image_optim_path = '/Applications/ImageOptim.app/Contents/MacOS/ImageOptim'
|
66
|
-
image_optim = File.exists?(image_optim_path)
|
67
|
-
abort "Can't find ImageOptim (#{image_optim_path}) - download it from https://imageoptim.com" unless image_optim
|
64
|
+
def self.images email, force=false
|
68
65
|
|
69
66
|
images_path = email.image_dir
|
70
67
|
cache_path = email.project_file(IMAGE_CACHE)
|
71
68
|
|
69
|
+
# Check to see if there is an image optim configuration file.
|
70
|
+
config_path = email.project_file(IMAGE_OPTIM_CONFIG_YML)
|
71
|
+
config_last_modified = Util.last_modified(config_path)
|
72
|
+
|
72
73
|
# If the image cache exists, we need to check to see if any images have been
|
73
74
|
# removed since the last build.
|
74
75
|
if File.exists?(cache_path)
|
@@ -80,42 +81,54 @@ module Inkcite
|
|
80
81
|
|
81
82
|
# Convert the images to fully-qualified paths and then remove
|
82
83
|
# those files from the cache
|
83
|
-
removed_images = removed_images.collect { |img| File.join(cache_path, img
|
84
|
-
FileUtils.rm
|
84
|
+
removed_images = removed_images.collect { |img| File.join(cache_path, img) }
|
85
|
+
FileUtils.rm(removed_images)
|
85
86
|
|
86
87
|
end
|
87
88
|
|
88
89
|
end
|
89
90
|
|
90
91
|
# Check to see if there are new or updated images that need to be re-optimized.
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
92
|
+
# Compare existing images against both the most recently cached version and
|
93
|
+
# the timestamp of the config file.
|
94
|
+
updated_images = Dir.glob(File.join(images_path, '*.*')).select do |img|
|
95
|
+
cached_img = File.join(cache_path, File.basename(img))
|
96
|
+
cache_last_modified = Util.last_modified(cached_img)
|
97
|
+
force || config_last_modified > cache_last_modified || Util.last_modified(img) > cache_last_modified
|
96
98
|
end
|
97
99
|
|
100
|
+
# Return unless there is something to compress
|
98
101
|
return if updated_images.blank?
|
99
102
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
+
FileUtils.mkpath(cache_path)
|
104
|
+
|
105
|
+
# Check to see if there is an image_optim.yml file in this directory that
|
106
|
+
# overrides the default settings.
|
107
|
+
image_optim_opts = if config_last_modified > 0
|
108
|
+
{
|
109
|
+
:config_paths => [IMAGE_OPTIM_CONFIG_YML]
|
110
|
+
}
|
111
|
+
else
|
112
|
+
{
|
113
|
+
:allow_lossy => true,
|
114
|
+
:gifsicle => { :level => 3 },
|
115
|
+
:jpegoptim => { :max_quality => 50 },
|
116
|
+
:jpegrecompress => { :quality => 1 },
|
117
|
+
:pngout => false,
|
118
|
+
:svgo => false
|
119
|
+
}
|
120
|
+
end
|
103
121
|
|
104
|
-
|
105
|
-
# with the image processing.
|
106
|
-
FileUtils.rm_rf(temp_path)
|
107
|
-
FileUtils.mkpath(temp_path)
|
122
|
+
image_optim = ImageOptim.new(image_optim_opts)
|
108
123
|
|
109
124
|
# Copy all of the images that need updating into the temporary directory.
|
110
125
|
# Specifically joining the images_path to the image to avoid Email's
|
111
126
|
# image_path which may change it's directory if optimization is enabled.
|
112
|
-
updated_images.each
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
FileUtils.cp_r(File.join(temp_path, "."), cache_path)
|
118
|
-
FileUtils.rm_rf(temp_path)
|
127
|
+
updated_images.each do |img|
|
128
|
+
cached_img = File.join(cache_path, File.basename(img))
|
129
|
+
FileUtils.cp(img, cached_img)
|
130
|
+
image_optim.optimize_image!(cached_img)
|
131
|
+
end
|
119
132
|
|
120
133
|
end
|
121
134
|
|
@@ -125,9 +138,11 @@ module Inkcite
|
|
125
138
|
|
126
139
|
private
|
127
140
|
|
128
|
-
#
|
129
|
-
#
|
130
|
-
|
141
|
+
# Name of the Image Optim configuration yml file that can be
|
142
|
+
# put in the project directory to explicitly control the image
|
143
|
+
# optimization process.
|
144
|
+
IMAGE_OPTIM_CONFIG_YML = 'image_optim.yml'
|
145
|
+
|
131
146
|
|
132
147
|
NEW_LINE = "\n"
|
133
148
|
MAXIMUM_LINE_LENGTH = 800
|
@@ -145,7 +160,7 @@ module Inkcite
|
|
145
160
|
end
|
146
161
|
|
147
162
|
def self.css_compressor ctx
|
148
|
-
ctx.css_compressor ||= YUI::CssCompressor.new(:line_break => (ctx.email
|
163
|
+
ctx.css_compressor ||= YUI::CssCompressor.new(:line_break => (ctx.email? ? MAXIMUM_LINE_LENGTH : nil))
|
149
164
|
end
|
150
165
|
|
151
166
|
end
|
@@ -12,7 +12,7 @@ module Inkcite
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def bgcolor
|
15
|
-
hex(@opt[:bgcolor] || @ctx[BUTTON_BACKGROUND_COLOR] || @ctx[Base::LINK_COLOR])
|
15
|
+
hex(@opt[:bgcolor] || @ctx[BUTTON_BGCOLOR] || @ctx[BUTTON_BACKGROUND_COLOR] || @ctx[Base::LINK_COLOR])
|
16
16
|
end
|
17
17
|
|
18
18
|
def border
|
@@ -89,6 +89,7 @@ module Inkcite
|
|
89
89
|
BEVEL_COLOR = :'bevel-color'
|
90
90
|
|
91
91
|
BUTTON_BACKGROUND_COLOR = :'button-background-color'
|
92
|
+
BUTTON_BGCOLOR = :'button-bgcolor'
|
92
93
|
BUTTON_BEVEL = :'button-bevel'
|
93
94
|
BUTTON_BEVEL_COLOR = :'button-bevel-color'
|
94
95
|
BUTTON_BORDER = :'button-border'
|
@@ -129,26 +130,34 @@ module Inkcite
|
|
129
130
|
|
130
131
|
# Responsive button is just a highly styled table/td combination with optional
|
131
132
|
# curved corners and a lower bevel (border).
|
132
|
-
|
133
|
+
bgcolor = cfg.bgcolor
|
134
|
+
html << "{table bgcolor=#{bgcolor}"
|
133
135
|
html << " padding=#{cfg.padding}" if cfg.padding > 0
|
134
|
-
html <<
|
136
|
+
html << %Q( border="#{cfg.border}") if cfg.border
|
135
137
|
html << " border-radius=#{cfg.border_radius}" if cfg.border_radius > 0
|
138
|
+
html << %Q( border-bottom="#{cfg.border_bottom}") if cfg.bevel > 0
|
136
139
|
|
137
140
|
# Need to separate borders that are collapsed by default - otherwise, the bevel
|
138
141
|
# renders incorrectly.
|
139
|
-
html << " border-
|
142
|
+
html << " border-collapse=separate" if cfg.border || cfg.bevel > 0
|
140
143
|
|
141
144
|
html << " margin-top=#{cfg.margin_top}" if cfg.margin_top > 0
|
142
145
|
html << " width=#{cfg.width}" if cfg.width > 0
|
143
146
|
html << " float=#{cfg.float}" if cfg.float
|
144
|
-
html <<
|
147
|
+
html << %Q( mobile="fill"}\n)
|
145
148
|
html << "{td align=center"
|
146
149
|
html << " height=#{cfg.height} valign=middle" if cfg.height > 0
|
147
150
|
html << " font=\"#{cfg.font}\""
|
148
151
|
html << " line-height=#{cfg.line_height}" unless cfg.line_height.blank?
|
149
|
-
html <<
|
150
|
-
html <<
|
151
|
-
|
152
|
+
html << %Q( font-size="#{cfg.font_size}") if cfg.font_size > 0
|
153
|
+
html << %Q( font-weight="#{cfg.font_weight}") unless cfg.font_weight.blank?
|
154
|
+
|
155
|
+
# Text on the button gets a shadow automatically unless the shadow
|
156
|
+
# color matches the background color of the button.
|
157
|
+
shadow = cfg.text_shadow
|
158
|
+
html << %Q( shadow="#{shadow}" shadow-offset=-1) if shadow != bgcolor
|
159
|
+
|
160
|
+
html << '}'
|
152
161
|
|
153
162
|
# Second, internal link for Outlook users that makes the inside of the button
|
154
163
|
# clickable.
|
@@ -17,10 +17,16 @@ module Inkcite
|
|
17
17
|
# when the {footnotes} tag is rendered.
|
18
18
|
attr_reader :text
|
19
19
|
|
20
|
-
|
20
|
+
# True if this footnote is active. By default all footnotes are
|
21
|
+
# activate but those read from footnotes.tsv are inactive until
|
22
|
+
# referenced in the source.
|
23
|
+
attr_accessor :active
|
24
|
+
|
25
|
+
def initialize id, symbol, text, active=true
|
21
26
|
@id = id
|
22
27
|
@symbol = symbol.to_s
|
23
28
|
@text = text
|
29
|
+
@active = active
|
24
30
|
end
|
25
31
|
|
26
32
|
def number
|
@@ -33,6 +39,10 @@ module Inkcite
|
|
33
39
|
@symbol == @symbol.to_i.to_s
|
34
40
|
end
|
35
41
|
|
42
|
+
def symbol=symbol
|
43
|
+
@symbol = symbol.to_s
|
44
|
+
end
|
45
|
+
|
36
46
|
def symbol?
|
37
47
|
!numeric?
|
38
48
|
end
|
@@ -59,19 +69,13 @@ module Inkcite
|
|
59
69
|
# this isn't specified count the number of existing numeric footnotes
|
60
70
|
# and increment it for this new footnote's symbol.
|
61
71
|
symbol = opt[:symbol]
|
62
|
-
if symbol.blank?
|
63
|
-
|
64
|
-
# Grab the last numeric footnote that was specified and, assuming
|
65
|
-
# there is one, increment the count. Otherwise, start the count
|
66
|
-
# off at one.
|
67
|
-
last_instance = ctx.footnotes.select(&:numeric?).last
|
68
|
-
symbol = last_instance.nil? ? 1 : last_instance.symbol.to_i + 1
|
69
|
-
|
70
|
-
end
|
71
72
|
|
72
73
|
# Grab the text associated with this footnote.
|
73
74
|
text = opt[:text]
|
74
|
-
|
75
|
+
if text.blank?
|
76
|
+
ctx.error("Footnote requires text attribute", { :id => id, :symbol => symbol })
|
77
|
+
return
|
78
|
+
end
|
75
79
|
|
76
80
|
# Create a new Footnote instance
|
77
81
|
instance = Instance.new(id, symbol, text)
|
@@ -81,6 +85,24 @@ module Inkcite
|
|
81
85
|
|
82
86
|
end
|
83
87
|
|
88
|
+
# Check to see if the footnote's symbol is blank (either because one
|
89
|
+
# wasn't defined in the source.html or because the one read from the
|
90
|
+
# footnotes.tsv had no symbol associated with it) and if so, generate
|
91
|
+
# one based on the number of previously declared numeric footnotes.
|
92
|
+
if instance.symbol.blank?
|
93
|
+
|
94
|
+
# Grab the last numeric footnote that was specified and, assuming
|
95
|
+
# there is one, increment the count. Otherwise, start the count
|
96
|
+
# off at one.
|
97
|
+
last_instance = ctx.footnotes.select(&:numeric?).last
|
98
|
+
instance.symbol = last_instance.nil? ? 1 : last_instance.symbol.to_i + 1
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
# Make sure the instance is marked as having been used so it will
|
103
|
+
# appear in the {footnotes} rendering.
|
104
|
+
instance.active = true
|
105
|
+
|
84
106
|
# Allow footnotes to be defined without showing a symbol
|
85
107
|
hidden = opt[:hidden].to_i == 1
|
86
108
|
"#{instance.symbol}" unless hidden
|
@@ -94,6 +116,10 @@ module Inkcite
|
|
94
116
|
# Nothing to do if footnotes are blank.
|
95
117
|
return if ctx.footnotes.blank?
|
96
118
|
|
119
|
+
# Grab the active footnotes.
|
120
|
+
active_footnotes = ctx.footnotes.select(&:active)
|
121
|
+
return if active_footnotes.blank?
|
122
|
+
|
97
123
|
# Check to see if a template has been provided. Otherwise use a default one based
|
98
124
|
# on the format of the email.
|
99
125
|
tmpl = opt[:tmpl] || opt[:template]
|
@@ -114,11 +140,11 @@ module Inkcite
|
|
114
140
|
|
115
141
|
# First, collect all symbols in the natural order they are defined
|
116
142
|
# in the email.
|
117
|
-
footnotes =
|
143
|
+
footnotes = active_footnotes.select(&:symbol?)
|
118
144
|
|
119
145
|
# Now add to the list all numeric footnotes ordered naturally
|
120
146
|
# regardless of how they were ordered in the email.
|
121
|
-
footnotes +=
|
147
|
+
footnotes += active_footnotes.select(&:numeric?).sort { |f1, f2| f1.number <=> f2.number }
|
122
148
|
|
123
149
|
html = ''
|
124
150
|
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'litmus'
|
2
|
+
|
3
|
+
module Inkcite
|
4
|
+
module Renderer
|
5
|
+
class LitmusAnalytics < Base
|
6
|
+
|
7
|
+
def render tag, opt, ctx
|
8
|
+
|
9
|
+
# Litmus tracking is enabled only for production emails.
|
10
|
+
return nil unless ctx.production? && ctx.email?
|
11
|
+
|
12
|
+
# Deprecated code/id parameters. They shouldn't be passed anymore.
|
13
|
+
report_id = opt[:code] || opt[:id]
|
14
|
+
merge_tag = opt[MERGE_TAG] || ctx[MERGE_TAG]
|
15
|
+
|
16
|
+
# Initialize the Litmus API.
|
17
|
+
config = ctx.config[:litmus]
|
18
|
+
Litmus::Base.new(config[:subdomain], config[:username], config[:password], true)
|
19
|
+
|
20
|
+
# Will hold the Litmus Report object from which we'll retrieve the
|
21
|
+
# bug HTML to inject into the email.
|
22
|
+
report = nil
|
23
|
+
|
24
|
+
# If no code has been provided by the designer, check to see
|
25
|
+
# if one has been previously recorded for this version. If
|
26
|
+
# so, use it - otherwise, require one from litmus automatically.
|
27
|
+
if report_id.blank?
|
28
|
+
|
29
|
+
# Check to see if a campaign has been previously created for this
|
30
|
+
# version so the ID can be reused.
|
31
|
+
report_id = ctx.meta(:litmus_report_id)
|
32
|
+
if report_id.blank?
|
33
|
+
|
34
|
+
# Create a new report object using the title of the email specified
|
35
|
+
# in the helpers file.
|
36
|
+
report = Litmus::Report.create(ctx.title)
|
37
|
+
|
38
|
+
# Retrieve the unique ID assigned by Litmus and then stuff it
|
39
|
+
# into the meta data so we don't create a new one on future
|
40
|
+
# builds.
|
41
|
+
report_id = report['id']
|
42
|
+
ctx.set_meta :litmus_report_id, report_id
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
if report.nil?
|
49
|
+
|
50
|
+
report = Litmus::Report.show(report_id)
|
51
|
+
if report.nil?
|
52
|
+
ctx.error 'Invalid Litmus Analytics code or id', :code => report_id
|
53
|
+
return nil
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
# Grab the HTML from Litmus that needs to be injected into the source
|
59
|
+
# of the email.
|
60
|
+
bug_html = report[BUG_HTML]
|
61
|
+
|
62
|
+
# Replace the merge tag, if one was provided.
|
63
|
+
bug_html.gsub!('[UNIQUE]', merge_tag) unless merge_tag.nil?
|
64
|
+
|
65
|
+
# Inject HTML into the footer of the email where it won't be subject
|
66
|
+
# to inline'n or compression.
|
67
|
+
ctx.footer << bug_html
|
68
|
+
|
69
|
+
nil
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
BUG_HTML = 'bug_html'
|
75
|
+
MERGE_TAG = :'merge-tag'
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -21,34 +21,43 @@ module Inkcite
|
|
21
21
|
# css isn't supported.
|
22
22
|
element[:bgcolor] = hex(bgcolor) unless bgcolor.blank?
|
23
23
|
|
24
|
-
# Assisted background image handling for maximum compatibility.
|
25
24
|
bgimage = opt[:background]
|
26
25
|
bgposition = opt[BACKGROUND_POSITION]
|
27
26
|
bgrepeat = opt[BACKGROUND_REPEAT]
|
27
|
+
bgsize = opt[BACKGROUND_SIZE]
|
28
|
+
|
29
|
+
# Sets the background image attributes in the element's style
|
30
|
+
# attribute. These values take precedence on the desktop
|
31
|
+
# version of the email.
|
32
|
+
desktop_background = mix_background_shorthand(
|
33
|
+
bgcolor,
|
34
|
+
bgimage,
|
35
|
+
bgposition,
|
36
|
+
bgrepeat,
|
37
|
+
bgsize,
|
38
|
+
ctx
|
39
|
+
)
|
28
40
|
|
29
|
-
|
30
|
-
# element. Previously, it would also set the background-color attribute
|
31
|
-
# for unnecessary duplication.
|
32
|
-
background_css(element.style, bgcolor, bgimage, bgposition, bgrepeat, nil, false, ctx) unless bgimage.blank?
|
33
|
-
|
34
|
-
m_bgcolor = detect(opt[MOBILE_BACKGROUND_COLOR], opt[MOBILE_BGCOLOR])
|
35
|
-
m_bgimage = detect(opt[MOBILE_BACKGROUND_IMAGE], opt[MOBILE_BACKGROUND])
|
41
|
+
element.style[:background] = desktop_background unless bgimage.blank?
|
36
42
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
43
|
+
# Set the mobile background image attributes. These values take
|
44
|
+
# precedence on the mobile version of the email. If unset the
|
45
|
+
# mobile version inherits from the desktop version.
|
46
|
+
mobile_background = mix_background_shorthand(
|
47
|
+
detect(opt[MOBILE_BACKGROUND_COLOR], opt[MOBILE_BGCOLOR], bgcolor),
|
48
|
+
detect(opt[MOBILE_BACKGROUND_IMAGE], opt[MOBILE_BACKGROUND], bgimage),
|
41
49
|
detect(opt[MOBILE_BACKGROUND_POSITION], bgposition),
|
42
50
|
detect(opt[MOBILE_BACKGROUND_REPEAT], bgrepeat),
|
43
|
-
detect(opt[MOBILE_BACKGROUND_SIZE]),
|
44
|
-
(m_bgcolor && bgcolor) || (m_bgimage && bgimage),
|
51
|
+
detect(opt[MOBILE_BACKGROUND_SIZE], bgsize),
|
45
52
|
ctx
|
46
53
|
)
|
47
54
|
|
48
|
-
unless mobile_background.blank?
|
55
|
+
unless mobile_background.blank? || mobile_background == desktop_background
|
56
|
+
|
57
|
+
mobile_background << ' !important' unless desktop_background.blank?
|
49
58
|
|
50
59
|
# Add the responsive rule that applies to this element.
|
51
|
-
rule = Rule.new(element.tag, unique_klass(ctx), mobile_background)
|
60
|
+
rule = Rule.new(element.tag, unique_klass(ctx), { :background => mobile_background })
|
52
61
|
|
53
62
|
# Add the rule to the view and the element
|
54
63
|
ctx.media_query << rule
|
@@ -87,61 +96,43 @@ module Inkcite
|
|
87
96
|
|
88
97
|
private
|
89
98
|
|
90
|
-
def
|
99
|
+
def mix_background_shorthand bgcolor, img, position, repeat, size, ctx
|
91
100
|
|
92
|
-
|
101
|
+
values = []
|
93
102
|
|
94
|
-
|
103
|
+
values << hex(bgcolor) unless none?(bgcolor)
|
104
|
+
|
105
|
+
unless img.blank?
|
95
106
|
|
96
107
|
# If no image has been provided or if the image provided is equal
|
97
108
|
# to "none" then we'll set the values independently. Otherwise
|
98
109
|
# we'll use a composite background declaration.
|
99
110
|
if none?(img)
|
111
|
+
values << 'none'
|
100
112
|
|
101
|
-
|
102
|
-
bgcolor << ' !important' if important
|
103
|
-
into[BACKGROUND_COLOR] = bgcolor
|
104
|
-
end
|
113
|
+
else
|
105
114
|
|
106
|
-
|
107
|
-
# designer to the background that is otherwise present on the
|
108
|
-
# desktop version of the email.
|
109
|
-
if img == NONE
|
110
|
-
img = 'none'
|
111
|
-
img << ' !important' if important
|
112
|
-
into[BACKGROUND_IMAGE] = img
|
113
|
-
end
|
115
|
+
values << "url(#{ctx.image_url(img)})"
|
114
116
|
|
115
|
-
|
117
|
+
position = '0% 0%' if position.blank? && !size.blank?
|
118
|
+
unless position.blank?
|
119
|
+
values << position
|
120
|
+
unless size.blank?
|
121
|
+
values << '/'
|
122
|
+
values << (size == 'fill' ? '100% auto' : size)
|
123
|
+
end
|
124
|
+
end
|
116
125
|
|
117
126
|
# Default to no-repeat if a position has been supplied or replace
|
118
127
|
# 'none' as a convenience (cause none is easier to type than no-repeat).
|
119
128
|
repeat = 'no-repeat' if (repeat.blank? && !position.blank?) || repeat == NONE
|
129
|
+
values << repeat unless repeat.blank?
|
120
130
|
|
121
|
-
sty = []
|
122
|
-
sty << bgcolor unless bgcolor.blank?
|
123
|
-
|
124
|
-
ctx.assert_image_exists(img)
|
125
|
-
|
126
|
-
sty << "url(#{ctx.image_url(img)})"
|
127
|
-
sty << position unless position.blank?
|
128
|
-
sty << repeat unless repeat.blank?
|
129
|
-
sty << '!important' if important
|
130
|
-
|
131
|
-
into[:background] = sty.join(' ')
|
132
|
-
|
133
|
-
end
|
134
|
-
|
135
|
-
# Background size needs to be set independently. Perhaps it can be
|
136
|
-
# mixed into background: but I couldn't make it work.
|
137
|
-
unless size.blank?
|
138
|
-
into[BACKGROUND_SIZE] = size
|
139
|
-
into[BACKGROUND_SIZE] << ' !important' if important
|
140
131
|
end
|
141
132
|
|
142
133
|
end
|
143
134
|
|
144
|
-
|
135
|
+
values.blank?? nil : values.join(' ')
|
145
136
|
end
|
146
137
|
|
147
138
|
end
|
data/lib/inkcite/renderer/td.rb
CHANGED
data/lib/inkcite/renderer.rb
CHANGED
@@ -13,7 +13,7 @@ require_relative 'renderer/in_browser'
|
|
13
13
|
require_relative 'renderer/increment'
|
14
14
|
require_relative 'renderer/like'
|
15
15
|
require_relative 'renderer/link'
|
16
|
-
require_relative 'renderer/
|
16
|
+
require_relative 'renderer/litmus_analytics'
|
17
17
|
require_relative 'renderer/lorem'
|
18
18
|
require_relative 'renderer/mobile_image'
|
19
19
|
require_relative 'renderer/mobile_style'
|
@@ -161,7 +161,7 @@ module Inkcite
|
|
161
161
|
:'in-browser' => InBrowser.new,
|
162
162
|
:include => Partial.new,
|
163
163
|
:like => Like.new,
|
164
|
-
:litmus =>
|
164
|
+
:litmus => LitmusAnalytics.new,
|
165
165
|
:lorem => Lorem.new,
|
166
166
|
:'mobile-img' => MobileImage.new,
|
167
167
|
:'mobile-style' => MobileStyle.new,
|
data/lib/inkcite/uploader.rb
CHANGED
@@ -75,6 +75,10 @@ module Inkcite
|
|
75
75
|
# The preview version defines the configuration for the server to which
|
76
76
|
# the files will be sftp'd.
|
77
77
|
config = email.config[:sftp]
|
78
|
+
if config.nil? || config.blank?
|
79
|
+
puts "Unable to upload assets to CDN ('sftp:' section not found in config.yml)"
|
80
|
+
return
|
81
|
+
end
|
78
82
|
|
79
83
|
# TODO: Verify SFTP configuration
|
80
84
|
host = config[:host]
|
data/lib/inkcite/util.rb
CHANGED
data/lib/inkcite/version.rb
CHANGED