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

Replace Redcarpet by CommonMarker #419

Merged
merged 4 commits into from
Nov 16, 2023
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ gem 'actionpack-page_caching'
gem 'active_link_to'
gem 'bootstrap', '~> 5.3.2'
gem 'carrierwave', '~> 3.0'
gem 'commonmarker', '~>1.0.0.pre11'
gem 'draper'
gem 'friendly_id', '~> 5.5'
gem 'globalize'
Expand All @@ -27,7 +28,6 @@ gem 'oj' # Fast JSON parser and object serializer
gem 'pg' # Ruby interface to PostgreSQL RDBMS
gem 'puma' # Ruby web server built for concurrency
gem 'rails-i18n'
gem 'redcarpet'
gem 'route_translator' # Manage translations of routes
gem 'sass-rails', '~> 6.0'
gem 'simple_form', '~> 5.3'
Expand Down
10 changes: 5 additions & 5 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ GEM
descendants_tracker (~> 0.0.1)
colored (1.2)
colorize (0.8.1)
commonmarker (1.0.0.pre11-x86_64-linux)
concurrent-ruby (1.2.2)
connection_pool (2.4.1)
crass (1.0.6)
Expand Down Expand Up @@ -281,7 +282,7 @@ GEM
language_server-protocol (3.17.0.3)
launchy (2.5.2)
addressable (~> 2.8)
libv8-node (18.16.0.0)
libv8-node (18.16.0.0-x86_64-linux)
listen (3.8.0)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
Expand Down Expand Up @@ -310,7 +311,6 @@ GEM
mini_histogram (0.3.1)
mini_magick (4.12.0)
mini_mime (1.1.5)
mini_portile2 (2.8.5)
mini_racer (0.8.0)
libv8-node (~> 18.16.0.0)
minitest (5.20.0)
Expand All @@ -329,8 +329,7 @@ GEM
net-protocol
net-ssh (7.2.0)
nio4r (2.5.9)
nokogiri (1.15.4)
mini_portile2 (~> 2.8.2)
nokogiri (1.15.4-x86_64-linux)
racc (~> 1.4)
normalize-rails (8.0.1)
oj (3.16.1)
Expand Down Expand Up @@ -579,6 +578,7 @@ GEM

PLATFORMS
ruby
x86_64-linux

DEPENDENCIES
actionpack-action_caching
Expand All @@ -596,6 +596,7 @@ DEPENDENCIES
capybara
carrierwave (~> 3.0)
colored
commonmarker (~> 1.0.0.pre11)
cuprite
database_cleaner (~> 2.0.1)
derailed_benchmarks
Expand Down Expand Up @@ -627,7 +628,6 @@ DEPENDENCIES
rails-controller-testing
rails-i18n
rails_best_practices
redcarpet
reek
route_translator
rspec-rails (~> 6.0.3)
Expand Down
38 changes: 2 additions & 36 deletions config/initializers/markdown.rb
Original file line number Diff line number Diff line change
@@ -1,40 +1,6 @@
require 'redcarpet'

module ActionView
module Template::Handlers
# Rails template handler for Markdown
class Markdown
class_attribute :default_format
self.default_format = Mime[:html]

# @param template [ActionView::Template]
# @return [String] Ruby code that when evaluated will return the rendered
# content
def call(_template, body)
@markdown ||= Redcarpet::Markdown.new(renderer, params)
"#{@markdown.render(body).inspect}.html_safe"
end

private

def params
{
autolink: true, fenced_code_blocks: true,
space_after_headers: true, tables: true
}
end

def renderer
options = {
link_attributes: { rel: 'nofollow' }
}
Redcarpet::Render::HTML.new(options)
end
end
end
end
require 'handlers/markdown_handler'

ActionView::Template.register_template_handler(
:md,
ActionView::Template::Handlers::Markdown.new
Handlers::MarkdownHandler.new
)
12 changes: 12 additions & 0 deletions lib/handlers/markdown_handler.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
require 'markdown'

module Handlers
class MarkdownHandler
class_attribute :default_format
self.default_format = Mime[:html]

def call(_template, body)
Markdown.new(body).to_html.inspect
end
end
end
25 changes: 25 additions & 0 deletions lib/markdown.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
require 'commonmarker'

class Markdown
RENDER = {
hardbreaks: false
}.freeze

def initialize(input)
@input = input
end

def to_html
Commonmarker.to_html(input, options:)
end

private

attr_reader :input

def options
{
render: RENDER
}
end
end
214 changes: 214 additions & 0 deletions spec/lib/markdown_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
require 'spec_helper'
require 'markdown'

RSpec.describe Markdown do
describe '#to_html' do
it 'returns the HTML code that corresponds to the important text in Markdown' do
important_text_in_markdown = '**Hello** world'
expected_result_in_html = "<p><strong>Hello</strong> world</p>\n"
markdown = described_class.new(important_text_in_markdown)

expect(markdown.to_html).to eq(expected_result_in_html)
end

it 'returns the HTML code that corresponds to the emphasized text in Markdown' do
emphasized_text_in_markdown = 'Hello *world*'
expected_result_in_html = "<p>Hello <em>world</em></p>\n"
markdown = described_class.new(emphasized_text_in_markdown)

expect(markdown.to_html).to eq(expected_result_in_html)
end

it 'returns the HTML code that corresponds to the striked text in Markdown' do
strikethrough_in_markdown = 'Hello ~~World~~'
expected_result_in_html = "<p>Hello <del>World</del></p>\n"
markdown = described_class.new(strikethrough_in_markdown)

expect(markdown.to_html).to eq(expected_result_in_html)
end

it 'returns the HTML code the corresponds to the text containing quotation marks in Markdown' do
input = "My name is 'Bond, James Bond'"
expected_result_in_html = "<p>My name is 'Bond, James Bond'</p>\n"
markdown = described_class.new(input)

expect(markdown.to_html).to eq(expected_result_in_html)
end

it 'returns the HTML code that corresponds to the inline code in Markdown' do
inline_code_in_markdown = 'This is `inline code`'
expected_result_in_html = "<p>This is <code>inline code</code></p>\n"
markdown = described_class.new(inline_code_in_markdown)

expect(markdown.to_html).to eq(expected_result_in_html)
end

it 'returns the HTML code that corresponds to the block code in Markdown' do
block_code_in_markdown = <<~BLOCK_CODE
```ruby
puts 'Hello world'
```
torrocus marked this conversation as resolved.
Show resolved Hide resolved
BLOCK_CODE
expected_result_in_html = '<pre style="background-color:#2b303b;">' \
'<code class="language-ruby">' \
'<span style="color:#96b5b4;">puts </span>' \
'<span style="color:#c0c5ce;">&#39;</span>' \
'<span style="color:#a3be8c;">Hello world</span>' \
"<span style=\"color:#c0c5ce;\">&#39;\n</span></code></pre>\n"
markdown = described_class.new(block_code_in_markdown)

expect(markdown.to_html).to eq(expected_result_in_html)
end

it 'returns the HTML code that corresponds to the header in Markdown' do
header_in_markdown = '# The most important heading'
expected_result_in_html = '<h1><a href="#the-most-important-heading" ' \
'aria-hidden="true" class="anchor" id="the-most-important-heading"></a>' \
"The most important heading</h1>\n"
markdown = described_class.new(header_in_markdown)

expect(markdown.to_html).to eq(expected_result_in_html)
end

it 'returns the HTML code that corresponds to the link in Markdown' do
link_in_markdown = 'This is the [FractalSoft](https://fractalsoft.org/)'
expected_result_in_html = <<~HTML
<p>This is the <a href="https://fractalsoft.org/">FractalSoft</a></p>
HTML
markdown = described_class.new(link_in_markdown)

expect(markdown.to_html).to eq(expected_result_in_html)
end

it 'returns the HTML code that corresponds to the link in Markdown with www recognition' do
markdown_with_www_link = 'Visit www.fractalsoft.org'
expected_result_in_html = <<~HTML
<p>Visit <a href="http://www.fractalsoft.org">www.fractalsoft.org</a></p>
HTML
markdown = described_class.new(markdown_with_www_link)

expect(markdown.to_html).to eq(expected_result_in_html)
end

it 'returns the HTML code that corresponds to the image as link in Markdown' do
image_as_link_in_markdown = '[![My image](/path/to/image)](https://fractalsoft.org/)'
expected_result_in_html = <<~HTML
<p><a href="https://fractalsoft.org/"><img src="/path/to/image" alt="My image" /></a></p>
HTML
markdown = described_class.new(image_as_link_in_markdown)

expect(markdown.to_html).to eq(expected_result_in_html)
end

it 'returns the HTML code that corresponds to the link in header in Markdown' do
header_link_in_markdown = '# My Header [link](https://fractalsoft.org/)'
expected_result_in_html = '<h1><a href="#my-header-link" aria-hidden="true" class="anchor" id="my-header-link"></a>' \
"My Header <a href=\"https://fractalsoft.org/\">link</a></h1>\n"
markdown = described_class.new(header_link_in_markdown)

expect(markdown.to_html).to eq(expected_result_in_html)
end

it 'returns the HTML code that corresponds to the quote in Markdown' do
quote_in_markdown = '> Be or not to be'
expected_result_in_html = "<blockquote>\n<p>Be or not to be</p>\n</blockquote>\n"
markdown = described_class.new(quote_in_markdown)

expect(markdown.to_html).to eq(expected_result_in_html)
end

it 'returns the HTML code that corresponds to the ordered list in Markdown' do
ordered_list_in_markdown = <<~ORDERED_LIST
1. First item
2. Second item
3. Third item
ORDERED_LIST
expected_result_in_html = <<~HTML
<ol>
<li>First item</li>
<li>Second item</li>
<li>Third item</li>
</ol>
HTML
markdown = described_class.new(ordered_list_in_markdown)

expect(markdown.to_html).to eq(expected_result_in_html)
end

it 'returns the HTML code that corresponds to the unordered list in Markdown' do
unordered_list_in_markdown = <<~UNORDERED_LIST
- First item
- Second item
- Third item
UNORDERED_LIST
expected_result_in_html = <<~HTML
<ul>
<li>First item</li>
<li>Second item</li>
<li>Third item</li>
</ul>
HTML
markdown = described_class.new(unordered_list_in_markdown)

expect(markdown.to_html).to eq(expected_result_in_html)
end

it 'returns the HTML code that corresponds to the image in Markdown' do
image_in_markdown = '![My image](/path/to/image)'
expected_result_in_html = <<~HTML
<p><img src="/path/to/image" alt="My image" /></p>
HTML
markdown = described_class.new(image_in_markdown)

expect(markdown.to_html).to eq(expected_result_in_html)
end

it 'returns the HTML code that corresponds to the table in Markdown' do
table_in_markdown = <<~TABLE
| Syntax | Description |
|-------------|-------------|
| Header | Title |
| Paragraph | Text |
TABLE
expected_result_in_html = <<~HTML
<table>
<thead>
<tr>
<th>Syntax</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>Header</td>
<td>Title</td>
</tr>
<tr>
<td>Paragraph</td>
<td>Text</td>
</tr>
</tbody>
</table>
HTML
markdown = described_class.new(table_in_markdown)

expect(markdown.to_html).to eq(expected_result_in_html)
end

it 'returns the HTML code that corresponds to the task list in Markdown' do
task_list_in_markdown = <<~TASK_LIST
- [ ] first to do
- [x] second to do
TASK_LIST
expected_result_in_html = <<~HTML
<ul>
<li><input type="checkbox" disabled="" /> first to do</li>
<li><input type="checkbox" checked="" disabled="" /> second to do</li>
</ul>
HTML
markdown = described_class.new(task_list_in_markdown)

expect(markdown.to_html).to eq(expected_result_in_html)
end
end
end
Loading