diff --git a/.gitignore b/.gitignore
index 89ab5bb..cb1d476 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
*.tmproj
.DS_Store
_blog
+assets/*.min.js
discussd/discuss.dirty
diff --git a/Makefile b/Makefile
index 2dea411..00fd2c2 100644
--- a/Makefile
+++ b/Makefile
@@ -1,2 +1,45 @@
-build:
+JAVASCRIPTS=assets/blog.js assets/gitter.js assets/jquery-serializeObject.js assets/proj.js \
+ assets/request.js assets/showdown.js assets/storage-polyfill.js assets/store.js \
+ assets/strftime.js assets/tmpl.js
+
+MIN_JAVASCRIPTS=assets/blog.min.js assets/gitter.min.js assets/jquery-serializeObject.min.js assets/proj.min.js \
+ assets/request.min.js assets/showdown.min.js assets/storage-polyfill.min.js assets/store.min.js \
+ assets/strftime.min.js assets/tmpl.min.js
+
+POSTS=$(shell echo _blog/*.html)
+
+all: proj blog combine
+
+proj: projects.json templates/proj/index.html templates/proj/proj/index.html
./build.js
+
+blog: _blog/posts.json templates/blog/index.html templates/blog/post.html $(POSTS)
+ @echo
+ ./blog.rb _blog blog
+
+minify: $(JAVASCRIPTS)
+ @echo
+ ./minify.sh
+
+combine: minify $(MIN_JAVASCRIPTS)
+ @echo
+ ./combine.sh
+
+publish_blog: blog combine
+ publish blog
+
+publish_proj: proj combine
+ publish proj
+
+publish: publish_blog publish_proj index.html
+ publish index.html
+ publish assets
+ publish blog
+ publish proj
+
+clean:
+ rm -rf proj/*
+ rm -rf blog/*
+ rm assets/*.min.js
+
+.PHONY: blog
diff --git a/assets/blog.css b/assets/blog.css
index f85005b..c4873b8 100644
--- a/assets/blog.css
+++ b/assets/blog.css
@@ -48,9 +48,9 @@ a.img { border: none }
}
#index { display: none
- ; width: 60%
+ ; width: 80%
; min-width: 200px
- ; max-width: 600px
+ ; max-width: 800px
; border: solid 1px #999
; -moz-border-radius: 10px
; -webkit-border-radius: 10px
@@ -68,22 +68,22 @@ a.img { border: none }
.date { float: right }
-#posts { border-left: solid 0.2em #ddd
+#posts { border-left: solid 0.15em #999
; width: 80%
; min-width: 400px
; max-width: 800px
- ; margin: 4em auto
+ ; margin: 4em auto 2em
; padding: 0 3em
; font-size: 1.2em
}
article { color: #222
- ; padding-bottom: 5em
+ ; padding-bottom: 1em
; line-height: 1.2em
}
article:last-child { padding-bottom: 0
- ; margin-bottom: 2em
+ ; margin-bottom: 1em
}
article h1 { text-align: left
@@ -116,7 +116,9 @@ article h3 { font-size: 1.6em
; font-weight: normal
}
-time { color: #444 }
+time { color: #444
+ ; font-size: 1.2em
+ }
.gist { font-size: 0.8em }
@@ -124,21 +126,93 @@ time { color: #444 }
; min-width: 400px
; max-width: 800px
; margin: 1em auto
- ; padding: 0 3em
+ ; padding: 0 3em 1em
; font-size: 1.2em
}
#prev { float: left }
#next { float: right }
-#comments { display: none }
+.sep { text-align: center
+ ; font-size: 2em
+ ; color: #666
+ }
+
+/* show discussion */
+#sd-container { margin: 3em 0 }
+
+input[type=submit],
+#sd { border: solid 1px #999
+ ; border-right-color: #333
+ ; border-bottom-color: #333
+ ; padding: 0.4em 1em
+ ; color: #444
+ ; background-color: #ececec
+ ; -moz-border-radius: 5px
+ ; -webkit-border-radius: 5px
+ ; border-radius: 5px
+ ; text-decoration: none
+ ; margin: 0 2px 2px 0
+ }
+
+input[type=submit]:active,
+#sd:active { margin: 2px 0 0 2px
+ ; color: #000
+ ; background-color: #ffc
+ }
+
+#comment-stuff { display: none
+ ; color: #efefef
+ ; margin: 0
+ ; padding: 2em 0
+ }
+
+#comments { width: 70%
+ ; max-width: 400px
+ ; margin: 0 auto
+ }
+
+.comment { color: #555
+ ; border-top: solid 2px #ccc
+ ; padding-bottom: 2em
+ ; margin-bottom: 2em
+ }
+
+.comment big { font-size: 2em
+ ; font-family: Georgia, serif
+ }
+
+#comment-form { width: 400px
+ ; margin: 2em auto 0
+ }
+
+input[type=text],
+textarea { font-size: 1.4em
+ ; color: #333
+ ; width: 100%
+ ; padding: 0.2em
+ ; border: solid 1px #999
+ ; -moz-border-radius: 5px
+ ; -webkit-border-radius: 5px
+ ; border-radius: 5px
+ ; font-family: verdana, sans-serif
+ }
+
+input:focus[type=text],
+textarea:focus { border: solid 1px #333 }
+
+textarea { height: 100px }
+
+input[type=submit] { font-size: 1.1em
+ ; cursor: pointer
+ }
footer { text-align: center
; font-size: 1.2em
; margin: 0
- ; padding: 1em
- ; background-color: #a6bcdf
- ; border-top: solid 1px #666
+ ; padding: 1em 0
+ ; background-color: #cdf
+ ; border-top: solid 1px #bbb
; clear: both
}
diff --git a/assets/blog.js b/assets/blog.js
new file mode 100644
index 0000000..edadf14
--- /dev/null
+++ b/assets/blog.js
@@ -0,0 +1,78 @@
+;(function() {
+ var server = 'http://bohodev.net:8000/'
+ , getCommentsURL = function(post) { return server + 'comments/' + post }
+ , postCommentURL = function() { return server + 'comment' }
+ , countCommentsURL = function(post) { return server + 'count/' + post }
+
+ function getComments() {
+ console.log('*** getComments()')
+ SJS.request({uri: getCommentsURL(SJS.filename)}, function(err, request, body) {
+ if (err) {
+ $('#comments').text('derp')
+ return
+ }
+ var data
+ , comments
+ , h = ''
+ try {
+ data = JSON.parse(body)
+ } catch (e) {
+ console && console.log && console.log('not json -> ' + body)
+ }
+ comments = data.comments
+ if (comments.length) {
+ h = data.comments.map(function(c) {
+ return tmpl('comment_tmpl', c)
+ }).join('')
+ }
+ $('#comments').html(h)
+ })
+ }
+ jQuery(function($) {
+ $('#need-js').remove()
+
+ SJS.request({uri: countCommentsURL(SJS.filename)}, function(err, request, body) {
+ if (err) return
+ var data
+ , n
+ try {
+ data = JSON.parse(body)
+ } catch (e) {
+ console && console.log && console.log('not json -> ' + body)
+ }
+ n = data.count
+ $('#sd').text(n > 0 ? 'show the discussion (' + n + ')' : 'start the discussion')
+ })
+
+ $('#sd').click(function() {
+ $('#sd-container').remove()
+ $('#comment-stuff').slideDown(1.5, function() { this.scrollIntoView(true) })
+ getComments()
+ return false
+ })
+
+ var showdown = new Showdown.converter()
+
+ $('#comment-form').submit(function() {
+ var comment = $(this).serializeObject()
+ var options = { method: 'POST'
+ , uri: postCommentURL()
+ , body: JSON.stringify(comment)
+ }
+ SJS.request(options, function(err, request, body) {
+ if (err) {
+ console.dir(err)
+ alert('derp')
+ return
+ }
+
+ // FIXME check for error, how do we get the returned status code?
+
+ comment.timestamp = +new Date()
+ comment.html = showdown.makeHtml(comment.body)
+ $('#comments').append(tmpl('comment_tmpl', comment))
+ })
+ return false
+ })
+ })
+}());
diff --git a/assets/gitter.js b/assets/gitter.js
index 6c86bb7..1866161 100644
--- a/assets/gitter.js
+++ b/assets/gitter.js
@@ -23,7 +23,7 @@
while (
div.innerHTML = '',
all[0]
- );
+ ){/* braces here to satifsy closure-compiler */};
return v > 4 ? v : undef
}())
@@ -594,7 +594,7 @@
, url = options.uri + '?callback=GITR.' + jsonpCallbackName
GITR[jsonpCallbackName] = function(obj) {
cb(null, null, obj)
- setTimeout(function() { delete GITR[jsonpCallbackName] }, 0)
+ delete GITR[jsonpCallbackName]
}
load(url)
}
diff --git a/assets/jquery-serializeObject.js b/assets/jquery-serializeObject.js
new file mode 100644
index 0000000..6956ba4
--- /dev/null
+++ b/assets/jquery-serializeObject.js
@@ -0,0 +1,31 @@
+/*!
+ * jQuery serializeObject - v0.2 - 1/20/2010
+ * http://benalman.com/projects/jquery-misc-plugins/
+ *
+ * Copyright (c) 2010 "Cowboy" Ben Alman
+ * Dual licensed under the MIT and GPL licenses.
+ * http://benalman.com/about/license/
+ */
+
+// Whereas .serializeArray() serializes a form into an array, .serializeObject()
+// serializes a form into an (arguably more useful) object.
+
+;(function($,undefined){
+ '$:nomunge'; // Used by YUI compressor.
+
+ $.fn.serializeObject = function(){
+ var obj = {};
+
+ $.each( this.serializeArray(), function(i,o){
+ var n = o.name,
+ v = o.value;
+
+ obj[n] = obj[n] === undefined ? v
+ : $.isArray( obj[n] ) ? obj[n].concat( v )
+ : [ obj[n], v ];
+ });
+
+ return obj;
+ };
+
+})(jQuery);
diff --git a/assets/request.js b/assets/request.js
index 48a880e..eeae9d9 100644
--- a/assets/request.js
+++ b/assets/request.js
@@ -1,54 +1,31 @@
-(function() {
+;(function() {
if (typeof SJS === 'undefined') SJS = {}
- var load, _jsonpCounter = 1
- SJS.request = function(options, cb) { // jsonp request, quacks like mikeal's request module
- var jsonpCallbackName = '_jsonpCallback' + _jsonpCounter++
- , url = options.uri + '?callback=SJS.' + jsonpCallbackName
- SJS[jsonpCallbackName] = function(obj) {
- cb(null, obj)
- setTimeout(function() { delete SJS[jsonpCallbackName] }, 0)
+
+ // cors xhr request, quacks like mikeal's request module
+ SJS.request = function(options, cb) {
+ var url = options.uri
+ , method = options.method || 'GET'
+ , headers = options.headers || {}
+ , body = typeof options.body === 'undefined' ? null : String(options.body)
+ , xhr = new XMLHttpRequest()
+
+ // withCredentials => cors
+ if ('withCredentials' in xhr) {
+ xhr.open(method, url, true)
+ } else if (typeof XDomainRequest === 'functon') {
+ xhr = new XDomainRequest()
+ xhr.open(method, url)
+ } else {
+ cb(new Error('cross domain requests not supported'))
+ return
}
- load(url)
- }
-
- // bootstrap loader from LABjs
- load = function(url) {
- var oDOC = document
- , handler
- , head = oDOC.head || oDOC.getElementsByTagName("head")
-
- // loading code borrowed directly from LABjs itself
- // (now removes script elem when done and nullifies its reference --sjs)
- setTimeout(function () {
- if ("item" in head) { // check if ref is still a live node list
- if (!head[0]) { // append_to node not yet ready
- setTimeout(arguments.callee, 25)
- return
- }
- head = head[0]; // reassign from live node list ref to pure node ref -- avoids nasty IE bug where changes to DOM invalidate live node lists
- }
- var scriptElem = oDOC.createElement("script"),
- scriptdone = false
- scriptElem.onload = scriptElem.onreadystatechange = function () {
- if ((scriptElem.readyState && scriptElem.readyState !== "complete" && scriptElem.readyState !== "loaded") || scriptdone) {
- return false
- }
- scriptElem.onload = scriptElem.onreadystatechange = null
- scriptElem.parentNode.removeChild(scriptElem)
- scriptElem = null
- scriptdone = true
- };
- scriptElem.src = url
- head.insertBefore(scriptElem, head.firstChild)
- }, 0)
-
- // required: shim for FF <= 3.5 not having document.readyState
- if (oDOC.readyState == null && oDOC.addEventListener) {
- oDOC.readyState = "loading"
- oDOC.addEventListener("DOMContentLoaded", function handler() {
- oDOC.removeEventListener("DOMContentLoaded", handler, false)
- oDOC.readyState = "complete"
- }, false)
+ for (var k in headers) if (headers.hasOwnProperty(k)) {
+ xhr.setRequestHeader(k, headers[k])
}
+ xhr.onload = function() {
+ var response = xhr.responseText
+ cb(null, xhr, response)
+ }
+ xhr.send(body)
}
-}())
\ No newline at end of file
+}());
diff --git a/assets/showdown.js b/assets/showdown.js
new file mode 100644
index 0000000..226ae8b
--- /dev/null
+++ b/assets/showdown.js
@@ -0,0 +1,1296 @@
+//
+// showdown.js -- A javascript port of Markdown.
+//
+// Copyright (c) 2007 John Fraser.
+//
+// Original Markdown Copyright (c) 2004-2005 John Gruber
+// s around
+ // "paragraphs" that are wrapped in non-block-level tags, such as anchors,
+ // phrase emphasis, and spans. The list of tags we're looking for is
+ // hard-coded:
+ var block_tags_a = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del"
+ var block_tags_b = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math"
+
+ // First, look for nested blocks, e.g.:
+ // tags around block-level tags.
+ text = _HashHTMLBlocks(text);
+ text = _FormParagraphs(text);
+
+ return text;
+}
+
+
+var _RunSpanGamut = function(text) {
+//
+// These are all the transformations that occur *within* block-level
+// tags like paragraphs, headers, and list items.
+//
+
+ text = _DoCodeSpans(text);
+ text = _EscapeSpecialCharsWithinTagAttributes(text);
+ text = _EncodeBackslashEscapes(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 ` Just type tags
+//
+
+ // Strip leading and trailing lines:
+ text = text.replace(/^\n+/g,"");
+ text = text.replace(/\n+$/g,"");
+
+ var grafs = text.split(/\n{2,}/g);
+ var grafsOut = new Array();
+
+ //
+ // Wrap tags.
+ //
+ var end = grafs.length;
+ for (var i=0; i ");
+ str += " ༄ (discussion requires JavaScript) ༄ (discussion requires JavaScript) ༄ (discussion requires JavaScript) ༄ (discussion requires JavaScript) tags get encoded.
+//
+
+ // Clear the global hashes. If we don't clear these, you get conflicts
+ // from other articles when generating a page which contains more than
+ // one article (e.g. an index page that shows the N most recent
+ // articles):
+ g_urls = new Array();
+ g_titles = new Array();
+ g_html_blocks = new Array();
+
+ // attacklab: Replace ~ with ~T
+ // This lets us use tilde as an escape char to avoid md5 hashes
+ // The choice of character is arbitray; anything that isn't
+ // magic in Markdown will work.
+ text = text.replace(/~/g,"~T");
+
+ // attacklab: Replace $ with ~D
+ // RegExp interprets $ as a special character
+ // when it's in a replacement string
+ text = text.replace(/\$/g,"~D");
+
+ // Standardize line endings
+ text = text.replace(/\r\n/g,"\n"); // DOS to Unix
+ text = text.replace(/\r/g,"\n"); // Mac to Unix
+
+ // Make sure text begins and ends with a couple of newlines:
+ text = "\n\n" + text + "\n\n";
+
+ // Convert all tabs to spaces.
+ text = _Detab(text);
+
+ // Strip any lines consisting only of spaces and tabs.
+ // This makes subsequent regexen easier to write, because we can
+ // match consecutive blank lines with /\n+/ instead of something
+ // contorted like /[ \t]*\n+/ .
+ text = text.replace(/^[ \t]+$/mg,"");
+
+ // Turn block-level HTML blocks into hash entries
+ text = _HashHTMLBlocks(text);
+
+ // Strip link definitions, store in hashes.
+ text = _StripLinkDefinitions(text);
+
+ text = _RunBlockGamut(text);
+
+ text = _UnescapeSpecialChars(text);
+
+ // attacklab: Restore dollar signs
+ text = text.replace(/~D/g,"$$");
+
+ // attacklab: Restore tildes
+ text = text.replace(/~T/g,"~");
+
+ return text;
+}
+
+
+var _StripLinkDefinitions = function(text) {
+//
+// Strips link definitions from text, stores the URLs and titles in
+// hash references.
+//
+
+ // Link defs are in the form: ^[id]: url "optional title"
+
+ /*
+ var text = text.replace(/
+ ^[ ]{0,3}\[(.+)\]: // id = $1 attacklab: g_tab_width - 1
+ [ \t]*
+ \n? // maybe *one* newline
+ [ \t]*
+ (\S+?)>? // url = $2
+ [ \t]*
+ \n? // maybe one newline
+ [ \t]*
+ (?:
+ (\n*) // any lines skipped = $3 attacklab: lookbehind removed
+ ["(]
+ (.+?) // title = $4
+ [")]
+ [ \t]*
+ )? // title is optional
+ (?:\n+|$)
+ /gm,
+ function(){...});
+ */
+ var text = text.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*(\S+?)>?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|\Z)/gm,
+ function (wholeMatch,m1,m2,m3,m4) {
+ m1 = m1.toLowerCase();
+ g_urls[m1] = _EncodeAmpsAndAngles(m2); // Link IDs are case-insensitive
+ if (m3) {
+ // Oops, found blank lines, so it's not a title.
+ // Put back the parenthetical statement we stole.
+ return m3+m4;
+ } else if (m4) {
+ g_titles[m1] = m4.replace(/"/g,""");
+ }
+
+ // Completely remove the definition from the text
+ return "";
+ }
+ );
+
+ return text;
+}
+
+
+var _HashHTMLBlocks = function(text) {
+ // attacklab: Double up blank lines to reduce lookaround
+ text = text.replace(/\n/g,"\n\n");
+
+ // Hashify HTML blocks:
+ // We only want to do this for block-level HTML tags, such as headers,
+ // lists, and tables. That's because we still want to wrap
. It was easier to make a special case than
+ // to make the other regex more complicated.
+
+ /*
+ text = text.replace(/
+ ( // save in $1
+ \n\n // Starting after a blank line
+ [ ]{0,3}
+ (<(hr) // start tag = $2
+ \b // word break
+ ([^<>])*? //
+ \/?>) // the matching end tag
+ [ \t]*
+ (?=\n{2,}) // followed by a blank line
+ )
+ /g,hashElement);
+ */
+ text = text.replace(/(\n[ ]{0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g,hashElement);
+
+ // Special case for standalone HTML comments:
+
+ /*
+ text = text.replace(/
+ ( // save in $1
+ \n\n // Starting after a blank line
+ [ ]{0,3} // attacklab: g_tab_width - 1
+
+ [ \t]*
+ (?=\n{2,}) // followed by a blank line
+ )
+ /g,hashElement);
+ */
+ text = text.replace(/(\n\n[ ]{0,3}[ \t]*(?=\n{2,}))/g,hashElement);
+
+ // PHP and ASP-style processor instructions (...?> and <%...%>)
+
+ /*
+ text = text.replace(/
+ (?:
+ \n\n // Starting after a blank line
+ )
+ ( // save in $1
+ [ ]{0,3} // attacklab: g_tab_width - 1
+ (?:
+ <([?%]) // $2
+ [^\r]*?
+ \2>
+ )
+ [ \t]*
+ (?=\n{2,}) // followed by a blank line
+ )
+ /g,hashElement);
+ */
+ text = text.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g,hashElement);
+
+ // attacklab: Undo double lines (see comment at top of this function)
+ text = text.replace(/\n\n/g,"\n");
+ return text;
+}
+
+var hashElement = function(wholeMatch,m1) {
+ var blockText = m1;
+
+ // Undo double lines
+ blockText = blockText.replace(/\n\n/g,"\n");
+ blockText = blockText.replace(/^\n/,"");
+
+ // strip trailing blank lines
+ blockText = blockText.replace(/\n+$/g,"");
+
+ // Replace the element text with a marker ("~KxK" where x is its key)
+ blockText = "\n\n~K" + (g_html_blocks.push(blockText)-1) + "K\n\n";
+
+ return blockText;
+};
+
+var _RunBlockGamut = function(text) {
+//
+// These are all the transformations that form block-level
+// tags like paragraphs, headers, and list items.
+//
+ text = _DoHeaders(text);
+
+ // Do Horizontal Rules:
+ var key = hashBlock("
");
+ text = text.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm,key);
+ text = text.replace(/^[ ]{0,2}([ ]?\-[ ]?){3,}[ \t]*$/gm,key);
+ text = text.replace(/^[ ]{0,2}([ ]?\_[ ]?){3,}[ \t]*$/gm,key);
+
+ text = _DoLists(text);
+ text = _DoCodeBlocks(text);
+ text = _DoBlockQuotes(text);
+
+ // 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
+ //
\n");
+
+ return text;
+}
+
+var _EscapeSpecialCharsWithinTagAttributes = function(text) {
+//
+// Within tags -- meaning between < and > -- encode [\ ` * _] so they
+// don't conflict with their use in Markdown for code, italics and strong.
+//
+
+ // Build a regex to find HTML tags and comments. See Friedl's
+ // "Mastering Regular Expressions", 2nd Ed., pp. 200-201.
+ var regex = /(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|)/gi;
+
+ text = text.replace(regex, function(wholeMatch) {
+ var tag = wholeMatch.replace(/(.)<\/?code>(?=.)/g,"$1`");
+ tag = escapeCharacters(tag,"\\`*_");
+ return tag;
+ });
+
+ return text;
+}
+
+var _DoAnchors = function(text) {
+//
+// Turn Markdown link shortcuts into XHTML tags.
+//
+ //
+ // First, handle reference-style links: [link text] [id]
+ //
+
+ /*
+ text = text.replace(/
+ ( // wrap whole match in $1
+ \[
+ (
+ (?:
+ \[[^\]]*\] // allow brackets nested one level
+ |
+ [^\[] // or anything else
+ )*
+ )
+ \]
+
+ [ ]? // one optional space
+ (?:\n[ ]*)? // one optional newline followed by spaces
+
+ \[
+ (.*?) // id = $3
+ \]
+ )()()()() // pad remaining backreferences
+ /g,_DoAnchors_callback);
+ */
+ text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,writeAnchorTag);
+
+ //
+ // Next, inline-style links: [link text](url "optional title")
+ //
+
+ /*
+ text = text.replace(/
+ ( // wrap whole match in $1
+ \[
+ (
+ (?:
+ \[[^\]]*\] // allow brackets nested one level
+ |
+ [^\[\]] // or anything else
+ )
+ )
+ \]
+ \( // literal paren
+ [ \t]*
+ () // no id, so leave $3 empty
+ (.*?)>? // href = $4
+ [ \t]*
+ ( // $5
+ (['"]) // quote char = $6
+ (.*?) // Title = $7
+ \6 // matching quote
+ [ \t]* // ignore any spaces/tabs between closing quote and )
+ )? // title is optional
+ \)
+ )
+ /g,writeAnchorTag);
+ */
+ text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()(.*?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,writeAnchorTag);
+
+ //
+ // Last, handle reference-style shortcuts: [link text]
+ // These must come last in case you've also got [link test][1]
+ // or [link test](/foo)
+ //
+
+ /*
+ text = text.replace(/
+ ( // wrap whole match in $1
+ \[
+ ([^\[\]]+) // link text = $2; can't contain '[' or ']'
+ \]
+ )()()()()() // pad rest of backreferences
+ /g, writeAnchorTag);
+ */
+ text = text.replace(/(\[([^\[\]]+)\])()()()()()/g, writeAnchorTag);
+
+ return text;
+}
+
+var writeAnchorTag = function(wholeMatch,m1,m2,m3,m4,m5,m6,m7) {
+ if (m7 == undefined) m7 = "";
+ var whole_match = m1;
+ var link_text = m2;
+ var link_id = m3.toLowerCase();
+ var url = m4;
+ var title = m7;
+
+ if (url == "") {
+ if (link_id == "") {
+ // lower-case and turn embedded newlines into spaces
+ link_id = link_text.toLowerCase().replace(/ ?\n/g," ");
+ }
+ url = "#"+link_id;
+
+ if (g_urls[link_id] != undefined) {
+ url = g_urls[link_id];
+ if (g_titles[link_id] != undefined) {
+ title = g_titles[link_id];
+ }
+ }
+ else {
+ if (whole_match.search(/\(\s*\)$/m)>-1) {
+ // Special case for explicit empty url
+ url = "";
+ } else {
+ return whole_match;
+ }
+ }
+ }
+
+ url = escapeCharacters(url,"*_");
+ var result = "" + link_text + "";
+
+ return result;
+}
+
+
+var _DoImages = function(text) {
+//
+// Turn Markdown image shortcuts into tags.
+//
+
+ //
+ // First, handle reference-style labeled images: ![alt text][id]
+ //
+
+ /*
+ text = text.replace(/
+ ( // wrap whole match in $1
+ !\[
+ (.*?) // alt text = $2
+ \]
+
+ [ ]? // one optional space
+ (?:\n[ ]*)? // one optional newline followed by spaces
+
+ \[
+ (.*?) // id = $3
+ \]
+ )()()()() // pad rest of backreferences
+ /g,writeImageTag);
+ */
+ text = text.replace(/(!\[(.*?)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,writeImageTag);
+
+ //
+ // Next, handle inline images: 
+ // Don't forget: encode * and _
+
+ /*
+ text = text.replace(/
+ ( // wrap whole match in $1
+ !\[
+ (.*?) // alt text = $2
+ \]
+ \s? // One optional whitespace character
+ \( // literal paren
+ [ \t]*
+ () // no id, so leave $3 empty
+ (\S+?)>? // src url = $4
+ [ \t]*
+ ( // $5
+ (['"]) // quote char = $6
+ (.*?) // title = $7
+ \6 // matching quote
+ [ \t]*
+ )? // title is optional
+ \)
+ )
+ /g,writeImageTag);
+ */
+ text = text.replace(/(!\[(.*?)\]\s?\([ \t]*()(\S+?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,writeImageTag);
+
+ return text;
+}
+
+var writeImageTag = function(wholeMatch,m1,m2,m3,m4,m5,m6,m7) {
+ var whole_match = m1;
+ var alt_text = m2;
+ var link_id = m3.toLowerCase();
+ var url = m4;
+ var title = m7;
+
+ if (!title) title = "";
+
+ if (url == "") {
+ if (link_id == "") {
+ // lower-case and turn embedded newlines into spaces
+ link_id = alt_text.toLowerCase().replace(/ ?\n/g," ");
+ }
+ url = "#"+link_id;
+
+ if (g_urls[link_id] != undefined) {
+ url = g_urls[link_id];
+ if (g_titles[link_id] != undefined) {
+ title = g_titles[link_id];
+ }
+ }
+ else {
+ return whole_match;
+ }
+ }
+
+ alt_text = alt_text.replace(/"/g,""");
+ url = escapeCharacters(url,"*_");
+ var result = "
";
+
+ return result;
+}
+
+
+var _DoHeaders = function(text) {
+
+ // Setext-style headers:
+ // Header 1
+ // ========
+ //
+ // Header 2
+ // --------
+ //
+ text = text.replace(/^(.+)[ \t]*\n=+[ \t]*\n+/gm,
+ function(wholeMatch,m1){return hashBlock("
" + _RunSpanGamut(m1) + "
");});
+
+ text = text.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm,
+ function(matchFound,m1){return hashBlock("" + _RunSpanGamut(m1) + "
");});
+
+ // atx-style headers:
+ // # Header 1
+ // ## Header 2
+ // ## Header 2 with closing hashes ##
+ // ...
+ // ###### Header 6
+ //
+
+ /*
+ text = text.replace(/
+ ^(\#{1,6}) // $1 = string of #'s
+ [ \t]*
+ (.+?) // $2 = Header text
+ [ \t]*
+ \#* // optional closing #'s (not counted)
+ \n+
+ /gm, function() {...});
+ */
+
+ text = text.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm,
+ function(wholeMatch,m1,m2) {
+ var h_level = m1.length;
+ return hashBlock("` blocks.
+//
+
+ /*
+ text = text.replace(text,
+ /(?:\n\n|^)
+ ( // $1 = the code block -- one or more lines, starting with a space/tab
+ (?:
+ (?:[ ]{4}|\t) // Lines must start with a tab or a tab-width of spaces - attacklab: g_tab_width
+ .*\n+
+ )+
+ )
+ (\n*[ ]{0,3}[^ \t\n]|(?=~0)) // attacklab: g_tab_width
+ /g,function(){...});
+ */
+
+ // attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
+ text += "~0";
+
+ text = text.replace(/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g,
+ function(wholeMatch,m1,m2) {
+ var codeblock = m1;
+ var nextChar = m2;
+
+ codeblock = _EncodeCode( _Outdent(codeblock));
+ codeblock = _Detab(codeblock);
+ codeblock = codeblock.replace(/^\n+/g,""); // trim leading newlines
+ codeblock = codeblock.replace(/\n+$/g,""); // trim trailing whitespace
+
+ codeblock = "
";
+
+ return hashBlock(codeblock) + nextChar;
+ }
+ );
+
+ // attacklab: strip sentinel
+ text = text.replace(/~0/,"");
+
+ return text;
+}
+
+var hashBlock = function(text) {
+ text = text.replace(/(^\n+|\n+$)/g,"");
+ return "\n\n~K" + (g_html_blocks.push(text)-1) + "K\n\n";
+}
+
+
+var _DoCodeSpans = function(text) {
+//
+// * Backtick quotes are used for " + codeblock + "\n 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:
+//
+// foo `bar` baz at the prompt.`bar` ...
+//
+
+ /*
+ text = text.replace(/
+ (^|[^\\]) // Character before opening ` can't be a backslash
+ (`+) // $2 = Opening run of `
+ ( // $3 = The code block
+ [^\r]*?
+ [^`] // attacklab: work around lack of lookbehind
+ )
+ \2 // Matching closer
+ (?!`)
+ /gm, function(){...});
+ */
+
+ text = text.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm,
+ function(wholeMatch,m1,m2,m3,m4) {
+ var c = m3;
+ c = c.replace(/^([ \t]*)/g,""); // leading whitespace
+ c = c.replace(/[ \t]*$/g,""); // trailing whitespace
+ c = _EncodeCode(c);
+ return m1+""+c+"";
+ });
+
+ return text;
+}
+
+
+var _EncodeCode = function(text) {
+//
+// Encode/escape certain characters inside Markdown code runs.
+// The point is that in code, these characters are literals,
+// and lose their special Markdown meanings.
+//
+ // Encode all ampersands; HTML entities are not
+ // entities within a Markdown code span.
+ text = text.replace(/&/g,"&");
+
+ // Do the angle bracket song and dance:
+ text = text.replace(//g,">");
+
+ // Now, escape characters that are magic in Markdown:
+ text = escapeCharacters(text,"\*_{}[]\\",false);
+
+// jj the line above breaks this:
+//---
+
+//* Item
+
+// 1. Subitem
+
+// special char: *
+//---
+
+ return text;
+}
+
+
+var _DoItalicsAndBold = function(text) {
+
+ // must go first:
+ text = text.replace(/(\*\*|__)(?=\S)([^\r]*?\S[*_]*)\1/g,
+ "$2");
+
+ text = text.replace(/(\*|_)(?=\S)([^\r]*?\S)\1/g,
+ "$2");
+
+ return text;
+}
+
+
+var _DoBlockQuotes = function(text) {
+
+ /*
+ text = text.replace(/
+ ( // Wrap whole match in $1
+ (
+ ^[ \t]*>[ \t]? // '>' at the start of a line
+ .+\n // rest of the first line
+ (.+\n)* // subsequent consecutive lines
+ \n* // blanks
+ )+
+ )
+ /gm, function(){...});
+ */
+
+ text = text.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm,
+ function(wholeMatch,m1) {
+ var bq = m1;
+
+ // attacklab: hack around Konqueror 3.5.4 bug:
+ // "----------bug".replace(/^-/g,"") == "bug"
+
+ bq = bq.replace(/^[ \t]*>[ \t]?/gm,"~0"); // trim one level of quoting
+
+ // attacklab: clean up hack
+ bq = bq.replace(/~0/g,"");
+
+ bq = bq.replace(/^[ \t]+$/gm,""); // trim whitespace-only lines
+ bq = _RunBlockGamut(bq); // recurse
+
+ bq = bq.replace(/(^|\n)/g,"$1 ");
+ // These leading spaces screw with content, so we need to fix that:
+ bq = bq.replace(
+ /(\s*
[^\r]+?<\/pre>)/gm,
+ function(wholeMatch,m1) {
+ var pre = m1;
+ // attacklab: hack around Konqueror 3.5.4 bug:
+ pre = pre.replace(/^ /mg,"~0");
+ pre = pre.replace(/~0/g,"");
+ return pre;
+ });
+
+ return hashBlock("\n" + bq + "\n
");
+ });
+ return text;
+}
+
+
+var _FormParagraphs = function(text) {
+//
+// Params:
+// $text - string to process with html sjs' blog
sjs' blog
sjs' blog
sjs' blog
+ sjs' blog
+
༄
- -(discussion requires JavaScript)
+ +
༄
(discussion requires JavaScript)
+ +
+ ༄