add layout for notifications

This commit is contained in:
scossar
2016-01-19 16:42:56 -08:00
parent 6a7bdfecc8
commit 3f09ec2aca
4 changed files with 122 additions and 68 deletions

View File

@ -5,6 +5,7 @@ require_dependency 'age_words'
class UserNotifications < ActionMailer::Base class UserNotifications < ActionMailer::Base
helper :application helper :application
default charset: 'UTF-8' default charset: 'UTF-8'
layout 'user_notifications'
include Email::BuildEmailHelper include Email::BuildEmailHelper
@ -290,6 +291,7 @@ class UserNotifications < ActionMailer::Base
else else
html = UserNotificationRenderer.new(Rails.configuration.paths["app/views"]).render( html = UserNotificationRenderer.new(Rails.configuration.paths["app/views"]).render(
template: 'email/notification', template: 'email/notification',
layout: 'layouts/user_notifications',
format: :html, format: :html,
locals: { context_posts: context_posts, locals: { context_posts: context_posts,
post: post, post: post,

View File

@ -0,0 +1,42 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<!--[if !mso]><!-->
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<!--<![endif]-->
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title></title>
<!--[if (gte mso 9)|(IE)]>
<style type="text/css">
table {border-collapse: collapse;}
</style>
<![endif]-->
</head>
<body style="Margin: 0;padding:0;min-width:100%;background-color:#ffffff">
<center class="wrapper" style="width:100%;table-layout:fixed;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;">
<div class="webkit" style="max-width:600px">
<!--[if (gte mso 9)|(IE)]>
<table width="600" align="center" cellspacing="0" cellpadding="0" border="0">
<tr>
<td>
<![endif]-->
<table class="outer" align="center" cellspacing="0" cellpadding="0" border="0" style="Margin: 0 auto; width: 100%; max-width: 600px">
<tr>
<td style="padding: 10px;">
<%= yield %>
</td>
</tr>
</table>
<!--[if (gte mso 9)|(IE)]>
</td>
</tr>
</table>
<![endif]-->
</div>
</center>
</body>
</html>

View File

@ -9,7 +9,7 @@ module Email
def initialize(html, opts=nil) def initialize(html, opts=nil)
@html = html @html = html
@opts = opts || {} @opts = opts || {}
@fragment = Nokogiri::HTML.fragment(@html) @doc = Nokogiri::HTML(@html)
end end
def self.register_plugin_style(&block) def self.register_plugin_style(&block)
@ -30,7 +30,7 @@ module Email
uri = URI(Discourse.base_url) uri = URI(Discourse.base_url)
# images # images
@fragment.css('img').each do |img| @doc.css('img').each do |img|
next if img['class'] == 'site-logo' next if img['class'] == 'site-logo'
if img['class'] == "emoji" || img['src'] =~ /plugins\/emoji/ if img['class'] == "emoji" || img['src'] =~ /plugins\/emoji/
@ -56,15 +56,16 @@ module Email
end end
# add max-width to big images # add max-width to big images
big_images = @fragment.css('img[width="auto"][height="auto"]') - big_images = @doc.css('img[width="auto"][height="auto"]') -
@fragment.css('aside.onebox img') - @doc.css('aside.onebox img') -
@fragment.css('img.site-logo, img.emoji') @doc.css('img.site-logo, img.emoji')
big_images.each do |img| big_images.each do |img|
add_styles(img, 'max-width: 100%;') if img['style'] !~ /max-width/ add_styles(img, 'width: 100%; height: auto; max-width: 600px;') if img['style'] !~ /max-width/
add_attributes(img, width: '600')
end end
# attachments # attachments
@fragment.css('a.attachment').each do |a| @doc.css('a.attachment').each do |a|
# ensure all urls are absolute # ensure all urls are absolute
if a['href'] =~ /^\/[^\/]/ if a['href'] =~ /^\/[^\/]/
a['href'] = "#{Discourse.base_url}#{a['href']}" a['href'] = "#{Discourse.base_url}#{a['href']}"
@ -117,12 +118,12 @@ module Email
style('aside.clearfix', "clear: both") style('aside.clearfix', "clear: both")
# Finally, convert all `aside` tags to `div`s # Finally, convert all `aside` tags to `div`s
@fragment.css('aside, article, header').each do |n| @doc.css('aside, article, header').each do |n|
n.name = "div" n.name = "div"
end end
# iframes can't go in emails, so replace them with clickable links # iframes can't go in emails, so replace them with clickable links
@fragment.css('iframe').each do |i| @doc.css('iframe').each do |i|
begin begin
src_uri = URI(i['src']) src_uri = URI(i['src'])
@ -157,20 +158,21 @@ module Email
# this method is reserved for styles specific to plugin # this method is reserved for styles specific to plugin
def plugin_styles def plugin_styles
@@plugin_callbacks.each { |block| block.call(@fragment, @opts) } @@plugin_callbacks.each { |block| block.call(@doc, @opts) }
end end
def to_html def to_html
return "" unless has_body?
strip_classes_and_ids strip_classes_and_ids
replace_relative_urls replace_relative_urls
@fragment.to_html.tap do |result| @doc.to_html.tap do |result|
result.gsub!(/\[email-indent\]/, "<div style='margin-left: 15px'>") result.gsub!(/\[email-indent\]/, "<div style='margin-left: 15px'>")
result.gsub!(/\[\/email-indent\]/, "</div>") result.gsub!(/\[\/email-indent\]/, "</div>")
end end
end end
def strip_avatars_and_emojis def strip_avatars_and_emojis
@fragment.search('img').each do |img| @doc.search('img').each do |img|
if img['src'] =~ /_avatar/ if img['src'] =~ /_avatar/
img.parent['style'] = "vertical-align: top;" if img.parent.name == 'td' img.parent['style'] = "vertical-align: top;" if img.parent.name == 'td'
img.remove img.remove
@ -182,7 +184,7 @@ module Email
end end
end end
@fragment.to_s @doc.to_s
end end
private private
@ -192,7 +194,7 @@ module Email
host = forum_uri.host host = forum_uri.host
scheme = forum_uri.scheme scheme = forum_uri.scheme
@fragment.css('[href]').each do |element| @doc.css('[href]').each do |element|
href = element['href'] href = element['href']
if href =~ /^\/\/#{host}/ if href =~ /^\/\/#{host}/
element['href'] = "#{scheme}:#{href}" element['href'] = "#{scheme}:#{href}"
@ -201,14 +203,14 @@ module Email
end end
def correct_first_body_margin def correct_first_body_margin
@fragment.css('.body p').each do |element| @doc.css('.body p').each do |element|
element['style'] = "margin-top:0; border: 0;" element['style'] = "margin-top:0; border: 0;"
end end
end end
def correct_footer_style def correct_footer_style
footernum = 0 footernum = 0
@fragment.css('.footer').each do |element| @doc.css('.footer').each do |element|
element['style'] = "color:#666;" element['style'] = "color:#666;"
linknum = 0 linknum = 0
element.css('a').each do |inner| element.css('a').each do |inner|
@ -225,7 +227,7 @@ module Email
end end
def strip_classes_and_ids def strip_classes_and_ids
@fragment.css('*').each do |element| @doc.css('*').each do |element|
element.delete('class') element.delete('class')
element.delete('id') element.delete('id')
end end
@ -235,13 +237,21 @@ module Email
style('table', nil, cellspacing: '0', cellpadding: '0', border: '0') style('table', nil, cellspacing: '0', cellpadding: '0', border: '0')
end end
def style(selector, style, attribs = {}) def add_attributes(element, attribs)
@fragment.css(selector).each do |element|
add_styles(element, style) if style
attribs.each do |k, v| attribs.each do |k, v|
element[k] = v element[k] = v
end end
end end
def has_body?
@doc.at_css('body')
end
def style(selector, style, attribs = {})
@doc.css(selector).each do |element|
add_styles(element, style) if style
add_attributes(element, attribs)
end
end end
end end
end end

View File

@ -3,17 +3,17 @@ require 'email'
describe Email::Styles do describe Email::Styles do
def basic_fragment(html) def basic_doc(html)
styler = Email::Styles.new(html) styler = Email::Styles.new(html)
styler.format_basic styler.format_basic
Nokogiri::HTML.fragment(styler.to_html) Nokogiri::HTML(styler.to_html)
end end
def html_fragment(html) def html_doc(html)
styler = Email::Styles.new(html) styler = Email::Styles.new(html)
styler.format_basic styler.format_basic
styler.format_html styler.format_html
Nokogiri::HTML.fragment(styler.to_html) Nokogiri::HTML(styler.to_html)
end end
context "basic formatter" do context "basic formatter" do
@ -26,24 +26,24 @@ describe Email::Styles do
# Pending due to email effort @coding-horror made in d2fb2bc4c # Pending due to email effort @coding-horror made in d2fb2bc4c
skip "adds a max-width to images" do skip "adds a max-width to images" do
frag = basic_fragment("<img src='gigantic.jpg'>") doc = basic_doc("<img src='gigantic.jpg'>")
expect(frag.at("img")["style"]).to match("max-width") expect(doc.at("img")["style"]).to match("max-width")
end end
it "adds a width and height to images with an emoji path" do it "adds a width and height to images with an emoji path" do
frag = basic_fragment("<img src='/images/emoji/fish.png' class='emoji'>") doc = basic_doc("<img src='/images/emoji/fish.png' class='emoji'>")
expect(frag.at("img")["width"]).to eq("20") expect(doc.at("img")["width"]).to eq("20")
expect(frag.at("img")["height"]).to eq("20") expect(doc.at("img")["height"]).to eq("20")
end end
it "converts relative paths to absolute paths" do it "converts relative paths to absolute paths" do
frag = basic_fragment("<img src='/some-image.png'>") doc = basic_doc("<img src='/some-image.png'>")
expect(frag.at("img")["src"]).to eq("#{Discourse.base_url}/some-image.png") expect(doc.at("img")["src"]).to eq("#{Discourse.base_url}/some-image.png")
end end
it "strips classes and ids" do it "strips classes and ids" do
frag = basic_fragment("<div class='foo' id='bar'><div class='foo' id='bar'></div></div>") doc = basic_doc("<div class='foo' id='bar'><div class='foo' id='bar'></div></div>")
expect(frag.to_html).to eq("<div><div></div></div>") expect(doc.to_html).to match(/<div><div><\/div><\/div>/)
end end
end end
@ -56,51 +56,51 @@ describe Email::Styles do
end end
it "attaches a style to h3 tags" do it "attaches a style to h3 tags" do
frag = html_fragment("<h3>hello</h3>") doc = html_doc("<h3>hello</h3>")
expect(frag.at('h3')['style']).to be_present expect(doc.at('h3')['style']).to be_present
end end
it "attaches a style to hr tags" do it "attaches a style to hr tags" do
frag = html_fragment("hello<hr>") doc = html_doc("hello<hr>")
expect(frag.at('hr')['style']).to be_present expect(doc.at('hr')['style']).to be_present
end end
it "attaches a style to a tags" do it "attaches a style to a tags" do
frag = html_fragment("<a href>wat</a>") doc = html_doc("<a href>wat</a>")
expect(frag.at('a')['style']).to be_present expect(doc.at('a')['style']).to be_present
end end
it "attaches a style to a tags" do it "attaches a style to a tags" do
frag = html_fragment("<a href>wat</a>") doc = html_doc("<a href>wat</a>")
expect(frag.at('a')['style']).to be_present expect(doc.at('a')['style']).to be_present
end end
it "attaches a style to ul and li tags" do it "attaches a style to ul and li tags" do
frag = html_fragment("<ul><li>hello</li></ul>") doc = html_doc("<ul><li>hello</li></ul>")
expect(frag.at('ul')['style']).to be_present expect(doc.at('ul')['style']).to be_present
expect(frag.at('li')['style']).to be_present expect(doc.at('li')['style']).to be_present
end end
it "converts iframes to links" do it "converts iframes to links" do
iframe_url = "http://www.youtube.com/embed/7twifrxOTQY?feature=oembed&wmode=opaque" iframe_url = "http://www.youtube.com/embed/7twifrxOTQY?feature=oembed&wmode=opaque"
frag = html_fragment("<iframe src=\"#{iframe_url}\"></iframe>") doc = html_doc("<iframe src=\"#{iframe_url}\"></iframe>")
expect(frag.at('iframe')).to be_blank expect(doc.at('iframe')).to be_blank
expect(frag.at('a')).to be_present expect(doc.at('a')).to be_present
expect(frag.at('a')['href']).to eq(iframe_url) expect(doc.at('a')['href']).to eq(iframe_url)
end end
it "won't allow non URLs in iframe src, strips them with no link" do it "won't allow non URLs in iframe src, strips them with no link" do
iframe_url = "alert('xss hole')" iframe_url = "alert('xss hole')"
frag = html_fragment("<iframe src=\"#{iframe_url}\"></iframe>") doc = html_doc("<iframe src=\"#{iframe_url}\"></iframe>")
expect(frag.at('iframe')).to be_blank expect(doc.at('iframe')).to be_blank
expect(frag.at('a')).to be_blank expect(doc.at('a')).to be_blank
end end
end end
context "rewriting protocol relative URLs to the forum" do context "rewriting protocol relative URLs to the forum" do
it "doesn't rewrite a url to another site" do it "doesn't rewrite a url to another site" do
frag = html_fragment('<a href="//youtube.com/discourse">hello</a>') doc = html_doc('<a href="//youtube.com/discourse">hello</a>')
expect(frag.at('a')['href']).to eq("//youtube.com/discourse") expect(doc.at('a')['href']).to eq("//youtube.com/discourse")
end end
context "without https" do context "without https" do
@ -109,18 +109,18 @@ describe Email::Styles do
end end
it "rewrites the href to have http" do it "rewrites the href to have http" do
frag = html_fragment('<a href="//test.localhost/discourse">hello</a>') doc = html_doc('<a href="//test.localhost/discourse">hello</a>')
expect(frag.at('a')['href']).to eq("http://test.localhost/discourse") expect(doc.at('a')['href']).to eq("http://test.localhost/discourse")
end end
it "rewrites the href for attachment files to have http" do it "rewrites the href for attachment files to have http" do
frag = html_fragment('<a class="attachment" href="//try-discourse.global.ssl.fastly.net/uploads/default/368/40b610b0aa90cfcf.txt">attachment_file.txt</a>') doc = html_doc('<a class="attachment" href="//try-discourse.global.ssl.fastly.net/uploads/default/368/40b610b0aa90cfcf.txt">attachment_file.txt</a>')
expect(frag.at('a')['href']).to eq("http://try-discourse.global.ssl.fastly.net/uploads/default/368/40b610b0aa90cfcf.txt") expect(doc.at('a')['href']).to eq("http://try-discourse.global.ssl.fastly.net/uploads/default/368/40b610b0aa90cfcf.txt")
end end
it "rewrites the src to have http" do it "rewrites the src to have http" do
frag = html_fragment('<img src="//test.localhost/blah.jpg">') doc = html_doc('<img src="//test.localhost/blah.jpg">')
expect(frag.at('img')['src']).to eq("http://test.localhost/blah.jpg") expect(doc.at('img')['src']).to eq("http://test.localhost/blah.jpg")
end end
end end
@ -130,18 +130,18 @@ describe Email::Styles do
end end
it "rewrites the forum URL to have https" do it "rewrites the forum URL to have https" do
frag = html_fragment('<a href="//test.localhost/discourse">hello</a>') doc = html_doc('<a href="//test.localhost/discourse">hello</a>')
expect(frag.at('a')['href']).to eq("https://test.localhost/discourse") expect(doc.at('a')['href']).to eq("https://test.localhost/discourse")
end end
it "rewrites the href for attachment files to have https" do it "rewrites the href for attachment files to have https" do
frag = html_fragment('<a class="attachment" href="//try-discourse.global.ssl.fastly.net/uploads/default/368/40b610b0aa90cfcf.txt">attachment_file.txt</a>') doc = html_doc('<a class="attachment" href="//try-discourse.global.ssl.fastly.net/uploads/default/368/40b610b0aa90cfcf.txt">attachment_file.txt</a>')
expect(frag.at('a')['href']).to eq("https://try-discourse.global.ssl.fastly.net/uploads/default/368/40b610b0aa90cfcf.txt") expect(doc.at('a')['href']).to eq("https://try-discourse.global.ssl.fastly.net/uploads/default/368/40b610b0aa90cfcf.txt")
end end
it "rewrites the src to have https" do it "rewrites the src to have https" do
frag = html_fragment('<img src="//test.localhost/blah.jpg">') doc = html_doc('<img src="//test.localhost/blah.jpg">')
expect(frag.at('img')['src']).to eq("https://test.localhost/blah.jpg") expect(doc.at('img')['src']).to eq("https://test.localhost/blah.jpg")
end end
end end
@ -152,14 +152,14 @@ describe Email::Styles do
emoji = "<img src='/images/emoji/emoji_one/crying_cat_face.png'>" emoji = "<img src='/images/emoji/emoji_one/crying_cat_face.png'>"
style = Email::Styles.new(emoji) style = Email::Styles.new(emoji)
style.strip_avatars_and_emojis style.strip_avatars_and_emojis
expect(style.to_html).to match_html(emoji) expect(style.to_html).to match_html("<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\"><html><body>#{emoji}</body></html>")
end end
it "works for lonesome emoji with title" do it "works for lonesome emoji with title" do
emoji = "<img title='cry_cry' src='/images/emoji/emoji_one/crying_cat_face.png'>" emoji = "<img title='cry_cry' src='/images/emoji/emoji_one/crying_cat_face.png'>"
style = Email::Styles.new(emoji) style = Email::Styles.new(emoji)
style.strip_avatars_and_emojis style.strip_avatars_and_emojis
expect(style.to_html).to match_html("cry_cry") expect(style.to_html).to match_html("<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\"><html><body>cry_cry</body></html>")
end end
end end