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