My First Rails Plugin - Usertext
-
Upload
frankieroberto -
Category
Technology
-
view
1.673 -
download
0
description
Transcript of My First Rails Plugin - Usertext
My First Rails PluginMarking up user content
The problem
Lots of websites invite user input.
Blogs
Forums
Wikis
‘Web 2.0’ sites
And they all have to format it somehow.
There are some existing solutions
‘Some HTML accepted’
Textile
Markdown
But asking users to pick between them is confusing!
Some of the syntax is a bit unnatural too...
Textile
“Test”:http://www.test.com
# Ordered list# Ordered list
Textile
Markdown> This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet,> consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus.> Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.> > Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse> id sem consectetuer libero luctus adipiscing.
=> <blockquote><p>This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.</p></blockquote>
Users shouldn’t have to think about the markup
syntax!
Instead, interpret what they’ve typed and make
intelligent guesses!
I decided to write my own code...
...as a Ruby on Rails plugin
I want to support...Paragraphs
Unordered lists
Ordered lists
Blockquotes
Code examples
Rails already has a simple_format(text) helper
simple_format(text)
simple_format(“Hello.
Are you my mummy?”)
=> “<p>Hello.</p>
<p>Are you my mummy?</p>”
How does simple_format work?
simple_format()def simple_format(text, html_options={}) start_tag = tag('p', html_options, true)
text = text.to_s.dup text.gsub!(/\r\n?/, "\n") # \r\n and \r -> \n
text.gsub!(/\n\n+/, "</p>\n\n#{start_tag}") # 2+ newline -> paragraph text.gsub!(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
text.insert 0, start_tag text << "</p>"
end
So borrowing from that...
Dealing with newlinesdef cleanup_newlines(text) text.gsub(/\r\n?/, "\n") # \r\n and \r -> \nend
Block elementsUnordered list:
* Item* Item
Ordered list:
1. Item2. Item
Or
1) Item2) Item
Blockquotes:
“To be, or not to be.”
Block elementsdef block_element_markup(text) blocks = text.split("\n\n") new_text = Array.new blocks.each do |block| # Work out what type of block element it is. end return new_text.join("\n\n")end
Block elementsif block.match(/((^\*\s.*$\n?)+)/) lines = Array.new block.each_line do |line| lines << line.gsub(/\*\s(.*)$/, '<li>\1</li>') end new_text << content_tag("ul", "\n" + lines.join("") + "\n")
elsif block.match(/((^\d+[\.\)]\s.*$\n?)+)/) lines = Array.new block.each_line do |line| lines << line.gsub(/\d+.\s(.*)$/, '<li>\1</li>') end new_text << content_tag("ol", "\n" + lines.join("") + "\n") ...end
Block elementselsif block.match(/^“.*”$/) new_text << content_tag("blockquote", content_tag("p", block.gsub(/^“(.*)”$/, '\1'))) elsif block.match(/(^<code>.*<\/code>$)/) new_text << content_tag("div", block)else new_text << content_tag("p", block)end
Dealing with links... def auto_link_urls(text, href_options = {})
extra_options = tag_options(href_options.stringify_keys) || ""
auto_link_re = %r{
( # leading text
<\w+.*?>| # leading HTML tag, or [^=!:'"/]| # leading punctuation, or ^ # beginning of line
)
(https?://|ftp://) # protocol spec
(
[-\w]+ # subdomain or domain (?:\.[-\w]+)* # remaining subdomains or domain (?::\d+)? # port (?:/(?:(?:[~\w\+@%-]|(?:[,.;:][^\s$]))+)?)* # path (?:\?[\w\+@%&=.;-]+)? # query string (?:\#[\w\-\/]*)? # trailing anchor
)(([[:punct:]]|\s|<|$)) # trailing text
}x
text.gsub(auto_link_re) do
all, a, b, c, d = $&, $1, $2, $3, $5 text = a + "<a href=\"" + b + c + "\">" + truncate_in_middle(c, 40) + "</a>" + $5
end
end
Truncate URLS in the middle...
def truncate_in_middle(text, length = 30, truncate_string = "...") if text l = ((length - truncate_string.chars.length) / 2).to_int chars = text.chars return (chars.length > length ? chars[0...l] + truncate_string + chars[chars.length-l...chars.length] : text).to_s endend
Detecting code snippetsdef mark_code(text) h(text). gsub(/(^<[a-zA-Z]+.*$|<[a-zA-Z]+.*>)/) do text = "<code>" + ($1) + "</code>" endend
<html> => <code><html></code>
Detecting phone numbers
def auto_link_phone_numbers(text) text.gsub(/(\s|^)((0|\+44)\d{10,10})\b/) do text = $1 + "<a href=\"tel:" + $2 + "\">" + $2 + "</a>" endend
07736111111
=> <a tel="07736111111"> 07736111111</a>
Typography" => “ / ”
' => ‘ / ’ (quote) / ’ (apos) / ' (prime)
... => …
etc...
Typography
gsub(/([^\.])\.\.\.([^\.]|$)/, '\1…\2')
gsub(/([^\s]+)\"/, '\1”')
gsub(/([^\s]+)\'(\s)/, '\1’\2')
gsub(/([^\s])\'([^\s])/, '\1’\2')
etc...
Tests!
Hosting
http://code.google.com/p/usertext/
What next?
What next?• Test on some real user input!
What next?• Test on some real user input!
• Make it configurable
What next?• Test on some real user input!
• Make it configurable
• Port to PHP (for Wordpress)?
What next?• Test on some real user input!
• Make it configurable
• Port to PHP (for Wordpress)?
• gem vs plugin?
What next?• Test on some real user input!
• Make it configurable
• Port to PHP (for Wordpress)?
• gem vs plugin?
• Get feedback from other developers.