<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array">
    <added>
      <filename>static/upstream/images/wmd-buttons.png</filename>
    </added>
    <added>
      <filename>static/upstream/js/wmd-setup.js</filename>
    </added>
    <added>
      <filename>static/upstream/js/wmd.js</filename>
    </added>
  </added>
  <modified type="array">
    <modified>
      <diff>@@ -17,9 +17,14 @@ $var title: $_(&quot;Edit %(title)s&quot;, title=page.title or page.key)
         &lt;strong&gt;$_(&quot;Title&quot;)&lt;/strong&gt;
         &lt;input type=&quot;text&quot; name=&quot;title&quot; value=&quot;$page.title&quot; style=&quot;width:100%&quot; /&gt;
         &lt;br/&gt;&lt;br/&gt;
-        &lt;textarea name=&quot;body&quot; rows=&quot;15&quot; cols=&quot;80&quot;&gt;$page.body&lt;/textarea&gt;
+
+        &lt;textarea id=&quot;wmd-input&quot; name=&quot;body&quot; rows=&quot;15&quot; cols=&quot;80&quot;&gt;$page.body&lt;/textarea&gt;
         
         $:macros.EditButtons(comment=page.comment_)
         $:macros.HelpFormatting()
     &lt;/form&gt;
-&lt;/div&gt;
\ No newline at end of file
+&lt;/div&gt;
+
+&lt;script&gt;
+\$().ready(setup_wmd);
+&lt;/script&gt;
\ No newline at end of file</diff>
      <filename>openlibrary/plugins/upstream/templates/type/page/edit.html</filename>
    </modified>
    <modified>
      <diff>@@ -1,51 +1,50 @@
 $def with (page)
 
-$ _t = i18n.get_namespace('/type/user')
-$ _ = i18n.get_namespace('/mode/edit')
- 
 $var title: Editing $page.displayname
 
 &lt;script type=&quot;text/javascript&quot;&gt;
+\$().ready(setup_wmd);
+&lt;/script&gt;
+
+ 
+&lt;script type=&quot;text/javascript&quot;&gt;
 \$().ready(cloneURLs);
 &lt;/script&gt;
 
 
-    $:macros.RowButtons(page.type)
+$:macros.RowButtons(page.type)
 
 &lt;div id=&quot;contentHead&quot;&gt;
     &lt;div class=&quot;head&quot;&gt;
-        &lt;span class=&quot;tools&quot;&gt;$_.pretitle:&lt;/span&gt;
+        &lt;span class=&quot;tools&quot;&gt;$_(&quot;Currently Editing:&quot;)&lt;/span&gt;
         &lt;h1&gt;$page.displayname&lt;/h1&gt;
     &lt;/div&gt;
 &lt;/div&gt;
 
 &lt;div id=&quot;contentBody&quot;&gt;
+    &lt;div id=&quot;slickbox&quot; style=&quot;overflow: hidden&quot;&gt;
+        $:macros.TypeChanger(page.type)
+    &lt;/div&gt;
 
-&lt;div id=&quot;slickbox&quot; style=&quot;overflow: hidden&quot;&gt;
-    $:macros.TypeChanger(page.type)
-&lt;/div&gt;
-
-&lt;form method=&quot;POST&quot; class=&quot;olform&quot;&gt;
-
-    &lt;input type=&quot;hidden&quot; name=&quot;type.key&quot; id=&quot;type.key&quot; value=&quot;/type/user&quot;/&gt;
-
-    &lt;!-- WIKI EDIT SECTION --&gt;
-    $:macros.UserEditRow(page, 'displayname', True)
-    $:macros.UserEditRow(page, 'website', False)
-
-&lt;script type=&quot;text/javascript&quot;&gt;
-\$().ready(textareaExpand);
-&lt;/script&gt;
-    &lt;div class=&quot;formElement&quot;&gt;
-        &lt;div class=&quot;label&quot;&gt;&lt;label for=&quot;description&quot;&gt;$_t.description&lt;/label&gt;&lt;/div&gt;
-        &lt;div class=&quot;input&quot;&gt;
-            &lt;textarea name=&quot;description&quot; id=&quot;description&quot; class=&quot;resize&quot; rows=&quot;6&quot; cols=&quot;40&quot;&gt;$page.description&lt;/textarea&gt;
+    &lt;form method=&quot;POST&quot; class=&quot;olform&quot;&gt;
+        &lt;input type=&quot;hidden&quot; name=&quot;type.key&quot; id=&quot;type.key&quot; value=&quot;/type/user&quot;/&gt;
+
+        &lt;!-- WIKI EDIT SECTION --&gt;
+        $:macros.UserEditRow(page, 'displayname', True)
+        $:macros.UserEditRow(page, 'website', False)
+
+        &lt;script type=&quot;text/javascript&quot;&gt;
+        \$().ready(textareaExpand);
+        &lt;/script&gt;
+        
+        &lt;div class=&quot;formElement&quot;&gt;
+            &lt;div class=&quot;label&quot;&gt;&lt;label for=&quot;description&quot;&gt;$_(&quot;Description&quot;)&lt;/label&gt;&lt;/div&gt;
+            &lt;div class=&quot;input&quot;&gt;
+                &lt;textarea name=&quot;description&quot; id=&quot;wmd-input&quot; class=&quot;resize&quot; rows=&quot;6&quot; cols=&quot;40&quot;&gt;$page.description&lt;/textarea&gt;
+            &lt;/div&gt;
         &lt;/div&gt;
-    &lt;/div&gt;
+        
         &lt;!-- edit summary and buttons --&gt;
-
-             $:macros.EditButtons(comment=page.comment_)
-
-&lt;/form&gt;
-
+        $:macros.EditButtons(comment=page.comment_)
+    &lt;/form&gt;
 &lt;/div&gt;
\ No newline at end of file</diff>
      <filename>openlibrary/plugins/upstream/templates/type/user/edit.html</filename>
    </modified>
    <modified>
      <diff>@@ -13,8 +13,9 @@ JS_SOURCES=			\
     jquery.flickrfeed.js    \
     jquery.flot.js  \
     jquery.sparklines.js    \
-    jquery.markitup.js  \
-    jquery.ccopy.js
+    jquery.ccopy.js		\
+    wmd-setup.js		\
+    wmd.js
     
 
 JS_OBJECTS=$(JS_SOURCES:%=js/%)</diff>
      <filename>static/upstream/Makefile</filename>
    </modified>
    <modified>
      <diff>@@ -28,6 +28,15 @@ a:visited {color: #036;}
 .attn,.neg{color:#e44028!important;}
 .pos{color:#35672e!important;}
 .black{color:#000!important;}
+.highlight {background-color: #fffdcd;}
+.orange {color: #ffa337;}
+.brown {color: #615132;}
+.teal {color: #00636a;}
+.darkgreen {color: #35672e;}
+.lightgreen {color: #748d36;}
+.blue {color: #036daa;}
+.red {color: #e44028;}
+.yellow {color: #fffdcd;}
 a.plain {text-decoration:none!important;}
 .right{float:right;margin:0 0 8px 8px;}
 .left{float:left;margin:0 8px 8px 0;}
@@ -125,6 +134,22 @@ div.formElement label {
 div.formElement .input label {
     font-size: 1.0em;
 }
+div.formElement .tip {
+    font-size:0.6875em;
+    color:#666;
+    font-family:&quot;Lucida Grande&quot;, Verdana, Geneva, Helvetica, Arial, sans-serif!important;
+}
+div.formElement .footnote {
+    border-top: 1px solid #ccc;
+    padding: 5px;
+}
+div.formElement blockquote {
+    font-family: &quot;Palatino Linotype&quot;, &quot;Book Antiqua&quot;, Palatino, Georgia, serif;
+    font-size: 0.875em;
+    margin: 1em 40px;
+    line-height: 1.5em;
+}
+
 
 /* EDITING */
 body#edit div#placement,body#edit div#position,body#edit div#header,body#edit div#content {width:100%;}
@@ -369,6 +394,42 @@ form.oledit select {
     padding: 3px;
 }
 
+/* EDIT/ADD BOOK */
+form.olform.books .input,
+form.olform.books .label {
+    width: auto!important;
+}
+.TitleAuthor {
+	margin: 30px auto 61px;
+	width: 875px;
+}
+.TitleAuthor input#work-title {
+    font-size: 1.875em; width: 500px; margin: 10px 0 0 0;
+}
+.TitleAuthor input#author {
+    font-size: 1.875em; width: 300px; margin: 10px 0 0 0;
+}
+#tabsAddbook {
+    margin-bottom: 30px;
+}
+fieldset {position:relative;padding:20px 0; margin:10px 0;}
+fieldset.minor {display:block;float:left;width:400px;margin-top:40px;}
+fieldset.major legend {
+    padding:5px 0;font-family:&quot;News Gothic MT&quot;,&quot;Arial Rounded MT&quot;,Geneva,Helvetica,sans-serif;color:#00636a;font-weight:600;font-size:1.375em;
+}
+fieldset.minor legend {
+    font-family:&quot;News Gothic MT&quot;,&quot;Arial Rounded MT&quot;,Geneva,Helvetica,sans-serif;color:#00636a;font-weight:600;font-size:1.125em;
+}
+.formBack {float:left;padding:10px;background-color:#f5f5f5;}
+div.formDivider {float:left;width:30px;min-height:200px;}
+.identifiers td {
+	color: #333333;
+	border-bottom: 1px solid #ddd;
+	background-color: #f3f3f3;
+	padding: 5px;
+}
+#cover {position:absolute;top:50px;right:20px;border:2px solid #615132;background:#fff;width:120px;height:180px;text-align:center;line-height:1.5em;}
+
 /* PAGE TOOLS (ICONS) */
 div#tools {float:right;margin:0 0 20px;position:relative;}
 div#tools.edit {position:absolute;right:30px;top:20px;}
@@ -721,6 +782,20 @@ ul.urlList span.date {
     font-size: 0.6875em;
     color: #999;
 }
+.formElement ul.authorlinks {
+    margin-left: 20px;
+}
+.formElement ul.authorlinks li {
+    list-style-type: disc;
+    width: 475px; 
+    border: 1px solid #fff;
+    padding: 3px;
+}
+.formElement ul.authorlinks li:hover {
+    cursor: pointer;
+    color: #036daa;
+}
+
 
 /* HISTORY */
 
@@ -956,14 +1031,14 @@ div#editionsCovers a {text-decoration: none;}
     The following rules are the styles that are consistant between themes.
     Avoid changing this area to maintain compatability with future versions of ColorBox.
 */
-#colorbox, #cboxOverlay, #cboxWrapper{position:absolute; top:0; left:0; z-index:9999; overflow:hidden;}
+#colorbox, #cboxOverlay, #cboxWrapper{position:absolute; top:0; left:0; z-index:9999;}
 #cboxOverlay{position:fixed; width:100%; height:100%;}
 #cboxMiddleLeft, #cboxBottomLeft{clear:left;}
-#cboxContent{position:relative; overflow:hidden;}
-#cboxLoadedContent{overflow:auto;}
+#cboxContent{position:relative;}
+#cboxLoadedContent{overflow:visible;}
 #cboxLoadedContent iframe{display:block; width:100%; height:100%; border:0;}
 #cboxTitle{margin:0;}
-#cboxLoadingOverlay, #cboxLoadingGraphic{position:absolute; top:0; left:0; width:100%;}
+#cboxLoadingOverlay, #cboxLoadingGraphic{position:absolute; top:25px; left:25px; width:100%;}
 #cboxPrevious, #cboxNext, #cboxClose, #cboxSlideshow{cursor:pointer;}
 
 /* 
@@ -971,26 +1046,22 @@ div#editionsCovers a {text-decoration: none;}
     The following rules are ordered and tabbed in a way that represents the
     order/nesting of the generated HTML, so that the structure easier to understand.
 */
-#cboxOverlay{background:#fff;}
+#cboxOverlay{background:#000;}
 
 #colorBox{}
-    #cboxTopLeft{width:25px; height:25px; background:url(images/border1.png) 0 0 no-repeat;}
-    #cboxTopCenter{height:25px; background:url(images/border1.png) 0 -50px repeat-x;}
-    #cboxTopRight{width:25px; height:25px; background:url(images/border1.png) -25px 0 no-repeat;}
-    #cboxBottomLeft{width:25px; height:25px; background:url(images/border1.png) 0 -25px no-repeat;}
-    #cboxBottomCenter{height:25px; background:url(images/border1.png) 0 -75px repeat-x;}
-    #cboxBottomRight{width:25px; height:25px; background:url(images/border1.png) -25px -25px no-repeat;}
-    #cboxMiddleLeft{width:25px; background:url(images/border2.png) 0 0 repeat-y;}
-    #cboxMiddleRight{width:25px; background:url(images/border2.png) -25px 0 repeat-y;}
+    #cboxTopLeft{width:25px; height:25px; background:url(/images/border1.png) 0 0 no-repeat;}
+    #cboxTopCenter{height:25px; background:url(/images/border1.png) 0 -50px repeat-x;}
+    #cboxTopRight{width:25px; height:25px; background:url(/images/border1.png) -25px 0 no-repeat;}
+    #cboxBottomLeft{width:25px; height:25px; background:url(/images/border1.png) 0 -25px no-repeat;}
+    #cboxBottomCenter{height:25px; background:url(/images/border1.png) 0 -75px repeat-x;}
+    #cboxBottomRight{width:25px; height:25px; background:url(/images/border1.png) -25px -25px no-repeat;}
+    #cboxMiddleLeft{width:25px; background:url(/images/border2.png) 0 0 repeat-y;}
+    #cboxMiddleRight{width:25px; background:url(/images/border2.png) -25px 0 repeat-y;}
     #cboxContent{background:#fff;}
-        #cboxLoadedContent{margin-bottom:20px;}
-        #cboxTitle{position:absolute; bottom:0px; left:0; text-align:center; width:100%; color:#999;}
-        #cboxCurrent{position:absolute; bottom:0px; left:100px; color:#999;}
-        #cboxSlideshow{position:absolute; bottom:0px; right:42px; color:#444;}
-        #cboxPrevious{position:absolute; bottom:0px; left:0; color:#444;}
-        #cboxNext{position:absolute; bottom:0px; left:63px; color:#444;}
-        #cboxLoadingOverlay{background:url(images/loading.gif) 5px 5px no-repeat #fff;}
-        #cboxClose{position:absolute; bottom:0; right:0; display:block; color:#444;}
+        #cboxLoadedContent{background:#fff;margin:0;}
+        #cboxLoadingOverlay{background:#fff;}
+        #cboxClose{position:absolute;top:20px;right:20px;display:block;width:32px;height:32px;background-image:url(/images/icons/icon_close-pop.png);background-position:0 0;background-repeat:no-repeat;}
+        #cboxClose:hover{background-position:0 -32px;}
 
 /*
     The following fixes png-transparency for IE6.  
@@ -1002,14 +1073,57 @@ div#editionsCovers a {text-decoration: none;}
     !! Important Note: AlphaImageLoader src paths are relative to the HTML document,
     while regular CSS background images are relative to the CSS document.
 */
-.cboxIE #cboxTopLeft{background:transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=images/internet_explorer/borderTopLeft.png, sizingMethod='scale');}
-.cboxIE #cboxTopCenter{background:transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=images/internet_explorer/borderTopCenter.png, sizingMethod='scale');}
-.cboxIE #cboxTopRight{background:transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=images/internet_explorer/borderTopRight.png, sizingMethod='scale');}
-.cboxIE #cboxBottomLeft{background:transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=images/internet_explorer/borderBottomLeft.png, sizingMethod='scale');}
-.cboxIE #cboxBottomCenter{background:transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=images/internet_explorer/borderBottomCenter.png, sizingMethod='scale');}
-.cboxIE #cboxBottomRight{background:transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=images/internet_explorer/borderBottomRight.png, sizingMethod='scale');}
-.cboxIE #cboxMiddleLeft{background:transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=images/internet_explorer/borderMiddleLeft.png, sizingMethod='scale');}
-.cboxIE #cboxMiddleRight{background:transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=images/internet_explorer/borderMiddleRight.png, sizingMethod='scale');}
+.cboxIE #cboxTopLeft{background:transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=/images/internet_explorer/borderTopLeft.png, sizingMethod='scale');}
+.cboxIE #cboxTopCenter{background:transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=/images/internet_explorer/borderTopCenter.png, sizingMethod='scale');}
+.cboxIE #cboxTopRight{background:transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=/images/internet_explorer/borderTopRight.png, sizingMethod='scale');}
+.cboxIE #cboxBottomLeft{background:transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=/images/internet_explorer/borderBottomLeft.png, sizingMethod='scale');}
+.cboxIE #cboxBottomCenter{background:transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=/images/internet_explorer/borderBottomCenter.png, sizingMethod='scale');}
+.cboxIE #cboxBottomRight{background:transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=/images/internet_explorer/borderBottomRight.png, sizingMethod='scale');}
+.cboxIE #cboxMiddleLeft{background:transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=/images/internet_explorer/borderMiddleLeft.png, sizingMethod='scale');}
+.cboxIE #cboxMiddleRight{background:transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=/images/internet_explorer/borderMiddleRight.png, sizingMethod='scale');}
+
+
+div.floater {
+    position: relative;
+    font-family: &quot;News Gothic MT&quot;,&quot;Arial Rounded MT&quot;,Geneva,Helvetica,sans-serif;
+    width: 640px;
+    height: 480px;
+    background: #fff;
+    text-align: left;
+}
+div.floaterAlert {
+    position: absolute;
+    width: 580px;
+    height: 0;
+    top: -12px;
+    left: 30px;
+    background: #e44028 url(/images/icons/icon_alert-pop.png) no-repeat 12px 12px;
+    -webkit-border-top-right-radius: 15px;
+    -webkit-border-top-left-radius: 15px;
+    -moz-border-radius-topright: 15px;
+    -moz-border-radius-topleft: 15px;
+    border-top-right-radius: 15px;
+    border-top-left-radius: 15px;
+	-moz-box-shadow: 0 -1px 5px #333;
+ 	-webkit-box-shadow: 0 -1px 5px #333;
+ 	box-shadow: 0 -1px 5px #333;
+    font-size: 0.875em;
+    color: #fff;
+    text-align: center;
+}
+div.floater a#coverShut {position:absolute;top:10px;right:10px;display:block;width:32px;height:32px;background-image:url(/images/icons/icon_close-pop.png);background-position:0 0;background-repeat:no-repeat;}
+div.floater a#coverShut:hover {background-position:0 -32px;}
+div.floater h2 {position:absolute;top:50px;left:50px;}
+div#addCover div#coverIntro {position:absolute;top:83px;left:50px;font-size:0.875em;}
+div#addCover label#coverBrowse {position:absolute;top:118px;left:50px;font-weight:700;}
+div#addCover input#coverFile {position:absolute;top:157px;left:78px;font-size:1.125em;}
+div#addCover label#coverWeb {position:absolute;top:210px;left:50px;font-weight: 700;}
+div#addCover input#coverUrl {position:absolute;top:256px;left:78px;font-size:1.0em;width:350px;padding:2px;}
+div#addCover button#coverUpload {position:absolute;top:322px;left:78px;font-size:1.125em;}
+div#addCover a#coverClose {position:absolute;top:327px;left:180px;font-size:1.0em;color:#e44028;}
+div#addCover div#coverLegal {position:absolute;bottom:20px;left:20px;font-size:0.6875em;text-transform:uppercase;color:#615132;line-height:normal;font-weight:700;width:600px;}
+
+
 
 /* ADMIN */
 body#admin div#contentHead {
@@ -1166,7 +1280,10 @@ div.moduleSide h4 {
     text-transform: uppercase;
     margin-bottom: 5px;
 }
-div.moduleSide a.link {
+div.moduleSide p {
+    margin: 0!important;
+}
+div.moduleSide a {
     display: block;
     font-family: &quot;News Gothic MT&quot;,&quot;Trebuchet MS&quot;,Helvetica,sans-serif;
     font-size: 0.875em;
@@ -1208,4 +1325,145 @@ div#publicTwitter div.tweet_cell{width:205px;}
 div#publicTwitter div.tweet_avatar{width:35px;}
 div#publicTwitter div.tweet_copy{width:170px;}
 div#publicTwitter div.tweet_cell {float:left;font-size:0.75em;}
-div#publicTwitter a.tweetFeed {font-size:0.75em;}
\ No newline at end of file
+div#publicTwitter a.tweetFeed {font-size:0.75em;}
+
+.wmd-panel
+{
+	margin-left: 25%;
+	margin-right: 25%;
+	width: 50%;
+	min-width: 500px;
+}
+
+#wmd-editor
+{
+	background-color: Aquamarine;
+}
+
+#wmd-button-bar 
+{
+	width: 100%;
+	background-color: Silver; 
+}
+
+#wmd-input 
+{ 
+	height: 500px;
+	width: 100%;
+	background-color: Gainsboro;
+	border: 1px solid DarkGray;
+}
+
+#wmd-preview 
+{ 
+	background-color: LightSkyBlue; 
+}
+
+#wmd-output 
+{ 
+	background-color: Pink;
+}
+
+#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; 
+	margin-left: 5px;
+	margin-right: 5px;
+	
+	position: absolute;
+	background-image: url(images/wmd-buttons.png);
+	background-repeat: no-repeat;
+	background-position: 0px 0px;
+	display: inline-block; 
+	list-style: none;
+}
+
+.wmd-button &gt; a
+{
+	width: 20px; 
+	height: 20px; 
+	margin-left: 5px;
+	margin-right: 5px;
+	
+	position: absolute;
+	display: inline-block; 
+}
+
+
+/* sprite button slicing style information */
+#wmd-button-bar #wmd-bold-button    {left: 0px;   background-position: 0px 0;}
+#wmd-button-bar #wmd-italic-button  {left: 25px;  background-position: -20px 0;}
+#wmd-button-bar #wmd-spacer1        {left: 50px;}
+#wmd-button-bar #wmd-link-button    {left: 75px;  background-position: -40px 0;}
+#wmd-button-bar #wmd-quote-button   {left: 100px;  background-position: -60px 0;}
+#wmd-button-bar #wmd-code-button    {left: 125px;  background-position: -80px 0;}
+#wmd-button-bar #wmd-image-button   {left: 150px;  background-position: -100px 0;}
+#wmd-button-bar #wmd-spacer2        {left: 175px;}
+#wmd-button-bar #wmd-olist-button   {left: 200px;  background-position: -120px 0;}
+#wmd-button-bar #wmd-ulist-button   {left: 225px; background-position: -140px 0;}
+#wmd-button-bar #wmd-heading-button {left: 250px; background-position: -160px 0;}
+#wmd-button-bar #wmd-hr-button      {left: 275px; background-position: -180px 0;}
+#wmd-button-bar #wmd-spacer3        {left: 300px;}
+#wmd-button-bar #wmd-undo-button    {left: 325px; background-position: -200px 0;}
+#wmd-button-bar #wmd-redo-button    {left: 350px; background-position: -220px 0;}
+#wmd-button-bar #wmd-help-button    {right: 0px; background-position: -240px 0;}
+
+
+.wmd-prompt-background
+{
+	background-color: Black;
+}
+
+.wmd-prompt-dialog
+{
+	border: 1px solid #999999;
+	background-color: #F5F5F5;
+}
+
+.wmd-prompt-dialog &gt; div {
+	font-size: 0.8em;
+	font-family: arial, helvetica, sans-serif;
+}
+
+
+.wmd-prompt-dialog &gt; form &gt; input[type=&quot;text&quot;] {
+	border: 1px solid #999999;
+	color: black;
+}
+
+.wmd-prompt-dialog &gt; form &gt; input[type=&quot;button&quot;]{
+	border: 1px solid #888888;
+	font-family: trebuchet MS, helvetica, sans-serif;
+	font-size: 0.8em;
+	font-weight: bold;
+}
+
+#wmd-input { background: white; }
+#wmd-button-bar { background: white; }
+.wmd-button {
+	background-image: url(/images/wmd-buttons.png);
+}</diff>
      <filename>static/upstream/css/master.css</filename>
    </modified>
    <modified>
      <diff>@@ -599,3 +599,2401 @@ function validateForms(){
 });
 };
  
+
+wmd_options = {&quot;autostart&quot;: false};
+
+// WMD setup for OpenLibrary. This script should be loaded before loading wmd.js
+
+function setup_wmd() {
+    Attacklab.panels = new Attacklab.PanelCollection();
+    
+    $(&quot;&lt;div id='wmd-button-bar'&gt;&lt;/div&gt;&quot;).insertBefore(&quot;#wmd-input&quot;);
+
+    var previewMgr = new Attacklab.previewManager();
+    var edit = new Attacklab.editor(previewMgr.refresh);
+    previewMgr.refresh(true);
+    
+    // change title and url of help link.
+    $(&quot;#wmd-help-button a&quot;)
+        .attr(&quot;href&quot;, &quot;/help/markdown&quot;)
+        .attr('title', &quot;Markdown Help&quot;);
+}
+
+var Attacklab = Attacklab || {};
+
+Attacklab.wmdBase = function(){
+
+	// A few handy aliases for readability.
+	var wmd  = top.Attacklab;
+	var doc  = top.document;
+	var re   = top.RegExp;
+	var nav  = top.navigator;
+	
+	// Some namespaces.
+	wmd.Util = {};
+	wmd.Position = {};
+	wmd.Command = {};
+	wmd.Global = {};
+	
+	var util = wmd.Util;
+	var position = wmd.Position;
+	var command = wmd.Command;
+	var global = wmd.Global;
+	
+	
+	// Used to work around some browser bugs where we can't use feature testing.
+	global.isIE 		= /msie/.test(nav.userAgent.toLowerCase());
+	global.isIE_5or6 	= /msie 6/.test(nav.userAgent.toLowerCase()) || /msie 5/.test(nav.userAgent.toLowerCase());
+	global.isIE_7plus 	= global.isIE &amp;&amp; !global.isIE_5or6;
+	global.isOpera 		= /opera/.test(nav.userAgent.toLowerCase());
+	global.isKonqueror 	= /konqueror/.test(nav.userAgent.toLowerCase());
+	
+	
+	// -------------------------------------------------------------------
+	//  YOUR CHANGES GO HERE
+	//
+	// I've tried to localize the things you are likely to change to 
+	// this area.
+	// -------------------------------------------------------------------
+	
+	// The text that appears on the upper part of the dialog box when
+	// entering links.
+	var imageDialogText = &quot;&lt;p style='margin-top: 0px'&gt;&lt;b&gt;Enter the image URL.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;You can also add a title, which will be displayed as a tool tip.&lt;/p&gt;&lt;p&gt;Example:&lt;br /&gt;http://wmd-editor.com/images/cloud1.jpg   \&quot;Optional title\&quot;&lt;/p&gt;&quot;;
+	var linkDialogText = &quot;&lt;p style='margin-top: 0px'&gt;&lt;b&gt;Enter the web address.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;You can also add a title, which will be displayed as a tool tip.&lt;/p&gt;&lt;p&gt;Example:&lt;br /&gt;http://wmd-editor.com/   \&quot;Optional title\&quot;&lt;/p&gt;&quot;;
+	
+	// The default text that appears in the dialog input box when entering
+	// links.
+	var imageDefaultText = &quot;http://&quot;;
+	var linkDefaultText = &quot;http://&quot;;
+	
+	// The location of your button images relative to the base directory.
+	var imageDirectory = &quot;images/&quot;;
+	
+	// Some intervals in ms.  These can be adjusted to reduce the control's load.
+	var previewPollInterval = 500;
+	var pastePollInterval = 100;
+	
+	// The link and title for the help button
+	var helpLink = &quot;http://wmd-editor.com/&quot;;
+	var helpHoverTitle = &quot;WMD website&quot;;
+	var helpTarget = &quot;_blank&quot;;
+	
+	// -------------------------------------------------------------------
+	//  END OF YOUR CHANGES
+	// -------------------------------------------------------------------
+	
+	// A collection of the important regions on the page.
+	// Cached so we don't have to keep traversing the DOM.
+	wmd.PanelCollection = function(){
+		this.buttonBar = doc.getElementById(&quot;wmd-button-bar&quot;);
+		this.preview = doc.getElementById(&quot;wmd-preview&quot;);
+		this.output = doc.getElementById(&quot;wmd-output&quot;);
+		this.input = doc.getElementById(&quot;wmd-input&quot;);
+	};
+	
+	// This PanelCollection object can't be filled until after the page
+	// has loaded.
+	wmd.panels = undefined;
+	
+	// Internet explorer has problems with CSS sprite buttons that use HTML
+	// lists.  When you click on the background image &quot;button&quot;, 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.
+	wmd.ieCachedRange = null;		// cached textarea selection
+	wmd.ieRetardedClick = false;	// flag
+	
+	// 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(&quot;display&quot;) !== &quot;none&quot;;
+		}
+		else if (elem.currentStyle) {
+		    // IE
+			return elem.currentStyle[&quot;display&quot;] !== &quot;none&quot;;
+		}
+	};
+	
+	
+	// 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 &quot;on&quot; is mandatory.
+			elem.attachEvent(&quot;on&quot; + 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 &quot;on&quot; is mandatory.
+			elem.detachEvent(&quot;on&quot; + 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, &quot;\n&quot;);
+		text = text.replace(/\r/g, &quot;\n&quot;);
+		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 &quot;$&quot; 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 = &quot;&quot;;
+		}
+		if(post === null || post === undefined)
+		{
+			post = &quot;&quot;;
+		}
+		
+		var pattern = regex.toString();
+		var flags = &quot;&quot;;
+		
+		// Replace the flags with empty space and store them.
+		// Technically, this can match incorrect flags like &quot;gmm&quot;.
+		var result = pattern.match(/\/([gim]*)$/);
+		if (result === null) {
+			flags = result[0];
+		}
+		else {
+			flags = &quot;&quot;;
+		}
+		
+		// Remove the flags and slash delimiters from the regular expression.
+		pattern = pattern.replace(/(^\/|\/[gim]*$)/g, &quot;&quot;);
+		pattern = pre + pattern + post;
+		
+		return new RegExp(pattern, flags);
+	}
+
+	
+	// Sets the image for a button passed to the WMD editor.
+	// Returns a new element with the image attached.
+	// Adds several style properties to the image.
+	util.createImage = function(img){
+		
+		var imgPath = imageDirectory + img;
+		
+		var elem = doc.createElement(&quot;img&quot;);
+		elem.className = &quot;wmd-button&quot;;
+		elem.src = imgPath;
+
+		return elem;
+	};
+	
+
+	// 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.
+	// makeLinkMarkdown: The function which is executed when the prompt is dismissed, either via OK or Cancel
+	util.prompt = function(text, defaultInputText, makeLinkMarkdown){
+	
+		// These variables need to be declared at this level since they are used
+		// in multiple functions.
+		var dialog;			// The dialog box.
+		var background;		// The background beind the dialog box.
+		var input;			// The text box where you enter the hyperlink.
+		
+
+		if (defaultInputText === undefined) {
+			defaultInputText = &quot;&quot;;
+		}
+		
+		// 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, &quot;keydown&quot;, checkEscape);
+			var text = input.value;
+
+			if (isCancel){
+				text = null;
+			}
+			else{
+				// Fixes common pasting errors.
+				text = text.replace('http://http://', 'http://');
+				text = text.replace('http://https://', 'https://');
+				text = text.replace('http://ftp://', 'ftp://');
+				
+				if (text.indexOf('http://') === -1 &amp;&amp; text.indexOf('ftp://') === -1 &amp;&amp; text.indexOf('https://') === -1) {
+					text = 'http://' + text;
+				}
+			}
+			
+			dialog.parentNode.removeChild(dialog);
+			background.parentNode.removeChild(background);
+			makeLinkMarkdown(text);
+			return false;
+		};
+		
+		// Creates the background behind the hyperlink text entry box.
+		// Most of this has been moved to CSS but the div creation and
+		// browser-specific hacks remain here.
+		var createBackground = function(){
+		
+			background = doc.createElement(&quot;div&quot;);
+			background.className = &quot;wmd-prompt-background&quot;;
+			style = background.style;
+			style.position = &quot;absolute&quot;;
+			style.top = &quot;0&quot;;
+			
+			style.zIndex = &quot;1000&quot;;
+			
+			// Some versions of Konqueror don't support transparent colors
+			// so we make the whole window transparent.
+			//
+			// Is this necessary on modern konqueror browsers?
+			if (global.isKonqueror){
+				style.backgroundColor = &quot;transparent&quot;;
+			}
+			else if (global.isIE){
+				style.filter = &quot;alpha(opacity=50)&quot;;
+			}
+			else {
+				style.opacity = &quot;0.5&quot;;
+			}
+			
+			var pageSize = position.getPageSize();
+			style.height = pageSize[1] + &quot;px&quot;;
+			
+			if(global.isIE){
+				style.left = doc.documentElement.scrollLeft;
+				style.width = doc.documentElement.clientWidth;
+			}
+			else {
+				style.left = &quot;0&quot;;
+				style.width = &quot;100%&quot;;
+			}
+			
+			doc.body.appendChild(background);
+		};
+		
+		// Create the text input box form/window.
+		var createDialog = function(){
+		
+			// The main dialog box.
+			dialog = doc.createElement(&quot;div&quot;);
+			dialog.className = &quot;wmd-prompt-dialog&quot;;
+			dialog.style.padding = &quot;10px;&quot;;
+			dialog.style.position = &quot;fixed&quot;;
+			dialog.style.width = &quot;400px&quot;;
+			dialog.style.zIndex = &quot;1001&quot;;
+			
+			// The dialog text.
+			var question = doc.createElement(&quot;div&quot;);
+			question.innerHTML = text;
+			question.style.padding = &quot;5px&quot;;
+			dialog.appendChild(question);
+			
+			// The web form container for the text box and buttons.
+			var form = doc.createElement(&quot;form&quot;);
+			form.onsubmit = function(){ return close(false); };
+			style = form.style;
+			style.padding = &quot;0&quot;;
+			style.margin = &quot;0&quot;;
+			style.cssFloat = &quot;left&quot;;
+			style.width = &quot;100%&quot;;
+			style.textAlign = &quot;center&quot;;
+			style.position = &quot;relative&quot;;
+			dialog.appendChild(form);
+			
+			// The input text box
+			input = doc.createElement(&quot;input&quot;);
+			input.type = &quot;text&quot;;
+			input.value = defaultInputText;
+			style = input.style;
+			style.display = &quot;block&quot;;
+			style.width = &quot;80%&quot;;
+			style.marginLeft = style.marginRight = &quot;auto&quot;;
+			form.appendChild(input);
+			
+			// The ok button
+			var okButton = doc.createElement(&quot;input&quot;);
+			okButton.type = &quot;button&quot;;
+			okButton.onclick = function(){ return close(false); };
+			okButton.value = &quot;OK&quot;;
+			style = okButton.style;
+			style.margin = &quot;10px&quot;;
+			style.display = &quot;inline&quot;;
+			style.width = &quot;7em&quot;;
+
+			
+			// The cancel button
+			var cancelButton = doc.createElement(&quot;input&quot;);
+			cancelButton.type = &quot;button&quot;;
+			cancelButton.onclick = function(){ return close(true); };
+			cancelButton.value = &quot;Cancel&quot;;
+			style = cancelButton.style;
+			style.margin = &quot;10px&quot;;
+			style.display = &quot;inline&quot;;
+			style.width = &quot;7em&quot;;
+
+			// The order of these buttons is different on macs.
+			if (/mac/.test(nav.platform.toLowerCase())) {
+				form.appendChild(cancelButton);
+				form.appendChild(okButton);
+			}
+			else {
+				form.appendChild(okButton);
+				form.appendChild(cancelButton);
+			}
+
+			util.addEvent(doc.body, &quot;keydown&quot;, checkEscape);
+			dialog.style.top = &quot;50%&quot;;
+			dialog.style.left = &quot;50%&quot;;
+			dialog.style.display = &quot;block&quot;;
+			if(global.isIE_5or6){
+				dialog.style.position = &quot;absolute&quot;;
+				dialog.style.top = doc.documentElement.scrollTop + 200 + &quot;px&quot;;
+				dialog.style.left = &quot;50%&quot;;
+			}
+			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) + &quot;px&quot;;
+			dialog.style.marginLeft = -(position.getWidth(dialog) / 2) + &quot;px&quot;;
+			
+		};
+		
+		createBackground();
+		
+		// Why is this in a zero-length timeout?
+		// Is it working around a browser bug?
+		top.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(&quot;character&quot;, -defTextLen);
+				range.moveEnd(&quot;character&quot;, defTextLen);
+				range.select();
+			}
+			
+			input.focus();
+		}, 0);
+	};
+	
+	
+	// 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 &amp;&amp; self.scrollMaxY){
+			scrollWidth = doc.body.scrollWidth;
+			scrollHeight = self.innerHeight + self.scrollMaxY;
+		}
+		else if(doc.body.scrollHeight &gt; 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 &amp;&amp; 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];
+	};
+	
+	// Watches the input textarea, polling at an interval and runs
+	// a callback function if anything has changed.
+	wmd.inputPoller = function(callback, interval){
+	
+		var pollerObj = this;
+		var inputArea = wmd.panels.input;
+		
+		// Stored start, end and text.  Used to see if there are changes to the input.
+		var lastStart;
+		var lastEnd;
+		var markdown;
+		
+		var killHandle; // Used to cancel monitoring on destruction.
+		// Checks to see if anything has changed in the textarea.
+		// If so, it runs the callback.
+		this.tick = function(){
+		
+			if (!util.isVisible(inputArea)) {
+				return;
+			}
+			
+			// Update the selection start and end, text.
+			if (inputArea.selectionStart || inputArea.selectionStart === 0) {
+				var start = inputArea.selectionStart;
+				var end = inputArea.selectionEnd;
+				if (start != lastStart || end != lastEnd) {
+					lastStart = start;
+					lastEnd = end;
+					
+					if (markdown != inputArea.value) {
+						markdown = inputArea.value;
+						return true;
+					}
+				}
+			}
+			return false;
+		};
+		
+		
+		var doTickCallback = function(){
+		
+			if (!util.isVisible(inputArea)) {
+				return;
+			}
+			
+			// If anything has changed, call the function.
+			if (pollerObj.tick()) {
+				callback();
+			}
+		};
+		
+		// Set how often we poll the textarea for changes.
+		var assignInterval = function(){
+			// previewPollInterval is set at the top of the namespace.
+			killHandle = top.setInterval(doTickCallback, interval);
+		};
+		
+		this.destroy = function(){
+			top.clearInterval(killHandle);
+		};
+		
+		assignInterval();
+	};
+	
+	// Handles pushing and popping TextareaStates for undo/redo commands.
+	// I should rename the stack variables to list.
+	wmd.undoManager = function(callback){
+	
+		var undoObj = this;
+		var undoStack = []; // A stack of undo states
+		var stackPtr = 0; // The index of the current state
+		var mode = &quot;none&quot;;
+		var lastState; // The last state
+		var poller;
+		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 (!global.isIE || mode != &quot;moving&quot;) {
+				timer = top.setTimeout(refreshState, 1);
+			}
+			else {
+				inputStateObj = null;
+			}
+		};
+		
+		var refreshState = function(){
+			inputStateObj = new wmd.TextareaState();
+			poller.tick();
+			timer = undefined;
+		};
+		
+		this.setCommandMode = function(){
+			mode = &quot;command&quot;;
+			saveState();
+			timer = top.setTimeout(refreshState, 0);
+		};
+		
+		this.canUndo = function(){
+			return stackPtr &gt; 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 wmd.TextareaState();
+					undoStack[--stackPtr].restore();
+					
+					if (callback) {
+						callback();
+					}
+				}
+			}
+			
+			mode = &quot;none&quot;;
+			wmd.panels.input.focus();
+			refreshState();
+		};
+		
+		// Redo an action.
+		this.redo = function(){
+		
+			if (undoObj.canRedo()) {
+			
+				undoStack[++stackPtr].restore();
+				
+				if (callback) {
+					callback();
+				}
+			}
+			
+			mode = &quot;none&quot;;
+			wmd.panels.input.focus();
+			refreshState();
+		};
+		
+		// Push the input area state to the stack.
+		var saveState = function(){
+		
+			var currState = inputStateObj || new wmd.TextareaState();
+			
+			if (!currState) {
+				return false;
+			}
+			if (mode == &quot;moving&quot;) {
+				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) {
+				
+					case &quot;y&quot;:
+						undoObj.redo();
+						handled = true;
+						break;
+						
+					case &quot;z&quot;:
+						if (!event.shiftKey) {
+							undoObj.undo();
+						}
+						else {
+							undoObj.redo();
+						}
+						handled = true;
+						break;
+				}
+			}
+			
+			if (handled) {
+				if (event.preventDefault) {
+					event.preventDefault();
+				}
+				if (top.event) {
+					top.event.returnValue = false;
+				}
+				return;
+			}
+		};
+		
+		// Set the mode depending on what is going on in the input area.
+		var handleModeChange = function(event){
+		
+			if (!event.ctrlKey &amp;&amp; !event.metaKey) {
+			
+				var keyCode = event.keyCode;
+				
+				if ((keyCode &gt;= 33 &amp;&amp; keyCode &lt;= 40) || (keyCode &gt;= 63232 &amp;&amp; keyCode &lt;= 63235)) {
+					// 33 - 40: page up/dn and arrow keys
+					// 63232 - 63235: page up/dn and arrow keys on safari
+					setMode(&quot;moving&quot;);
+				}
+				else if (keyCode == 8 || keyCode == 46 || keyCode == 127) {
+					// 8: backspace
+					// 46: delete
+					// 127: delete
+					setMode(&quot;deleting&quot;);
+				}
+				else if (keyCode == 13) {
+					// 13: Enter
+					setMode(&quot;newlines&quot;);
+				}
+				else if (keyCode == 27) {
+					// 27: escape
+					setMode(&quot;escape&quot;);
+				}
+				else if ((keyCode &lt; 16 || keyCode &gt; 20) &amp;&amp; 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(&quot;typing&quot;);
+				}
+			}
+		};
+		
+		var setEventHandlers = function(){
+		
+			util.addEvent(wmd.panels.input, &quot;keypress&quot;, function(event){
+				// keyCode 89: y
+				// keyCode 90: z
+				if ((event.ctrlKey || event.metaKey) &amp;&amp; (event.keyCode == 89 || event.keyCode == 90)) {
+					event.preventDefault();
+				}
+			});
+			
+			var handlePaste = function(){
+				if (global.isIE || (inputStateObj &amp;&amp; inputStateObj.text != wmd.panels.input.value)) {
+					if (timer == undefined) {
+						mode = &quot;paste&quot;;
+						saveState();
+						refreshState();
+					}
+				}
+			};
+			
+			// pastePollInterval is specified at the beginning of this namespace.
+			poller = new wmd.inputPoller(handlePaste, pastePollInterval);
+			
+			util.addEvent(wmd.panels.input, &quot;keydown&quot;, handleCtrlYZ);
+			util.addEvent(wmd.panels.input, &quot;keydown&quot;, handleModeChange);
+			
+			util.addEvent(wmd.panels.input, &quot;mousedown&quot;, function(){
+				setMode(&quot;moving&quot;);
+			});
+			wmd.panels.input.onpaste = handlePaste;
+			wmd.panels.input.ondrop = handlePaste;
+		};
+		
+		var init = function(){
+			setEventHandlers();
+			refreshState();
+			saveState();
+		};
+		
+		this.destroy = function(){
+			if (poller) {
+				poller.destroy();
+			}
+		};
+		
+		init();
+	};
+	
+	// I think my understanding of how the buttons and callbacks are stored in the array is incomplete.
+	wmd.editor = function(previewRefreshCallback){
+	
+		if (!previewRefreshCallback) {
+			previewRefreshCallback = function(){};
+		}
+		
+		var inputBox = wmd.panels.input;
+		
+		var offsetHeight = 0;
+		
+		var editObj = this;
+		
+		var mainDiv;
+		var mainSpan;
+		
+		var div; // This name is pretty ambiguous.  I should rename this.
+		
+		// Used to cancel recurring events from setInterval.
+		var creationHandle;
+		
+		var undoMgr; // The undo manager
+		
+		// Perform the button's action.
+		var doClick = function(button){
+		
+			inputBox.focus();
+			
+			if (button.textOp) {
+				
+				if (undoMgr) {
+					undoMgr.setCommandMode();
+				}
+				
+				var state = new wmd.TextareaState();
+				
+				if (!state) {
+					return;
+				}
+				
+				var chunks = state.getChunks();
+				
+				// Some commands launch a &quot;modal&quot; 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();
+					previewRefreshCallback();
+				};
+				
+				var useDefaultText = true;
+				var noCleanup = button.textOp(chunks, fixupInputArea, useDefaultText);
+				
+				if(!noCleanup) {
+					fixupInputArea();
+				}
+				
+			}
+			
+			if (button.execute) {
+				button.execute(editObj);
+			}
+		};
+			
+		var setUndoRedoButtonStates = function(){
+			if(undoMgr){
+				setupButton(document.getElementById(&quot;wmd-undo-button&quot;), undoMgr.canUndo());
+				setupButton(document.getElementById(&quot;wmd-redo-button&quot;), undoMgr.canRedo());
+			}
+		};
+		
+		var setupButton = function(button, isEnabled) {
+		
+			var normalYShift = &quot;0px&quot;;
+			var disabledYShift = &quot;-20px&quot;;
+			var highlightYShift = &quot;-40px&quot;;
+			
+			if(isEnabled) {
+				button.style.backgroundPosition = button.XShift + &quot; &quot; + normalYShift;
+				button.onmouseover = function(){
+					this.style.backgroundPosition = this.XShift + &quot; &quot; + highlightYShift;
+				};
+							
+				button.onmouseout = function(){
+					this.style.backgroundPosition = this.XShift + &quot; &quot; + normalYShift;
+				};
+				
+				// IE tries to select the background image &quot;button&quot; text (it's
+				// implemented in a list item) so we have to cache the selection
+				// on mousedown.
+				if(global.isIE) {
+					button.onmousedown =  function() { 
+						wmd.ieRetardedClick = true;
+						wmd.ieCachedRange = document.selection.createRange(); 
+					};
+				}
+				
+				if (!button.isHelp)
+				{
+					button.onclick = function() {
+						if (this.onmouseout) {
+							this.onmouseout();
+						}
+						doClick(this);
+						return false;
+					}
+				}
+			}
+			else {
+				button.style.backgroundPosition = button.XShift + &quot; &quot; + disabledYShift;
+				button.onmouseover = button.onmouseout = button.onclick = function(){};
+			}
+		}
+	
+		var makeSpritedButtonRow = function(){
+		 	
+			var buttonBar = document.getElementById(&quot;wmd-button-bar&quot;);
+ 	
+			var normalYShift = &quot;0px&quot;;
+			var disabledYShift = &quot;-20px&quot;;
+			var highlightYShift = &quot;-40px&quot;;
+			
+			var buttonRow = document.createElement(&quot;ul&quot;);
+			buttonRow.id = &quot;wmd-button-row&quot;;
+			buttonRow = buttonBar.appendChild(buttonRow);
+
+			
+			var boldButton = document.createElement(&quot;li&quot;);
+			boldButton.className = &quot;wmd-button&quot;;
+			boldButton.id = &quot;wmd-bold-button&quot;;
+			boldButton.title = &quot;Strong &lt;strong&gt; Ctrl+B&quot;;
+			boldButton.XShift = &quot;0px&quot;;
+			boldButton.textOp = command.doBold;
+			setupButton(boldButton, true);
+			buttonRow.appendChild(boldButton);
+			
+			var italicButton = document.createElement(&quot;li&quot;);
+			italicButton.className = &quot;wmd-button&quot;;
+			italicButton.id = &quot;wmd-italic-button&quot;;
+			italicButton.title = &quot;Emphasis &lt;em&gt; Ctrl+I&quot;;
+			italicButton.XShift = &quot;-20px&quot;;
+			italicButton.textOp = command.doItalic;
+			setupButton(italicButton, true);
+			buttonRow.appendChild(italicButton);
+
+			var spacer1 = document.createElement(&quot;li&quot;);
+			spacer1.className = &quot;wmd-spacer&quot;;
+			spacer1.id = &quot;wmd-spacer1&quot;;
+			buttonRow.appendChild(spacer1); 
+
+			var linkButton = document.createElement(&quot;li&quot;);
+			linkButton.className = &quot;wmd-button&quot;;
+			linkButton.id = &quot;wmd-link-button&quot;;
+			linkButton.title = &quot;Hyperlink &lt;a&gt; Ctrl+L&quot;;
+			linkButton.XShift = &quot;-40px&quot;;
+			linkButton.textOp = function(chunk, postProcessing, useDefaultText){
+				return command.doLinkOrImage(chunk, postProcessing, false);
+			};
+			setupButton(linkButton, true);
+			buttonRow.appendChild(linkButton);
+
+			var quoteButton = document.createElement(&quot;li&quot;);
+			quoteButton.className = &quot;wmd-button&quot;;
+			quoteButton.id = &quot;wmd-quote-button&quot;;
+			quoteButton.title = &quot;Blockquote &lt;blockquote&gt; Ctrl+Q&quot;;
+			quoteButton.XShift = &quot;-60px&quot;;
+			quoteButton.textOp = command.doBlockquote;
+			setupButton(quoteButton, true);
+			buttonRow.appendChild(quoteButton);
+			
+			var codeButton = document.createElement(&quot;li&quot;);
+			codeButton.className = &quot;wmd-button&quot;;
+			codeButton.id = &quot;wmd-code-button&quot;;
+			codeButton.title = &quot;Code Sample &lt;pre&gt;&lt;code&gt; Ctrl+K&quot;;
+			codeButton.XShift = &quot;-80px&quot;;
+			codeButton.textOp = command.doCode;
+			setupButton(codeButton, true);
+			buttonRow.appendChild(codeButton);
+
+			var imageButton = document.createElement(&quot;li&quot;);
+			imageButton.className = &quot;wmd-button&quot;;
+			imageButton.id = &quot;wmd-image-button&quot;;
+			imageButton.title = &quot;Image &lt;img&gt; Ctrl+G&quot;;
+			imageButton.XShift = &quot;-100px&quot;;
+			imageButton.textOp = function(chunk, postProcessing, useDefaultText){
+				return command.doLinkOrImage(chunk, postProcessing, true);
+			};
+			setupButton(imageButton, true);
+			buttonRow.appendChild(imageButton);
+
+			var spacer2 = document.createElement(&quot;li&quot;);
+			spacer2.className = &quot;wmd-spacer&quot;;
+			spacer2.id = &quot;wmd-spacer2&quot;;
+			buttonRow.appendChild(spacer2); 
+
+			var olistButton = document.createElement(&quot;li&quot;);
+			olistButton.className = &quot;wmd-button&quot;;
+			olistButton.id = &quot;wmd-olist-button&quot;;
+			olistButton.title = &quot;Numbered List &lt;ol&gt; Ctrl+O&quot;;
+			olistButton.XShift = &quot;-120px&quot;;
+			olistButton.textOp = function(chunk, postProcessing, useDefaultText){
+				command.doList(chunk, postProcessing, true, useDefaultText);
+			};
+			setupButton(olistButton, true);
+			buttonRow.appendChild(olistButton);
+			
+			var ulistButton = document.createElement(&quot;li&quot;);
+			ulistButton.className = &quot;wmd-button&quot;;
+			ulistButton.id = &quot;wmd-ulist-button&quot;;
+			ulistButton.title = &quot;Bulleted List &lt;ul&gt; Ctrl+U&quot;;
+			ulistButton.XShift = &quot;-140px&quot;;
+			ulistButton.textOp = function(chunk, postProcessing, useDefaultText){
+				command.doList(chunk, postProcessing, false, useDefaultText);
+			};
+			setupButton(ulistButton, true);
+			buttonRow.appendChild(ulistButton);
+			
+			var headingButton = document.createElement(&quot;li&quot;);
+			headingButton.className = &quot;wmd-button&quot;;
+			headingButton.id = &quot;wmd-heading-button&quot;;
+			headingButton.title = &quot;Heading &lt;h1&gt;/&lt;h2&gt; Ctrl+H&quot;;
+			headingButton.XShift = &quot;-160px&quot;;
+			headingButton.textOp = command.doHeading;
+			setupButton(headingButton, true);
+			buttonRow.appendChild(headingButton); 
+			
+			var hrButton = document.createElement(&quot;li&quot;);
+			hrButton.className = &quot;wmd-button&quot;;
+			hrButton.id = &quot;wmd-hr-button&quot;;
+			hrButton.title = &quot;Horizontal Rule &lt;hr&gt; Ctrl+R&quot;;
+			hrButton.XShift = &quot;-180px&quot;;
+			hrButton.textOp = command.doHorizontalRule;
+			setupButton(hrButton, true);
+			buttonRow.appendChild(hrButton); 
+			
+			var spacer3 = document.createElement(&quot;li&quot;);
+			spacer3.className = &quot;wmd-spacer&quot;;
+			spacer3.id = &quot;wmd-spacer3&quot;;
+			buttonRow.appendChild(spacer3); 
+			
+			var undoButton = document.createElement(&quot;li&quot;);
+			undoButton.className = &quot;wmd-button&quot;;
+			undoButton.id = &quot;wmd-undo-button&quot;;
+			undoButton.title = &quot;Undo - Ctrl+Z&quot;;
+			undoButton.XShift = &quot;-200px&quot;;
+			undoButton.execute = function(manager){
+				manager.undo();
+			};
+			setupButton(undoButton, true);
+			buttonRow.appendChild(undoButton); 
+			
+			var redoButton = document.createElement(&quot;li&quot;);
+			redoButton.className = &quot;wmd-button&quot;;
+			redoButton.id = &quot;wmd-redo-button&quot;;
+			redoButton.title = &quot;Redo - Ctrl+Y&quot;;
+			if (/win/.test(nav.platform.toLowerCase())) {
+				redoButton.title = &quot;Redo - Ctrl+Y&quot;;
+			}
+			else {
+				// mac and other non-Windows platforms
+				redoButton.title = &quot;Redo - Ctrl+Shift+Z&quot;;
+			}
+			redoButton.XShift = &quot;-220px&quot;;
+			redoButton.execute = function(manager){
+				manager.redo();
+			};
+			setupButton(redoButton, true);
+			buttonRow.appendChild(redoButton); 
+			
+			var helpButton = document.createElement(&quot;li&quot;);
+			helpButton.className = &quot;wmd-button&quot;;
+			helpButton.id = &quot;wmd-help-button&quot;;
+			helpButton.XShift = &quot;-240px&quot;;
+			helpButton.isHelp = true;
+			
+			var helpAnchor = document.createElement(&quot;a&quot;);
+			helpAnchor.href = helpLink;
+			helpAnchor.target = helpTarget
+			helpAnchor.title = helpHoverTitle;
+			helpButton.appendChild(helpAnchor);
+			
+			setupButton(helpButton, true);
+			buttonRow.appendChild(helpButton);
+			
+			setUndoRedoButtonStates();
+		}
+		
+		var setupEditor = function(){
+		
+			if (/\?noundo/.test(doc.location.href)) {
+				wmd.nativeUndo = true;
+			}
+			
+			if (!wmd.nativeUndo) {
+				undoMgr = new wmd.undoManager(function(){
+					previewRefreshCallback();
+					setUndoRedoButtonStates();
+				});
+			}
+			
+			makeSpritedButtonRow();
+			
+			
+			var keyEvent = &quot;keydown&quot;;
+			if (global.isOpera) {
+				keyEvent = &quot;keypress&quot;;
+			}
+			
+			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) {
+			
+					var keyCode = key.charCode || key.keyCode;
+					var keyCodeStr = String.fromCharCode(keyCode).toLowerCase();
+					
+					switch(keyCodeStr) {
+						case &quot;b&quot;:
+							doClick(document.getElementById(&quot;wmd-bold-button&quot;));
+							break;
+						case &quot;i&quot;:
+							doClick(document.getElementById(&quot;wmd-italic-button&quot;));
+							break;
+						case &quot;l&quot;:
+							doClick(document.getElementById(&quot;wmd-link-button&quot;));
+							break;
+						case &quot;q&quot;:
+							doClick(document.getElementById(&quot;wmd-quote-button&quot;));
+							break;
+						case &quot;k&quot;:
+							doClick(document.getElementById(&quot;wmd-code-button&quot;));
+							break;
+						case &quot;g&quot;:
+							doClick(document.getElementById(&quot;wmd-image-button&quot;));
+							break;
+						case &quot;o&quot;:
+							doClick(document.getElementById(&quot;wmd-olist-button&quot;));
+							break;
+						case &quot;u&quot;:
+							doClick(document.getElementById(&quot;wmd-ulist-button&quot;));
+							break;
+						case &quot;h&quot;:
+							doClick(document.getElementById(&quot;wmd-heading-button&quot;));
+							break;
+						case &quot;r&quot;:
+							doClick(document.getElementById(&quot;wmd-hr-button&quot;));
+							break;
+						case &quot;y&quot;:
+							doClick(document.getElementById(&quot;wmd-redo-button&quot;));
+							break;
+						case &quot;z&quot;:
+							if(key.shiftKey) {
+								doClick(document.getElementById(&quot;wmd-redo-button&quot;));
+							}
+							else {
+								doClick(document.getElementById(&quot;wmd-undo-button&quot;));
+							}
+							break;
+						default:
+							return;
+					}
+					
+
+					if (key.preventDefault) {
+						key.preventDefault();
+					}
+					
+					if (top.event) {
+						top.event.returnValue = false;
+					}
+				}
+			});
+			
+			// Auto-continue lists, code blocks and block quotes when
+			// the enter key is pressed.
+			util.addEvent(inputBox, &quot;keyup&quot;, function(key){
+				if (!key.shiftKey &amp;&amp; !key.ctrlKey &amp;&amp; !key.metaKey) {
+					var keyCode = key.charCode || key.keyCode;
+					// Key code 13 is Enter
+					if (keyCode === 13) {
+						fakeButton = {};
+						fakeButton.textOp = command.doAutoindent;
+						doClick(fakeButton);
+					}
+				}
+			});
+			
+			// Disable ESC clearing the input textarea on IE
+			if (global.isIE) {
+				util.addEvent(inputBox, &quot;keydown&quot;, function(key){
+					var code = key.keyCode;
+					// Key code 27 is ESC
+					if (code === 27) {
+						return false;
+					}
+				});
+			}
+			
+			if (inputBox.form) {
+				var submitCallback = inputBox.form.onsubmit;
+				inputBox.form.onsubmit = function(){
+					convertToHtml();
+					if (submitCallback) {
+						return submitCallback.apply(this, arguments);
+					}
+				};
+			}
+		};
+		
+		// Convert the contents of the input textarea to HTML in the output/preview panels.
+		var convertToHtml = function(){
+		
+			if (wmd.showdown) {
+				var markdownConverter = new wmd.showdown.converter();
+			}
+			var text = inputBox.value;
+			
+			var callback = function(){
+				inputBox.value = text;
+			};
+			
+			if (!/markdown/.test(wmd.wmd_env.output.toLowerCase())) {
+				if (markdownConverter) {
+					inputBox.value = markdownConverter.makeHtml(text);
+					top.setTimeout(callback, 0);
+				}
+			}
+			return true;
+		};
+		
+		
+		this.undo = function(){
+			if (undoMgr) {
+				undoMgr.undo();
+			}
+		};
+		
+		this.redo = function(){
+			if (undoMgr) {
+				undoMgr.redo();
+			}
+		};
+		
+		// This is pretty useless.  The setupEditor function contents
+		// should just be copied here.
+		var init = function(){
+			setupEditor();
+		};
+		
+		this.destroy = function(){
+			if (undoMgr) {
+				undoMgr.destroy();
+			}
+			if (div.parentNode) {
+				div.parentNode.removeChild(div);
+			}
+			if (inputBox) {
+				inputBox.style.marginTop = &quot;&quot;;
+			}
+			top.clearInterval(creationHandle);
+		};
+		
+		init();
+	};
+	
+	// The input textarea state/contents.
+	// This is used to implement undo/redo by the undo manager.
+	wmd.TextareaState = function(){
+	
+		// Aliases
+		var stateObj = this;
+		var inputArea = wmd.panels.input;
+		
+		this.init = function() {
+		
+			if (!util.isVisible(inputArea)) {
+				return;
+			}
+				
+			this.setInputAreaSelectionStartEnd();
+			this.scrollTop = inputArea.scrollTop;
+			if (!this.text &amp;&amp; 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 &amp;&amp; !global.isOpera) {
+			
+				inputArea.focus();
+				inputArea.selectionStart = stateObj.start;
+				inputArea.selectionEnd = stateObj.end;
+				inputArea.scrollTop = stateObj.scrollTop;
+			}
+			else if (doc.selection) {
+				
+				if (doc.activeElement &amp;&amp; doc.activeElement !== inputArea) {
+					return;
+				}
+					
+				inputArea.focus();
+				var range = inputArea.createTextRange();
+				range.moveStart(&quot;character&quot;, -inputArea.value.length);
+				range.moveEnd(&quot;character&quot;, -inputArea.value.length);
+				range.moveEnd(&quot;character&quot;, stateObj.end);
+				range.moveStart(&quot;character&quot;, stateObj.start);
+				range.select();
+			}
+		};
+		
+		this.setInputAreaSelectionStartEnd = function(){
+		
+			if (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 and set a flag
+				// which we check for here.
+				var range;
+				if(wmd.ieRetardedClick &amp;&amp; wmd.ieCachedRange) {
+					range = wmd.ieCachedRange;
+					wmd.ieRetardedClick = false;
+				}
+				else {
+					range = doc.selection.createRange();
+				}
+
+				var fixedRange = util.fixEolChars(range.text);
+				var marker = &quot;\x07&quot;;
+				var markedRange = marker + fixedRange + marker;
+				range.text = markedRange;
+				var inputText = util.fixEolChars(inputArea.value);
+					
+				range.moveStart(&quot;character&quot;, -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(&quot;character&quot;, -fixedRange.length);
+					while (len--) {
+						fixedRange += &quot;\n&quot;;
+						stateObj.end += 1;
+					}
+					range.text = fixedRange;
+				}
+					
+				this.setInputAreaSelection();
+			}
+		};
+		
+		// Restore this state into the input area.
+		this.restore = function(){
+		
+			if (stateObj.text != undefined &amp;&amp; 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 wmd.Chunks();
+			
+			chunk.before = util.fixEolChars(stateObj.text.substring(0, stateObj.start));
+			chunk.startTag = &quot;&quot;;
+			chunk.selection = util.fixEolChars(stateObj.text.substring(stateObj.start, stateObj.end));
+			chunk.endTag = &quot;&quot;;
+			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;
+			
+			if (global.isOpera) {
+				chunk.before = chunk.before.replace(/\n/g, &quot;\r\n&quot;);
+				chunk.selection = chunk.selection.replace(/\n/g, &quot;\r\n&quot;);
+				chunk.after = chunk.after.replace(/\n/g, &quot;\r\n&quot;);
+			}
+			
+			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();
+	};
+	
+	// before: contains all the text in the input box BEFORE the selection.
+	// after: contains all the text in the input box AFTER the selection.
+	wmd.Chunks = function(){
+	};
+	
+	// startRegex: a regular expression to find the start tag
+	// endRegex: a regular expresssion to find the end tag
+	wmd.Chunks.prototype.findTags = function(startRegex, endRegex){
+	
+		var chunkObj = this;
+		var regex;
+		
+		if (startRegex) {
+			
+			regex = util.extendRegExp(startRegex, &quot;&quot;, &quot;$&quot;);
+			
+			this.before = this.before.replace(regex, 
+				function(match){
+					chunkObj.startTag = chunkObj.startTag + match;
+					return &quot;&quot;;
+				});
+			
+			regex = util.extendRegExp(startRegex, &quot;^&quot;, &quot;&quot;);
+			
+			this.selection = this.selection.replace(regex, 
+				function(match){
+					chunkObj.startTag = chunkObj.startTag + match;
+					return &quot;&quot;;
+				});
+		}
+		
+		if (endRegex) {
+			
+			regex = util.extendRegExp(endRegex, &quot;&quot;, &quot;$&quot;);
+			
+			this.selection = this.selection.replace(regex,
+				function(match){
+					chunkObj.endTag = match + chunkObj.endTag;
+					return &quot;&quot;;
+				});
+
+			regex = util.extendRegExp(endRegex, &quot;^&quot;, &quot;&quot;);
+			
+			this.after = this.after.replace(regex,
+				function(match){
+					chunkObj.endTag = match + chunkObj.endTag;
+					return &quot;&quot;;
+				});
+		}
+	};
+	
+	// If remove is false, the whitespace is transferred
+	// to the before/after regions.
+	//
+	// If remove is true, the whitespace disappears.
+	wmd.Chunks.prototype.trimWhitespace = function(remove){
+	
+		this.selection = this.selection.replace(/^(\s*)/, &quot;&quot;);
+		
+		if (!remove) {
+			this.before += re.$1;
+		}
+		
+		this.selection = this.selection.replace(/(\s*)$/, &quot;&quot;);
+		
+		if (!remove) {
+			this.after = re.$1 + this.after;
+		}
+	};
+	
+	
+	wmd.Chunks.prototype.addBlankLines = function(nLinesBefore, nLinesAfter, findExtraNewlines){
+	
+		if (nLinesBefore === undefined) {
+			nLinesBefore = 1;
+		}
+		
+		if (nLinesAfter === undefined) {
+			nLinesAfter = 1;
+		}
+		
+		nLinesBefore++;
+		nLinesAfter++;
+		
+		var regexText;
+		var replacementText;
+		
+		this.selection = this.selection.replace(/(^\n*)/, &quot;&quot;);
+		this.startTag = this.startTag + re.$1;
+		this.selection = this.selection.replace(/(\n*$)/, &quot;&quot;);
+		this.endTag = this.endTag + re.$1;
+		this.startTag = this.startTag.replace(/(^\n*)/, &quot;&quot;);
+		this.before = this.before + re.$1;
+		this.endTag = this.endTag.replace(/(\n*$)/, &quot;&quot;);
+		this.after = this.after + re.$1;
+		
+		if (this.before) {
+		
+			regexText = replacementText = &quot;&quot;;
+			
+			while (nLinesBefore--) {
+				regexText += &quot;\\n?&quot;;
+				replacementText += &quot;\n&quot;;
+			}
+			
+			if (findExtraNewlines) {
+				regexText = &quot;\\n*&quot;;
+			}
+			this.before = this.before.replace(new re(regexText + &quot;$&quot;, &quot;&quot;), replacementText);
+		}
+		
+		if (this.after) {
+		
+			regexText = replacementText = &quot;&quot;;
+			
+			while (nLinesAfter--) {
+				regexText += &quot;\\n?&quot;;
+				replacementText += &quot;\n&quot;;
+			}
+			if (findExtraNewlines) {
+				regexText = &quot;\\n*&quot;;
+			}
+			
+			this.after = this.after.replace(new re(regexText, &quot;&quot;), replacementText);
+		}
+	};
+	
+	// The markdown symbols - 4 spaces = code, &gt; = blockquote, etc.
+	command.prefixes = &quot;(?:\\s{4,}|\\s*&gt;|\\s*-\\s+|\\s*\\d+\\.|=|\\+|-|_|\\*|#|\\s*\\[[^\n]]+\\]:)&quot;;
+	
+	// Remove markdown symbols from the chunk selection.
+	command.unwrap = function(chunk){
+		var txt = new re(&quot;([^\\n])\\n(?!(\\n|&quot; + command.prefixes + &quot;))&quot;, &quot;g&quot;);
+		chunk.selection = chunk.selection.replace(txt, &quot;$1 $2&quot;);
+	};
+	
+	command.wrap = function(chunk, len){
+		command.unwrap(chunk);
+		var regex = new re(&quot;(.{1,&quot; + len + &quot;})( +|$\\n?)&quot;, &quot;gm&quot;);
+		
+		chunk.selection = chunk.selection.replace(regex, function(line, marked){
+			if (new re(&quot;^&quot; + command.prefixes, &quot;&quot;).test(line)) {
+				return line;
+			}
+			return marked + &quot;\n&quot;;
+		});
+		
+		chunk.selection = chunk.selection.replace(/\s+$/, &quot;&quot;);
+	};
+	
+	command.doBold = function(chunk, postProcessing, useDefaultText){
+		return command.doBorI(chunk, 2, &quot;strong text&quot;);
+	};
+	
+	command.doItalic = function(chunk, postProcessing, useDefaultText){
+		return command.doBorI(chunk, 1, &quot;emphasized text&quot;);
+	};
+	
+	// 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
+	command.doBorI = function(chunk, nStars, insertText){
+	
+		// Get rid of whitespace and fixup newlines.
+		chunk.trimWhitespace();
+		chunk.selection = chunk.selection.replace(/\n{2,}/g, &quot;\n&quot;);
+		
+		// Look for stars before and after.  Is the chunk already marked up?
+		chunk.before.search(/(\**$)/);
+		var starsBefore = re.$1;
+		
+		chunk.after.search(/(^\**)/);
+		var starsAfter = re.$1;
+		
+		var prevStars = Math.min(starsBefore.length, starsAfter.length);
+		
+		// Remove stars if we have to since the button acts as a toggle.
+		if ((prevStars &gt;= nStars) &amp;&amp; (prevStars != 2 || nStars != 1)) {
+			chunk.before = chunk.before.replace(re(&quot;[*]{&quot; + nStars + &quot;}$&quot;, &quot;&quot;), &quot;&quot;);
+			chunk.after = chunk.after.replace(re(&quot;^[*]{&quot; + nStars + &quot;}&quot;, &quot;&quot;), &quot;&quot;);
+		}
+		else if (!chunk.selection &amp;&amp; starsAfter) {
+			// It's not really clear why this code is necessary.  It just moves
+			// some arbitrary stuff around.
+			chunk.after = chunk.after.replace(/^([*_]*)/, &quot;&quot;);
+			chunk.before = chunk.before.replace(/(\s?)$/, &quot;&quot;);
+			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 &amp;&amp; !starsAfter) {
+				chunk.selection = insertText;
+			}
+			
+			// Add the true markup.
+			var markup = nStars &lt;= 1 ? &quot;*&quot; : &quot;**&quot;; // shouldn't the test be = ?
+			chunk.before = chunk.before + markup;
+			chunk.after = markup + chunk.after;
+		}
+		
+		return;
+	};
+	
+	command.stripLinkDefs = function(text, defsToAdd){
+	
+		text = text.replace(/^[ ]{0,3}\[(\d+)\]:[ \t]*\n?[ \t]*&lt;?(\S+?)&gt;?[ \t]*\n?[ \t]*(?:(\n*)[&quot;(](.+?)[&quot;)][ \t]*)?(?:\n+|$)/gm, 
+			function(totalMatch, id, link, newlines, title){	
+				defsToAdd[id] = totalMatch.replace(/\s*$/, &quot;&quot;);
+				if (newlines) {
+					// Strip the title and return that separately.
+					defsToAdd[id] = totalMatch.replace(/[&quot;(](.+?)[&quot;)]$/, &quot;&quot;);
+					return newlines + title;
+				}
+				return &quot;&quot;;
+			});
+		
+		return text;
+	};
+	
+	command.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 = command.stripLinkDefs(chunk.before, defsToAdd);
+		chunk.selection = command.stripLinkDefs(chunk.selection, defsToAdd);
+		chunk.after = command.stripLinkDefs(chunk.after, defsToAdd);
+		
+		var defs = &quot;&quot;;
+		var regex = /(\[(?:\[[^\]]*\]|[^\[\]])*\][ ]?(?:\n[ ]*)?\[)(\d+)(\])/g;
+		
+		var addDefNumber = function(def){
+			refNumber++;
+			def = def.replace(/^[ ]{0,3}\[(\d+)\]:/, &quot;  [&quot; + refNumber + &quot;]:&quot;);
+			defs += &quot;\n&quot; + def;
+		};
+		
+		var getLink = function(wholeMatch, link, id, end){
+		
+			if (defsToAdd[id]) {
+				addDefNumber(defsToAdd[id]);
+				return link + 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*$/, &quot;&quot;);
+		}
+		if (!chunk.after) {
+			chunk.selection = chunk.selection.replace(/\n*$/, &quot;&quot;);
+		}
+		
+		chunk.after += &quot;\n\n&quot; + defs;
+		
+		return refOut;
+	};
+	
+	command.doLinkOrImage = function(chunk, postProcessing, isImage){
+	
+		chunk.trimWhitespace();
+		chunk.findTags(/\s*!?\[/, /\][ ]?(?:\n[ ]*)?(\[.*?\])?/);
+		
+		if (chunk.endTag.length &gt; 1) {
+		
+			chunk.startTag = chunk.startTag.replace(/!?\[/, &quot;&quot;);
+			chunk.endTag = &quot;&quot;;
+			command.addLinkDef(chunk, null);
+			
+		}
+		else {
+		
+			if (/\n\n/.test(chunk.selection)) {
+				command.addLinkDef(chunk, null);
+				return;
+			}
+			
+			// The function to be executed when you enter a link and press OK or Cancel.
+			// Marks up the link and adds the ref.
+			var makeLinkMarkdown = function(link){
+			
+				if (link !== null) {
+				
+					chunk.startTag = chunk.endTag = &quot;&quot;;
+					var linkDef = &quot; [999]: &quot; + link;
+					
+					var num = command.addLinkDef(chunk, linkDef);
+					chunk.startTag = isImage ? &quot;![&quot; : &quot;[&quot;;
+					chunk.endTag = &quot;][&quot; + num + &quot;]&quot;;
+					
+					if (!chunk.selection) {
+						if (isImage) {
+							chunk.selection = &quot;alt text&quot;;
+						}
+						else {
+							chunk.selection = &quot;link text&quot;;
+						}
+					}
+				}
+				postProcessing();
+			};
+			
+			if (isImage) {
+				util.prompt(imageDialogText, imageDefaultText, makeLinkMarkdown);
+			}
+			else {
+				util.prompt(linkDialogText, linkDefaultText, makeLinkMarkdown);
+			}
+			return true;
+		}
+	};
+	
+	util.makeAPI = function(){
+		wmd.wmd = {};
+		wmd.wmd.editor = wmd.editor;
+		wmd.wmd.previewManager = wmd.previewManager;
+	};
+	
+	util.startEditor = function(){
+	
+		if (wmd.wmd_env.autostart === false) {
+			util.makeAPI();
+			return;
+		}
+
+		var edit;		// The editor (buttons + input + outputs) - the main object.
+		var previewMgr;	// The preview manager.
+		
+		// Fired after the page has fully loaded.
+		var loadListener = function(){
+		
+			wmd.panels = new wmd.PanelCollection();
+			
+			previewMgr = new wmd.previewManager();
+			var previewRefreshCallback = previewMgr.refresh;
+						
+			edit = new wmd.editor(previewRefreshCallback);
+			
+			previewMgr.refresh(true);
+			
+		};
+		
+		util.addEvent(top, &quot;load&quot;, loadListener);
+	};
+	
+	wmd.previewManager = function(){
+		
+		var managerObj = this;
+		var converter;
+		var poller;
+		var timeout;
+		var elapsedTime;
+		var oldInputText;
+		var htmlOut;
+		var maxDelay = 3000;
+		var startType = &quot;delayed&quot;; // The other legal value is &quot;manual&quot;
+		
+		// Adds event listeners to elements and creates the input poller.
+		var setupEvents = function(inputElem, listener){
+		
+			util.addEvent(inputElem, &quot;input&quot;, listener);
+			inputElem.onpaste = listener;
+			inputElem.ondrop = listener;
+			
+			util.addEvent(inputElem, &quot;keypress&quot;, listener);
+			util.addEvent(inputElem, &quot;keydown&quot;, listener);
+			// previewPollInterval is set at the top of this file.
+			poller = new wmd.inputPoller(listener, previewPollInterval);
+		};
+		
+		var getDocScrollTop = function(){
+		
+			var result = 0;
+			
+			if (top.innerHeight) {
+				result = top.pageYOffset;
+			}
+			else 
+				if (doc.documentElement &amp;&amp; doc.documentElement.scrollTop) {
+					result = doc.documentElement.scrollTop;
+				}
+				else 
+					if (doc.body) {
+						result = doc.body.scrollTop;
+					}
+			
+			return result;
+		};
+		
+		var makePreviewHtml = function(){
+		
+			// If there are no registered preview and output panels
+			// there is nothing to do.
+			if (!wmd.panels.preview &amp;&amp; !wmd.panels.output) {
+				return;
+			}
+			
+			var text = wmd.panels.input.value;
+			if (text &amp;&amp; text == oldInputText) {
+				return; // Input text hasn't changed.
+			}
+			else {
+				oldInputText = text;
+			}
+			
+			var prevTime = new Date().getTime();
+			
+			if (!converter &amp;&amp; wmd.showdown) {
+				converter = new wmd.showdown.converter();
+			}
+			
+			if (converter) {
+				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);
+			htmlOut = text;
+		};
+		
+		// setTimeout is already used.  Used as an event listener.
+		var applyTimeout = function(){
+		
+			if (timeout) {
+				top.clearTimeout(timeout);
+				timeout = undefined;
+			}
+			
+			if (startType !== &quot;manual&quot;) {
+			
+				var delay = 0;
+				
+				if (startType === &quot;delayed&quot;) {
+					delay = elapsedTime;
+				}
+				
+				if (delay &gt; maxDelay) {
+					delay = maxDelay;
+				}
+				timeout = top.setTimeout(makePreviewHtml, delay);
+			}
+		};
+		
+		var getScaleFactor = function(panel){
+			if (panel.scrollHeight &lt;= panel.clientHeight) {
+				return 1;
+			}
+			return panel.scrollTop / (panel.scrollHeight - panel.clientHeight);
+		};
+		
+		var setPanelScrollTops = function(){
+		
+			if (wmd.panels.preview) {
+				wmd.panels.preview.scrollTop = (wmd.panels.preview.scrollHeight - wmd.panels.preview.clientHeight) * getScaleFactor(wmd.panels.preview);
+				;
+			}
+			
+			if (wmd.panels.output) {
+				wmd.panels.output.scrollTop = (wmd.panels.output.scrollHeight - wmd.panels.output.clientHeight) * getScaleFactor(wmd.panels.output);
+				;
+			}
+		};
+		
+		this.refresh = function(requiresRefresh){
+		
+			if (requiresRefresh) {
+				oldInputText = &quot;&quot;;
+				makePreviewHtml();
+			}
+			else {
+				applyTimeout();
+			}
+		};
+		
+		this.processingTime = function(){
+			return elapsedTime;
+		};
+		
+		// The output HTML
+		this.output = function(){
+			return htmlOut;
+		};
+		
+		// The mode can be &quot;manual&quot; or &quot;delayed&quot;
+		this.setUpdateMode = function(mode){
+			startType = mode;
+			managerObj.refresh();
+		};
+		
+		var isFirstTimeFilled = true;
+		
+		var pushPreviewHtml = function(text){
+		
+			var emptyTop = position.getTop(wmd.panels.input) - getDocScrollTop();
+			
+			// Send the encoded HTML to the output textarea/div.
+			if (wmd.panels.output) {
+				// The value property is only defined if the output is a textarea.
+				if (wmd.panels.output.value !== undefined) {
+					wmd.panels.output.value = text;
+					wmd.panels.output.readOnly = true;
+				}
+				// Otherwise we are just replacing the text in a div.
+				// Send the HTML wrapped in &lt;pre&gt;&lt;code&gt;
+				else {
+					var newText = text.replace(/&amp;/g, &quot;&amp;amp;&quot;);
+					newText = newText.replace(/&lt;/g, &quot;&amp;lt;&quot;);
+					wmd.panels.output.innerHTML = &quot;&lt;pre&gt;&lt;code&gt;&quot; + newText + &quot;&lt;/code&gt;&lt;/pre&gt;&quot;;
+				}
+			}
+			
+			if (wmd.panels.preview) {
+				wmd.panels.preview.innerHTML = text;
+			}
+			
+			setPanelScrollTops();
+			
+			if (isFirstTimeFilled) {
+				isFirstTimeFilled = false;
+				return;
+			}
+			
+			var fullTop = position.getTop(wmd.panels.input) - getDocScrollTop();
+			
+			if (global.isIE) {
+				top.setTimeout(function(){
+					top.scrollBy(0, fullTop - emptyTop);
+				}, 0);
+			}
+			else {
+				top.scrollBy(0, fullTop - emptyTop);
+			}
+		};
+		
+		var init = function(){
+		
+			setupEvents(wmd.panels.input, applyTimeout);
+			makePreviewHtml();
+			
+			if (wmd.panels.preview) {
+				wmd.panels.preview.scrollTop = 0;
+			}
+			if (wmd.panels.output) {
+				wmd.panels.output.scrollTop = 0;
+			}
+		};
+		
+		this.destroy = function(){
+			if (poller) {
+				poller.destroy();
+			}
+		};
+		
+		init();
+	};
+
+	// Moves the cursor to the next line and continues lists, quotes and code.
+	command.doAutoindent = function(chunk, postProcessing, useDefaultText){
+		
+		chunk.before = chunk.before.replace(/(\n|^)[ ]{0,3}([*+-]|\d+[.])[ \t]*\n$/, &quot;\n\n&quot;);
+		chunk.before = chunk.before.replace(/(\n|^)[ ]{0,3}&gt;[ \t]*\n$/, &quot;\n\n&quot;);
+		chunk.before = chunk.before.replace(/(\n|^)[ \t]+\n$/, &quot;\n\n&quot;);
+		
+		useDefaultText = false;
+		
+		if(/(\n|^)[ ]{0,3}([*+-])[ \t]+.*\n$/.test(chunk.before)){
+			if(command.doList){
+				command.doList(chunk, postProcessing, false, true);
+			}
+		}
+		if(/(\n|^)[ ]{0,3}(\d+[.])[ \t]+.*\n$/.test(chunk.before)){
+			if(command.doList){
+				command.doList(chunk, postProcessing, true, true);
+			}
+		}
+		if(/(\n|^)[ ]{0,3}&gt;[ \t]+.*\n$/.test(chunk.before)){
+			if(command.doBlockquote){
+				command.doBlockquote(chunk, postProcessing, useDefaultText);
+			}
+		}
+		if(/(\n|^)(\t|[ ]{4,}).*\n$/.test(chunk.before)){
+			if(command.doCode){
+				command.doCode(chunk, postProcessing, useDefaultText);
+			}
+		}
+	};
+	
+	command.doBlockquote = function(chunk, postProcessing, useDefaultText){
+		
+		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(/(&gt;[ \t]*)$/,
+			function(totalMatch, blankLine){
+				chunk.selection = blankLine + chunk.selection;
+				return &quot;&quot;;
+			});
+		
+		var defaultText = useDefaultText ? &quot;Blockquote&quot; : &quot;&quot;;
+		chunk.selection = chunk.selection.replace(/^(\s|&gt;)+$/ ,&quot;&quot;);
+		chunk.selection = chunk.selection || defaultText;
+		
+		if(chunk.before){
+			chunk.before = chunk.before.replace(/\n?$/,&quot;\n&quot;);
+		}
+		if(chunk.after){
+			chunk.after = chunk.after.replace(/^\n?/,&quot;\n&quot;);
+		}
+		
+		chunk.before = chunk.before.replace(/(((\n|^)(\n[ \t]*)*&gt;(.+\n)*.*)+(\n[ \t]*)*$)/,
+			function(totalMatch){
+				chunk.startTag = totalMatch;
+				return &quot;&quot;;
+			});
+			
+		chunk.after = chunk.after.replace(/^(((\n|^)(\n[ \t]*)*&gt;(.+\n)*.*)+(\n[ \t]*)*)/,
+			function(totalMatch){
+				chunk.endTag = totalMatch;
+				return &quot;&quot;;
+			});
+		
+		var replaceBlanksInTags = function(useBracket){
+			
+			var replacement = useBracket ? &quot;&gt; &quot; : &quot;&quot;;
+			
+			if(chunk.startTag){
+				chunk.startTag = chunk.startTag.replace(/\n((&gt;|\s)*)\n$/,
+					function(totalMatch, markdown){
+						return &quot;\n&quot; + markdown.replace(/^[ ]{0,3}&gt;?[ \t]*$/gm, replacement) + &quot;\n&quot;;
+					});
+			}
+			if(chunk.endTag){
+				chunk.endTag = chunk.endTag.replace(/^\n((&gt;|\s)*)\n/,
+					function(totalMatch, markdown){
+						return &quot;\n&quot; + markdown.replace(/^[ ]{0,3}&gt;?[ \t]*$/gm, replacement) + &quot;\n&quot;;
+					});
+			}
+		};
+		
+		if(/^(?![ ]{0,3}&gt;)/m.test(chunk.selection)){
+			command.wrap(chunk, wmd.wmd_env.lineLength - 2);
+			chunk.selection = chunk.selection.replace(/^/gm, &quot;&gt; &quot;);
+			replaceBlanksInTags(true);
+			chunk.addBlankLines();
+		}
+		else{
+			chunk.selection = chunk.selection.replace(/^[ ]{0,3}&gt; ?/gm, &quot;&quot;);
+			command.unwrap(chunk);
+			replaceBlanksInTags(false);
+			
+			if(!/^(\n|^)[ ]{0,3}&gt;/.test(chunk.selection) &amp;&amp; chunk.startTag){
+				chunk.startTag = chunk.startTag.replace(/\n{0,2}$/, &quot;\n\n&quot;);
+			}
+			
+			if(!/(\n|^)[ ]{0,3}&gt;.*$/.test(chunk.selection) &amp;&amp; chunk.endTag){
+				chunk.endTag=chunk.endTag.replace(/^\n{0,2}/, &quot;\n\n&quot;);
+			}
+		}
+		
+		if(!/\n/.test(chunk.selection)){
+			chunk.selection = chunk.selection.replace(/^(&gt; *)/,
+			function(wholeMatch, blanks){
+				chunk.startTag += blanks;
+				return &quot;&quot;;
+			});
+		}
+	};
+
+	command.doCode = function(chunk, postProcessing, useDefaultText){
+		
+		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 &amp;&amp; !hasTextBefore) || /\n/.test(chunk.selection)){
+			
+			chunk.before = chunk.before.replace(/[ ]{4}$/,
+				function(totalMatch){
+					chunk.selection = totalMatch + chunk.selection;
+					return &quot;&quot;;
+				});
+				
+			var nLinesBefore = 1;
+			var nLinesAfter = 1;
+			
+			
+			if(/\n(\t|[ ]{4,}).*\n$/.test(chunk.before) || chunk.after === &quot;&quot;){
+				nLinesBefore = 0; 
+			}
+			if(/^\n(\t|[ ]{4,})/.test(chunk.after)){
+				nLinesAfter = 0; // This needs to happen on line 1
+			}
+			
+			chunk.addBlankLines(nLinesBefore, nLinesAfter);
+			
+			if(!chunk.selection){
+				chunk.startTag = &quot;    &quot;;
+				chunk.selection = useDefaultText ? &quot;enter code here&quot; : &quot;&quot;;
+			}
+			else {
+				if(/^[ ]{0,3}\S/m.test(chunk.selection)){
+					chunk.selection = chunk.selection.replace(/^/gm, &quot;    &quot;);
+				}
+				else{
+					chunk.selection = chunk.selection.replace(/^[ ]{4}/gm, &quot;&quot;);
+				}
+			}
+		}
+		else{
+			// Use backticks (`) to delimit the code block.
+			
+			chunk.trimWhitespace();
+			chunk.findTags(/`/, /`/);
+			
+			if(!chunk.startTag &amp;&amp; !chunk.endTag){
+				chunk.startTag = chunk.endTag=&quot;`&quot;;
+				if(!chunk.selection){
+					chunk.selection = useDefaultText ? &quot;enter code here&quot; : &quot;&quot;;
+				}
+			}
+			else if(chunk.endTag &amp;&amp; !chunk.startTag){
+				chunk.before += chunk.endTag;
+				chunk.endTag = &quot;&quot;;
+			}
+			else{
+				chunk.startTag = chunk.endTag=&quot;&quot;;
+			}
+		}
+	};
+	
+	command.doList = function(chunk, postProcessing, isNumberedList, useDefaultText){
+				
+		// 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 = &quot;-&quot;;
+		
+		// The number in a numbered list.
+		var num = 1;
+		
+		// Get the item prefix - e.g. &quot; 1. &quot; for a numbered list, &quot; - &quot; for a bulleted list.
+		var getItemPrefix = function(){
+			var prefix;
+			if(isNumberedList){
+				prefix = &quot; &quot; + num + &quot;. &quot;;
+				num++;
+			}
+			else{
+				prefix = &quot; &quot; + bullet + &quot; &quot;;
+			}
+			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 &amp;&amp; !/\n$/.test(chunk.before) &amp;&amp; !/^\n/.test(chunk.startTag)){
+			chunk.before += chunk.startTag;
+			chunk.startTag = &quot;&quot;;
+		}
+		
+		if(chunk.startTag){
+			
+			var hasDigits = /\d+[.]/.test(chunk.startTag);
+			chunk.startTag = &quot;&quot;;
+			chunk.selection = chunk.selection.replace(/\n[ ]{4}/g, &quot;\n&quot;);
+			command.unwrap(chunk);
+			chunk.addBlankLines();
+			
+			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 nLinesBefore = 1;
+		
+		chunk.before = chunk.before.replace(previousItemsRegex,
+			function(itemText){
+				if(/^\s*([*+-])/.test(itemText)){
+					bullet = re.$1;
+				}
+				nLinesBefore = /[^\n]\n\n[^\n]/.test(itemText) ? 1 : 0;
+				return getPrefixedItem(itemText);
+			});
+			
+		if(!chunk.selection){
+			chunk.selection = useDefaultText ? &quot;List item&quot; : &quot; &quot;;
+		}
+		
+		var prefix = getItemPrefix();
+		
+		var nLinesAfter = 1;
+		
+		chunk.after = chunk.after.replace(nextItemsRegex,
+			function(itemText){
+				nLinesAfter = /[^\n]\n\n[^\n]/.test(itemText) ? 1 : 0;
+				return getPrefixedItem(itemText);
+			});
+			
+		chunk.trimWhitespace(true);
+		chunk.addBlankLines(nLinesBefore, nLinesAfter, true);
+		chunk.startTag = prefix;
+		var spaces = prefix.replace(/./g, &quot; &quot;);
+		command.wrap(chunk, wmd.wmd_env.lineLength - spaces.length);
+		chunk.selection = chunk.selection.replace(/\n/g, &quot;\n&quot; + spaces);
+		
+	};
+	
+	command.doHeading = function(chunk, postProcessing, useDefaultText){
+		
+		// Remove leading/trailing whitespace and reduce internal spaces to single spaces.
+		chunk.selection = chunk.selection.replace(/\s+/g, &quot; &quot;);
+		chunk.selection = chunk.selection.replace(/(^\s+|\s+$)/g, &quot;&quot;);
+		
+		// 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 = &quot;## &quot;;
+			chunk.selection = &quot;Heading&quot;;
+			chunk.endTag = &quot; ##&quot;;
+			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 = &quot;&quot;;
+		
+		// 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 = &quot;&quot;;
+		chunk.addBlankLines(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 &gt; 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 &gt;= 2 ? &quot;-&quot; : &quot;=&quot;;
+			var len = chunk.selection.length;
+			if(len &gt; wmd.wmd_env.lineLength){
+				len = wmd.wmd_env.lineLength;
+			}
+			chunk.endTag = &quot;\n&quot;;
+			while(len--){
+				chunk.endTag += headerChar;
+			}
+		}
+	};	
+	
+	command.doHorizontalRule = function(chunk, postProcessing, useDefaultText){
+		chunk.startTag = &quot;----------\n&quot;;
+		chunk.selection = &quot;&quot;;
+		chunk.addBlankLines(2, 1, true);
+	}
+};
+
+
+Attacklab.wmd_env = {};
+Attacklab.account_options = {};
+Attacklab.wmd_defaults = {version:1, output:&quot;HTML&quot;, lineLength:40, delayLoad:false};
+
+if(!Attacklab.wmd)
+{
+	Attacklab.wmd = function()
+	{
+		Attacklab.loadEnv = function()
+		{
+			var mergeEnv = function(env)
+			{
+				if(!env)
+				{
+					return;
+				}
+			
+				for(var key in env)
+				{
+					Attacklab.wmd_env[key] = env[key];
+				}
+			};
+			
+			mergeEnv(Attacklab.wmd_defaults);
+			mergeEnv(Attacklab.account_options);
+			mergeEnv(top[&quot;wmd_options&quot;]);
+			Attacklab.full = true;
+			
+			var defaultButtons = &quot;bold italic link blockquote code image ol ul heading hr&quot;;
+			Attacklab.wmd_env.buttons = Attacklab.wmd_env.buttons || defaultButtons;
+		};
+		Attacklab.loadEnv();
+
+	};
+	
+	Attacklab.wmd();
+	Attacklab.wmdBase();
+	Attacklab.Util.startEditor();
+};
+</diff>
      <filename>static/upstream/js/vendor.js</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>c22c031492b8b8d1dd3581183cde2ddc3d8be84a</id>
    </parent>
    <parent>
      <id>89193d47fd2440908c1de0152890e73b2964c7b3</id>
    </parent>
  </parents>
  <author>
    <name>Edward Betts</name>
    <email>edwardbetts@gmail.com</email>
  </author>
  <url>http://github.com/openlibrary/openlibrary/commit/f5e6d925554ba1a9421e18684c4e261376297287</url>
  <id>f5e6d925554ba1a9421e18684c4e261376297287</id>
  <committed-date>2009-10-08T07:42:05-07:00</committed-date>
  <authored-date>2009-10-08T07:42:05-07:00</authored-date>
  <message>Merge branch 'master' of git@github.com:openlibrary/openlibrary</message>
  <tree>0d5f9afa901180992be95fc4211c1f7960b2da46</tree>
  <committer>
    <name>Edward Betts</name>
    <email>edwardbetts@gmail.com</email>
  </committer>
</commit>
