inkcite 1.0.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 +7 -0
- data/LICENSE +20 -0
- data/README.md +110 -0
- data/Rakefile +8 -0
- data/assets/facebook-like.css +62 -0
- data/assets/facebook-like.js +59 -0
- data/assets/init/config.yml +97 -0
- data/assets/init/helpers.tsv +31 -0
- data/assets/init/source.html +60 -0
- data/assets/init/source.txt +6 -0
- data/bin/inkcite +6 -0
- data/inkcite.gemspec +42 -0
- data/lib/inkcite.rb +32 -0
- data/lib/inkcite/cli/base.rb +128 -0
- data/lib/inkcite/cli/build.rb +130 -0
- data/lib/inkcite/cli/init.rb +58 -0
- data/lib/inkcite/cli/preview.rb +30 -0
- data/lib/inkcite/cli/server.rb +123 -0
- data/lib/inkcite/cli/test.rb +61 -0
- data/lib/inkcite/email.rb +219 -0
- data/lib/inkcite/mailer.rb +140 -0
- data/lib/inkcite/minifier.rb +151 -0
- data/lib/inkcite/parser.rb +111 -0
- data/lib/inkcite/renderer.rb +177 -0
- data/lib/inkcite/renderer/base.rb +186 -0
- data/lib/inkcite/renderer/button.rb +168 -0
- data/lib/inkcite/renderer/div.rb +29 -0
- data/lib/inkcite/renderer/element.rb +82 -0
- data/lib/inkcite/renderer/footnote.rb +132 -0
- data/lib/inkcite/renderer/google_analytics.rb +35 -0
- data/lib/inkcite/renderer/image.rb +95 -0
- data/lib/inkcite/renderer/image_base.rb +82 -0
- data/lib/inkcite/renderer/in_browser.rb +38 -0
- data/lib/inkcite/renderer/like.rb +73 -0
- data/lib/inkcite/renderer/link.rb +243 -0
- data/lib/inkcite/renderer/litmus.rb +33 -0
- data/lib/inkcite/renderer/lorem.rb +39 -0
- data/lib/inkcite/renderer/mobile_image.rb +67 -0
- data/lib/inkcite/renderer/mobile_style.rb +40 -0
- data/lib/inkcite/renderer/mobile_toggle.rb +27 -0
- data/lib/inkcite/renderer/outlook_background.rb +48 -0
- data/lib/inkcite/renderer/partial.rb +31 -0
- data/lib/inkcite/renderer/preheader.rb +22 -0
- data/lib/inkcite/renderer/property.rb +39 -0
- data/lib/inkcite/renderer/responsive.rb +334 -0
- data/lib/inkcite/renderer/span.rb +21 -0
- data/lib/inkcite/renderer/table.rb +67 -0
- data/lib/inkcite/renderer/table_base.rb +149 -0
- data/lib/inkcite/renderer/td.rb +92 -0
- data/lib/inkcite/uploader.rb +173 -0
- data/lib/inkcite/util.rb +85 -0
- data/lib/inkcite/version.rb +3 -0
- data/lib/inkcite/view.rb +745 -0
- data/lib/inkcite/view/context.rb +38 -0
- data/lib/inkcite/view/media_query.rb +60 -0
- data/lib/inkcite/view/tag_stack.rb +38 -0
- data/test/email_spec.rb +16 -0
- data/test/parser_spec.rb +72 -0
- data/test/project/config.yml +98 -0
- data/test/project/helpers.tsv +56 -0
- data/test/project/images/inkcite.jpg +0 -0
- data/test/project/source.html +58 -0
- data/test/project/source.txt +6 -0
- data/test/renderer/button_spec.rb +45 -0
- data/test/renderer/div_spec.rb +101 -0
- data/test/renderer/element_spec.rb +31 -0
- data/test/renderer/footnote_spec.rb +57 -0
- data/test/renderer/image_spec.rb +82 -0
- data/test/renderer/link_spec.rb +84 -0
- data/test/renderer/mobile_image_spec.rb +27 -0
- data/test/renderer/mobile_style_spec.rb +37 -0
- data/test/renderer/td_spec.rb +126 -0
- data/test/renderer_spec.rb +28 -0
- data/test/view_spec.rb +15 -0
- metadata +333 -0
@@ -0,0 +1,95 @@
|
|
1
|
+
module Inkcite
|
2
|
+
module Renderer
|
3
|
+
class Image < ImageBase
|
4
|
+
|
5
|
+
def render tag, opt, ctx
|
6
|
+
|
7
|
+
img = Element.new('img', { :border => 0 })
|
8
|
+
|
9
|
+
# Ensure that height and width are defined in the image's attributes.
|
10
|
+
mix_dimensions img, opt
|
11
|
+
|
12
|
+
# Get the fully-qualified URL to the image or placeholder image if it's
|
13
|
+
# missing from the images directory.
|
14
|
+
img[:src] = image_url(opt[:src], opt, ctx)
|
15
|
+
|
16
|
+
mix_background img, opt
|
17
|
+
|
18
|
+
# Check to see if there is alt text specified for this image. We are
|
19
|
+
# testing against nil because sometimes the author desires an empty
|
20
|
+
# alt-text attribute.
|
21
|
+
alt = opt[:alt]
|
22
|
+
if alt
|
23
|
+
|
24
|
+
# Ensure that the alt-tag has quotes around it.
|
25
|
+
img[:alt] = quote(alt)
|
26
|
+
|
27
|
+
# The rest of this logic is only appropriate if the alt text
|
28
|
+
# is not blank.
|
29
|
+
unless alt.blank?
|
30
|
+
|
31
|
+
# Copy the text to the title attribute if enabled for this issue
|
32
|
+
img[:title] = img[:alt] if ctx.is_enabled?(COPY_ALT_TO_TITLE)
|
33
|
+
|
34
|
+
# All images with alt text inherit small font unless otherwise specified.
|
35
|
+
opt[:font] ||= 'small'
|
36
|
+
|
37
|
+
mix_font img, opt, ctx
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
# Images default to block display to prevent unexpected margins in Gmail
|
44
|
+
# http://www.campaignmonitor.com/blog/post/3132/how-to-stop-gmail-from-adding-a-margin-to-your-images/
|
45
|
+
display = opt[:display] || BLOCK
|
46
|
+
img.style[:display] = display unless display == DEFAULT
|
47
|
+
|
48
|
+
# True if the designer wants this image to flow inline. When true it
|
49
|
+
# vertically aligns the image with the text.
|
50
|
+
inline = (display == INLINE)
|
51
|
+
|
52
|
+
align = opt[:align] || ('absmiddle' if inline)
|
53
|
+
img[:align] = align unless align.blank?
|
54
|
+
|
55
|
+
valign = opt[:valign] || ('middle' if inline)
|
56
|
+
img.style[VERTICAL_ALIGN] = valign unless valign.blank?
|
57
|
+
|
58
|
+
mobile_src = opt[:'mobile-src']
|
59
|
+
unless mobile_src.blank?
|
60
|
+
|
61
|
+
# Get a unique CSS class name that will be used to swap in the alternate
|
62
|
+
# image on mobile.
|
63
|
+
klass = klass_name(mobile_src, ctx)
|
64
|
+
|
65
|
+
# Fully-qualify the image URL.
|
66
|
+
mobile_src = image_url(mobile_src, opt, ctx)
|
67
|
+
|
68
|
+
# Add a responsive rule that replaces the image with a different source
|
69
|
+
# with the same dimensions. Warning, this isn't supported on earlier
|
70
|
+
# versions of iOS 6 and Android 4.
|
71
|
+
# http://www.emailonacid.com/forum/viewthread/404/
|
72
|
+
ctx.media_query << img.add_rule(Rule.new(tag, klass, "content: url(#{mobile_src}) !important;"))
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
mobile = opt[:mobile]
|
77
|
+
|
78
|
+
# Check to see if this image is inside of a mobile-image declaration.
|
79
|
+
# If so, the image defaults to hide on mobile.
|
80
|
+
mobile = HIDE if mobile.blank? && !ctx.parent_opts(:mobile_image).blank?
|
81
|
+
|
82
|
+
mix_responsive img, opt, ctx, mobile
|
83
|
+
|
84
|
+
img.to_s
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
# Name of the property controlling whether or not the alt text should
|
90
|
+
# be copied to the title field.
|
91
|
+
COPY_ALT_TO_TITLE = :'copy-alt-to-title'
|
92
|
+
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module Inkcite
|
2
|
+
module Renderer
|
3
|
+
class ImageBase < Responsive
|
4
|
+
|
5
|
+
protected
|
6
|
+
|
7
|
+
# Display mode constants
|
8
|
+
BLOCK = 'block'
|
9
|
+
DEFAULT = 'default'
|
10
|
+
INLINE = 'inline'
|
11
|
+
|
12
|
+
def image_url _src, opt, ctx
|
13
|
+
|
14
|
+
src = _src
|
15
|
+
|
16
|
+
# True if dimensions are missing.
|
17
|
+
missing_dimensions = missing_dimensions?(opt)
|
18
|
+
|
19
|
+
# Fully-qualify the image path for this version of the email unless it
|
20
|
+
# is already includes a full address.
|
21
|
+
unless src.include?('://')
|
22
|
+
|
23
|
+
# Verify that the image exists.
|
24
|
+
if ctx.assert_image_exists(src) || ctx.is_disabled?(Inkcite::Email::IMAGE_PLACEHOLDERS)
|
25
|
+
|
26
|
+
if missing_dimensions
|
27
|
+
# TODO read the image dimensions from the file and auto-populate
|
28
|
+
# the width and height fields.
|
29
|
+
end
|
30
|
+
|
31
|
+
# Convert the source (e.g. "cover.jpg") into a fully-qualified reference
|
32
|
+
# to the image. In development this may be images/cover.jpg but in the
|
33
|
+
# other environments this would likely be a full URL to the image where it
|
34
|
+
# is being hosted.
|
35
|
+
src = ctx.image_url(src)
|
36
|
+
|
37
|
+
elsif DIMENSIONS.all? { |dim| opt[dim].to_i > MINIMUM_DIMENSION_FOR_PLACEHOLDER }
|
38
|
+
|
39
|
+
# As a convenience, replace missing images with placehold.it as long as they
|
40
|
+
# meet the minimum dimensions. No need to spam the design with tiny, tiny
|
41
|
+
# placeholders.
|
42
|
+
src = "http://placehold.it/#{opt[:width]}x#{opt[:height]}#{File.extname(src)}"
|
43
|
+
|
44
|
+
# Check to see if the designer specified FPO text for this placeholder -
|
45
|
+
# otherwise default to the dimensions of the image.
|
46
|
+
fpo = opt[:fpo]
|
47
|
+
src << "&text=#{URI::encode(fpo)}" unless fpo.blank?
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
# Don't let an image go into production without dimensions. Using the original
|
54
|
+
# src so that we don't display the verbose qualified URL to the developer.
|
55
|
+
ctx.error('Missing image dimensions', { :src => _src }) if missing_dimensions
|
56
|
+
|
57
|
+
quote(src)
|
58
|
+
end
|
59
|
+
|
60
|
+
def klass_name src, ctx
|
61
|
+
klass = "i%02d" % ctx.unique_id(:i)
|
62
|
+
end
|
63
|
+
|
64
|
+
def missing_dimensions? att
|
65
|
+
DIMENSIONS.any? { |dim| att[dim].to_i <= 0 }
|
66
|
+
end
|
67
|
+
|
68
|
+
def mix_dimensions img, opt
|
69
|
+
DIMENSIONS.each { |dim| img[dim] = opt[dim].to_i }
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
# Both the height and width of the image must exceed this amount in order
|
75
|
+
# to get a placehold.it automatically inserted. Otherwise only an error
|
76
|
+
# is raised for missing images.
|
77
|
+
MINIMUM_DIMENSION_FOR_PLACEHOLDER = 25
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
@@ -0,0 +1,38 @@
|
|
1
|
+
|
2
|
+
module Inkcite
|
3
|
+
module Renderer
|
4
|
+
class InBrowser < Base
|
5
|
+
|
6
|
+
def render tag, opt, ctx
|
7
|
+
|
8
|
+
# You can only view in-browser if we're viewing an email.
|
9
|
+
return nil unless ctx.email?
|
10
|
+
|
11
|
+
url = ctx[Inkcite::Email::VIEW_IN_BROWSER_URL]
|
12
|
+
return nil if url.blank?
|
13
|
+
|
14
|
+
browser_view = ctx.email.view(ctx.environment, :browser, ctx.version)
|
15
|
+
|
16
|
+
# Make sure we're converting any embedded values in the host URL
|
17
|
+
url = Renderer.render(url, browser_view)
|
18
|
+
|
19
|
+
# Optional link override color.
|
20
|
+
color = opt[:color]
|
21
|
+
|
22
|
+
# Optional call-to-action override - otherwise defaults to view in browser.
|
23
|
+
cta = opt[:cta] || 'View in Browser'
|
24
|
+
|
25
|
+
id = opt[:id] || 'in-browser'
|
26
|
+
|
27
|
+
html = "{a id=\"#{id}\" href=\"#{url}\""
|
28
|
+
html << " color=\"#{color}\"" unless color.blank?
|
29
|
+
html << '}'
|
30
|
+
html << cta
|
31
|
+
html << '{/a}'
|
32
|
+
|
33
|
+
html
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Inkcite
|
2
|
+
module Renderer
|
3
|
+
class Like < Base
|
4
|
+
|
5
|
+
def render tag, opt, ctx
|
6
|
+
|
7
|
+
# Handle the case where we're building the hosted version of the email and
|
8
|
+
# JavaScript is used to trigger the Facebook like dialog.
|
9
|
+
if ctx.browser?
|
10
|
+
|
11
|
+
return '{/a}' if tag == '/like'
|
12
|
+
|
13
|
+
page = opt[:page]
|
14
|
+
if page.blank?
|
15
|
+
ctx.error("Like tag missing 'page' attribute")
|
16
|
+
|
17
|
+
else
|
18
|
+
|
19
|
+
brand = opt[:brand] || 'Us'
|
20
|
+
|
21
|
+
# Add an externally-hosted script to the context.
|
22
|
+
ctx.scripts << URI.parse('http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js')
|
23
|
+
|
24
|
+
# Add the java script to be embedded.
|
25
|
+
ctx.scripts << URI.parse('file://facebook-like.js')
|
26
|
+
|
27
|
+
# Add the Facebook like stylesheet.
|
28
|
+
ctx.styles << URI.parse('file://facebook-like.css')
|
29
|
+
|
30
|
+
ctx.footer << <<-eos
|
31
|
+
<div id="dialog-wrap">
|
32
|
+
<div id="dialog">
|
33
|
+
<h2>Like #{brand} on Facebook</h2>
|
34
|
+
<div id="dialog-content" class='loading'>
|
35
|
+
<div class="fb-like" data-href="http://www.facebook.com/#{page}" data-send="true" data-height="100" data-width="450" data-show-faces="true"></div>
|
36
|
+
</div>
|
37
|
+
<div id="dialog-buttons">
|
38
|
+
<a href=# onclick="return closeLike();"><span>Close</span></a>
|
39
|
+
</div>
|
40
|
+
</div>
|
41
|
+
</div>
|
42
|
+
|
43
|
+
<div id="fb-root"></div>
|
44
|
+
eos
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
'{a href=# onclick="return openLike();"}'
|
49
|
+
|
50
|
+
else
|
51
|
+
|
52
|
+
url = ctx[Inkcite::Email::VIEW_IN_BROWSER_URL]
|
53
|
+
unless url.blank?
|
54
|
+
|
55
|
+
return '{/a}' if tag == '/like'
|
56
|
+
|
57
|
+
# Otherwise, link to the hosted version of the email with the like hash tag
|
58
|
+
# to trigger like automatically on arrival.
|
59
|
+
href = Inkcite::Renderer.render(ctx[Inkcite::Email::VIEW_IN_BROWSER_URL] + '#like', ctx)
|
60
|
+
|
61
|
+
id = opt[:id]
|
62
|
+
|
63
|
+
"{a id=\"#{id}\" href=\"#{href}\"}"
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
@@ -0,0 +1,243 @@
|
|
1
|
+
module Inkcite
|
2
|
+
module Renderer
|
3
|
+
class Link < Responsive
|
4
|
+
|
5
|
+
def render tag, opt, ctx
|
6
|
+
|
7
|
+
tag_stack = ctx.tag_stack(:a)
|
8
|
+
|
9
|
+
if tag == '/a'
|
10
|
+
|
11
|
+
# Grab the attributes of the opening tag.
|
12
|
+
opening = tag_stack.pop
|
13
|
+
|
14
|
+
# Nothing to do in the
|
15
|
+
return '' if ctx.text?
|
16
|
+
|
17
|
+
html = '</a>'
|
18
|
+
|
19
|
+
# Check to see if the declaration has been marked as a block
|
20
|
+
# element and if so, close the div.
|
21
|
+
html << '</div>' if opening[:block]
|
22
|
+
|
23
|
+
return html
|
24
|
+
end
|
25
|
+
|
26
|
+
# Push the link's options onto the tag stack so that we can have
|
27
|
+
# access to its attributes when we close it.
|
28
|
+
tag_stack << opt
|
29
|
+
|
30
|
+
# Get the currently open table cell and see if link color is
|
31
|
+
# overridden in there.
|
32
|
+
td_parent = ctx.tag_stack(:td).opts
|
33
|
+
table_parent = ctx.tag_stack(:table).opts
|
34
|
+
|
35
|
+
# Choose a color from the parameters or inherit from the parent td, table or context.
|
36
|
+
opt[:color] = detect(opt[:color], td_parent[:link], table_parent[:link], ctx[LINK_COLOR])
|
37
|
+
|
38
|
+
a = Element.new('a')
|
39
|
+
|
40
|
+
mix_font a, opt, ctx
|
41
|
+
|
42
|
+
id = opt[:id]
|
43
|
+
href = opt[:href]
|
44
|
+
|
45
|
+
# True if the href is missing. If so, we may try to look it up by it's ID
|
46
|
+
# or we'll insert a default TBD link.
|
47
|
+
missing = href.blank?
|
48
|
+
|
49
|
+
# True if it's a link deeper into the content.
|
50
|
+
hash = !missing && href.starts_with?(POUND_SIGN)
|
51
|
+
|
52
|
+
# True if this is a mailto link.
|
53
|
+
mailto = !missing && !hash && href.starts_with?(MAILTO)
|
54
|
+
|
55
|
+
# Only perform special processing on the link if it's TBD or not a link to
|
56
|
+
# something in the page.
|
57
|
+
unless hash || mailto
|
58
|
+
|
59
|
+
if id.blank?
|
60
|
+
|
61
|
+
# Generate a placeholder ID and warn the user about it.
|
62
|
+
id = "link#{ctx.links.size + 1}"
|
63
|
+
ctx.error 'Link missing ID', { :href => href }
|
64
|
+
|
65
|
+
else
|
66
|
+
|
67
|
+
# Check to see if we've encountered an auto-incrementing link ID (e.g. event++)
|
68
|
+
# Replace the ++ with a unique count for this ID prefix.
|
69
|
+
id = id.gsub(PLUS_PLUS, ctx.unique_id(id).to_s) if id.end_with?(PLUS_PLUS)
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
# Get the HREF that we have previously encountered for this ID. When not blank
|
74
|
+
# we'll sanity check that the URL is the same.
|
75
|
+
last_href = ctx.links[id]
|
76
|
+
|
77
|
+
if missing
|
78
|
+
|
79
|
+
# If we don't have a URL, check to see if we've encountered this
|
80
|
+
href = last_href || ctx[MISSING_LINK_HREF]
|
81
|
+
|
82
|
+
ctx.error "Link missing href", { :id => id } unless last_href
|
83
|
+
|
84
|
+
else
|
85
|
+
|
86
|
+
# Optionally tag the link's query string for post-send log analytics.
|
87
|
+
href = add_tagging(id, href, ctx)
|
88
|
+
|
89
|
+
if last_href.blank?
|
90
|
+
|
91
|
+
# Associate the href with it's ID in case we bump into this link again.
|
92
|
+
ctx.links[id] = href
|
93
|
+
|
94
|
+
elsif last_href != href
|
95
|
+
|
96
|
+
# It saves everyone a lot of time if you alert them that an ID appears multiple times
|
97
|
+
# in the email and with mismatched URLs.
|
98
|
+
ctx.error "Link href mismatch", { :id => id, :expected => last_href, :found => href }
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
# Optionally replace the href with an ESP trackable url. Gotta do this after
|
105
|
+
# the link has been stored in the context because we don't want trackable
|
106
|
+
# URLs interfering with the links file.
|
107
|
+
href = add_tracking(id, href, ctx)
|
108
|
+
|
109
|
+
a[:target] = BLANK
|
110
|
+
|
111
|
+
end
|
112
|
+
|
113
|
+
# Make sure that these types of links have quotes.
|
114
|
+
href = quote(href) unless ctx.text?
|
115
|
+
|
116
|
+
# Set the href attribute to the resolved href.
|
117
|
+
a[:href] = href
|
118
|
+
|
119
|
+
# Links never get any text decoration.
|
120
|
+
a.style[TEXT_DECORATION] = NONE
|
121
|
+
|
122
|
+
if ctx.browser?
|
123
|
+
|
124
|
+
# Programmatically we can install onclick listeners for hosted versions.
|
125
|
+
# Check to see if one is specified and the Javascript is permitted in
|
126
|
+
# this version.
|
127
|
+
onclick = opt[:onclick]
|
128
|
+
a[:onclick] = quote(onclick) unless onclick.blank?
|
129
|
+
|
130
|
+
end
|
131
|
+
|
132
|
+
html = ''
|
133
|
+
|
134
|
+
if ctx.text?
|
135
|
+
html << a[:href]
|
136
|
+
|
137
|
+
else
|
138
|
+
|
139
|
+
klass = opt[:class]
|
140
|
+
a.classes << klass unless klass.blank?
|
141
|
+
|
142
|
+
mix_responsive a, opt, ctx
|
143
|
+
|
144
|
+
# Some responsive modes (e.g. button) change the display type from in-line
|
145
|
+
# to block. This change can cause unexpected whitespace or other unexpected
|
146
|
+
# layout changes. Outlook doesn't support block display on link elements
|
147
|
+
# so the best workaround is simply to wrap the element in <div> tags.
|
148
|
+
if a.responsive_styles.any?(&:block?)
|
149
|
+
html << '<div>'
|
150
|
+
|
151
|
+
# Remember that we made this element block-display so that we can append
|
152
|
+
# the extra div when we close the tag.
|
153
|
+
opt[:block] = true
|
154
|
+
|
155
|
+
end
|
156
|
+
|
157
|
+
html << a.to_s
|
158
|
+
|
159
|
+
end
|
160
|
+
|
161
|
+
html
|
162
|
+
end
|
163
|
+
|
164
|
+
private
|
165
|
+
|
166
|
+
# Property controlling where missing links are pointed.
|
167
|
+
MISSING_LINK_HREF = :'missing-link-url'
|
168
|
+
|
169
|
+
# The configuration name of the field that holds the query parameter that
|
170
|
+
# will be tacked onto the end of all links.
|
171
|
+
TAG_LINKS = :'tag-links'
|
172
|
+
|
173
|
+
# The configuration name of the field that holds the domain name(s) for
|
174
|
+
# links that will be tagged.
|
175
|
+
TAG_LINKS_DOMAIN = :'tag-links-domain'
|
176
|
+
|
177
|
+
# The property name used to indicate that links in this email should be
|
178
|
+
# replaced with [trackable URLs].
|
179
|
+
TRACK_LINKS = :'track-links'
|
180
|
+
|
181
|
+
# Value to open links in a new window.
|
182
|
+
BLANK = '_blank'
|
183
|
+
|
184
|
+
MAILTO = 'mailto:'
|
185
|
+
|
186
|
+
# Signifies an auto-incrementing link ID.
|
187
|
+
PLUS_PLUS = '++'
|
188
|
+
|
189
|
+
def add_tagging id, href, ctx
|
190
|
+
|
191
|
+
# Check to see if we're tagging links.
|
192
|
+
tag = ctx[TAG_LINKS]
|
193
|
+
unless tag.blank?
|
194
|
+
|
195
|
+
# Blank tag domain means tag all the links - otherwise, make sure the
|
196
|
+
# href matches the desired domain name.
|
197
|
+
tag_domain = ctx[TAG_LINKS_DOMAIN]
|
198
|
+
if tag_domain.blank? || href =~ /^https?:\/\/[^\/]*#{tag_domain}/
|
199
|
+
|
200
|
+
# Prepend it with a question mark or an ampersand depending on the current
|
201
|
+
# state of the lin.
|
202
|
+
stag = href.include?('?') ? '&' : '?'
|
203
|
+
stag << replace_tag(tag, id, ctx)
|
204
|
+
|
205
|
+
# Inject before the pound sign if present - otherwise, just tack it on
|
206
|
+
# to the end of the href.
|
207
|
+
if hash = href.index(POUND_SIGN)
|
208
|
+
href[hash..0] = stag
|
209
|
+
else
|
210
|
+
href << stag
|
211
|
+
end
|
212
|
+
|
213
|
+
end
|
214
|
+
|
215
|
+
end
|
216
|
+
|
217
|
+
href
|
218
|
+
end
|
219
|
+
|
220
|
+
def add_tracking id, href, ctx
|
221
|
+
|
222
|
+
# Check to see if a trackable link string has been defined.
|
223
|
+
tracking = ctx[Inkcite::Email::TRACK_LINKS]
|
224
|
+
|
225
|
+
# Replace the fully-qualified URL with a tracking tag - presuming that the
|
226
|
+
# ESP will replace this href with it's own trackable URL at deployment.
|
227
|
+
href = URI.encode(replace_tag(tracking, id, ctx)) unless tracking.blank?
|
228
|
+
|
229
|
+
href
|
230
|
+
end
|
231
|
+
|
232
|
+
def replace_tag tag, id, ctx
|
233
|
+
|
234
|
+
# Inject the link's ID into the tag - that's the only value that can't
|
235
|
+
# be resolved from the context.
|
236
|
+
tag = tag.gsub('{id}', id)
|
237
|
+
|
238
|
+
Inkcite::Renderer.render(tag, ctx)
|
239
|
+
end
|
240
|
+
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|