Blogger Markdown support
By Jasper de Vries
—
Last update Jan 5, 2006
—
Installed
1,534 times.
Add Syntax Highlighting (this will take a few seconds, probably freezing your browser while it works)
// ==UserScript==
// @name Blogger Markdown support
// @namespace http://browservulsel.blogspot.com/
// @description v0.2 - Adds Markdown support to the HTML editor
// @include http://*blogger.com/post-*
// ==/UserScript==
/*
Author: Jasper de Vries, jepsar@gmail.com
Date: 2006-01-23
*/
GM_addStyle(
'span#htmlbar_Markdown, span#htmlbar_Syntax { '+
' font-size: 95% !important; '+
' margin-right: 3px; '+
' padding: .1em .2em 0 .1em; '+
' border-bottom: 1px solid #00c; '+
' color: #00c; '+
'}'
);
unsafeWindow.addEventListener('load', function(){
var ta = document.getElementById('textarea');
var htmlbar = document.getElementById('htmlbar');
var previewAction = document.getElementById('htmlbar_PreviewAction');
if (ta && htmlbar && previewAction) {
var mdBtn = document.createElement('span');
mdBtn.setAttribute('id', 'htmlbar_Markdown');
mdBtn.setAttribute('onmouseover', 'ButtonHoverOn(this)');
mdBtn.setAttribute('onmouseout', 'ButtonHoverOff(this)');
mdBtn.innerHTML = 'Markdown';
mdBtn.addEventListener('click', function(){
ta.value = Markdown(ta.value);
}, false);
htmlbar.insertBefore(mdBtn, previewAction);
var syntaxBtn = document.createElement('span');
syntaxBtn.setAttribute('id', 'htmlbar_Syntax');
syntaxBtn.setAttribute('onmouseover', 'ButtonHoverOn(this)');
syntaxBtn.setAttribute('onmouseout', 'ButtonHoverOff(this)');
syntaxBtn.innerHTML = 'Syntax';
syntaxBtn.addEventListener('click', function(){
GM_openInTab('http://daringfireball.net/projects/markdown/syntax');
}, false);
htmlbar.insertBefore(syntaxBtn, previewAction);
previewAction.addEventListener('mousedown', function(){
setTimeout(function(){
unsafeWindow.document.getElementById("previewbody").innerHTML = Markdown(ta.value);
}, 100);
}, false);
}
}, false);
// Folowing script is copied as is from http://rephrase.net/box/js-markdown/thingy/markdown.js
//
// Markdown - A text-to-HTML conversion tool for web writers
//
// Copyright (c) 2005 John Gruber
// <http://daringfireball.net/projects/markdown/>
//
// Copyright (c) 2005 Michel Fortin - PHP Port
// <http://www.michelf.com/projects/php-markdown/>
//
// Copyright (c) 2005 Sam Angove - this JavaScript port
// <http://rephrase.net/box/js-markdown/>
//
// Most of this was ported directly from the Perl version, with
// the PHP used as a reference. All credit goes to John and Michel.
//
//
// (Except for the bugs I introduced -- they're all mine.)
//
// Version 1.01b4, 2005.01.06
// Based on Markdown v1.01
//
// 1.01 b2: mostly working, announce on markdown-discuss
// 1.01 b3: fix stupid HTML comments bug (I want to save $2, not $1...)
// remove extra newlines in blockquotes (Symptom, I think, not problem.)
// 1.01 b4: kludge: add an extra newline in DoLists, fixes a whitespace error
// Globals
md_empty_element_suffix = " />";
md_tab_width = 4
md_tab_width = 4;
md_html_blocks = new Array();
md_urls = new Array();
md_titles = new Array();
md_list_level = 0;
md_nested_brackets_depth = 6;
md_nested_brackets = str_repeat('[^\\[\\]]+|\\[', md_nested_brackets_depth) + str_repeat('\\]*', md_nested_brackets_depth);
function Markdown(text) {
// Kludge #1: add some whitespace; otherwise you can't start a document with a list.
text = "\n\n"+text;
text = text.replace("\r\n", "\n");
text = text.replace("\r", "\n");
text += "\n\n";
text = Detab(text);
text = text.replace('/^[ \t]+$/m', '');
text = HashHTMLBlocks(text);
text = StripLinkDefinitions(text);
text = RunBlockGamut(text);
//text = EncodeAmpsAndAngles(text);
text = UnescapeSpecialChars(text); // uh, again
text = text.replace(/^(\n|\s)*/, "").replace(/(\n|\s)*$/, ""); // in case my added whitespace is still there
return text+"\n";
}
function fakemd5(chars) {
// Paj's JavaScript md5 library is a full 9KB, and I imagine it's way slower than this.
//
// Since we only really *need* it to generate unique ids that are unlikely
// to collide with normal text, any freakish and unique string will do.
// This uses different characters to the real md5 sums (they're still used to encode special
// characters), but any can be used, really.
if (!chars) {
chars = "qvxptrghj";
}
r = "";
for (i=0; i<32; i++) {
rand = Math.floor(Math.random()*chars.length);
r += chars.charAt(rand)
}
return r;
}
//
// a friendly helper function
function str_repeat(str, count) {
out="";
for (i = 0; i < count; i++) {
out+=str;
}
return out;
}
function Detab(text) {
// global md_tab_width
text = text.replace(/(.*?)\t/g, function(match, substr) {
return substr += str_repeat(" ", (md_tab_width - substr.length % md_tab_width));
});
return text;
}
//
// Since there's no genuine md5 encoding included anymore, these values are hardcoded.
//
// There's doubtless a more efficient way of doing this.
//
function UnescapeSpecialChars(text) {
text = text.replace(/7f8137798425a7fed2b8c5703b70d078/gm, "\\")
text = text.replace(/833344d5e1432da82ef02e1301477ce8/gm, "`")
text = text.replace(/3389dae361af79b04c9c8e7057f60cc6/gm, "*")
text = text.replace(/b14a7b8059d9c055954c92674ce60032/gm, "_")
text = text.replace(/f95b70fdc3088560732a5ac135644506/gm, "{")
text = text.replace(/cbb184dd8e05c9709e5dcaedaa0495cf/gm, "}")
text = text.replace(/815417267f76f6f460a4a61f9db75fdb/gm, "[")
text = text.replace(/0fbd1776e1ad22c59a7080d35c7fd4db/gm, "]")
text = text.replace(/84c40473414caf2ed4a7b1283e48bbf4/gm, "(")
text = text.replace(/9371d7a2e3ae86a00aab4771e39d255d/gm, ")")
text = text.replace(/01abfc750a0c942167651c40d088531d/gm, "#")
text = text.replace(/5058f1af8388633f609cadb75a75dc9d/gm, ".")
text = text.replace(/9033e0e305f247c0c3c80d0c7848c8b3/gm, "!")
text = text.replace(/853ae90f0351324bd73ea615e6487517/gm, ":")
return text;
}
function Houdini(trick) {
// Houdini, escape... ? It was very late, I was tired, okay?
trick = trick.replace(/\\/mg, "7f8137798425a7fed2b8c5703b70d078");
trick = trick.replace(/\`/mg, "833344d5e1432da82ef02e1301477ce8");
trick = trick.replace(/\*/mg, "3389dae361af79b04c9c8e7057f60cc6");
trick = trick.replace(/\_/mg, "b14a7b8059d9c055954c92674ce60032");
trick = trick.replace(/\{/mg, "f95b70fdc3088560732a5ac135644506");
trick = trick.replace(/\}/mg, "cbb184dd8e05c9709e5dcaedaa0495cf");
trick = trick.replace(/\[/mg, "815417267f76f6f460a4a61f9db75fdb");
trick = trick.replace(/\]/mg, "0fbd1776e1ad22c59a7080d35c7fd4db");
trick = trick.replace(/\(/mg, "84c40473414caf2ed4a7b1283e48bbf4");
trick = trick.replace(/\)/mg, "9371d7a2e3ae86a00aab4771e39d255d");
trick = trick.replace(/\#/mg, "01abfc750a0c942167651c40d088531d");
trick = trick.replace(/\./mg, "5058f1af8388633f609cadb75a75dc9d");
trick = trick.replace(/\!/mg, "9033e0e305f247c0c3c80d0c7848c8b3");
trick = trick.replace(/\:/mg, "853ae90f0351324bd73ea615e6487517");
return trick;
}
function antiEm(toto) {
//there's no place like home
toto = toto.replace(/\*/mg, "3389dae361af79b04c9c8e7057f60cc6");
toto = toto.replace(/\_/mg, "b14a7b8059d9c055954c92674ce60032");
return toto;
}
function EncodeBackslashEscapes(trick) {
trick = trick.replace(/\\\\/mg, "7f8137798425a7fed2b8c5703b70d078");
trick = trick.replace(/\\\`/mg, "833344d5e1432da82ef02e1301477ce8");
trick = trick.replace(/\\\*/mg, "3389dae361af79b04c9c8e7057f60cc6");
trick = trick.replace(/\\\_/mg, "b14a7b8059d9c055954c92674ce60032");
trick = trick.replace(/\\\{/mg, "f95b70fdc3088560732a5ac135644506");
trick = trick.replace(/\\\}/mg, "cbb184dd8e05c9709e5dcaedaa0495cf");
trick = trick.replace(/\\\[/mg, "815417267f76f6f460a4a61f9db75fdb");
trick = trick.replace(/\\\]/mg, "0fbd1776e1ad22c59a7080d35c7fd4db");
trick = trick.replace(/\\\(/mg, "84c40473414caf2ed4a7b1283e48bbf4");
trick = trick.replace(/\\\)/mg, "9371d7a2e3ae86a00aab4771e39d255d");
trick = trick.replace(/\\\#/mg, "01abfc750a0c942167651c40d088531d");
trick = trick.replace(/\\\./mg, "5058f1af8388633f609cadb75a75dc9d");
trick = trick.replace(/\\\!/mg, "9033e0e305f247c0c3c80d0c7848c8b3");
trick = trick.replace(/\\\:/mg, "853ae90f0351324bd73ea615e6487517");
return trick;
}
function UnslashQuotes(text) {
/*#
# This function is useful to remove automaticaly slashed double quotes
# when using preg_replace and evaluating an expression.
# Parameter: String.
# Returns: The string with any slash-double-quote (\") sequence replaced
# by a single double quote.
#*/
return text.replace("\\\"", "\"");
}
function DoItalicsAndBold(text) {
//# <strong> must go first:
text = text.replace(/(__)([^_]+)(__)/g, "<strong>$2</strong>");
text = text.replace(/(\*\*)([^\*]+)(\*\*)/g, "<strong>$2</strong>");
//# Then <em>:
text = text.replace(/(_)([^_]+)(_{1})/g, "<em>$2</em>");
text = text.replace(/(\*)([^\*]+)(\*{1})/g, "<em>$2</em>");
return text;
}
function DoAnchors(text) {
// Turn Markdown link shortcuts into XHTML <a> tags.
//
//
// First, handle reference-style links: [link text] [id]
//
r = "(\\[("+md_nested_brackets+")\\][ ]?(?:\\n[ ]*)?\\[([\\S\\s]*?)\\])";
i = new RegExp(r, "g");
text = text.replace(i, function(match, str1, str2, str3, str4, str5, str6, str7, str8, str9) {
whole_match = str1;
link_text = str2;
link_id = str3.toLowerCase();
if (link_id == "") {
link_id = link_text.toLowerCase(); // for shortcut links like [this][].
}
if (md_urls[link_id]) {
url = md_urls[link_id];
url = antiEm(url);
result = '<a href="'+url+'"';
if (md_titles[link_id]) {
title = md_titles[link_id];
title = antiEm(title);
result += " title=\""+title+"\"";
}
result += ">"+link_text+"</a>";
}
else {
result = whole_match;
}
return result;
} );
// Next, inline-style links: [link text](url "optional title")
r = "(\\[("+md_nested_brackets+")\\][ \\t]*\\(<?(\\S*)>?[ \\t]*((['\"])(.*?)\\5)?\\))";
i = new RegExp(r, "g");
//i = /(\[(>[^\[\]]+|\[)\]\([ \t]*<?(.*?)>?[ \t]*((['"])(.*?)\5)?\))/g
text = text.replace(i, function(match, str1, str2, str3, str4, str5, str6) {
whole_match = str1;
link_text = str2;
url = str3;
title = str6;
url = antiEm(url);
result = '<a href="'+url+'"';
if (title) {
title = title.replace("\"", """);
url = antiEm(url);
result += " title=\""+title+"\"";
}
result += ">"+link_text+"</a>";
return result;
} );
return text;
}
function DoImages(text) {
//
// Turn Markdown image shortcuts into <img> tags.
//
//
// First, handle reference-style labeled images: ![alt text][id]
//
text = text.replace(/(!\[([\s\S]*?)\][ ]?(?:\n[ ]*)?\[([\s\S]*?)\])/gm, function(match, str1, str2, str3) {
whole_match = str1;
alt_text = str2;
link_id = str3.toLowerCase();
if (link_id == "") {
link_id = alt_text.toLowerCase(); // for shortcut links like ![this][].
}
alt_text = alt_text.replace("\"", """);
if (md_urls[link_id]) {
url = md_urls[link_id];
url = antiEm(url);
result = "<img src="+url+" alt=\""+alt_text+"\"";
if (md_titles[link_id]) {
title = md_titles[link_id];
title = antiEm(title);
result += " title=\""+title+"\"";
}
result += md_empty_element_suffix;
} else {
// If there's no such link ID, leave intact:
result = whole_match;
}
return result;
} );
//
// Next, handle inline images: 
// Don't forget: encode * and _
text = text.replace(/(!\[([\s\S]*?)\]\([ \t]*<?(\S+?)>?[ \t]*(([\'\"])([\s\S]*?)\5[ \t]*)?\))/g, function(match, str1, str2, str3, str4, str5, str6) {
whole_match = str1;
alt_text = str2;
url = str3;
title = '';
if (str6) {
title = str6;
}
alt_text = alt_text.replace("\"", """);
title = title.replace("\"", """);
url = antiEm(url);
result = "<img src="+url+" alt=\""+alt_text+"\"";
if (title) {
title = antiEm(title);
result += " title=\""+title+"\"";
}
result += md_empty_element_suffix;
return result;
} );
return text;
}
function DoCodeSpans(text) {
/*#
# * Backtick quotes are used for <code></code> spans.
#
# * You can use multiple backticks as the delimiters if you want to
# include literal backticks in the code span. So, this input:
#
# Just type ``foo `bar` baz`` at the prompt.
#
# Will translate to:
#
# <p>Just type <code>foo `bar` baz</code> at the prompt.</p>
#
# There's no arbitrary limit to the number of backticks you
# can use as delimters. If you need three consecutive backticks
# in your code, use four for delimiters, etc.
#
# * You can use spaces to get literal backticks at the edges:
#
# ... type `` `bar` `` ...
#
# Turns to:
#
# ... type <code>`bar`</code> ...
#
text = text.replace(/([\`]+)(.+?)(\1)/g, "<code>$2</code>");
*/
r = /(`+)(.+?)\1(?!`)/g
text = text.replace(r, function(match, str1, str2) {
str2 = str2.replace(/^[ \t]*/g, "");
str2 = str2.replace(/[ \t]*$/g, "");
str2 = EncodeCode(str2);
return "<code>"+str2+"</code>";
} );
return text;
}
function EncodeCode(c) {
c = c.replace(/\&/gm, '&');
c = c.replace(/\</gm, '<');
c = c.replace(/\>/gm, '>');
c = Houdini(c);
return c;
}
function RunSpanGamut(text) {
/* #
# These are all the transformations that occur *within* block-level
# tags like paragraphs, headers, and list items.
# */
text = DoCodeSpans(text);
// moved "do hard breaks" up here 'cos EscapeSpecialChars eats trailing spaces.
// don't *think* it breaks anything...
text = text.replace(/[ ]{2,}$\n/gm, "<br"+md_empty_element_suffix+"\n");
text = EscapeSpecialChars(text);
//# Process anchor and image tags. Images must come first,
//# because ![foo][f] looks like an anchor.
text = DoImages(text);
text = DoAnchors(text);
//# Make links out of things like `<http://example.com/>`
//# Must come after _DoAnchors(), because you can use < and >
//# delimiters in inline links like [this](<url>).
text = DoAutoLinks(text);
//# Fix unencoded ampersands and <'s:
text = EncodeAmpsAndAngles(text);
text = DoItalicsAndBold(text);
return text;
}
function DoHeaders(text) {
r = "^(.+)[ \\t]*\\n=+[ \\t]*\\n+";
i = new RegExp(r, "gm");
text = text.replace(i, function(match, str1) { return "<h1>"+RunSpanGamut(str1)+"</h1>\n\n"; } );
r = "^(.+)[ \\t]*\\n-+[ \\t]*\\n+";
i = new RegExp(r, "gm");
text = text.replace(i, function(match, str1) { return "<h2>"+RunSpanGamut(str1)+"</h2>\n\n"; } );
r = "^(\#{1,6})[ \\t]*(.+?)[ \\t]*\#*\\n+";
reg = new RegExp(r, "gm");
text = text.replace(reg, function(match, str1, str2) {
h_level = str1.length;
return "<h"+h_level+">"+RunSpanGamut(str2)+"</h"+h_level+">\n\n";
});
return text;
}
function Outdent(text) {
r = "^(\\\\t|[ ]{1,"+md_tab_width+"})";
i = new RegExp(r, "gm");
return text.replace(i, "");
}
function rtrim ( $s )
{
return $s.replace( /\s*$/, "" );
}
function ProcessListItems(list_str, marker_any) {
/*
# The $g_list_level global keeps track of when we're inside a list.
# Each time we enter a list, we increment it; when we leave a list,
# we decrement. If it's zero, we're not in a list anymore.
#
# We do this because when we're not inside a list, we want to treat
# something like this:
#
# I recommend upgrading to version
# 8. Oops, now this line is treated
# as a sub-list.
#
# As a single paragraph, despite the fact that the second line starts
# with a digit-period-space sequence.
#
# Whereas when we're inside a list (or sub-list), that line will be
# treated as the start of a sub-list. What a kludge, huh? This is
# an aspect of Markdown's syntax that's hard to parse perfectly
# without resorting to mind-reading. Perhaps the solution is to
# change the syntax rules such that sub-lists must start with a
# starting cardinal number; e.g. "1." or "a.".
*/
md_list_level++;
// Trim trailing blank lines.
// JS has no \z for end of data -- so I'll add a ridiculous flag on the end and check for it.
// $ without /m is end of input (instead of end-of-line), but that doesn't work here?
// And to think John called the md_list_level thing a kludge...
eflag = "someunlikelystringrahrahrah";
//eflag = fakemd5("kasldklasd");
list_str = list_str.replace(/([\s\S]*)\n{2,}/gm, "$1"+eflag);
// "$1\n"+eflag gets nested lists working a bit, but causes other problems
// Original regexp with \z
// r = "(\\n)?(^[ \\t]*)("+marker_any+")[ \\t]+((?:[\\s\\S]+?)(\\n{1,2}))(?=\\n*(\\z|\\2("+marker_any+")[ \\t]+))";
r = "(\\n)?(^[ \\t]*)("+marker_any+")[ \\t]+((?:[\\s\\S]+?)(\\n{1,2}))(?=\\n*("+eflag+"|\\2("+marker_any+")[ \\t]+))";
reg = new RegExp(r, "gm");
list_str = list_str.replace(reg, function(match, str1, str2, str3, str4) {
item = str4;
leading_line = str1;
leading_space = str2;
if (leading_line || (item.match(/\n{2,}/gm))) {
item = RunBlockGamut(item);
} else {
//# Recursion for sub-lists:
item = DoLists(Outdent(item));
item = item.replace(/\n*$/, ""); // does this replace chomp/rtrim?
item = RunSpanGamut(item);
}
return "<li>" + item + "</li>\n";
} );
md_list_level--;
list_str = list_str.replace(eflag, "");
return list_str;
}
function DoLists(text) {
less_than_tab = md_tab_width - 1;
//# Re-usable patterns to match list item bullets and number markers:
marker_ul = '[*+-]';
marker_ol = '\\d+[.]';
marker_any = '(?:[*+-]|\\d+[.])';
//whole_list = '(([ ]{0,'+less_than_tab+'}((?:[*+-]|\\d+[.]))[ \\t]+)(?:[\\s\\S]+?)(\\z|\\n{2,}(?=\\S)(?![ \\t]*(?:[*+-]|\\d+[.])[ \\t]+)))';
whole_list = '(([ ]{0,'+less_than_tab+'}((?:[*+-]|\\d+[.]))[ \\t]+)(?:[\\s\\S]+?)(\$|\\n{2,}(?=\\S)(?![ \\t]*(?:[*+-]|\\d+[.])[ \\t]+)))';
flag = "ridiculousendlistflag";
text = text+flag;
/*
# We use a different prefix before nested lists than top-level lists.
# See extended comment in _ProcessListItems().
#
# Note: There's a bit of duplication here. My original implementation
# created a scalar regex pattern as the conditional result of the test on
# $g_list_level, and then only ran the text =~ s{...}{...}egmx
# substitution once, using the scalar as the pattern. This worked,
# everywhere except when running under MT on my hosting account at Pair
# Networks. There, this caused all rebuilds to be killed by the reaper (or
# perhaps they crashed, but that seems incredibly unlikely given that the
# same script on the same server ran fine *except* under MT. I've spent
# more time trying to figure out why this is happening than I'd like to
# admit. My only guess, backed up by the fact that this workaround works,
# is that Perl optimizes the substition when it can figure out that the
# pattern will never change, and when this optimization isn't on, we run
# afoul of the reaper. Thus, the slightly redundant code to that uses two
# static s/// patterns rather than one conditional pattern. */
if (md_list_level) {
r = '^(([ ]{0,3}((?:[*+-]|\\d+[.]))[ \\t]+)(?:[\\s\\S]+?)('+flag+'|\\n{2,}(?=\\S)(?![ \\t]*(?:[*+-]|\\d+[.])[ \\t]+)))';
i = new RegExp(r, "gm");
text = text.replace(i, function(match, str1, str2, str3) {
list = str1;
i = new RegExp(marker_ul, "gm");
if (str3.match(i)) {
list_type = "ul";
} else {
list_type = "ol";
}
//# Turn double returns into triple returns, so that we can make a
//# paragraph for the last item in a list, if necessary:
list = list.replace(/\n{2,}/g, '\n\n\n');
result = ProcessListItems(list, marker_any);
result = "<"+list_type+">\n"+result+"</"+list_type+">\n";
return result;
} );
} else {
r = '(?:\\n\\n|\\A\\n?)(([ ]{0,3}((?:[*+-]|\\d+[.]))[ \\t]+)(?:[\\s\\S]+?)('+flag+'|\\n{2,}(?=\\S)(?![ \\t]*(?:[*+-]|\\d+[.])[ \\t]+)))';
i = new RegExp(r, "gm");
text = text.replace(i, function(match, str1, str2, str3) {
list = str1;
i = new RegExp(marker_ul, "gm");
if (str3.match(i)) {
list_type = "ul";
} else {
list_type = "ol";
}
//# Turn double returns into triple returns, so that we can make a
//# paragraph for the last item in a list, if necessary:
list = list.replace(/\n{2,}/g, '\n\n\n');
result = ProcessListItems(list, marker_any);
result = "\n<"+list_type+">\n" + result + "</"+list_type+">\n";
return result;
} );
}
//alert(text);
//alert(flag);
text = text.replace(flag, "");
return text;
}
function DoCodeBlocks(text) {
//#
//# Process Markdown `<pre><code>` blocks.
//#
// $m_tab_width hard coded?
r = "(?:\\n\\n|\\A)((?:(?:[ ]{"+md_tab_width+"}|\\t).*\\n+)+)((?=^[ ]{0,"+md_tab_width+"}\\S)|\$)";
i = new RegExp(r, "gm");
text = text.replace(i, function(match, str1) {
codeblock = str1;
codeblock = EncodeCode(Outdent(codeblock));
codeblock = Detab(codeblock);
codeblock = codeblock.replace(/\A\n+/gm, "");
codeblock = codeblock.replace(/\s+$/g, "");
result = "\n\n<pre><code>" + codeblock + "\n</code></pre>\n\n";
return result;
} );
return text;
}
function DoBlockQuotes(text) {
text = text.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm, function(match, str1) {
bq = str1;
bq = bq.replace(/^[ \t]*>[ \t]?/gm, ""); //# trim one level of quoting
bq = bq.replace(/^[ \t]+$/gm, ""); //# trim whitespace-only lines
bq = RunBlockGamut(bq); //# recurse
bq = bq.replace(/\n+$/, "");
bq = bq.replace(/^/gm, " ");
//# These leading spaces screw with <pre> content, so we need to fix that:
bq = bq.replace(/(\s*<pre>[\s\S]+?<\/pre>)/g, function(match, str1) {
pre = str1;
pre = pre.replace(/^ /mg, "");
return pre;
} );
return "<blockquote>\n"+bq+"\n</blockquote>\n\n";
} );
return text;
}
function dechex($char) {
$char = $char.toString(16)
return $char;
}
function EncodeEmailAddress($addr) {
$matches = $addr.match(/([^\:])/g);
$r = Math.round(Math.random()*100);
$newaddr = "";
for (var $match in $matches) {
$newaddr += rencode($matches[$match]);
}
$m = ""
for ($i=0; $i<6; $i++) {
$m+=rencode("mailto".charAt($i));
}
$addr = '<a href="'+$m+':'+$newaddr+'">'+$newaddr+'</a>';
return $addr;
}
function rencode($char) {
$r = Math.round(Math.random()*100);
// roughly 10% raw, 45% hex, 45% dec
// '@' *must* be encoded. I insist.
if ($r > 90 && $char != "@") { return $char; }
else if ($r < 45) { return "&#x"+$char.charCodeAt(0).toString(16)+";"; }
else { return "&#"+$char.charCodeAt(0)+";"; }
}
function DoAutoLinks(text) {
text = text.replace(/<((https?|ftp):[^'">\s]+)>/gi, function(match, str1) {
str1 = antiEm(str1);
return '<a href="'+str1+'">'+str1+'</a>';
});
//# Email addresses: <address@domain.foo>
text = text.replace(/<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gmi, function(match, str1) {
return EncodeEmailAddress(UnescapeSpecialChars(str1));
} );
//text = Houdini(text); // hashify special characters.
return text;
}
function FormParagraphs(text) {
//#
//# Params:
//# text - string to process with html <p> tags
//#
//# Strip leading and trailing lines:
text = text.replace(/\A\n+/mg, "");
text = text.replace(/\n+\z/mg, "");
grafs = text.split(/\n{2,}/mg);
//#
//# Wrap <p> tags.
//#
for (var i in grafs) {
if (!md_html_blocks[grafs[i]]) {
// don't process blocks (all wrapped in some <tag>, but let through <autolinks> -- another kludge for you to enjoy
if (grafs[i] && (grafs[i].match(/\S+/gm)) && !grafs[i].match(/^[<](?!((https?|ftp):[^'">\s]+)>|(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>)/mi)) {
grafs[i] = RunSpanGamut(grafs[i]);
grafs[i] = "<p>"+grafs[i];
//grafs[i] = grafs[i].replace(/^([ \t]*)/mg, "<p>");
grafs[i] += "</p>";
}
}
}
//#
//# Unhashify HTML blocks
//#
for (var i in grafs) {
if (md_html_blocks[grafs[i]]) {
grafs[i] = md_html_blocks[grafs[i]];
}
}
return grafs.join("\n\n");
}
function RunBlockGamut(text) {
//
// These are all the transformations that form block-level
// tags like paragraphs, headers, and list items.
//
text = DoHeaders(text);
// Do Horizontal Rules:
text = text.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm, "\n<hr"+md_empty_element_suffix+"\n");
text = text.replace(/^[ ]{0,2}([ ]?-[ ]?){3,}[ \t]*$/gm, "\n<hr"+md_empty_element_suffix+"\n");
text = text.replace(/^[ ]{0,2}([ ]?_[ ]?){3,}[ \t]*$/gm, "\n<hr"+md_empty_element_suffix+"\n");
text = DoLists(text); // Half working, TO DO FIXME!
text = DoCodeBlocks(text);
text = DoBlockQuotes(text); // nested blockquotes don't work
//# We already ran _HashHTMLBlocks() before, in Markdown(), but that
//# was to escape raw HTML in the original Markdown source. This time,
//# we're escaping the markup we've just created, so that we don't wrap
//# <p> tags around block-level tags.
text = HashHTMLBlocks(text);
text = FormParagraphs(text);
return text;
}
function RunBlockQuoteGamut(text) {
// recursion doesn't work at present
text = DoHeaders(text);
text = text.replace(/^( ?\* ?){3,}$/m, "\n<hr"+md_empty_element_suffix+"\n");
text = text.replace(/^( ?- ?){3,}$/m, "\n<hr"+md_empty_element_suffix+"\n");
text = text.replace(/^( ?_ ?){3,}$/m, "\n<hr"+md_empty_element_suffix+"\n");
text = DoLists(text); // Half working, TO DO FIXME!
text = DoCodeBlocks(text);
text = DoAutoLinks(text);
text = HashHTMLBlocks(text);
text = FormParagraphs(text);
return text;
}
/*
function EncodeBackslashEscapes(text) {
//
// Parameter: String.
// Returns: The string, with after processing the following backslash
// escape sequences.
//
// Must process escaped backslashes first.
for (var $i in md_backslash_escape_table)
{
text = text.replace($i, md_backslash_escape_table[$i]);
}
return text;
}
*/
function HashHTMLBlocks(text) {
//
// global md_tab_width
less_than_tab = md_tab_width-1;
block_tags_a = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del';
block_tags_b = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math'
r = "(^<("+block_tags_a+")\\b(.*\\n)*?<\\/\\2>[ \\t]*(?=(\\n+|\\Z)))";
reg = new RegExp(r, "gm");
text = text.replace(reg, function(match, str1, str2) {
key=fakemd5();
while (md_html_blocks[key]) {
key=fakemd5();
}
md_html_blocks[key] = str1;
return "\n\n"+key+"\n\n";
});
r = "(^<("+block_tags_b+")\\b(.*\\n)*?.*<\\/\\2>[ \\t]*(?=(\\n+|\\Z)))";
reg = new RegExp(r, "gm");
text = text.replace(reg, function(match, str1, str2) {
key=fakemd5();
while (md_html_blocks[key]) {
key=fakemd5();
}
md_html_blocks[key] = str1;
return "\n\n"+key+"\n\n";
});
//
// Special case for <hr />. Since JS doesn't support lookbehind, mightn't work right.
r = "(?:(\\n\\n)|\\A\\n?)([ ]{0,"+less_than_tab+"}<(hr)\\b([^<>])*?\\/?>[ \\t]*(?=\\n{2,}|\\Z))";
reg = new RegExp(r, "gm");
text = text.replace(reg, function(match, str1, str2) {
key=fakemd5();
while (md_html_blocks[key]) {
key=fakemd5();
}
md_html_blocks[key] = str2;
return "\n\n"+key+"\n\n";
});
//
// Special case for standalone comments. Same as above -- no lookbehind, mightn't work right.
r = "(?:(\\n\\n)|\\A\\n?)([ ]{0,"+less_than_tab+"}(?:<!(--[\\s\\S]*?--\\s*)+>)[ \\t]*(?=\\n{2,}|\\Z))";
reg = new RegExp(r, "gm");
text = text.replace(reg, function(match, str1, str2) {
key=fakemd5();
while (md_html_blocks[key]) {
key=fakemd5();
}
md_html_blocks[key] = str2;
return "\n\n"+key+"\n\n";
});
return text;
}
function StripLinkDefinitions(text) {
r = "^[ ]{0,"+less_than_tab+"}\\[(.+)\\]:[ \\t]*\\n?[ \\t]*<?(\\S+)>?[ \\t]*\\n?[ \\t]*(?:[\"(](.+?)[\")][ \\t]*)?(?:\\n+|\\Z)";
reg = new RegExp(r, "gm");
text = text.replace(reg, function(match, str1, str2, str3) {
link_id = str1.toLowerCase();
md_urls[link_id] = EncodeAmpsAndAngles(str2);
if (str3) {
md_titles[link_id] = str3;
}
return "";
} );
return text;
}
function htmlentities(text) {
// Note: just uses the hex entities. Easier than a gigantic associative array.
$re = /(%([a-zA-Z0-9]{1,4}))/g
$escaped = escape(text);
$entitized = $escaped.replace($re, "&#x$2;")
return $entitized;
}
function EncodeAmpsAndAngles(text) {
// Smart processing for ampersands and angle brackets that need to be encoded.
text = text.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g, "&");
text = text.replace(/<(?![a-z\/?\$!])/gi, "<");
return text;
}
function EscapeSpecialChars(text) {
tokens = TokenizeHTML(text);
text = '';
// $in_pre = 0; # Keep track of when we're inside <pre> or <code> tags.
// $tags_to_skip = "!<(/?)(?:pre|code|kbd|script|math)[\s>]!";
for (var i in tokens) {
if (tokens[i].type == "tag") {
// Within tags, encode * and _ so they don't conflict
// with their use in Markdown for italics and strong.
// We're replacing each such character with its
// corresponding MD5 checksum value; this is likely
// overkill, but it should prevent us from colliding
// with the escape values by accident.
t = tokens[i].value;
t = antiEm(t);
text += t;
} else {
t = tokens[i].value;
t = EncodeBackslashEscapes(t);
text += t;
}
}
return text;
}
function TokenizeHTML(string) {
//
// Parameter: String containing HTML markup.
// Returns: An array of the tokens comprising the input
// string. Each token is either a tag (possibly with nested,
// tags contained therein, such as <a href="<MTFoo>">, or a
// actually, I have no idea if this version will work with nested tags like that -- haven't tested it
// run of text between tags. Each element of the array is a
// two-element array; the first is either 'tag' or 'text';
// the second is the actual value.
//
// Returns: An array of objects with "type" and "value".
//
//
// Regular expression derived from the _tokenize() subroutine in
// Brad Choate's MTRegex plugin.
// <http://www.bradchoate.com/past/mtregex.php>
//
function token(type, value) {
this.type = type;
this.value = value;
}
tokens = new Array();
r = /(?:<!(--[\s\S]*?--\s*)+>)|(?:<\?[\s\S]*?\?>)|(?:<[a-z\/!$](?:[^<>]|(?:<[a-z\/!$](?:[^<>]|(?:<[a-z\/!$](?:[^<>]|(?:<[a-z\/!$](?:[^<>]|(?:<[a-z\/!$](?:[^<>]|(?:<[a-z\/!$](?:[^<>])*>))*>))*>))*>))*>))*>)/i
while (r.test(string)) {
txt = RegExp.leftContext;
tag = RegExp.lastMatch;
tokens.push(new token("text", txt));
tokens.push(new token("tag", tag));
string = string.replace(txt, "");
string = string.replace(tag, "");
}
// everything past the last tag
if (string != "") {
tokens.push(new token("text", string));
}
return tokens;
}