Mercurial > zeropaste
changeset 100:90b59418828a
Three times more fun with tabs (added plain and markdown tab).
author | Edho Arief <edho@myconan.net> |
---|---|
date | Mon, 12 Nov 2012 10:42:00 +0700 |
parents | e0e99705e79d |
children | 17f682a53b13 |
files | app/assets/javascripts/application.js app/assets/javascripts/init.prettify.js app/assets/javascripts/init.tabs.js app/views/pastes/show.html.erb lib/assets/javascripts/marked.js |
diffstat | 5 files changed, 808 insertions(+), 4 deletions(-) [+] |
line wrap: on
line diff
--- a/app/assets/javascripts/application.js Mon Nov 12 09:44:44 2012 +0700 +++ b/app/assets/javascripts/application.js Mon Nov 12 10:42:00 2012 +0700 @@ -12,6 +12,8 @@ // //= require jquery //= require jquery_ujs +//= require twitter/bootstrap/tab //= require prettify +//= require marked //= require jquery.autosize //= require_tree .
--- a/app/assets/javascripts/init.prettify.js Mon Nov 12 09:44:44 2012 +0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,3 +0,0 @@ -$(document).ready(function() { - window.prettyPrint && prettyPrint(); -});
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/assets/javascripts/init.tabs.js Mon Nov 12 10:42:00 2012 +0700 @@ -0,0 +1,6 @@ +$(document).ready(function() { + raw = $('#plain > pre').text(); + $('#highlight > pre').text(raw); + $('#markdown > div').html(marked(raw)); + window.prettyPrint && prettyPrint(); +});
--- a/app/views/pastes/show.html.erb Mon Nov 12 09:44:44 2012 +0700 +++ b/app/views/pastes/show.html.erb Mon Nov 12 10:42:00 2012 +0700 @@ -3,7 +3,25 @@ <div class="page-header"> <h1><%= content_for :title %></h1> </div> -<pre class="prettyprint linenums"><%= @paste.paste.gsub(/^$/, ' ') %></pre> + +<div class="tabbable"> + <ul class="nav nav-tabs"> + <li class="active"><%= link_to 'Plain', '#plain', :data => { :toggle => 'tab' } %></li> + <li><%= link_to 'Highlight', '#highlight', :data => { :toggle => 'tab' } %></li> + <li><%= link_to 'Markdown', '#markdown', :data => { :toggle => 'tab' } %></li> + </ul> + <div class="tab-content"> + <div class="tab-pane active" id="plain"> + <pre><%= @paste.paste %></pre> + </div> + <div class="tab-pane" id="highlight"> + <pre class="prettyprint linenums"></pre> + </div> + <div class="tab-pane" id="markdown"> + <div></div> + </div> + </div> +</div> <ul class="nav nav-pills"> <li><%= link_to 'Raw', paste_path(@paste, :txt) %></li>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/assets/javascripts/marked.js Mon Nov 12 10:42:00 2012 +0700 @@ -0,0 +1,781 @@ +/** + * marked - A markdown parser (https://github.com/chjj/marked) + * Copyright (c) 2011-2012, Christopher Jeffrey. (MIT Licensed) + */ + +;(function() { + +/** + * Block-Level Grammar + */ + +var block = { + newline: /^\n+/, + code: /^( {4}[^\n]+\n*)+/, + fences: noop, + hr: /^( *[-*_]){3,} *(?:\n+|$)/, + heading: /^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/, + lheading: /^([^\n]+)\n *(=|-){3,} *\n*/, + blockquote: /^( *>[^\n]+(\n[^\n]+)*\n*)+/, + list: /^( *)(bull) [^\0]+?(?:hr|\n{2,}(?! )(?!\1bull )\n*|\s*$)/, + html: /^ *(?:comment|closed|closing) *(?:\n{2,}|\s*$)/, + def: /^ *\[([^\]]+)\]: *([^\s]+)(?: +["(]([^\n]+)[")])? *(?:\n+|$)/, + paragraph: /^([^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+\n*/, + text: /^[^\n]+/ +}; + +block.bullet = /(?:[*+-]|\d+\.)/; +block.item = /^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/; +block.item = replace(block.item, 'gm') + (/bull/g, block.bullet) + (); + +block.list = replace(block.list) + (/bull/g, block.bullet) + ('hr', /\n+(?=(?: *[-*_]){3,} *(?:\n+|$))/) + (); + +block.html = replace(block.html) + ('comment', /<!--[^\0]*?-->/) + ('closed', /<(tag)[^\0]+?<\/\1>/) + ('closing', /<tag(?:"[^"]*"|'[^']*'|[^'">])*?>/) + (/tag/g, tag()) + (); + +block.paragraph = replace(block.paragraph) + ('hr', block.hr) + ('heading', block.heading) + ('lheading', block.lheading) + ('blockquote', block.blockquote) + ('tag', '<' + tag()) + ('def', block.def) + (); + +block.normal = { + fences: block.fences, + paragraph: block.paragraph +}; + +block.gfm = { + fences: /^ *(```|~~~) *(\w+)? *\n([^\0]+?)\s*\1 *(?:\n+|$)/, + paragraph: /^/ +}; + +block.gfm.paragraph = replace(block.paragraph) + ('(?!', '(?!' + block.gfm.fences.source.replace('\\1', '\\2') + '|') + (); + +/** + * Block Lexer + */ + +block.lexer = function(src) { + var tokens = []; + + tokens.links = {}; + + src = src + .replace(/\r\n|\r/g, '\n') + .replace(/\t/g, ' '); + + return block.token(src, tokens, true); +}; + +block.token = function(src, tokens, top) { + var src = src.replace(/^ +$/gm, '') + , next + , loose + , cap + , item + , space + , i + , l; + + while (src) { + // newline + if (cap = block.newline.exec(src)) { + src = src.substring(cap[0].length); + if (cap[0].length > 1) { + tokens.push({ + type: 'space' + }); + } + } + + // code + if (cap = block.code.exec(src)) { + src = src.substring(cap[0].length); + cap = cap[0].replace(/^ {4}/gm, ''); + tokens.push({ + type: 'code', + text: !options.pedantic + ? cap.replace(/\n+$/, '') + : cap + }); + continue; + } + + // fences (gfm) + if (cap = block.fences.exec(src)) { + src = src.substring(cap[0].length); + tokens.push({ + type: 'code', + lang: cap[2], + text: cap[3] + }); + continue; + } + + // heading + if (cap = block.heading.exec(src)) { + src = src.substring(cap[0].length); + tokens.push({ + type: 'heading', + depth: cap[1].length, + text: cap[2] + }); + continue; + } + + // lheading + if (cap = block.lheading.exec(src)) { + src = src.substring(cap[0].length); + tokens.push({ + type: 'heading', + depth: cap[2] === '=' ? 1 : 2, + text: cap[1] + }); + continue; + } + + // hr + if (cap = block.hr.exec(src)) { + src = src.substring(cap[0].length); + tokens.push({ + type: 'hr' + }); + continue; + } + + // blockquote + if (cap = block.blockquote.exec(src)) { + src = src.substring(cap[0].length); + + tokens.push({ + type: 'blockquote_start' + }); + + cap = cap[0].replace(/^ *> ?/gm, ''); + + // Pass `top` to keep the current + // "toplevel" state. This is exactly + // how markdown.pl works. + block.token(cap, tokens, top); + + tokens.push({ + type: 'blockquote_end' + }); + + continue; + } + + // list + if (cap = block.list.exec(src)) { + src = src.substring(cap[0].length); + + tokens.push({ + type: 'list_start', + ordered: isFinite(cap[2]) + }); + + // Get each top-level item. + cap = cap[0].match(block.item); + + next = false; + l = cap.length; + i = 0; + + for (; i < l; i++) { + item = cap[i]; + + // Remove the list item's bullet + // so it is seen as the next token. + space = item.length; + item = item.replace(/^ *([*+-]|\d+\.) +/, ''); + + // Outdent whatever the + // list item contains. Hacky. + if (~item.indexOf('\n ')) { + space -= item.length; + item = !options.pedantic + ? item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '') + : item.replace(/^ {1,4}/gm, ''); + } + + // Determine whether item is loose or not. + // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/ + // for discount behavior. + loose = next || /\n\n(?!\s*$)/.test(item); + if (i !== l - 1) { + next = item[item.length-1] === '\n'; + if (!loose) loose = next; + } + + tokens.push({ + type: loose + ? 'loose_item_start' + : 'list_item_start' + }); + + // Recurse. + block.token(item, tokens); + + tokens.push({ + type: 'list_item_end' + }); + } + + tokens.push({ + type: 'list_end' + }); + + continue; + } + + // html + if (cap = block.html.exec(src)) { + src = src.substring(cap[0].length); + tokens.push({ + type: options.sanitize + ? 'paragraph' + : 'html', + pre: cap[1] === 'pre', + text: cap[0] + }); + continue; + } + + // def + if (top && (cap = block.def.exec(src))) { + src = src.substring(cap[0].length); + tokens.links[cap[1].toLowerCase()] = { + href: cap[2], + title: cap[3] + }; + continue; + } + + // top-level paragraph + if (top && (cap = block.paragraph.exec(src))) { + src = src.substring(cap[0].length); + tokens.push({ + type: 'paragraph', + text: cap[0] + }); + continue; + } + + // text + if (cap = block.text.exec(src)) { + // Top-level should never reach here. + src = src.substring(cap[0].length); + tokens.push({ + type: 'text', + text: cap[0] + }); + continue; + } + } + + return tokens; +}; + +/** + * Inline Processing + */ + +var inline = { + escape: /^\\([\\`*{}\[\]()#+\-.!_>])/, + autolink: /^<([^ >]+(@|:\/)[^ >]+)>/, + url: noop, + tag: /^<!--[^\0]*?-->|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/, + link: /^!?\[(inside)\]\(href\)/, + reflink: /^!?\[(inside)\]\s*\[([^\]]*)\]/, + nolink: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/, + strong: /^__([^\0]+?)__(?!_)|^\*\*([^\0]+?)\*\*(?!\*)/, + em: /^\b_((?:__|[^\0])+?)_\b|^\*((?:\*\*|[^\0])+?)\*(?!\*)/, + code: /^(`+)([^\0]*?[^`])\1(?!`)/, + br: /^ {2,}\n(?!\s*$)/, + text: /^[^\0]+?(?=[\\<!\[_*`]| {2,}\n|$)/ +}; + +inline._linkInside = /(?:\[[^\]]*\]|[^\]]|\](?=[^\[]*\]))*/; +inline._linkHref = /\s*<?([^\s]*?)>?(?:\s+['"]([^\0]*?)['"])?\s*/; + +inline.link = replace(inline.link) + ('inside', inline._linkInside) + ('href', inline._linkHref) + (); + +inline.reflink = replace(inline.reflink) + ('inside', inline._linkInside) + (); + +inline.normal = { + url: inline.url, + strong: inline.strong, + em: inline.em, + text: inline.text +}; + +inline.pedantic = { + strong: /^__(?=\S)([^\0]*?\S)__(?!_)|^\*\*(?=\S)([^\0]*?\S)\*\*(?!\*)/, + em: /^_(?=\S)([^\0]*?\S)_(?!_)|^\*(?=\S)([^\0]*?\S)\*(?!\*)/ +}; + +inline.gfm = { + url: /^(https?:\/\/[^\s]+[^.,:;"')\]\s])/, + text: /^[^\0]+?(?=[\\<!\[_*`]|https?:\/\/| {2,}\n|$)/ +}; + +/** + * Inline Lexer + */ + +inline.lexer = function(src) { + var out = '' + , links = tokens.links + , link + , text + , href + , cap; + + while (src) { + // escape + if (cap = inline.escape.exec(src)) { + src = src.substring(cap[0].length); + out += cap[1]; + continue; + } + + // autolink + if (cap = inline.autolink.exec(src)) { + src = src.substring(cap[0].length); + if (cap[2] === '@') { + text = cap[1][6] === ':' + ? mangle(cap[1].substring(7)) + : mangle(cap[1]); + href = mangle('mailto:') + text; + } else { + text = escape(cap[1]); + href = text; + } + out += '<a href="' + + href + + '">' + + text + + '</a>'; + continue; + } + + // url (gfm) + if (cap = inline.url.exec(src)) { + src = src.substring(cap[0].length); + text = escape(cap[1]); + href = text; + out += '<a href="' + + href + + '">' + + text + + '</a>'; + continue; + } + + // tag + if (cap = inline.tag.exec(src)) { + src = src.substring(cap[0].length); + out += options.sanitize + ? escape(cap[0]) + : cap[0]; + continue; + } + + // link + if (cap = inline.link.exec(src)) { + src = src.substring(cap[0].length); + out += outputLink(cap, { + href: cap[2], + title: cap[3] + }); + continue; + } + + // reflink, nolink + if ((cap = inline.reflink.exec(src)) + || (cap = inline.nolink.exec(src))) { + src = src.substring(cap[0].length); + link = (cap[2] || cap[1]).replace(/\s+/g, ' '); + link = links[link.toLowerCase()]; + if (!link || !link.href) { + out += cap[0][0]; + src = cap[0].substring(1) + src; + continue; + } + out += outputLink(cap, link); + continue; + } + + // strong + if (cap = inline.strong.exec(src)) { + src = src.substring(cap[0].length); + out += '<strong>' + + inline.lexer(cap[2] || cap[1]) + + '</strong>'; + continue; + } + + // em + if (cap = inline.em.exec(src)) { + src = src.substring(cap[0].length); + out += '<em>' + + inline.lexer(cap[2] || cap[1]) + + '</em>'; + continue; + } + + // code + if (cap = inline.code.exec(src)) { + src = src.substring(cap[0].length); + out += '<code>' + + escape(cap[2], true) + + '</code>'; + continue; + } + + // br + if (cap = inline.br.exec(src)) { + src = src.substring(cap[0].length); + out += '<br>'; + continue; + } + + // text + if (cap = inline.text.exec(src)) { + src = src.substring(cap[0].length); + out += escape(cap[0]); + continue; + } + } + + return out; +}; + +function outputLink(cap, link) { + if (cap[0][0] !== '!') { + return '<a href="' + + escape(link.href) + + '"' + + (link.title + ? ' title="' + + escape(link.title) + + '"' + : '') + + '>' + + inline.lexer(cap[1]) + + '</a>'; + } else { + return '<img src="' + + escape(link.href) + + '" alt="' + + escape(cap[1]) + + '"' + + (link.title + ? ' title="' + + escape(link.title) + + '"' + : '') + + '>'; + } +} + +/** + * Parsing + */ + +var tokens + , token; + +function next() { + return token = tokens.pop(); +} + +function tok() { + switch (token.type) { + case 'space': { + return ''; + } + case 'hr': { + return '<hr>\n'; + } + case 'heading': { + return '<h' + + token.depth + + '>' + + inline.lexer(token.text) + + '</h' + + token.depth + + '>\n'; + } + case 'code': { + if (options.highlight) { + token.code = options.highlight(token.text, token.lang); + if (token.code != null && token.code !== token.text) { + token.escaped = true; + token.text = token.code; + } + } + + if (!token.escaped) { + token.text = escape(token.text, true); + } + + return '<pre><code' + + (token.lang + ? ' class="lang-' + + token.lang + + '"' + : '') + + '>' + + token.text + + '</code></pre>\n'; + } + case 'blockquote_start': { + var body = ''; + + while (next().type !== 'blockquote_end') { + body += tok(); + } + + return '<blockquote>\n' + + body + + '</blockquote>\n'; + } + case 'list_start': { + var type = token.ordered ? 'ol' : 'ul' + , body = ''; + + while (next().type !== 'list_end') { + body += tok(); + } + + return '<' + + type + + '>\n' + + body + + '</' + + type + + '>\n'; + } + case 'list_item_start': { + var body = ''; + + while (next().type !== 'list_item_end') { + body += token.type === 'text' + ? parseText() + : tok(); + } + + return '<li>' + + body + + '</li>\n'; + } + case 'loose_item_start': { + var body = ''; + + while (next().type !== 'list_item_end') { + body += tok(); + } + + return '<li>' + + body + + '</li>\n'; + } + case 'html': { + return !token.pre && !options.pedantic + ? inline.lexer(token.text) + : token.text; + } + case 'paragraph': { + return '<p>' + + inline.lexer(token.text) + + '</p>\n'; + } + case 'text': { + return '<p>' + + parseText() + + '</p>\n'; + } + } +} + +function parseText() { + var body = token.text + , top; + + while ((top = tokens[tokens.length-1]) + && top.type === 'text') { + body += '\n' + next().text; + } + + return inline.lexer(body); +} + +function parse(src) { + tokens = src.reverse(); + + var out = ''; + while (next()) { + out += tok(); + } + + tokens = null; + token = null; + + return out; +} + +/** + * Helpers + */ + +function escape(html, encode) { + return html + .replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&') + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +} + +function mangle(text) { + var out = '' + , l = text.length + , i = 0 + , ch; + + for (; i < l; i++) { + ch = text.charCodeAt(i); + if (Math.random() > 0.5) { + ch = 'x' + ch.toString(16); + } + out += '&#' + ch + ';'; + } + + return out; +} + +function tag() { + var tag = '(?!(?:' + + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code' + + '|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo' + + '|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|@)\\b'; + + return tag; +} + +function replace(regex, opt) { + regex = regex.source; + opt = opt || ''; + return function self(name, val) { + if (!name) return new RegExp(regex, opt); + val = val.source || val; + val = val.replace(/(^|[^\[])\^/g, '$1'); + regex = regex.replace(name, val); + return self; + }; +} + +function noop() {} +noop.exec = noop; + +/** + * Marked + */ + +function marked(src, opt) { + setOptions(opt); + return parse(block.lexer(src)); +} + +/** + * Options + */ + +var options + , defaults; + +function setOptions(opt) { + if (!opt) opt = defaults; + if (options === opt) return; + options = opt; + + if (options.gfm) { + block.fences = block.gfm.fences; + block.paragraph = block.gfm.paragraph; + inline.text = inline.gfm.text; + inline.url = inline.gfm.url; + } else { + block.fences = block.normal.fences; + block.paragraph = block.normal.paragraph; + inline.text = inline.normal.text; + inline.url = inline.normal.url; + } + + if (options.pedantic) { + inline.em = inline.pedantic.em; + inline.strong = inline.pedantic.strong; + } else { + inline.em = inline.normal.em; + inline.strong = inline.normal.strong; + } +} + +marked.options = +marked.setOptions = function(opt) { + defaults = opt; + setOptions(opt); + return marked; +}; + +marked.setOptions({ + gfm: true, + pedantic: false, + sanitize: false, + highlight: null +}); + +/** + * Expose + */ + +marked.parser = function(src, opt) { + setOptions(opt); + return parse(src); +}; + +marked.lexer = function(src, opt) { + setOptions(opt); + return block.lexer(src); +}; + +marked.parse = marked; + +if (typeof module !== 'undefined') { + module.exports = marked; +} else { + this.marked = marked; +} + +}).call(function() { + return this || (typeof window !== 'undefined' ? window : global); +}());