clean up onebox application so it uses a single code path

use fragments for oneboxes
strip parent <p> if <div> is in it
clean some tests
This commit is contained in:
Sam 2013-04-10 17:52:38 +10:00
parent ab9c55689e
commit 33e3ad1603
6 changed files with 90 additions and 29 deletions

View file

@ -243,15 +243,11 @@ class Post < ActiveRecord::Base
# If we have any of the oneboxes in the cache, throw them in right away, don't # If we have any of the oneboxes in the cache, throw them in right away, don't
# wait for the post processor. # wait for the post processor.
dirty = false dirty = false
doc = Oneboxer.each_onebox_link(cooked) do |url, elem| result = Oneboxer.apply(cooked) do |url, elem|
cached = Oneboxer.render_from_cache(url) Oneboxer.render_from_cache(url)
if cached.present?
elem.swap(cached)
dirty = true
end
end end
cooked = doc.to_html if dirty cooked = result.to_html if result.changed?
cooked cooked
end end

View file

@ -10,7 +10,7 @@ class CookedPostProcessor
@dirty = false @dirty = false
@opts = opts @opts = opts
@post = post @post = post
@doc = Nokogiri::HTML(post.cooked) @doc = Nokogiri::HTML::fragment(post.cooked)
@size_cache = {} @size_cache = {}
end end
@ -23,13 +23,10 @@ class CookedPostProcessor
args = {post_id: @post.id} args = {post_id: @post.id}
args[:invalidate_oneboxes] = true if @opts[:invalidate_oneboxes] args[:invalidate_oneboxes] = true if @opts[:invalidate_oneboxes]
Oneboxer.each_onebox_link(@doc) do |url, element| result = Oneboxer.apply(@doc) do |url, element|
onebox, preview = Oneboxer.onebox(url, args) Oneboxer.onebox(url, args)
if onebox
element.swap onebox
@dirty = true
end
end end
@dirty ||= result.changed?
end end
# First let's consider the images # First let's consider the images
@ -127,6 +124,10 @@ class CookedPostProcessor
@doc.try(:to_html) @doc.try(:to_html)
end end
def doc
@doc
end
def get_size(url) def get_size(url)
return nil unless SiteSetting.crawl_images? || url.start_with?(Discourse.base_url) return nil unless SiteSetting.crawl_images? || url.start_with?(Discourse.base_url)
@size_cache[url] ||= FastImage.size(url) @size_cache[url] ||= FastImage.size(url)

View file

@ -10,6 +10,16 @@ Dir["#{Rails.root}/lib/oneboxer/*_onebox.rb"].each {|f|
module Oneboxer module Oneboxer
extend Oneboxer::Base extend Oneboxer::Base
Result = Struct.new(:doc, :changed) do
def to_html
doc.to_html
end
def changed?
changed
end
end
Dir["#{Rails.root}/lib/oneboxer/*_onebox.rb"].sort.each do |f| Dir["#{Rails.root}/lib/oneboxer/*_onebox.rb"].sort.each do |f|
add_onebox "Oneboxer::#{Pathname.new(f).basename.to_s.gsub(/\.rb$/, '').classify}".constantize add_onebox "Oneboxer::#{Pathname.new(f).basename.to_s.gsub(/\.rb$/, '').classify}".constantize
end end
@ -66,7 +76,7 @@ module Oneboxer
# Parse URLs out of HTML, returning the document when finished. # Parse URLs out of HTML, returning the document when finished.
def self.each_onebox_link(string_or_doc) def self.each_onebox_link(string_or_doc)
doc = string_or_doc doc = string_or_doc
doc = Nokogiri::HTML(doc) if doc.is_a?(String) doc = Nokogiri::HTML::fragment(doc) if doc.is_a?(String)
onebox_links = doc.search("a.onebox") onebox_links = doc.search("a.onebox")
if onebox_links.present? if onebox_links.present?
@ -80,6 +90,32 @@ module Oneboxer
doc doc
end end
def self.apply(string_or_doc)
doc = string_or_doc
doc = Nokogiri::HTML::fragment(doc) if doc.is_a?(String)
changed = false
Oneboxer.each_onebox_link(doc) do |url, element|
onebox, preview = yield(url,element)
if onebox
parsed_onebox = Nokogiri::HTML::fragment(onebox)
next unless parsed_onebox.children.count > 0
# special logic to strip empty p elements
if element.parent &&
element.parent.node_name.downcase == "p" &&
element.parent.children.count == 1 &&
parsed_onebox.children.first.name.downcase == "div"
element = element.parent
end
changed = true
element.swap parsed_onebox.to_html
end
end
Result.new(doc, changed)
end
def self.cache_key_for(url) def self.cache_key_for(url)
"onebox:#{Digest::SHA1.hexdigest(url)}" "onebox:#{Digest::SHA1.hexdigest(url)}"
end end

View file

@ -3,19 +3,19 @@ require 'spec_helper'
require 'cooked_post_processor' require 'cooked_post_processor'
describe CookedPostProcessor do describe CookedPostProcessor do
let :cpp do
def cpp(cooked = nil, options = {})
post = Fabricate.build(:post_with_youtube) post = Fabricate.build(:post_with_youtube)
post.cooked = cooked if cooked
post.id = 123 post.id = 123
CookedPostProcessor.new(post) CookedPostProcessor.new(post, options)
end end
context 'process_onebox' do context 'process_onebox' do
before do before do
post = Fabricate.build(:post_with_youtube) @cpp = cpp(nil, invalidate_oneboxes: true)
post.id = 123 Oneboxer.expects(:onebox).with("http://www.youtube.com/watch?v=9bZkp7q19f0", post_id: 123, invalidate_oneboxes: true).returns('<div>GANGNAM STYLE</div>')
@cpp = CookedPostProcessor.new(post, invalidate_oneboxes: true)
Oneboxer.expects(:onebox).with("http://www.youtube.com/watch?v=9bZkp7q19f0", post_id: 123, invalidate_oneboxes: true).returns('GANGNAM STYLE')
@cpp.post_process_oneboxes @cpp.post_process_oneboxes
end end
@ -23,15 +23,13 @@ describe CookedPostProcessor do
@cpp.should be_dirty @cpp.should be_dirty
end end
it 'inserts the onebox' do it 'inserts the onebox without wrapping p' do
@cpp.html.should == <<EXPECTED @cpp.html.should match_html "<div>GANGNAM STYLE</div>"
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body>GANGNAM STYLE</body></html>
EXPECTED
end end
end end
context 'process_images' do context 'process_images' do
it "has no topic image if there isn't one in the post" do it "has no topic image if there isn't one in the post" do

View file

@ -159,7 +159,7 @@ describe Oneboxer do
element.is_a?(Nokogiri::XML::Element).should be_true element.is_a?(Nokogiri::XML::Element).should be_true
url.should == 'http://discourse.org' url.should == 'http://discourse.org'
end end
result.kind_of?(Nokogiri::HTML::Document).should be_true result.kind_of?(Nokogiri::HTML::DocumentFragment).should be_true
end end
it 'yields each url and element when given a doc' do it 'yields each url and element when given a doc' do
@ -172,5 +172,35 @@ describe Oneboxer do
end end
context "apply_onebox" do
it "is able to nuke wrapping p" do
doc = Oneboxer.apply "<p><a href='http://bla.com' class='onebox'>bla</p>" do |url, element|
"<div>foo</div>" if url == "http://bla.com"
end
doc.changed? == true
doc.to_html.should match_html "<div>foo</div>"
end
it "is able to do nothing if nil is returned" do
orig = "<p><a href='http://bla.com' class='onebox'>bla</p>"
doc = Oneboxer.apply orig do |url, element|
nil
end
doc.changed? == false
doc.to_html.should match_html orig
end
it "does not strip if there is a br in same node" do
doc = Oneboxer.apply "<p><br><a href='http://bla.com' class='onebox'>bla</p>" do |url, element|
"<div>foo</div>" if url == "http://bla.com"
end
doc.changed? == true
doc.to_html.should match_html "<p><br><div>foo</div></p>"
end
end
end end

View file

@ -5,7 +5,7 @@ Fabricator(:post) do
end end
Fabricator(:post_with_youtube, from: :post) do Fabricator(:post_with_youtube, from: :post) do
cooked '<a href="http://www.youtube.com/watch?v=9bZkp7q19f0" class="onebox" target="_blank">http://www.youtube.com/watch?v=9bZkp7q19f0</a>' cooked '<p><a href="http://www.youtube.com/watch?v=9bZkp7q19f0" class="onebox" target="_blank">http://www.youtube.com/watch?v=9bZkp7q19f0</a></p>'
end end
Fabricator(:old_post, from: :post) do Fabricator(:old_post, from: :post) do
@ -75,4 +75,4 @@ Fabricator(:private_message_post, from: :post) do
) )
end end
raw "Ssshh! This is our secret conversation!" raw "Ssshh! This is our secret conversation!"
end end