support youtube videos

master
Jordan Orelli 9 years ago
parent 6d1995a0c3
commit 13482a12a4

@ -1,8 +1,24 @@
require 'parslet' require 'parslet'
require 'sanitize' require 'sanitize'
require 'uri'
module Bristlecode module Bristlecode
class YoutubeFilter
def call(env)
node = env[:node]
node_name = env[:node_name]
return if env[:is_whitelisted] || !node.element?
return unless node_name == 'iframe'
return unless node['src'] =~ %r|\A(?:https?:)?//(?:www\.)?youtube(?:-nocookie)?\.com/|
Sanitize.node!(node, {
:elements => %w[iframe],
:attributes => {'iframe' => %w[allowfullscreen frameborder height src width]}
})
{:node_whitelist => [node]}
end
end
Config = Sanitize::Config::freeze_config( Config = Sanitize::Config::freeze_config(
:elements => %w[b em i strong u a strike br img], :elements => %w[b em i strong u a strike br img],
:attributes => { :attributes => {
@ -14,7 +30,8 @@ module Bristlecode
}, },
:protocols => { :protocols => {
'a' => {'href' => ['http', 'https', :relative]} 'a' => {'href' => ['http', 'https', :relative]}
} },
:transformers => [YoutubeFilter.new]
) )
def Bristlecode.to_html(text) def Bristlecode.to_html(text)
@ -29,6 +46,7 @@ module Bristlecode
Sanitize.fragment(html, Bristlecode::Config) Sanitize.fragment(html, Bristlecode::Config)
end end
def Bristlecode.clean!(text) def Bristlecode.clean!(text)
text.gsub!('&', '&') text.gsub!('&', '&')
text.gsub!('<', '&lt;') text.gsub!('<', '&lt;')
@ -64,16 +82,21 @@ module Bristlecode
} }
rule(:url) { (simple_url | url_with_title).as(:url) } rule(:url) { (simple_url | url_with_title).as(:url) }
rule(:youtube_open) { str('[youtube]') }
rule(:youtube_close) { str('[/youtube]') }
rule(:youtube_url) { (youtube_close.absent? >> any).repeat(1) }
rule(:youtube) { (youtube_open >> youtube_url.as(:src) >> youtube_close).as(:youtube) }
rule(:img_open) { str('[img]') } rule(:img_open) { str('[img]') }
rule(:img_close) { str('[/img]') } rule(:img_close) { str('[/img]') }
rule(:img_src) { (img_close.absent? >> any).repeat(1) } rule(:img_src) { (img_close.absent? >> any).repeat(1) }
rule(:img) { (img_open >> img_src.as(:src) >> img_close).as(:img) } rule(:img) { (img_open >> img_src.as(:src) >> img_close).as(:img) }
rule(:eof) { any.absent? } rule(:eof) { any.absent? }
rule(:tag) { bold | italic | url | linebreak | img } rule(:tag) { bold | italic | url | linebreak | img | youtube }
rule(:elem) { text.as(:text) | tag } rule(:elem) { text.as(:text) | tag }
rule(:tag_open) { bold_open | italic_open | url_open | url_title_open | img_open } rule(:tag_open) { bold_open | italic_open | url_open | url_title_open | img_open | youtube_open }
rule(:tag_close) { bold_close | italic_close | url_close | img_close } rule(:tag_close) { bold_close | italic_close | url_close | img_close | youtube_close }
rule(:tag_delim) { tag_open | tag_close | linebreak } rule(:tag_delim) { tag_open | tag_close | linebreak }
rule(:text) { (tag_delim.absent? >> any).repeat(1) } rule(:text) { (tag_delim.absent? >> any).repeat(1) }
@ -90,6 +113,7 @@ module Bristlecode
rule(url: subtree(:url)) { Url.new(url) } rule(url: subtree(:url)) { Url.new(url) }
rule(br: simple(:br)) { Linebreak.new } rule(br: simple(:br)) { Linebreak.new }
rule(img: subtree(:img)) { Img.new(img) } rule(img: subtree(:img)) { Img.new(img) }
rule(youtube: subtree(:youtube)) { Youtube.new(youtube) }
end end
class Doc class Doc
@ -180,11 +204,8 @@ module Bristlecode
end end
def to_html def to_html
if href_ok? return to_text unless href_ok?
"<a href=\"#{href}\">#{title.to_html}</a>" "<a href=\"#{href}\">#{title.to_html}</a>"
else
to_text
end
end end
def to_text def to_text
@ -220,15 +241,48 @@ module Bristlecode
end end
def to_html def to_html
if src_ok? return to_text unless src_ok?
"<img src=\"#{src}\">" "<img src=\"#{src}\">"
end
def to_text
text = "[img]#{src}[/img]"
Bristlecode.clean!(text)
text
end
end
class Youtube
attr_accessor :raw_url, :video_id
def initialize(args)
self.raw_url = args[:src].to_str.strip
self.video_id = parse_url
end
def parse_url
begin
uri = URI::parse(raw_url)
return false unless ['http', 'https'].include? uri.scheme
return false unless ['www.youtube.com', 'youtube.com', 'youtu.be'].include? uri.host
if uri.host == 'youtu.be'
return uri.path[1..-1]
else else
to_text URI::decode_www_form(uri.query).each{|key, value| return value if key == 'v'}
end
rescue URI::InvalidURIError
end end
return false
end
def to_html
return to_text unless video_id
"<iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/#{video_id}\" frameborder=\"0\" allowfullscreen></iframe>"
end end
def to_text def to_text
text = "[img]#{src}[/img]" text = "[youtube]#{raw_url}[/youtube]"
Bristlecode.clean!(text) Bristlecode.clean!(text)
text text
end end

@ -123,6 +123,32 @@ module Bristlecode
input = '[url][url]x[/url][/url]' input = '[url][url]x[/url][/url]'
expect(to_html(input)).to eq(input) expect(to_html(input)).to eq(input)
end end
it 'can render a youtube video with a watch link' do
input = '[youtube]https://youtube.com/watch?v=uxpDa-c-4Mc[/youtube]'
output = '<iframe width="560" height="315" src="https://www.youtube.com/embed/uxpDa-c-4Mc" frameborder="0" allowfullscreen=""></iframe>'
expect(to_html(input)).to eq(output)
input = '[youtube]https://www.youtube.com/watch?v=uxpDa-c-4Mc[/youtube]'
output = '<iframe width="560" height="315" src="https://www.youtube.com/embed/uxpDa-c-4Mc" frameborder="0" allowfullscreen=""></iframe>'
expect(to_html(input)).to eq(output)
end
it 'can render a youtube video with a share link' do
input = '[youtube]https://youtu.be/uxpDa-c-4Mc[/youtube]'
output = '<iframe width="560" height="315" src="https://www.youtube.com/embed/uxpDa-c-4Mc" frameborder="0" allowfullscreen=""></iframe>'
expect(to_html(input)).to eq(output)
end
it 'refuses bad youtube urls' do
input = '[youtube]http://example.com/cats.gif[/youtube]'
expect(to_html(input)).to eq(input)
end
it "requires full url for youtube vids" do
input = '[youtube]dQw4w9WgXcQ[/youtube]'
expect(to_html(input)).to eq(input)
end
end end
describe Parser do describe Parser do

Loading…
Cancel
Save