inkcite 1.2.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/inkcite/view.rb CHANGED
@@ -151,7 +151,32 @@ module Inkcite
151
151
  end
152
152
 
153
153
  def footnotes
154
- @footnotes ||= []
154
+
155
+ if @footnotes.nil?
156
+ @footnotes = []
157
+
158
+ # Preload the array of footnotes if they exist
159
+ footnotes_tsv_file = @email.project_file(FOOTNOTES_TSV_FILE)
160
+ if File.exists?(footnotes_tsv_file)
161
+ CSV.foreach(footnotes_tsv_file, { :col_sep => "\t" }) do |fn|
162
+
163
+ id = fn[0]
164
+ next if id.blank?
165
+
166
+ text = fn[2]
167
+ next if text.blank?
168
+
169
+ # Read the symbol and replace it with nil (so that one will be auto-generated)
170
+ symbol = fn[1]
171
+ symbol = nil if symbol.blank?
172
+
173
+ @footnotes << Renderer::Footnote::Instance.new(id, symbol, text, false)
174
+
175
+ end
176
+ end
177
+ end
178
+
179
+ @footnotes
155
180
  end
156
181
 
157
182
  def file_name ext=nil
@@ -190,7 +215,12 @@ module Inkcite
190
215
 
191
216
  # Prepend the image host onto the src if one is specified in the properties.
192
217
  # During local development, images are always expected in an images/ subdirectory.
193
- image_host = development?? "#{Email::IMAGES}/" : self[Email::IMAGE_HOST]
218
+ image_host = if development?
219
+ (@email.optimize_images?? Minifier::IMAGE_CACHE : Email::IMAGES) + '/'
220
+ else
221
+ self[Email::IMAGE_HOST]
222
+ end
223
+
194
224
  src_url << image_host unless image_host.blank?
195
225
 
196
226
  # Add the source of the image.
@@ -344,13 +374,19 @@ module Inkcite
344
374
  html << '</style>'
345
375
  html << '</head>'
346
376
 
347
- # Render the body statement and apply the email's background color to it.
348
- bgcolor = Renderer.hex(self[BACKGROUND])
349
-
350
- # Intentially not setting the link colors because those should be entirely
377
+ # Intentionally not setting the link colors because those should be entirely
351
378
  # controlled by the styles and attributes of the links themselves. By not
352
379
  # setting it, links created sans-helper should be visually distinct.
353
- html << Renderer.render("<body bgcolor=\"#{bgcolor}\" style=\"background-color: #{bgcolor}; width: 100% !important; min-width: 100% !important; margin: 0; padding: 0; -webkit-text-size-adjust: none; -ms-text-size-adjust: none;\">", self)
380
+ html << '<body style="width: 100% !important; min-width: 100% !important; margin: 0 !important; padding: 0; -webkit-text-size-adjust: none; -ms-text-size-adjust: none;'
381
+
382
+ # A pleasing but obvious background exposed in development mode to alert
383
+ # the designer that they have exposed the body background - which means
384
+ # unpredictable results if sent.
385
+ if development?
386
+ html << " background: #ccc url('data:image/png;base64,#{Inkcite.blueprint_image64}');"
387
+ end
388
+
389
+ html << %q(">)
354
390
 
355
391
  html << minified
356
392
 
@@ -479,7 +515,6 @@ module Inkcite
479
515
  private
480
516
 
481
517
  ASSETS = 'assets'
482
- BACKGROUND = :'#background'
483
518
  FILE_SCHEME = 'file'
484
519
  FILE_NAME = :'file-name'
485
520
  HTML_EXTENSION = '.html'
@@ -522,6 +557,9 @@ module Inkcite
522
557
  # Tab-separated file containing links declarations.
523
558
  LINKS_TSV_FILE = 'links.tsv'
524
559
 
560
+ # Tab-separated file containing footnote declarations.
561
+ FOOTNOTES_TSV_FILE = 'footnotes.tsv'
562
+
525
563
  def assert_in_browser msg
526
564
  raise msg if email? && !development?
527
565
  end
@@ -716,6 +754,10 @@ module Inkcite
716
754
  # Ensure that telephone numbers are displayed using the same style as links.
717
755
  reset << "a[href^=tel] { color: #{self[Renderer::Base::LINK_COLOR]}; text-decoration:none;}"
718
756
 
757
+ # Remove extraneous left-margins on Android 4.4
758
+ # https://litmus.com/community/code/4194-why-is-email-not-centered-on-android-4-4#comment-5727
759
+ reset << 'div[style*="margin: 16px 0"] { margin:0 !important; }'
760
+
719
761
  end
720
762
 
721
763
  # Reset the font on every cell to the default family.
data/lib/inkcite.rb CHANGED
@@ -23,7 +23,16 @@ require 'inkcite/renderer'
23
23
  module Inkcite
24
24
 
25
25
  def self.asset_path
26
- File.expand_path('../../..', File.dirname(__FILE__))
26
+ File.join(File.expand_path('../', File.dirname(__FILE__)), 'assets')
27
+ end
28
+
29
+ # Loads (and caches) the base64-encoded PNG data for the subtle background
30
+ # texture that Inkcite installs on the <body> tag in development mode.
31
+ def self.blueprint_image64
32
+ @blueprint64 ||= begin
33
+ blueprint_path = File.join(asset_path, 'blueprint.png')
34
+ Base64.encode64(File.read(blueprint_path)).gsub(/[\r\f\n]/, '')
35
+ end
27
36
  end
28
37
 
29
38
  end
@@ -0,0 +1,97 @@
1
+ require 'minitest/spec'
2
+ require 'minitest/autorun'
3
+ require 'inkcite'
4
+
5
+ describe Inkcite::Renderer::Span do
6
+
7
+ before do
8
+ @view = Inkcite::Email.new('test/project/').view(:development, :email)
9
+ end
10
+
11
+ it 'can have empty parameters' do
12
+ Inkcite::Renderer.render('{span}{/span}', @view).must_equal('<span></span>')
13
+ end
14
+
15
+ it 'can have a custom font color' do
16
+ Inkcite::Renderer.render('{span color=#f90}{/span}', @view).must_equal('<span style="color:#ff9900"></span>')
17
+ end
18
+
19
+ it 'can have a custom font family' do
20
+ Inkcite::Renderer.render('{span font-family="Comic Sans"}{/span}', @view).must_equal('<span style="font-family:Comic Sans"></span>')
21
+ end
22
+
23
+ it 'can have a custom font size' do
24
+ Inkcite::Renderer.render('{span font-size=18}{/span}', @view).must_equal('<span style="font-size:18px"></span>')
25
+ end
26
+
27
+ it 'can have a custom font weight' do
28
+ Inkcite::Renderer.render('{span font-weight=bold}{/span}', @view).must_equal('<span style="font-weight:bold"></span>')
29
+ end
30
+
31
+ it 'can have a custom line height' do
32
+ Inkcite::Renderer.render('{span line-height=15}{/span}', @view).must_equal('<span style="line-height:15px"></span>')
33
+ end
34
+
35
+ it 'can inherit a font from the context' do
36
+ Inkcite::Renderer.render('{span font=large}{/span}', @view).must_equal('<span style="color:#ff0000;font-family:serif;font-size:24px;font-weight:bold;line-height:24px"></span>')
37
+ end
38
+
39
+ it 'can override the font size of an inherited font' do
40
+ Inkcite::Renderer.render('{span font=large font-size=8}{/span}', @view).must_equal('<span style="color:#ff0000;font-family:serif;font-size:8px;font-weight:bold;line-height:24px"></span>')
41
+ Inkcite::Renderer.render('{span font=large font-size=none}{/span}', @view).must_equal('<span style="color:#ff0000;font-family:serif;font-weight:bold;line-height:24px"></span>')
42
+ end
43
+
44
+ it 'can override the color of an inherited font' do
45
+ Inkcite::Renderer.render('{span font=large color=#00f}{/span}', @view).must_equal('<span style="color:#0000ff;font-family:serif;font-size:24px;font-weight:bold;line-height:24px"></span>')
46
+ Inkcite::Renderer.render('{span font=large color=none}{/span}', @view).must_equal('<span style="font-family:serif;font-size:24px;font-weight:bold;line-height:24px"></span>')
47
+ end
48
+
49
+ it 'can override the font weight of an inherited font' do
50
+ Inkcite::Renderer.render('{span font=large font-weight=normal}{/span}', @view).must_equal('<span style="color:#ff0000;font-family:serif;font-size:24px;font-weight:normal;line-height:24px"></span>')
51
+ Inkcite::Renderer.render('{span font=large font-weight=none}{/span}', @view).must_equal('<span style="color:#ff0000;font-family:serif;font-size:24px;line-height:24px"></span>')
52
+ end
53
+
54
+ it 'can override the line height of an inherited font' do
55
+ Inkcite::Renderer.render('{span font=large line-height=12}{/span}', @view).must_equal('<span style="color:#ff0000;font-family:serif;font-size:24px;font-weight:bold;line-height:12px"></span>')
56
+ Inkcite::Renderer.render('{span font=large line-height=normal}{/span}', @view).must_equal('<span style="color:#ff0000;font-family:serif;font-size:24px;font-weight:bold;line-height:normal"></span>')
57
+ Inkcite::Renderer.render('{span font=large line-height=none}{/span}', @view).must_equal('<span style="color:#ff0000;font-family:serif;font-size:24px;font-weight:bold"></span>')
58
+ end
59
+
60
+ it 'can have a text shadow' do
61
+ Inkcite::Renderer.render('{span shadow=#99c}{/span}', @view).must_equal('<span style="text-shadow:0 1px 0 #9999cc"></span>')
62
+ Inkcite::Renderer.render('{span shadow=#9c9 shadow-blur=2}{/span}', @view).must_equal('<span style="text-shadow:0 1px 2px #99cc99"></span>')
63
+ Inkcite::Renderer.render('{span shadow=#c99 shadow-offset=-1}{/span}', @view).must_equal('<span style="text-shadow:0 -1px 0 #cc9999"></span>')
64
+ end
65
+
66
+ it 'can have a background color' do
67
+ Inkcite::Renderer.render('{span bgcolor=#06c}{/span}', @view).must_equal('<span style="background-color:#0066cc"></span>')
68
+ end
69
+
70
+ it 'can be responsive' do
71
+ Inkcite::Renderer.render('{span mobile=hide}{/span}', @view).must_equal('<span class="hide"></span>')
72
+ end
73
+
74
+ it 'can have custom letter spacing' do
75
+ Inkcite::Renderer.render('{span letter-spacing=3}{/span}', @view).must_equal('<span style="letter-spacing:3px"></span>')
76
+ end
77
+
78
+ it 'can have a custom font size on mobile' do
79
+ Inkcite::Renderer.render('{span font-size=15 mobile-font-size=20}{/span}', @view).must_equal('<span class="m1" style="font-size:15px"></span>')
80
+ @view.media_query.find_by_klass('m1').declarations.must_match('font-size:20px !important')
81
+ end
82
+
83
+ it 'can have a custom line height on mobile' do
84
+ Inkcite::Renderer.render('{span line-height=15 mobile-line-height=20}{/span}', @view).must_equal('<span class="m1" style="line-height:15px"></span>')
85
+ @view.media_query.find_by_klass('m1').to_css.must_equal('span[class~="m1"] { line-height:20px !important }')
86
+ end
87
+
88
+ it 'can inherit a custom font size on mobile from the context' do
89
+ Inkcite::Renderer.render('{span font=responsive}{/span}', @view).must_equal('<span class="m1" style="font-size:20px"></span>')
90
+ @view.media_query.find_by_klass('m1').declarations.must_match('font-size:40px')
91
+ end
92
+
93
+ it 'supports padding' do
94
+ Inkcite::Renderer.render('{span padding=15}{/span}', @view).must_equal('<span style="padding:15px"></span>')
95
+ end
96
+
97
+ end
@@ -91,12 +91,12 @@ describe Inkcite::Renderer::Td do
91
91
 
92
92
  it 'can have a custom background color on mobile' do
93
93
  Inkcite::Renderer.render('{td mobile-bgcolor=#f09}', @view).must_equal('<td class="m1">')
94
- @view.media_query.find_by_klass('m1').to_css.must_equal('td[class~="m1"] { background-color:#ff0099 }')
94
+ @view.media_query.find_by_klass('m1').to_css.must_equal('td[class~="m1"] { background:#ff0099 }')
95
95
  end
96
96
 
97
97
  it 'can override background color on mobile' do
98
98
  Inkcite::Renderer.render('{td bgcolor=#f00 mobile-bgcolor=#00f}', @view).must_equal('<td bgcolor=#ff0000 class="m1">')
99
- @view.media_query.find_by_klass('m1').to_css.must_equal('td[class~="m1"] { background-color:#0000ff !important }')
99
+ @view.media_query.find_by_klass('m1').to_css.must_equal('td[class~="m1"] { background:#0000ff !important }')
100
100
  end
101
101
 
102
102
  it 'can have a background image' do
@@ -109,18 +109,18 @@ describe Inkcite::Renderer::Td do
109
109
  end
110
110
 
111
111
  it 'can override background image on mobile' do
112
- Inkcite::Renderer.render('{td background=floor.jpg background-position=bottom mobile-background-image=sky.jpg mobile-background-position=top}', @view).must_equal('<td class="m1" style="background:url(images/floor.jpg) bottom no-repeat">')
113
- @view.media_query.find_by_klass('m1').to_css.must_equal('td[class~="m1"] { background:url(images/sky.jpg) top no-repeat !important }')
112
+ Inkcite::Renderer.render('{td background=floor.jpg mobile-background-image=sky.jpg }', @view).must_equal('<td class="m1" style="background:url(images/floor.jpg)">')
113
+ @view.media_query.find_by_klass('m1').to_css.must_equal('td[class~="m1"] { background:url(images/sky.jpg) !important }')
114
114
  end
115
115
 
116
- it 'can disable background image on mobile' do
117
- Inkcite::Renderer.render('{td bgcolor=#f00 background=floor.jpg background-position=bottom mobile-bgcolor=#0f0 mobile-background-image=none}', @view).must_equal('<td bgcolor=#ff0000 class="m1" style="background:#ff0000 url(images/floor.jpg) bottom no-repeat">')
118
- @view.media_query.find_by_klass('m1').to_css.must_equal('td[class~="m1"] { background-color:#00ff00 !important;background-image:none !important }')
116
+ it 'can override background position on mobile' do
117
+ Inkcite::Renderer.render('{td background=floor.jpg background-position=bottom mobile-background-position=top}', @view).must_equal('<td class="m1" style="background:url(images/floor.jpg) bottom no-repeat">')
118
+ @view.media_query.find_by_klass('m1').to_css.must_equal('td[class~="m1"] { background:url(images/floor.jpg) top no-repeat !important }')
119
119
  end
120
120
 
121
- it 'inherits background position and repeat on mobile' do
122
- Inkcite::Renderer.render('{td background=floor.jpg background-position=bottom mobile-background-image=sky.jpg}', @view).must_equal('<td class="m1" style="background:url(images/floor.jpg) bottom no-repeat">')
123
- @view.media_query.find_by_klass('m1').to_css.must_equal('td[class~="m1"] { background:url(images/sky.jpg) bottom no-repeat !important }')
121
+ it 'can disable background image on mobile' do
122
+ Inkcite::Renderer.render('{td background=floor.jpg mobile-background-image=none}', @view).must_equal('<td class="m1" style="background:url(images/floor.jpg)">')
123
+ @view.media_query.find_by_klass('m1').to_css.must_equal('td[class~="m1"] { background:none !important }')
124
124
  end
125
125
 
126
126
  end
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.2.0
4
+ version: 1.6.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-09-15 00:00:00.000000000 Z
11
+ date: 2015-10-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -66,6 +66,34 @@ dependencies:
66
66
  - - '>='
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: image_optim
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: image_optim_pack
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
69
97
  - !ruby/object:Gem::Dependency
70
98
  name: litmus
71
99
  requirement: !ruby/object:Gem::Requirement
@@ -232,12 +260,16 @@ files:
232
260
  - LICENSE
233
261
  - README.md
234
262
  - Rakefile
263
+ - assets/blueprint.png
264
+ - assets/example/helpers.tsv
265
+ - assets/example/source.html
266
+ - assets/example/source.txt
235
267
  - assets/facebook-like.css
236
268
  - assets/facebook-like.js
237
269
  - assets/init/config.yml
238
270
  - assets/init/helpers.tsv
271
+ - assets/init/image_optim.yml
239
272
  - assets/init/source.html
240
- - assets/init/source.txt
241
273
  - bin/inkcite
242
274
  - bin/release-major
243
275
  - bin/release-minor
@@ -267,7 +299,7 @@ files:
267
299
  - lib/inkcite/renderer/increment.rb
268
300
  - lib/inkcite/renderer/like.rb
269
301
  - lib/inkcite/renderer/link.rb
270
- - lib/inkcite/renderer/litmus.rb
302
+ - lib/inkcite/renderer/litmus_analytics.rb
271
303
  - lib/inkcite/renderer/lorem.rb
272
304
  - lib/inkcite/renderer/mobile_image.rb
273
305
  - lib/inkcite/renderer/mobile_style.rb
@@ -304,6 +336,7 @@ files:
304
336
  - test/renderer/link_spec.rb
305
337
  - test/renderer/mobile_image_spec.rb
306
338
  - test/renderer/mobile_style_spec.rb
339
+ - test/renderer/span_spec.rb
307
340
  - test/renderer/table_spec.rb
308
341
  - test/renderer/td_spec.rb
309
342
  - test/renderer_spec.rb
@@ -349,6 +382,7 @@ test_files:
349
382
  - test/renderer/link_spec.rb
350
383
  - test/renderer/mobile_image_spec.rb
351
384
  - test/renderer/mobile_style_spec.rb
385
+ - test/renderer/span_spec.rb
352
386
  - test/renderer/table_spec.rb
353
387
  - test/renderer/td_spec.rb
354
388
  - test/renderer_spec.rb
@@ -1,33 +0,0 @@
1
- module Inkcite
2
- module Renderer
3
- class Litmus < Base
4
-
5
- def render tag, opt, ctx
6
-
7
- # Litmus tracking is enabled only for production emails.
8
- return nil unless ctx.production? && ctx.email?
9
-
10
- code = opt[:code] || opt[:id]
11
- return nil if code.blank?
12
-
13
- merge_tag = opt[MERGE_TAG] || ctx[MERGE_TAG]
14
-
15
- ctx.styles << "@media print{#_t { background-image: url('https://#{code}.emltrk.com/#{code}?p&d=#{merge_tag}');}}"
16
- ctx.styles << "div.OutlookMessageHeader {background-image:url('https://#{code}.emltrk.com/#{code}?f&d=#{merge_tag}')}"
17
- ctx.styles << "table.moz-email-headers-table {background-image:url('https://#{code}.emltrk.com/#{code}?f&d=#{merge_tag}')}"
18
- ctx.styles << "blockquote #_t {background-image:url('https://#{code}.emltrk.com/#{code}?f&d=#{merge_tag}')}"
19
- ctx.styles << "#MailContainerBody #_t {background-image:url('https://#{code}.emltrk.com/#{code}?f&d=#{merge_tag}')}"
20
-
21
- ctx.footer << '<div id="_t"></div>'
22
- ctx.footer << "<img src=\"https://#{code}.emltrk.com/#{code}?d=#{merge_tag}\" width=1 height=1 border=0 />"
23
-
24
- nil
25
- end
26
-
27
- private
28
-
29
- MERGE_TAG = :'merge-tag'
30
-
31
- end
32
- end
33
- end