inkcite 1.8.0 → 1.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Rakefile +1 -1
- data/assets/init/config.yml +18 -8
- data/lib/inkcite/cli/base.rb +12 -8
- data/lib/inkcite/cli/preview.rb +3 -5
- data/lib/inkcite/cli/server.rb +13 -5
- data/lib/inkcite/cli/test.rb +36 -17
- data/lib/inkcite/email.rb +1 -1
- data/lib/inkcite/mailer.rb +128 -92
- data/lib/inkcite/renderer/base.rb +5 -1
- data/lib/inkcite/renderer/image_base.rb +18 -5
- data/lib/inkcite/renderer/link.rb +2 -15
- data/lib/inkcite/renderer/responsive.rb +20 -12
- data/lib/inkcite/renderer/table.rb +10 -3
- data/lib/inkcite/renderer/td.rb +4 -3
- data/lib/inkcite/uploader.rb +57 -34
- data/lib/inkcite/util.rb +20 -0
- data/lib/inkcite/version.rb +1 -1
- data/lib/inkcite/view.rb +30 -16
- data/test/renderer/image_spec.rb +6 -2
- data/test/renderer/link_spec.rb +6 -0
- data/test/renderer/mobile_image_spec.rb +1 -1
- data/test/renderer/table_spec.rb +12 -0
- data/test/renderer/td_spec.rb +4 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c9a6108e258e26ebffcba5b417592af73932bede
|
4
|
+
data.tar.gz: c6390f145e38ebc22ab57c458e22ce3f1739bb1e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 12fa75df86d37aaf12ea3161a16533d2d09c17aa7b04629c7aaffc01cc28d8521d611dbaebc2a3be15c0fa589a81774df7ec6d4b069f923c0e29825ef39c23a0
|
7
|
+
data.tar.gz: da6c930926846bd56d665317ddca6014c61b7ac2e43488923ee9678b02a140b9f54aa8fb9c3fbb7abc4ae614289bfcbbe1d094ffa5ebeb28fe5d8d1f18793ffd
|
data/Rakefile
CHANGED
data/assets/init/config.yml
CHANGED
@@ -22,6 +22,13 @@ optimize-images: true
|
|
22
22
|
# and needs to be provided.
|
23
23
|
missing-link-url: 'https://github.com/404'
|
24
24
|
|
25
|
+
# Litmus (litmus.com) and Email on Acid (emailonacid.com) (paid services)
|
26
|
+
# customers enter your static testing address here to enable instant
|
27
|
+
# compatibility testing.
|
28
|
+
# https://inkcite.readme.io/docs/compatibility-testing
|
29
|
+
#
|
30
|
+
test-address: ''
|
31
|
+
|
25
32
|
# Add Google Fonts (https://www.google.com/fonts) to your emails. Add
|
26
33
|
# the URLs to the family and sizes needed in your email. Then reference
|
27
34
|
# the font family in either source.html:
|
@@ -78,14 +85,6 @@ recipients:
|
|
78
85
|
- 'Creative Director <creative.director@domain.com>'
|
79
86
|
- 'Proofreader <proof.reader@domain.com>'
|
80
87
|
|
81
|
-
# Easy Litmus integration for compatibility testing.
|
82
|
-
# https://inkcite.readme.io/docs/compatibility-testing
|
83
|
-
#
|
84
|
-
litmus:
|
85
|
-
subdomain: ''
|
86
|
-
username: ''
|
87
|
-
password: ''
|
88
|
-
|
89
88
|
# Easy deployment of static assets to a CDN or publicly-accessible
|
90
89
|
# server - required when your email has images.
|
91
90
|
# https://dash.readme.io/project/inkcite/v1.0/docs/cdn-upload
|
@@ -112,6 +111,17 @@ tag-links: "from_email=myemail|{id}"
|
|
112
111
|
#
|
113
112
|
#tag-links-domain: 'clientdomain.com'
|
114
113
|
|
114
|
+
# Easy Litmus (litmus.com) analytics integration. Provide your account
|
115
|
+
# information here and Inkcite will automatically request a new
|
116
|
+
# analytics ID for each version of your email.
|
117
|
+
# https://inkcite.readme.io/docs/litmus-analytics
|
118
|
+
#
|
119
|
+
litmus:
|
120
|
+
subdomain: ''
|
121
|
+
username: ''
|
122
|
+
password: ''
|
123
|
+
merge-tag: ''
|
124
|
+
|
115
125
|
|
116
126
|
# Environment-specific overrides allow you to change any setting
|
117
127
|
# for each environment (e.g local development vs. client preview).
|
data/lib/inkcite/cli/base.rb
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
require 'thor'
|
2
2
|
require 'fileutils'
|
3
3
|
|
4
|
+
# For improved heredoc
|
5
|
+
# http://stackoverflow.com/a/9654275
|
6
|
+
require 'active_support/core_ext/string/strip'
|
7
|
+
|
4
8
|
module Inkcite
|
5
9
|
module Cli
|
6
10
|
class Base < Thor
|
@@ -37,8 +41,10 @@ module Inkcite
|
|
37
41
|
Cli::Init.invoke(name, options)
|
38
42
|
end
|
39
43
|
|
40
|
-
desc 'preview TO [options]', 'Send a preview of the email
|
41
|
-
|
44
|
+
desc 'preview TO [options]', 'Send a preview of the email to a recipient list: developer, internal or client'
|
45
|
+
option :version,
|
46
|
+
:aliases => '-v',
|
47
|
+
:desc => 'Preview a specific version of the email'
|
42
48
|
def preview to=:developer
|
43
49
|
require_relative 'preview'
|
44
50
|
Cli::Preview.invoke(email, to, options)
|
@@ -80,12 +86,10 @@ module Inkcite
|
|
80
86
|
|
81
87
|
end
|
82
88
|
|
83
|
-
desc 'test [options]', 'Tests (or re-tests) the email with Litmus'
|
84
|
-
option :
|
85
|
-
:aliases => '-
|
86
|
-
:desc => '
|
87
|
-
:type => :boolean
|
88
|
-
|
89
|
+
desc 'test [options]', 'Tests (or re-tests) the email with Litmus or Email on Acid'
|
90
|
+
option :version,
|
91
|
+
:aliases => '-v',
|
92
|
+
:desc => 'Test a specific version of the email'
|
89
93
|
def test
|
90
94
|
require_relative 'test'
|
91
95
|
Cli::Test.invoke(email, options)
|
data/lib/inkcite/cli/preview.rb
CHANGED
@@ -10,15 +10,13 @@ module Inkcite
|
|
10
10
|
# latest images and "view in browser" versions are available.
|
11
11
|
email.upload
|
12
12
|
|
13
|
-
puts "Sending preview to #{to} ..."
|
14
|
-
|
15
13
|
case to.to_sym
|
16
14
|
when :client
|
17
|
-
Inkcite::Mailer.client(email)
|
15
|
+
Inkcite::Mailer.client(email, opt)
|
18
16
|
when :internal
|
19
|
-
Inkcite::Mailer.internal(email)
|
17
|
+
Inkcite::Mailer.internal(email, opt)
|
20
18
|
when :developer
|
21
|
-
Inkcite::Mailer.developer(email)
|
19
|
+
Inkcite::Mailer.developer(email, opt)
|
22
20
|
else
|
23
21
|
raise "Invalid preview distribution target"
|
24
22
|
end
|
data/lib/inkcite/cli/server.rb
CHANGED
@@ -37,7 +37,7 @@ module Inkcite
|
|
37
37
|
guardfile = <<-EOF
|
38
38
|
guard :livereload do
|
39
39
|
watch(%r{^*.+\.(html|tsv|yml)})
|
40
|
-
watch(%r{
|
40
|
+
watch(%r{images/.+\.(gif|jpg|png)})
|
41
41
|
end
|
42
42
|
|
43
43
|
logger level: :error
|
@@ -73,7 +73,15 @@ module Inkcite
|
|
73
73
|
:app => app
|
74
74
|
})
|
75
75
|
rescue Errno::EADDRINUSE
|
76
|
-
abort
|
76
|
+
abort <<-USAGE.strip_heredoc
|
77
|
+
|
78
|
+
Oops! Inkcite can't start its preview server. Port #{port} is
|
79
|
+
unavailable. Either close the instance of Inkcite already running
|
80
|
+
on that port or start this Inkcite instance on a new port with:
|
81
|
+
|
82
|
+
inkcit server --port=#{port+1}
|
83
|
+
|
84
|
+
USAGE
|
77
85
|
end
|
78
86
|
|
79
87
|
end
|
@@ -124,9 +132,9 @@ module Inkcite
|
|
124
132
|
|
125
133
|
html = view.render!
|
126
134
|
|
127
|
-
# If
|
128
|
-
#
|
129
|
-
html = HtmlBeautifier.beautify(html)
|
135
|
+
# If minification is disabled, then beautify the output to make it easier
|
136
|
+
# for the designer to inspect the code being produced by Inkcite.
|
137
|
+
html = HtmlBeautifier.beautify(html) unless view.is_enabled?(:minify)
|
130
138
|
|
131
139
|
unless view.errors.blank?
|
132
140
|
error_count = view.errors.count
|
data/lib/inkcite/cli/test.rb
CHANGED
@@ -7,40 +7,59 @@ module Inkcite
|
|
7
7
|
|
8
8
|
def self.invoke email, opt
|
9
9
|
|
10
|
-
#
|
11
|
-
|
12
|
-
if
|
13
|
-
|
14
|
-
|
10
|
+
# Check to see if the test-address has been specified.
|
11
|
+
send_to = email.config[TEST_ADDRESS]
|
12
|
+
if send_to.blank?
|
13
|
+
|
14
|
+
# Deprecated check for the test address buried in the Litmus section.
|
15
|
+
litmus_config = email.config[:litmus]
|
16
|
+
send_to = litmus_config[TEST_ADDRESS] unless litmus_config.blank?
|
17
|
+
|
15
18
|
end
|
16
19
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
20
|
+
if send_to.blank?
|
21
|
+
abort <<-USAGE.strip_heredoc
|
22
|
+
|
23
|
+
Oops! Inkcite can't start a compatibility test because of a missing
|
24
|
+
configuration value. In config.yml, please add or uncomment this line
|
25
|
+
and insert your Litmus or Email on Acid static testing email address:
|
26
|
+
|
27
|
+
test-address: '(your.static.address@testingservice.com)'
|
28
|
+
|
29
|
+
USAGE
|
24
30
|
end
|
25
31
|
|
26
32
|
# Push the browser preview up to the server to ensure that the
|
27
33
|
# latest images are available.
|
28
34
|
email.upload
|
29
35
|
|
30
|
-
#
|
31
|
-
|
36
|
+
# Typically the user will only provide a single test address but here
|
37
|
+
# we convert to an array in case the user is sending to multiple
|
38
|
+
# addresses for their own compatibility testing.
|
39
|
+
send_to = Array(send_to)
|
32
40
|
|
33
|
-
|
41
|
+
# Check to see if the user wants to test a specific version of the
|
42
|
+
# email - otherwise test all of them.
|
43
|
+
versions = Array(opt[:version] || email.versions)
|
44
|
+
|
45
|
+
# Send each version to the testing service separately
|
46
|
+
versions.each do |version|
|
34
47
|
|
35
|
-
|
48
|
+
view = email.view(:preview, :email, version)
|
49
|
+
puts "Sending '#{view.subject}' to #{send_to.join(', ')} ..."
|
36
50
|
|
37
|
-
Inkcite::Mailer.
|
51
|
+
Inkcite::Mailer.send_version(email, version, { :to => send_to })
|
38
52
|
|
39
53
|
end
|
40
54
|
|
41
55
|
true
|
42
56
|
end
|
43
57
|
|
58
|
+
private
|
59
|
+
|
60
|
+
# Name of the config property that
|
61
|
+
TEST_ADDRESS = :'test-address'
|
62
|
+
|
44
63
|
end
|
45
64
|
end
|
46
65
|
end
|
data/lib/inkcite/email.rb
CHANGED
data/lib/inkcite/mailer.rb
CHANGED
@@ -4,7 +4,7 @@ require 'mailgun'
|
|
4
4
|
module Inkcite
|
5
5
|
class Mailer
|
6
6
|
|
7
|
-
def self.client email
|
7
|
+
def self.client email, opts
|
8
8
|
|
9
9
|
# Determine which preview this is
|
10
10
|
count = increment(email, :preview)
|
@@ -27,151 +27,187 @@ module Inkcite
|
|
27
27
|
# Always cc internal recipients so everyone stays informed of feedback.
|
28
28
|
cc = recipients[:internal]
|
29
29
|
|
30
|
-
self.send(email, {
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
30
|
+
self.send(email, opts.merge({
|
31
|
+
:to => to,
|
32
|
+
:cc => cc,
|
33
|
+
:bcc => true,
|
34
|
+
:tag => "Preview ##{count}"
|
35
|
+
}))
|
36
36
|
|
37
37
|
end
|
38
38
|
|
39
|
-
def self.developer email
|
39
|
+
def self.developer email, opts
|
40
40
|
|
41
41
|
count = increment(email, :developer)
|
42
42
|
|
43
|
-
self.send(email, {
|
44
|
-
|
45
|
-
|
43
|
+
self.send(email, opts.merge({
|
44
|
+
:tag => "Developer Test ##{count}"
|
45
|
+
}))
|
46
46
|
|
47
47
|
end
|
48
48
|
|
49
|
-
def self.
|
50
|
-
self.send_version(email, version, { :to => litmus_email })
|
51
|
-
end
|
52
|
-
|
53
|
-
def self.internal email
|
49
|
+
def self.internal email, opts
|
54
50
|
|
55
51
|
recipients = email.config[:recipients]
|
56
52
|
|
57
53
|
# Determine which preview this is
|
58
54
|
count = increment(email, :internal)
|
59
55
|
|
60
|
-
self.send(email, {
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
56
|
+
self.send(email, opts.merge({
|
57
|
+
:to => recipients[:internal],
|
58
|
+
:bcc => true,
|
59
|
+
:tag => "Internal Proof ##{count}"
|
60
|
+
}))
|
65
61
|
|
66
62
|
end
|
67
63
|
|
68
|
-
|
64
|
+
# Sends each version of the provided email with the indicated options.
|
65
|
+
def self.send email, opts
|
69
66
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
FIRST_PREVIEW = :'first-preview'
|
67
|
+
# Check to see if a specific version is requested or if unspecified
|
68
|
+
# all versions of the email should be sent.
|
69
|
+
versions = Array(opts[:version] || email.versions)
|
74
70
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
end
|
71
|
+
# Will hold the instance of the Mailer::Base that will handle the
|
72
|
+
# actual sending of the email.
|
73
|
+
mailer_base = nil
|
79
74
|
|
80
|
-
|
81
|
-
|
75
|
+
# Check to see if
|
76
|
+
if config = email.config[:mailgun]
|
77
|
+
mailer_base = MailgunMailer.new
|
78
|
+
elsif config = email.config[:smtp]
|
79
|
+
mailer_base = SmtpMailer.new
|
80
|
+
else
|
81
|
+
abort <<-USAGE.strip_heredoc
|
82
|
+
|
83
|
+
Oops! Inkcite can't send this email because of a configuration problem.
|
84
|
+
Please update the mailgun or smtp sections of your config.yml file.
|
85
|
+
|
86
|
+
smtp:
|
87
|
+
host: 'smtp.gmail.com'
|
88
|
+
port: 587
|
89
|
+
domain: 'yourdomain.com'
|
90
|
+
username: ''
|
91
|
+
password: ''
|
92
|
+
from: 'Your Name <email@domain.com>'
|
82
93
|
|
83
|
-
|
84
|
-
|
94
|
+
Or send via Mailgun:
|
95
|
+
|
96
|
+
mailgun:
|
97
|
+
api-key: 'key-your-api-key'
|
98
|
+
domain: 'mg.sending-domain.com'
|
99
|
+
from: 'Your Name <email@domain.com>'
|
100
|
+
|
101
|
+
USAGE
|
85
102
|
end
|
86
103
|
|
87
|
-
|
104
|
+
versions.each do |version|
|
88
105
|
|
89
|
-
|
106
|
+
# The version of the email we will be sending.
|
107
|
+
view = email.view(:preview, :email, version)
|
90
108
|
|
91
|
-
|
92
|
-
|
109
|
+
# Subject line tag such as "Preview #3"
|
110
|
+
tag = opts[:tag]
|
93
111
|
|
94
|
-
|
95
|
-
|
112
|
+
subject = view.subject
|
113
|
+
subject = "#{subject} (#{tag})" unless tag.blank?
|
96
114
|
|
97
|
-
|
98
|
-
|
115
|
+
puts "Sending '#{subject}' ..."
|
116
|
+
|
117
|
+
mailer_base.send! config, view, subject, opts
|
99
118
|
|
100
|
-
if config = email.config[:mailgun]
|
101
|
-
send_version_via_mailgun config, view, subject, opt
|
102
|
-
elsif config = email.config[:smtp]
|
103
|
-
send_version_via_smtp config, view, subject, opt
|
104
|
-
else
|
105
|
-
puts 'Unable to send previews. Please configure mailgun or smtp sections in config.yml'
|
106
119
|
end
|
107
120
|
|
108
121
|
end
|
109
122
|
|
110
123
|
private
|
111
124
|
|
112
|
-
|
125
|
+
# Name of the distribution list used on the first preview. For one
|
126
|
+
# client, they wanted the first preview sent to additional people
|
127
|
+
# but subsequent previews went to a shorter list.
|
128
|
+
FIRST_PREVIEW = :'first-preview'
|
113
129
|
|
114
|
-
|
115
|
-
|
130
|
+
def self.increment email, sym
|
131
|
+
count = email.meta(sym).to_i + 1
|
132
|
+
email.set_meta sym, count
|
133
|
+
end
|
116
134
|
|
117
|
-
|
118
|
-
|
135
|
+
# Abstract base class for the workhorses of the Mailer class.
|
136
|
+
# Instantiated based on the config.yml settings.
|
137
|
+
class Base
|
138
|
+
def send! config, view, subject, opt
|
139
|
+
raise NotImplementedError
|
140
|
+
end
|
141
|
+
end
|
119
142
|
|
120
|
-
|
121
|
-
|
122
|
-
:from => from,
|
123
|
-
:to => opt[:to] || from,
|
124
|
-
:subject => subject,
|
125
|
-
:html => view.render!
|
126
|
-
}
|
143
|
+
class MailgunMailer < Base
|
144
|
+
def send! config, view, subject, opt
|
127
145
|
|
128
|
-
|
129
|
-
|
146
|
+
# The address of the developer
|
147
|
+
from = config[:from]
|
130
148
|
|
131
|
-
|
132
|
-
|
149
|
+
# First, instantiate the Mailgun Client with your API key
|
150
|
+
mg_client = Mailgun::Client.new config[:'api-key']
|
133
151
|
|
134
|
-
|
152
|
+
# Define your message parameters
|
153
|
+
message_params = {
|
154
|
+
:from => from,
|
155
|
+
:to => opt[:to] || from,
|
156
|
+
:subject => subject,
|
157
|
+
:html => view.render!
|
158
|
+
}
|
159
|
+
|
160
|
+
message_params[:cc] = opt[:cc] unless opt[:cc].blank?
|
161
|
+
message_params[:bcc] = from if opt[:bcc] == true
|
162
|
+
|
163
|
+
# Send your message through the client
|
164
|
+
mg_client.send_message config[:domain], message_params
|
135
165
|
|
136
|
-
def self.send_version_via_smtp config, view, _subject, opt
|
137
|
-
|
138
|
-
Mail.defaults do
|
139
|
-
delivery_method :smtp, {
|
140
|
-
:address => config[:host],
|
141
|
-
:port => config[:port],
|
142
|
-
:user_name => config[:username],
|
143
|
-
:password => config[:password],
|
144
|
-
:authentication => :plain,
|
145
|
-
:enable_starttls_auto => true
|
146
|
-
}
|
147
166
|
end
|
167
|
+
end
|
148
168
|
|
149
|
-
|
150
|
-
|
169
|
+
class SmtpMailer < Base
|
170
|
+
def send! config, view, _subject, opt
|
171
|
+
|
172
|
+
Mail.defaults do
|
173
|
+
delivery_method :smtp, {
|
174
|
+
:address => config[:host],
|
175
|
+
:port => config[:port],
|
176
|
+
:user_name => config[:username],
|
177
|
+
:password => config[:password],
|
178
|
+
:authentication => :plain,
|
179
|
+
:enable_starttls_auto => true
|
180
|
+
}
|
181
|
+
end
|
151
182
|
|
152
|
-
|
153
|
-
|
183
|
+
# The address of the developer
|
184
|
+
_from = config[:from]
|
154
185
|
|
155
|
-
|
186
|
+
# True if the developer should be bcc'd.
|
187
|
+
_bcc = !!opt[:bcc]
|
156
188
|
|
157
|
-
|
158
|
-
cc opt[:cc]
|
159
|
-
from _from
|
160
|
-
subject _subject
|
189
|
+
mail = Mail.new do
|
161
190
|
|
162
|
-
|
191
|
+
to opt[:to] || _from
|
192
|
+
cc opt[:cc]
|
193
|
+
from _from
|
194
|
+
subject _subject
|
163
195
|
|
164
|
-
|
165
|
-
content_type 'text/html; charset=UTF-8'
|
166
|
-
body view.render!
|
167
|
-
end
|
196
|
+
bcc(_from) if _bcc
|
168
197
|
|
169
|
-
|
198
|
+
html_part do
|
199
|
+
content_type 'text/html; charset=UTF-8'
|
200
|
+
body view.render!
|
201
|
+
end
|
202
|
+
|
203
|
+
end
|
170
204
|
|
171
|
-
|
205
|
+
mail.deliver!
|
172
206
|
|
207
|
+
end
|
173
208
|
end
|
174
209
|
|
175
210
|
end
|
176
211
|
end
|
177
212
|
|
213
|
+
|
@@ -74,6 +74,10 @@ module Inkcite
|
|
74
74
|
val
|
75
75
|
end
|
76
76
|
|
77
|
+
def detect_bgcolor opt
|
78
|
+
detect(opt[:bgcolor], opt[BACKGROUND_COLOR])
|
79
|
+
end
|
80
|
+
|
77
81
|
# Convenience pass-thru to Renderer's static helper method.
|
78
82
|
def hex color
|
79
83
|
Renderer.hex(color)
|
@@ -92,7 +96,7 @@ module Inkcite
|
|
92
96
|
def mix_background element, opt
|
93
97
|
|
94
98
|
# Background color of the image, if populated.
|
95
|
-
bgcolor =
|
99
|
+
bgcolor = detect_bgcolor(opt)
|
96
100
|
element.style[BACKGROUND_COLOR] = hex(bgcolor) unless none?(bgcolor)
|
97
101
|
|
98
102
|
end
|
@@ -5,10 +5,13 @@ module Inkcite
|
|
5
5
|
protected
|
6
6
|
|
7
7
|
# Display mode constants
|
8
|
-
BLOCK
|
8
|
+
BLOCK = 'block'
|
9
9
|
DEFAULT = 'default'
|
10
|
-
INLINE
|
10
|
+
INLINE = 'inline'
|
11
11
|
|
12
|
+
# For the given image source URL provided, returns either the fully-qualfied
|
13
|
+
# path to the image (via View's image_url method) or returns a placeholder
|
14
|
+
# if the image is missing.
|
12
15
|
def image_url _src, opt, ctx
|
13
16
|
|
14
17
|
src = _src
|
@@ -18,7 +21,7 @@ module Inkcite
|
|
18
21
|
|
19
22
|
# Fully-qualify the image path for this version of the email unless it
|
20
23
|
# is already includes a full address.
|
21
|
-
unless
|
24
|
+
unless Util::is_fully_qualified?(src)
|
22
25
|
|
23
26
|
# Verify that the image exists.
|
24
27
|
if ctx.assert_image_exists(src) || ctx.is_disabled?(Inkcite::Email::IMAGE_PLACEHOLDERS)
|
@@ -36,15 +39,25 @@ module Inkcite
|
|
36
39
|
|
37
40
|
elsif DIMENSIONS.all? { |dim| opt[dim].to_i > MINIMUM_DIMENSION_FOR_PLACEHOLDER }
|
38
41
|
|
42
|
+
width = opt[:width]
|
43
|
+
height = opt[:height]
|
44
|
+
|
39
45
|
# As a convenience, replace missing images with placehold.it as long as they
|
40
46
|
# meet the minimum dimensions. No need to spam the design with tiny, tiny
|
41
47
|
# placeholders.
|
42
|
-
src = "http://placehold.it/#{
|
48
|
+
src = "http://placehold.it/#{width}x#{height}.jpg"
|
49
|
+
|
50
|
+
# Check to see if the image has a background color. If so, we'll use that
|
51
|
+
# to set the background color of the placeholder.
|
52
|
+
bgcolor = detect_bgcolor(opt)
|
53
|
+
src << "/#{bgcolor}".gsub('#', '') unless none?(bgcolor)
|
43
54
|
|
44
55
|
# Check to see if the designer specified FPO text for this placeholder -
|
45
56
|
# otherwise default to the dimensions of the image.
|
46
57
|
fpo = opt[:fpo]
|
47
|
-
|
58
|
+
fpo = _src.dup if fpo.blank?
|
59
|
+
fpo << "\n(#{width}×#{height})"
|
60
|
+
src << "?text=#{URI::encode(fpo)}"
|
48
61
|
|
49
62
|
end
|
50
63
|
|
@@ -44,7 +44,7 @@ module Inkcite
|
|
44
44
|
|
45
45
|
# If a URL wasn't provided in the HTML, then check to see if there is
|
46
46
|
# a link declared in the project's links_tsv file.
|
47
|
-
href = ctx.links_tsv[id] if href.blank?
|
47
|
+
href = ctx.links_tsv[id].dup if href.blank? && ctx.links_tsv[id]
|
48
48
|
|
49
49
|
# True if the href is missing. If so, we may try to look it up by it's ID
|
50
50
|
# or we'll insert a default TBD link.
|
@@ -200,20 +200,7 @@ module Inkcite
|
|
200
200
|
# href matches the desired domain name.
|
201
201
|
tag_domain = ctx[TAG_LINKS_DOMAIN]
|
202
202
|
if tag_domain.blank? || href =~ /^https?:\/\/[^\/]*#{tag_domain}/
|
203
|
-
|
204
|
-
# Prepend it with a question mark or an ampersand depending on the current
|
205
|
-
# state of the lin.
|
206
|
-
stag = href.include?('?') ? '&' : '?'
|
207
|
-
stag << replace_tag(tag, id, ctx)
|
208
|
-
|
209
|
-
# Inject before the pound sign if present - otherwise, just tack it on
|
210
|
-
# to the end of the href.
|
211
|
-
if hash = href.index(POUND_SIGN)
|
212
|
-
href[hash..0] = stag
|
213
|
-
else
|
214
|
-
href << stag
|
215
|
-
end
|
216
|
-
|
203
|
+
Util::add_query_param(href, replace_tag(tag, id, ctx))
|
217
204
|
end
|
218
205
|
|
219
206
|
end
|
@@ -2,17 +2,18 @@ module Inkcite
|
|
2
2
|
module Renderer
|
3
3
|
class Responsive < Base
|
4
4
|
|
5
|
-
BUTTON
|
6
|
-
DROP
|
7
|
-
FILL
|
8
|
-
FLUID
|
9
|
-
FLUID_DROP
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
5
|
+
BUTTON = 'button'
|
6
|
+
DROP = 'drop'
|
7
|
+
FILL = 'fill'
|
8
|
+
FLUID = 'fluid'
|
9
|
+
FLUID_DROP = 'fluid-drop'
|
10
|
+
FLUID_STACK = 'fluid-stack'
|
11
|
+
HIDE = 'hide'
|
12
|
+
IMAGE = 'img'
|
13
|
+
SHOW = 'show'
|
14
|
+
SWITCH = 'switch'
|
15
|
+
SWITCH_UP = 'switch-up'
|
16
|
+
TOGGLE = 'toggle'
|
16
17
|
|
17
18
|
# For elements that take on different background properties
|
18
19
|
# when they go responsive
|
@@ -185,7 +186,14 @@ module Inkcite
|
|
185
186
|
# Returns true if the mobile klass provided matches any of the
|
186
187
|
# Fluid-Hybrid classes.
|
187
188
|
def is_fluid? mobile
|
188
|
-
mobile == FLUID || mobile
|
189
|
+
mobile == FLUID || is_fluid_drop?(mobile)
|
190
|
+
end
|
191
|
+
|
192
|
+
# Returns true if the mobile klass provided matches any of the
|
193
|
+
# Fluid-Hybrid classes that result in a table's columns stacking
|
194
|
+
# vertically.
|
195
|
+
def is_fluid_drop? mobile
|
196
|
+
mobile == FLUID_DROP || mobile == FLUID_STACK
|
189
197
|
end
|
190
198
|
|
191
199
|
def mix_font element, opt, ctx, parent=nil
|
@@ -21,7 +21,7 @@ module Inkcite
|
|
21
21
|
# If the table was declared as Fluid-Hybrid Drop, then there are some additional
|
22
22
|
# elements that need to be closed before the regular row-table closure that
|
23
23
|
# the Table helper normally produces.
|
24
|
-
if open_mobile
|
24
|
+
if is_fluid_drop?(open_mobile)
|
25
25
|
|
26
26
|
# Close the interior conditional table for Outlook that contains the floating blocks.
|
27
27
|
html << if_mso('</tr></table>')
|
@@ -50,7 +50,7 @@ module Inkcite
|
|
50
50
|
|
51
51
|
# Check if fluid-drop has been specified. This will force a lot more HTML to
|
52
52
|
# be produced for this table and its child TDs.
|
53
|
-
is_fluid_drop = mobile
|
53
|
+
is_fluid_drop = is_fluid_drop?(mobile)
|
54
54
|
|
55
55
|
# Inherit base cell attributes - border, background color and image, etc.
|
56
56
|
mix_all table, opt, ctx
|
@@ -129,7 +129,14 @@ module Inkcite
|
|
129
129
|
#
|
130
130
|
# The zero-size font addresses a rendering problem in Outlook:
|
131
131
|
# https://css-tricks.com/fighting-the-space-between-inline-block-elements/
|
132
|
-
|
132
|
+
fluid_td = Element.new('td', :style => { TEXT_ALIGN => :center, VERTICAL_ALIGN => opt[:valign], FONT_SIZE => 0 })
|
133
|
+
|
134
|
+
# If fluid-stack is specified, then reverse the order of the columns to make
|
135
|
+
# the right-most orient to the top.
|
136
|
+
# http://webdesign.tutsplus.com/tutorials/creating-a-future-proof-responsive-email-without-media-queries--cms-23919
|
137
|
+
fluid_td[:dir] = :rtl if mobile == FLUID_STACK
|
138
|
+
|
139
|
+
html << fluid_td.to_s
|
133
140
|
|
134
141
|
# Lastly, Outlook needs yet another conditional table that will be used
|
135
142
|
# to contain the floating blocks. The TD elements are generated by
|
data/lib/inkcite/renderer/td.rb
CHANGED
@@ -12,6 +12,7 @@ module Inkcite
|
|
12
12
|
# Grab the attributes of the parent table so that the TD can inherit
|
13
13
|
# specific values like padding, valign, responsiveness, etc.
|
14
14
|
table_opt = ctx.parent_opts(:table)
|
15
|
+
table_mobile = table_opt[:mobile]
|
15
16
|
|
16
17
|
# Check to see if the parent table was set to fluid-drop which causes
|
17
18
|
# the table cells to be wrapped in <div> elements and floated to
|
@@ -19,7 +20,7 @@ module Inkcite
|
|
19
20
|
#
|
20
21
|
# Fluid-Hybrid TD courtesy of @moonstrips and our friends at Campaign Monitor
|
21
22
|
# https://www.campaignmonitor.com/blog/email-marketing/2014/07/creating-a-centred-responsive-design-without-media-queries/
|
22
|
-
is_fluid_drop =
|
23
|
+
is_fluid_drop = is_fluid_drop?(table_mobile)
|
23
24
|
|
24
25
|
if tag == CLOSE_TD
|
25
26
|
|
@@ -77,8 +78,8 @@ module Inkcite
|
|
77
78
|
# Width must be specified for Fluid-Drop cells. Vertical-alignment is
|
78
79
|
# also important but should have been preset by the Table Helper if it
|
79
80
|
# was omitted by the designer.
|
80
|
-
ctx.error("Width is a required attribute when #{
|
81
|
-
ctx.error("Vertical alignment should be specified when #{
|
81
|
+
ctx.error("Width is a required attribute when #{table_mobile} is specified", opt) unless width > 0
|
82
|
+
ctx.error("Vertical alignment should be specified when #{table_mobile} is specified", opt) if valign.blank?
|
82
83
|
|
83
84
|
# Conditional Outlook cell to prevent the 100%-wide table within from
|
84
85
|
# stretching beyond the max-width. Also, valign necessary to get float
|
data/lib/inkcite/uploader.rb
CHANGED
@@ -101,55 +101,78 @@ module Inkcite
|
|
101
101
|
|
102
102
|
puts "Uploading to #{host} ..."
|
103
103
|
|
104
|
-
|
105
|
-
Net::SFTP.start(host, username, :password => password) do |sftp|
|
104
|
+
begin
|
106
105
|
|
107
|
-
#
|
108
|
-
|
106
|
+
# Get a local handle on the litmus configuration.
|
107
|
+
Net::SFTP.start(host, username, :password => password) do |sftp|
|
109
108
|
|
110
|
-
|
109
|
+
# Upload each version of the email.
|
110
|
+
email.versions.each do |version|
|
111
111
|
|
112
|
-
|
113
|
-
# that embedded tags will be converted into data.
|
114
|
-
remote_root = Inkcite::Renderer.render(path, view)
|
112
|
+
view = email.view(:preview, :email, version)
|
115
113
|
|
116
|
-
|
117
|
-
|
118
|
-
|
114
|
+
# Need to pass the upload path through the renderer to ensure
|
115
|
+
# that embedded tags will be converted into data.
|
116
|
+
remote_root = Inkcite::Renderer.render(path, view)
|
119
117
|
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
# remote roots.
|
124
|
-
copy! sftp, local_images, remote_root, force && last_remote_root != remote_root
|
125
|
-
last_remote_root = remote_root
|
118
|
+
# Recursively ensure that the full directory structure necessary for
|
119
|
+
# the content and images is present.
|
120
|
+
mkdir! sftp, remote_root
|
126
121
|
|
127
|
-
|
128
|
-
|
122
|
+
# Upload the images to the remote directory. We use the last_remote_root
|
123
|
+
# to ensure that we're not repeatedly uploading the same images over and
|
124
|
+
# over when force is enabled -- but will re-upload images to distinct
|
125
|
+
# remote roots.
|
126
|
+
copy! sftp, local_images, remote_root, force && last_remote_root != remote_root
|
127
|
+
last_remote_root = remote_root
|
129
128
|
|
130
|
-
|
129
|
+
# Check to see if we're creating an in-browser version of the email.
|
130
|
+
next unless email.formats.include?(:browser)
|
131
131
|
|
132
|
-
|
133
|
-
# do not have a hosted version and so it is not necessary to upload the
|
134
|
-
# HTML version of the email - but this is a bad practice.
|
135
|
-
file_name = view.file_name
|
136
|
-
next if file_name.blank?
|
132
|
+
browser_view = email.view(:preview, :browser, version)
|
137
133
|
|
138
|
-
|
139
|
-
|
134
|
+
# Check to see if there is a HTML version of this preview. Some emails
|
135
|
+
# do not have a hosted version and so it is not necessary to upload the
|
136
|
+
# HTML version of the email - but this is a bad practice.
|
137
|
+
file_name = browser_view.file_name
|
138
|
+
next if file_name.blank?
|
140
139
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
140
|
+
remote_file_name = File.join(remote_root, file_name)
|
141
|
+
puts "Uploading #{remote_file_name}"
|
142
|
+
|
143
|
+
# We need to use StringIO to write the email to a buffer in order to upload
|
144
|
+
# the email's content in binary so that its encoding is honored. SFTP defaults
|
145
|
+
# to ASCII-8bit in non-binary mode, so it was blowing up on UTF-8 special
|
146
|
+
# characters (e.g. "Mäkinen").
|
147
|
+
# http://stackoverflow.com/questions/9439289/netsftp-transfer-mode-binary-vs-text
|
148
|
+
io = StringIO.new(browser_view.render!)
|
149
|
+
sftp.upload!(io, remote_file_name)
|
150
|
+
|
151
|
+
end
|
148
152
|
|
149
153
|
end
|
150
154
|
|
155
|
+
rescue SocketError => e
|
156
|
+
abort <<-USAGE.strip_heredoc
|
157
|
+
|
158
|
+
Oops! There was an unexpected error trying to upload to your
|
159
|
+
CDN or image host:
|
160
|
+
|
161
|
+
#{e.message}
|
162
|
+
|
163
|
+
Please check that the sftp section of config.yml is correct:
|
164
|
+
|
165
|
+
sftp:
|
166
|
+
host: '#{host}'
|
167
|
+
path: '#{path}'
|
168
|
+
username: '#{username}'
|
169
|
+
password: '#{password.gsub(/./, '*')}'
|
170
|
+
|
171
|
+
USAGE
|
172
|
+
|
151
173
|
end
|
152
174
|
|
175
|
+
|
153
176
|
# Timestamp to indicate we uploaded now
|
154
177
|
email.set_meta :last_upload, Time.now.to_i
|
155
178
|
|
data/lib/inkcite/util.rb
CHANGED
@@ -3,6 +3,22 @@
|
|
3
3
|
module Inkcite
|
4
4
|
module Util
|
5
5
|
|
6
|
+
def self.add_query_param href, value
|
7
|
+
|
8
|
+
# Start with either a question mark or an ampersand depending on
|
9
|
+
# whether or not there is already a question mark in the URI.
|
10
|
+
param = href.include?('?') ? '&' : '?'
|
11
|
+
param << value.to_s
|
12
|
+
|
13
|
+
if hash_position = href.index('#')
|
14
|
+
href[hash_position..0] = param
|
15
|
+
else
|
16
|
+
href << param
|
17
|
+
end
|
18
|
+
|
19
|
+
href
|
20
|
+
end
|
21
|
+
|
6
22
|
def self.brightness_value color
|
7
23
|
color.nil? ? 0 : (color.gsub('#', '').scan(/../).map { |c| c.hex }).inject { |sum, c| sum + c }
|
8
24
|
end
|
@@ -45,6 +61,10 @@ module Inkcite
|
|
45
61
|
|
46
62
|
end
|
47
63
|
|
64
|
+
def self.is_fully_qualified? href
|
65
|
+
href.include?('//')
|
66
|
+
end
|
67
|
+
|
48
68
|
def self.last_modified file
|
49
69
|
file && File.exists?(file) ? File.mtime(file).to_i : 0
|
50
70
|
end
|
data/lib/inkcite/version.rb
CHANGED
data/lib/inkcite/view.rb
CHANGED
@@ -216,29 +216,47 @@ module Inkcite
|
|
216
216
|
fn
|
217
217
|
end
|
218
218
|
|
219
|
+
# Returns the fully-qualified URL to the designated image (e.g. logo.gif)
|
220
|
+
# appropriate for the current rendering environment. In development
|
221
|
+
# mode, local will have either images/ or images-optim/ prepended on them
|
222
|
+
# depending on the status of image optimization.
|
223
|
+
#
|
224
|
+
# For non-development builds, fully-qualified URLs may be returned depending
|
225
|
+
# on the state of the config.yml and how image-host attributes have been
|
226
|
+
# configured.
|
227
|
+
#
|
228
|
+
# If a fully-qualified URL is provided, the URL will be returned with the
|
229
|
+
# possible addition of the cache-breaker tag.
|
219
230
|
def image_url src
|
220
231
|
|
221
232
|
src_url = ''
|
222
233
|
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
(@email.optimize_images?? Minifier::IMAGE_CACHE : Email::IMAGES) + '/'
|
234
|
+
if Util.is_fully_qualified?(src)
|
235
|
+
src_url << src
|
236
|
+
|
227
237
|
else
|
228
238
|
|
229
|
-
#
|
230
|
-
# in
|
231
|
-
|
239
|
+
# Prepend the image host onto the src if one is specified in the properties.
|
240
|
+
# During local development, images are always expected in an images/ subdirectory.
|
241
|
+
image_host = if development?
|
242
|
+
(@email.optimize_images?? Minifier::IMAGE_CACHE : Email::IMAGES) + '/'
|
243
|
+
else
|
244
|
+
|
245
|
+
# Use the image host defined in config.yml or, out-of-the-box refer to images/
|
246
|
+
# in the build directory.
|
247
|
+
self[Email::IMAGE_HOST] || (Email::IMAGES + '/')
|
232
248
|
|
233
|
-
|
249
|
+
end
|
250
|
+
|
251
|
+
src_url << image_host unless image_host.blank?
|
234
252
|
|
235
|
-
|
253
|
+
# Add the source of the image.
|
254
|
+
src_url << src
|
236
255
|
|
237
|
-
|
238
|
-
src_url << src
|
256
|
+
end
|
239
257
|
|
240
258
|
# Cache-bust the image if the caller is expecting it to be there.
|
241
|
-
src_url
|
259
|
+
Util::add_query_param(src_url, Time.now.to_i) if !production? && is_enabled?(Email::CACHE_BUST)
|
242
260
|
|
243
261
|
# Transpose any embedded tags into actual values.
|
244
262
|
Renderer.render(src_url, self)
|
@@ -548,10 +566,6 @@ module Inkcite
|
|
548
566
|
# Empty hash used when there is no environment or format-specific configuration
|
549
567
|
EMPTY_HASH = {}
|
550
568
|
|
551
|
-
# Name of the property holding the email field used to ensure that an unsubscribe has
|
552
|
-
# been placed into emails.
|
553
|
-
EMAIL_MERGE_TAG = :'email-merge-tag'
|
554
|
-
|
555
569
|
# Used when there is no subject or title for this email.
|
556
570
|
UNTITLED_EMAIL = 'Untitled Email'
|
557
571
|
|
data/test/renderer/image_spec.rb
CHANGED
@@ -20,12 +20,12 @@ describe Inkcite::Renderer::Image do
|
|
20
20
|
|
21
21
|
it 'substitutes a placeholder for a missing image of sufficient size' do
|
22
22
|
@view.config[Inkcite::Email::IMAGE_PLACEHOLDERS] = true
|
23
|
-
Inkcite::Renderer.render('{img src=missing.jpg height=50 width=100}', @view).must_equal('<img border=0 height=50 src="http://placehold.it/100x50.jpg" style="display:block" width=100>')
|
23
|
+
Inkcite::Renderer.render('{img src=missing.jpg height=50 width=100}', @view).must_equal('<img border=0 height=50 src="http://placehold.it/100x50.jpg?text=missing.jpg%0A(100%C3%9750)" style="display:block" width=100>')
|
24
24
|
end
|
25
25
|
|
26
26
|
it 'has configurable placeholder text' do
|
27
27
|
@view.config[Inkcite::Email::IMAGE_PLACEHOLDERS] = true
|
28
|
-
Inkcite::Renderer.render('{img src=missing.jpg height=50 width=100 fpo="F P O"}', @view).must_equal('<img border=0 height=50 src="http://placehold.it/100x50.jpg
|
28
|
+
Inkcite::Renderer.render('{img src=missing.jpg height=50 width=100 fpo="F P O"}', @view).must_equal('<img border=0 height=50 src="http://placehold.it/100x50.jpg?text=F%20P%20O%0A(100%C3%9750)" style="display:block" width=100>')
|
29
29
|
end
|
30
30
|
|
31
31
|
it 'does not substitute placeholders for small images' do
|
@@ -33,6 +33,10 @@ describe Inkcite::Renderer::Image do
|
|
33
33
|
Inkcite::Renderer.render('{img src=missing.jpg height=5 width=15}', @view).must_equal('<img border=0 height=5 src="missing.jpg" style="display:block" width=15>')
|
34
34
|
end
|
35
35
|
|
36
|
+
it 'does not alter externally referenced images' do
|
37
|
+
Inkcite::Renderer.render('{img src=://i.imgur.com/YJOX1PC.png height=5 width=15}', @view).must_equal('<img border=0 height=5 src="://i.imgur.com/YJOX1PC.png" style="display:block" width=15>')
|
38
|
+
end
|
39
|
+
|
36
40
|
it 'has configurable dimensions' do
|
37
41
|
Inkcite::Renderer.render('{img src=inkcite.jpg height=73 width=73}', @view).must_equal('<img border=0 height=73 src="images/inkcite.jpg" style="display:block" width=73>')
|
38
42
|
end
|
data/test/renderer/link_spec.rb
CHANGED
@@ -34,6 +34,12 @@ describe Inkcite::Renderer::Link do
|
|
34
34
|
Inkcite::Renderer.render('{a href="#news"}Latest News{/a}', @view).must_equal('<a href="#news" style="color:#0099cc;text-decoration:none">Latest News</a>')
|
35
35
|
end
|
36
36
|
|
37
|
+
it 'tags a reused link once and only once' do
|
38
|
+
@view.config[:'tag-links'] = "tag=inkcite|{id}"
|
39
|
+
Inkcite::Renderer.render('{a id="litmus" href="http://litmus.com"}Test Emails Here{/a}{a id="litmus"}Also Here{/a}', @view).must_equal('<a href="http://litmus.com?tag=inkcite|litmus" style="color:#0099cc;text-decoration:none" target=_blank>Test Emails Here</a><a href="http://litmus.com?tag=inkcite|litmus" style="color:#0099cc;text-decoration:none" target=_blank>Also Here</a>')
|
40
|
+
|
41
|
+
end
|
42
|
+
|
37
43
|
it 'raises a warning and generates an ID if one is not present' do
|
38
44
|
Inkcite::Renderer.render('{a href="http://inkceptional.com"}Click Here{/a}', @view).must_equal('<a href="http://inkceptional.com" style="color:#0099cc;text-decoration:none" target=_blank>Click Here</a>')
|
39
45
|
@view.errors.must_include('Link missing ID (line 0) [href=http://inkceptional.com]')
|
@@ -17,7 +17,7 @@ describe Inkcite::Renderer::MobileImage do
|
|
17
17
|
it 'substitutes a placeholder for a missing image of sufficient size' do
|
18
18
|
@view.config[Inkcite::Email::IMAGE_PLACEHOLDERS] = true
|
19
19
|
Inkcite::Renderer.render('{mobile-img src=inkcite-mobile.jpg height=100 width=300}{/mobile-img}', @view).must_equal('<span class="i01 img"></span>')
|
20
|
-
@view.media_query.find_by_klass('i01').to_css.must_equal('span[class~="i01"] { background-image:url("http://placehold.it/300x100.jpg");height:100px;width:300px }')
|
20
|
+
@view.media_query.find_by_klass('i01').to_css.must_equal('span[class~="i01"] { background-image:url("http://placehold.it/300x100.jpg?text=inkcite-mobile.jpg%0A(300%C3%97100)");height:100px;width:300px }')
|
21
21
|
end
|
22
22
|
|
23
23
|
it 'hides any images it wraps' do
|
data/test/renderer/table_spec.rb
CHANGED
@@ -51,4 +51,16 @@ describe Inkcite::Renderer::Table do
|
|
51
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
52
|
end
|
53
53
|
|
54
|
+
it 'supports fluid-stack desktop and style' do
|
55
|
+
|
56
|
+
markup = ''
|
57
|
+
markup << %Q({table font-size=25 bgcolor=#090 border="5px solid #f0f" padding=15 width=600 mobile="fluid-stack"})
|
58
|
+
markup << %Q({td width=195 bgcolor=#009 color=#fff valign=top}left{/td})
|
59
|
+
markup << %Q({td width=195 align=center}centered two-line{/td})
|
60
|
+
markup << %Q({td width=195 bgcolor=#900 color=#fff align=right font-size=30}right<br>three<br>lines{/td})
|
61
|
+
markup << %Q({/table})
|
62
|
+
|
63
|
+
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 dir=rtl 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]-->))
|
64
|
+
end
|
65
|
+
|
54
66
|
end
|
data/test/renderer/td_spec.rb
CHANGED
@@ -103,6 +103,10 @@ describe Inkcite::Renderer::Td do
|
|
103
103
|
Inkcite::Renderer.render('{td background=floor.jpg background-position=bottom}', @view).must_equal('<td style="background:url(images/floor.jpg) bottom no-repeat">')
|
104
104
|
end
|
105
105
|
|
106
|
+
it 'can have an externally hosted background image' do
|
107
|
+
Inkcite::Renderer.render('{td background=//i.imgur.com/YJOX1PC.png background-position=bottom}', @view).must_equal('<td style="background:url(//i.imgur.com/YJOX1PC.png) bottom no-repeat">')
|
108
|
+
end
|
109
|
+
|
106
110
|
it 'can have a background image on mobile' do
|
107
111
|
Inkcite::Renderer.render('{td mobile-background-image=wall.jpg mobile-background-position=right mobile-background-repeat=repeat-y}', @view).must_equal('<td class="m1">')
|
108
112
|
@view.media_query.find_by_klass('m1').to_css.must_equal('td[class~="m1"] { background:url(images/wall.jpg) right repeat-y }')
|
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.9.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-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|