FIX: Make Oneboxer#apply insert block Oneboxes correctly (#11449)
It used to insert block Oneboxes inside paragraphs which resulted in
invalid HTML. This needed an additional parsing for removal of empty
paragraphs and the resulting HTML could still be invalid.
This commit ensure that block Oneboxes are inserted correctly, by
splitting the paragraph containing the link and putting the block
between the two. Paragraphs left with nothing but whitespaces will
be removed.
Follow up to 7f3a30d79f
.
This commit is contained in:
parent
a7cc17cff7
commit
2d51833ca9
|
@ -99,32 +99,57 @@ module Oneboxer
|
||||||
|
|
||||||
each_onebox_link(doc, extra_paths: extra_paths) do |url, element|
|
each_onebox_link(doc, extra_paths: extra_paths) do |url, element|
|
||||||
onebox, _ = yield(url, element)
|
onebox, _ = yield(url, element)
|
||||||
|
next if onebox.blank?
|
||||||
|
|
||||||
if onebox
|
parsed_onebox = Nokogiri::HTML5::fragment(onebox)
|
||||||
parsed_onebox = Nokogiri::HTML5::fragment(onebox)
|
next if parsed_onebox.children.blank?
|
||||||
next unless parsed_onebox.children.count > 0
|
|
||||||
|
|
||||||
if element&.parent&.node_name&.downcase == "p" &&
|
changed = true
|
||||||
element.parent.children.count == 1 &&
|
|
||||||
HTML5_BLOCK_ELEMENTS.include?(parsed_onebox.children[0].node_name.downcase)
|
parent = element.parent
|
||||||
element = element.parent
|
if parent&.node_name&.downcase == "p" &&
|
||||||
|
parsed_onebox.children.any? { |child| HTML5_BLOCK_ELEMENTS.include?(child.node_name.downcase) }
|
||||||
|
|
||||||
|
siblings = parent.children
|
||||||
|
element_idx = siblings.find_index(element)
|
||||||
|
before_idx = first_significant_element_index(siblings, element_idx - 1, -1)
|
||||||
|
after_idx = first_significant_element_index(siblings, element_idx + 1, +1)
|
||||||
|
|
||||||
|
if before_idx < 0 && after_idx >= siblings.size
|
||||||
|
parent.replace parsed_onebox
|
||||||
|
elsif before_idx < 0
|
||||||
|
parent.children = siblings[after_idx..siblings.size]
|
||||||
|
parent.add_previous_sibling(parsed_onebox)
|
||||||
|
elsif after_idx >= siblings.size
|
||||||
|
parent.children = siblings[0..before_idx]
|
||||||
|
parent.add_next_sibling(parsed_onebox)
|
||||||
|
else
|
||||||
|
parent_rest = parent.dup
|
||||||
|
|
||||||
|
parent.children = siblings[0..before_idx]
|
||||||
|
parent_rest.children = siblings[after_idx..siblings.size]
|
||||||
|
|
||||||
|
parent.add_next_sibling(parent_rest)
|
||||||
|
parent.add_next_sibling(parsed_onebox)
|
||||||
end
|
end
|
||||||
|
else
|
||||||
changed = true
|
element.replace parsed_onebox
|
||||||
element.swap parsed_onebox.to_html
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# strip empty <p> elements
|
|
||||||
doc.css("p").each do |p|
|
|
||||||
if p.children.empty? && doc.children.count > 1
|
|
||||||
p.remove
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Result.new(doc, changed)
|
Result.new(doc, changed)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.first_significant_element_index(elements, index, step)
|
||||||
|
while index >= 0 && index < elements.size &&
|
||||||
|
(elements[index].node_name.downcase == "br" ||
|
||||||
|
(elements[index].node_name.downcase == "text" && elements[index].to_html.strip.blank?))
|
||||||
|
index = index + step
|
||||||
|
end
|
||||||
|
|
||||||
|
index
|
||||||
|
end
|
||||||
|
|
||||||
def self.is_previewing?(user_id)
|
def self.is_previewing?(user_id)
|
||||||
Discourse.redis.get(preview_key(user_id)) == "1"
|
Discourse.redis.get(preview_key(user_id)) == "1"
|
||||||
end
|
end
|
||||||
|
|
|
@ -413,7 +413,7 @@ describe CookedPostProcessor do
|
||||||
it "generates overlay information" do
|
it "generates overlay information" do
|
||||||
cpp.post_process
|
cpp.post_process
|
||||||
|
|
||||||
expect(cpp.html).to match_html <<~HTML.rstrip
|
expect(cpp.html).to match_html <<~HTML
|
||||||
<p><div class="lightbox-wrapper"><a class="lightbox" href="//test.localhost#{upload.url}" data-download-href="//test.localhost/#{upload_path}/#{upload.sha1}" title="logo.png"><img src="//test.localhost/#{upload_path}/optimized/1X/#{upload.sha1}_#{OptimizedImage::VERSION}_690x788.png" width="690" height="788"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use xlink:href="#far-image"></use></svg><span class="filename">logo.png</span><span class="informations">1750×2000 1.21 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use xlink:href="#discourse-expand"></use></svg></div></a></div></p>
|
<p><div class="lightbox-wrapper"><a class="lightbox" href="//test.localhost#{upload.url}" data-download-href="//test.localhost/#{upload_path}/#{upload.sha1}" title="logo.png"><img src="//test.localhost/#{upload_path}/optimized/1X/#{upload.sha1}_#{OptimizedImage::VERSION}_690x788.png" width="690" height="788"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use xlink:href="#far-image"></use></svg><span class="filename">logo.png</span><span class="informations">1750×2000 1.21 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use xlink:href="#discourse-expand"></use></svg></div></a></div></p>
|
||||||
HTML
|
HTML
|
||||||
|
|
||||||
|
@ -433,7 +433,7 @@ describe CookedPostProcessor do
|
||||||
|
|
||||||
cpp.post_process
|
cpp.post_process
|
||||||
|
|
||||||
expect(cpp.html).to match_html <<~HTML.rstrip
|
expect(cpp.html).to match_html <<~HTML
|
||||||
<p><img class="onebox" src="//test.localhost/#{upload_path}/original/1X/1234567890123456.jpg" width="690" height="788"></p>
|
<p><img class="onebox" src="//test.localhost/#{upload_path}/original/1X/1234567890123456.jpg" width="690" height="788"></p>
|
||||||
HTML
|
HTML
|
||||||
end
|
end
|
||||||
|
@ -449,7 +449,7 @@ describe CookedPostProcessor do
|
||||||
|
|
||||||
cpp.post_process
|
cpp.post_process
|
||||||
|
|
||||||
expect(cpp.html).to match_html <<~HTML.rstrip
|
expect(cpp.html).to match_html <<~HTML
|
||||||
<p><img src="//test.localhost/#{upload_path}/original/1X/1234567890123456.svg" width="690" height="788"></p>
|
<p><img src="//test.localhost/#{upload_path}/original/1X/1234567890123456.svg" width="690" height="788"></p>
|
||||||
HTML
|
HTML
|
||||||
end
|
end
|
||||||
|
@ -576,7 +576,7 @@ describe CookedPostProcessor do
|
||||||
it "crops the image" do
|
it "crops the image" do
|
||||||
cpp.post_process
|
cpp.post_process
|
||||||
|
|
||||||
expect(cpp.html).to match_html <<~HTML.rstrip
|
expect(cpp.html).to match_html <<~HTML
|
||||||
<p><div class="lightbox-wrapper"><a class="lightbox" href="//test.localhost#{upload.url}" data-download-href="//test.localhost/#{upload_path}/#{upload.sha1}" title="logo.png"><img src="//test.localhost/#{upload_path}/optimized/1X/#{upload.sha1}_#{OptimizedImage::VERSION}_230x500.png" width="230" height="500"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use xlink:href="#far-image"></use></svg><span class="filename">logo.png</span><span class="informations">1125×2436 1.21 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use xlink:href="#discourse-expand"></use></svg></div></a></div></p>
|
<p><div class="lightbox-wrapper"><a class="lightbox" href="//test.localhost#{upload.url}" data-download-href="//test.localhost/#{upload_path}/#{upload.sha1}" title="logo.png"><img src="//test.localhost/#{upload_path}/optimized/1X/#{upload.sha1}_#{OptimizedImage::VERSION}_230x500.png" width="230" height="500"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use xlink:href="#far-image"></use></svg><span class="filename">logo.png</span><span class="informations">1125×2436 1.21 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use xlink:href="#discourse-expand"></use></svg></div></a></div></p>
|
||||||
HTML
|
HTML
|
||||||
|
|
||||||
|
@ -607,7 +607,7 @@ describe CookedPostProcessor do
|
||||||
it "generates overlay information" do
|
it "generates overlay information" do
|
||||||
cpp.post_process
|
cpp.post_process
|
||||||
|
|
||||||
expect(cpp.html). to match_html <<~HTML.rstrip
|
expect(cpp.html). to match_html <<~HTML
|
||||||
<p><div class="lightbox-wrapper"><a class="lightbox" href="//test.localhost/subfolder#{upload.url}" data-download-href="//test.localhost/subfolder/#{upload_path}/#{upload.sha1}" title="logo.png"><img src="//test.localhost/subfolder/#{upload_path}/optimized/1X/#{upload.sha1}_#{OptimizedImage::VERSION}_690x788.png" width="690" height="788"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use xlink:href="#far-image"></use></svg><span class="filename">logo.png</span><span class="informations">1750×2000 1.21 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use xlink:href="#discourse-expand"></use></svg></div></a></div></p>
|
<p><div class="lightbox-wrapper"><a class="lightbox" href="//test.localhost/subfolder#{upload.url}" data-download-href="//test.localhost/subfolder/#{upload_path}/#{upload.sha1}" title="logo.png"><img src="//test.localhost/subfolder/#{upload_path}/optimized/1X/#{upload.sha1}_#{OptimizedImage::VERSION}_690x788.png" width="690" height="788"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use xlink:href="#far-image"></use></svg><span class="filename">logo.png</span><span class="informations">1750×2000 1.21 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use xlink:href="#discourse-expand"></use></svg></div></a></div></p>
|
||||||
HTML
|
HTML
|
||||||
|
|
||||||
|
@ -618,7 +618,7 @@ describe CookedPostProcessor do
|
||||||
upload.update!(original_filename: "><img src=x onerror=alert('haha')>.png")
|
upload.update!(original_filename: "><img src=x onerror=alert('haha')>.png")
|
||||||
cpp.post_process
|
cpp.post_process
|
||||||
|
|
||||||
expect(cpp.html).to match_html <<~HTML.rstrip
|
expect(cpp.html).to match_html <<~HTML
|
||||||
<p><div class="lightbox-wrapper"><a class="lightbox" href="//test.localhost/subfolder#{upload.url}" data-download-href="//test.localhost/subfolder/#{upload_path}/#{upload.sha1}" title="&gt;&lt;img src=x onerror=alert(&#39;haha&#39;)&gt;.png"><img src="//test.localhost/subfolder/#{upload_path}/optimized/1X/#{upload.sha1}_#{OptimizedImage::VERSION}_690x788.png" width="690" height="788"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use xlink:href="#far-image"></use></svg><span class="filename">&gt;&lt;img src=x onerror=alert(&#39;haha&#39;)&gt;.png</span><span class="informations">1750×2000 1.21 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use xlink:href="#discourse-expand"></use></svg></div></a></div></p>
|
<p><div class="lightbox-wrapper"><a class="lightbox" href="//test.localhost/subfolder#{upload.url}" data-download-href="//test.localhost/subfolder/#{upload_path}/#{upload.sha1}" title="&gt;&lt;img src=x onerror=alert(&#39;haha&#39;)&gt;.png"><img src="//test.localhost/subfolder/#{upload_path}/optimized/1X/#{upload.sha1}_#{OptimizedImage::VERSION}_690x788.png" width="690" height="788"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use xlink:href="#far-image"></use></svg><span class="filename">&gt;&lt;img src=x onerror=alert(&#39;haha&#39;)&gt;.png</span><span class="informations">1750×2000 1.21 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use xlink:href="#discourse-expand"></use></svg></div></a></div></p>
|
||||||
HTML
|
HTML
|
||||||
end
|
end
|
||||||
|
@ -644,7 +644,7 @@ describe CookedPostProcessor do
|
||||||
it "generates overlay information using image title and ignores alt" do
|
it "generates overlay information using image title and ignores alt" do
|
||||||
cpp.post_process
|
cpp.post_process
|
||||||
|
|
||||||
expect(cpp.html).to match_html <<~HTML.rstrip
|
expect(cpp.html).to match_html <<~HTML
|
||||||
<p><div class="lightbox-wrapper"><a class="lightbox" href="//test.localhost#{upload.url}" data-download-href="//test.localhost/#{upload_path}/#{upload.sha1}" title="WAT"><img src="//test.localhost/#{upload_path}/optimized/1X/#{upload.sha1}_#{OptimizedImage::VERSION}_690x788.png" title="WAT" alt="RED" width="690" height="788"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use xlink:href="#far-image"></use></svg><span class="filename">WAT</span><span class="informations">1750×2000 1.21 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use xlink:href="#discourse-expand"></use></svg></div></a></div></p>
|
<p><div class="lightbox-wrapper"><a class="lightbox" href="//test.localhost#{upload.url}" data-download-href="//test.localhost/#{upload_path}/#{upload.sha1}" title="WAT"><img src="//test.localhost/#{upload_path}/optimized/1X/#{upload.sha1}_#{OptimizedImage::VERSION}_690x788.png" title="WAT" alt="RED" width="690" height="788"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use xlink:href="#far-image"></use></svg><span class="filename">WAT</span><span class="informations">1750×2000 1.21 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use xlink:href="#discourse-expand"></use></svg></div></a></div></p>
|
||||||
HTML
|
HTML
|
||||||
|
|
||||||
|
@ -672,7 +672,7 @@ describe CookedPostProcessor do
|
||||||
it "generates overlay information using image title" do
|
it "generates overlay information using image title" do
|
||||||
cpp.post_process
|
cpp.post_process
|
||||||
|
|
||||||
expect(cpp.html).to match_html <<~HTML.rstrip
|
expect(cpp.html).to match_html <<~HTML
|
||||||
<p><div class="lightbox-wrapper"><a class="lightbox" href="//test.localhost#{upload.url}" data-download-href="//test.localhost/#{upload_path}/#{upload.sha1}" title="WAT"><img src="//test.localhost/#{upload_path}/optimized/1X/#{upload.sha1}_#{OptimizedImage::VERSION}_690x788.png" title="WAT" width="690" height="788"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use xlink:href="#far-image"></use></svg><span class="filename">WAT</span><span class="informations">1750×2000 1.21 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use xlink:href="#discourse-expand"></use></svg></div></a></div></p>
|
<p><div class="lightbox-wrapper"><a class="lightbox" href="//test.localhost#{upload.url}" data-download-href="//test.localhost/#{upload_path}/#{upload.sha1}" title="WAT"><img src="//test.localhost/#{upload_path}/optimized/1X/#{upload.sha1}_#{OptimizedImage::VERSION}_690x788.png" title="WAT" width="690" height="788"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use xlink:href="#far-image"></use></svg><span class="filename">WAT</span><span class="informations">1750×2000 1.21 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use xlink:href="#discourse-expand"></use></svg></div></a></div></p>
|
||||||
HTML
|
HTML
|
||||||
|
|
||||||
|
@ -700,7 +700,7 @@ describe CookedPostProcessor do
|
||||||
it "generates overlay information using image alt" do
|
it "generates overlay information using image alt" do
|
||||||
cpp.post_process
|
cpp.post_process
|
||||||
|
|
||||||
expect(cpp.html).to match_html <<~HTML.rstrip
|
expect(cpp.html).to match_html <<~HTML
|
||||||
<p><div class="lightbox-wrapper"><a class="lightbox" href="//test.localhost#{upload.url}" data-download-href="//test.localhost/#{upload_path}/#{upload.sha1}" title="RED"><img src="//test.localhost/#{upload_path}/optimized/1X/#{upload.sha1}_#{OptimizedImage::VERSION}_690x788.png" alt="RED" width="690" height="788"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use xlink:href="#far-image"></use></svg><span class="filename">RED</span><span class="informations">1750×2000 1.21 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use xlink:href="#discourse-expand"></use></svg></div></a></div></p>
|
<p><div class="lightbox-wrapper"><a class="lightbox" href="//test.localhost#{upload.url}" data-download-href="//test.localhost/#{upload_path}/#{upload.sha1}" title="RED"><img src="//test.localhost/#{upload_path}/optimized/1X/#{upload.sha1}_#{OptimizedImage::VERSION}_690x788.png" alt="RED" width="690" height="788"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use xlink:href="#far-image"></use></svg><span class="filename">RED</span><span class="informations">1750×2000 1.21 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use xlink:href="#discourse-expand"></use></svg></div></a></div></p>
|
||||||
HTML
|
HTML
|
||||||
|
|
||||||
|
@ -1202,7 +1202,7 @@ describe CookedPostProcessor do
|
||||||
|
|
||||||
it "uses schemaless url for uploads" do
|
it "uses schemaless url for uploads" do
|
||||||
cpp.optimize_urls
|
cpp.optimize_urls
|
||||||
expect(cpp.html).to match_html <<~HTML.rstrip
|
expect(cpp.html).to match_html <<~HTML
|
||||||
<p><a href="//test.localhost/#{upload_path}/original/2X/2345678901234567.jpg">Link</a><br>
|
<p><a href="//test.localhost/#{upload_path}/original/2X/2345678901234567.jpg">Link</a><br>
|
||||||
<img src="//test.localhost/#{upload_path}/original/1X/1234567890123456.jpg"><br>
|
<img src="//test.localhost/#{upload_path}/original/1X/1234567890123456.jpg"><br>
|
||||||
<a href="http://www.google.com" rel="noopener nofollow ugc">Google</a><br>
|
<a href="http://www.google.com" rel="noopener nofollow ugc">Google</a><br>
|
||||||
|
@ -1217,7 +1217,7 @@ describe CookedPostProcessor do
|
||||||
it "uses schemaless CDN url for http uploads" do
|
it "uses schemaless CDN url for http uploads" do
|
||||||
Rails.configuration.action_controller.stubs(:asset_host).returns("http://my.cdn.com")
|
Rails.configuration.action_controller.stubs(:asset_host).returns("http://my.cdn.com")
|
||||||
cpp.optimize_urls
|
cpp.optimize_urls
|
||||||
expect(cpp.html).to match_html <<~HTML.rstrip
|
expect(cpp.html).to match_html <<~HTML
|
||||||
<p><a href="//my.cdn.com/#{upload_path}/original/2X/2345678901234567.jpg">Link</a><br>
|
<p><a href="//my.cdn.com/#{upload_path}/original/2X/2345678901234567.jpg">Link</a><br>
|
||||||
<img src="//my.cdn.com/#{upload_path}/original/1X/1234567890123456.jpg"><br>
|
<img src="//my.cdn.com/#{upload_path}/original/1X/1234567890123456.jpg"><br>
|
||||||
<a href="http://www.google.com" rel="noopener nofollow ugc">Google</a><br>
|
<a href="http://www.google.com" rel="noopener nofollow ugc">Google</a><br>
|
||||||
|
@ -1230,7 +1230,7 @@ describe CookedPostProcessor do
|
||||||
it "doesn't use schemaless CDN url for https uploads" do
|
it "doesn't use schemaless CDN url for https uploads" do
|
||||||
Rails.configuration.action_controller.stubs(:asset_host).returns("https://my.cdn.com")
|
Rails.configuration.action_controller.stubs(:asset_host).returns("https://my.cdn.com")
|
||||||
cpp.optimize_urls
|
cpp.optimize_urls
|
||||||
expect(cpp.html).to match_html <<~HTML.rstrip
|
expect(cpp.html).to match_html <<~HTML
|
||||||
<p><a href="https://my.cdn.com/#{upload_path}/original/2X/2345678901234567.jpg">Link</a><br>
|
<p><a href="https://my.cdn.com/#{upload_path}/original/2X/2345678901234567.jpg">Link</a><br>
|
||||||
<img src="https://my.cdn.com/#{upload_path}/original/1X/1234567890123456.jpg"><br>
|
<img src="https://my.cdn.com/#{upload_path}/original/1X/1234567890123456.jpg"><br>
|
||||||
<a href="http://www.google.com" rel="noopener nofollow ugc">Google</a><br>
|
<a href="http://www.google.com" rel="noopener nofollow ugc">Google</a><br>
|
||||||
|
@ -1244,7 +1244,7 @@ describe CookedPostProcessor do
|
||||||
SiteSetting.login_required = true
|
SiteSetting.login_required = true
|
||||||
Rails.configuration.action_controller.stubs(:asset_host).returns("http://my.cdn.com")
|
Rails.configuration.action_controller.stubs(:asset_host).returns("http://my.cdn.com")
|
||||||
cpp.optimize_urls
|
cpp.optimize_urls
|
||||||
expect(cpp.html).to match_html <<~HTML.rstrip
|
expect(cpp.html).to match_html <<~HTML
|
||||||
<p><a href="//my.cdn.com/#{upload_path}/original/2X/2345678901234567.jpg">Link</a><br>
|
<p><a href="//my.cdn.com/#{upload_path}/original/2X/2345678901234567.jpg">Link</a><br>
|
||||||
<img src="//my.cdn.com/#{upload_path}/original/1X/1234567890123456.jpg"><br>
|
<img src="//my.cdn.com/#{upload_path}/original/1X/1234567890123456.jpg"><br>
|
||||||
<a href="http://www.google.com" rel="noopener nofollow ugc">Google</a><br>
|
<a href="http://www.google.com" rel="noopener nofollow ugc">Google</a><br>
|
||||||
|
@ -1258,7 +1258,7 @@ describe CookedPostProcessor do
|
||||||
SiteSetting.prevent_anons_from_downloading_files = true
|
SiteSetting.prevent_anons_from_downloading_files = true
|
||||||
Rails.configuration.action_controller.stubs(:asset_host).returns("http://my.cdn.com")
|
Rails.configuration.action_controller.stubs(:asset_host).returns("http://my.cdn.com")
|
||||||
cpp.optimize_urls
|
cpp.optimize_urls
|
||||||
expect(cpp.html).to match_html <<~HTML.rstrip
|
expect(cpp.html).to match_html <<~HTML
|
||||||
<p><a href="//my.cdn.com/#{upload_path}/original/2X/2345678901234567.jpg">Link</a><br>
|
<p><a href="//my.cdn.com/#{upload_path}/original/2X/2345678901234567.jpg">Link</a><br>
|
||||||
<img src="//my.cdn.com/#{upload_path}/original/1X/1234567890123456.jpg"><br>
|
<img src="//my.cdn.com/#{upload_path}/original/1X/1234567890123456.jpg"><br>
|
||||||
<a href="http://www.google.com" rel="noopener nofollow ugc">Google</a><br>
|
<a href="http://www.google.com" rel="noopener nofollow ugc">Google</a><br>
|
||||||
|
@ -1297,7 +1297,7 @@ describe CookedPostProcessor do
|
||||||
cpp = CookedPostProcessor.new(the_post)
|
cpp = CookedPostProcessor.new(the_post)
|
||||||
cpp.optimize_urls
|
cpp.optimize_urls
|
||||||
|
|
||||||
expect(cpp.html).to match_html <<~HTML.rstrip
|
expect(cpp.html).to match_html <<~HTML
|
||||||
<p>This post has a local emoji <img src="https://local.cdn.com/images/emoji/twitter/+1.png?v=#{Emoji::EMOJI_VERSION}" title=":+1:" class="emoji" alt=":+1:"> and an external upload</p>
|
<p>This post has a local emoji <img src="https://local.cdn.com/images/emoji/twitter/+1.png?v=#{Emoji::EMOJI_VERSION}" title=":+1:" class="emoji" alt=":+1:"> and an external upload</p>
|
||||||
<p><img src="https://s3.cdn.com/#{stored_path}" alt="smallest.png" data-base62-sha1="#{upload.base62_sha1}" width="10" height="20"></p>
|
<p><img src="https://s3.cdn.com/#{stored_path}" alt="smallest.png" data-base62-sha1="#{upload.base62_sha1}" width="10" height="20"></p>
|
||||||
HTML
|
HTML
|
||||||
|
@ -1315,7 +1315,7 @@ describe CookedPostProcessor do
|
||||||
cpp = CookedPostProcessor.new(the_post)
|
cpp = CookedPostProcessor.new(the_post)
|
||||||
cpp.optimize_urls
|
cpp.optimize_urls
|
||||||
|
|
||||||
expect(cpp.html).to match_html <<~HTML.rstrip
|
expect(cpp.html).to match_html <<~HTML
|
||||||
<p>This post has a local emoji <img src="https://local.cdn.com/images/emoji/twitter/+1.png?v=#{Emoji::EMOJI_VERSION}" title=":+1:" class="emoji" alt=":+1:"> and an external upload</p>
|
<p>This post has a local emoji <img src="https://local.cdn.com/images/emoji/twitter/+1.png?v=#{Emoji::EMOJI_VERSION}" title=":+1:" class="emoji" alt=":+1:"> and an external upload</p>
|
||||||
<p><img src="/secure-media-uploads/#{stored_path}" alt="smallest.png" data-base62-sha1="#{upload.base62_sha1}" width="10" height="20"></p>
|
<p><img src="/secure-media-uploads/#{stored_path}" alt="smallest.png" data-base62-sha1="#{upload.base62_sha1}" width="10" height="20"></p>
|
||||||
HTML
|
HTML
|
||||||
|
@ -1339,17 +1339,14 @@ describe CookedPostProcessor do
|
||||||
cpp = CookedPostProcessor.new(the_post.reload)
|
cpp = CookedPostProcessor.new(the_post.reload)
|
||||||
cpp.post_process_oneboxes
|
cpp.post_process_oneboxes
|
||||||
|
|
||||||
cpp = CookedPostProcessor.new(the_post.reload)
|
|
||||||
cpp.post_process_oneboxes
|
|
||||||
|
|
||||||
expect(cpp.html).to match_html <<~HTML
|
expect(cpp.html).to match_html <<~HTML
|
||||||
<p>This post has an S3 video onebox:<br>
|
<p>This post has an S3 video onebox:</p>
|
||||||
</p><div class="onebox video-onebox">
|
<div class="onebox video-onebox">
|
||||||
<video width="100%" height="100%" controls="">
|
<video width="100%" height="100%" controls="">
|
||||||
<source src="#{video_upload.url}">
|
<source src="#{video_upload.url}">
|
||||||
<a href="#{video_upload.url}" rel="nofollow ugc noopener">#{video_upload.url}</a>
|
<a href="#{video_upload.url}" rel="nofollow ugc noopener">#{video_upload.url}</a>
|
||||||
</video>
|
</video>
|
||||||
</div>
|
</div>
|
||||||
HTML
|
HTML
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1365,15 +1362,13 @@ describe CookedPostProcessor do
|
||||||
|
|
||||||
secure_url = video_upload.url.sub(SiteSetting.s3_cdn_url, "#{Discourse.base_url}/secure-media-uploads")
|
secure_url = video_upload.url.sub(SiteSetting.s3_cdn_url, "#{Discourse.base_url}/secure-media-uploads")
|
||||||
|
|
||||||
expect(cpp.html).to match_html <<~HTML.rstrip
|
expect(cpp.html).to match_html <<~HTML
|
||||||
<p>This post has an S3 video onebox:<br>
|
<p>This post has an S3 video onebox:</p><div class="onebox video-onebox">
|
||||||
<div class="onebox video-onebox">
|
|
||||||
<video width="100%" height="100%" controls="">
|
<video width="100%" height="100%" controls="">
|
||||||
<source src="#{secure_url}">
|
<source src="#{secure_url}">
|
||||||
<a href="#{secure_url}">#{secure_url}</a>
|
<a href="#{secure_url}">#{secure_url}</a>
|
||||||
</video>
|
</video>
|
||||||
</div>
|
</div>
|
||||||
</p>
|
|
||||||
HTML
|
HTML
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1415,20 +1410,18 @@ describe CookedPostProcessor do
|
||||||
secure_video_url = video_upload.url.sub(SiteSetting.s3_cdn_url, "#{Discourse.base_url}/secure-media-uploads")
|
secure_video_url = video_upload.url.sub(SiteSetting.s3_cdn_url, "#{Discourse.base_url}/secure-media-uploads")
|
||||||
secure_audio_url = audio_upload.url.sub(SiteSetting.s3_cdn_url, "#{Discourse.base_url}/secure-media-uploads")
|
secure_audio_url = audio_upload.url.sub(SiteSetting.s3_cdn_url, "#{Discourse.base_url}/secure-media-uploads")
|
||||||
|
|
||||||
expect(cpp.html).to match_html <<~HTML.rstrip
|
expect(cpp.html).to match_html <<~HTML
|
||||||
<p>This post has a video upload.<br>
|
<p>This post has a video upload.</p>
|
||||||
<div class="onebox video-onebox">
|
<div class="onebox video-onebox">
|
||||||
<video width="100%" height="100%" controls="">
|
<video width="100%" height="100%" controls="">
|
||||||
<source src="#{secure_video_url}">
|
<source src="#{secure_video_url}">
|
||||||
<a href="#{secure_video_url}">#{secure_video_url}</a>
|
<a href="#{secure_video_url}">#{secure_video_url}</a>
|
||||||
</video>
|
</video>
|
||||||
</div>
|
</div>
|
||||||
</p>
|
|
||||||
<p>This post has an audio upload.<br>
|
<p>This post has an audio upload.<br>
|
||||||
<audio controls=""><source src="#{secure_audio_url}"><a href="#{secure_audio_url}">#{secure_audio_url}</a></audio></p>
|
<audio controls=""><source src="#{secure_audio_url}"><a href="#{secure_audio_url}">#{secure_audio_url}</a></audio></p>
|
||||||
<p>And an image upload.<br>
|
<p>And an image upload.<br>
|
||||||
<img src="#{image_upload.url}" alt="#{image_upload.original_filename}" data-base62-sha1="#{image_upload.base62_sha1}"></p>
|
<img src="#{image_upload.url}" alt="#{image_upload.original_filename}" data-base62-sha1="#{image_upload.base62_sha1}"></p>
|
||||||
|
|
||||||
HTML
|
HTML
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ describe ExcerptParser do
|
||||||
</details>
|
</details>
|
||||||
HTML
|
HTML
|
||||||
|
|
||||||
expect(ExcerptParser.get_excerpt(html, 50, {})).to match_html(<<~HTML.rstrip)
|
expect(ExcerptParser.get_excerpt(html, 50, {})).to match_html <<~HTML
|
||||||
<details><summary>FOO</summary>BAR
|
<details><summary>FOO</summary>BAR
|
||||||
Lorem ipsum dolor sit amet, consectetur adi…</details>
|
Lorem ipsum dolor sit amet, consectetur adi…</details>
|
||||||
HTML
|
HTML
|
||||||
|
|
|
@ -300,4 +300,27 @@ describe Oneboxer do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#apply' do
|
||||||
|
it 'generates valid HTML' do
|
||||||
|
raw = "Before Onebox\nhttps://example.com\nAfter Onebox"
|
||||||
|
cooked = Oneboxer.apply(PrettyText.cook(raw)) { '<div>onebox</div>' }
|
||||||
|
doc = Nokogiri::HTML5::fragment(cooked.to_html)
|
||||||
|
expect(doc.to_html).to match_html <<~HTML
|
||||||
|
<p>Before Onebox</p>
|
||||||
|
<div>onebox</div>
|
||||||
|
<p>After Onebox</p>
|
||||||
|
HTML
|
||||||
|
|
||||||
|
raw = "Before Onebox\nhttps://example.com\nhttps://example.com\nAfter Onebox"
|
||||||
|
cooked = Oneboxer.apply(PrettyText.cook(raw)) { '<div>onebox</div>' }
|
||||||
|
doc = Nokogiri::HTML5::fragment(cooked.to_html)
|
||||||
|
expect(doc.to_html).to match_html <<~HTML
|
||||||
|
<p>Before Onebox</p>
|
||||||
|
<div>onebox</div>
|
||||||
|
<div>onebox</div>
|
||||||
|
<p>After Onebox</p>
|
||||||
|
HTML
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -147,7 +147,7 @@ describe UsernameChanger do
|
||||||
post = create_post_and_change_username(raw: ".@foo -@foo %@foo _@foo ,@foo ;@foo @@foo")
|
post = create_post_and_change_username(raw: ".@foo -@foo %@foo _@foo ,@foo ;@foo @@foo")
|
||||||
|
|
||||||
expect(post.raw).to eq(".@bar -@bar %@bar _@bar ,@bar ;@bar @@bar")
|
expect(post.raw).to eq(".@bar -@bar %@bar _@bar ,@bar ;@bar @@bar")
|
||||||
expect(post.cooked).to match_html(<<~HTML.rstrip)
|
expect(post.cooked).to match_html <<~HTML
|
||||||
<p>.<a class="mention" href="/u/bar">@bar</a>
|
<p>.<a class="mention" href="/u/bar">@bar</a>
|
||||||
-<a class="mention" href="/u/bar">@bar</a>
|
-<a class="mention" href="/u/bar">@bar</a>
|
||||||
%<a class="mention" href="/u/bar">@bar</a>
|
%<a class="mention" href="/u/bar">@bar</a>
|
||||||
|
@ -169,7 +169,7 @@ describe UsernameChanger do
|
||||||
post = create_post_and_change_username(raw: "**@foo** *@foo* _@foo_ ~~@foo~~")
|
post = create_post_and_change_username(raw: "**@foo** *@foo* _@foo_ ~~@foo~~")
|
||||||
|
|
||||||
expect(post.raw).to eq("**@bar** *@bar* _@bar_ ~~@bar~~")
|
expect(post.raw).to eq("**@bar** *@bar* _@bar_ ~~@bar~~")
|
||||||
expect(post.cooked).to match_html(<<~HTML.rstrip)
|
expect(post.cooked).to match_html <<~HTML
|
||||||
<p><strong><a class="mention" href="/u/bar">@bar</a></strong>
|
<p><strong><a class="mention" href="/u/bar">@bar</a></strong>
|
||||||
<em><a class="mention" href="/u/bar">@bar</a></em>
|
<em><a class="mention" href="/u/bar">@bar</a></em>
|
||||||
<em><a class="mention" href="/u/bar">@bar</a></em>
|
<em><a class="mention" href="/u/bar">@bar</a></em>
|
||||||
|
@ -181,7 +181,7 @@ describe UsernameChanger do
|
||||||
post = create_post_and_change_username(raw: "@foo. @foo, @foo: @foo; @foo_ @foo-")
|
post = create_post_and_change_username(raw: "@foo. @foo, @foo: @foo; @foo_ @foo-")
|
||||||
|
|
||||||
expect(post.raw).to eq("@bar. @bar, @bar: @bar; @bar_ @bar-")
|
expect(post.raw).to eq("@bar. @bar, @bar: @bar; @bar_ @bar-")
|
||||||
expect(post.cooked).to match_html(<<~HTML.rstrip)
|
expect(post.cooked).to match_html <<~HTML
|
||||||
<p><a class="mention" href="/u/bar">@bar</a>.
|
<p><a class="mention" href="/u/bar">@bar</a>.
|
||||||
<a class="mention" href="/u/bar">@bar</a>,
|
<a class="mention" href="/u/bar">@bar</a>,
|
||||||
<a class="mention" href="/u/bar">@bar</a>:
|
<a class="mention" href="/u/bar">@bar</a>:
|
||||||
|
@ -225,7 +225,7 @@ describe UsernameChanger do
|
||||||
post = create_post_and_change_username(raw: "@foo @foobar @foo-bar @foo_bar @foo1")
|
post = create_post_and_change_username(raw: "@foo @foobar @foo-bar @foo_bar @foo1")
|
||||||
|
|
||||||
expect(post.raw).to eq("@bar @foobar @foo-bar @foo_bar @foo1")
|
expect(post.raw).to eq("@bar @foobar @foo-bar @foo_bar @foo1")
|
||||||
expect(post.cooked).to match_html(<<~HTML.rstrip)
|
expect(post.cooked).to match_html <<~HTML
|
||||||
<p><a class="mention" href="/u/bar">@bar</a> <a class="mention" href="/u/foobar">@foobar</a> <a class="mention" href="/u/foo-bar">@foo-bar</a> <a class="mention" href="/u/foo_bar">@foo_bar</a> <a class="mention" href="/u/foo1">@foo1</a></p>
|
<p><a class="mention" href="/u/bar">@bar</a> <a class="mention" href="/u/foobar">@foobar</a> <a class="mention" href="/u/foo-bar">@foo-bar</a> <a class="mention" href="/u/foo_bar">@foo_bar</a> <a class="mention" href="/u/foo1">@foo1</a></p>
|
||||||
HTML
|
HTML
|
||||||
end
|
end
|
||||||
|
@ -312,7 +312,7 @@ describe UsernameChanger do
|
||||||
post = create_post_and_change_username(raw: "@թռչուն @թռչուն鳥 @թռչուն-鳥 @թռչուն_鳥 @թռչուն٩", target_username: 'птица')
|
post = create_post_and_change_username(raw: "@թռչուն @թռչուն鳥 @թռչուն-鳥 @թռչուն_鳥 @թռչուն٩", target_username: 'птица')
|
||||||
|
|
||||||
expect(post.raw).to eq("@птица @թռչուն鳥 @թռչուն-鳥 @թռչուն_鳥 @թռչուն٩")
|
expect(post.raw).to eq("@птица @թռչուն鳥 @թռչուն-鳥 @թռչուն_鳥 @թռչուն٩")
|
||||||
expect(post.cooked).to match_html(<<~HTML.rstrip)
|
expect(post.cooked).to match_html <<~HTML
|
||||||
<p><a class="mention" href="/u/%D0%BF%D1%82%D0%B8%D1%86%D0%B0">@птица</a> <a class="mention" href="/u/%D5%A9%D5%BC%D5%B9%D5%B8%D6%82%D5%B6%E9%B3%A5">@թռչուն鳥</a> <a class="mention" href="/u/%D5%A9%D5%BC%D5%B9%D5%B8%D6%82%D5%B6-%E9%B3%A5">@թռչուն-鳥</a> <a class="mention" href="/u/%D5%A9%D5%BC%D5%B9%D5%B8%D6%82%D5%B6_%E9%B3%A5">@թռչուն_鳥</a> <a class="mention" href="/u/%D5%A9%D5%BC%D5%B9%D5%B8%D6%82%D5%B6%D9%A9">@թռչուն٩</a></p>
|
<p><a class="mention" href="/u/%D0%BF%D1%82%D0%B8%D1%86%D0%B0">@птица</a> <a class="mention" href="/u/%D5%A9%D5%BC%D5%B9%D5%B8%D6%82%D5%B6%E9%B3%A5">@թռչուն鳥</a> <a class="mention" href="/u/%D5%A9%D5%BC%D5%B9%D5%B8%D6%82%D5%B6-%E9%B3%A5">@թռչուն-鳥</a> <a class="mention" href="/u/%D5%A9%D5%BC%D5%B9%D5%B8%D6%82%D5%B6_%E9%B3%A5">@թռչուն_鳥</a> <a class="mention" href="/u/%D5%A9%D5%BC%D5%B9%D5%B8%D6%82%D5%B6%D9%A9">@թռչուն٩</a></p>
|
||||||
HTML
|
HTML
|
||||||
end
|
end
|
||||||
|
@ -361,7 +361,7 @@ describe UsernameChanger do
|
||||||
dolor sit amet
|
dolor sit amet
|
||||||
RAW
|
RAW
|
||||||
|
|
||||||
expect(post.cooked).to match_html(<<~HTML.rstrip)
|
expect(post.cooked).to match_html <<~HTML
|
||||||
<p>Lorem ipsum</p>
|
<p>Lorem ipsum</p>
|
||||||
<aside class="quote no-group" data-username="bar" data-post="1" data-topic="#{quoted_post.topic.id}">
|
<aside class="quote no-group" data-username="bar" data-post="1" data-topic="#{quoted_post.topic.id}">
|
||||||
<div class="title">
|
<div class="title">
|
||||||
|
@ -455,9 +455,8 @@ describe UsernameChanger do
|
||||||
post = create_post_and_change_username(raw: raw)
|
post = create_post_and_change_username(raw: raw)
|
||||||
|
|
||||||
expect(post.raw).to eq(raw)
|
expect(post.raw).to eq(raw)
|
||||||
|
expect(post.cooked).to match_html <<~HTML
|
||||||
expect(post.cooked).to match_html(<<~HTML.rstrip)
|
<aside class="quote" data-post="#{quoted_post.post_number}" data-topic="#{quoted_post.topic.id}">
|
||||||
<p><aside class="quote" data-post="#{quoted_post.post_number}" data-topic="#{quoted_post.topic.id}">
|
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<div class="quote-controls"></div>
|
<div class="quote-controls"></div>
|
||||||
<img alt="" width="20" height="20" src="#{avatar_url}" class="avatar">
|
<img alt="" width="20" height="20" src="#{avatar_url}" class="avatar">
|
||||||
|
@ -467,7 +466,7 @@ describe UsernameChanger do
|
||||||
quoted post
|
quoted post
|
||||||
</blockquote>
|
</blockquote>
|
||||||
</aside>
|
</aside>
|
||||||
<br>
|
|
||||||
<aside class="quote" data-post="#{quoted_post.post_number}" data-topic="#{quoted_post.topic.id}">
|
<aside class="quote" data-post="#{quoted_post.post_number}" data-topic="#{quoted_post.topic.id}">
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<div class="quote-controls"></div>
|
<div class="quote-controls"></div>
|
||||||
|
@ -478,7 +477,6 @@ describe UsernameChanger do
|
||||||
quoted post
|
quoted post
|
||||||
</blockquote>
|
</blockquote>
|
||||||
</aside>
|
</aside>
|
||||||
</p>
|
|
||||||
HTML
|
HTML
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -487,9 +485,8 @@ describe UsernameChanger do
|
||||||
post = create_post_and_change_username(raw: raw)
|
post = create_post_and_change_username(raw: raw)
|
||||||
|
|
||||||
expect(post.raw).to eq(raw)
|
expect(post.raw).to eq(raw)
|
||||||
|
expect(post.cooked).to match_html <<~HTML
|
||||||
expect(post.cooked).to match_html(<<~HTML.rstrip)
|
<aside class="quote" data-post="#{quoted_post.post_number}" data-topic="#{quoted_post.topic.id}">
|
||||||
<p><aside class="quote" data-post="#{quoted_post.post_number}" data-topic="#{quoted_post.topic.id}">
|
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<div class="quote-controls"></div>
|
<div class="quote-controls"></div>
|
||||||
<img alt="" width="20" height="20" src="#{avatar_url}" class="avatar">
|
<img alt="" width="20" height="20" src="#{avatar_url}" class="avatar">
|
||||||
|
@ -499,7 +496,7 @@ describe UsernameChanger do
|
||||||
quoted post
|
quoted post
|
||||||
</blockquote>
|
</blockquote>
|
||||||
</aside>
|
</aside>
|
||||||
<br>
|
|
||||||
<aside class="quote" data-post="#{another_quoted_post.post_number}" data-topic="#{another_quoted_post.topic.id}">
|
<aside class="quote" data-post="#{another_quoted_post.post_number}" data-topic="#{another_quoted_post.topic.id}">
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<div class="quote-controls"></div>
|
<div class="quote-controls"></div>
|
||||||
|
@ -510,7 +507,6 @@ describe UsernameChanger do
|
||||||
evil post
|
evil post
|
||||||
</blockquote>
|
</blockquote>
|
||||||
</aside>
|
</aside>
|
||||||
</p>
|
|
||||||
HTML
|
HTML
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,9 +3,7 @@
|
||||||
require 'nokogiri/xml/parse_options'
|
require 'nokogiri/xml/parse_options'
|
||||||
RSpec::Matchers.define :match_html do |expected|
|
RSpec::Matchers.define :match_html do |expected|
|
||||||
match do |actual|
|
match do |actual|
|
||||||
a = make_canonical_html(expected).to_html.gsub(/\s+/, " ").strip
|
make_canonical_html(expected).eql? make_canonical_html(actual)
|
||||||
b = make_canonical_html(actual).to_html.gsub(/\s+/, " ").strip
|
|
||||||
a.eql? b
|
|
||||||
end
|
end
|
||||||
|
|
||||||
failure_message do |actual|
|
failure_message do |actual|
|
||||||
|
@ -17,7 +15,17 @@ RSpec::Matchers.define :match_html do |expected|
|
||||||
end
|
end
|
||||||
|
|
||||||
def make_canonical_html(html)
|
def make_canonical_html(html)
|
||||||
Nokogiri::HTML5(html) { |config| config[:options] = Nokogiri::XML::ParseOptions::NOBLANKS | Nokogiri::XML::ParseOptions::COMPACT }
|
doc = Nokogiri::HTML5(html) do |config|
|
||||||
|
config[:options] = Nokogiri::XML::ParseOptions::NOBLANKS | Nokogiri::XML::ParseOptions::COMPACT
|
||||||
|
end
|
||||||
|
|
||||||
|
doc.traverse do |node|
|
||||||
|
if node.node_name&.downcase == "text"
|
||||||
|
node.content = node.content.gsub(/\s+/, ' ').strip
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
doc.to_html
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue