Easier Image Markup With Redcarpet
So my girlfriend wants to start a blog and being the awesome boyfriend I am, I offered to write it for her. Writing a simple blog in rails isn’t that much work and having an easy to set up blog app lying around can’t hurt, right? Today while trying to make posting as frictionless as possible I stumbled upon something really cool I wasn’t really aware of before. I’m kind of forcing my girlfriend to use Markdown1 and am using Redcarpet to parse it. On the bottom of the post form I’m enabling the user to upload pictures with Paperclip she want’s to use in the post but wasn’t really sure how to make easy to embed them in the post content. In come custom Redcarpet renderer. They basically enable you to rewrite the methods used by Redcarpet and give them your own spin.
But let’s start from the beginning and add all the gems we need to the Gemfile:
gem 'redcarpet'
gem 'paperclip', '~> 3.0'
gem 'paperclip-meta'
Redcarpet and Paperclip should be clear and Paperclip Meta writes the dimension of the Paperclip styles in an extra column which makes them nicely accessible as you’ll see later.
Now let’s create the models:
$ rails g model Post title:string content:text
$ rails g model Image name:string file:attachment file_meta:text post_id:integer
And edit them:
class Post < ActiveRecord::Base
attr_accessible :content, :title, :images_attributes
has_many :images, :dependent => :destroy
accepts_nested_attributes_for :images, allow_destroy: true
end
class Image < ActiveRecord::Base
attr_accessible :file, :post_id, :name
belongs_to :post
has_attached_file :file, :styles => { :thumb => "140>x140" }
end
Now normally an image would like like this in Markdown:
![title](path/to/image "alt text")
I thought it would be cool if the user could give the image a name and I could use this name to generate the link. Furthermore it would be cool if the user could set a size and a class2 for the image while still using Markdown. That could look something like this:
![image.title](thumb|right "alt text")
The size “thumb” of course meaning the Paperclip style thumb nad right being a html class, that floats the image to the right side.
Now we’ll create our custom Redcarpet renderer to process our new markup. Initially I had a helper method in the ApplicationHelper for Redcarpet:
module ApplicationHelper
def markdown(text)
Redcarpet::Markdown.new(Redcarpet::Render::HTML.new(:hard_wrap => true), :space_after_headers => true, :autolink => true, :strikethrough => true, :superscript => true).render(text).html_safe
end
end
But now I’d rather put this and the custom renderer in it’s own helper module.
module RedcarpetHelper
def markdown(text)
Redcarpet::Markdown.new(HTMLBlockCode.new(:hard_wrap => true), :space_after_headers => true, :autolink => true, :strikethrough => true, :superscript => true).render(text).html_safe
end
end
class HTMLBlockCode < Redcarpet::Render::HTML
include Sprockets::Helpers::RailsHelper
include Sprockets::Helpers::IsolatedHelper
include ActionView::Helpers::UrlHelper
def parse_media_link(link)
matches = link.match(/^(\w+)?\|([\w\s\d]+)?/)
{
:size => (matches[1] || 'original').to_sym,
:class => matches[2]
} if matches
end
def image(link, alt_text, title)
size = nil
klass = nil
if nil != (parse = parse_media_link(link))
image = Image.find_by_name(title)
if image
size = image.file.image_size(parse[:size])
link = image.file.url(parse[:size])
klass = parse[:class]
image_tag(link, :size => size, :title => title, :alt => alt_text, :class => klass)
else
""
end
end
end
def link(link, title, content)
klass = nil
if nil != (parse = parse_media_link(link))
image = Image.find_by_name(title)
if image
link = image.file.url(parse[:size])
klass = parse[:class]
link_to(content, link, :title => title, :class => klass)
else
""
end
end
end
end
Here we’ve overwritten how Redcarpet generates an image_tag. First it’s parsing the link for paperclip style3 and class, than it searches the image by title and generates an image_tag with the url of the image file, the dimensions of the chosen style4, the title, alt text and class.
Since our new renderer inherits from the existing renderer and only changes one method, we now can just exchange the Redcarpet::Render::HTML
bit in the markdown()
helper method with our renderer HTMLBlockCode
and are done.
Helpful links: