Combining truncate with Redcarpet markdown in Rails: Links don't work - ruby-on-rails

I'm using Redcarpet for syntax highlighting in my Rails blog application.
In my posts/index.html.erb, I want to truncate the blog posts in order to preview the first few sentences (or paragraph). The user should be able to click on "read more" at the end of the truncated post to read the whole blog post. Unfortunately the "read more" link is not working with Redcarpet (when I don't use my markdown method (see below) the link is working fine). How can I fix that? Do I have to use other options in Redcarpet?
My markdown method in /helpers/application_helper.rb using Redcarpet:
def markdown(content)
renderer = HTMLwithPygments.new(hard_wrap: true, filter_html: true)
options = {
autolink: true,
no_intra_emphasis: true,
disable_indented_code_blocks: true,
fenced_code_blocks: true,
lax_html_blocks: true,
strikethrough: true,
superscript: true
}
Redcarpet::Markdown.new(renderer, options).render(content).html_safe
end
/views/posts/index.html.erb
<%= markdown (truncate(post.content,
length: 600,
separator: ' ',
omission: '... ') {
link_to "read more", post
}) %>
By the way: I am looping through the #posts variable, so "post.content" gives me the content of one post and "post" gives me the post's path.
The "read more" text is showing up but you cannot click on it. When I leave the "markdown" method out, the "read more"-link is working fine.
How can I create the link with my "markdown"-method?

That link isn't Markdown though, it's HTML. Maybe change it to Markdown?
<%= markdown(truncate(post.content, length: 600,
separator: ' ', omission: '... ') {
"[read more](#{post_path(post)})"
}) %>
Change post_path to something appropriate if that's not right.

Related

How do I implement Rouge syntax highlighting in Rails?

There are a bunch of tutorials floating around, but they seem to be incomplete or not fully current or don't fully work for me.
This is what I have done.
Gemfile:
gem 'rouge'
gem 'redcarpet'
Then I created a config/initializer/rouge.rb:
require 'rouge/plugins/redcarpet'
Then I created a file called app/assets/stylesheets/rouge.css.erb
<%= Rouge::Themes::Github.render(:scope => '.highlight') %>
Then in my app/helpers/application_helper.rb, I added this:
module ApplicationHelper
class HTML < Redcarpet::Render::HTML
include Rouge::Plugins::Redcarpet
def block_code(code, language)
Rouge.highlight(code, language || 'text', 'html')
end
end
def markdown(text)
render_options = {
filter_html: true,
hard_wrap: true,
link_attributes: { rel: 'nofollow' }
}
renderer = HTML.new(render_options)
extensions = {
autolink: true,
fenced_code_blocks: true,
lax_spacing: true,
no_intra_emphasis: true,
strikethrough: true,
superscript: true
}
Redcarpet::Markdown.new(renderer, extensions).render(text).html_safe
end
end
Then in my show.html.erb, I did this:
<%= markdown(#question.body) %>
But that literally does not work. It outputs my ruby code snippet like this:
How do I get this snippet of code to be formatted like Github? Or even just the first step being to be formatted any at all, then how do I tweak the formatting?
I don't see a stylesheet included in the source of the page, so I don't know which styles to tweak for what I want.
Edit 1
Or even when I do this:
<div class="highlight">
<%= #question.test_suite %>
</div>
It renders like this:
Edit 2
I attempted BoraMa's suggestion and I got output that looks like this:
Edit 3
I made a modification to BoraMa's answer as follows.
In my block_code method, I call highlight as follows:
Rouge.highlight(code, 'ruby', 'html')
Then in my view I do this:
<%= raw rouge_markdown(<<-'EOF'
def rouge_me
puts "this is a #{'test'} for rouge"
end
EOF
) %>
Then that produces this:
Note I am referring to the code snippet at the bottom of the screenshot.
However, the text at the top is generated with this:
<pre class="highlight ruby">
<%= rouge_markdown(#question.body) %>
</pre>
And it is rendered as is shown in the screenshot.
Edit 4
After removing the <div class="highlight">, I see this:
Aka....nothing is being rendered at all.
Once I add raw to my view...aka <%= raw rouge_markdown(#question.body) %>
The view renders this:
Edit 5
Here is the content for various #question objects:
[1] pry(#<#<Class:0x007fc041b97ce8>>)> #question.body
=> "5.times do\r\n puts \"Herro Rerl!\"\r\nend"
[1] pry(#<#<Class:0x007fc041b97ce8>>)> #question.body
=> "puts \"Hello World version 9\"\r\nputs \"This comes after version 8.\"\r\nputs \"This comes after version 7.\"\r\nputs \"This comes after version 6.\"\r\nputs \"This comes after version 5.\"\r\nputs \"This comes after version 4.\"\r\nputs \"This comes after version 3.\"\r\nputs \"This comes after version 2.\"\r\nputs \"This definitely comes after version 1.\""
[1] pry(#<#<Class:0x007fc041b97ce8>>)> #question.body
=> "def convert_relation(invited_gender, relation)\r\n case invited_gender\r\n \twhen \"male\"\r\n \tcase relation\r\n when \"daughter\", \"son\" then \"dad\"\r\n when \"mom\", \"dad\" then \"son\"\r\n when \"grandfather\", \"grandmother\" then \"grandson\"\r\n when \"sister\", \"brother\" then \"brother\"\r\n when \"wife\" then \"husband\"\r\n when \"husband\" then \"husband\"\r\n end\r\n when \"female\"\r\n \tcase relation\r\n when \"daughter\", \"son\" then \"mom\"\r\n when \"mom\", \"dad\" then \"daughter\"\r\n when \"grandfather\", \"grandmother\" then \"granddaughter\"\r\n when \"sister\", \"brother\" then \"sister\"\r\n when \"wife\" then \"wife\"\r\n when \"husband\" then \"wife\"\r\n end\r\n end\r\nend\r\n\r\nputs convert_relation(\"male\", \"wife\")"
The original question indicated (in the solution attempted) that markdown would be used in the highlighted questions but it turned out not to be the case. So this answer is split to two distinct sections, one for highlighting pure code without markdown, the other one for markdown text with code.
A) You want to highlight pure code (no Markdown involved)
In this case, and according to the README, all you need to highlight the code with Rouge is a lexer and a formatter. Since the highlighted text will be displayed on a web page, you need the HTML formatter. For the lexer, you need to know the language the code is in beforehand (or you may try guessing it from the source code itself but it does not seem to be very reliable for small code snippets).
You can create a simple helper method for the highlighting:
module RougeHelper
def rouge(text, language)
formatter = Rouge::Formatters::HTML.new(css_class: 'highlight')
lexer = Rouge::Lexer.find(language)
formatter.format(lexer.lex(text))
end
end
Then in the template, simply call this helper with a text to highlight and the language:
<%= raw rouge("def rouge_me\n puts 'hey!'\nend", "ruby") %>
Which will render:
To get a list of all languages that Rouge supports and their corresponding names that should be passed to the rouge helper, you can use the following code. The code gets all defined lexers from Rouge and shows their tags (i.e. the names Rouge recognizes them with):
Rouge::Lexer.all.map(&:tag).sort
# => ["actionscript", "apache", "apiblueprint", "applescript", ..., "xml", "yaml"]
You can (and probably should) use this list when showing users the languages to choose from in the selection box. Note that each lexer also has the title and desc methods defined that will give you a human-readable name and a short description of each of them. You might want to use this info to be shown to the user, too.
Note: you should get rid of the initializer, the custom HTML class and the div wrapped around the rouge helper call (all of these you have in your original attempt). The only thing you need besides the code above is the CSS rules, which you have already correctly included in the web page.
B) The highlighted text is a Markdown text with code blocks
A couple of changes from your attempt to make it work:
The initializer is not needed, you can remove it I think (but if you don't want to require all the files later in the helper, I guess you can leave it).
Remove the block_code method from the helper class, the same is already done by including the markdown plugin.
Remove the <div class="highlight"> wrapper div from your template and just use the helper in it. Rouge adds its own wrapper with the "highlight" class and another div seems to confuse it.
Try the following helper code. BTW, I moved the code from ApplicationHelper to a separate RougeHelper (but that is not a required change):
module RougeHelper
require 'redcarpet'
require 'rouge'
require 'rouge/plugins/redcarpet'
class HTML < Redcarpet::Render::HTML
include Rouge::Plugins::Redcarpet
end
def rouge_markdown(text)
render_options = {
filter_html: true,
hard_wrap: true,
link_attributes: { rel: 'nofollow' }
}
renderer = HTML.new(render_options)
extensions = {
autolink: true,
fenced_code_blocks: true,
lax_spacing: true,
no_intra_emphasis: true,
strikethrough: true,
superscript: true
}
markdown = Redcarpet::Markdown.new(renderer, extensions)
markdown.render(text)
end
end
Then, in the template, I tried to highlight a test ruby code:
<%= raw rouge_markdown(<<-'EOF'
```ruby
def rouge_me
puts "this is a #{'test'} for rouge"
end
```
EOF
) %>
Note that I needed to specify the language manually, which made me use the 3 backticks way to delimit the code instead of space indentation. I have no clue why the code language autodetection did not work here, perhaps it's a too short code.
In the end this rendered the colors for me nicely:

Rails: Redcarpet markdown and auto_html conflict

I am working on an app that allows users to input youtube videos, images, tweets, etc. To accomplish that I used auto_html(https://github.com/dejan/auto_html) and the code works fine. I am now trying to implement redcarpet markdown but whenever I use the function "markdown" (which I defined myself), auto_html stops working.
Here is the code for redcarpet (in the helper file):
module ApplicationHelper
def markdown(text)
markdown = Redcarpet::Markdown.new(Redcarpet::Render::HTML, hard_wrap: true, autolink: true, quote: true, fenced_code_blocks: true, strikethrough: true)
return markdown.render(text).html_safe
end
end
Here is the code for auto_html (in the Msg Model):
class Msg < ActiveRecord::Base
auto_html_for :content do
html_escape
image
twitter
vimeo
youtube(:width => 575, :height => 300, :autoplay => false)
soundcloud
link :target => "_blank", :rel => "nofollow"
simple_format
end
end
This is the view:
<p><%= markdown(msg.content_html) %></p>
where msg.content is users' input in text form, and msg.content_html applies auto_html filters and transforms an input URL to its format (image, video, etc).
I got auto_html and markdown working separately. If I leave the code in the view as above, my auto_html loads fine but markdown doesn't work. If I suppress the "_html" from msg.content, markdown works.
Any ideas how to go around this? Am I missing anything?

How to simply validate a checkbox in rails

How do you simply validate that a checkbox is checked in rails?
The checkbox is for a end user agreement. And it is located in a modal window.
Lets say i have the checkbox:
<%= check_box_tag '' %>
Where and how should i validate this?
I have seen most posts about checkbox validation in rails here, but none of them suit my needs.
Adding
validates :terms_of_service, :acceptance => true
to your model should do it. Look here for more details and options.
However, if accepting the terms is not part of a form for your model, you should use client-side validations, i.e. JavaScript, like this (in jQuery):
function validateCheckbox()
{
if( $('#checkbox').attr('checked')){
alert("you have to accept the terms first");
}
}
You can add a script file to your view like this:
<%= javascript_include_tag "my_javascipt_file" %>
and trigger the function on click:
<%= submit_tag "Submit", :onclick: "validateCheckbox();" %>
EDIT: you can assign an id to your checkbox like this: check_box_tag :checkbox. The HTML will look like this: <input id="checkbox" See these examples for more options.
I was able to skip the jQuery portion and get it validation to work with this questions help. My method is below, I'm on Rails 5.1.2 & Ruby 2.4.2.
Put this code in your slim, erb or haml; note syntax may differ slightly in each.
The line below was specifically for slim.
f.check_box :terms_of_service, required: true
I used a portion of kostja's code suggestion in the model.
validates :terms_of_service, :acceptance => true
Adding on to what has been said already, if you want to add a custom error message, you can add the following to your form:
f.input :terms_of_service, as: :boolean
and then add the following to your model:
validates :terms_of_service, acceptance: { message: "must be accepted"}
Error messages will start with the field name by default followed by your custom message (e.g. Terms of service [CUSTOM MESSAGE]). Something I also found useful was to include a link to the terms of service in the label so users can easily access it to see what they are agreeing to:
f.input :terms_of_service, as: :boolean, label: "I agree to the #{link_to "terms of service", [TERMS AND CONDITIONS PATH]}".html_safe

Rails truncate helper with link as omit text

I'm quite long description that I want to truncate using truncate helper.
So i'm using the:
truncate article.description, :length => 200, :omission => ' ...'
The problem is that I want to use more as a clickable link so in theory I could use this:
truncate article.description, :length => 200, :omission => "... #{link_to('[more]', articles_path(article)}"
Omission text is handled as unsafe so it's escaped. I tried to make it html_safe but it didn't work, instead of link [more] my browser is still showing the html for that link.
Is there any way to force truncate to print omission link instead of omission text?
I would suggest doing this on your own in a helper method, that way you'll have a little more control over the output as well:
def article_description article
output = h truncate(article.description, length: 200, omission: '...')
output += link_to('[more]', article_path(article)) if article.description.size > 200
output.html_safe
end
With Rails 4, you can/should pass in a block for the link:
truncate("Once upon a time in a world far far away",
length: 10,
separator: ' ',
omission: '... ') {
link_to "Read more", "#"
}
Dirty solution... use the method "raw" to unescape it.
you have to be sure of "sanity" of your content.
raw(truncate article.description, :length => 200, :omission => "... #{link_to('[more]', articles_path(article)}")
raw is a helper acting like html_safe .
bye
edit: is not the omission of being escaped , but the result of truncate method.
I encountered a similar situation and this did the trick. Try (line breaks for readability):
(truncate h(article.description),
:length => 200,
:omission => "... #{link_to('[more]',articles_path(article)}")
.html_safe
You can use h to ensure sanity of article description, and since you are setting the link_to to a path you know to not be something potentially nefarious, you can mark the resulting string as html_safe without concern.
TextHelper#truncate has a block form of truncate, which lets you use a link_to that isn't escaped, while still escaping the truncated text:
truncate("<script>alert('hello world')</script>") { link_to "Read More", "#" }
#=> <script>alert('hello world'...Read More
The only one that worked for me :
<%= truncate(#article.content, length: 200, omission: " ... %s") % link_to('read more', article_path(#article)) %>
I had the same problem, in my case i just used :escape => false.
That worked:
truncate article.description, :length => 200, :omission => "... #{link_to('[more]', articles_path(article)}", :escape => false
From documentation :
The result is marked as HTML-safe, but it is escaped by default, unless :escape is false....
link: http://apidock.com/rails/ActionView/Helpers/TextHelper/truncate

Adding a span tag to this menu in rails?

I am unable to add a span tag to the redmine project menu. You can see the full code at the redmine repository under trunk/lib/redmine/menu_manager.rb around line 182. This is the line where I'm trying to add a span tag.
return content_tag('li', render_single_menu_node(node, caption, url, selected))
I tried adding a span tag like this:
return content_tag('li', render_single_menu_node(node, content_tag(:span, caption), url, selected))
but it renders the span tag as text in the browser like this:
<span>Overview</span>
The 'li' renders just fine. Is there a way to add the span tag correctly?
See if this helps:
span_tag = content_tag(:span, caption).html_safe
return content_tag('li', render_single_menu_node(node, span_tag, url, selected))
By default, content_tag will escape any html content. You can use html_safe to prevent a string from being escaped.
On rails 2.3.x, there is no html_safe method, but you can pass an option to content_tag to tell it not to escape its contents:
span_tag = content_tag(:span, caption, {}, false)
return content_tag( 'li',
render_single_menu_node(node, span_tag, url, selected),
{},
false )

Resources