inkcite 1.2.0 → 1.6.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/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