Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: markdown support #46

Merged
merged 1 commit into from Jun 23, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions Gemfile
Expand Up @@ -17,6 +17,10 @@ gem "bootstrap-sass"
gem 'font-awesome-sass'
gem "meta-tags"
gem "local_time"
gem "twemoji"
gem "html-pipeline"
gem "github-markdown"
gem "sanitize"

group :production do
gem "rails_12factor"
Expand Down
17 changes: 17 additions & 0 deletions Gemfile.lock
Expand Up @@ -56,6 +56,7 @@ GEM
coffee-script-source (1.10.0)
concurrent-ruby (1.0.2)
connection_pool (2.2.0)
crass (1.0.2)
database_rewinder (0.6.0)
debug_inspector (0.0.2)
diff-lcs (1.2.5)
Expand All @@ -68,10 +69,14 @@ GEM
railties (>= 3.0.0)
font-awesome-sass (4.6.2)
sass (>= 3.2)
github-markdown (0.6.9)
globalid (0.3.6)
activesupport (>= 4.1.0)
heroku-deflater (0.6.2)
rack (>= 1.4.5)
html-pipeline (2.4.1)
activesupport (>= 2, < 5)
nokogiri (>= 1.4)
i18n (0.7.0)
jquery-rails (4.1.1)
rails-dom-testing (>= 1, < 3)
Expand All @@ -96,6 +101,8 @@ GEM
nokogiri (1.6.8)
mini_portile2 (~> 2.1.0)
pkg-config (~> 1.1.7)
nokogumbo (1.4.7)
nokogiri
pg (0.18.4)
pkg-config (1.1.7)
pry (0.10.3)
Expand Down Expand Up @@ -158,6 +165,10 @@ GEM
rspec-mocks (~> 3.1.0)
rspec-support (~> 3.1.0)
rspec-support (3.1.2)
sanitize (4.0.1)
crass (~> 1.0.2)
nokogiri (>= 1.4.4)
nokogumbo (~> 1.4.1)
sass (3.4.22)
sass-rails (5.0.4)
railties (>= 4.0.0, < 5.0)
Expand Down Expand Up @@ -193,6 +204,8 @@ GEM
tilt (2.0.5)
turbolinks (2.5.3)
coffee-rails
twemoji (3.0.0)
nokogiri (~> 1.6.2)
tzinfo (1.2.2)
thread_safe (~> 0.1)
uglifier (3.0.0)
Expand All @@ -215,7 +228,9 @@ DEPENDENCIES
database_rewinder
factory_girl_rails
font-awesome-sass
github-markdown
heroku-deflater
html-pipeline
jquery-rails
local_time
meta-tags
Expand All @@ -226,12 +241,14 @@ DEPENDENCIES
rails_12factor
redis (~> 3.3.0)
rspec-rails
sanitize
sass-rails
shoulda-matchers (~> 3.1)
sidekiq
slim-rails
spring
turbolinks
twemoji
uglifier (>= 1.3.0)
web-console (~> 3.0)

Expand Down
7 changes: 7 additions & 0 deletions app/assets/stylesheets/application.scss
Expand Up @@ -19,3 +19,10 @@ textarea#message_content {
.announcement {
color: red;
}

img.emoji {
height: 1em;
width: 1em;
margin: 0 .05em 0 .1em;
vertical-align: -0.1em;
}
23 changes: 23 additions & 0 deletions app/models/render_markdown.rb
@@ -0,0 +1,23 @@
require 'html/pipeline/red_dot_ruby_conf/nohtml_markdown_filter'
require 'html/pipeline/red_dot_ruby_conf/emoji_filter'

class RenderMarkdown
def initialize(content)
@content = content
end

def call(**options)
pipeline = HTML::Pipeline.new [
HTML::Pipeline::RedDotRubyConf::NohtmlMarkdownFilter,
HTML::Pipeline::SanitizationFilter,
HTML::Pipeline::ImageMaxWidthFilter,
HTML::Pipeline::RedDotRubyConf::EmojiFilter,
], { gfm: true, **options }

pipeline.call(content)[:output].to_s.html_safe
end

private

attr_reader :content
end
6 changes: 5 additions & 1 deletion app/models/render_message.rb
Expand Up @@ -14,14 +14,18 @@ def call
content_tag(:div, class: "message-body") do
content_tag(:strong, "#{nickname} ") +
content_tag(:span, local_time_ago(created_at), class: "text-muted") +
content_tag(:p, content, class: message_body_class)
content_tag(:p, markdown_content, class: message_body_class)
end
end

private

attr_reader :nickname, :created_at, :content, :announcement

def markdown_content
RenderMarkdown.new(content).call
end

def message_body_class
["message-body", announcement_class].join(" ")
end
Expand Down
17 changes: 17 additions & 0 deletions lib/html/pipeline/red_dot_ruby_conf/emoji_filter.rb
@@ -0,0 +1,17 @@
require "twemoji"

module HTML
class Pipeline
module RedDotRubyConf
class EmojiFilter < HTML::Pipeline::Filter
def call
Twemoji.parse(doc,
file_ext: context[:file_ext] || "svg",
class_name: context[:class_name] || "emoji",
img_attrs: context[:img_attrs] || {},
)
end
end
end
end
end
15 changes: 15 additions & 0 deletions lib/html/pipeline/red_dot_ruby_conf/nohtml_markdown_filter.rb
@@ -0,0 +1,15 @@
module HTML
class Pipeline
module RedDotRubyConf
class NohtmlMarkdownFilter < MarkdownFilter
def call
while @text.index(unique = "#{SecureRandom.hex}"); end

@text.gsub!("<", "#{unique} ")

super.gsub(Regexp.new("#{unique}\\s?"), "&lt;")
end
end
end
end
end
64 changes: 64 additions & 0 deletions spec/models/render_markdown_spec.rb
@@ -0,0 +1,64 @@
require "rails_helper"

RSpec.describe RenderMarkdown do
it "should convert text to markdown" do
content = <<-TEXT.strip_heredoc
This is *great*:
some_code(:first)
TEXT

html = RenderMarkdown.new(content).call

expect(html).to eq("<p>This is <em>great</em>:<br>\n some_code(:first)</p>")
end

it "keep code & autolink" do
content = <<-TEXT.strip_heredoc
> quoted text

123`<img src="" onerror="alert(1)" />`45678

hey Blah <trump@example.com>
TEXT

html = RenderMarkdown.new(content).call

expect(html).to eq(<<-TEXT.strip_heredoc.strip)
<blockquote>
<p>quoted text</p>
</blockquote>

<p>123<code>&lt;img src="" onerror="alert(1)" /&gt;</code>45678</p>

<p>hey Blah &lt;<a href="mailto:trump@example.com">trump@example.com</a>&gt;</p>
TEXT
end

it "sanitizes bad input" do
content = <<-TEXT.strip_heredoc
> quoted text

123<img src="" onerror="alert(1)" />45678
TEXT

html = RenderMarkdown.new(content).call

expect(html).to eq(<<-TEXT.strip_heredoc.strip)
<blockquote>
<p>quoted text</p>
</blockquote>

<p>123&lt;img src="" onerror="alert(1)" /&gt;45678</p>
TEXT
end

it "Twemoji" do
content = <<-TEXT.strip_heredoc
my oh my :heart_eyes:
TEXT

html = RenderMarkdown.new(content).call

expect(html).to eq("<p>my oh my <img draggable=\"false\" title=\":heart_eyes:\" alt=\"😍\" src=\"https://twemoji.maxcdn.com/2/svg/1f60d.svg\" class=\"emoji\"></p>")
end
end
4 changes: 2 additions & 2 deletions spec/models/render_message_spec.rb
Expand Up @@ -9,14 +9,14 @@ def render(nickname, created_at, content, announcement: false)
it "renders correct HTML" do
result = render("realDonaldTrump", "2016-01-01T00:00:00Z", "Make America Great Again")

expect(result).to eq %(<div class=\"message-body\"><strong>realDonaldTrump </strong><span class=\"text-muted\"><time datetime=\"2016-01-01T00:00:00Z\" data-local=\"time-ago\">January 1, 2016 12:00am</time></span><p class=\"message-body \">Make America Great Again</p></div>)
expect(result).to eq %(<div class=\"message-body\"><strong>realDonaldTrump </strong><span class=\"text-muted\"><time datetime=\"2016-01-01T00:00:00Z\" data-local=\"time-ago\">January 1, 2016 12:00am</time></span><p class=\"message-body \"><p>Make America Great Again</p></p></div>)
end

context "announcement" do
it "renders correct HTML with announcement class on content" do
result = render("RedDotRubyConf2016", Time.current, "Lunch time!", announcement: true)

expect(result).to include %(<p class=\"message-body announcement\">Lunch time!</p>)
expect(result).to include %(<p class=\"message-body announcement\"><p>Lunch time!</p>)
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion spec/requests/messages_spec.rb
Expand Up @@ -33,7 +33,7 @@

post render_message_path, params: { nickname: "realDonaldTrump" , timestamp: "2016-01-01T00:00:00", message: "Make America Great Again" }

expect(response.body).to eq %(<div class=\"message-body\"><strong>realDonaldTrump </strong><span class=\"text-muted\"><time datetime=\"2016-01-01T00:00:00Z\" data-local=\"time-ago\">January 1, 2016 12:00am</time></span><p class=\"message-body \">Make America Great Again</p></div>)
expect(response.body).to eq %(<div class=\"message-body\"><strong>realDonaldTrump </strong><span class=\"text-muted\"><time datetime=\"2016-01-01T00:00:00Z\" data-local=\"time-ago\">January 1, 2016 12:00am</time></span><p class=\"message-body \"><p>Make America Great Again</p></p></div>)
end
end
end