changeset 3:59cad6ce1a1c

Added support for history and diffing. Added Pagedown editor/live-preview. Added simple graphic style.
author Ludovic Chabant <ludovic@chabant.com>
date Wed, 19 Dec 2012 00:23:12 -0800
parents f8b3a6dc65af
children 036f39c535d9
files requirements.txt wikked/fs.py wikked/scm.py wikked/static/960gs/css/960.css wikked/static/960gs/css/960_12_col.css wikked/static/960gs/css/960_16_col.css wikked/static/960gs/css/960_24_col.css wikked/static/960gs/css/reset.css wikked/static/960gs/css/text.css wikked/static/960gs/img/12_col.gif wikked/static/960gs/img/16_col.gif wikked/static/960gs/img/24_col.gif wikked/static/css/syntax.css wikked/static/css/wikked.less wikked/static/css/wmd.css wikked/static/img/creampaper.png wikked/static/img/creampaper_@2X.png wikked/static/img/gray_jean.png wikked/static/img/gray_jean_@2X.jpg wikked/static/img/grid.png wikked/static/img/grid_@2X.png wikked/static/img/retina_dust.png wikked/static/img/retina_dust_@2X.png wikked/static/img/wmd-buttons.png wikked/static/js/less-1.3.1.min.js wikked/static/js/pagedown/Markdown.Converter.js wikked/static/js/pagedown/Markdown.Editor.js wikked/static/js/pagedown/Markdown.Sanitizer.js wikked/static/js/wikked.js wikked/static/tpl/diff-page.html wikked/static/tpl/edit-page.html wikked/static/tpl/history-page.html wikked/static/tpl/inlinks-page.html wikked/static/tpl/read-page.html wikked/static/tpl/revision-page.html wikked/templates/index.html wikked/views.py wikked/wiki.py
diffstat 38 files changed, 6941 insertions(+), 105 deletions(-) [+]
line wrap: on
line diff
--- a/requirements.txt	Tue Dec 11 22:13:29 2012 -0800
+++ b/requirements.txt	Wed Dec 19 00:23:12 2012 -0800
@@ -4,6 +4,7 @@
 Flask-WTF==0.8
 Jinja2==2.6
 Markdown==2.2.1
+Pygments==1.5
 WTForms==1.0.2
 Werkzeug==0.8.3
 argparse==1.2.1
--- a/wikked/fs.py	Tue Dec 11 22:13:29 2012 -0800
+++ b/wikked/fs.py	Wed Dec 19 00:23:12 2012 -0800
@@ -20,7 +20,7 @@
         self.root = root
         self.excluded = []
 
-    def getPageNames(self, subdir=None):
+    def getPageInfos(self, subdir=None):
         basepath = self.root
         if subdir is not None:
             basepath = self.getPhysicalNamespacePath(subdir)
@@ -29,9 +29,15 @@
             dirnames[:] = [d for d in dirnames if os.path.join(dirpath, d) not in self.excluded]
             for filename in filenames:
                 path = os.path.join(dirpath, filename)
-                path_split = os.path.splitext(os.path.relpath(path, self.root))
-                if path_split[1] != '':
-                    yield path_split[0]
+                rel_path = os.path.relpath(path, self.root)
+                rel_path_split = os.path.splitext(rel_path)
+                url = re.sub(r'[^A-Za-z0-9_\.\-\(\)/]+', '-', rel_path_split[0].lower())
+                yield {
+                        'url': url,
+                        'path': path,
+                        'name': rel_path_split[0],
+                        'ext': rel_path_split[1]
+                        }
 
     def getPage(self, url):
         path = self.getPhysicalPagePath(url)
@@ -47,6 +53,13 @@
                 'content': content
                 }
 
+    def pageExists(self, url):
+        try:
+            self.getPhysicalPagePath(url)
+            return True
+        except PageNotFoundError:
+            return False
+
     def getPhysicalNamespacePath(self, url):
         return self._getPhysicalPath(url, False)
 
--- a/wikked/scm.py	Tue Dec 11 22:13:29 2012 -0800
+++ b/wikked/scm.py	Wed Dec 19 00:23:12 2012 -0800
@@ -26,6 +26,12 @@
     def getState(self, path):
         raise NotImplementedError()
 
+    def getRevision(self, path, rev):
+        raise NotImplementedError()
+
+    def diff(self, path, rev1, rev2):
+        raise NotImplementedError()
+
     def commit(self, paths, op_meta):
         raise NotImplementedError()
 
@@ -95,6 +101,17 @@
                 return STATE_MODIFIED
         return STATE_COMMITTED
 
+    def getRevision(self, path, rev):
+        cat_out = self._run('cat', '-r', rev, path)
+        return cat_out
+
+    def diff(self, path, rev1, rev2):
+        if rev2 is None:
+            diff_out = self._run('diff', '-c', rev1, '--git', path);
+        else:
+            diff_out = self._run('diff', '-r', rev1, '-r', rev2, '--git', path)
+        return diff_out
+
     def commit(self, paths, op_meta):
         if 'message' not in op_meta or not op_meta['message']:
             raise ValueError("No commit message specified.")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/wikked/static/960gs/css/960.css	Wed Dec 19 00:23:12 2012 -0800
@@ -0,0 +1,653 @@
+/*
+  960 Grid System ~ Core CSS.
+  Learn more ~ http://960.gs/
+
+  Licensed under GPL and MIT.
+*/
+
+/*
+  Forces backgrounds to span full width,
+  even if there is horizontal scrolling.
+  Increase this if your layout is wider.
+
+  Note: IE6 works fine without this fix.
+*/
+
+body {
+  min-width: 960px;
+}
+
+/* `Container
+----------------------------------------------------------------------------------------------------*/
+
+.container_12,
+.container_16 {
+  margin-left: auto;
+  margin-right: auto;
+  width: 960px;
+}
+
+/* `Grid >> Global
+----------------------------------------------------------------------------------------------------*/
+
+.grid_1,
+.grid_2,
+.grid_3,
+.grid_4,
+.grid_5,
+.grid_6,
+.grid_7,
+.grid_8,
+.grid_9,
+.grid_10,
+.grid_11,
+.grid_12,
+.grid_13,
+.grid_14,
+.grid_15,
+.grid_16 {
+  display: inline;
+  float: left;
+  margin-left: 10px;
+  margin-right: 10px;
+}
+
+.push_1, .pull_1,
+.push_2, .pull_2,
+.push_3, .pull_3,
+.push_4, .pull_4,
+.push_5, .pull_5,
+.push_6, .pull_6,
+.push_7, .pull_7,
+.push_8, .pull_8,
+.push_9, .pull_9,
+.push_10, .pull_10,
+.push_11, .pull_11,
+.push_12, .pull_12,
+.push_13, .pull_13,
+.push_14, .pull_14,
+.push_15, .pull_15 {
+  position: relative;
+}
+
+.container_12 .grid_3,
+.container_16 .grid_4 {
+  width: 220px;
+}
+
+.container_12 .grid_6,
+.container_16 .grid_8 {
+  width: 460px;
+}
+
+.container_12 .grid_9,
+.container_16 .grid_12 {
+  width: 700px;
+}
+
+.container_12 .grid_12,
+.container_16 .grid_16 {
+  width: 940px;
+}
+
+/* `Grid >> Children (Alpha ~ First, Omega ~ Last)
+----------------------------------------------------------------------------------------------------*/
+
+.alpha {
+  margin-left: 0;
+}
+
+.omega {
+  margin-right: 0;
+}
+
+/* `Grid >> 12 Columns
+----------------------------------------------------------------------------------------------------*/
+
+.container_12 .grid_1 {
+  width: 60px;
+}
+
+.container_12 .grid_2 {
+  width: 140px;
+}
+
+.container_12 .grid_4 {
+  width: 300px;
+}
+
+.container_12 .grid_5 {
+  width: 380px;
+}
+
+.container_12 .grid_7 {
+  width: 540px;
+}
+
+.container_12 .grid_8 {
+  width: 620px;
+}
+
+.container_12 .grid_10 {
+  width: 780px;
+}
+
+.container_12 .grid_11 {
+  width: 860px;
+}
+
+/* `Grid >> 16 Columns
+----------------------------------------------------------------------------------------------------*/
+
+.container_16 .grid_1 {
+  width: 40px;
+}
+
+.container_16 .grid_2 {
+  width: 100px;
+}
+
+.container_16 .grid_3 {
+  width: 160px;
+}
+
+.container_16 .grid_5 {
+  width: 280px;
+}
+
+.container_16 .grid_6 {
+  width: 340px;
+}
+
+.container_16 .grid_7 {
+  width: 400px;
+}
+
+.container_16 .grid_9 {
+  width: 520px;
+}
+
+.container_16 .grid_10 {
+  width: 580px;
+}
+
+.container_16 .grid_11 {
+  width: 640px;
+}
+
+.container_16 .grid_13 {
+  width: 760px;
+}
+
+.container_16 .grid_14 {
+  width: 820px;
+}
+
+.container_16 .grid_15 {
+  width: 880px;
+}
+
+/* `Prefix Extra Space >> Global
+----------------------------------------------------------------------------------------------------*/
+
+.container_12 .prefix_3,
+.container_16 .prefix_4 {
+  padding-left: 240px;
+}
+
+.container_12 .prefix_6,
+.container_16 .prefix_8 {
+  padding-left: 480px;
+}
+
+.container_12 .prefix_9,
+.container_16 .prefix_12 {
+  padding-left: 720px;
+}
+
+/* `Prefix Extra Space >> 12 Columns
+----------------------------------------------------------------------------------------------------*/
+
+.container_12 .prefix_1 {
+  padding-left: 80px;
+}
+
+.container_12 .prefix_2 {
+  padding-left: 160px;
+}
+
+.container_12 .prefix_4 {
+  padding-left: 320px;
+}
+
+.container_12 .prefix_5 {
+  padding-left: 400px;
+}
+
+.container_12 .prefix_7 {
+  padding-left: 560px;
+}
+
+.container_12 .prefix_8 {
+  padding-left: 640px;
+}
+
+.container_12 .prefix_10 {
+  padding-left: 800px;
+}
+
+.container_12 .prefix_11 {
+  padding-left: 880px;
+}
+
+/* `Prefix Extra Space >> 16 Columns
+----------------------------------------------------------------------------------------------------*/
+
+.container_16 .prefix_1 {
+  padding-left: 60px;
+}
+
+.container_16 .prefix_2 {
+  padding-left: 120px;
+}
+
+.container_16 .prefix_3 {
+  padding-left: 180px;
+}
+
+.container_16 .prefix_5 {
+  padding-left: 300px;
+}
+
+.container_16 .prefix_6 {
+  padding-left: 360px;
+}
+
+.container_16 .prefix_7 {
+  padding-left: 420px;
+}
+
+.container_16 .prefix_9 {
+  padding-left: 540px;
+}
+
+.container_16 .prefix_10 {
+  padding-left: 600px;
+}
+
+.container_16 .prefix_11 {
+  padding-left: 660px;
+}
+
+.container_16 .prefix_13 {
+  padding-left: 780px;
+}
+
+.container_16 .prefix_14 {
+  padding-left: 840px;
+}
+
+.container_16 .prefix_15 {
+  padding-left: 900px;
+}
+
+/* `Suffix Extra Space >> Global
+----------------------------------------------------------------------------------------------------*/
+
+.container_12 .suffix_3,
+.container_16 .suffix_4 {
+  padding-right: 240px;
+}
+
+.container_12 .suffix_6,
+.container_16 .suffix_8 {
+  padding-right: 480px;
+}
+
+.container_12 .suffix_9,
+.container_16 .suffix_12 {
+  padding-right: 720px;
+}
+
+/* `Suffix Extra Space >> 12 Columns
+----------------------------------------------------------------------------------------------------*/
+
+.container_12 .suffix_1 {
+  padding-right: 80px;
+}
+
+.container_12 .suffix_2 {
+  padding-right: 160px;
+}
+
+.container_12 .suffix_4 {
+  padding-right: 320px;
+}
+
+.container_12 .suffix_5 {
+  padding-right: 400px;
+}
+
+.container_12 .suffix_7 {
+  padding-right: 560px;
+}
+
+.container_12 .suffix_8 {
+  padding-right: 640px;
+}
+
+.container_12 .suffix_10 {
+  padding-right: 800px;
+}
+
+.container_12 .suffix_11 {
+  padding-right: 880px;
+}
+
+/* `Suffix Extra Space >> 16 Columns
+----------------------------------------------------------------------------------------------------*/
+
+.container_16 .suffix_1 {
+  padding-right: 60px;
+}
+
+.container_16 .suffix_2 {
+  padding-right: 120px;
+}
+
+.container_16 .suffix_3 {
+  padding-right: 180px;
+}
+
+.container_16 .suffix_5 {
+  padding-right: 300px;
+}
+
+.container_16 .suffix_6 {
+  padding-right: 360px;
+}
+
+.container_16 .suffix_7 {
+  padding-right: 420px;
+}
+
+.container_16 .suffix_9 {
+  padding-right: 540px;
+}
+
+.container_16 .suffix_10 {
+  padding-right: 600px;
+}
+
+.container_16 .suffix_11 {
+  padding-right: 660px;
+}
+
+.container_16 .suffix_13 {
+  padding-right: 780px;
+}
+
+.container_16 .suffix_14 {
+  padding-right: 840px;
+}
+
+.container_16 .suffix_15 {
+  padding-right: 900px;
+}
+
+/* `Push Space >> Global
+----------------------------------------------------------------------------------------------------*/
+
+.container_12 .push_3,
+.container_16 .push_4 {
+  left: 240px;
+}
+
+.container_12 .push_6,
+.container_16 .push_8 {
+  left: 480px;
+}
+
+.container_12 .push_9,
+.container_16 .push_12 {
+  left: 720px;
+}
+
+/* `Push Space >> 12 Columns
+----------------------------------------------------------------------------------------------------*/
+
+.container_12 .push_1 {
+  left: 80px;
+}
+
+.container_12 .push_2 {
+  left: 160px;
+}
+
+.container_12 .push_4 {
+  left: 320px;
+}
+
+.container_12 .push_5 {
+  left: 400px;
+}
+
+.container_12 .push_7 {
+  left: 560px;
+}
+
+.container_12 .push_8 {
+  left: 640px;
+}
+
+.container_12 .push_10 {
+  left: 800px;
+}
+
+.container_12 .push_11 {
+  left: 880px;
+}
+
+/* `Push Space >> 16 Columns
+----------------------------------------------------------------------------------------------------*/
+
+.container_16 .push_1 {
+  left: 60px;
+}
+
+.container_16 .push_2 {
+  left: 120px;
+}
+
+.container_16 .push_3 {
+  left: 180px;
+}
+
+.container_16 .push_5 {
+  left: 300px;
+}
+
+.container_16 .push_6 {
+  left: 360px;
+}
+
+.container_16 .push_7 {
+  left: 420px;
+}
+
+.container_16 .push_9 {
+  left: 540px;
+}
+
+.container_16 .push_10 {
+  left: 600px;
+}
+
+.container_16 .push_11 {
+  left: 660px;
+}
+
+.container_16 .push_13 {
+  left: 780px;
+}
+
+.container_16 .push_14 {
+  left: 840px;
+}
+
+.container_16 .push_15 {
+  left: 900px;
+}
+
+/* `Pull Space >> Global
+----------------------------------------------------------------------------------------------------*/
+
+.container_12 .pull_3,
+.container_16 .pull_4 {
+  left: -240px;
+}
+
+.container_12 .pull_6,
+.container_16 .pull_8 {
+  left: -480px;
+}
+
+.container_12 .pull_9,
+.container_16 .pull_12 {
+  left: -720px;
+}
+
+/* `Pull Space >> 12 Columns
+----------------------------------------------------------------------------------------------------*/
+
+.container_12 .pull_1 {
+  left: -80px;
+}
+
+.container_12 .pull_2 {
+  left: -160px;
+}
+
+.container_12 .pull_4 {
+  left: -320px;
+}
+
+.container_12 .pull_5 {
+  left: -400px;
+}
+
+.container_12 .pull_7 {
+  left: -560px;
+}
+
+.container_12 .pull_8 {
+  left: -640px;
+}
+
+.container_12 .pull_10 {
+  left: -800px;
+}
+
+.container_12 .pull_11 {
+  left: -880px;
+}
+
+/* `Pull Space >> 16 Columns
+----------------------------------------------------------------------------------------------------*/
+
+.container_16 .pull_1 {
+  left: -60px;
+}
+
+.container_16 .pull_2 {
+  left: -120px;
+}
+
+.container_16 .pull_3 {
+  left: -180px;
+}
+
+.container_16 .pull_5 {
+  left: -300px;
+}
+
+.container_16 .pull_6 {
+  left: -360px;
+}
+
+.container_16 .pull_7 {
+  left: -420px;
+}
+
+.container_16 .pull_9 {
+  left: -540px;
+}
+
+.container_16 .pull_10 {
+  left: -600px;
+}
+
+.container_16 .pull_11 {
+  left: -660px;
+}
+
+.container_16 .pull_13 {
+  left: -780px;
+}
+
+.container_16 .pull_14 {
+  left: -840px;
+}
+
+.container_16 .pull_15 {
+  left: -900px;
+}
+
+/* `Clear Floated Elements
+----------------------------------------------------------------------------------------------------*/
+
+/* http://sonspring.com/journal/clearing-floats */
+
+.clear {
+  clear: both;
+  display: block;
+  overflow: hidden;
+  visibility: hidden;
+  width: 0;
+  height: 0;
+}
+
+/* http://www.yuiblog.com/blog/2010/09/27/clearfix-reloaded-overflowhidden-demystified */
+
+.clearfix:before,
+.clearfix:after,
+.container_12:before,
+.container_12:after,
+.container_16:before,
+.container_16:after {
+  content: '.';
+  display: block;
+  overflow: hidden;
+  visibility: hidden;
+  font-size: 0;
+  line-height: 0;
+  width: 0;
+  height: 0;
+}
+
+.clearfix:after,
+.container_12:after,
+.container_16:after {
+  clear: both;
+}
+
+/*
+  The following zoom:1 rule is specifically for IE6 + IE7.
+  Move to separate stylesheet if invalid CSS is a problem.
+*/
+
+.clearfix,
+.container_12,
+.container_16 {
+  zoom: 1;
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/wikked/static/960gs/css/960_12_col.css	Wed Dec 19 00:23:12 2012 -0800
@@ -0,0 +1,357 @@
+/*
+  960 Grid System ~ Core CSS.
+  Learn more ~ http://960.gs/
+
+  Licensed under GPL and MIT.
+*/
+
+/*
+  Forces backgrounds to span full width,
+  even if there is horizontal scrolling.
+  Increase this if your layout is wider.
+
+  Note: IE6 works fine without this fix.
+*/
+
+body {
+  min-width: 960px;
+}
+
+/* `Container
+----------------------------------------------------------------------------------------------------*/
+
+.container_12 {
+  margin-left: auto;
+  margin-right: auto;
+  width: 960px;
+}
+
+/* `Grid >> Global
+----------------------------------------------------------------------------------------------------*/
+
+.grid_1,
+.grid_2,
+.grid_3,
+.grid_4,
+.grid_5,
+.grid_6,
+.grid_7,
+.grid_8,
+.grid_9,
+.grid_10,
+.grid_11,
+.grid_12 {
+  display: inline;
+  float: left;
+  margin-left: 10px;
+  margin-right: 10px;
+}
+
+.push_1, .pull_1,
+.push_2, .pull_2,
+.push_3, .pull_3,
+.push_4, .pull_4,
+.push_5, .pull_5,
+.push_6, .pull_6,
+.push_7, .pull_7,
+.push_8, .pull_8,
+.push_9, .pull_9,
+.push_10, .pull_10,
+.push_11, .pull_11 {
+  position: relative;
+}
+
+/* `Grid >> Children (Alpha ~ First, Omega ~ Last)
+----------------------------------------------------------------------------------------------------*/
+
+.alpha {
+  margin-left: 0;
+}
+
+.omega {
+  margin-right: 0;
+}
+
+/* `Grid >> 12 Columns
+----------------------------------------------------------------------------------------------------*/
+
+.container_12 .grid_1 {
+  width: 60px;
+}
+
+.container_12 .grid_2 {
+  width: 140px;
+}
+
+.container_12 .grid_3 {
+  width: 220px;
+}
+
+.container_12 .grid_4 {
+  width: 300px;
+}
+
+.container_12 .grid_5 {
+  width: 380px;
+}
+
+.container_12 .grid_6 {
+  width: 460px;
+}
+
+.container_12 .grid_7 {
+  width: 540px;
+}
+
+.container_12 .grid_8 {
+  width: 620px;
+}
+
+.container_12 .grid_9 {
+  width: 700px;
+}
+
+.container_12 .grid_10 {
+  width: 780px;
+}
+
+.container_12 .grid_11 {
+  width: 860px;
+}
+
+.container_12 .grid_12 {
+  width: 940px;
+}
+
+/* `Prefix Extra Space >> 12 Columns
+----------------------------------------------------------------------------------------------------*/
+
+.container_12 .prefix_1 {
+  padding-left: 80px;
+}
+
+.container_12 .prefix_2 {
+  padding-left: 160px;
+}
+
+.container_12 .prefix_3 {
+  padding-left: 240px;
+}
+
+.container_12 .prefix_4 {
+  padding-left: 320px;
+}
+
+.container_12 .prefix_5 {
+  padding-left: 400px;
+}
+
+.container_12 .prefix_6 {
+  padding-left: 480px;
+}
+
+.container_12 .prefix_7 {
+  padding-left: 560px;
+}
+
+.container_12 .prefix_8 {
+  padding-left: 640px;
+}
+
+.container_12 .prefix_9 {
+  padding-left: 720px;
+}
+
+.container_12 .prefix_10 {
+  padding-left: 800px;
+}
+
+.container_12 .prefix_11 {
+  padding-left: 880px;
+}
+
+/* `Suffix Extra Space >> 12 Columns
+----------------------------------------------------------------------------------------------------*/
+
+.container_12 .suffix_1 {
+  padding-right: 80px;
+}
+
+.container_12 .suffix_2 {
+  padding-right: 160px;
+}
+
+.container_12 .suffix_3 {
+  padding-right: 240px;
+}
+
+.container_12 .suffix_4 {
+  padding-right: 320px;
+}
+
+.container_12 .suffix_5 {
+  padding-right: 400px;
+}
+
+.container_12 .suffix_6 {
+  padding-right: 480px;
+}
+
+.container_12 .suffix_7 {
+  padding-right: 560px;
+}
+
+.container_12 .suffix_8 {
+  padding-right: 640px;
+}
+
+.container_12 .suffix_9 {
+  padding-right: 720px;
+}
+
+.container_12 .suffix_10 {
+  padding-right: 800px;
+}
+
+.container_12 .suffix_11 {
+  padding-right: 880px;
+}
+
+/* `Push Space >> 12 Columns
+----------------------------------------------------------------------------------------------------*/
+
+.container_12 .push_1 {
+  left: 80px;
+}
+
+.container_12 .push_2 {
+  left: 160px;
+}
+
+.container_12 .push_3 {
+  left: 240px;
+}
+
+.container_12 .push_4 {
+  left: 320px;
+}
+
+.container_12 .push_5 {
+  left: 400px;
+}
+
+.container_12 .push_6 {
+  left: 480px;
+}
+
+.container_12 .push_7 {
+  left: 560px;
+}
+
+.container_12 .push_8 {
+  left: 640px;
+}
+
+.container_12 .push_9 {
+  left: 720px;
+}
+
+.container_12 .push_10 {
+  left: 800px;
+}
+
+.container_12 .push_11 {
+  left: 880px;
+}
+
+/* `Pull Space >> 12 Columns
+----------------------------------------------------------------------------------------------------*/
+
+.container_12 .pull_1 {
+  left: -80px;
+}
+
+.container_12 .pull_2 {
+  left: -160px;
+}
+
+.container_12 .pull_3 {
+  left: -240px;
+}
+
+.container_12 .pull_4 {
+  left: -320px;
+}
+
+.container_12 .pull_5 {
+  left: -400px;
+}
+
+.container_12 .pull_6 {
+  left: -480px;
+}
+
+.container_12 .pull_7 {
+  left: -560px;
+}
+
+.container_12 .pull_8 {
+  left: -640px;
+}
+
+.container_12 .pull_9 {
+  left: -720px;
+}
+
+.container_12 .pull_10 {
+  left: -800px;
+}
+
+.container_12 .pull_11 {
+  left: -880px;
+}
+
+/* `Clear Floated Elements
+----------------------------------------------------------------------------------------------------*/
+
+/* http://sonspring.com/journal/clearing-floats */
+
+.clear {
+  clear: both;
+  display: block;
+  overflow: hidden;
+  visibility: hidden;
+  width: 0;
+  height: 0;
+}
+
+/* http://www.yuiblog.com/blog/2010/09/27/clearfix-reloaded-overflowhidden-demystified */
+
+.clearfix:before,
+.clearfix:after,
+.container_12:before,
+.container_12:after {
+  content: '.';
+  display: block;
+  overflow: hidden;
+  visibility: hidden;
+  font-size: 0;
+  line-height: 0;
+  width: 0;
+  height: 0;
+}
+
+.clearfix:after,
+.container_12:after {
+  clear: both;
+}
+
+/*
+  The following zoom:1 rule is specifically for IE6 + IE7.
+  Move to separate stylesheet if invalid CSS is a problem.
+*/
+
+.clearfix,
+.container_12 {
+  zoom: 1;
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/wikked/static/960gs/css/960_16_col.css	Wed Dec 19 00:23:12 2012 -0800
@@ -0,0 +1,446 @@
+/*
+  960 Grid System ~ Core CSS.
+  Learn more ~ http://960.gs/
+
+  Licensed under GPL and MIT.
+*/
+
+/*
+  Forces backgrounds to span full width,
+  even if there is horizontal scrolling.
+  Increase this if your layout is wider.
+
+  Note: IE6 works fine without this fix.
+*/
+
+body {
+  min-width: 960px;
+}
+
+/* Container
+----------------------------------------------------------------------------------------------------*/
+
+.container_16 {
+  margin-left: auto;
+  margin-right: auto;
+  width: 960px;
+}
+
+/* Grid >> Global
+----------------------------------------------------------------------------------------------------*/
+
+.grid_1,
+.grid_2,
+.grid_3,
+.grid_4,
+.grid_5,
+.grid_6,
+.grid_7,
+.grid_8,
+.grid_9,
+.grid_10,
+.grid_11,
+.grid_12,
+.grid_13,
+.grid_14,
+.grid_15,
+.grid_16 {
+  display: inline;
+  float: left;
+  margin-left: 10px;
+  margin-right: 10px;
+}
+
+.push_1, .pull_1,
+.push_2, .pull_2,
+.push_3, .pull_3,
+.push_4, .pull_4,
+.push_5, .pull_5,
+.push_6, .pull_6,
+.push_7, .pull_7,
+.push_8, .pull_8,
+.push_9, .pull_9,
+.push_10, .pull_10,
+.push_11, .pull_11,
+.push_12, .pull_12,
+.push_13, .pull_13,
+.push_14, .pull_14,
+.push_15, .pull_15,
+.push_16, .pull_16 {
+  position: relative;
+}
+
+/* Grid >> Children (Alpha ~ First, Omega ~ Last)
+----------------------------------------------------------------------------------------------------*/
+
+.alpha {
+  margin-left: 0;
+}
+
+.omega {
+  margin-right: 0;
+}
+
+/* Grid >> 16 Columns
+----------------------------------------------------------------------------------------------------*/
+
+.container_16 .grid_1 {
+  width: 40px;
+}
+
+.container_16 .grid_2 {
+  width: 100px;
+}
+
+.container_16 .grid_3 {
+  width: 160px;
+}
+
+.container_16 .grid_4 {
+  width: 220px;
+}
+
+.container_16 .grid_5 {
+  width: 280px;
+}
+
+.container_16 .grid_6 {
+  width: 340px;
+}
+
+.container_16 .grid_7 {
+  width: 400px;
+}
+
+.container_16 .grid_8 {
+  width: 460px;
+}
+
+.container_16 .grid_9 {
+  width: 520px;
+}
+
+.container_16 .grid_10 {
+  width: 580px;
+}
+
+.container_16 .grid_11 {
+  width: 640px;
+}
+
+.container_16 .grid_12 {
+  width: 700px;
+}
+
+.container_16 .grid_13 {
+  width: 760px;
+}
+
+.container_16 .grid_14 {
+  width: 820px;
+}
+
+.container_16 .grid_15 {
+  width: 880px;
+}
+
+.container_16 .grid_16 {
+  width: 940px;
+}
+
+/* Prefix Extra Space >> 16 Columns
+----------------------------------------------------------------------------------------------------*/
+
+.container_16 .prefix_1 {
+  padding-left: 60px;
+}
+
+.container_16 .prefix_2 {
+  padding-left: 120px;
+}
+
+.container_16 .prefix_3 {
+  padding-left: 180px;
+}
+
+.container_16 .prefix_4 {
+  padding-left: 240px;
+}
+
+.container_16 .prefix_5 {
+  padding-left: 300px;
+}
+
+.container_16 .prefix_6 {
+  padding-left: 360px;
+}
+
+.container_16 .prefix_7 {
+  padding-left: 420px;
+}
+
+.container_16 .prefix_8 {
+  padding-left: 480px;
+}
+
+.container_16 .prefix_9 {
+  padding-left: 540px;
+}
+
+.container_16 .prefix_10 {
+  padding-left: 600px;
+}
+
+.container_16 .prefix_11 {
+  padding-left: 660px;
+}
+
+.container_16 .prefix_12 {
+  padding-left: 720px;
+}
+
+.container_16 .prefix_13 {
+  padding-left: 780px;
+}
+
+.container_16 .prefix_14 {
+  padding-left: 840px;
+}
+
+.container_16 .prefix_15 {
+  padding-left: 900px;
+}
+
+/* Suffix Extra Space >> 16 Columns
+----------------------------------------------------------------------------------------------------*/
+
+.container_16 .suffix_1 {
+  padding-right: 60px;
+}
+
+.container_16 .suffix_2 {
+  padding-right: 120px;
+}
+
+.container_16 .suffix_3 {
+  padding-right: 180px;
+}
+
+.container_16 .suffix_4 {
+  padding-right: 240px;
+}
+
+.container_16 .suffix_5 {
+  padding-right: 300px;
+}
+
+.container_16 .suffix_6 {
+  padding-right: 360px;
+}
+
+.container_16 .suffix_7 {
+  padding-right: 420px;
+}
+
+.container_16 .suffix_8 {
+  padding-right: 480px;
+}
+
+.container_16 .suffix_9 {
+  padding-right: 540px;
+}
+
+.container_16 .suffix_10 {
+  padding-right: 600px;
+}
+
+.container_16 .suffix_11 {
+  padding-right: 660px;
+}
+
+.container_16 .suffix_12 {
+  padding-right: 720px;
+}
+
+.container_16 .suffix_13 {
+  padding-right: 780px;
+}
+
+.container_16 .suffix_14 {
+  padding-right: 840px;
+}
+
+.container_16 .suffix_15 {
+  padding-right: 900px;
+}
+
+/* Push Space >> 16 Columns
+----------------------------------------------------------------------------------------------------*/
+
+.container_16 .push_1 {
+  left: 60px;
+}
+
+.container_16 .push_2 {
+  left: 120px;
+}
+
+.container_16 .push_3 {
+  left: 180px;
+}
+
+.container_16 .push_4 {
+  left: 240px;
+}
+
+.container_16 .push_5 {
+  left: 300px;
+}
+
+.container_16 .push_6 {
+  left: 360px;
+}
+
+.container_16 .push_7 {
+  left: 420px;
+}
+
+.container_16 .push_8 {
+  left: 480px;
+}
+
+.container_16 .push_9 {
+  left: 540px;
+}
+
+.container_16 .push_10 {
+  left: 600px;
+}
+
+.container_16 .push_11 {
+  left: 660px;
+}
+
+.container_16 .push_12 {
+  left: 720px;
+}
+
+.container_16 .push_13 {
+  left: 780px;
+}
+
+.container_16 .push_14 {
+  left: 840px;
+}
+
+.container_16 .push_15 {
+  left: 900px;
+}
+
+/* Pull Space >> 16 Columns
+----------------------------------------------------------------------------------------------------*/
+
+.container_16 .pull_1 {
+  left: -60px;
+}
+
+.container_16 .pull_2 {
+  left: -120px;
+}
+
+.container_16 .pull_3 {
+  left: -180px;
+}
+
+.container_16 .pull_4 {
+  left: -240px;
+}
+
+.container_16 .pull_5 {
+  left: -300px;
+}
+
+.container_16 .pull_6 {
+  left: -360px;
+}
+
+.container_16 .pull_7 {
+  left: -420px;
+}
+
+.container_16 .pull_8 {
+  left: -480px;
+}
+
+.container_16 .pull_9 {
+  left: -540px;
+}
+
+.container_16 .pull_10 {
+  left: -600px;
+}
+
+.container_16 .pull_11 {
+  left: -660px;
+}
+
+.container_16 .pull_12 {
+  left: -720px;
+}
+
+.container_16 .pull_13 {
+  left: -780px;
+}
+
+.container_16 .pull_14 {
+  left: -840px;
+}
+
+.container_16 .pull_15 {
+  left: -900px;
+}
+
+/* `Clear Floated Elements
+----------------------------------------------------------------------------------------------------*/
+
+/* http://sonspring.com/journal/clearing-floats */
+
+.clear {
+  clear: both;
+  display: block;
+  overflow: hidden;
+  visibility: hidden;
+  width: 0;
+  height: 0;
+}
+
+/* http://www.yuiblog.com/blog/2010/09/27/clearfix-reloaded-overflowhidden-demystified */
+
+.clearfix:before,
+.clearfix:after,
+.container_16:before,
+.container_16:after {
+  content: '.';
+  display: block;
+  overflow: hidden;
+  visibility: hidden;
+  font-size: 0;
+  line-height: 0;
+  width: 0;
+  height: 0;
+}
+
+.clearfix:after,
+.container_16:after {
+  clear: both;
+}
+
+/*
+  The following zoom:1 rule is specifically for IE6 + IE7.
+  Move to separate stylesheet if invalid CSS is a problem.
+*/
+
+.clearfix,
+.container_16 {
+  zoom: 1;
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/wikked/static/960gs/css/960_24_col.css	Wed Dec 19 00:23:12 2012 -0800
@@ -0,0 +1,621 @@
+/*
+  960 Grid System ~ Core CSS.
+  Learn more ~ http://960.gs/
+
+  Licensed under GPL and MIT.
+*/
+
+/*
+  Forces backgrounds to span full width,
+  even if there is horizontal scrolling.
+  Increase this if your layout is wider.
+
+  Note: IE6 works fine without this fix.
+*/
+
+body {
+  min-width: 960px;
+}
+
+/* `Container
+----------------------------------------------------------------------------------------------------*/
+
+.container_24 {
+  margin-left: auto;
+  margin-right: auto;
+  width: 960px;
+}
+
+/* `Grid >> Global
+----------------------------------------------------------------------------------------------------*/
+
+.grid_1,
+.grid_2,
+.grid_3,
+.grid_4,
+.grid_5,
+.grid_6,
+.grid_7,
+.grid_8,
+.grid_9,
+.grid_10,
+.grid_11,
+.grid_12,
+.grid_13,
+.grid_14,
+.grid_15,
+.grid_16,
+.grid_17,
+.grid_18,
+.grid_19,
+.grid_20,
+.grid_21,
+.grid_22,
+.grid_23,
+.grid_24 {
+  display: inline;
+  float: left;
+  margin-left: 5px;
+  margin-right: 5px;
+}
+
+.push_1, .pull_1,
+.push_2, .pull_2,
+.push_3, .pull_3,
+.push_4, .pull_4,
+.push_5, .pull_5,
+.push_6, .pull_6,
+.push_7, .pull_7,
+.push_8, .pull_8,
+.push_9, .pull_9,
+.push_10, .pull_10,
+.push_11, .pull_11,
+.push_12, .pull_12,
+.push_13, .pull_13,
+.push_14, .pull_14,
+.push_15, .pull_15,
+.push_16, .pull_16,
+.push_17, .pull_17,
+.push_18, .pull_18,
+.push_19, .pull_19,
+.push_20, .pull_20,
+.push_21, .pull_21,
+.push_22, .pull_22,
+.push_23, .pull_23 {
+  position: relative;
+}
+
+/* `Grid >> Children (Alpha ~ First, Omega ~ Last)
+----------------------------------------------------------------------------------------------------*/
+
+.alpha {
+  margin-left: 0;
+}
+
+.omega {
+  margin-right: 0;
+}
+
+/* `Grid >> 24 Columns
+----------------------------------------------------------------------------------------------------*/
+
+.container_24 .grid_1 {
+  width: 30px;
+}
+
+.container_24 .grid_2 {
+  width: 70px;
+}
+
+.container_24 .grid_3 {
+  width: 110px;
+}
+
+.container_24 .grid_4 {
+  width: 150px;
+}
+
+.container_24 .grid_5 {
+  width: 190px;
+}
+
+.container_24 .grid_6 {
+  width: 230px;
+}
+
+.container_24 .grid_7 {
+  width: 270px;
+}
+
+.container_24 .grid_8 {
+  width: 310px;
+}
+
+.container_24 .grid_9 {
+  width: 350px;
+}
+
+.container_24 .grid_10 {
+  width: 390px;
+}
+
+.container_24 .grid_11 {
+  width: 430px;
+}
+
+.container_24 .grid_12 {
+  width: 470px;
+}
+
+.container_24 .grid_13 {
+  width: 510px;
+}
+
+.container_24 .grid_14 {
+  width: 550px;
+}
+
+.container_24 .grid_15 {
+  width: 590px;
+}
+
+.container_24 .grid_16 {
+  width: 630px;
+}
+
+.container_24 .grid_17 {
+  width: 670px;
+}
+
+.container_24 .grid_18 {
+  width: 710px;
+}
+
+.container_24 .grid_19 {
+  width: 750px;
+}
+
+.container_24 .grid_20 {
+  width: 790px;
+}
+
+.container_24 .grid_21 {
+  width: 830px;
+}
+
+.container_24 .grid_22 {
+  width: 870px;
+}
+
+.container_24 .grid_23 {
+  width: 910px;
+}
+
+.container_24 .grid_24 {
+  width: 950px;
+}
+
+/* `Prefix Extra Space >> 24 Columns
+----------------------------------------------------------------------------------------------------*/
+
+.container_24 .prefix_1 {
+  padding-left: 40px;
+}
+
+.container_24 .prefix_2 {
+  padding-left: 80px;
+}
+
+.container_24 .prefix_3 {
+  padding-left: 120px;
+}
+
+.container_24 .prefix_4 {
+  padding-left: 160px;
+}
+
+.container_24 .prefix_5 {
+  padding-left: 200px;
+}
+
+.container_24 .prefix_6 {
+  padding-left: 240px;
+}
+
+.container_24 .prefix_7 {
+  padding-left: 280px;
+}
+
+.container_24 .prefix_8 {
+  padding-left: 320px;
+}
+
+.container_24 .prefix_9 {
+  padding-left: 360px;
+}
+
+.container_24 .prefix_10 {
+  padding-left: 400px;
+}
+
+.container_24 .prefix_11 {
+  padding-left: 440px;
+}
+
+.container_24 .prefix_12 {
+  padding-left: 480px;
+}
+
+.container_24 .prefix_13 {
+  padding-left: 520px;
+}
+
+.container_24 .prefix_14 {
+  padding-left: 560px;
+}
+
+.container_24 .prefix_15 {
+  padding-left: 600px;
+}
+
+.container_24 .prefix_16 {
+  padding-left: 640px;
+}
+
+.container_24 .prefix_17 {
+  padding-left: 680px;
+}
+
+.container_24 .prefix_18 {
+  padding-left: 720px;
+}
+
+.container_24 .prefix_19 {
+  padding-left: 760px;
+}
+
+.container_24 .prefix_20 {
+  padding-left: 800px;
+}
+
+.container_24 .prefix_21 {
+  padding-left: 840px;
+}
+
+.container_24 .prefix_22 {
+  padding-left: 880px;
+}
+
+.container_24 .prefix_23 {
+  padding-left: 920px;
+}
+
+/* `Suffix Extra Space >> 24 Columns
+----------------------------------------------------------------------------------------------------*/
+
+.container_24 .suffix_1 {
+  padding-right: 40px;
+}
+
+.container_24 .suffix_2 {
+  padding-right: 80px;
+}
+
+.container_24 .suffix_3 {
+  padding-right: 120px;
+}
+
+.container_24 .suffix_4 {
+  padding-right: 160px;
+}
+
+.container_24 .suffix_5 {
+  padding-right: 200px;
+}
+
+.container_24 .suffix_6 {
+  padding-right: 240px;
+}
+
+.container_24 .suffix_7 {
+  padding-right: 280px;
+}
+
+.container_24 .suffix_8 {
+  padding-right: 320px;
+}
+
+.container_24 .suffix_9 {
+  padding-right: 360px;
+}
+
+.container_24 .suffix_10 {
+  padding-right: 400px;
+}
+
+.container_24 .suffix_11 {
+  padding-right: 440px;
+}
+
+.container_24 .suffix_12 {
+  padding-right: 480px;
+}
+
+.container_24 .suffix_13 {
+  padding-right: 520px;
+}
+
+.container_24 .suffix_14 {
+  padding-right: 560px;
+}
+
+.container_24 .suffix_15 {
+  padding-right: 600px;
+}
+
+.container_24 .suffix_16 {
+  padding-right: 640px;
+}
+
+.container_24 .suffix_17 {
+  padding-right: 680px;
+}
+
+.container_24 .suffix_18 {
+  padding-right: 720px;
+}
+
+.container_24 .suffix_19 {
+  padding-right: 760px;
+}
+
+.container_24 .suffix_20 {
+  padding-right: 800px;
+}
+
+.container_24 .suffix_21 {
+  padding-right: 840px;
+}
+
+.container_24 .suffix_22 {
+  padding-right: 880px;
+}
+
+.container_24 .suffix_23 {
+  padding-right: 920px;
+}
+
+/* `Push Space >> 24 Columns
+----------------------------------------------------------------------------------------------------*/
+
+.container_24 .push_1 {
+  left: 40px;
+}
+
+.container_24 .push_2 {
+  left: 80px;
+}
+
+.container_24 .push_3 {
+  left: 120px;
+}
+
+.container_24 .push_4 {
+  left: 160px;
+}
+
+.container_24 .push_5 {
+  left: 200px;
+}
+
+.container_24 .push_6 {
+  left: 240px;
+}
+
+.container_24 .push_7 {
+  left: 280px;
+}
+
+.container_24 .push_8 {
+  left: 320px;
+}
+
+.container_24 .push_9 {
+  left: 360px;
+}
+
+.container_24 .push_10 {
+  left: 400px;
+}
+
+.container_24 .push_11 {
+  left: 440px;
+}
+
+.container_24 .push_12 {
+  left: 480px;
+}
+
+.container_24 .push_13 {
+  left: 520px;
+}
+
+.container_24 .push_14 {
+  left: 560px;
+}
+
+.container_24 .push_15 {
+  left: 600px;
+}
+
+.container_24 .push_16 {
+  left: 640px;
+}
+
+.container_24 .push_17 {
+  left: 680px;
+}
+
+.container_24 .push_18 {
+  left: 720px;
+}
+
+.container_24 .push_19 {
+  left: 760px;
+}
+
+.container_24 .push_20 {
+  left: 800px;
+}
+
+.container_24 .push_21 {
+  left: 840px;
+}
+
+.container_24 .push_22 {
+  left: 880px;
+}
+
+.container_24 .push_23 {
+  left: 920px;
+}
+
+/* `Pull Space >> 24 Columns
+----------------------------------------------------------------------------------------------------*/
+
+.container_24 .pull_1 {
+  left: -40px;
+}
+
+.container_24 .pull_2 {
+  left: -80px;
+}
+
+.container_24 .pull_3 {
+  left: -120px;
+}
+
+.container_24 .pull_4 {
+  left: -160px;
+}
+
+.container_24 .pull_5 {
+  left: -200px;
+}
+
+.container_24 .pull_6 {
+  left: -240px;
+}
+
+.container_24 .pull_7 {
+  left: -280px;
+}
+
+.container_24 .pull_8 {
+  left: -320px;
+}
+
+.container_24 .pull_9 {
+  left: -360px;
+}
+
+.container_24 .pull_10 {
+  left: -400px;
+}
+
+.container_24 .pull_11 {
+  left: -440px;
+}
+
+.container_24 .pull_12 {
+  left: -480px;
+}
+
+.container_24 .pull_13 {
+  left: -520px;
+}
+
+.container_24 .pull_14 {
+  left: -560px;
+}
+
+.container_24 .pull_15 {
+  left: -600px;
+}
+
+.container_24 .pull_16 {
+  left: -640px;
+}
+
+.container_24 .pull_17 {
+  left: -680px;
+}
+
+.container_24 .pull_18 {
+  left: -720px;
+}
+
+.container_24 .pull_19 {
+  left: -760px;
+}
+
+.container_24 .pull_20 {
+  left: -800px;
+}
+
+.container_24 .pull_21 {
+  left: -840px;
+}
+
+.container_24 .pull_22 {
+  left: -880px;
+}
+
+.container_24 .pull_23 {
+  left: -920px;
+}
+
+/* `Clear Floated Elements
+----------------------------------------------------------------------------------------------------*/
+
+/* http://sonspring.com/journal/clearing-floats */
+
+.clear {
+  clear: both;
+  display: block;
+  overflow: hidden;
+  visibility: hidden;
+  width: 0;
+  height: 0;
+}
+
+/* http://www.yuiblog.com/blog/2010/09/27/clearfix-reloaded-overflowhidden-demystified */
+
+.clearfix:before,
+.clearfix:after,
+.container_24:before,
+.container_24:after {
+  content: '.';
+  display: block;
+  overflow: hidden;
+  visibility: hidden;
+  font-size: 0;
+  line-height: 0;
+  width: 0;
+  height: 0;
+}
+
+.clearfix:after,
+.container_24:after {
+  clear: both;
+}
+
+/*
+  The following zoom:1 rule is specifically for IE6 + IE7.
+  Move to separate stylesheet if invalid CSS is a problem.
+*/
+
+.clearfix,
+.container_24 {
+  zoom: 1;
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/wikked/static/960gs/css/reset.css	Wed Dec 19 00:23:12 2012 -0800
@@ -0,0 +1,211 @@
+/* `XHTML, HTML4, HTML5 Reset
+----------------------------------------------------------------------------------------------------*/
+
+a,
+abbr,
+acronym,
+address,
+applet,
+article,
+aside,
+audio,
+b,
+big,
+blockquote,
+body,
+canvas,
+caption,
+center,
+cite,
+code,
+dd,
+del,
+details,
+dfn,
+dialog,
+div,
+dl,
+dt,
+em,
+embed,
+fieldset,
+figcaption,
+figure,
+font,
+footer,
+form,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+header,
+hgroup,
+hr,
+html,
+i,
+iframe,
+img,
+ins,
+kbd,
+label,
+legend,
+li,
+mark,
+menu,
+meter,
+nav,
+object,
+ol,
+output,
+p,
+pre,
+progress,
+q,
+rp,
+rt,
+ruby,
+s,
+samp,
+section,
+small,
+span,
+strike,
+strong,
+sub,
+summary,
+sup,
+table,
+tbody,
+td,
+tfoot,
+th,
+thead,
+time,
+tr,
+tt,
+u,
+ul,
+var,
+video,
+xmp {
+  border: 0;
+  margin: 0;
+  padding: 0;
+  font-size: 100%;
+}
+
+html,
+body {
+  height: 100%;
+}
+
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+menu,
+nav,
+section {
+/*
+  Override the default (display: inline) for
+  browsers that do not recognize HTML5 tags.
+
+  IE8 (and lower) requires a shiv:
+  http://ejohn.org/blog/html5-shiv
+*/
+  display: block;
+}
+
+b,
+strong {
+/*
+  Makes browsers agree.
+  IE + Opera = font-weight: bold.
+  Gecko + WebKit = font-weight: bolder.
+*/
+  font-weight: bold;
+}
+
+img {
+  color: transparent;
+  font-size: 0;
+  vertical-align: middle;
+/*
+  For IE.
+  http://css-tricks.com/ie-fix-bicubic-scaling-for-images
+*/
+  -ms-interpolation-mode: bicubic;
+}
+
+ol,
+ul {
+  list-style: none;
+}
+
+li {
+/*
+  For IE6 + IE7:
+
+  "display: list-item" keeps bullets from
+  disappearing if hasLayout is triggered.
+*/
+  display: list-item;
+}
+
+table {
+  border-collapse: collapse;
+  border-spacing: 0;
+}
+
+th,
+td,
+caption {
+  font-weight: normal;
+  vertical-align: top;
+  text-align: left;
+}
+
+q {
+  quotes: none;
+}
+
+q:before,
+q:after {
+  content: '';
+  content: none;
+}
+
+sub,
+sup,
+small {
+  font-size: 75%;
+}
+
+sub,
+sup {
+  line-height: 0;
+  position: relative;
+  vertical-align: baseline;
+}
+
+sub {
+  bottom: -0.25em;
+}
+
+sup {
+  top: -0.5em;
+}
+
+svg {
+/*
+  For IE9. Without, occasionally draws shapes
+  outside the boundaries of <svg> rectangle.
+*/
+  overflow: hidden;
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/wikked/static/960gs/css/text.css	Wed Dec 19 00:23:12 2012 -0800
@@ -0,0 +1,86 @@
+/*
+  960 Grid System ~ Text CSS.
+  Learn more ~ http://960.gs/
+
+  Licensed under GPL and MIT.
+*/
+
+/* `Basic HTML
+----------------------------------------------------------------------------------------------------*/
+
+body {
+  font: 13px/1.5 'Helvetica Neue', Arial, 'Liberation Sans', FreeSans, sans-serif;
+}
+
+pre,
+code {
+  font-family: 'DejaVu Sans Mono', Menlo, Consolas, monospace;
+}
+
+hr {
+  border: 0 #ccc solid;
+  border-top-width: 1px;
+  clear: both;
+  height: 0;
+}
+
+/* `Headings
+----------------------------------------------------------------------------------------------------*/
+
+h1 {
+  font-size: 25px;
+}
+
+h2 {
+  font-size: 23px;
+}
+
+h3 {
+  font-size: 21px;
+}
+
+h4 {
+  font-size: 19px;
+}
+
+h5 {
+  font-size: 17px;
+}
+
+h6 {
+  font-size: 15px;
+}
+
+/* `Spacing
+----------------------------------------------------------------------------------------------------*/
+
+ol {
+  list-style: decimal;
+}
+
+ul {
+  list-style: disc;
+}
+
+li {
+  margin-left: 30px;
+}
+
+p,
+dl,
+hr,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+ol,
+ul,
+pre,
+table,
+address,
+fieldset,
+figure {
+  margin-bottom: 20px;
+}
\ No newline at end of file
Binary file wikked/static/960gs/img/12_col.gif has changed
Binary file wikked/static/960gs/img/16_col.gif has changed
Binary file wikked/static/960gs/img/24_col.gif has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/wikked/static/css/syntax.css	Wed Dec 19 00:23:12 2012 -0800
@@ -0,0 +1,60 @@
+.highlight  { background: #ffffff; }
+.highlight .c { color: #999988; font-style: italic } /* Comment */
+.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
+.highlight .k { font-weight: bold } /* Keyword */
+.highlight .o { font-weight: bold } /* Operator */
+.highlight .cm { color: #999988; font-style: italic } /* Comment.Multiline */
+.highlight .cp { color: #999999; font-weight: bold } /* Comment.Preproc */
+.highlight .c1 { color: #999988; font-style: italic } /* Comment.Single */
+.highlight .cs { color: #999999; font-weight: bold; font-style: italic } /* Comment.Special */
+.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
+.highlight .gd .x { color: #000000; background-color: #ffaaaa } /* Generic.Deleted.Specific */
+.highlight .ge { font-style: italic } /* Generic.Emph */
+.highlight .gr { color: #aa0000 } /* Generic.Error */
+.highlight .gh { color: #999999 } /* Generic.Heading */
+.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
+.highlight .gi .x { color: #000000; background-color: #aaffaa } /* Generic.Inserted.Specific */
+.highlight .go { color: #888888 } /* Generic.Output */
+.highlight .gp { color: #555555 } /* Generic.Prompt */
+.highlight .gs { font-weight: bold } /* Generic.Strong */
+.highlight .gu { color: #aaaaaa } /* Generic.Subheading */
+.highlight .gt { color: #aa0000 } /* Generic.Traceback */
+.highlight .kc { font-weight: bold } /* Keyword.Constant */
+.highlight .kd { font-weight: bold } /* Keyword.Declaration */
+.highlight .kp { font-weight: bold } /* Keyword.Pseudo */
+.highlight .kr { font-weight: bold } /* Keyword.Reserved */
+.highlight .kt { color: #445588; font-weight: bold } /* Keyword.Type */
+.highlight .m { color: #009999 } /* Literal.Number */
+.highlight .s { color: #d14 } /* Literal.String */
+.highlight .na { color: #008080 } /* Name.Attribute */
+.highlight .nb { color: #0086B3 } /* Name.Builtin */
+.highlight .nc { color: #445588; font-weight: bold } /* Name.Class */
+.highlight .no { color: #008080 } /* Name.Constant */
+.highlight .ni { color: #800080 } /* Name.Entity */
+.highlight .ne { color: #990000; font-weight: bold } /* Name.Exception */
+.highlight .nf { color: #990000; font-weight: bold } /* Name.Function */
+.highlight .nn { color: #555555 } /* Name.Namespace */
+.highlight .nt { color: #000080 } /* Name.Tag */
+.highlight .nv { color: #008080 } /* Name.Variable */
+.highlight .ow { font-weight: bold } /* Operator.Word */
+.highlight .w { color: #bbbbbb } /* Text.Whitespace */
+.highlight .mf { color: #009999 } /* Literal.Number.Float */
+.highlight .mh { color: #009999 } /* Literal.Number.Hex */
+.highlight .mi { color: #009999 } /* Literal.Number.Integer */
+.highlight .mo { color: #009999 } /* Literal.Number.Oct */
+.highlight .sb { color: #d14 } /* Literal.String.Backtick */
+.highlight .sc { color: #d14 } /* Literal.String.Char */
+.highlight .sd { color: #d14 } /* Literal.String.Doc */
+.highlight .s2 { color: #d14 } /* Literal.String.Double */
+.highlight .se { color: #d14 } /* Literal.String.Escape */
+.highlight .sh { color: #d14 } /* Literal.String.Heredoc */
+.highlight .si { color: #d14 } /* Literal.String.Interpol */
+.highlight .sx { color: #d14 } /* Literal.String.Other */
+.highlight .sr { color: #009926 } /* Literal.String.Regex */
+.highlight .s1 { color: #d14 } /* Literal.String.Single */
+.highlight .ss { color: #990073 } /* Literal.String.Symbol */
+.highlight .bp { color: #999999 } /* Name.Builtin.Pseudo */
+.highlight .vc { color: #008080 } /* Name.Variable.Class */
+.highlight .vg { color: #008080 } /* Name.Variable.Global */
+.highlight .vi { color: #008080 } /* Name.Variable.Instance */
+.highlight .il { color: #009999 } /* Literal.Number.Integer.Long */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/wikked/static/css/wikked.less	Wed Dec 19 00:23:12 2012 -0800
@@ -0,0 +1,152 @@
+@import "../960gs/css/reset.css";
+@import "../960gs/css/text.css";
+@import "../960gs/css/960.css";
+@import "css/wmd.css";
+
+@baseFontSize: 18px;
+@baseLineHeight: (@baseFontSize * 1.5);
+@smallFontSize: 16px;
+
+@colorNavLink: rgb(128, 128, 128);
+@colorNavLinkHover: rgb(96, 96, 96);
+
+@colorBlueDark: #48577D;
+@colorBlueLight: #7690CF;
+@colorGrayLight: #D3DCF2;
+@colorGrayMedium: #9197A6;
+@colorGrayDark: #43464C;
+
+@colorCode: #523C37;
+
+@backgroundPage: white;
+
+.box-shadow(@style, @c) {
+  box-shadow:         @style @c;
+  -webkit-box-shadow: @style @c;
+  -moz-box-shadow:    @style @c;
+}
+
+
+body {
+    background: url('../img/gray_jean.png');
+    //background-image: url(http://basehold.it/i/27);
+    font-size: @baseFontSize;
+}
+
+p {
+    font-size: @baseFontSize;
+    line-height: @baseLineHeight;
+    margin-top: @baseLineHeight;
+    margin-bottom: @baseLineHeight;
+}
+h1 {
+    font-size: (@baseFontSize * 2);
+    line-height: (@baseLineHeight * 2);
+    margin-top: @baseLineHeight;
+    margin-bottom: 0;
+}
+h2 {
+    font-size: (@baseFontSize * 1.5);
+    line-height: @baseLineHeight;
+    margin-top: @baseLineHeight;
+    margin-bottom: 0;
+}
+h3 {
+    font-size: (@baseFontSize * 1.2);
+    line-height: @baseLineHeight;
+    margin-top: @baseLineHeight;
+    margin-bottom: 0;
+}
+h4 {
+    font-size: @baseFontSize;
+    line-height: @baseLineHeight;
+    margin-top: @baseLineHeight;
+    margin-bottom: 0;
+}
+h5 {
+    font-size: (@baseFontSize / 1.1);
+    line-height: @baseLineHeight;
+    margin-top: @baseLineHeight;
+    margin-bottom: 0;
+}
+h6 {
+    font-size: (@baseFontSize / 1.2);
+    line-height: @baseLineHeight;
+    margin-top: @baseLineHeight;
+    margin-bottom: 0;
+}
+sup, sub {
+    line-height: 0;
+}
+
+a.wiki-link {
+    color: #12f;
+    text-decoration: none;
+    &:link { color: #12f; }
+    &:visited { color: #12f; }
+    &:hover { color: #36f; }
+    &:active { color: #12f; }
+}
+a.wiki-link.missing {
+    color: #f12;
+}
+
+.decorator {
+    text-transform: lowercase;
+    font-weight: normal;
+    font-size: 0.8em;
+}
+
+.rev_id {
+    font-family: monospace;
+    font-size: 0.8em;
+    color: @colorCode;
+}
+
+.wrapper {
+    nav, div.meta {
+        color: @colorNavLink;
+        font-size: @smallFontSize;
+        line-height: (@baseLineHeight * 2);
+        a {
+            padding: 0 1em;
+            display: inline-block;
+            text-decoration: none;
+            color: @colorNavLink;
+            &:link { color: @colorNavLink; }
+            &:visited { color: @colorNavLink; }
+            &:hover { color: @colorNavLinkHover; }
+            &:active { color: @colorNavLink; }
+        }
+    }
+    article {
+        .page {
+            .box-shadow(0 0 10px, rgb(210, 210, 210));
+            padding: @baseLineHeight;
+            background: @backgroundPage;
+        }
+
+        .page>pre {
+            margin-top: @baseLineHeight;
+        }
+
+        form.page-edit {
+            textarea {
+                width: 90%;
+                height: 15em;
+                font-size: 1.15em;
+                font-family: "Lucida Console", Monaco, Consolas, "Courier New", monospace;
+            }
+            input {
+                margin: 0;
+                padding: 0;
+            }
+        }
+        .page-preview {
+        }
+        #wmd-button-bar li {
+            margin-left: 0;
+        }
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/wikked/static/css/wmd.css	Wed Dec 19 00:23:12 2012 -0800
@@ -0,0 +1,115 @@
+
+.wmd-panel
+{
+    margin-left: 25%;
+    margin-right: 25%;
+    width: 50%;
+    min-width: 500px;
+}
+
+.wmd-button-bar 
+{
+    width: 100%;
+    background-color: Silver; 
+}
+
+.wmd-input 
+{ 
+    height: 300px;
+    width: 100%;
+    background-color: Gainsboro;
+    border: 1px solid DarkGray;
+}
+
+.wmd-preview 
+{ 
+    background-color: #c0e0ff; 
+}
+
+.wmd-button-row 
+{
+    position: relative; 
+    margin-left: 5px;
+    margin-right: 5px;
+    margin-bottom: 5px;
+    margin-top: 10px;
+    padding: 0px;  
+    height: 20px;
+}
+
+.wmd-spacer
+{
+    width: 1px; 
+    height: 20px; 
+    margin-left: 14px;
+
+    position: absolute;
+    background-color: Silver;
+    display: inline-block; 
+    list-style: none;
+}
+
+.wmd-button {
+    width: 20px;
+    height: 20px;
+    padding-left: 2px;
+    padding-right: 3px;
+    position: absolute;
+    display: inline-block;
+    list-style: none;
+    cursor: pointer;
+}
+
+.wmd-button > span {
+    background-image: url(../img/wmd-buttons.png);
+    background-repeat: no-repeat;
+    background-position: 0px 0px;
+    width: 20px;
+    height: 20px;
+    display: inline-block;
+}
+
+.wmd-spacer1
+{
+    left: 50px;
+}
+.wmd-spacer2
+{
+    left: 175px;
+}
+.wmd-spacer3
+{
+    left: 300px;
+}
+
+
+
+
+.wmd-prompt-background
+{
+    background-color: Black;
+}
+
+.wmd-prompt-dialog
+{
+    border: 1px solid #999999;
+    background-color: #F5F5F5;
+}
+
+.wmd-prompt-dialog > div {
+    font-size: 0.8em;
+    font-family: arial, helvetica, sans-serif;
+}
+
+
+.wmd-prompt-dialog > form > input[type="text"] {
+    border: 1px solid #999999;
+    color: black;
+}
+
+.wmd-prompt-dialog > form > input[type="button"]{
+    border: 1px solid #888888;
+    font-family: trebuchet MS, helvetica, sans-serif;
+    font-size: 0.8em;
+    font-weight: bold;
+}
Binary file wikked/static/img/creampaper.png has changed
Binary file wikked/static/img/creampaper_@2X.png has changed
Binary file wikked/static/img/gray_jean.png has changed
Binary file wikked/static/img/gray_jean_@2X.jpg has changed
Binary file wikked/static/img/grid.png has changed
Binary file wikked/static/img/grid_@2X.png has changed
Binary file wikked/static/img/retina_dust.png has changed
Binary file wikked/static/img/retina_dust_@2X.png has changed
Binary file wikked/static/img/wmd-buttons.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/wikked/static/js/less-1.3.1.min.js	Wed Dec 19 00:23:12 2012 -0800
@@ -0,0 +1,9 @@
+//
+// LESS - Leaner CSS v1.3.1
+// http://lesscss.org
+// 
+// Copyright (c) 2009-2011, Alexis Sellier
+// Licensed under the Apache 2.0 License.
+//
+(function(e,t){function n(t){return e.less[t.split("/")[1]]}function h(){var e=document.getElementsByTagName("style");for(var t=0;t<e.length;t++)e[t].type.match(l)&&(new r.Parser({filename:document.location.href.replace(/#.*$/,""),dumpLineNumbers:r.dumpLineNumbers})).parse(e[t].innerHTML||"",function(n,r){var i=r.toCSS(),s=e[t];s.type="text/css",s.styleSheet?s.styleSheet.cssText=i:s.innerHTML=i})}function p(e,t){for(var n=0;n<r.sheets.length;n++)d(r.sheets[n],e,t,r.sheets.length-(n+1))}function d(t,n,i,s){var o=t.contents||{},a=e.location.href.replace(/[#?].*$/,""),f=t.href.replace(/\?.*$/,""),l=u&&u.getItem(f),c=u&&u.getItem(f+":timestamp"),h={css:l,timestamp:c};/^[a-z-]+:/.test(f)||(f.charAt(0)=="/"?f=e.location.protocol+"//"+e.location.host+f:f=a.slice(0,a.lastIndexOf("/")+1)+f),g(t.href,t.type,function(e,u){if(!i&&h&&u&&(new Date(u)).valueOf()===(new Date(h.timestamp)).valueOf())m(h.css,t),n(null,null,e,t,{local:!0,remaining:s});else try{o[f]=e,(new r.Parser({optimization:r.optimization,paths:[f.replace(/[\w\.-]+$/,"")],mime:t.type,filename:f,contents:o,dumpLineNumbers:r.dumpLineNumbers})).parse(e,function(r,i){if(r)return E(r,f);try{n(r,i,e,t,{local:!1,lastModified:u,remaining:s}),b(document.getElementById("less-error-message:"+v(f)))}catch(r){E(r,f)}})}catch(a){E(a,f)}},function(e,t){throw new Error("Couldn't load "+t+" ("+e+")")})}function v(e){return e.replace(/^[a-z]+:\/\/?[^\/]+/,"").replace(/^\//,"").replace(/\?.*$/,"").replace(/\.[^\.\/]+$/,"").replace(/[^\.\w-]+/g,"-").replace(/\./g,":")}function m(e,t,n){var r,i=t.href?t.href.replace(/\?.*$/,""):"",s="less:"+(t.title||v(i));if((r=document.getElementById(s))===null){r=document.createElement("style"),r.type="text/css",t.media&&(r.media=t.media),r.id=s;var o=t&&t.nextSibling||null;document.getElementsByTagName("head")[0].insertBefore(r,o)}if(r.styleSheet)try{r.styleSheet.cssText=e}catch(a){throw new Error("Couldn't reassign styleSheet.cssText.")}else(function(e){r.childNodes.length>0?r.firstChild.nodeValue!==e.nodeValue&&r.replaceChild(e,r.firstChild):r.appendChild(e)})(document.createTextNode(e));if(n&&u){w("saving "+i+" to cache.");try{u.setItem(i,e),u.setItem(i+":timestamp",n)}catch(a){w("failed to save")}}}function g(e,t,n,i){function a(t,n,r){t.status>=200&&t.status<300?n(t.responseText,t.getResponseHeader("Last-Modified")):typeof r=="function"&&r(t.status,e)}var o=y(),u=s?r.fileAsync:r.async;typeof o.overrideMimeType=="function"&&o.overrideMimeType("text/css"),o.open("GET",e,u),o.setRequestHeader("Accept",t||"text/x-less, text/css; q=0.9, */*; q=0.5"),o.send(null),s&&!r.fileAsync?o.status===0||o.status>=200&&o.status<300?n(o.responseText):i(o.status,e):u?o.onreadystatechange=function(){o.readyState==4&&a(o,n,i)}:a(o,n,i)}function y(){if(e.XMLHttpRequest)return new XMLHttpRequest;try{return new ActiveXObject("MSXML2.XMLHTTP.3.0")}catch(t){return w("browser doesn't support AJAX."),null}}function b(e){return e&&e.parentNode.removeChild(e)}function w(e){r.env=="development"&&typeof console!="undefined"&&console.log("less: "+e)}function E(e,t){var n="less-error-message:"+v(t),i='<li><label>{line}</label><pre class="{class}">{content}</pre></li>',s=document.createElement("div"),o,u,a=[],f=e.filename||t,l=f.match(/([^\/]+)$/)[1];s.id=n,s.className="less-error-message",u="<h3>"+(e.message||"There is an error in your .less file")+"</h3>"+'<p>in <a href="'+f+'">'+l+"</a> ";var c=function(e,t,n){e.extract[t]&&a.push(i.replace(/\{line\}/,parseInt(e.line)+(t-1)).replace(/\{class\}/,n).replace(/\{content\}/,e.extract[t]))};e.stack?u+="<br/>"+e.stack.split("\n").slice(1).join("<br/>"):e.extract&&(c(e,0,""),c(e,1,"line"),c(e,2,""),u+="on line "+e.line+", column "+(e.column+1)+":</p>"+"<ul>"+a.join("")+"</ul>"),s.innerHTML=u,m([".less-error-message ul, .less-error-message li {","list-style-type: none;","margin-right: 15px;","padding: 4px 0;","margin: 0;","}",".less-error-message label {","font-size: 12px;","margin-right: 15px;","padding: 4px 0;","color: #cc7777;","}",".less-error-message pre {","color: #dd6666;","padding: 4px 0;","margin: 0;","display: inline-block;","}",".less-error-message pre.line {","color: #ff0000;","}",".less-error-message h3 {","font-size: 20px;","font-weight: bold;","padding: 15px 0 5px 0;","margin: 0;","}",".less-error-message a {","color: #10a","}",".less-error-message .error {","color: red;","font-weight: bold;","padding-bottom: 2px;","border-bottom: 1px dashed red;","}"].join("\n"),{title:"error-message"}),s.style.cssText=["font-family: Arial, sans-serif","border: 1px solid #e00","background-color: #eee","border-radius: 5px","-webkit-border-radius: 5px","-moz-border-radius: 5px","color: #e00","padding: 15px","margin-bottom: 15px"].join(";"),r.env=="development"&&(o=setInterval(function(){document.body&&(document.getElementById(n)?document.body.replaceChild(s,document.getElementById(n)):document.body.insertBefore(s,document.body.firstChild),clearInterval(o))},10))}Array.isArray||(Array.isArray=function(e){return Object.prototype.toString.call(e)==="[object Array]"||e instanceof Array}),Array.prototype.forEach||(Array.prototype.forEach=function(e,t){var n=this.length>>>0;for(var r=0;r<n;r++)r in this&&e.call(t,this[r],r,this)}),Array.prototype.map||(Array.prototype.map=function(e){var t=this.length>>>0,n=new Array(t),r=arguments[1];for(var i=0;i<t;i++)i in this&&(n[i]=e.call(r,this[i],i,this));return n}),Array.prototype.filter||(Array.prototype.filter=function(e){var t=[],n=arguments[1];for(var r=0;r<this.length;r++)e.call(n,this[r])&&t.push(this[r]);return t}),Array.prototype.reduce||(Array.prototype.reduce=function(e){var t=this.length>>>0,n=0;if(t===0&&arguments.length===1)throw new TypeError;if(arguments.length>=2)var r=arguments[1];else do{if(n in this){r=this[n++];break}if(++n>=t)throw new TypeError}while(!0);for(;n<t;n++)n in this&&(r=e.call(null,r,this[n],n,this));return r}),Array.prototype.indexOf||(Array.prototype.indexOf=function(e){var t=this.length,n=arguments[1]||0;if(!t)return-1;if(n>=t)return-1;n<0&&(n+=t);for(;n<t;n++){if(!Object.prototype.hasOwnProperty.call(this,n))continue;if(e===this[n])return n}return-1}),Object.keys||(Object.keys=function(e){var t=[];for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&t.push(n);return t}),String.prototype.trim||(String.prototype.trim=function(){return String(this).replace(/^\s\s*/,"").replace(/\s\s*$/,"")});var r,i;typeof environment=="object"&&{}.toString.call(environment)==="[object Environment]"?(typeof e=="undefined"?r={}:r=e.less={},i=r.tree={},r.mode="rhino"):typeof e=="undefined"?(r=exports,i=n("./tree"),r.mode="node"):(typeof e.less=="undefined"&&(e.less={}),r=e.less,i=e.less.tree={},r.mode="browser"),r.Parser=function(t){function g(){a=c[u],f=o,h=o}function y(){c[u]=a,o=f,h=o}function b(){o>h&&(c[u]=c[u].slice(o-h),h=o)}function w(e){var t=e.charCodeAt(0);return t===32||t===10||t===9}function E(e){var t,n,r,i,a;if(e instanceof Function)return e.call(p.parsers);if(typeof e=="string")t=s.charAt(o)===e?e:null,r=1,b();else{b();if(!(t=e.exec(c[u])))return null;r=t[0].length}if(t)return S(r),typeof t=="string"?t:t.length===1?t[0]:t}function S(e){var t=o,n=u,r=o+c[u].length,i=o+=e;while(o<r){if(!w(s.charAt(o)))break;o++}return c[u]=c[u].slice(e+(o-i)),h=o,c[u].length===0&&u<c.length-1&&u++,t!==o||n!==u}function x(e,t){var n=E(e);if(!!n)return n;T(t||(typeof e=="string"?"expected '"+e+"' got '"+s.charAt(o)+"'":"unexpected token"))}function T(e,t){throw{index:o,type:t||"Syntax",message:e}}function N(e){return typeof e=="string"?s.charAt(o)===e:e.test(c[u])?!0:!1}function C(e,t){return e.filename&&t.filename&&e.filename!==t.filename?p.imports.contents[e.filename]:s}function k(e,t){for(var n=e,r=-1;n>=0&&t.charAt(n)!=="\n";n--)r++;return{line:typeof e=="number"?(t.slice(0,e).match(/\n/g)||"").length:null,column:r}}function L(e){return r.mode==="browser"||r.mode==="rhino"?e.filename:n("path").resolve(e.filename)}function A(e,t,n){return{lineNumber:k(e,t).line+1,fileName:L(n)}}function O(e,t){var n=C(e,t),r=k(e.index,n),i=r.line,s=r.column,o=n.split("\n");this.type=e.type||"Syntax",this.message=e.message,this.filename=e.filename||t.filename,this.index=e.index,this.line=typeof i=="number"?i+1:null,this.callLine=e.call&&k(e.call,n).line+1,this.callExtract=o[k(e.call,n).line],this.stack=e.stack,this.column=s,this.extract=[o[i-1],o[i],o[i+1]]}var s,o,u,a,f,l,c,h,p,d=this,t=t||{};t.contents||(t.contents={});var v=function(){},m=this.imports={paths:t&&t.paths||[],queue:[],files:{},contents:t.contents,mime:t&&t.mime,error:null,push:function(e,n){var i=this;this.queue.push(e),r.Parser.importer(e,this.paths,function(t,r){i.queue.splice(i.queue.indexOf(e),1);var s=e in i.files;i.files[e]=r,t&&!i.error&&(i.error=t),n(t,r,s),i.queue.length===0&&v(t)},t)}};return this.env=t=t||{},this.optimization="optimization"in this.env?this.env.optimization:1,this.env.filename=this.env.filename||null,p={imports:m,parse:function(e,a){var f,d,m,g,y,b,w=[],S,x=null;o=u=h=l=0,s=e.replace(/\r\n/g,"\n"),s=s.replace(/^\uFEFF/,""),c=function(e){var n=0,r=/(?:@\{[\w-]+\}|[^"'`\{\}\/\(\)\\])+/g,i=/\/\*(?:[^*]|\*+[^\/*])*\*+\/|\/\/.*/g,o=/"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'|`((?:[^`]|\\.)*)`/g,u=0,a,f=e[0],l;for(var c=0,h,p;c<s.length;c++){r.lastIndex=c,(a=r.exec(s))&&a.index===c&&(c+=a[0].length,f.push(a[0])),h=s.charAt(c),i.lastIndex=o.lastIndex=c,(a=o.exec(s))&&a.index===c&&(c+=a[0].length,f.push(a[0]),h=s.charAt(c)),!l&&h==="/"&&(p=s.charAt(c+1),(p==="/"||p==="*")&&(a=i.exec(s))&&a.index===c&&(c+=a[0].length,f.push(a[0]),h=s.charAt(c)));switch(h){case"{":if(!l){u++,f.push(h);break};case"}":if(!l){u--,f.push(h),e[++n]=f=[];break};case"(":if(!l){l=!0,f.push(h);break};case")":if(l){l=!1,f.push(h);break};default:f.push(h)}}return u>0&&(x=new O({index:c,type:"Parse",message:"missing closing `}`",filename:t.filename},t)),e.map(function(e){return e.join("")})}([[]]);if(x)return a(x);try{f=new i.Ruleset([],E(this.parsers.primary)),f.root=!0}catch(T){return a(new O(T,t))}f.toCSS=function(e){var s,o,u;return function(s,o){var u=[],a;s=s||{},typeof o=="object"&&!Array.isArray(o)&&(o=Object.keys(o).map(function(e){var t=o[e];return t instanceof i.Value||(t instanceof i.Expression||(t=new i.Expression([t])),t=new i.Value([t])),new i.Rule("@"+e,t,!1,0)}),u=[new i.Ruleset(null,o)]);try{var f=e.call(this,{frames:u}).toCSS([],{compress:s.compress||!1,dumpLineNumbers:t.dumpLineNumbers})}catch(l){throw new O(l,t)}if(a=p.imports.error)throw a instanceof O?a:new O(a,t);return s.yuicompress&&r.mode==="node"?n("./cssmin").compressor.cssmin(f):s.compress?f.replace(/(\s)+/g,"$1"):f}}(f.eval);if(o<s.length-1){o=l,b=s.split("\n"),y=(s.slice(0,o).match(/\n/g)||"").length+1;for(var N=o,C=-1;N>=0&&s.charAt(N)!=="\n";N--)C++;x={type:"Parse",message:"Syntax Error on line "+y,index:o,filename:t.filename,line:y,column:C,extract:[b[y-2],b[y-1],b[y]]}}this.imports.queue.length>0?v=function(e){e?a(e):a(null,f)}:a(x,f)},parsers:{primary:function(){var e,t=[];while((e=E(this.mixin.definition)||E(this.rule)||E(this.ruleset)||E(this.mixin.call)||E(this.comment)||E(this.directive))||E(/^[\s\n]+/))e&&t.push(e);return t},comment:function(){var e;if(s.charAt(o)!=="/")return;if(s.charAt(o+1)==="/")return new i.Comment(E(/^\/\/.*/),!0);if(e=E(/^\/\*(?:[^*]|\*+[^\/*])*\*+\/\n?/))return new i.Comment(e)},entities:{quoted:function(){var e,t=o,n;s.charAt(t)==="~"&&(t++,n=!0);if(s.charAt(t)!=='"'&&s.charAt(t)!=="'")return;n&&E("~");if(e=E(/^"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'/))return new i.Quoted(e[0],e[1]||e[2],n)},keyword:function(){var e;if(e=E(/^[_A-Za-z-][_A-Za-z0-9-]*/))return i.colors.hasOwnProperty(e)?new i.Color(i.colors[e].slice(1)):new i.Keyword(e)},call:function(){var e,n,r,s,a=o;if(!(e=/^([\w-]+|%|progid:[\w\.]+)\(/.exec(c[u])))return;e=e[1],n=e.toLowerCase();if(n==="url")return null;o+=e.length;if(n==="alpha"){s=E(this.alpha);if(typeof s!="undefined")return s}E("("),r=E(this.entities.arguments);if(!E(")"))return;if(e)return new i.Call(e,r,a,t.filename)},arguments:function(){var e=[],t;while(t=E(this.entities.assignment)||E(this.expression)){e.push(t);if(!E(","))break}return e},literal:function(){return E(this.entities.ratio)||E(this.entities.dimension)||E(this.entities.color)||E(this.entities.quoted)},assignment:function(){var e,t;if((e=E(/^\w+(?=\s?=)/i))&&E("=")&&(t=E(this.entity)))return new i.Assignment(e,t)},url:function(){var e;if(s.charAt(o)!=="u"||!E(/^url\(/))return;return e=E(this.entities.quoted)||E(this.entities.variable)||E(/^(?:(?:\\[\(\)'"])|[^\(\)'"])+/)||"",x(")"),new i.URL(e.value!=null||e instanceof i.Variable?e:new i.Anonymous(e),m.paths)},variable:function(){var e,n=o;if(s.charAt(o)==="@"&&(e=E(/^@@?[\w-]+/)))return new i.Variable(e,n,t.filename)},variableCurly:function(){var e,n,r=o;if(s.charAt(o)==="@"&&(n=E(/^@\{([\w-]+)\}/)))return new i.Variable("@"+n[1],r,t.filename)},color:function(){var e;if(s.charAt(o)==="#"&&(e=E(/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/)))return new i.Color(e[1])},dimension:function(){var e,t=s.charCodeAt(o);if(t>57||t<45||t===47)return;if(e=E(/^(-?\d*\.?\d+)(px|%|em|pc|ex|in|deg|s|ms|pt|cm|mm|rad|grad|turn|dpi|dpcm|dppx|rem|vw|vh|vmin|vm|ch)?/))return new i.Dimension(e[1],e[2])},ratio:function(){var e,t=s.charCodeAt(o);if(t>57||t<48)return;if(e=E(/^(\d+\/\d+)/))return new i.Ratio(e[1])},javascript:function(){var e,t=o,n;s.charAt(t)==="~"&&(t++,n=!0);if(s.charAt(t)!=="`")return;n&&E("~");if(e=E(/^`([^`]*)`/))return new i.JavaScript(e[1],o,n)}},variable:function(){var e;if(s.charAt(o)==="@"&&(e=E(/^(@[\w-]+)\s*:/)))return e[1]},shorthand:function(){var e,t;if(!N(/^[@\w.%-]+\/[@\w.-]+/))return;g();if((e=E(this.entity))&&E("/")&&(t=E(this.entity)))return new i.Shorthand(e,t);y()},mixin:{call:function(){var e=[],n,r,u=[],a,f=o,l=s.charAt(o),c,h,p=!1;if(l!=="."&&l!=="#")return;g();while(n=E(/^[#.](?:[\w-]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/))e.push(new i.Element(r,n,o)),r=E(">");if(E("(")){while(a=E(this.expression)){h=a,c=null;if(a.value.length==1){var d=a.value[0];if(d instanceof i.Variable&&E(":")){if(!(h=E(this.expression)))throw new Error("Expected value");c=d.name}}u.push({name:c,value:h});if(!E(","))break}if(!E(")"))throw new Error("Expected )")}E(this.important)&&(p=!0);if(e.length>0&&(E(";")||N("}")))return new i.mixin.Call(e,u,f,t.filename,p);y()},definition:function(){var e,t=[],n,r,u,a,f,c=!1;if(s.charAt(o)!=="."&&s.charAt(o)!=="#"||N(/^[^{]*(;|})/))return;g();if(n=E(/^([#.](?:[\w-]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+)\s*\(/)){e=n[1];do{if(s.charAt(o)==="."&&E(/^\.{3}/)){c=!0;break}if(!(u=E(this.entities.variable)||E(this.entities.literal)||E(this.entities.keyword)))break;if(u instanceof i.Variable)if(E(":"))a=x(this.expression,"expected expression"),t.push({name:u.name,value:a});else{if(E(/^\.{3}/)){t.push({name:u.name,variadic:!0}),c=!0;break}t.push({name:u.name})}else t.push({value:u})}while(E(","));E(")")||(l=o,y()),E(/^when/)&&(f=x(this.conditions,"expected condition")),r=E(this.block);if(r)return new i.mixin.Definition(e,t,r,f,c);y()}}},entity:function(){return E(this.entities.literal)||E(this.entities.variable)||E(this.entities.url)||E(this.entities.call)||E(this.entities.keyword)||E(this.entities.javascript)||E(this.comment)},end:function(){return E(";")||N("}")},alpha:function(){var e;if(!E(/^\(opacity=/i))return;if(e=E(/^\d+/)||E(this.entities.variable))return x(")"),new i.Alpha(e)},element:function(){var e,t,n,r;n=E(this.combinator),e=E(/^(?:\d+\.\d+|\d+)%/)||E(/^(?:[.#]?|:*)(?:[\w-]|[^\x00-\x9f]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/)||E("*")||E("&")||E(this.attribute)||E(/^\([^)@]+\)/)||E(/^[\.#](?=@)/)||E(this.entities.variableCurly),e||E("(")&&(r=E(this.entities.variableCurly)||E(this.entities.variable))&&E(")")&&(e=new i.Paren(r));if(e)return new i.Element(n,e,o)},combinator:function(){var e,t=s.charAt(o);if(t===">"||t==="+"||t==="~"){o++;while(s.charAt(o).match(/\s/))o++;return new i.Combinator(t)}return s.charAt(o-1).match(/\s/)?new i.Combinator(" "):new i.Combinator(null)},selector:function(){var e,t,n=[],r,u;if(E("("))return e=E(this.entity),x(")"),new i.Selector([new i.Element("",e,o)]);while(t=E(this.element)){r=s.charAt(o),n.push(t);if(r==="{"||r==="}"||r===";"||r===",")break}if(n.length>0)return new i.Selector(n)},tag:function(){return E(/^[A-Za-z][A-Za-z-]*[0-9]?/)||E("*")},attribute:function(){var e="",t,n,r;if(!E("["))return;if(t=E(/^(?:[_A-Za-z0-9-]|\\.)+/)||E(this.entities.quoted))(r=E(/^[|~*$^]?=/))&&(n=E(this.entities.quoted)||E(/^[\w-]+/))?e=[t,r,n.toCSS?n.toCSS():n].join(""):e=t;if(!E("]"))return;if(e)return"["+e+"]"},block:function(){var e;if(E("{")&&(e=E(this.primary))&&E("}"))return e},ruleset:function(){var e=[],n,r,u,a;g(),t.dumpLineNumbers&&(a=A(o,s,t));while(n=E(this.selector)){e.push(n),E(this.comment);if(!E(","))break;E(this.comment)}if(e.length>0&&(r=E(this.block))){var f=new i.Ruleset(e,r,t.strictImports);return t.dumpLineNumbers&&(f.debugInfo=a),f}l=o,y()},rule:function(){var e,t,n=s.charAt(o),r,a;g();if(n==="."||n==="#"||n==="&")return;if(e=E(this.variable)||E(this.property)){e.charAt(0)!="@"&&(a=/^([^@+\/'"*`(;{}-]*);/.exec(c[u]))?(o+=a[0].length-1,t=new i.Anonymous(a[1])):e==="font"?t=E(this.font):t=E(this.value),r=E(this.important);if(t&&E(this.end))return new i.Rule(e,t,r,f);l=o,y()}},"import":function(){var e,t,n=o;g();var r=E(/^@import(?:-(once))?\s+/);if(r&&(e=E(this.entities.quoted)||E(this.entities.url))){t=E(this.mediaFeatures);if(E(";"))return new i.Import(e,m,t,r[1]==="once",n)}y()},mediaFeature:function(){var e,t,n=[];do if(e=E(this.entities.keyword))n.push(e);else if(E("(")){t=E(this.property),e=E(this.entity);if(!E(")"))return null;if(t&&e)n.push(new i.Paren(new i.Rule(t,e,null,o,!0)));else{if(!e)return null;n.push(new i.Paren(e))}}while(e);if(n.length>0)return new i.Expression(n)},mediaFeatures:function(){var e,t=[];do if(e=E(this.mediaFeature)){t.push(e);if(!E(","))break}else if(e=E(this.entities.variable)){t.push(e);if(!E(","))break}while(e);return t.length>0?t:null},media:function(){var e,n,r,u;t.dumpLineNumbers&&(u=A(o,s,t));if(E(/^@media/)){e=E(this.mediaFeatures);if(n=E(this.block))return r=new i.Media(n,e),t.dumpLineNumbers&&(r.debugInfo=u),r}},directive:function(){var e,t,n,r,u,a,f,l,c;if(s.charAt(o)!=="@")return;if(t=E(this["import"])||E(this.media))return t;g(),e=E(/^@[a-z-]+/),f=e,e.charAt(1)=="-"&&e.indexOf("-",2)>0&&(f="@"+e.slice(e.indexOf("-",2)+1));switch(f){case"@font-face":l=!0;break;case"@viewport":case"@top-left":case"@top-left-corner":case"@top-center":case"@top-right":case"@top-right-corner":case"@bottom-left":case"@bottom-left-corner":case"@bottom-center":case"@bottom-right":case"@bottom-right-corner":case"@left-top":case"@left-middle":case"@left-bottom":case"@right-top":case"@right-middle":case"@right-bottom":l=!0;break;case"@page":case"@document":case"@supports":case"@keyframes":l=!0,c=!0}c&&(e+=" "+(E(/^[^{]+/)||"").trim());if(l){if(n=E(this.block))return new i.Directive(e,n)}else if((t=E(this.entity))&&E(";"))return new i.Directive(e,t);y()},font:function(){var e=[],t=[],n,r,s,o;while(o=E(this.shorthand)||E(this.entity))t.push(o);e.push(new i.Expression(t));if(E(","))while(o=E(this.expression)){e.push(o);if(!E(","))break}return new i.Value(e)},value:function(){var e,t=[],n;while(e=E(this.expression)){t.push(e);if(!E(","))break}if(t.length>0)return new i.Value(t)},important:function(){if(s.charAt(o)==="!")return E(/^! *important/)},sub:function(){var e;if(E("(")&&(e=E(this.expression))&&E(")"))return e},multiplication:function(){var e,t,n,r;if(e=E(this.operand)){while(!N(/^\/\*/)&&(n=E("/")||E("*"))&&(t=E(this.operand)))r=new i.Operation(n,[r||e,t]);return r||e}},addition:function(){var e,t,n,r;if(e=E(this.multiplication)){while((n=E(/^[-+]\s+/)||!w(s.charAt(o-1))&&(E("+")||E("-")))&&(t=E(this.multiplication)))r=new i.Operation(n,[r||e,t]);return r||e}},conditions:function(){var e,t,n=o,r;if(e=E(this.condition)){while(E(",")&&(t=E(this.condition)))r=new i.Condition("or",r||e,t,n);return r||e}},condition:function(){var e,t,n,r,s=o,u=!1;E(/^not/)&&(u=!0),x("(");if(e=E(this.addition)||E(this.entities.keyword)||E(this.entities.quoted))return(r=E(/^(?:>=|=<|[<=>])/))?(t=E(this.addition)||E(this.entities.keyword)||E(this.entities.quoted))?n=new i.Condition(r,e,t,s,u):T("expected expression"):n=new i.Condition("=",e,new i.Keyword("true"),s,u),x(")"),E(/^and/)?new i.Condition("and",n,E(this.condition)):n},operand:function(){var e,t=s.charAt(o+1);s.charAt(o)==="-"&&(t==="@"||t==="(")&&(e=E("-"));var n=E(this.sub)||E(this.entities.dimension)||E(this.entities.color)||E(this.entities.variable)||E(this.entities.call);return e?new i.Operation("*",[new i.Dimension(-1),n]):n},expression:function(){var e,t,n=[],r;while(e=E(this.addition)||E(this.entity))n.push(e);if(n.length>0)return new i.Expression(n)},property:function(){var e;if(e=E(/^(\*?-?[_a-z0-9-]+)\s*:/))return e[1]}}}};if(r.mode==="browser"||r.mode==="rhino")r.Parser.importer=function(e,t,n,r){!/^([a-z-]+:)?\//.test(e)&&t.length>0&&(e=t[0]+e),d({href:e,title:e,type:r.mime,contents:r.contents},function(i){i&&typeof r.errback=="function"?r.errback.call(null,e,t,n,r):n.apply(null,arguments)},!0)};(function(e){function t(t){return e.functions.hsla(t.h,t.s,t.l,t.a)}function n(t){if(t instanceof e.Dimension)return parseFloat(t.unit=="%"?t.value/100:t.value);if(typeof t=="number")return t;throw{error:"RuntimeError",message:"color functions take numbers as parameters"}}function r(e){return Math.min(1,Math.max(0,e))}e.functions={rgb:function(e,t,n){return this.rgba(e,t,n,1)},rgba:function(t,r,i,s){var o=[t,r,i].map(function(e){return n(e)}),s=n(s);return new e.Color(o,s)},hsl:function(e,t,n){return this.hsla(e,t,n,1)},hsla:function(e,t,r,i){function u(e){return e=e<0?e+1:e>1?e-1:e,e*6<1?o+(s-o)*e*6:e*2<1?s:e*3<2?o+(s-o)*(2/3-e)*6:o}e=n(e)%360/360,t=n(t),r=n(r),i=n(i);var s=r<=.5?r*(t+1):r+t-r*t,o=r*2-s;return this.rgba(u(e+1/3)*255,u(e)*255,u(e-1/3)*255,i)},hue:function(t){return new e.Dimension(Math.round(t.toHSL().h))},saturation:function(t){return new e.Dimension(Math.round(t.toHSL().s*100),"%")},lightness:function(t){return new e.Dimension(Math.round(t.toHSL().l*100),"%")},red:function(t){return new e.Dimension(t.rgb[0])},green:function(t){return new e.Dimension(t.rgb[1])},blue:function(t){return new e.Dimension(t.rgb[2])},alpha:function(t){return new e.Dimension(t.toHSL().a)},luma:function(t){return new e.Dimension(Math.round((.2126*(t.rgb[0]/255)+.7152*(t.rgb[1]/255)+.0722*(t.rgb[2]/255))*t.alpha*100),"%")},saturate:function(e,n){var i=e.toHSL();return i.s+=n.value/100,i.s=r(i.s),t(i)},desaturate:function(e,n){var i=e.toHSL();return i.s-=n.value/100,i.s=r(i.s),t(i)},lighten:function(e,n){var i=e.toHSL();return i.l+=n.value/100,i.l=r(i.l),t(i)},darken:function(e,n){var i=e.toHSL();return i.l-=n.value/100,i.l=r(i.l),t(i)},fadein:function(e,n){var i=e.toHSL();return i.a+=n.value/100,i.a=r(i.a),t(i)},fadeout:function(e,n){var i=e.toHSL();return i.a-=n.value/100,i.a=r(i.a),t(i)},fade:function(e,n){var i=e.toHSL();return i.a=n.value/100,i.a=r(i.a),t(i)},spin:function(e,n){var r=e.toHSL(),i=(r.h+n.value)%360;return r.h=i<0?360+i:i,t(r)},mix:function(t,n,r){r||(r=new e.Dimension(50));var i=r.value/100,s=i*2-1,o=t.toHSL().a-n.toHSL().a,u=((s*o==-1?s:(s+o)/(1+s*o))+1)/2,a=1-u,f=[t.rgb[0]*u+n.rgb[0]*a,t.rgb[1]*u+n.rgb[1]*a,t.rgb[2]*u+n.rgb[2]*a],l=t.alpha*i+n.alpha*(1-i);return new e.Color(f,l)},greyscale:function(t){return this.desaturate(t,new e.Dimension(100))},contrast:function(e,t,n,r){return typeof n=="undefined"&&(n=this.rgba(255,255,255,1)),typeof t=="undefined"&&(t=this.rgba(0,0,0,1)),typeof r=="undefined"?r=.43:r=r.value,(.2126*(e.rgb[0]/255)+.7152*(e.rgb[1]/255)+.0722*(e.rgb[2]/255))*e.alpha<r?n:t},e:function(t){return new e.Anonymous(t instanceof e.JavaScript?t.evaluated:t)},escape:function(t){return new e.Anonymous(encodeURI(t.value).replace(/=/g,"%3D").replace(/:/g,"%3A").replace(/#/g,"%23").replace(/;/g,"%3B").replace(/\(/g,"%28").replace(/\)/g,"%29"))},"%":function(t){var n=Array.prototype.slice.call(arguments,1),r=t.value;for(var i=0;i<n.length;i++)r=r.replace(/%[sda]/i,function(e){var t=e.match(/s/i)?n[i].value:n[i].toCSS();return e.match(/[A-Z]$/)?encodeURIComponent(t):t});return r=r.replace(/%%/g,"%"),new e.Quoted('"'+r+'"',r)},round:function(t,r){var i=typeof r=="undefined"?0:r.value;if(t instanceof e.Dimension)return new e.Dimension(n(t).toFixed(i),t.unit);if(typeof t=="number")return t.toFixed(i);throw{type:"Argument",message:"argument must be a number"}},ceil:function(e){return this._math("ceil",e)},floor:function(e){return this._math("floor",e)},_math:function(t,r){if(r instanceof e.Dimension)return new e.Dimension(Math[t](n(r)),r.unit);if(typeof r=="number")return Math[t](r);throw{type:"Argument",message:"argument must be a number"}},argb:function(t){return new e.Anonymous(t.toARGB())},percentage:function(t){return new e.Dimension(t.value*100,"%")},color:function(t){if(t instanceof e.Quoted)return new e.Color(t.value.slice(1));throw{type:"Argument",message:"argument must be a string"}},iscolor:function(t){return this._isa(t,e.Color)},isnumber:function(t){return this._isa(t,e.Dimension)},isstring:function(t){return this._isa(t,e.Quoted)},iskeyword:function(t){return this._isa(t,e.Keyword)},isurl:function(t){return this._isa(t,e.URL)},ispixel:function(t){return t instanceof e.Dimension&&t.unit==="px"?e.True:e.False},ispercentage:function(t){return t instanceof e.Dimension&&t.unit==="%"?e.True:e.False},isem:function(t){return t instanceof e.Dimension&&t.unit==="em"?e.True:e.False},_isa:function(t,n){return t instanceof n?e.True:e.False},multiply:function(e,t){var n=e.rgb[0]*t.rgb[0]/255,r=e.rgb[1]*t.rgb[1]/255,i=e.rgb[2]*t.rgb[2]/255;return this.rgb(n,r,i)},screen:function(e,t){var n=255-(255-e.rgb[0])*(255-t.rgb[0])/255,r=255-(255-e.rgb[1])*(255-t.rgb[1])/255,i=255-(255-e.rgb[2])*(255-t.rgb[2])/255;return this.rgb(n,r,i)},overlay:function(e,t){var n=e.rgb[0]<128?2*e.rgb[0]*t.rgb[0]/255:255-2*(255-e.rgb[0])*(255-t.rgb[0])/255,r=e.rgb[1]<128?2*e.rgb[1]*t.rgb[1]/255:255-2*(255-e.rgb[1])*(255-t.rgb[1])/255,i=e.rgb[2]<128?2*e.rgb[2]*t.rgb[2]/255:255-2*(255-e.rgb[2])*(255-t.rgb[2])/255;return this.rgb(n,r,i)},softlight:function(e,t){var n=t.rgb[0]*e.rgb[0]/255,r=n+e.rgb[0]*(255-(255-e.rgb[0])*(255-t.rgb[0])/255-n)/255;n=t.rgb[1]*e.rgb[1]/255;var i=n+e.rgb[1]*(255-(255-e.rgb[1])*(255-t.rgb[1])/255-n)/255;n=t.rgb[2]*e.rgb[2]/255;var s=n+e.rgb[2]*(255-(255-e.rgb[2])*(255-t.rgb[2])/255-n)/255;return this.rgb(r,i,s)},hardlight:function(e,t){var n=t.rgb[0]<128?2*t.rgb[0]*e.rgb[0]/255:255-2*(255-t.rgb[0])*(255-e.rgb[0])/255,r=t.rgb[1]<128?2*t.rgb[1]*e.rgb[1]/255:255-2*(255-t.rgb[1])*(255-e.rgb[1])/255,i=t.rgb[2]<128?2*t.rgb[2]*e.rgb[2]/255:255-2*(255-t.rgb[2])*(255-e.rgb[2])/255;return this.rgb(n,r,i)},difference:function(e,t){var n=Math.abs(e.rgb[0]-t.rgb[0]),r=Math.abs(e.rgb[1]-t.rgb[1]),i=Math.abs(e.rgb[2]-t.rgb[2]);return this.rgb(n,r,i)},exclusion:function(e,t){var n=e.rgb[0]+t.rgb[0]*(255-e.rgb[0]-e.rgb[0])/255,r=e.rgb[1]+t.rgb[1]*(255-e.rgb[1]-e.rgb[1])/255,i=e.rgb[2]+t.rgb[2]*(255-e.rgb[2]-e.rgb[2])/255;return this.rgb(n,r,i)},average:function(e,t){var n=(e.rgb[0]+t.rgb[0])/2,r=(e.rgb[1]+t.rgb[1])/2,i=(e.rgb[2]+t.rgb[2])/2;return this.rgb(n,r,i)},negation:function(e,t){var n=255-Math.abs(255-t.rgb[0]-e.rgb[0]),r=255-Math.abs(255-t.rgb[1]-e.rgb[1]),i=255-Math.abs(255-t.rgb[2]-e.rgb[2]);return this.rgb(n,r,i)},tint:function(e,t){return this.mix(this.rgb(255,255,255),e,t)},shade:function(e,t){return this.mix(this.rgb(0,0,0),e,t)}}})(n("./tree")),function(e){e.colors={aliceblue:"#f0f8ff",antiquewhite:"#faebd7",aqua:"#00ffff",aquamarine:"#7fffd4",azure:"#f0ffff",beige:"#f5f5dc",bisque:"#ffe4c4",black:"#000000",blanchedalmond:"#ffebcd",blue:"#0000ff",blueviolet:"#8a2be2",brown:"#a52a2a",burlywood:"#deb887",cadetblue:"#5f9ea0",chartreuse:"#7fff00",chocolate:"#d2691e",coral:"#ff7f50",cornflowerblue:"#6495ed",cornsilk:"#fff8dc",crimson:"#dc143c",cyan:"#00ffff",darkblue:"#00008b",darkcyan:"#008b8b",darkgoldenrod:"#b8860b",darkgray:"#a9a9a9",darkgrey:"#a9a9a9",darkgreen:"#006400",darkkhaki:"#bdb76b",darkmagenta:"#8b008b",darkolivegreen:"#556b2f",darkorange:"#ff8c00",darkorchid:"#9932cc",darkred:"#8b0000",darksalmon:"#e9967a",darkseagreen:"#8fbc8f",darkslateblue:"#483d8b",darkslategray:"#2f4f4f",darkslategrey:"#2f4f4f",darkturquoise:"#00ced1",darkviolet:"#9400d3",deeppink:"#ff1493",deepskyblue:"#00bfff",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1e90ff",firebrick:"#b22222",floralwhite:"#fffaf0",forestgreen:"#228b22",fuchsia:"#ff00ff",gainsboro:"#dcdcdc",ghostwhite:"#f8f8ff",gold:"#ffd700",goldenrod:"#daa520",gray:"#808080",grey:"#808080",green:"#008000",greenyellow:"#adff2f",honeydew:"#f0fff0",hotpink:"#ff69b4",indianred:"#cd5c5c",indigo:"#4b0082",ivory:"#fffff0",khaki:"#f0e68c",lavender:"#e6e6fa",lavenderblush:"#fff0f5",lawngreen:"#7cfc00",lemonchiffon:"#fffacd",lightblue:"#add8e6",lightcoral:"#f08080",lightcyan:"#e0ffff",lightgoldenrodyellow:"#fafad2",lightgray:"#d3d3d3",lightgrey:"#d3d3d3",lightgreen:"#90ee90",lightpink:"#ffb6c1",lightsalmon:"#ffa07a",lightseagreen:"#20b2aa",lightskyblue:"#87cefa",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#b0c4de",lightyellow:"#ffffe0",lime:"#00ff00",limegreen:"#32cd32",linen:"#faf0e6",magenta:"#ff00ff",maroon:"#800000",mediumaquamarine:"#66cdaa",mediumblue:"#0000cd",mediumorchid:"#ba55d3",mediumpurple:"#9370d8",mediumseagreen:"#3cb371",mediumslateblue:"#7b68ee",mediumspringgreen:"#00fa9a",mediumturquoise:"#48d1cc",mediumvioletred:"#c71585",midnightblue:"#191970",mintcream:"#f5fffa",mistyrose:"#ffe4e1",moccasin:"#ffe4b5",navajowhite:"#ffdead",navy:"#000080",oldlace:"#fdf5e6",olive:"#808000",olivedrab:"#6b8e23",orange:"#ffa500",orangered:"#ff4500",orchid:"#da70d6",palegoldenrod:"#eee8aa",palegreen:"#98fb98",paleturquoise:"#afeeee",palevioletred:"#d87093",papayawhip:"#ffefd5",peachpuff:"#ffdab9",peru:"#cd853f",pink:"#ffc0cb",plum:"#dda0dd",powderblue:"#b0e0e6",purple:"#800080",red:"#ff0000",rosybrown:"#bc8f8f",royalblue:"#4169e1",saddlebrown:"#8b4513",salmon:"#fa8072",sandybrown:"#f4a460",seagreen:"#2e8b57",seashell:"#fff5ee",sienna:"#a0522d",silver:"#c0c0c0",skyblue:"#87ceeb",slateblue:"#6a5acd",slategray:"#708090",slategrey:"#708090",snow:"#fffafa",springgreen:"#00ff7f",steelblue:"#4682b4",tan:"#d2b48c",teal:"#008080",thistle:"#d8bfd8",tomato:"#ff6347",turquoise:"#40e0d0",violet:"#ee82ee",wheat:"#f5deb3",white:"#ffffff",whitesmoke:"#f5f5f5",yellow:"#ffff00",yellowgreen:"#9acd32"}}(n("./tree")),function(e){e.Alpha=function(e){this.value=e},e.Alpha.prototype={toCSS:function(){return"alpha(opacity="+(this.value.toCSS?this.value.toCSS():this.value)+")"},eval:function(e){return this.value.eval&&(this.value=this.value.eval(e)),this}}}(n("../tree")),function(e){e.Anonymous=function(e){this.value=e.value||e},e.Anonymous.prototype={toCSS:function(){return this.value},eval:function(){return this},compare:function(e){if(!e.toCSS)return-1;var t=this.toCSS(),n=e.toCSS();return t===n?0:t<n?-1:1}}}(n("../tree")),function(e){e.Assignment=function(e,t){this.key=e,this.value=t},e.Assignment.prototype={toCSS:function(){return this.key+"="+(this.value.toCSS?this.value.toCSS():this.value)},eval:function(t){return this.value.eval?new e.Assignment(this.key,this.value.eval(t)):this}}}(n("../tree")),function(e){e.Call=function(e,t,n,r){this.name=e,this.args=t,this.index=n,this.filename=r},e.Call.prototype={eval:function(t){var n=this.args.map(function(e){return e.eval(t)});if(!(this.name in e.functions))return new e.Anonymous(this.name+"("+n.map(function(e){return e.toCSS(t)}).join(", ")+")");try{return e.functions[this.name].apply(e.functions,n)}catch(r){throw{type:r.type||"Runtime",message:"error evaluating function `"+this.name+"`"+(r.message?": "+r.message:""),index:this.index,filename:this.filename}}},toCSS:function(e){return this.eval(e).toCSS()}}}(n("../tree")),function(e){e.Color=function(e,t){Array.isArray(e)?this.rgb=e:e.length==6?this.rgb=e.match(/.{2}/g).map(function(e){return parseInt(e,16)}):this.rgb=e.split("").map(function(e){return parseInt(e+e,16)}),this.alpha=typeof t=="number"?t:1},e.Color.prototype={eval:function(){return this},toCSS:function(){return this.alpha<1?"rgba("+this.rgb.map(function(e){return Math.round(e)}).concat(this.alpha).join(", ")+")":"#"+this.rgb.map(function(e){return e=Math.round(e),e=(e>255?255:e<0?0:e).toString(16),e.length===1?"0"+e:e}).join("")},operate:function(t,n){var r=[];n instanceof e.Color||(n=n.toColor());for(var i=0;i<3;i++)r[i]=e.operate(t,this.rgb[i],n.rgb[i]);return new e.Color(r,this.alpha+n.alpha)},toHSL:function(){var e=this.rgb[0]/255,t=this.rgb[1]/255,n=this.rgb[2]/255,r=this.alpha,i=Math.max(e,t,n),s=Math.min(e,t,
+n),o,u,a=(i+s)/2,f=i-s;if(i===s)o=u=0;else{u=a>.5?f/(2-i-s):f/(i+s);switch(i){case e:o=(t-n)/f+(t<n?6:0);break;case t:o=(n-e)/f+2;break;case n:o=(e-t)/f+4}o/=6}return{h:o*360,s:u,l:a,a:r}},toARGB:function(){var e=[Math.round(this.alpha*255)].concat(this.rgb);return"#"+e.map(function(e){return e=Math.round(e),e=(e>255?255:e<0?0:e).toString(16),e.length===1?"0"+e:e}).join("")},compare:function(e){return e.rgb?e.rgb[0]===this.rgb[0]&&e.rgb[1]===this.rgb[1]&&e.rgb[2]===this.rgb[2]&&e.alpha===this.alpha?0:-1:-1}}}(n("../tree")),function(e){e.Comment=function(e,t){this.value=e,this.silent=!!t},e.Comment.prototype={toCSS:function(e){return e.compress?"":this.value},eval:function(){return this}}}(n("../tree")),function(e){e.Condition=function(e,t,n,r,i){this.op=e.trim(),this.lvalue=t,this.rvalue=n,this.index=r,this.negate=i},e.Condition.prototype.eval=function(e){var t=this.lvalue.eval(e),n=this.rvalue.eval(e),r=this.index,i,i=function(e){switch(e){case"and":return t&&n;case"or":return t||n;default:if(t.compare)i=t.compare(n);else{if(!n.compare)throw{type:"Type",message:"Unable to perform comparison",index:r};i=n.compare(t)}switch(i){case-1:return e==="<"||e==="=<";case 0:return e==="="||e===">="||e==="=<";case 1:return e===">"||e===">="}}}(this.op);return this.negate?!i:i}}(n("../tree")),function(e){e.Dimension=function(e,t){this.value=parseFloat(e),this.unit=t||null},e.Dimension.prototype={eval:function(){return this},toColor:function(){return new e.Color([this.value,this.value,this.value])},toCSS:function(){var e=this.value+this.unit;return e},operate:function(t,n){return new e.Dimension(e.operate(t,this.value,n.value),this.unit||n.unit)},compare:function(t){return t instanceof e.Dimension?t.value>this.value?-1:t.value<this.value?1:0:-1}}}(n("../tree")),function(e){e.Directive=function(t,n){this.name=t,Array.isArray(n)?(this.ruleset=new e.Ruleset([],n),this.ruleset.allowImports=!0):this.value=n},e.Directive.prototype={toCSS:function(e,t){return this.ruleset?(this.ruleset.root=!0,this.name+(t.compress?"{":" {\n  ")+this.ruleset.toCSS(e,t).trim().replace(/\n/g,"\n  ")+(t.compress?"}":"\n}\n")):this.name+" "+this.value.toCSS()+";\n"},eval:function(t){var n=this;return this.ruleset&&(t.frames.unshift(this),n=new e.Directive(this.name),n.ruleset=this.ruleset.eval(t),t.frames.shift()),n},variable:function(t){return e.Ruleset.prototype.variable.call(this.ruleset,t)},find:function(){return e.Ruleset.prototype.find.apply(this.ruleset,arguments)},rulesets:function(){return e.Ruleset.prototype.rulesets.apply(this.ruleset)}}}(n("../tree")),function(e){e.Element=function(t,n,r){this.combinator=t instanceof e.Combinator?t:new e.Combinator(t),typeof n=="string"?this.value=n.trim():n?this.value=n:this.value="",this.index=r},e.Element.prototype.eval=function(t){return new e.Element(this.combinator,this.value.eval?this.value.eval(t):this.value,this.index)},e.Element.prototype.toCSS=function(e){var t=this.value.toCSS?this.value.toCSS(e):this.value;return t==""&&this.combinator.value.charAt(0)=="&"?"":this.combinator.toCSS(e||{})+t},e.Combinator=function(e){e===" "?this.value=" ":this.value=e?e.trim():""},e.Combinator.prototype.toCSS=function(e){return{"":""," ":" ",":":" :","+":e.compress?"+":" + ","~":e.compress?"~":" ~ ",">":e.compress?">":" > "}[this.value]}}(n("../tree")),function(e){e.Expression=function(e){this.value=e},e.Expression.prototype={eval:function(t){return this.value.length>1?new e.Expression(this.value.map(function(e){return e.eval(t)})):this.value.length===1?this.value[0].eval(t):this},toCSS:function(e){return this.value.map(function(t){return t.toCSS?t.toCSS(e):""}).join(" ")}}}(n("../tree")),function(e){e.Import=function(t,n,r,i,s){var o=this;this.once=i,this.index=s,this._path=t,this.features=r&&new e.Value(r),t instanceof e.Quoted?this.path=/\.(le?|c)ss(\?.*)?$/.test(t.value)?t.value:t.value+".less":this.path=t.value.value||t.value,this.css=/css(\?.*)?$/.test(this.path),this.css||n.push(this.path,function(t,n,r){t&&(t.index=s),r&&o.once&&(o.skip=r),o.root=n||new e.Ruleset([],[])})},e.Import.prototype={toCSS:function(e){var t=this.features?" "+this.features.toCSS(e):"";return this.css?"@import "+this._path.toCSS()+t+";\n":""},eval:function(t){var n,r=this.features&&this.features.eval(t);if(this.skip)return[];if(this.css)return this;n=new e.Ruleset([],this.root.rules.slice(0));for(var i=0;i<n.rules.length;i++)n.rules[i]instanceof e.Import&&Array.prototype.splice.apply(n.rules,[i,1].concat(n.rules[i].eval(t)));return this.features?new e.Media(n.rules,this.features.value):n.rules}}}(n("../tree")),function(e){e.JavaScript=function(e,t,n){this.escaped=n,this.expression=e,this.index=t},e.JavaScript.prototype={eval:function(t){var n,r=this,i={},s=this.expression.replace(/@\{([\w-]+)\}/g,function(n,i){return e.jsify((new e.Variable("@"+i,r.index)).eval(t))});try{s=new Function("return ("+s+")")}catch(o){throw{message:"JavaScript evaluation error: `"+s+"`",index:this.index}}for(var u in t.frames[0].variables())i[u.slice(1)]={value:t.frames[0].variables()[u].value,toJS:function(){return this.value.eval(t).toCSS()}};try{n=s.call(i)}catch(o){throw{message:"JavaScript evaluation error: '"+o.name+": "+o.message+"'",index:this.index}}return typeof n=="string"?new e.Quoted('"'+n+'"',n,this.escaped,this.index):Array.isArray(n)?new e.Anonymous(n.join(", ")):new e.Anonymous(n)}}}(n("../tree")),function(e){e.Keyword=function(e){this.value=e},e.Keyword.prototype={eval:function(){return this},toCSS:function(){return this.value},compare:function(t){return t instanceof e.Keyword?t.value===this.value?0:1:-1}},e.True=new e.Keyword("true"),e.False=new e.Keyword("false")}(n("../tree")),function(e){e.Media=function(t,n){var r=this.emptySelectors();this.features=new e.Value(n),this.ruleset=new e.Ruleset(r,t),this.ruleset.allowImports=!0},e.Media.prototype={toCSS:function(e,t){var n=this.features.toCSS(t);return this.ruleset.root=e.length===0||e[0].multiMedia,"@media "+n+(t.compress?"{":" {\n  ")+this.ruleset.toCSS(e,t).trim().replace(/\n/g,"\n  ")+(t.compress?"}":"\n}\n")},eval:function(t){t.mediaBlocks||(t.mediaBlocks=[],t.mediaPath=[]);var n=t.mediaBlocks.length;t.mediaPath.push(this),t.mediaBlocks.push(this);var r=new e.Media([],[]);return this.debugInfo&&(this.ruleset.debugInfo=this.debugInfo,r.debugInfo=this.debugInfo),r.features=this.features.eval(t),t.frames.unshift(this.ruleset),r.ruleset=this.ruleset.eval(t),t.frames.shift(),t.mediaBlocks[n]=r,t.mediaPath.pop(),t.mediaPath.length===0?r.evalTop(t):r.evalNested(t)},variable:function(t){return e.Ruleset.prototype.variable.call(this.ruleset,t)},find:function(){return e.Ruleset.prototype.find.apply(this.ruleset,arguments)},rulesets:function(){return e.Ruleset.prototype.rulesets.apply(this.ruleset)},emptySelectors:function(){var t=new e.Element("","&",0);return[new e.Selector([t])]},evalTop:function(t){var n=this;if(t.mediaBlocks.length>1){var r=this.emptySelectors();n=new e.Ruleset(r,t.mediaBlocks),n.multiMedia=!0}return delete t.mediaBlocks,delete t.mediaPath,n},evalNested:function(t){var n,r,i=t.mediaPath.concat([this]);for(n=0;n<i.length;n++)r=i[n].features instanceof e.Value?i[n].features.value:i[n].features,i[n]=Array.isArray(r)?r:[r];return this.features=new e.Value(this.permute(i).map(function(t){t=t.map(function(t){return t.toCSS?t:new e.Anonymous(t)});for(n=t.length-1;n>0;n--)t.splice(n,0,new e.Anonymous("and"));return new e.Expression(t)})),new e.Ruleset([],[])},permute:function(e){if(e.length===0)return[];if(e.length===1)return e[0];var t=[],n=this.permute(e.slice(1));for(var r=0;r<n.length;r++)for(var i=0;i<e[0].length;i++)t.push([e[0][i]].concat(n[r]));return t},bubbleSelectors:function(t){this.ruleset=new e.Ruleset(t.slice(0),[this.ruleset])}}}(n("../tree")),function(e){e.mixin={},e.mixin.Call=function(t,n,r,i,s){this.selector=new e.Selector(t),this.arguments=n,this.index=r,this.filename=i,this.important=s},e.mixin.Call.prototype={eval:function(e){var t,n,r=[],i=!1;for(var s=0;s<e.frames.length;s++)if((t=e.frames[s].find(this.selector)).length>0){n=this.arguments&&this.arguments.map(function(t){return{name:t.name,value:t.value.eval(e)}});for(var o=0;o<t.length;o++)if(t[o].match(n,e))try{Array.prototype.push.apply(r,t[o].eval(e,this.arguments,this.important).rules),i=!0}catch(u){throw{message:u.message,index:this.index,filename:this.filename,stack:u.stack}}if(i)return r;throw{type:"Runtime",message:"No matching definition was found for `"+this.selector.toCSS().trim()+"("+this.arguments.map(function(e){return e.toCSS()}).join(", ")+")`",index:this.index,filename:this.filename}}throw{type:"Name",message:this.selector.toCSS().trim()+" is undefined",index:this.index,filename:this.filename}}},e.mixin.Definition=function(t,n,r,i,s){this.name=t,this.selectors=[new e.Selector([new e.Element(null,t)])],this.params=n,this.condition=i,this.variadic=s,this.arity=n.length,this.rules=r,this._lookups={},this.required=n.reduce(function(e,t){return!t.name||t.name&&!t.value?e+1:e},0),this.parent=e.Ruleset.prototype,this.frames=[]},e.mixin.Definition.prototype={toCSS:function(){return""},variable:function(e){return this.parent.variable.call(this,e)},variables:function(){return this.parent.variables.call(this)},find:function(){return this.parent.find.apply(this,arguments)},rulesets:function(){return this.parent.rulesets.apply(this)},evalParams:function(t,n){var r=new e.Ruleset(null,[]),i,s;for(var o=0,u,a;o<this.params.length;o++){s=n&&n[o];if(s&&s.name){r.rules.unshift(new e.Rule(s.name,s.value.eval(t))),n.splice(o,1),o--;continue}if(a=this.params[o].name)if(this.params[o].variadic&&n){i=[];for(var f=o;f<n.length;f++)i.push(n[f].value.eval(t));r.rules.unshift(new e.Rule(a,(new e.Expression(i)).eval(t)))}else{if(!(u=s&&s.value||this.params[o].value))throw{type:"Runtime",message:"wrong number of arguments for "+this.name+" ("+n.length+" for "+this.arity+")"};r.rules.unshift(new e.Rule(a,u.eval(t)))}}return r},eval:function(t,n,r){var i=this.evalParams(t,n),s,o=[],u,a;for(var f=0;f<Math.max(this.params.length,n&&n.length);f++)o.push(n[f]&&n[f].value||this.params[f].value);return i.rules.unshift(new e.Rule("@arguments",(new e.Expression(o)).eval(t))),u=r?this.rules.map(function(t){return new e.Rule(t.name,t.value,"!important",t.index)}):this.rules.slice(0),(new e.Ruleset(null,u)).eval({frames:[this,i].concat(this.frames,t.frames)})},match:function(e,t){var n=e&&e.length||0,r,i;if(!this.variadic){if(n<this.required)return!1;if(n>this.params.length)return!1;if(this.required>0&&n>this.params.length)return!1}if(this.condition&&!this.condition.eval({frames:[this.evalParams(t,e)].concat(t.frames)}))return!1;r=Math.min(n,this.arity);for(var s=0;s<r;s++)if(!this.params[s].name&&e[s].value.eval(t).toCSS()!=this.params[s].value.eval(t).toCSS())return!1;return!0}}}(n("../tree")),function(e){e.Operation=function(e,t){this.op=e.trim(),this.operands=t},e.Operation.prototype.eval=function(t){var n=this.operands[0].eval(t),r=this.operands[1].eval(t),i;if(n instanceof e.Dimension&&r instanceof e.Color){if(this.op!=="*"&&this.op!=="+")throw{name:"OperationError",message:"Can't substract or divide a color from a number"};i=r,r=n,n=i}return n.operate(this.op,r)},e.operate=function(e,t,n){switch(e){case"+":return t+n;case"-":return t-n;case"*":return t*n;case"/":return t/n}}}(n("../tree")),function(e){e.Paren=function(e){this.value=e},e.Paren.prototype={toCSS:function(e){return"("+this.value.toCSS(e)+")"},eval:function(t){return new e.Paren(this.value.eval(t))}}}(n("../tree")),function(e){e.Quoted=function(e,t,n,r){this.escaped=n,this.value=t||"",this.quote=e.charAt(0),this.index=r},e.Quoted.prototype={toCSS:function(){return this.escaped?this.value:this.quote+this.value+this.quote},eval:function(t){var n=this,r=this.value.replace(/`([^`]+)`/g,function(r,i){return(new e.JavaScript(i,n.index,!0)).eval(t).value}).replace(/@\{([\w-]+)\}/g,function(r,i){var s=(new e.Variable("@"+i,n.index)).eval(t);return"value"in s?s.value:s.toCSS()});return new e.Quoted(this.quote+r+this.quote,r,this.escaped,this.index)},compare:function(e){if(!e.toCSS)return-1;var t=this.toCSS(),n=e.toCSS();return t===n?0:t<n?-1:1}}}(n("../tree")),function(e){e.Ratio=function(e){this.value=e},e.Ratio.prototype={toCSS:function(e){return this.value},eval:function(){return this}}}(n("../tree")),function(e){e.Rule=function(t,n,r,i,s){this.name=t,this.value=n instanceof e.Value?n:new e.Value([n]),this.important=r?" "+r.trim():"",this.index=i,this.inline=s||!1,t.charAt(0)==="@"?this.variable=!0:this.variable=!1},e.Rule.prototype.toCSS=function(e){return this.variable?"":this.name+(e.compress?":":": ")+this.value.toCSS(e)+this.important+(this.inline?"":";")},e.Rule.prototype.eval=function(t){return new e.Rule(this.name,this.value.eval(t),this.important,this.index,this.inline)},e.Shorthand=function(e,t){this.a=e,this.b=t},e.Shorthand.prototype={toCSS:function(e){return this.a.toCSS(e)+"/"+this.b.toCSS(e)},eval:function(){return this}}}(n("../tree")),function(e){e.Ruleset=function(e,t,n){this.selectors=e,this.rules=t,this._lookups={},this.strictImports=n},e.Ruleset.prototype={eval:function(t){var n=this.selectors&&this.selectors.map(function(e){return e.eval(t)}),r=new e.Ruleset(n,this.rules.slice(0),this.strictImports),i=[];r.root=this.root,r.allowImports=this.allowImports,this.debugInfo&&(r.debugInfo=this.debugInfo),t.frames.unshift(r);if(r.root||r.allowImports||!r.strictImports){for(var s=0;s<r.rules.length;s++)r.rules[s]instanceof e.Import?i=i.concat(r.rules[s].eval(t)):i.push(r.rules[s]);r.rules=i,i=[]}for(var s=0;s<r.rules.length;s++)r.rules[s]instanceof e.mixin.Definition&&(r.rules[s].frames=t.frames.slice(0));var o=t.mediaBlocks&&t.mediaBlocks.length||0;for(var s=0;s<r.rules.length;s++)r.rules[s]instanceof e.mixin.Call?i=i.concat(r.rules[s].eval(t)):i.push(r.rules[s]);r.rules=i;for(var s=0,u;s<r.rules.length;s++)u=r.rules[s],u instanceof e.mixin.Definition||(r.rules[s]=u.eval?u.eval(t):u);t.frames.shift();if(t.mediaBlocks)for(var s=o;s<t.mediaBlocks.length;s++)t.mediaBlocks[s].bubbleSelectors(n);return r},match:function(e){return!e||e.length===0},variables:function(){return this._variables?this._variables:this._variables=this.rules.reduce(function(t,n){return n instanceof e.Rule&&n.variable===!0&&(t[n.name]=n),t},{})},variable:function(e){return this.variables()[e]},rulesets:function(){return this._rulesets?this._rulesets:this._rulesets=this.rules.filter(function(t){return t instanceof e.Ruleset||t instanceof e.mixin.Definition})},find:function(t,n){n=n||this;var r=[],i,s,o=t.toCSS();return o in this._lookups?this._lookups[o]:(this.rulesets().forEach(function(i){if(i!==n)for(var o=0;o<i.selectors.length;o++)if(s=t.match(i.selectors[o])){t.elements.length>i.selectors[o].elements.length?Array.prototype.push.apply(r,i.find(new e.Selector(t.elements.slice(1)),n)):r.push(i);break}}),this._lookups[o]=r)},toCSS:function(t,n){var r=[],i=[],s=[],o=[],u=[],a,f,l;this.root||this.joinSelectors(u,t,this.selectors);for(var c=0;c<this.rules.length;c++)l=this.rules[c],l.rules||l instanceof e.Directive||l instanceof e.Media?o.push(l.toCSS(u,n)):l instanceof e.Comment?l.silent||(this.root?o.push(l.toCSS(n)):i.push(l.toCSS(n))):l.toCSS&&!l.variable?i.push(l.toCSS(n)):l.value&&!l.variable&&i.push(l.value.toString());o=o.join("");if(this.root)r.push(i.join(n.compress?"":"\n"));else if(i.length>0){f=e.debugInfo(n,this),a=u.map(function(e){return e.map(function(e){return e.toCSS(n)}).join("").trim()}).join(n.compress?",":",\n");for(var c=i.length-1;c>=0;c--)s.indexOf(i[c])===-1&&s.unshift(i[c]);i=s,r.push(f+a+(n.compress?"{":" {\n  ")+i.join(n.compress?"":"\n  ")+(n.compress?"}":"\n}\n"))}return r.push(o),r.join("")+(n.compress?"\n":"")},joinSelectors:function(e,t,n){for(var r=0;r<n.length;r++)this.joinSelector(e,t,n[r])},joinSelector:function(t,n,r){var i,s,o,u,a,f,l,c,h,p,d,v,m,g,y;for(i=0;i<r.elements.length;i++)f=r.elements[i],f.value==="&"&&(u=!0);if(!u){if(n.length>0)for(i=0;i<n.length;i++)t.push(n[i].concat(r));else t.push([r]);return}g=[],a=[[]];for(i=0;i<r.elements.length;i++){f=r.elements[i];if(f.value!=="&")g.push(f);else{y=[],g.length>0&&this.mergeElementsOnToSelectors(g,a);for(s=0;s<a.length;s++){l=a[s];if(n.length==0)l.length>0&&(l[0].elements=l[0].elements.slice(0),l[0].elements.push(new e.Element(f.combinator,"",0))),y.push(l);else for(o=0;o<n.length;o++)c=n[o],h=[],p=[],v=!0,l.length>0?(h=l.slice(0),m=h.pop(),d=new e.Selector(m.elements.slice(0)),v=!1):d=new e.Selector([]),c.length>1&&(p=p.concat(c.slice(1))),c.length>0&&(v=!1,d.elements.push(new e.Element(f.combinator,c[0].elements[0].value,0)),d.elements=d.elements.concat(c[0].elements.slice(1))),v||h.push(d),h=h.concat(p),y.push(h)}a=y,g=[]}}g.length>0&&this.mergeElementsOnToSelectors(g,a);for(i=0;i<a.length;i++)t.push(a[i])},mergeElementsOnToSelectors:function(t,n){var r,i;if(n.length==0){n.push([new e.Selector(t)]);return}for(r=0;r<n.length;r++)i=n[r],i.length>0?i[i.length-1]=new e.Selector(i[i.length-1].elements.concat(t)):i.push(new e.Selector(t))}}}(n("../tree")),function(e){e.Selector=function(e){this.elements=e},e.Selector.prototype.match=function(e){var t=this.elements.length,n=e.elements.length,r=Math.min(t,n);if(t<n)return!1;for(var i=0;i<r;i++)if(this.elements[i].value!==e.elements[i].value)return!1;return!0},e.Selector.prototype.eval=function(t){return new e.Selector(this.elements.map(function(e){return e.eval(t)}))},e.Selector.prototype.toCSS=function(e){return this._css?this._css:(this.elements[0].combinator.value===""?this._css=" ":this._css="",this._css+=this.elements.map(function(t){return typeof t=="string"?" "+t.trim():t.toCSS(e)}).join(""),this._css)}}(n("../tree")),function(t){t.URL=function(e,t){this.value=e,this.paths=t},t.URL.prototype={toCSS:function(){return"url("+this.value.toCSS()+")"},eval:function(n){var r=this.value.eval(n);return typeof e!="undefined"&&typeof r.value=="string"&&!/^(?:[a-z-]+:|\/)/.test(r.value)&&this.paths.length>0&&(r.value=this.paths[0]+(r.value.charAt(0)==="/"?r.value.slice(1):r.value)),new t.URL(r,this.paths)}}}(n("../tree")),function(e){e.Value=function(e){this.value=e,this.is="value"},e.Value.prototype={eval:function(t){return this.value.length===1?this.value[0].eval(t):new e.Value(this.value.map(function(e){return e.eval(t)}))},toCSS:function(e){return this.value.map(function(t){return t.toCSS(e)}).join(e.compress?",":", ")}}}(n("../tree")),function(e){e.Variable=function(e,t,n){this.name=e,this.index=t,this.file=n},e.Variable.prototype={eval:function(t){var n,r,i=this.name;i.indexOf("@@")==0&&(i="@"+(new e.Variable(i.slice(1))).eval(t).value);if(n=e.find(t.frames,function(e){if(r=e.variable(i))return r.value.eval(t)}))return n;throw{type:"Name",message:"variable "+i+" is undefined",filename:this.file,index:this.index}}}}(n("../tree")),function(e){e.debugInfo=function(t,n){var r="";if(t.dumpLineNumbers&&!t.compress)switch(t.dumpLineNumbers){case"comments":r=e.debugInfo.asComment(n);break;case"mediaquery":r=e.debugInfo.asMediaQuery(n);break;case"all":r=e.debugInfo.asComment(n)+e.debugInfo.asMediaQuery(n)}return r},e.debugInfo.asComment=function(e){return"/* line "+e.debugInfo.lineNumber+", "+e.debugInfo.fileName+" */\n"},e.debugInfo.asMediaQuery=function(e){return'@media -sass-debug-info{filename{font-family:"'+e.debugInfo.fileName+'";}line{font-family:"'+e.debugInfo.lineNumber+'";}}\n'},e.find=function(e,t){for(var n=0,r;n<e.length;n++)if(r=t.call(e,e[n]))return r;return null},e.jsify=function(e){return Array.isArray(e.value)&&e.value.length>1?"["+e.value.map(function(e){return e.toCSS(!1)}).join(", ")+"]":e.toCSS(!1)}}(n("./tree"));var s=/^(file|chrome(-extension)?|resource|qrc|app):/.test(location.protocol);r.env=r.env||(location.hostname=="127.0.0.1"||location.hostname=="0.0.0.0"||location.hostname=="localhost"||location.port.length>0||s?"development":"production"),r.async=r.async||!1,r.fileAsync=r.fileAsync||!1,r.poll=r.poll||(s?1e3:1500),r.watch=function(){return this.watchMode=!0},r.unwatch=function(){return this.watchMode=!1};if(r.env==="development"){r.optimization=0,/!watch/.test(location.hash)&&r.watch();var o=/!dumpLineNumbers:(comments|mediaquery|all)/.exec(location.hash);o&&(r.dumpLineNumbers=o[1]),r.watchTimer=setInterval(function(){r.watchMode&&p(function(e,t,n,r,i){t&&m(t.toCSS(),r,i.lastModified)})},r.poll)}else r.optimization=3;var u;try{u=typeof e.localStorage=="undefined"?null:e.localStorage}catch(a){u=null}var f=document.getElementsByTagName("link"),l=/^text\/(x-)?less$/;r.sheets=[];for(var c=0;c<f.length;c++)(f[c].rel==="stylesheet/less"||f[c].rel.match(/stylesheet/)&&f[c].type.match(l))&&r.sheets.push(f[c]);r.refresh=function(e){var t,n;t=n=new Date,p(function(e,r,i,s,o){o.local?w("loading "+s.href+" from cache."):(w("parsed "+s.href+" successfully."),m(r.toCSS(),s,o.lastModified)),w("css for "+s.href+" generated in "+(new Date-n)+"ms"),o.remaining===0&&w("css generated in "+(new Date-t)+"ms"),n=new Date},e),h()},r.refreshStyles=h,r.refresh(r.env==="development"),typeof define=="function"&&define.amd&&define("less",[],function(){return r})})(window);
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/wikked/static/js/pagedown/Markdown.Converter.js	Wed Dec 19 00:23:12 2012 -0800
@@ -0,0 +1,1332 @@
+var Markdown;
+
+if (typeof exports === "object" && typeof require === "function") // we're in a CommonJS (e.g. Node.js) module
+    Markdown = exports;
+else
+    Markdown = {};
+    
+// The following text is included for historical reasons, but should
+// be taken with a pinch of salt; it's not all true anymore.
+
+//
+// Wherever possible, Showdown is a straight, line-by-line port
+// of the Perl version of Markdown.
+//
+// This is not a normal parser design; it's basically just a
+// series of string substitutions.  It's hard to read and
+// maintain this way,  but keeping Showdown close to the original
+// design makes it easier to port new features.
+//
+// More importantly, Showdown behaves like markdown.pl in most
+// edge cases.  So web applications can do client-side preview
+// in Javascript, and then build identical HTML on the server.
+//
+// This port needs the new RegExp functionality of ECMA 262,
+// 3rd Edition (i.e. Javascript 1.5).  Most modern web browsers
+// should do fine.  Even with the new regular expression features,
+// We do a lot of work to emulate Perl's regex functionality.
+// The tricky changes in this file mostly have the "attacklab:"
+// label.  Major or self-explanatory changes don't.
+//
+// Smart diff tools like Araxis Merge will be able to match up
+// this file with markdown.pl in a useful way.  A little tweaking
+// helps: in a copy of markdown.pl, replace "#" with "//" and
+// replace "$text" with "text".  Be sure to ignore whitespace
+// and line endings.
+//
+
+
+//
+// Usage:
+//
+//   var text = "Markdown *rocks*.";
+//
+//   var converter = new Markdown.Converter();
+//   var html = converter.makeHtml(text);
+//
+//   alert(html);
+//
+// Note: move the sample code to the bottom of this
+// file before uncommenting it.
+//
+
+(function () {
+
+    function identity(x) { return x; }
+    function returnFalse(x) { return false; }
+
+    function HookCollection() { }
+
+    HookCollection.prototype = {
+
+        chain: function (hookname, func) {
+            var original = this[hookname];
+            if (!original)
+                throw new Error("unknown hook " + hookname);
+
+            if (original === identity)
+                this[hookname] = func;
+            else
+                this[hookname] = function (x) { return func(original(x)); }
+        },
+        set: function (hookname, func) {
+            if (!this[hookname])
+                throw new Error("unknown hook " + hookname);
+            this[hookname] = func;
+        },
+        addNoop: function (hookname) {
+            this[hookname] = identity;
+        },
+        addFalse: function (hookname) {
+            this[hookname] = returnFalse;
+        }
+    };
+
+    Markdown.HookCollection = HookCollection;
+
+    // g_urls and g_titles allow arbitrary user-entered strings as keys. This
+    // caused an exception (and hence stopped the rendering) when the user entered
+    // e.g. [push] or [__proto__]. Adding a prefix to the actual key prevents this
+    // (since no builtin property starts with "s_"). See
+    // http://meta.stackoverflow.com/questions/64655/strange-wmd-bug
+    // (granted, switching from Array() to Object() alone would have left only __proto__
+    // to be a problem)
+    function SaveHash() { }
+    SaveHash.prototype = {
+        set: function (key, value) {
+            this["s_" + key] = value;
+        },
+        get: function (key) {
+            return this["s_" + key];
+        }
+    };
+
+    Markdown.Converter = function () {
+        var pluginHooks = this.hooks = new HookCollection();
+        pluginHooks.addNoop("plainLinkText");  // given a URL that was encountered by itself (without markup), should return the link text that's to be given to this link
+        pluginHooks.addNoop("preConversion");  // called with the orignal text as given to makeHtml. The result of this plugin hook is the actual markdown source that will be cooked
+        pluginHooks.addNoop("postConversion"); // called with the final cooked HTML code. The result of this plugin hook is the actual output of makeHtml
+
+        //
+        // Private state of the converter instance:
+        //
+
+        // Global hashes, used by various utility routines
+        var g_urls;
+        var g_titles;
+        var g_html_blocks;
+
+        // Used to track when we're inside an ordered or unordered list
+        // (see _ProcessListItems() for details):
+        var g_list_level;
+
+        this.makeHtml = function (text) {
+
+            //
+            // Main function. The order in which other subs are called here is
+            // essential. Link and image substitutions need to happen before
+            // _EscapeSpecialCharsWithinTagAttributes(), so that any *'s or _'s in the <a>
+            // and <img> tags get encoded.
+            //
+
+            // This will only happen if makeHtml on the same converter instance is called from a plugin hook.
+            // Don't do that.
+            if (g_urls)
+                throw new Error("Recursive call to converter.makeHtml");
+        
+            // Create the private state objects.
+            g_urls = new SaveHash();
+            g_titles = new SaveHash();
+            g_html_blocks = [];
+            g_list_level = 0;
+
+            text = pluginHooks.preConversion(text);
+
+            // 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, "~");
+
+            text = pluginHooks.postConversion(text);
+
+            g_html_blocks = g_titles = g_urls = null;
+
+            return text;
+        };
+
+        function _StripLinkDefinitions(text) {
+            //
+            // Strips link definitions from text, stores the URLs and titles in
+            // hash references.
+            //
+
+            // Link defs are in the form: ^[id]: url "optional title"
+
+            /*
+            text = text.replace(/
+                ^[ ]{0,3}\[(.+)\]:  // id = $1  attacklab: g_tab_width - 1
+                [ \t]*
+                \n?                 // maybe *one* newline
+                [ \t]*
+                <?(\S+?)>?          // url = $2
+                (?=\s|$)            // lookahead for whitespace instead of the lookbehind removed below
+                [ \t]*
+                \n?                 // maybe one newline
+                [ \t]*
+                (                   // (potential) title = $3
+                    (\n*)           // any lines skipped = $4 attacklab: lookbehind removed
+                    [ \t]+
+                    ["(]
+                    (.+?)           // title = $5
+                    [")]
+                    [ \t]*
+                )?                  // title is optional
+                (?:\n+|$)
+            /gm, function(){...});
+            */
+
+            text = text.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*<?(\S+?)>?(?=\s|$)[ \t]*\n?[ \t]*((\n*)["(](.+?)[")][ \t]*)?(?:\n+)/gm,
+                function (wholeMatch, m1, m2, m3, m4, m5) {
+                    m1 = m1.toLowerCase();
+                    g_urls.set(m1, _EncodeAmpsAndAngles(m2));  // Link IDs are case-insensitive
+                    if (m4) {
+                        // Oops, found blank lines, so it's not a title.
+                        // Put back the parenthetical statement we stole.
+                        return m3;
+                    } else if (m5) {
+                        g_titles.set(m1, m5.replace(/"/g, "&quot;"));
+                    }
+
+                    // Completely remove the definition from the text
+                    return "";
+                }
+            );
+
+            return text;
+        }
+
+        function _HashHTMLBlocks(text) {
+
+            // 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 <p>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.:
+            //   <div>
+            //     <div>
+            //     tags for inner block must be indented.
+            //     </div>
+            //   </div>
+            //
+            // The outermost tags must start at the left margin for this to match, and
+            // the inner nested divs must be indented.
+            // We need to do this before the next, more liberal match, because the next
+            // match will start at the first `<div>` and stop at the first `</div>`.
+
+            // attacklab: This regex can be expensive when it fails.
+
+            /*
+            text = text.replace(/
+                (                       // save in $1
+                    ^                   // start of line  (with /m)
+                    <($block_tags_a)    // start tag = $2
+                    \b                  // word break
+                                        // attacklab: hack around khtml/pcre bug...
+                    [^\r]*?\n           // any number of lines, minimally matching
+                    </\2>               // the matching end tag
+                    [ \t]*              // trailing spaces/tabs
+                    (?=\n+)             // followed by a newline
+                )                       // attacklab: there are sentinel newlines at end of document
+            /gm,function(){...}};
+            */
+            text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del)\b[^\r]*?\n<\/\2>[ \t]*(?=\n+))/gm, hashElement);
+
+            //
+            // Now match more liberally, simply from `\n<tag>` to `</tag>\n`
+            //
+
+            /*
+            text = text.replace(/
+                (                       // save in $1
+                    ^                   // start of line  (with /m)
+                    <($block_tags_b)    // start tag = $2
+                    \b                  // word break
+                                        // attacklab: hack around khtml/pcre bug...
+                    [^\r]*?             // any number of lines, minimally matching
+                    .*</\2>             // the matching end tag
+                    [ \t]*              // trailing spaces/tabs
+                    (?=\n+)             // followed by a newline
+                )                       // attacklab: there are sentinel newlines at end of document
+            /gm,function(){...}};
+            */
+            text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math)\b[^\r]*?.*<\/\2>[ \t]*(?=\n+)\n)/gm, hashElement);
+
+            // Special case just for <hr />. It was easier to make a special case than
+            // to make the other regex more complicated.  
+
+            /*
+            text = text.replace(/
+                \n                  // Starting after a blank line
+                [ ]{0,3}
+                (                   // save in $1
+                    (<(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(/
+                \n\n                                            // Starting after a blank line
+                [ ]{0,3}                                        // attacklab: g_tab_width - 1
+                (                                               // save in $1
+                    <!
+                    (--(?:|(?:[^>-]|-[^>])(?:[^-]|-[^-])*)--)   // see http://www.w3.org/TR/html-markup/syntax.html#comments and http://meta.stackoverflow.com/q/95256
+                    >
+                    [ \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);
+
+            return text;
+        }
+
+        function hashElement(wholeMatch, m1) {
+            var blockText = m1;
+
+            // Undo double lines
+            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;
+        }
+
+        function _RunBlockGamut(text, doNotUnhash) {
+            //
+            // These are all the transformations that form block-level
+            // tags like paragraphs, headers, and list items.
+            //
+            text = _DoHeaders(text);
+
+            // Do Horizontal Rules:
+            var replacement = "<hr />\n";
+            text = text.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm, replacement);
+            text = text.replace(/^[ ]{0,2}([ ]?-[ ]?){3,}[ \t]*$/gm, replacement);
+            text = text.replace(/^[ ]{0,2}([ ]?_[ ]?){3,}[ \t]*$/gm, replacement);
+
+            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
+            // <p> tags around block-level tags.
+            text = _HashHTMLBlocks(text);
+            text = _FormParagraphs(text, doNotUnhash);
+
+            return text;
+        }
+
+        function _RunSpanGamut(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 `<http://example.com/>`
+            // Must come after _DoAnchors(), because you can use < and >
+            // delimiters in inline links like [this](<url>).
+            text = _DoAutoLinks(text);
+            
+            text = text.replace(/~P/g, "://"); // put in place to prevent autolinking; reset now
+            
+            text = _EncodeAmpsAndAngles(text);
+            text = _DoItalicsAndBold(text);
+
+            // Do hard breaks:
+            text = text.replace(/  +\n/g, " <br>\n");
+
+            return text;
+        }
+
+        function _EscapeSpecialCharsWithinTagAttributes(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.
+
+            // SE: changed the comment part of the regex
+
+            var regex = /(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|<!(--(?:|(?:[^>-]|-[^>])(?:[^-]|-[^-])*)--)>)/gi;
+
+            text = text.replace(regex, function (wholeMatch) {
+                var tag = wholeMatch.replace(/(.)<\/?code>(?=.)/g, "$1`");
+                tag = escapeCharacters(tag, wholeMatch.charAt(1) == "!" ? "\\`*_/" : "\\`*_"); // also escape slashes in comments to prevent autolinking there -- http://meta.stackoverflow.com/questions/95987
+                return tag;
+            });
+
+            return text;
+        }
+
+        function _DoAnchors(text) {
+            //
+            // Turn Markdown link shortcuts into XHTML <a> 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, writeAnchorTag);
+            */
+            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
+                        (?:
+                            \([^)]*\)       // allow one level of (correctly nested) parens (think MSDN)
+                            |
+                            [^()\s]
+                        )*?
+                    )>?                
+                    [ \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]*()<?((?:\([^)]*\)|[^()\s])*?)>?[ \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;
+        }
+
+        function writeAnchorTag(wholeMatch, m1, m2, m3, m4, m5, m6, m7) {
+            if (m7 == undefined) m7 = "";
+            var whole_match = m1;
+            var link_text = m2.replace(/:\/\//g, "~P"); // to prevent auto-linking withing the link. will be converted back after the auto-linker runs
+            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.get(link_id) != undefined) {
+                    url = g_urls.get(link_id);
+                    if (g_titles.get(link_id) != undefined) {
+                        title = g_titles.get(link_id);
+                    }
+                }
+                else {
+                    if (whole_match.search(/\(\s*\)$/m) > -1) {
+                        // Special case for explicit empty url
+                        url = "";
+                    } else {
+                        return whole_match;
+                    }
+                }
+            }
+            url = encodeProblemUrlChars(url);
+            url = escapeCharacters(url, "*_");
+            var result = "<a href=\"" + url + "\"";
+
+            if (title != "") {
+                title = attributeEncode(title);
+                title = escapeCharacters(title, "*_");
+                result += " title=\"" + title + "\"";
+            }
+
+            result += ">" + link_text + "</a>";
+
+            return result;
+        }
+
+        function _DoImages(text) {
+            //
+            // Turn Markdown image shortcuts into <img> 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:  ![alt text](url "optional title")
+            // 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;
+        }
+        
+        function attributeEncode(text) {
+            // unconditionally replace angle brackets here -- what ends up in an attribute (e.g. alt or title)
+            // never makes sense to have verbatim HTML in it (and the sanitizer would totally break it)
+            return text.replace(/>/g, "&gt;").replace(/</g, "&lt;").replace(/"/g, "&quot;");
+        }
+
+        function writeImageTag(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.get(link_id) != undefined) {
+                    url = g_urls.get(link_id);
+                    if (g_titles.get(link_id) != undefined) {
+                        title = g_titles.get(link_id);
+                    }
+                }
+                else {
+                    return whole_match;
+                }
+            }
+            
+            alt_text = escapeCharacters(attributeEncode(alt_text), "*_[]()");
+            url = escapeCharacters(url, "*_");
+            var result = "<img src=\"" + url + "\" alt=\"" + alt_text + "\"";
+
+            // attacklab: Markdown.pl adds empty title attributes to images.
+            // Replicate this bug.
+
+            //if (title != "") {
+            title = attributeEncode(title);
+            title = escapeCharacters(title, "*_");
+            result += " title=\"" + title + "\"";
+            //}
+
+            result += " />";
+
+            return result;
+        }
+
+        function _DoHeaders(text) {
+
+            // Setext-style headers:
+            //  Header 1
+            //  ========
+            //  
+            //  Header 2
+            //  --------
+            //
+            text = text.replace(/^(.+)[ \t]*\n=+[ \t]*\n+/gm,
+                function (wholeMatch, m1) { return "<h1>" + _RunSpanGamut(m1) + "</h1>\n\n"; }
+            );
+
+            text = text.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm,
+                function (matchFound, m1) { return "<h2>" + _RunSpanGamut(m1) + "</h2>\n\n"; }
+            );
+
+            // 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 "<h" + h_level + ">" + _RunSpanGamut(m2) + "</h" + h_level + ">\n\n";
+                }
+            );
+
+            return text;
+        }
+
+        function _DoLists(text) {
+            //
+            // Form HTML ordered (numbered) and unordered (bulleted) lists.
+            //
+
+            // attacklab: add sentinel to hack around khtml/safari bug:
+            // http://bugs.webkit.org/show_bug.cgi?id=11231
+            text += "~0";
+
+            // Re-usable pattern to match any entirel ul or ol list:
+
+            /*
+            var whole_list = /
+                (                                   // $1 = whole list
+                    (                               // $2
+                        [ ]{0,3}                    // attacklab: g_tab_width - 1
+                        ([*+-]|\d+[.])              // $3 = first list item marker
+                        [ \t]+
+                    )
+                    [^\r]+?
+                    (                               // $4
+                        ~0                          // sentinel for workaround; should be $
+                        |
+                        \n{2,}
+                        (?=\S)
+                        (?!                         // Negative lookahead for another list item marker
+                            [ \t]*
+                            (?:[*+-]|\d+[.])[ \t]+
+                        )
+                    )
+                )
+            /g
+            */
+            var whole_list = /^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm;
+
+            if (g_list_level) {
+                text = text.replace(whole_list, function (wholeMatch, m1, m2) {
+                    var list = m1;
+                    var list_type = (m2.search(/[*+-]/g) > -1) ? "ul" : "ol";
+
+                    var result = _ProcessListItems(list, list_type);
+
+                    // Trim any trailing whitespace, to put the closing `</$list_type>`
+                    // up on the preceding line, to get it past the current stupid
+                    // HTML block parser. This is a hack to work around the terrible
+                    // hack that is the HTML block parser.
+                    result = result.replace(/\s+$/, "");
+                    result = "<" + list_type + ">" + result + "</" + list_type + ">\n";
+                    return result;
+                });
+            } else {
+                whole_list = /(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/g;
+                text = text.replace(whole_list, function (wholeMatch, m1, m2, m3) {
+                    var runup = m1;
+                    var list = m2;
+
+                    var list_type = (m3.search(/[*+-]/g) > -1) ? "ul" : "ol";
+                    var result = _ProcessListItems(list, list_type);
+                    result = runup + "<" + list_type + ">\n" + result + "</" + list_type + ">\n";
+                    return result;
+                });
+            }
+
+            // attacklab: strip sentinel
+            text = text.replace(/~0/, "");
+
+            return text;
+        }
+
+        var _listItemMarkers = { ol: "\\d+[.]", ul: "[*+-]" };
+
+        function _ProcessListItems(list_str, list_type) {
+            //
+            //  Process the contents of a single ordered or unordered list, splitting it
+            //  into individual list items.
+            //
+            //  list_type is either "ul" or "ol".
+
+            // 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.".
+
+            g_list_level++;
+
+            // trim trailing blank lines:
+            list_str = list_str.replace(/\n{2,}$/, "\n");
+
+            // attacklab: add sentinel to emulate \z
+            list_str += "~0";
+
+            // In the original attacklab showdown, list_type was not given to this function, and anything
+            // that matched /[*+-]|\d+[.]/ would just create the next <li>, causing this mismatch:
+            //
+            //  Markdown          rendered by WMD        rendered by MarkdownSharp
+            //  ------------------------------------------------------------------
+            //  1. first          1. first               1. first
+            //  2. second         2. second              2. second
+            //  - third           3. third                   * third
+            //
+            // We changed this to behave identical to MarkdownSharp. This is the constructed RegEx,
+            // with {MARKER} being one of \d+[.] or [*+-], depending on list_type:
+        
+            /*
+            list_str = list_str.replace(/
+                (^[ \t]*)                       // leading whitespace = $1
+                ({MARKER}) [ \t]+               // list marker = $2
+                ([^\r]+?                        // list item text   = $3
+                    (\n+)
+                )
+                (?=
+                    (~0 | \2 ({MARKER}) [ \t]+)
+                )
+            /gm, function(){...});
+            */
+
+            var marker = _listItemMarkers[list_type];
+            var re = new RegExp("(^[ \\t]*)(" + marker + ")[ \\t]+([^\\r]+?(\\n+))(?=(~0|\\1(" + marker + ")[ \\t]+))", "gm");
+            var last_item_had_a_double_newline = false;
+            list_str = list_str.replace(re,
+                function (wholeMatch, m1, m2, m3) {
+                    var item = m3;
+                    var leading_space = m1;
+                    var ends_with_double_newline = /\n\n$/.test(item);
+                    var contains_double_newline = ends_with_double_newline || item.search(/\n{2,}/) > -1;
+
+                    if (contains_double_newline || last_item_had_a_double_newline) {
+                        item = _RunBlockGamut(_Outdent(item), /* doNotUnhash = */true);
+                    }
+                    else {
+                        // Recursion for sub-lists:
+                        item = _DoLists(_Outdent(item));
+                        item = item.replace(/\n$/, ""); // chomp(item)
+                        item = _RunSpanGamut(item);
+                    }
+                    last_item_had_a_double_newline = ends_with_double_newline;
+                    return "<li>" + item + "</li>\n";
+                }
+            );
+
+            // attacklab: strip sentinel
+            list_str = list_str.replace(/~0/g, "");
+
+            g_list_level--;
+            return list_str;
+        }
+
+        function _DoCodeBlocks(text) {
+            //
+            //  Process Markdown `<pre><code>` blocks.
+            //  
+
+            /*
+            text = text.replace(/
+                (?:\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 = "<pre><code>" + codeblock + "\n</code></pre>";
+
+                    return "\n\n" + codeblock + "\n\n" + nextChar;
+                }
+            );
+
+            // attacklab: strip sentinel
+            text = text.replace(/~0/, "");
+
+            return text;
+        }
+
+        function hashBlock(text) {
+            text = text.replace(/(^\n+|\n+$)/g, "");
+            return "\n\n~K" + (g_html_blocks.push(text) - 1) + "K\n\n";
+        }
+
+        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(/
+                (^|[^\\])       // 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);
+                    c = c.replace(/:\/\//g, "~P"); // to prevent auto-linking. Not necessary in code *blocks*, but in code spans. Will be converted back after the auto-linker runs.
+                    return m1 + "<code>" + c + "</code>";
+                }
+            );
+
+            return text;
+        }
+
+        function _EncodeCode(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, "&amp;");
+
+            // Do the angle bracket song and dance:
+            text = text.replace(/</g, "&lt;");
+            text = text.replace(/>/g, "&gt;");
+
+            // 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;
+        }
+
+        function _DoItalicsAndBold(text) {
+
+            // <strong> must go first:
+            text = text.replace(/([\W_]|^)(\*\*|__)(?=\S)([^\r]*?\S[\*_]*)\2([\W_]|$)/g,
+            "$1<strong>$3</strong>$4");
+
+            text = text.replace(/([\W_]|^)(\*|_)(?=\S)([^\r\*_]*?\S)\2([\W_]|$)/g,
+            "$1<em>$3</em>$4");
+
+            return text;
+        }
+
+        function _DoBlockQuotes(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 <pre> content, so we need to fix that:
+                    bq = bq.replace(
+                            /(\s*<pre>[^\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("<blockquote>\n" + bq + "\n</blockquote>");
+                }
+            );
+            return text;
+        }
+
+        function _FormParagraphs(text, doNotUnhash) {
+            //
+            //  Params:
+            //    $text - string to process with html <p> 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 = [];
+            
+            var markerRe = /~K(\d+)K/;
+
+            //
+            // Wrap <p> tags.
+            //
+            var end = grafs.length;
+            for (var i = 0; i < end; i++) {
+                var str = grafs[i];
+
+                // if this is an HTML marker, copy it
+                if (markerRe.test(str)) {
+                    grafsOut.push(str);
+                }
+                else if (/\S/.test(str)) {
+                    str = _RunSpanGamut(str);
+                    str = str.replace(/^([ \t]*)/g, "<p>");
+                    str += "</p>"
+                    grafsOut.push(str);
+                }
+
+            }
+            //
+            // Unhashify HTML blocks
+            //
+            if (!doNotUnhash) {
+                end = grafsOut.length;
+                for (var i = 0; i < end; i++) {
+                    var foundAny = true;
+                    while (foundAny) { // we may need several runs, since the data may be nested
+                        foundAny = false;
+                        grafsOut[i] = grafsOut[i].replace(/~K(\d+)K/g, function (wholeMatch, id) {
+                            foundAny = true;
+                            return g_html_blocks[id];
+                        });
+                    }
+                }
+            }
+            return grafsOut.join("\n\n");
+        }
+
+        function _EncodeAmpsAndAngles(text) {
+            // Smart processing for ampersands and angle brackets that need to be encoded.
+
+            // Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin:
+            //   http://bumppo.net/projects/amputator/
+            text = text.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g, "&amp;");
+
+            // Encode naked <'s
+            text = text.replace(/<(?![a-z\/?\$!])/gi, "&lt;");
+
+            return text;
+        }
+
+        function _EncodeBackslashEscapes(text) {
+            //
+            //   Parameter:  String.
+            //   Returns:    The string, with after processing the following backslash
+            //               escape sequences.
+            //
+
+            // attacklab: The polite way to do this is with the new
+            // escapeCharacters() function:
+            //
+            //     text = escapeCharacters(text,"\\",true);
+            //     text = escapeCharacters(text,"`*_{}[]()>#+-.!",true);
+            //
+            // ...but we're sidestepping its use of the (slow) RegExp constructor
+            // as an optimization for Firefox.  This function gets called a LOT.
+
+            text = text.replace(/\\(\\)/g, escapeCharacters_callback);
+            text = text.replace(/\\([`*_{}\[\]()>#+-.!])/g, escapeCharacters_callback);
+            return text;
+        }
+
+        function _DoAutoLinks(text) {
+
+            // note that at this point, all other URL in the text are already hyperlinked as <a href=""></a>
+            // *except* for the <http://www.foo.com> case
+
+            // automatically add < and > around unadorned raw hyperlinks
+            // must be preceded by space/BOF and followed by non-word/EOF character    
+            text = text.replace(/(^|\s)(https?|ftp)(:\/\/[-A-Z0-9+&@#\/%?=~_|\[\]\(\)!:,\.;]*[-A-Z0-9+&@#\/%=~_|\[\]])($|\W)/gi, "$1<$2$3>$4");
+
+            //  autolink anything like <http://example.com>
+            
+            var replacer = function (wholematch, m1) { return "<a href=\"" + m1 + "\">" + pluginHooks.plainLinkText(m1) + "</a>"; }
+            text = text.replace(/<((https?|ftp):[^'">\s]+)>/gi, replacer);
+
+            // Email addresses: <address@domain.foo>
+            /*
+            text = text.replace(/
+                <
+                (?:mailto:)?
+                (
+                    [-.\w]+
+                    \@
+                    [-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+
+                )
+                >
+            /gi, _DoAutoLinks_callback());
+            */
+
+            /* disabling email autolinking, since we don't do that on the server, either
+            text = text.replace(/<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi,
+                function(wholeMatch,m1) {
+                    return _EncodeEmailAddress( _UnescapeSpecialChars(m1) );
+                }
+            );
+            */
+            return text;
+        }
+
+        function _UnescapeSpecialChars(text) {
+            //
+            // Swap back in all the special characters we've hidden.
+            //
+            text = text.replace(/~E(\d+)E/g,
+                function (wholeMatch, m1) {
+                    var charCodeToReplace = parseInt(m1);
+                    return String.fromCharCode(charCodeToReplace);
+                }
+            );
+            return text;
+        }
+
+        function _Outdent(text) {
+            //
+            // Remove one level of line-leading tabs or spaces
+            //
+
+            // attacklab: hack around Konqueror 3.5.4 bug:
+            // "----------bug".replace(/^-/g,"") == "bug"
+
+            text = text.replace(/^(\t|[ ]{1,4})/gm, "~0"); // attacklab: g_tab_width
+
+            // attacklab: clean up hack
+            text = text.replace(/~0/g, "")
+
+            return text;
+        }
+
+        function _Detab(text) {
+            if (!/\t/.test(text))
+                return text;
+
+            var spaces = ["    ", "   ", "  ", " "],
+            skew = 0,
+            v;
+
+            return text.replace(/[\n\t]/g, function (match, offset) {
+                if (match === "\n") {
+                    skew = offset + 1;
+                    return match;
+                }
+                v = (offset - skew) % 4;
+                skew = offset + 1;
+                return spaces[v];
+            });
+        }
+
+        //
+        //  attacklab: Utility functions
+        //
+
+        var _problemUrlChars = /(?:["'*()[\]:]|~D)/g;
+
+        // hex-encodes some unusual "problem" chars in URLs to avoid URL detection problems 
+        function encodeProblemUrlChars(url) {
+            if (!url)
+                return "";
+
+            var len = url.length;
+
+            return url.replace(_problemUrlChars, function (match, offset) {
+                if (match == "~D") // escape for dollar
+                    return "%24";
+                if (match == ":") {
+                    if (offset == len - 1 || /[0-9\/]/.test(url.charAt(offset + 1)))
+                        return ":"
+                }
+                return "%" + match.charCodeAt(0).toString(16);
+            });
+        }
+
+
+        function escapeCharacters(text, charsToEscape, afterBackslash) {
+            // First we have to escape the escape characters so that
+            // we can build a character class out of them
+            var regexString = "([" + charsToEscape.replace(/([\[\]\\])/g, "\\$1") + "])";
+
+            if (afterBackslash) {
+                regexString = "\\\\" + regexString;
+            }
+
+            var regex = new RegExp(regexString, "g");
+            text = text.replace(regex, escapeCharacters_callback);
+
+            return text;
+        }
+
+
+        function escapeCharacters_callback(wholeMatch, m1) {
+            var charCodeToEscape = m1.charCodeAt(0);
+            return "~E" + charCodeToEscape + "E";
+        }
+
+    }; // end of the Markdown.Converter constructor
+
+})();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/wikked/static/js/pagedown/Markdown.Editor.js	Wed Dec 19 00:23:12 2012 -0800
@@ -0,0 +1,2211 @@
+// needs Markdown.Converter.js at the moment
+
+(function () {
+
+    var util = {},
+        position = {},
+        ui = {},
+        doc = window.document,
+        re = window.RegExp,
+        nav = window.navigator,
+        SETTINGS = { lineLength: 72 },
+
+    // Used to work around some browser bugs where we can't use feature testing.
+        uaSniffed = {
+            isIE: /msie/.test(nav.userAgent.toLowerCase()),
+            isIE_5or6: /msie 6/.test(nav.userAgent.toLowerCase()) || /msie 5/.test(nav.userAgent.toLowerCase()),
+            isOpera: /opera/.test(nav.userAgent.toLowerCase())
+        };
+
+    var defaultsStrings = {
+        bold: "Strong <strong> Ctrl+B",
+        boldexample: "strong text",
+
+        italic: "Emphasis <em> Ctrl+I",
+        italicexample: "emphasized text",
+
+        link: "Hyperlink <a> Ctrl+L",
+        linkdescription: "enter link description here",
+        linkdialog: "<p><b>Insert Hyperlink</b></p><p>http://example.com/ \"optional title\"</p>",
+
+        quote: "Blockquote <blockquote> Ctrl+Q",
+        quoteexample: "Blockquote",
+
+        code: "Code Sample <pre><code> Ctrl+K",
+        codeexample: "enter code here",
+
+        image: "Image <img> Ctrl+G",
+        imagedescription: "enter image description here",
+        imagedialog: "<p><b>Insert Image</b></p><p>http://example.com/images/diagram.jpg \"optional title\"<br><br>Need <a href='http://www.google.com/search?q=free+image+hosting' target='_blank'>free image hosting?</a></p>",
+
+        olist: "Numbered List <ol> Ctrl+O",
+        ulist: "Bulleted List <ul> Ctrl+U",
+        litem: "List item",
+
+        heading: "Heading <h1>/<h2> Ctrl+H",
+        headingexample: "Heading",
+
+        hr: "Horizontal Rule <hr> Ctrl+R",
+
+        undo: "Undo - Ctrl+Z",
+        redo: "Redo - Ctrl+Y",
+        redomac: "Redo - Ctrl+Shift+Z",
+
+        help: "Markdown Editing Help"
+    };
+
+
+    // -------------------------------------------------------------------
+    //  YOUR CHANGES GO HERE
+    //
+    // I've tried to localize the things you are likely to change to
+    // this area.
+    // -------------------------------------------------------------------
+
+    // The default text that appears in the dialog input box when entering
+    // links.
+    var imageDefaultText = "http://";
+    var linkDefaultText = "http://";
+
+    // -------------------------------------------------------------------
+    //  END OF YOUR CHANGES
+    // -------------------------------------------------------------------
+
+    // options, if given, can have the following properties:
+    //   options.helpButton = { handler: yourEventHandler }
+    //   options.strings = { italicexample: "slanted text" }
+    // `yourEventHandler` is the click handler for the help button.
+    // If `options.helpButton` isn't given, not help button is created.
+    // `options.strings` can have any or all of the same properties as
+    // `defaultStrings` above, so you can just override some string displayed
+    // to the user on a case-by-case basis, or translate all strings to
+    // a different language.
+    //
+    // For backwards compatibility reasons, the `options` argument can also
+    // be just the `helpButton` object, and `strings.help` can also be set via
+    // `helpButton.title`. This should be considered legacy.
+    //
+    // The constructed editor object has the methods:
+    // - getConverter() returns the markdown converter object that was passed to the constructor
+    // - run() actually starts the editor; should be called after all necessary plugins are registered. Calling this more than once is a no-op.
+    // - refreshPreview() forces the preview to be updated. This method is only available after run() was called.
+    Markdown.Editor = function (markdownConverter, idPostfix, options) {
+        
+        options = options || {};
+
+        if (typeof options.handler === "function") { //backwards compatible behavior
+            options = { helpButton: options };
+        }
+        options.strings = options.strings || {};
+        if (options.helpButton) {
+            options.strings.help = options.strings.help || options.helpButton.title;
+        }
+        var getString = function (identifier) { return options.strings[identifier] || defaultsStrings[identifier]; }
+
+        idPostfix = idPostfix || "";
+
+        var hooks = this.hooks = new Markdown.HookCollection();
+        hooks.addNoop("onPreviewRefresh");       // called with no arguments after the preview has been refreshed
+        hooks.addNoop("postBlockquoteCreation"); // called with the user's selection *after* the blockquote was created; should return the actual to-be-inserted text
+        hooks.addFalse("insertImageDialog");     /* called with one parameter: a callback to be called with the URL of the image. If the application creates
+                                                  * its own image insertion dialog, this hook should return true, and the callback should be called with the chosen
+                                                  * image url (or null if the user cancelled). If this hook returns false, the default dialog will be used.
+                                                  */
+
+        this.getConverter = function () { return markdownConverter; }
+
+        var that = this,
+            panels;
+
+        this.run = function () {
+            if (panels)
+                return; // already initialized
+
+            panels = new PanelCollection(idPostfix);
+            var commandManager = new CommandManager(hooks, getString);
+            var previewManager = new PreviewManager(markdownConverter, panels, function () { hooks.onPreviewRefresh(); });
+            var undoManager, uiManager;
+
+            if (!/\?noundo/.test(doc.location.href)) {
+                undoManager = new UndoManager(function () {
+                    previewManager.refresh();
+                    if (uiManager) // not available on the first call
+                        uiManager.setUndoRedoButtonStates();
+                }, panels);
+                this.textOperation = function (f) {
+                    undoManager.setCommandMode();
+                    f();
+                    that.refreshPreview();
+                }
+            }
+
+            uiManager = new UIManager(idPostfix, panels, undoManager, previewManager, commandManager, options.helpButton, getString);
+            uiManager.setUndoRedoButtonStates();
+
+            var forceRefresh = that.refreshPreview = function () { previewManager.refresh(true); };
+
+            forceRefresh();
+        };
+
+    }
+
+    // before: contains all the text in the input box BEFORE the selection.
+    // after: contains all the text in the input box AFTER the selection.
+    function Chunks() { }
+
+    // startRegex: a regular expression to find the start tag
+    // endRegex: a regular expresssion to find the end tag
+    Chunks.prototype.findTags = function (startRegex, endRegex) {
+
+        var chunkObj = this;
+        var regex;
+
+        if (startRegex) {
+
+            regex = util.extendRegExp(startRegex, "", "$");
+
+            this.before = this.before.replace(regex,
+                function (match) {
+                    chunkObj.startTag = chunkObj.startTag + match;
+                    return "";
+                });
+
+            regex = util.extendRegExp(startRegex, "^", "");
+
+            this.selection = this.selection.replace(regex,
+                function (match) {
+                    chunkObj.startTag = chunkObj.startTag + match;
+                    return "";
+                });
+        }
+
+        if (endRegex) {
+
+            regex = util.extendRegExp(endRegex, "", "$");
+
+            this.selection = this.selection.replace(regex,
+                function (match) {
+                    chunkObj.endTag = match + chunkObj.endTag;
+                    return "";
+                });
+
+            regex = util.extendRegExp(endRegex, "^", "");
+
+            this.after = this.after.replace(regex,
+                function (match) {
+                    chunkObj.endTag = match + chunkObj.endTag;
+                    return "";
+                });
+        }
+    };
+
+    // If remove is false, the whitespace is transferred
+    // to the before/after regions.
+    //
+    // If remove is true, the whitespace disappears.
+    Chunks.prototype.trimWhitespace = function (remove) {
+        var beforeReplacer, afterReplacer, that = this;
+        if (remove) {
+            beforeReplacer = afterReplacer = "";
+        } else {
+            beforeReplacer = function (s) { that.before += s; return ""; }
+            afterReplacer = function (s) { that.after = s + that.after; return ""; }
+        }
+
+        this.selection = this.selection.replace(/^(\s*)/, beforeReplacer).replace(/(\s*)$/, afterReplacer);
+    };
+
+
+    Chunks.prototype.skipLines = function (nLinesBefore, nLinesAfter, findExtraNewlines) {
+
+        if (nLinesBefore === undefined) {
+            nLinesBefore = 1;
+        }
+
+        if (nLinesAfter === undefined) {
+            nLinesAfter = 1;
+        }
+
+        nLinesBefore++;
+        nLinesAfter++;
+
+        var regexText;
+        var replacementText;
+
+        // chrome bug ... documented at: http://meta.stackoverflow.com/questions/63307/blockquote-glitch-in-editor-in-chrome-6-and-7/65985#65985
+        if (navigator.userAgent.match(/Chrome/)) {
+            "X".match(/()./);
+        }
+
+        this.selection = this.selection.replace(/(^\n*)/, "");
+
+        this.startTag = this.startTag + re.$1;
+
+        this.selection = this.selection.replace(/(\n*$)/, "");
+        this.endTag = this.endTag + re.$1;
+        this.startTag = this.startTag.replace(/(^\n*)/, "");
+        this.before = this.before + re.$1;
+        this.endTag = this.endTag.replace(/(\n*$)/, "");
+        this.after = this.after + re.$1;
+
+        if (this.before) {
+
+            regexText = replacementText = "";
+
+            while (nLinesBefore--) {
+                regexText += "\\n?";
+                replacementText += "\n";
+            }
+
+            if (findExtraNewlines) {
+                regexText = "\\n*";
+            }
+            this.before = this.before.replace(new re(regexText + "$", ""), replacementText);
+        }
+
+        if (this.after) {
+
+            regexText = replacementText = "";
+
+            while (nLinesAfter--) {
+                regexText += "\\n?";
+                replacementText += "\n";
+            }
+            if (findExtraNewlines) {
+                regexText = "\\n*";
+            }
+
+            this.after = this.after.replace(new re(regexText, ""), replacementText);
+        }
+    };
+
+    // end of Chunks
+
+    // A collection of the important regions on the page.
+    // Cached so we don't have to keep traversing the DOM.
+    // Also holds ieCachedRange and ieCachedScrollTop, where necessary; working around
+    // this issue:
+    // Internet explorer has problems with CSS sprite buttons that use HTML
+    // lists.  When you click on the background image "button", IE will
+    // select the non-existent link text and discard the selection in the
+    // textarea.  The solution to this is to cache the textarea selection
+    // on the button's mousedown event and set a flag.  In the part of the
+    // code where we need to grab the selection, we check for the flag
+    // and, if it's set, use the cached area instead of querying the
+    // textarea.
+    //
+    // This ONLY affects Internet Explorer (tested on versions 6, 7
+    // and 8) and ONLY on button clicks.  Keyboard shortcuts work
+    // normally since the focus never leaves the textarea.
+    function PanelCollection(postfix) {
+        this.buttonBar = doc.getElementById("wmd-button-bar" + postfix);
+        this.preview = doc.getElementById("wmd-preview" + postfix);
+        this.input = doc.getElementById("wmd-input" + postfix);
+    };
+
+    // Returns true if the DOM element is visible, false if it's hidden.
+    // Checks if display is anything other than none.
+    util.isVisible = function (elem) {
+
+        if (window.getComputedStyle) {
+            // Most browsers
+            return window.getComputedStyle(elem, null).getPropertyValue("display") !== "none";
+        }
+        else if (elem.currentStyle) {
+            // IE
+            return elem.currentStyle["display"] !== "none";
+        }
+    };
+
+
+    // Adds a listener callback to a DOM element which is fired on a specified
+    // event.
+    util.addEvent = function (elem, event, listener) {
+        if (elem.attachEvent) {
+            // IE only.  The "on" is mandatory.
+            elem.attachEvent("on" + event, listener);
+        }
+        else {
+            // Other browsers.
+            elem.addEventListener(event, listener, false);
+        }
+    };
+
+
+    // Removes a listener callback from a DOM element which is fired on a specified
+    // event.
+    util.removeEvent = function (elem, event, listener) {
+        if (elem.detachEvent) {
+            // IE only.  The "on" is mandatory.
+            elem.detachEvent("on" + event, listener);
+        }
+        else {
+            // Other browsers.
+            elem.removeEventListener(event, listener, false);
+        }
+    };
+
+    // Converts \r\n and \r to \n.
+    util.fixEolChars = function (text) {
+        text = text.replace(/\r\n/g, "\n");
+        text = text.replace(/\r/g, "\n");
+        return text;
+    };
+
+    // Extends a regular expression.  Returns a new RegExp
+    // using pre + regex + post as the expression.
+    // Used in a few functions where we have a base
+    // expression and we want to pre- or append some
+    // conditions to it (e.g. adding "$" to the end).
+    // The flags are unchanged.
+    //
+    // regex is a RegExp, pre and post are strings.
+    util.extendRegExp = function (regex, pre, post) {
+
+        if (pre === null || pre === undefined) {
+            pre = "";
+        }
+        if (post === null || post === undefined) {
+            post = "";
+        }
+
+        var pattern = regex.toString();
+        var flags;
+
+        // Replace the flags with empty space and store them.
+        pattern = pattern.replace(/\/([gim]*)$/, function (wholeMatch, flagsPart) {
+            flags = flagsPart;
+            return "";
+        });
+
+        // Remove the slash delimiters on the regular expression.
+        pattern = pattern.replace(/(^\/|\/$)/g, "");
+        pattern = pre + pattern + post;
+
+        return new re(pattern, flags);
+    }
+
+    // UNFINISHED
+    // The assignment in the while loop makes jslint cranky.
+    // I'll change it to a better loop later.
+    position.getTop = function (elem, isInner) {
+        var result = elem.offsetTop;
+        if (!isInner) {
+            while (elem = elem.offsetParent) {
+                result += elem.offsetTop;
+            }
+        }
+        return result;
+    };
+
+    position.getHeight = function (elem) {
+        return elem.offsetHeight || elem.scrollHeight;
+    };
+
+    position.getWidth = function (elem) {
+        return elem.offsetWidth || elem.scrollWidth;
+    };
+
+    position.getPageSize = function () {
+
+        var scrollWidth, scrollHeight;
+        var innerWidth, innerHeight;
+
+        // It's not very clear which blocks work with which browsers.
+        if (self.innerHeight && self.scrollMaxY) {
+            scrollWidth = doc.body.scrollWidth;
+            scrollHeight = self.innerHeight + self.scrollMaxY;
+        }
+        else if (doc.body.scrollHeight > doc.body.offsetHeight) {
+            scrollWidth = doc.body.scrollWidth;
+            scrollHeight = doc.body.scrollHeight;
+        }
+        else {
+            scrollWidth = doc.body.offsetWidth;
+            scrollHeight = doc.body.offsetHeight;
+        }
+
+        if (self.innerHeight) {
+            // Non-IE browser
+            innerWidth = self.innerWidth;
+            innerHeight = self.innerHeight;
+        }
+        else if (doc.documentElement && doc.documentElement.clientHeight) {
+            // Some versions of IE (IE 6 w/ a DOCTYPE declaration)
+            innerWidth = doc.documentElement.clientWidth;
+            innerHeight = doc.documentElement.clientHeight;
+        }
+        else if (doc.body) {
+            // Other versions of IE
+            innerWidth = doc.body.clientWidth;
+            innerHeight = doc.body.clientHeight;
+        }
+
+        var maxWidth = Math.max(scrollWidth, innerWidth);
+        var maxHeight = Math.max(scrollHeight, innerHeight);
+        return [maxWidth, maxHeight, innerWidth, innerHeight];
+    };
+
+    // Handles pushing and popping TextareaStates for undo/redo commands.
+    // I should rename the stack variables to list.
+    function UndoManager(callback, panels) {
+
+        var undoObj = this;
+        var undoStack = []; // A stack of undo states
+        var stackPtr = 0; // The index of the current state
+        var mode = "none";
+        var lastState; // The last state
+        var timer; // The setTimeout handle for cancelling the timer
+        var inputStateObj;
+
+        // Set the mode for later logic steps.
+        var setMode = function (newMode, noSave) {
+            if (mode != newMode) {
+                mode = newMode;
+                if (!noSave) {
+                    saveState();
+                }
+            }
+
+            if (!uaSniffed.isIE || mode != "moving") {
+                timer = setTimeout(refreshState, 1);
+            }
+            else {
+                inputStateObj = null;
+            }
+        };
+
+        var refreshState = function (isInitialState) {
+            inputStateObj = new TextareaState(panels, isInitialState);
+            timer = undefined;
+        };
+
+        this.setCommandMode = function () {
+            mode = "command";
+            saveState();
+            timer = setTimeout(refreshState, 0);
+        };
+
+        this.canUndo = function () {
+            return stackPtr > 1;
+        };
+
+        this.canRedo = function () {
+            if (undoStack[stackPtr + 1]) {
+                return true;
+            }
+            return false;
+        };
+
+        // Removes the last state and restores it.
+        this.undo = function () {
+
+            if (undoObj.canUndo()) {
+                if (lastState) {
+                    // What about setting state -1 to null or checking for undefined?
+                    lastState.restore();
+                    lastState = null;
+                }
+                else {
+                    undoStack[stackPtr] = new TextareaState(panels);
+                    undoStack[--stackPtr].restore();
+
+                    if (callback) {
+                        callback();
+                    }
+                }
+            }
+
+            mode = "none";
+            panels.input.focus();
+            refreshState();
+        };
+
+        // Redo an action.
+        this.redo = function () {
+
+            if (undoObj.canRedo()) {
+
+                undoStack[++stackPtr].restore();
+
+                if (callback) {
+                    callback();
+                }
+            }
+
+            mode = "none";
+            panels.input.focus();
+            refreshState();
+        };
+
+        // Push the input area state to the stack.
+        var saveState = function () {
+            var currState = inputStateObj || new TextareaState(panels);
+
+            if (!currState) {
+                return false;
+            }
+            if (mode == "moving") {
+                if (!lastState) {
+                    lastState = currState;
+                }
+                return;
+            }
+            if (lastState) {
+                if (undoStack[stackPtr - 1].text != lastState.text) {
+                    undoStack[stackPtr++] = lastState;
+                }
+                lastState = null;
+            }
+            undoStack[stackPtr++] = currState;
+            undoStack[stackPtr + 1] = null;
+            if (callback) {
+                callback();
+            }
+        };
+
+        var handleCtrlYZ = function (event) {
+
+            var handled = false;
+
+            if (event.ctrlKey || event.metaKey) {
+
+                // IE and Opera do not support charCode.
+                var keyCode = event.charCode || event.keyCode;
+                var keyCodeChar = String.fromCharCode(keyCode);
+
+                switch (keyCodeChar.toLowerCase()) {
+
+                    case "y":
+                        undoObj.redo();
+                        handled = true;
+                        break;
+
+                    case "z":
+                        if (!event.shiftKey) {
+                            undoObj.undo();
+                        }
+                        else {
+                            undoObj.redo();
+                        }
+                        handled = true;
+                        break;
+                }
+            }
+
+            if (handled) {
+                if (event.preventDefault) {
+                    event.preventDefault();
+                }
+                if (window.event) {
+                    window.event.returnValue = false;
+                }
+                return;
+            }
+        };
+
+        // Set the mode depending on what is going on in the input area.
+        var handleModeChange = function (event) {
+
+            if (!event.ctrlKey && !event.metaKey) {
+
+                var keyCode = event.keyCode;
+
+                if ((keyCode >= 33 && keyCode <= 40) || (keyCode >= 63232 && keyCode <= 63235)) {
+                    // 33 - 40: page up/dn and arrow keys
+                    // 63232 - 63235: page up/dn and arrow keys on safari
+                    setMode("moving");
+                }
+                else if (keyCode == 8 || keyCode == 46 || keyCode == 127) {
+                    // 8: backspace
+                    // 46: delete
+                    // 127: delete
+                    setMode("deleting");
+                }
+                else if (keyCode == 13) {
+                    // 13: Enter
+                    setMode("newlines");
+                }
+                else if (keyCode == 27) {
+                    // 27: escape
+                    setMode("escape");
+                }
+                else if ((keyCode < 16 || keyCode > 20) && keyCode != 91) {
+                    // 16-20 are shift, etc.
+                    // 91: left window key
+                    // I think this might be a little messed up since there are
+                    // a lot of nonprinting keys above 20.
+                    setMode("typing");
+                }
+            }
+        };
+
+        var setEventHandlers = function () {
+            util.addEvent(panels.input, "keypress", function (event) {
+                // keyCode 89: y
+                // keyCode 90: z
+                if ((event.ctrlKey || event.metaKey) && (event.keyCode == 89 || event.keyCode == 90)) {
+                    event.preventDefault();
+                }
+            });
+
+            var handlePaste = function () {
+                if (uaSniffed.isIE || (inputStateObj && inputStateObj.text != panels.input.value)) {
+                    if (timer == undefined) {
+                        mode = "paste";
+                        saveState();
+                        refreshState();
+                    }
+                }
+            };
+
+            util.addEvent(panels.input, "keydown", handleCtrlYZ);
+            util.addEvent(panels.input, "keydown", handleModeChange);
+            util.addEvent(panels.input, "mousedown", function () {
+                setMode("moving");
+            });
+
+            panels.input.onpaste = handlePaste;
+            panels.input.ondrop = handlePaste;
+        };
+
+        var init = function () {
+            setEventHandlers();
+            refreshState(true);
+            saveState();
+        };
+
+        init();
+    }
+
+    // end of UndoManager
+
+    // The input textarea state/contents.
+    // This is used to implement undo/redo by the undo manager.
+    function TextareaState(panels, isInitialState) {
+
+        // Aliases
+        var stateObj = this;
+        var inputArea = panels.input;
+        this.init = function () {
+            if (!util.isVisible(inputArea)) {
+                return;
+            }
+            if (!isInitialState && doc.activeElement && doc.activeElement !== inputArea) { // this happens when tabbing out of the input box
+                return;
+            }
+
+            this.setInputAreaSelectionStartEnd();
+            this.scrollTop = inputArea.scrollTop;
+            if (!this.text && inputArea.selectionStart || inputArea.selectionStart === 0) {
+                this.text = inputArea.value;
+            }
+
+        }
+
+        // Sets the selected text in the input box after we've performed an
+        // operation.
+        this.setInputAreaSelection = function () {
+
+            if (!util.isVisible(inputArea)) {
+                return;
+            }
+
+            if (inputArea.selectionStart !== undefined && !uaSniffed.isOpera) {
+
+                inputArea.focus();
+                inputArea.selectionStart = stateObj.start;
+                inputArea.selectionEnd = stateObj.end;
+                inputArea.scrollTop = stateObj.scrollTop;
+            }
+            else if (doc.selection) {
+
+                if (doc.activeElement && doc.activeElement !== inputArea) {
+                    return;
+                }
+
+                inputArea.focus();
+                var range = inputArea.createTextRange();
+                range.moveStart("character", -inputArea.value.length);
+                range.moveEnd("character", -inputArea.value.length);
+                range.moveEnd("character", stateObj.end);
+                range.moveStart("character", stateObj.start);
+                range.select();
+            }
+        };
+
+        this.setInputAreaSelectionStartEnd = function () {
+
+            if (!panels.ieCachedRange && (inputArea.selectionStart || inputArea.selectionStart === 0)) {
+
+                stateObj.start = inputArea.selectionStart;
+                stateObj.end = inputArea.selectionEnd;
+            }
+            else if (doc.selection) {
+
+                stateObj.text = util.fixEolChars(inputArea.value);
+
+                // IE loses the selection in the textarea when buttons are
+                // clicked.  On IE we cache the selection. Here, if something is cached,
+                // we take it.
+                var range = panels.ieCachedRange || doc.selection.createRange();
+
+                var fixedRange = util.fixEolChars(range.text);
+                var marker = "\x07";
+                var markedRange = marker + fixedRange + marker;
+                range.text = markedRange;
+                var inputText = util.fixEolChars(inputArea.value);
+
+                range.moveStart("character", -markedRange.length);
+                range.text = fixedRange;
+
+                stateObj.start = inputText.indexOf(marker);
+                stateObj.end = inputText.lastIndexOf(marker) - marker.length;
+
+                var len = stateObj.text.length - util.fixEolChars(inputArea.value).length;
+
+                if (len) {
+                    range.moveStart("character", -fixedRange.length);
+                    while (len--) {
+                        fixedRange += "\n";
+                        stateObj.end += 1;
+                    }
+                    range.text = fixedRange;
+                }
+
+                if (panels.ieCachedRange)
+                    stateObj.scrollTop = panels.ieCachedScrollTop; // this is set alongside with ieCachedRange
+
+                panels.ieCachedRange = null;
+
+                this.setInputAreaSelection();
+            }
+        };
+
+        // Restore this state into the input area.
+        this.restore = function () {
+
+            if (stateObj.text != undefined && stateObj.text != inputArea.value) {
+                inputArea.value = stateObj.text;
+            }
+            this.setInputAreaSelection();
+            inputArea.scrollTop = stateObj.scrollTop;
+        };
+
+        // Gets a collection of HTML chunks from the inptut textarea.
+        this.getChunks = function () {
+
+            var chunk = new Chunks();
+            chunk.before = util.fixEolChars(stateObj.text.substring(0, stateObj.start));
+            chunk.startTag = "";
+            chunk.selection = util.fixEolChars(stateObj.text.substring(stateObj.start, stateObj.end));
+            chunk.endTag = "";
+            chunk.after = util.fixEolChars(stateObj.text.substring(stateObj.end));
+            chunk.scrollTop = stateObj.scrollTop;
+
+            return chunk;
+        };
+
+        // Sets the TextareaState properties given a chunk of markdown.
+        this.setChunks = function (chunk) {
+
+            chunk.before = chunk.before + chunk.startTag;
+            chunk.after = chunk.endTag + chunk.after;
+
+            this.start = chunk.before.length;
+            this.end = chunk.before.length + chunk.selection.length;
+            this.text = chunk.before + chunk.selection + chunk.after;
+            this.scrollTop = chunk.scrollTop;
+        };
+        this.init();
+    };
+
+    function PreviewManager(converter, panels, previewRefreshCallback) {
+
+        var managerObj = this;
+        var timeout;
+        var elapsedTime;
+        var oldInputText;
+        var maxDelay = 3000;
+        var startType = "delayed"; // The other legal value is "manual"
+
+        // Adds event listeners to elements
+        var setupEvents = function (inputElem, listener) {
+
+            util.addEvent(inputElem, "input", listener);
+            inputElem.onpaste = listener;
+            inputElem.ondrop = listener;
+
+            util.addEvent(inputElem, "keypress", listener);
+            util.addEvent(inputElem, "keydown", listener);
+        };
+
+        var getDocScrollTop = function () {
+
+            var result = 0;
+
+            if (window.innerHeight) {
+                result = window.pageYOffset;
+            }
+            else
+                if (doc.documentElement && doc.documentElement.scrollTop) {
+                    result = doc.documentElement.scrollTop;
+                }
+                else
+                    if (doc.body) {
+                        result = doc.body.scrollTop;
+                    }
+
+            return result;
+        };
+
+        var makePreviewHtml = function () {
+
+            // If there is no registered preview panel
+            // there is nothing to do.
+            if (!panels.preview)
+                return;
+
+            var text = panels.input.value;
+            if (text && text == oldInputText) {
+                return; // Input text hasn't changed.
+            }
+            else {
+                oldInputText = text;
+            }
+
+            var prevTime = new Date().getTime();
+
+            text = converter.makeHtml(text);
+
+            // Calculate the processing time of the HTML creation.
+            // It's used as the delay time in the event listener.
+            var currTime = new Date().getTime();
+            elapsedTime = currTime - prevTime;
+
+            pushPreviewHtml(text);
+        };
+
+        // setTimeout is already used.  Used as an event listener.
+        var applyTimeout = function () {
+
+            if (timeout) {
+                clearTimeout(timeout);
+                timeout = undefined;
+            }
+
+            if (startType !== "manual") {
+
+                var delay = 0;
+
+                if (startType === "delayed") {
+                    delay = elapsedTime;
+                }
+
+                if (delay > maxDelay) {
+                    delay = maxDelay;
+                }
+                timeout = setTimeout(makePreviewHtml, delay);
+            }
+        };
+
+        var getScaleFactor = function (panel) {
+            if (panel.scrollHeight <= panel.clientHeight) {
+                return 1;
+            }
+            return panel.scrollTop / (panel.scrollHeight - panel.clientHeight);
+        };
+
+        var setPanelScrollTops = function () {
+            if (panels.preview) {
+                panels.preview.scrollTop = (panels.preview.scrollHeight - panels.preview.clientHeight) * getScaleFactor(panels.preview);
+            }
+        };
+
+        this.refresh = function (requiresRefresh) {
+
+            if (requiresRefresh) {
+                oldInputText = "";
+                makePreviewHtml();
+            }
+            else {
+                applyTimeout();
+            }
+        };
+
+        this.processingTime = function () {
+            return elapsedTime;
+        };
+
+        var isFirstTimeFilled = true;
+
+        // IE doesn't let you use innerHTML if the element is contained somewhere in a table
+        // (which is the case for inline editing) -- in that case, detach the element, set the
+        // value, and reattach. Yes, that *is* ridiculous.
+        var ieSafePreviewSet = function (text) {
+            var preview = panels.preview;
+            var parent = preview.parentNode;
+            var sibling = preview.nextSibling;
+            parent.removeChild(preview);
+            preview.innerHTML = text;
+            if (!sibling)
+                parent.appendChild(preview);
+            else
+                parent.insertBefore(preview, sibling);
+        }
+
+        var nonSuckyBrowserPreviewSet = function (text) {
+            panels.preview.innerHTML = text;
+        }
+
+        var previewSetter;
+
+        var previewSet = function (text) {
+            if (previewSetter)
+                return previewSetter(text);
+
+            try {
+                nonSuckyBrowserPreviewSet(text);
+                previewSetter = nonSuckyBrowserPreviewSet;
+            } catch (e) {
+                previewSetter = ieSafePreviewSet;
+                previewSetter(text);
+            }
+        };
+
+        var pushPreviewHtml = function (text) {
+
+            var emptyTop = position.getTop(panels.input) - getDocScrollTop();
+
+            if (panels.preview) {
+                previewSet(text);
+                previewRefreshCallback();
+            }
+
+            setPanelScrollTops();
+
+            if (isFirstTimeFilled) {
+                isFirstTimeFilled = false;
+                return;
+            }
+
+            var fullTop = position.getTop(panels.input) - getDocScrollTop();
+
+            if (uaSniffed.isIE) {
+                setTimeout(function () {
+                    window.scrollBy(0, fullTop - emptyTop);
+                }, 0);
+            }
+            else {
+                window.scrollBy(0, fullTop - emptyTop);
+            }
+        };
+
+        var init = function () {
+
+            setupEvents(panels.input, applyTimeout);
+            makePreviewHtml();
+
+            if (panels.preview) {
+                panels.preview.scrollTop = 0;
+            }
+        };
+
+        init();
+    };
+
+    // Creates the background behind the hyperlink text entry box.
+    // And download dialog
+    // Most of this has been moved to CSS but the div creation and
+    // browser-specific hacks remain here.
+    ui.createBackground = function () {
+
+        var background = doc.createElement("div"),
+            style = background.style;
+        
+        background.className = "wmd-prompt-background";
+        
+        style.position = "absolute";
+        style.top = "0";
+
+        style.zIndex = "1000";
+
+        if (uaSniffed.isIE) {
+            style.filter = "alpha(opacity=50)";
+        }
+        else {
+            style.opacity = "0.5";
+        }
+
+        var pageSize = position.getPageSize();
+        style.height = pageSize[1] + "px";
+
+        if (uaSniffed.isIE) {
+            style.left = doc.documentElement.scrollLeft;
+            style.width = doc.documentElement.clientWidth;
+        }
+        else {
+            style.left = "0";
+            style.width = "100%";
+        }
+
+        doc.body.appendChild(background);
+        return background;
+    };
+
+    // This simulates a modal dialog box and asks for the URL when you
+    // click the hyperlink or image buttons.
+    //
+    // text: The html for the input box.
+    // defaultInputText: The default value that appears in the input box.
+    // callback: The function which is executed when the prompt is dismissed, either via OK or Cancel.
+    //      It receives a single argument; either the entered text (if OK was chosen) or null (if Cancel
+    //      was chosen).
+    ui.prompt = function (text, defaultInputText, callback) {
+
+        // These variables need to be declared at this level since they are used
+        // in multiple functions.
+        var dialog;         // The dialog box.
+        var input;         // The text box where you enter the hyperlink.
+
+
+        if (defaultInputText === undefined) {
+            defaultInputText = "";
+        }
+
+        // Used as a keydown event handler. Esc dismisses the prompt.
+        // Key code 27 is ESC.
+        var checkEscape = function (key) {
+            var code = (key.charCode || key.keyCode);
+            if (code === 27) {
+                close(true);
+            }
+        };
+
+        // Dismisses the hyperlink input box.
+        // isCancel is true if we don't care about the input text.
+        // isCancel is false if we are going to keep the text.
+        var close = function (isCancel) {
+            util.removeEvent(doc.body, "keydown", checkEscape);
+            var text = input.value;
+
+            if (isCancel) {
+                text = null;
+            }
+            else {
+                // Fixes common pasting errors.
+                text = text.replace(/^http:\/\/(https?|ftp):\/\//, '$1://');
+                if (!/^(?:https?|ftp):\/\//.test(text))
+                    text = 'http://' + text;
+            }
+
+            dialog.parentNode.removeChild(dialog);
+
+            callback(text);
+            return false;
+        };
+
+
+
+        // Create the text input box form/window.
+        var createDialog = function () {
+
+            // The main dialog box.
+            dialog = doc.createElement("div");
+            dialog.className = "wmd-prompt-dialog";
+            dialog.style.padding = "10px;";
+            dialog.style.position = "fixed";
+            dialog.style.width = "400px";
+            dialog.style.zIndex = "1001";
+
+            // The dialog text.
+            var question = doc.createElement("div");
+            question.innerHTML = text;
+            question.style.padding = "5px";
+            dialog.appendChild(question);
+
+            // The web form container for the text box and buttons.
+            var form = doc.createElement("form"),
+                style = form.style;
+            form.onsubmit = function () { return close(false); };
+            style.padding = "0";
+            style.margin = "0";
+            style.cssFloat = "left";
+            style.width = "100%";
+            style.textAlign = "center";
+            style.position = "relative";
+            dialog.appendChild(form);
+
+            // The input text box
+            input = doc.createElement("input");
+            input.type = "text";
+            input.value = defaultInputText;
+            style = input.style;
+            style.display = "block";
+            style.width = "80%";
+            style.marginLeft = style.marginRight = "auto";
+            form.appendChild(input);
+
+            // The ok button
+            var okButton = doc.createElement("input");
+            okButton.type = "button";
+            okButton.onclick = function () { return close(false); };
+            okButton.value = "OK";
+            style = okButton.style;
+            style.margin = "10px";
+            style.display = "inline";
+            style.width = "7em";
+
+
+            // The cancel button
+            var cancelButton = doc.createElement("input");
+            cancelButton.type = "button";
+            cancelButton.onclick = function () { return close(true); };
+            cancelButton.value = "Cancel";
+            style = cancelButton.style;
+            style.margin = "10px";
+            style.display = "inline";
+            style.width = "7em";
+
+            form.appendChild(okButton);
+            form.appendChild(cancelButton);
+
+            util.addEvent(doc.body, "keydown", checkEscape);
+            dialog.style.top = "50%";
+            dialog.style.left = "50%";
+            dialog.style.display = "block";
+            if (uaSniffed.isIE_5or6) {
+                dialog.style.position = "absolute";
+                dialog.style.top = doc.documentElement.scrollTop + 200 + "px";
+                dialog.style.left = "50%";
+            }
+            doc.body.appendChild(dialog);
+
+            // This has to be done AFTER adding the dialog to the form if you
+            // want it to be centered.
+            dialog.style.marginTop = -(position.getHeight(dialog) / 2) + "px";
+            dialog.style.marginLeft = -(position.getWidth(dialog) / 2) + "px";
+
+        };
+
+        // Why is this in a zero-length timeout?
+        // Is it working around a browser bug?
+        setTimeout(function () {
+
+            createDialog();
+
+            var defTextLen = defaultInputText.length;
+            if (input.selectionStart !== undefined) {
+                input.selectionStart = 0;
+                input.selectionEnd = defTextLen;
+            }
+            else if (input.createTextRange) {
+                var range = input.createTextRange();
+                range.collapse(false);
+                range.moveStart("character", -defTextLen);
+                range.moveEnd("character", defTextLen);
+                range.select();
+            }
+
+            input.focus();
+        }, 0);
+    };
+
+    function UIManager(postfix, panels, undoManager, previewManager, commandManager, helpOptions, getString) {
+
+        var inputBox = panels.input,
+            buttons = {}; // buttons.undo, buttons.link, etc. The actual DOM elements.
+
+        makeSpritedButtonRow();
+
+        var keyEvent = "keydown";
+        if (uaSniffed.isOpera) {
+            keyEvent = "keypress";
+        }
+
+        util.addEvent(inputBox, keyEvent, function (key) {
+
+            // Check to see if we have a button key and, if so execute the callback.
+            if ((key.ctrlKey || key.metaKey) && !key.altKey && !key.shiftKey) {
+
+                var keyCode = key.charCode || key.keyCode;
+                var keyCodeStr = String.fromCharCode(keyCode).toLowerCase();
+
+                switch (keyCodeStr) {
+                    case "b":
+                        doClick(buttons.bold);
+                        break;
+                    case "i":
+                        doClick(buttons.italic);
+                        break;
+                    case "l":
+                        doClick(buttons.link);
+                        break;
+                    case "q":
+                        doClick(buttons.quote);
+                        break;
+                    case "k":
+                        doClick(buttons.code);
+                        break;
+                    case "g":
+                        doClick(buttons.image);
+                        break;
+                    case "o":
+                        doClick(buttons.olist);
+                        break;
+                    case "u":
+                        doClick(buttons.ulist);
+                        break;
+                    case "h":
+                        doClick(buttons.heading);
+                        break;
+                    case "r":
+                        doClick(buttons.hr);
+                        break;
+                    case "y":
+                        doClick(buttons.redo);
+                        break;
+                    case "z":
+                        if (key.shiftKey) {
+                            doClick(buttons.redo);
+                        }
+                        else {
+                            doClick(buttons.undo);
+                        }
+                        break;
+                    default:
+                        return;
+                }
+
+
+                if (key.preventDefault) {
+                    key.preventDefault();
+                }
+
+                if (window.event) {
+                    window.event.returnValue = false;
+                }
+            }
+        });
+
+        // Auto-indent on shift-enter
+        util.addEvent(inputBox, "keyup", function (key) {
+            if (key.shiftKey && !key.ctrlKey && !key.metaKey) {
+                var keyCode = key.charCode || key.keyCode;
+                // Character 13 is Enter
+                if (keyCode === 13) {
+                    var fakeButton = {};
+                    fakeButton.textOp = bindCommand("doAutoindent");
+                    doClick(fakeButton);
+                }
+            }
+        });
+
+        // special handler because IE clears the context of the textbox on ESC
+        if (uaSniffed.isIE) {
+            util.addEvent(inputBox, "keydown", function (key) {
+                var code = key.keyCode;
+                if (code === 27) {
+                    return false;
+                }
+            });
+        }
+
+
+        // Perform the button's action.
+        function doClick(button) {
+
+            inputBox.focus();
+
+            if (button.textOp) {
+
+                if (undoManager) {
+                    undoManager.setCommandMode();
+                }
+
+                var state = new TextareaState(panels);
+
+                if (!state) {
+                    return;
+                }
+
+                var chunks = state.getChunks();
+
+                // Some commands launch a "modal" prompt dialog.  Javascript
+                // can't really make a modal dialog box and the WMD code
+                // will continue to execute while the dialog is displayed.
+                // This prevents the dialog pattern I'm used to and means
+                // I can't do something like this:
+                //
+                // var link = CreateLinkDialog();
+                // makeMarkdownLink(link);
+                //
+                // Instead of this straightforward method of handling a
+                // dialog I have to pass any code which would execute
+                // after the dialog is dismissed (e.g. link creation)
+                // in a function parameter.
+                //
+                // Yes this is awkward and I think it sucks, but there's
+                // no real workaround.  Only the image and link code
+                // create dialogs and require the function pointers.
+                var fixupInputArea = function () {
+
+                    inputBox.focus();
+
+                    if (chunks) {
+                        state.setChunks(chunks);
+                    }
+
+                    state.restore();
+                    previewManager.refresh();
+                };
+
+                var noCleanup = button.textOp(chunks, fixupInputArea);
+
+                if (!noCleanup) {
+                    fixupInputArea();
+                }
+
+            }
+
+            if (button.execute) {
+                button.execute(undoManager);
+            }
+        };
+
+        function setupButton(button, isEnabled) {
+
+            var normalYShift = "0px";
+            var disabledYShift = "-20px";
+            var highlightYShift = "-40px";
+            var image = button.getElementsByTagName("span")[0];
+            if (isEnabled) {
+                image.style.backgroundPosition = button.XShift + " " + normalYShift;
+                button.onmouseover = function () {
+                    image.style.backgroundPosition = this.XShift + " " + highlightYShift;
+                };
+
+                button.onmouseout = function () {
+                    image.style.backgroundPosition = this.XShift + " " + normalYShift;
+                };
+
+                // IE tries to select the background image "button" text (it's
+                // implemented in a list item) so we have to cache the selection
+                // on mousedown.
+                if (uaSniffed.isIE) {
+                    button.onmousedown = function () {
+                        if (doc.activeElement && doc.activeElement !== panels.input) { // we're not even in the input box, so there's no selection
+                            return;
+                        }
+                        panels.ieCachedRange = document.selection.createRange();
+                        panels.ieCachedScrollTop = panels.input.scrollTop;
+                    };
+                }
+
+                if (!button.isHelp) {
+                    button.onclick = function () {
+                        if (this.onmouseout) {
+                            this.onmouseout();
+                        }
+                        doClick(this);
+                        return false;
+                    }
+                }
+            }
+            else {
+                image.style.backgroundPosition = button.XShift + " " + disabledYShift;
+                button.onmouseover = button.onmouseout = button.onclick = function () { };
+            }
+        }
+
+        function bindCommand(method) {
+            if (typeof method === "string")
+                method = commandManager[method];
+            return function () { method.apply(commandManager, arguments); }
+        }
+
+        function makeSpritedButtonRow() {
+
+            var buttonBar = panels.buttonBar;
+
+            var normalYShift = "0px";
+            var disabledYShift = "-20px";
+            var highlightYShift = "-40px";
+
+            var buttonRow = document.createElement("ul");
+            buttonRow.id = "wmd-button-row" + postfix;
+            buttonRow.className = 'wmd-button-row';
+            buttonRow = buttonBar.appendChild(buttonRow);
+            var xPosition = 0;
+            var makeButton = function (id, title, XShift, textOp) {
+                var button = document.createElement("li");
+                button.className = "wmd-button";
+                button.style.left = xPosition + "px";
+                xPosition += 25;
+                var buttonImage = document.createElement("span");
+                button.id = id + postfix;
+                button.appendChild(buttonImage);
+                button.title = title;
+                button.XShift = XShift;
+                if (textOp)
+                    button.textOp = textOp;
+                setupButton(button, true);
+                buttonRow.appendChild(button);
+                return button;
+            };
+            var makeSpacer = function (num) {
+                var spacer = document.createElement("li");
+                spacer.className = "wmd-spacer wmd-spacer" + num;
+                spacer.id = "wmd-spacer" + num + postfix;
+                buttonRow.appendChild(spacer);
+                xPosition += 25;
+            }
+
+            buttons.bold = makeButton("wmd-bold-button", getString("bold"), "0px", bindCommand("doBold"));
+            buttons.italic = makeButton("wmd-italic-button", getString("italic"), "-20px", bindCommand("doItalic"));
+            makeSpacer(1);
+            buttons.link = makeButton("wmd-link-button", getString("link"), "-40px", bindCommand(function (chunk, postProcessing) {
+                return this.doLinkOrImage(chunk, postProcessing, false);
+            }));
+            buttons.quote = makeButton("wmd-quote-button", getString("quote"), "-60px", bindCommand("doBlockquote"));
+            buttons.code = makeButton("wmd-code-button", getString("code"), "-80px", bindCommand("doCode"));
+            buttons.image = makeButton("wmd-image-button", getString("image"), "-100px", bindCommand(function (chunk, postProcessing) {
+                return this.doLinkOrImage(chunk, postProcessing, true);
+            }));
+            makeSpacer(2);
+            buttons.olist = makeButton("wmd-olist-button", getString("olist"), "-120px", bindCommand(function (chunk, postProcessing) {
+                this.doList(chunk, postProcessing, true);
+            }));
+            buttons.ulist = makeButton("wmd-ulist-button", getString("ulist"), "-140px", bindCommand(function (chunk, postProcessing) {
+                this.doList(chunk, postProcessing, false);
+            }));
+            buttons.heading = makeButton("wmd-heading-button", getString("heading"), "-160px", bindCommand("doHeading"));
+            buttons.hr = makeButton("wmd-hr-button", getString("hr"), "-180px", bindCommand("doHorizontalRule"));
+            makeSpacer(3);
+            buttons.undo = makeButton("wmd-undo-button", getString("undo"), "-200px", null);
+            buttons.undo.execute = function (manager) { if (manager) manager.undo(); };
+
+            var redoTitle = /win/.test(nav.platform.toLowerCase()) ?
+                getString("redo") :
+                getString("redomac"); // mac and other non-Windows platforms
+
+            buttons.redo = makeButton("wmd-redo-button", redoTitle, "-220px", null);
+            buttons.redo.execute = function (manager) { if (manager) manager.redo(); };
+
+            if (helpOptions) {
+                var helpButton = document.createElement("li");
+                var helpButtonImage = document.createElement("span");
+                helpButton.appendChild(helpButtonImage);
+                helpButton.className = "wmd-button wmd-help-button";
+                helpButton.id = "wmd-help-button" + postfix;
+                helpButton.XShift = "-240px";
+                helpButton.isHelp = true;
+                helpButton.style.right = "0px";
+                helpButton.title = getString("help");
+                helpButton.onclick = helpOptions.handler;
+
+                setupButton(helpButton, true);
+                buttonRow.appendChild(helpButton);
+                buttons.help = helpButton;
+            }
+
+            setUndoRedoButtonStates();
+        }
+
+        function setUndoRedoButtonStates() {
+            if (undoManager) {
+                setupButton(buttons.undo, undoManager.canUndo());
+                setupButton(buttons.redo, undoManager.canRedo());
+            }
+        };
+
+        this.setUndoRedoButtonStates = setUndoRedoButtonStates;
+
+    }
+
+    function CommandManager(pluginHooks, getString) {
+        this.hooks = pluginHooks;
+        this.getString = getString;
+    }
+
+    var commandProto = CommandManager.prototype;
+
+    // The markdown symbols - 4 spaces = code, > = blockquote, etc.
+    commandProto.prefixes = "(?:\\s{4,}|\\s*>|\\s*-\\s+|\\s*\\d+\\.|=|\\+|-|_|\\*|#|\\s*\\[[^\n]]+\\]:)";
+
+    // Remove markdown symbols from the chunk selection.
+    commandProto.unwrap = function (chunk) {
+        var txt = new re("([^\\n])\\n(?!(\\n|" + this.prefixes + "))", "g");
+        chunk.selection = chunk.selection.replace(txt, "$1 $2");
+    };
+
+    commandProto.wrap = function (chunk, len) {
+        this.unwrap(chunk);
+        var regex = new re("(.{1," + len + "})( +|$\\n?)", "gm"),
+            that = this;
+
+        chunk.selection = chunk.selection.replace(regex, function (line, marked) {
+            if (new re("^" + that.prefixes, "").test(line)) {
+                return line;
+            }
+            return marked + "\n";
+        });
+
+        chunk.selection = chunk.selection.replace(/\s+$/, "");
+    };
+
+    commandProto.doBold = function (chunk, postProcessing) {
+        return this.doBorI(chunk, postProcessing, 2, this.getString("boldexample"));
+    };
+
+    commandProto.doItalic = function (chunk, postProcessing) {
+        return this.doBorI(chunk, postProcessing, 1, this.getString("italicexample"));
+    };
+
+    // chunk: The selected region that will be enclosed with */**
+    // nStars: 1 for italics, 2 for bold
+    // insertText: If you just click the button without highlighting text, this gets inserted
+    commandProto.doBorI = function (chunk, postProcessing, nStars, insertText) {
+
+        // Get rid of whitespace and fixup newlines.
+        chunk.trimWhitespace();
+        chunk.selection = chunk.selection.replace(/\n{2,}/g, "\n");
+
+        // Look for stars before and after.  Is the chunk already marked up?
+        // note that these regex matches cannot fail
+        var starsBefore = /(\**$)/.exec(chunk.before)[0];
+        var starsAfter = /(^\**)/.exec(chunk.after)[0];
+
+        var prevStars = Math.min(starsBefore.length, starsAfter.length);
+
+        // Remove stars if we have to since the button acts as a toggle.
+        if ((prevStars >= nStars) && (prevStars != 2 || nStars != 1)) {
+            chunk.before = chunk.before.replace(re("[*]{" + nStars + "}$", ""), "");
+            chunk.after = chunk.after.replace(re("^[*]{" + nStars + "}", ""), "");
+        }
+        else if (!chunk.selection && starsAfter) {
+            // It's not really clear why this code is necessary.  It just moves
+            // some arbitrary stuff around.
+            chunk.after = chunk.after.replace(/^([*_]*)/, "");
+            chunk.before = chunk.before.replace(/(\s?)$/, "");
+            var whitespace = re.$1;
+            chunk.before = chunk.before + starsAfter + whitespace;
+        }
+        else {
+
+            // In most cases, if you don't have any selected text and click the button
+            // you'll get a selected, marked up region with the default text inserted.
+            if (!chunk.selection && !starsAfter) {
+                chunk.selection = insertText;
+            }
+
+            // Add the true markup.
+            var markup = nStars <= 1 ? "*" : "**"; // shouldn't the test be = ?
+            chunk.before = chunk.before + markup;
+            chunk.after = markup + chunk.after;
+        }
+
+        return;
+    };
+
+    commandProto.stripLinkDefs = function (text, defsToAdd) {
+
+        text = text.replace(/^[ ]{0,3}\[(\d+)\]:[ \t]*\n?[ \t]*<?(\S+?)>?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|$)/gm,
+            function (totalMatch, id, link, newlines, title) {
+                defsToAdd[id] = totalMatch.replace(/\s*$/, "");
+                if (newlines) {
+                    // Strip the title and return that separately.
+                    defsToAdd[id] = totalMatch.replace(/["(](.+?)[")]$/, "");
+                    return newlines + title;
+                }
+                return "";
+            });
+
+        return text;
+    };
+
+    commandProto.addLinkDef = function (chunk, linkDef) {
+
+        var refNumber = 0; // The current reference number
+        var defsToAdd = {}; //
+        // Start with a clean slate by removing all previous link definitions.
+        chunk.before = this.stripLinkDefs(chunk.before, defsToAdd);
+        chunk.selection = this.stripLinkDefs(chunk.selection, defsToAdd);
+        chunk.after = this.stripLinkDefs(chunk.after, defsToAdd);
+
+        var defs = "";
+        var regex = /(\[)((?:\[[^\]]*\]|[^\[\]])*)(\][ ]?(?:\n[ ]*)?\[)(\d+)(\])/g;
+
+        var addDefNumber = function (def) {
+            refNumber++;
+            def = def.replace(/^[ ]{0,3}\[(\d+)\]:/, "  [" + refNumber + "]:");
+            defs += "\n" + def;
+        };
+
+        // note that
+        // a) the recursive call to getLink cannot go infinite, because by definition
+        //    of regex, inner is always a proper substring of wholeMatch, and
+        // b) more than one level of nesting is neither supported by the regex
+        //    nor making a lot of sense (the only use case for nesting is a linked image)
+        var getLink = function (wholeMatch, before, inner, afterInner, id, end) {
+            inner = inner.replace(regex, getLink);
+            if (defsToAdd[id]) {
+                addDefNumber(defsToAdd[id]);
+                return before + inner + afterInner + refNumber + end;
+            }
+            return wholeMatch;
+        };
+
+        chunk.before = chunk.before.replace(regex, getLink);
+
+        if (linkDef) {
+            addDefNumber(linkDef);
+        }
+        else {
+            chunk.selection = chunk.selection.replace(regex, getLink);
+        }
+
+        var refOut = refNumber;
+
+        chunk.after = chunk.after.replace(regex, getLink);
+
+        if (chunk.after) {
+            chunk.after = chunk.after.replace(/\n*$/, "");
+        }
+        if (!chunk.after) {
+            chunk.selection = chunk.selection.replace(/\n*$/, "");
+        }
+
+        chunk.after += "\n\n" + defs;
+
+        return refOut;
+    };
+
+    // takes the line as entered into the add link/as image dialog and makes
+    // sure the URL and the optinal title are "nice".
+    function properlyEncoded(linkdef) {
+        return linkdef.replace(/^\s*(.*?)(?:\s+"(.+)")?\s*$/, function (wholematch, link, title) {
+            link = link.replace(/\?.*$/, function (querypart) {
+                return querypart.replace(/\+/g, " "); // in the query string, a plus and a space are identical
+            });
+            link = decodeURIComponent(link); // unencode first, to prevent double encoding
+            link = encodeURI(link).replace(/'/g, '%27').replace(/\(/g, '%28').replace(/\)/g, '%29');
+            link = link.replace(/\?.*$/, function (querypart) {
+                return querypart.replace(/\+/g, "%2b"); // since we replaced plus with spaces in the query part, all pluses that now appear where originally encoded
+            });
+            if (title) {
+                title = title.trim ? title.trim() : title.replace(/^\s*/, "").replace(/\s*$/, "");
+                title = title.replace(/"/g, "quot;").replace(/\(/g, "&#40;").replace(/\)/g, "&#41;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
+            }
+            return title ? link + ' "' + title + '"' : link;
+        });
+    }
+
+    commandProto.doLinkOrImage = function (chunk, postProcessing, isImage) {
+
+        chunk.trimWhitespace();
+        chunk.findTags(/\s*!?\[/, /\][ ]?(?:\n[ ]*)?(\[.*?\])?/);
+        var background;
+
+        if (chunk.endTag.length > 1 && chunk.startTag.length > 0) {
+
+            chunk.startTag = chunk.startTag.replace(/!?\[/, "");
+            chunk.endTag = "";
+            this.addLinkDef(chunk, null);
+
+        }
+        else {
+            
+            // We're moving start and end tag back into the selection, since (as we're in the else block) we're not
+            // *removing* a link, but *adding* one, so whatever findTags() found is now back to being part of the
+            // link text. linkEnteredCallback takes care of escaping any brackets.
+            chunk.selection = chunk.startTag + chunk.selection + chunk.endTag;
+            chunk.startTag = chunk.endTag = "";
+
+            if (/\n\n/.test(chunk.selection)) {
+                this.addLinkDef(chunk, null);
+                return;
+            }
+            var that = this;
+            // The function to be executed when you enter a link and press OK or Cancel.
+            // Marks up the link and adds the ref.
+            var linkEnteredCallback = function (link) {
+
+                background.parentNode.removeChild(background);
+
+                if (link !== null) {
+                    // (                          $1
+                    //     [^\\]                  anything that's not a backslash
+                    //     (?:\\\\)*              an even number (this includes zero) of backslashes
+                    // )
+                    // (?=                        followed by
+                    //     [[\]]                  an opening or closing bracket
+                    // )
+                    //
+                    // In other words, a non-escaped bracket. These have to be escaped now to make sure they
+                    // don't count as the end of the link or similar.
+                    // Note that the actual bracket has to be a lookahead, because (in case of to subsequent brackets),
+                    // the bracket in one match may be the "not a backslash" character in the next match, so it
+                    // should not be consumed by the first match.
+                    // The "prepend a space and finally remove it" steps makes sure there is a "not a backslash" at the
+                    // start of the string, so this also works if the selection begins with a bracket. We cannot solve
+                    // this by anchoring with ^, because in the case that the selection starts with two brackets, this
+                    // would mean a zero-width match at the start. Since zero-width matches advance the string position,
+                    // the first bracket could then not act as the "not a backslash" for the second.
+                    chunk.selection = (" " + chunk.selection).replace(/([^\\](?:\\\\)*)(?=[[\]])/g, "$1\\").substr(1);
+                    
+                    var linkDef = " [999]: " + properlyEncoded(link);
+
+                    var num = that.addLinkDef(chunk, linkDef);
+                    chunk.startTag = isImage ? "![" : "[";
+                    chunk.endTag = "][" + num + "]";
+
+                    if (!chunk.selection) {
+                        if (isImage) {
+                            chunk.selection = that.getString("imagedescription");
+                        }
+                        else {
+                            chunk.selection = that.getString("linkdescription");
+                        }
+                    }
+                }
+                postProcessing();
+            };
+
+            background = ui.createBackground();
+
+            if (isImage) {
+                if (!this.hooks.insertImageDialog(linkEnteredCallback))
+                    ui.prompt(this.getString("imagedialog"), imageDefaultText, linkEnteredCallback);
+            }
+            else {
+                ui.prompt(this.getString("linkdialog"), linkDefaultText, linkEnteredCallback);
+            }
+            return true;
+        }
+    };
+
+    // When making a list, hitting shift-enter will put your cursor on the next line
+    // at the current indent level.
+    commandProto.doAutoindent = function (chunk, postProcessing) {
+
+        var commandMgr = this,
+            fakeSelection = false;
+
+        chunk.before = chunk.before.replace(/(\n|^)[ ]{0,3}([*+-]|\d+[.])[ \t]*\n$/, "\n\n");
+        chunk.before = chunk.before.replace(/(\n|^)[ ]{0,3}>[ \t]*\n$/, "\n\n");
+        chunk.before = chunk.before.replace(/(\n|^)[ \t]+\n$/, "\n\n");
+        
+        // There's no selection, end the cursor wasn't at the end of the line:
+        // The user wants to split the current list item / code line / blockquote line
+        // (for the latter it doesn't really matter) in two. Temporarily select the
+        // (rest of the) line to achieve this.
+        if (!chunk.selection && !/^[ \t]*(?:\n|$)/.test(chunk.after)) {
+            chunk.after = chunk.after.replace(/^[^\n]*/, function (wholeMatch) {
+                chunk.selection = wholeMatch;
+                return "";
+            });
+            fakeSelection = true;
+        }
+
+        if (/(\n|^)[ ]{0,3}([*+-]|\d+[.])[ \t]+.*\n$/.test(chunk.before)) {
+            if (commandMgr.doList) {
+                commandMgr.doList(chunk);
+            }
+        }
+        if (/(\n|^)[ ]{0,3}>[ \t]+.*\n$/.test(chunk.before)) {
+            if (commandMgr.doBlockquote) {
+                commandMgr.doBlockquote(chunk);
+            }
+        }
+        if (/(\n|^)(\t|[ ]{4,}).*\n$/.test(chunk.before)) {
+            if (commandMgr.doCode) {
+                commandMgr.doCode(chunk);
+            }
+        }
+        
+        if (fakeSelection) {
+            chunk.after = chunk.selection + chunk.after;
+            chunk.selection = "";
+        }
+    };
+
+    commandProto.doBlockquote = function (chunk, postProcessing) {
+
+        chunk.selection = chunk.selection.replace(/^(\n*)([^\r]+?)(\n*)$/,
+            function (totalMatch, newlinesBefore, text, newlinesAfter) {
+                chunk.before += newlinesBefore;
+                chunk.after = newlinesAfter + chunk.after;
+                return text;
+            });
+
+        chunk.before = chunk.before.replace(/(>[ \t]*)$/,
+            function (totalMatch, blankLine) {
+                chunk.selection = blankLine + chunk.selection;
+                return "";
+            });
+
+        chunk.selection = chunk.selection.replace(/^(\s|>)+$/, "");
+        chunk.selection = chunk.selection || this.getString("quoteexample");
+
+        // The original code uses a regular expression to find out how much of the
+        // text *directly before* the selection already was a blockquote:
+
+        /*
+        if (chunk.before) {
+        chunk.before = chunk.before.replace(/\n?$/, "\n");
+        }
+        chunk.before = chunk.before.replace(/(((\n|^)(\n[ \t]*)*>(.+\n)*.*)+(\n[ \t]*)*$)/,
+        function (totalMatch) {
+        chunk.startTag = totalMatch;
+        return "";
+        });
+        */
+
+        // This comes down to:
+        // Go backwards as many lines a possible, such that each line
+        //  a) starts with ">", or
+        //  b) is almost empty, except for whitespace, or
+        //  c) is preceeded by an unbroken chain of non-empty lines
+        //     leading up to a line that starts with ">" and at least one more character
+        // and in addition
+        //  d) at least one line fulfills a)
+        //
+        // Since this is essentially a backwards-moving regex, it's susceptible to
+        // catstrophic backtracking and can cause the browser to hang;
+        // see e.g. http://meta.stackoverflow.com/questions/9807.
+        //
+        // Hence we replaced this by a simple state machine that just goes through the
+        // lines and checks for a), b), and c).
+
+        var match = "",
+            leftOver = "",
+            line;
+        if (chunk.before) {
+            var lines = chunk.before.replace(/\n$/, "").split("\n");
+            var inChain = false;
+            for (var i = 0; i < lines.length; i++) {
+                var good = false;
+                line = lines[i];
+                inChain = inChain && line.length > 0; // c) any non-empty line continues the chain
+                if (/^>/.test(line)) {                // a)
+                    good = true;
+                    if (!inChain && line.length > 1)  // c) any line that starts with ">" and has at least one more character starts the chain
+                        inChain = true;
+                } else if (/^[ \t]*$/.test(line)) {   // b)
+                    good = true;
+                } else {
+                    good = inChain;                   // c) the line is not empty and does not start with ">", so it matches if and only if we're in the chain
+                }
+                if (good) {
+                    match += line + "\n";
+                } else {
+                    leftOver += match + line;
+                    match = "\n";
+                }
+            }
+            if (!/(^|\n)>/.test(match)) {             // d)
+                leftOver += match;
+                match = "";
+            }
+        }
+
+        chunk.startTag = match;
+        chunk.before = leftOver;
+
+        // end of change
+
+        if (chunk.after) {
+            chunk.after = chunk.after.replace(/^\n?/, "\n");
+        }
+
+        chunk.after = chunk.after.replace(/^(((\n|^)(\n[ \t]*)*>(.+\n)*.*)+(\n[ \t]*)*)/,
+            function (totalMatch) {
+                chunk.endTag = totalMatch;
+                return "";
+            }
+        );
+
+        var replaceBlanksInTags = function (useBracket) {
+
+            var replacement = useBracket ? "> " : "";
+
+            if (chunk.startTag) {
+                chunk.startTag = chunk.startTag.replace(/\n((>|\s)*)\n$/,
+                    function (totalMatch, markdown) {
+                        return "\n" + markdown.replace(/^[ ]{0,3}>?[ \t]*$/gm, replacement) + "\n";
+                    });
+            }
+            if (chunk.endTag) {
+                chunk.endTag = chunk.endTag.replace(/^\n((>|\s)*)\n/,
+                    function (totalMatch, markdown) {
+                        return "\n" + markdown.replace(/^[ ]{0,3}>?[ \t]*$/gm, replacement) + "\n";
+                    });
+            }
+        };
+
+        if (/^(?![ ]{0,3}>)/m.test(chunk.selection)) {
+            this.wrap(chunk, SETTINGS.lineLength - 2);
+            chunk.selection = chunk.selection.replace(/^/gm, "> ");
+            replaceBlanksInTags(true);
+            chunk.skipLines();
+        } else {
+            chunk.selection = chunk.selection.replace(/^[ ]{0,3}> ?/gm, "");
+            this.unwrap(chunk);
+            replaceBlanksInTags(false);
+
+            if (!/^(\n|^)[ ]{0,3}>/.test(chunk.selection) && chunk.startTag) {
+                chunk.startTag = chunk.startTag.replace(/\n{0,2}$/, "\n\n");
+            }
+
+            if (!/(\n|^)[ ]{0,3}>.*$/.test(chunk.selection) && chunk.endTag) {
+                chunk.endTag = chunk.endTag.replace(/^\n{0,2}/, "\n\n");
+            }
+        }
+
+        chunk.selection = this.hooks.postBlockquoteCreation(chunk.selection);
+
+        if (!/\n/.test(chunk.selection)) {
+            chunk.selection = chunk.selection.replace(/^(> *)/,
+            function (wholeMatch, blanks) {
+                chunk.startTag += blanks;
+                return "";
+            });
+        }
+    };
+
+    commandProto.doCode = function (chunk, postProcessing) {
+
+        var hasTextBefore = /\S[ ]*$/.test(chunk.before);
+        var hasTextAfter = /^[ ]*\S/.test(chunk.after);
+
+        // Use 'four space' markdown if the selection is on its own
+        // line or is multiline.
+        if ((!hasTextAfter && !hasTextBefore) || /\n/.test(chunk.selection)) {
+
+            chunk.before = chunk.before.replace(/[ ]{4}$/,
+                function (totalMatch) {
+                    chunk.selection = totalMatch + chunk.selection;
+                    return "";
+                });
+
+            var nLinesBack = 1;
+            var nLinesForward = 1;
+
+            if (/(\n|^)(\t|[ ]{4,}).*\n$/.test(chunk.before)) {
+                nLinesBack = 0;
+            }
+            if (/^\n(\t|[ ]{4,})/.test(chunk.after)) {
+                nLinesForward = 0;
+            }
+
+            chunk.skipLines(nLinesBack, nLinesForward);
+
+            if (!chunk.selection) {
+                chunk.startTag = "    ";
+                chunk.selection = this.getString("codeexample");
+            }
+            else {
+                if (/^[ ]{0,3}\S/m.test(chunk.selection)) {
+                    if (/\n/.test(chunk.selection))
+                        chunk.selection = chunk.selection.replace(/^/gm, "    ");
+                    else // if it's not multiline, do not select the four added spaces; this is more consistent with the doList behavior
+                        chunk.before += "    ";
+                }
+                else {
+                    chunk.selection = chunk.selection.replace(/^[ ]{4}/gm, "");
+                }
+            }
+        }
+        else {
+            // Use backticks (`) to delimit the code block.
+
+            chunk.trimWhitespace();
+            chunk.findTags(/`/, /`/);
+
+            if (!chunk.startTag && !chunk.endTag) {
+                chunk.startTag = chunk.endTag = "`";
+                if (!chunk.selection) {
+                    chunk.selection = this.getString("codeexample");
+                }
+            }
+            else if (chunk.endTag && !chunk.startTag) {
+                chunk.before += chunk.endTag;
+                chunk.endTag = "";
+            }
+            else {
+                chunk.startTag = chunk.endTag = "";
+            }
+        }
+    };
+
+    commandProto.doList = function (chunk, postProcessing, isNumberedList) {
+
+        // These are identical except at the very beginning and end.
+        // Should probably use the regex extension function to make this clearer.
+        var previousItemsRegex = /(\n|^)(([ ]{0,3}([*+-]|\d+[.])[ \t]+.*)(\n.+|\n{2,}([*+-].*|\d+[.])[ \t]+.*|\n{2,}[ \t]+\S.*)*)\n*$/;
+        var nextItemsRegex = /^\n*(([ ]{0,3}([*+-]|\d+[.])[ \t]+.*)(\n.+|\n{2,}([*+-].*|\d+[.])[ \t]+.*|\n{2,}[ \t]+\S.*)*)\n*/;
+
+        // The default bullet is a dash but others are possible.
+        // This has nothing to do with the particular HTML bullet,
+        // it's just a markdown bullet.
+        var bullet = "-";
+
+        // The number in a numbered list.
+        var num = 1;
+
+        // Get the item prefix - e.g. " 1. " for a numbered list, " - " for a bulleted list.
+        var getItemPrefix = function () {
+            var prefix;
+            if (isNumberedList) {
+                prefix = " " + num + ". ";
+                num++;
+            }
+            else {
+                prefix = " " + bullet + " ";
+            }
+            return prefix;
+        };
+
+        // Fixes the prefixes of the other list items.
+        var getPrefixedItem = function (itemText) {
+
+            // The numbering flag is unset when called by autoindent.
+            if (isNumberedList === undefined) {
+                isNumberedList = /^\s*\d/.test(itemText);
+            }
+
+            // Renumber/bullet the list element.
+            itemText = itemText.replace(/^[ ]{0,3}([*+-]|\d+[.])\s/gm,
+                function (_) {
+                    return getItemPrefix();
+                });
+
+            return itemText;
+        };
+
+        chunk.findTags(/(\n|^)*[ ]{0,3}([*+-]|\d+[.])\s+/, null);
+
+        if (chunk.before && !/\n$/.test(chunk.before) && !/^\n/.test(chunk.startTag)) {
+            chunk.before += chunk.startTag;
+            chunk.startTag = "";
+        }
+
+        if (chunk.startTag) {
+
+            var hasDigits = /\d+[.]/.test(chunk.startTag);
+            chunk.startTag = "";
+            chunk.selection = chunk.selection.replace(/\n[ ]{4}/g, "\n");
+            this.unwrap(chunk);
+            chunk.skipLines();
+
+            if (hasDigits) {
+                // Have to renumber the bullet points if this is a numbered list.
+                chunk.after = chunk.after.replace(nextItemsRegex, getPrefixedItem);
+            }
+            if (isNumberedList == hasDigits) {
+                return;
+            }
+        }
+
+        var nLinesUp = 1;
+
+        chunk.before = chunk.before.replace(previousItemsRegex,
+            function (itemText) {
+                if (/^\s*([*+-])/.test(itemText)) {
+                    bullet = re.$1;
+                }
+                nLinesUp = /[^\n]\n\n[^\n]/.test(itemText) ? 1 : 0;
+                return getPrefixedItem(itemText);
+            });
+
+        if (!chunk.selection) {
+            chunk.selection = this.getString("litem");
+        }
+
+        var prefix = getItemPrefix();
+
+        var nLinesDown = 1;
+
+        chunk.after = chunk.after.replace(nextItemsRegex,
+            function (itemText) {
+                nLinesDown = /[^\n]\n\n[^\n]/.test(itemText) ? 1 : 0;
+                return getPrefixedItem(itemText);
+            });
+
+        chunk.trimWhitespace(true);
+        chunk.skipLines(nLinesUp, nLinesDown, true);
+        chunk.startTag = prefix;
+        var spaces = prefix.replace(/./g, " ");
+        this.wrap(chunk, SETTINGS.lineLength - spaces.length);
+        chunk.selection = chunk.selection.replace(/\n/g, "\n" + spaces);
+
+    };
+
+    commandProto.doHeading = function (chunk, postProcessing) {
+
+        // Remove leading/trailing whitespace and reduce internal spaces to single spaces.
+        chunk.selection = chunk.selection.replace(/\s+/g, " ");
+        chunk.selection = chunk.selection.replace(/(^\s+|\s+$)/g, "");
+
+        // If we clicked the button with no selected text, we just
+        // make a level 2 hash header around some default text.
+        if (!chunk.selection) {
+            chunk.startTag = "## ";
+            chunk.selection = this.getString("headingexample");
+            chunk.endTag = " ##";
+            return;
+        }
+
+        var headerLevel = 0;     // The existing header level of the selected text.
+
+        // Remove any existing hash heading markdown and save the header level.
+        chunk.findTags(/#+[ ]*/, /[ ]*#+/);
+        if (/#+/.test(chunk.startTag)) {
+            headerLevel = re.lastMatch.length;
+        }
+        chunk.startTag = chunk.endTag = "";
+
+        // Try to get the current header level by looking for - and = in the line
+        // below the selection.
+        chunk.findTags(null, /\s?(-+|=+)/);
+        if (/=+/.test(chunk.endTag)) {
+            headerLevel = 1;
+        }
+        if (/-+/.test(chunk.endTag)) {
+            headerLevel = 2;
+        }
+
+        // Skip to the next line so we can create the header markdown.
+        chunk.startTag = chunk.endTag = "";
+        chunk.skipLines(1, 1);
+
+        // We make a level 2 header if there is no current header.
+        // If there is a header level, we substract one from the header level.
+        // If it's already a level 1 header, it's removed.
+        var headerLevelToCreate = headerLevel == 0 ? 2 : headerLevel - 1;
+
+        if (headerLevelToCreate > 0) {
+
+            // The button only creates level 1 and 2 underline headers.
+            // Why not have it iterate over hash header levels?  Wouldn't that be easier and cleaner?
+            var headerChar = headerLevelToCreate >= 2 ? "-" : "=";
+            var len = chunk.selection.length;
+            if (len > SETTINGS.lineLength) {
+                len = SETTINGS.lineLength;
+            }
+            chunk.endTag = "\n";
+            while (len--) {
+                chunk.endTag += headerChar;
+            }
+        }
+    };
+
+    commandProto.doHorizontalRule = function (chunk, postProcessing) {
+        chunk.startTag = "----------\n";
+        chunk.selection = "";
+        chunk.skipLines(2, 1, true);
+    }
+
+
+})();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/wikked/static/js/pagedown/Markdown.Sanitizer.js	Wed Dec 19 00:23:12 2012 -0800
@@ -0,0 +1,108 @@
+(function () {
+    var output, Converter;
+    if (typeof exports === "object" && typeof require === "function") { // we're in a CommonJS (e.g. Node.js) module
+        output = exports;
+        Converter = require("./Markdown.Converter").Converter;
+    } else {
+        output = window.Markdown;
+        Converter = output.Converter;
+    }
+        
+    output.getSanitizingConverter = function () {
+        var converter = new Converter();
+        converter.hooks.chain("postConversion", sanitizeHtml);
+        converter.hooks.chain("postConversion", balanceTags);
+        return converter;
+    }
+
+    function sanitizeHtml(html) {
+        return html.replace(/<[^>]*>?/gi, sanitizeTag);
+    }
+
+    // (tags that can be opened/closed) | (tags that stand alone)
+    var basic_tag_whitelist = /^(<\/?(b|blockquote|code|del|dd|dl|dt|em|h1|h2|h3|i|kbd|li|ol|p|pre|s|sup|sub|strong|strike|ul)>|<(br|hr)\s?\/?>)$/i;
+    // <a href="url..." optional title>|</a>
+    var a_white = /^(<a\shref="((https?|ftp):\/\/|\/)[-A-Za-z0-9+&@#\/%?=~_|!:,.;\(\)]+"(\stitle="[^"<>]+")?\s?>|<\/a>)$/i;
+
+    // <img src="url..." optional width  optional height  optional alt  optional title
+    var img_white = /^(<img\ssrc="(https?:\/\/|\/)[-A-Za-z0-9+&@#\/%?=~_|!:,.;\(\)]+"(\swidth="\d{1,3}")?(\sheight="\d{1,3}")?(\salt="[^"<>]*")?(\stitle="[^"<>]*")?\s?\/?>)$/i;
+
+    function sanitizeTag(tag) {
+        if (tag.match(basic_tag_whitelist) || tag.match(a_white) || tag.match(img_white))
+            return tag;
+        else
+            return "";
+    }
+
+    /// <summary>
+    /// attempt to balance HTML tags in the html string
+    /// by removing any unmatched opening or closing tags
+    /// IMPORTANT: we *assume* HTML has *already* been 
+    /// sanitized and is safe/sane before balancing!
+    /// 
+    /// adapted from CODESNIPPET: A8591DBA-D1D3-11DE-947C-BA5556D89593
+    /// </summary>
+    function balanceTags(html) {
+
+        if (html == "")
+            return "";
+
+        var re = /<\/?\w+[^>]*(\s|$|>)/g;
+        // convert everything to lower case; this makes
+        // our case insensitive comparisons easier
+        var tags = html.toLowerCase().match(re);
+
+        // no HTML tags present? nothing to do; exit now
+        var tagcount = (tags || []).length;
+        if (tagcount == 0)
+            return html;
+
+        var tagname, tag;
+        var ignoredtags = "<p><img><br><li><hr>";
+        var match;
+        var tagpaired = [];
+        var tagremove = [];
+        var needsRemoval = false;
+
+        // loop through matched tags in forward order
+        for (var ctag = 0; ctag < tagcount; ctag++) {
+            tagname = tags[ctag].replace(/<\/?(\w+).*/, "$1");
+            // skip any already paired tags
+            // and skip tags in our ignore list; assume they're self-closed
+            if (tagpaired[ctag] || ignoredtags.search("<" + tagname + ">") > -1)
+                continue;
+
+            tag = tags[ctag];
+            match = -1;
+
+            if (!/^<\//.test(tag)) {
+                // this is an opening tag
+                // search forwards (next tags), look for closing tags
+                for (var ntag = ctag + 1; ntag < tagcount; ntag++) {
+                    if (!tagpaired[ntag] && tags[ntag] == "</" + tagname + ">") {
+                        match = ntag;
+                        break;
+                    }
+                }
+            }
+
+            if (match == -1)
+                needsRemoval = tagremove[ctag] = true; // mark for removal
+            else
+                tagpaired[match] = true; // mark paired
+        }
+
+        if (!needsRemoval)
+            return html;
+
+        // delete all orphaned tags from the string
+
+        var ctag = 0;
+        html = html.replace(re, function (match) {
+            var res = tagremove[ctag] ? "" : match;
+            ctag++;
+            return res;
+        });
+        return html;
+    }
+})();
--- a/wikked/static/js/wikked.js	Tue Dec 11 22:13:29 2012 -0800
+++ b/wikked/static/js/wikked.js	Wed Dec 19 00:23:12 2012 -0800
@@ -3,19 +3,18 @@
  */
 String.prototype.format = function() {
     var args = arguments;
-    return this.replace(/{(\d+)}/g, function(match, number) { 
-        return typeof args[number] != 'undefined'
-            ? args[number]
-            : match
-            ;
+    return this.replace(/\{(\d+)\}/g, function(match, number) { 
+        return typeof args[number] != 'undefined' ? args[number] : match;
     });
 };
 
+this.Wikked = {};
+
 /**
  * Helper class to load template files
  * by name from the `tpl` directory.
  */
-var TemplateLoader = {
+var TemplateLoader = Wikked.TemplateLoader = {
     loadedTemplates: {},
     get: function(name, callback) {
         if (name in this.loadedTemplates) {
@@ -35,6 +34,63 @@
 //-------------------------------------------------------------//
 
 /**
+ * Handlebars helper: reverse iterator.
+ */
+Handlebars.registerHelper('eachr', function(context, options) {
+    data = undefined;
+    if (options.data) {
+        data = Handlebars.createFrame(options.data);
+    }
+    var out = '';
+    for (var i=context.length - 1; i >= 0; i--) {
+        if (data !== undefined) {
+            data.index = (context.length - 1 - i);
+            data.rindex = i;
+        }
+        out += options.fn(context[i], { data: data });
+    }
+    return out;
+});
+
+/**
+ * Would you believe Handlebars doesn't have an equality
+ * operator?
+ */
+Handlebars.registerHelper('ifeq', function(context, options) {
+	if (context == options.hash.to) {
+		return options.fn(this);
+    }
+	return options.inverse(this);
+});
+
+//-------------------------------------------------------------//
+
+/**
+ * Client-side Wikked.
+ */
+var PageFormatter = Wikked.PageFormatter = {
+    formatLink: function(link) {
+        return link.toLowerCase().replace(/[^a-z0-9_\.\-\(\)\/]+/g, '-');
+    },
+    formatText: function(text) {
+        var $f = this;
+        text = text.replace(/\[\[([a-z]+)\:\s*(.+)\]\]\s*$/m, 
+            '<p class="preview-wiki-meta">$1 = $2</p>\n\n');
+        text = text.replace(/\[\[([^\|\]]+)\|([^\]]+)\]\]/g, function(m, a, b) {
+            url = $f.formatLink(b);
+            return '[' + a + '](/#/read/' + url + ')';
+        });
+        text = text.replace(/\[\[([^\]]+\/)?([^\]]+)\]\]/g, function(m, a, b) {
+            url = $f.formatLink(a + b);
+            return '[' + b + '](/#/read/' + url + ')';
+        });
+        return text;
+    }
+};
+
+//-------------------------------------------------------------//
+
+/**
  * Start the main app once the page is loaded.
  */
 $(function() {
@@ -65,11 +121,25 @@
             if (this.isNew()) return base;
             return base + (base.charAt(base.length - 1) === '/' ? '' : '/') + this.id;
         },
+        title: function() {
+            return this.getMeta('title');
+        },
+        getMeta: function(key) {
+            var meta = this.get('meta');
+            if (meta === undefined) {
+                return undefined;
+            }
+            return meta[key];
+        },
         _onChangePath: function(path) {
             this.set('url_home', '/#/read/main-page');
             this.set('url_read', '/#/read/' + path);
             this.set('url_edit', '/#/edit/' + path);
             this.set('url_hist', '/#/changes/' + path);
+            this.set('url_rev', '/#/revision/' + path);
+            this.set('url_diffc', '/#/diff/c/' + path);
+            this.set('url_diffr', '/#/diff/r/' + path);
+            this.set('url_inlinks', '/#/inlinks/' + path);
         },
         _onChangeText: function(text) {
             this.set('content', new Handlebars.SafeString(text));
@@ -92,6 +162,77 @@
         urlRoot: '/api/history/'
     });
 
+    var PageRevisionModel = PageModel.extend({
+        urlRoot: '/api/revision/',
+        idAttribute: 'path_and_rev',
+        defaults: function() {
+            return {
+                path: "main-page",
+                rev: "tip"
+            };
+        },
+        initialize: function() {
+            this.on('change:path', function(model, path) {
+                model._onChangePathOrRev(path, model.get('rev'));
+            });
+            this.on('change:rev', function(model, rev) {
+                model._onChangePathOrRev(model.get('path'), rev);
+            });
+            this._onChangePathOrRev(this.get('path'), this.get('rev'));
+            PageRevisionModel.__super__.initialize.call(this);
+        },
+        _onChangePathOrRev: function(path, rev) {
+            this.set('path_and_rev', path + '/' + rev);
+            this.set('disp_rev', rev);
+            if (rev.match(/[a-f0-9]{40}/)) {
+                this.set('disp_rev', rev.substring(0, 8));
+            }
+        }
+    });
+
+    var PageDiffModel = PageModel.extend({
+        urlRoot: '/api/diff/',
+        idAttribute: 'path_and_revs',
+        defaults: function() {
+            return {
+                path: "main-page",
+                rev1: "tip",
+                rev2: ""
+            };
+        },
+        initialize: function() {
+            this.on('change:path', function(model, path) {
+                model._onChangePathOrRevs(path, model.get('rev'));
+            });
+            this.on('change:rev1', function(model, rev1) {
+                model._onChangePathOrRevs(model.get('path'), rev1, model.get('rev2'));
+            });
+            this.on('change:rev2', function(model, rev2) {
+                model._onChangePathOrRevs(model.get('path'), model.get('rev1'), rev2);
+            });
+            this._onChangePathOrRevs(this.get('path'), this.get('rev1'), this.get('rev2'));
+            PageRevisionModel.__super__.initialize.call(this);
+        },
+        _onChangePathOrRevs: function(path, rev1, rev2) {
+            this.set('path_and_revs', path + '/' + rev1 + '/' + rev2);
+            if (!rev2) {
+                this.set('path_and_revs', path + '/' + rev1);
+            }
+            this.set('disp_rev1', rev1);
+            if (rev1 !== undefined && rev1.match(/[a-f0-9]{40}/)) {
+                this.set('disp_rev1', rev1.substring(0, 8));
+            }
+            this.set('disp_rev2', rev2);
+            if (rev2 !== undefined && rev2.match(/[a-f0-9]{40}/)) {
+                this.set('disp_rev2', rev2.substring(0, 8));
+            }
+        }
+    });
+
+    var IncomingLinksModel = PageModel.extend({
+        urlRoot: '/api/inlinks/'
+    });
+
     /**
      * Wiki page views.
      */
@@ -100,7 +241,6 @@
         initialize: function() {
             var $view = this;
             var model = new PageModel({ path: this.id });
-            console.log('Loading page: {0}'.format(model.get('path')));
             model.fetch({
                 success: function(model, response, options) {
                     TemplateLoader.get('read-page', function(src) {
@@ -109,9 +249,12 @@
                         $view.$el.html(template(template_data));
                         $('a.wiki-link[data-wiki-url]').each(function(i, el) {
                             var jel = $(el);
-                            jel.attr('href', '/#/read/' + jel.attr('data-wiki-url'));
+                            if (jel.hasClass('missing'))
+                                jel.attr('href', '/#/edit/' + jel.attr('data-wiki-url'));
+                            else
+                                jel.attr('href', '/#/read/' + jel.attr('data-wiki-url'));
                         });
-                        document.title = model.get('title');
+                        document.title = model.title();
                     });
                 },
                 error: function(model, xhr, options) {
@@ -126,7 +269,6 @@
             var stateModel = new PageStateModel({ id: this.id });
             stateModel.fetch({
                 success: function(model, response, options) {
-                    console.log('Got state: ' + model.get('state'));
                     if (model.get('state') == 'new' || model.get('state') == 'modified') {
                         TemplateLoader.get('state-warning', function(src) {
                             var template_data = model.toJSON();
@@ -151,17 +293,15 @@
         initialize: function() {
             var $view = this;
             var model = new PageEditModel({ path: this.id });
-            console.log('Loading page "{0}" for edit.'.format(model.get('path')));
             model.fetch({
                 success: function(model, response, options) {
                     TemplateLoader.get('edit-page', function(src) {
                         var template_data = model.toJSON();
                         var template = Handlebars.compile(src);
                         $view.$el.html(template(template_data));
-                        document.title = 'Editing: ' + model.get('title');
+                        document.title = 'Editing: ' + model.title();
 
                         $('#page-edit').submit(function() {
-                            console.log('Submitting: ' + $(this).serialize());
                             $view._submitText(this, model.get('path'));
                             return false;
                         });
@@ -175,7 +315,6 @@
         _submitText: function(form, path) {
             $.post('/api/edit/' + path, $(form).serialize())
                 .success(function(data) {
-                    console.log("Edit successful, navigating back to: " + path);
                     app.navigate('/read/' + path, { trigger: true });
                 })
                 .error(function() {
@@ -188,14 +327,77 @@
         initialize: function() {
             var $view = this;
             var model = new PageHistoryModel({ path: this.id });
-            console.log('Loading history for page: ' + model.get('path'));
             model.fetch({
                 success: function(model, response, options) {
                     TemplateLoader.get('history-page', function(src) {
                         var template_data = model.toJSON();
                         var template = Handlebars.compile(src);
                         $view.$el.html(template(template_data));
-                        document.title = 'Changes: ' + model.get('title');
+                        document.title = 'Changes: ' + model.title();
+
+                        $('#diff-page').submit(function() {
+                            $view._triggerDiff(this, model.get('path'));
+                            return false;
+                        });
+                    });
+                },
+                error: function() {
+                }
+            });
+            return this;
+        },
+        _triggerDiff: function(form, path) {
+            var rev1 = $('input[name=rev1]:checked', form).val();
+            var rev2 = $('input[name=rev2]:checked', form).val();
+            app.navigate('/diff/r/' + path + '/' + rev1 + '/' + rev2, { trigger: true });
+        }
+    });
+
+    var PageRevisionView = Backbone.View.extend({
+        initialize: function() {
+            var $view = this;
+            var model = new PageRevisionModel({ path: this.id, rev: this.options.rev });
+            model.fetch({
+                success: function(model, response, options) {
+                    TemplateLoader.get('revision-page', function(src) {
+                        var template_data = model.toJSON();
+                        var template = Handlebars.compile(src);
+                        $view.$el.html(template(template_data));
+                        document.title = model.title() + ' [' + model.get('rev') + ']';
+                    });
+                }
+            });
+        }
+    });
+
+    var PageDiffView = Backbone.View.extend({
+        initialize: function() {
+            var $view = this;
+            var model = new PageDiffModel({ path: this.id, rev1: this.options.rev1, rev2: this.options.rev2 });
+            model.fetch({
+                success: function(model, response, options) {
+                    TemplateLoader.get('diff-page', function(src) {
+                        var template_data = model.toJSON();
+                        var template = Handlebars.compile(src);
+                        $view.$el.html(template(template_data));
+                        document.title = model.title() + ' [' + model.get('rev1') + '-' + model.get('rev2') + ']';
+                    });
+                }
+            });
+        }
+    });
+
+    var IncomingLinksView = Backbone.View.extend({
+        initialize: function() {
+            var $view = this;
+            var model = new IncomingLinksModel({ path: this.id });
+            model.fetch({
+                success: function(model, response, options) {
+                    TemplateLoader.get('inlinks-page', function(src) {
+                        var template_data = model.toJSON();
+                        var template = Handlebars.compile(src);
+                        $view.$el.html(template(template_data));
+                        document.title = 'Incoming Links: ' + model.title();
                     });
                 },
                 error: function() {
@@ -210,10 +412,14 @@
      */
     var AppRouter = Backbone.Router.extend({
         routes: {
-            'read/*path':   "readPage",
-            '':             "readMainPage",
-            'edit/*path':   "editPage",
-            'changes/*path':"showPageHistory"
+            'read/*path':           "readPage",
+            '':                     "readMainPage",
+            'edit/*path':           "editPage",
+            'changes/*path':        "showPageHistory",
+            'inlinks/*path':        "showIncomingLinks",
+            'revision/*path/:rev':  "readPageRevision",
+            'diff/c/*path/:rev':    "showDiffWithPrevious",
+            'diff/r/*path/:rev1/:rev2':"showDiff"
         },
         readPage: function(path) {
             var page_view = new PageReadView({ id: path, el: $('#app') });
@@ -229,13 +435,29 @@
         showPageHistory: function(path) {
             var changes_view = new PageHistoryView({ id: path, el: $('#app') });
             this.navigate('/changes/' + path);
+        },
+        showIncomingLinks: function(path) {
+            var in_view = new IncomingLinksView({ id: path, el: $('#app') });
+            this.navigate('/inlinks/' + path);
+        },
+        readPageRevision: function(path, rev) {
+            var rev_view = new PageRevisionView({ id: path, rev: rev, el: $('#app') });
+            this.navigate('/revision/' + path + '/' + rev);
+        },
+        showDiffWithPrevious: function(path, rev) {
+            var diff_view = new PageDiffView({ id: path, rev1: rev, el: $('#app') });
+            this.navigate('/diff/c/' + path + '/' + rev);
+        },
+        showDiff: function(path, rev1, rev2) {
+            var diff_view = new PageDiffView({ id: path, rev1: rev1, rev2: rev2, el: $('#app') });
+            this.navigate('/diff/r/' + path + '/' + rev1 + '/' + rev2);
         }
     });
 
     /**
      * Launch!
      */
-    var app = new AppRouter;
+    var app = new AppRouter();
     Backbone.history.start();//{ pushState: true });
 });
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/wikked/static/tpl/diff-page.html	Wed Dec 19 00:23:12 2012 -0800
@@ -0,0 +1,29 @@
+<div class="wrapper">
+    <nav class="container_12">
+        <div class="grid_12">
+            <a href="{{url_read}}">Read</a>
+            <a href="{{url_hist}}">History</a>
+        </div>
+    </nav>
+    <article class="container_12">
+        <div class="page grid_12">
+            <h1>
+                {{meta.title}}
+                <span class="decorator">Diff 
+                {{#if disp_rev2}}
+                <span class="rev_id">{{disp_rev1}}</span> to <span class="rev_id">{{disp_rev2}}</span>
+                {{else}}
+                change <span class="rev_id">{{disp_rev1}}</span>
+                {{/if}}
+                </span>
+            </h1>
+            <pre><code>{{{diff}}}</code></pre>
+        </div>
+    </article>
+    <div class="meta container_12">
+        <div class="grid_12">
+            <a href="{{url_inlinks}}">Pages Linking Here</a>
+            <!-- TODO: last modified, etc. -->
+        </div>
+    </div>
+</div>
--- a/wikked/static/tpl/edit-page.html	Tue Dec 11 22:13:29 2012 -0800
+++ b/wikked/static/tpl/edit-page.html	Wed Dec 19 00:23:12 2012 -0800
@@ -1,23 +1,55 @@
-<nav>
-    <a href="{{url_read}}">Read</a>
-</nav>
-<article>
-    <h1><span class="small">Editing:</span> {{title}}</h1>
-    <form id="page-edit">
-        <div>
-            <textarea name="text">{{content}}</textarea>
+<div class="wrapper">
+    <nav>
+        <div class="container_12">
+            <a href="{{url_read}}">Read</a>
         </div>
-        <div>
-            <label for="author">Author: </label>
-            <input type="text" name="author"></input>
+    </nav>
+    <article class="container_12">
+        <div class="page grid_12 alpha omega">
+            <h1 class="grid_12 alpha"><span class="decorator">Editing:</span> {{meta.title}}</h1>
+            <form id="page-edit" class="page-edit grid_12 omega">
+                <div class="grid_12 alpha">
+                    <div id="wmd-button-bar"></div>
+                </div>
+                <div class="grid_12">
+                    <textarea id="wmd-input" name="text">{{content}}</textarea>
+                </div>
+                <div class="grid_12">
+                    <label for="author" class="grid_2 alpha">Author: </label>
+                    <input class="grid_3 suffix_9 omega" type="text" name="author"></input>
+                </div>
+                <div class="grid_12">
+                    <label for="message" class="grid_2 alpha">Message: </label>
+                    <input class="grid_3 suffix_9 omega" type="text" name="message"></input>
+                </div>
+                <div class="grid_12">
+                    <button type="submit">Save</button>
+                    <a href="{{url_read}}">Cancel</a>
+                </div>
+                <div class="grid_12 omega">
+                    <div id="wmd-preview" class="page-preview"></div>
+                </div>
+            </form>
         </div>
-        <div>
-            <label for="message">Message: </label>
-            <input type="text" name="message"></input>
-        </div>
-        <div>
-            <button type="submit">Save</button>
-            <a href="{{url_read}}">Cancel</a>
-        </div>
-    </form>
-</article>
+    </article>
+    <script type="text/javascript" src="/js/pagedown/Markdown.Converter.js"></script>
+    <script type="text/javascript" src="/js/pagedown/Markdown.Sanitizer.js"></script>
+    <script type="text/javascript" src="/js/pagedown/Markdown.Editor.js"></script>
+    <script type="text/javascript">
+        (function() {
+                var formatter = _.extend(this.Wikked.PageFormatter, {});
+                var converter = Markdown.getSanitizingConverter();
+                converter.hooks.chain("preConversion", function(text) {
+                    return formatter.formatText(text);
+                });
+                
+                //var help = function () { alert("Do you need help?"); }
+                //var options = {
+                //    helpButton: { handler: help },
+                //    strings: { quoteexample: "whatever you're quoting, put it right here" }
+                //};
+                var editor = new Markdown.Editor(converter); //TODO: pass options
+                editor.run();
+            })();
+    </script>
+</div>
--- a/wikked/static/tpl/history-page.html	Tue Dec 11 22:13:29 2012 -0800
+++ b/wikked/static/tpl/history-page.html	Wed Dec 19 00:23:12 2012 -0800
@@ -1,11 +1,38 @@
-<nav>
-    <a href="{{url_read}}">Read</a>
-</nav>
-<article>
-    <h1><span class="small">History:</span> {{title}}</h1>
-    {{#each history}}
-    <div>
-        <p>{{index}} [{{rev_id}} {{rev_hash}}] : {{description}}</p>
+<div class="wrapper">
+    <nav class="container_12">
+        <div class="grid_12">
+            <a href="{{url_read}}">Read</a>
+        </div>
+    </nav>
+    <article class="container_12">
+        <div class="page grid_12">
+            <h1>{{meta.title}} <span class="decorator">History</span></h1>
+            <form id="diff-page">
+                <table>
+                    <tr>
+                        <th>Rev.</th>
+                        <th>Date</th>
+                        <th>Author</th>
+                        <th>Comment</th>
+                        <th><button id="diff-revs">Diff.</button></th>
+                    </tr>
+                    {{#eachr history}}
+                    <tr>
+                        <td><a href="{{../url_rev}}/{{rev_hash}}">{{index}}</a></td>
+                        <td>{{timestamp}}</td>
+                        <td>{{author}}</td>
+                        <td>{{description}}</td>
+                        <td>
+                            <input type="radio" name="rev1" value="{{rev_hash}}" {{#ifeq @index to=0 }}checked="true" {{/ifeq}}/>
+                            <input type="radio" name="rev2" value="{{rev_hash}}" {{#ifeq @index to=1 }}checked="true" {{/ifeq}}/>
+                            <a href="{{../url_diffc}}/{{rev_hash}}">with previous</a>
+                        </td>
+                    </tr>
+                    {{/eachr}}
+                </table>
+            </form>
+        </div>
+    </article>
+    <div class="meta container_12">
     </div>
-    {{/each}}
-</article>
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/wikked/static/tpl/inlinks-page.html	Wed Dec 19 00:23:12 2012 -0800
@@ -0,0 +1,21 @@
+<div class="wrapper">
+    <nav class="container_12">
+        <div class="grid_12">
+            <a href="{{url_read}}">Read</a>
+        </div>
+    </nav>
+    <article class="container_12">
+        <div class="page grid_12">
+            <h1><span class="decorator">Linking To:</span> {{meta.title}}</h1>
+            {{#each in_links}}
+            <div>
+                {{#if missing}}
+                <p><a class="wiki-link missing" href="/#/edit/{{url}}">{{url}}</a></p>
+                {{else}}
+                <p><a class="wiki-link" href="/#/read/{{url}}">{{meta.title}}</a></p>
+                {{/if}}
+            </div>
+            {{/each}}
+        </div>
+    </article>
+</div>
--- a/wikked/static/tpl/read-page.html	Tue Dec 11 22:13:29 2012 -0800
+++ b/wikked/static/tpl/read-page.html	Wed Dec 19 00:23:12 2012 -0800
@@ -1,14 +1,22 @@
-<nav>
-    <a href="{{url_home}}">Home</a>
-    <a href="{{url_edit}}">Edit</a>
-    <a href="{{url_hist}}">History</a>
-    <!-- TODO: move, delete -->
-    <!-- TODO: search -->
-</nav>
-<article>
-    <h1>{{title}}</h1>
-    {{content}}
-</article>
-<div>
-    <!-- TODO: last modified, etc. -->
+<div class="wrapper">
+    <nav class="container_12">
+        <div class="grid_12">
+            <a href="{{url_home}}">Home</a>
+            <a href="{{url_edit}}">Edit</a>
+            <a href="{{url_hist}}">History</a>
+            <span>Search</span>
+        </div>
+    </nav>
+    <article class="container_12">
+        <div class="page grid_12">
+            <h1>{{meta.title}}</h1>
+            {{content}}
+        </div>
+    </article>
+    <div class="meta container_12">
+        <div class="grid_12">
+            <a href="{{url_inlinks}}">Pages Linking Here</a>
+            <!-- TODO: last modified, etc. -->
+        </div>
+    </div>
 </div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/wikked/static/tpl/revision-page.html	Wed Dec 19 00:23:12 2012 -0800
@@ -0,0 +1,18 @@
+<div class="wrapper">
+    <nav>
+        <div class="container_12">
+            <a href="{{url_read}}">Read</a>
+            <a href="{{url_hist}}">History</a>
+        </div>
+    </nav>
+    <article class="container_12">
+        <div class="page">
+            <h1>{{meta.title}} <span class="decorator">Revision <span class="rev_id">{{disp_rev}}</span></span></h1>
+            <pre><code>{{text}}</code></pre>
+        </div>
+    </article>
+    <div class="meta container_12">
+        <a href="{{url_inlinks}}">Pages Linking Here</a>
+        <!-- TODO: last modified, etc. -->
+    </div>
+</div>
--- a/wikked/templates/index.html	Tue Dec 11 22:13:29 2012 -0800
+++ b/wikked/templates/index.html	Wed Dec 19 00:23:12 2012 -0800
@@ -2,9 +2,12 @@
 <html>
     <head>
         <title>Wikked</title>
+        <link rel="stylesheet/less" type="text/css" href="/css/wikked.less"></link>
+        <script src="/js/less-1.3.1.min.js" type="text/javascript"></script>
+        <link rel="stylesheet" type="text/css" href="/css/syntax.css"></link>
     </head>
     <body>
-        <div id="app" class="page">{% block app %}{% endblock %}</div>
+        <div id="app">{% block app %}{% endblock %}</div>
         <script src="/js/jquery-1.8.3.min.js"></script>
         <script src="/js/underscore-min.js"></script>
         <script src="/js/backbone-min.js"></script>
--- a/wikked/views.py	Tue Dec 11 22:13:29 2012 -0800
+++ b/wikked/views.py	Wed Dec 19 00:23:12 2012 -0800
@@ -5,6 +5,9 @@
         jsonify
         )
 from flask.ext.login import login_required, login_user, logout_user, current_user
+from pygments import highlight
+from pygments.lexers import get_lexer_by_name
+from pygments.formatters import get_formatter_by_name
 from wikked import app, wiki
 from auth import User
 from forms import RegistrationForm, EditPageForm
@@ -12,13 +15,19 @@
 import scm
 
 
-def get_page_or_404(url):
+def get_page_or_none(url):
     try:
         page = wiki.getPage(url)
         page._ensureMeta()
         return page
     except PageNotFoundError:
-        abort(404)
+        return None
+
+def get_page_or_404(url):
+    page = get_page_or_none(url)
+    if page is not None:
+        return page
+    abort(404)
 
 
 @app.route('/')
@@ -33,53 +42,101 @@
 
 @app.route('/api/list/<path:url>')
 def api_list_pages(url):
-    page_names = wiki.getPageNames(url)
-    result = { 'pages': list(page_names) }
+    page_metas = [page.all_meta for page in wiki.getPages(url)]
+    result = { 'path': url, 'pages': list(page_metas) }
     return jsonify(result)
 
 
 @app.route('/api/read/<path:url>')
 def api_read_page(url):
     page = get_page_or_404(url)
-    result = { 'path': url, 'title': page.title, 'text': page.formatted_text }
+    result = { 'path': url, 'meta': page.all_meta, 'text': page.formatted_text }
     return jsonify(result)
 
 
 @app.route('/api/raw/<path:url>')
 def api_read_page_raw(url):
     page = get_page_or_404(url)
-    result = { 'path': url, 'title': page.title, 'text': page.raw_text }
+    result = { 'path': url, 'meta': page.all_meta, 'text': page.raw_text }
+    return jsonify(result)
+
+
+@app.route('/api/revision/<path:url>/<rev>')
+def api_read_page_rev(url, rev):
+    page = get_page_or_404(url)
+    page_rev = page.getRevision(rev)
+    meta = dict(page.all_meta, rev=rev)
+    result = { 'path': url, 'meta': meta, 'text': page_rev }
+    return jsonify(result)
+
+
+@app.route('/api/diff/<path:url>/<rev>')
+def api_diff_page_change(url, rev):
+    return api_diff_page_revs(url, rev, None)
+
+
+@app.route('/api/diff/<path:url>/<rev1>/<rev2>')
+def api_diff_page_revs(url, rev1, rev2):
+    page = get_page_or_404(url)
+    diff = page.getDiff(rev1, rev2)
+    if 'raw' not in request.args:
+        lexer = get_lexer_by_name('diff')
+        formatter = get_formatter_by_name('html')
+        diff = highlight(diff, lexer, formatter)
+    if rev2 is None:
+        meta = dict(page.all_meta, change=rev)
+    else:
+        meta = dict(page.all_meta, rev1=rev1, rev2=rev2)
+    result = { 'path': url, 'meta': meta, 'diff': diff }
     return jsonify(result)
 
 
 @app.route('/api/state/<path:url>')
 def api_get_state(url):
-    try:
-        state = wiki.getPageState(url)
-    except PageNotFoundError:
-        abort(404)
+    page = get_page_or_404(url)
+    state = page.getState()
     if state == scm.STATE_NEW:
         result = 'new'
     elif state == scm.STATE_MODIFIED:
         result = 'modified'
     elif state == scm.STATE_COMMITTED:
         result = 'committed'
-    return jsonify({ 'path': url, 'state': result })
+    return jsonify({ 'path': url, 'meta': page.all_meta, 'state': result })
 
 
 @app.route('/api/outlinks/<path:url>')
 def api_get_outgoing_links(url):
     page = get_page_or_404(url)
-    links = page.out_links
-    result = { 'path': url, 'out_links': links }
+    links = []
+    for link in page.out_links:
+        other = get_page_or_none(link)
+        if other is not None:
+            links.append({
+                'url': link,
+                'title': other.title
+                })
+        else:
+            links.append({ 'url': link, 'missing': True })
+
+    result = { 'path': url, 'meta': page.all_meta, 'out_links': links }
     return jsonify(result)
 
 
 @app.route('/api/inlinks/<path:url>')
 def api_get_incoming_links(url):
     page = get_page_or_404(url)
-    links = page.in_links
-    result = { 'path': url, 'in_links': links }
+    links = []
+    for link in page.in_links:
+        other = get_page_or_none(link)
+        if other is not None:
+            links.append({
+                'url': link,
+                'meta': other.all_meta
+                })
+        else:
+            links.append({ 'url': link, 'missing': True })
+
+    result = { 'path': url, 'meta': page.all_meta, 'in_links': links }
     return jsonify(result)
 
 
@@ -126,7 +183,7 @@
 @app.route('/api/history/<path:url>')
 def api_page_history(url):
     page = get_page_or_404(url)
-    history = wiki.getPageHistory(url)
+    history = page.getHistory()
     hist_data = []
     for i, rev in enumerate(reversed(history)):
         hist_data.append({
@@ -137,6 +194,6 @@
             'timestamp': rev.timestamp,
             'description': rev.description
             })
-    result = { 'url': url, 'title': page.title, 'history': hist_data }
+    result = { 'url': url, 'meta': page.all_meta, 'history': hist_data }
     return jsonify(result)
 
--- a/wikked/wiki.py	Tue Dec 11 22:13:29 2012 -0800
+++ b/wikked/wiki.py	Wed Dec 19 00:23:12 2012 -0800
@@ -60,23 +60,26 @@
 
         # [[display name|Whatever/PageName]]
         def repl1(m):
-            ctx.out_links.append(m.group(2))
-            return s._formatWikiLink(m.group(1), m.group(2))
+            return s._formatWikiLink(ctx, m.group(1), m.group(2))
         text = re.sub(r'\[\[([^\|\]]+)\|([^\]]+)\]\]', repl1, text)
 
         # [[Namespace/PageName]]
         def repl2(m):
             a, b = m.group(1, 2)
             url = b if a is None else (a + b)
-            ctx.out_links.append(url)
-            return s._formatWikiLink(b, url)
+            return s._formatWikiLink(ctx, b, url)
         text = re.sub(r'\[\[([^\]]+/)?([^\]]+)\]\]', repl2, text)
 
         return text
 
-    def _formatWikiLink(self, display, url):
+    def _formatWikiLink(self, ctx, display, url):
         slug = re.sub(r'[^A-Za-z0-9_\.\-\(\)/]+', '-', url.lower())
-        return '<a class="wiki-link" data-wiki-url="%s">%s</a>' % (slug, display)
+        ctx.out_links.append(slug)
+
+        css_class = 'wiki-link'
+        if not self.wiki.pageExists(slug):
+            css_class += ' missing'
+        return '<a class="%s" data-wiki-url="%s">%s</a>' % (css_class, slug, display)
 
 
 class Page(object):
@@ -110,7 +113,7 @@
     @property
     def in_links(self):
         links = []
-        for other_url in self.wiki.getPageNames():
+        for other_url in self.wiki.getPageUrls():
             if other_url == self.url:
                 continue
             other_page = Page(self.wiki, other_url)
@@ -119,6 +122,31 @@
                     links.append(other_url)
         return links
 
+    @property
+    def all_meta(self):
+        self._ensureMeta()
+        return {
+                'url': self._meta['url'],
+                'name': self._meta['name'],
+                'title': self._meta['title']
+                }
+
+    def getHistory(self):
+        self._ensureMeta()
+        return self.wiki.scm.getHistory(self._meta['path'])
+
+    def getState(self):
+        self._ensureMeta()
+        return self.wiki.scm.getState(self._meta['path'])
+
+    def getRevision(self, rev):
+        self._ensureMeta()
+        return self.wiki.scm.getRevision(self._meta['path'], rev)
+
+    def getDiff(self, rev1, rev2):
+        self._ensureMeta()
+        return self.wiki.scm.diff(self._meta['path'], rev1, rev2)
+
     def _ensureMeta(self):
         if self._meta is not None:
             return
@@ -189,12 +217,16 @@
     def root(self):
         return self.fs.root
 
-    def getPageNames(self, subdir=None):
-        return self.fs.getPageNames(subdir)
+    def getPageUrls(self, subdir=None):
+        for info in self.fs.getPageInfos(subdir):
+            yield info['url']
+
+    def getPages(self, subdir=None):
+        for url in self.getPageUrls(subdir):
+            yield Page(self, url)
 
     def getPage(self, url):
-        page = Page(self, url)
-        return page
+        return Page(self, url)
 
     def setPage(self, url, page_fields):
         if 'author' not in page_fields:
@@ -217,13 +249,8 @@
                     }
             self.scm.commit([ path ], commit_meta)
 
-    def getPageHistory(self, url):
-        path = self.fs.getPhysicalPagePath(url)
-        return self.scm.getHistory(path)
-
-    def getPageState(self, url):
-        path = self.fs.getPhysicalPagePath(url)
-        return self.scm.getState(path)
+    def pageExists(self, url):
+        return self.fs.pageExists(url)
 
     def _passthrough(self, content):
         return content