├── .gitignore ├── README.md ├── css ├── print_style.css ├── style.css └── theme-balupton.css ├── icon_128.png ├── index.html ├── issues.html ├── js ├── functions.js └── jquery.syntaxhighlighter.min.js ├── manifest.json ├── screenshot.png └── wmd ├── showdown.js ├── wmd-toolbar.png ├── wmd.css └── wmd.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## What is it? 2 | 3 | This is a Markdown editor, which is based on [WMD](http://github.com/innocead/wmd), the editor from the [Stack Exchange](stackexchange.com) sites. It uses jQuery and the [jQuery Syntax Highlighter by balupton](http://balupton.com/projects/jquery-syntaxhighlighter). 4 | 5 | You can see a live version of it [on my homepage](http://homepage.univie.ac.at/werner.robitza/markdown). 6 | 7 | ## Screenshot 8 | 9 | ![](http://homepage.univie.ac.at/werner.robitza/markdown/screenshot.png) 10 | 11 | ## Issues 12 | 13 | Probably quite a few. Report them on github, please. -------------------------------------------------------------------------------- /css/print_style.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin:0; 3 | padding:0; 4 | } 5 | 6 | body { 7 | font-family: "Helvetica Neue", Helvetica, Verdana, sans-serif; 8 | font-size: 12px; 9 | color: #333; 10 | } 11 | 12 | 13 | textarea { 14 | width:580px; 15 | } 16 | 17 | #tools { 18 | margin-top:20px; 19 | } 20 | 21 | #tools .toolbutton { 22 | width: auto; 23 | float:left; 24 | margin-right:5px; 25 | padding: 5px; 26 | border: 1px solid #999; 27 | border-radius:8px; 28 | background-color: #eee; 29 | } 30 | 31 | 32 | 33 | 34 | /* CONTENT STYLING */ 35 | #preview p, dt, dd { 36 | font-size: 12px; 37 | line-height: 15px; 38 | text-align: justify; 39 | margin-bottom:10px; 40 | } 41 | 42 | #preview pre, blockquote { 43 | line-height: 1.714em; 44 | margin-bottom: 1.714em; 45 | } 46 | 47 | #preview h1 { 48 | font-size: 24px; 49 | line-height: 30px; 50 | margin-top: 20px; 51 | margin-bottom: 15px; 52 | } 53 | 54 | #preview h2 { 55 | font-size: 20px; 56 | line-height: 24px; 57 | margin-top: 14px; 58 | margin-bottom: 10px; 59 | } 60 | 61 | #preview h3 { 62 | font-size: 14px; 63 | line-height: 18px; 64 | margin-top: 10px; 65 | margin-bottom: 8px; 66 | } 67 | 68 | #preview h4 { 69 | font-size: 12px; 70 | line-height: 14px; 71 | margin-top: 10px; 72 | margin-bottom: 8px; 73 | } 74 | 75 | #preview hr { 76 | margin-top: 1.6em; 77 | } 78 | 79 | #preview input { 80 | font-size: 1.0em; 81 | } 82 | 83 | #preview ul { 84 | list-style: square; 85 | } 86 | 87 | #preview li { 88 | padding-bottom:10px; 89 | } 90 | #preview a { 91 | color:#069; 92 | text-decoration: none; 93 | } 94 | #preview a:hover { 95 | text-decoration: underline; 96 | } 97 | 98 | #preview blockquote { 99 | margin-left:5px; 100 | padding:1px; 101 | padding-left:10px; 102 | padding-right:10px; 103 | border-left:2px double #999; 104 | color: #C0C0C0; 105 | } 106 | 107 | #preview code, pre { 108 | font-family: Consolas, 'Bitstream Vera Sans Mono', 'Courier New', Courier, monospace; 109 | color: #808080; 110 | font-size:1.0em; 111 | } 112 | 113 | #preview pre { 114 | padding:5px; 115 | margin-left:5px; 116 | display:block; 117 | width:auto; 118 | border-left:1px dotted #999; 119 | overflow: wrap; 120 | white-space: pre-wrap; /* css-3 */ 121 | white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ 122 | white-space: -pre-wrap; /* Opera 4-6 */ 123 | white-space: -o-pre-wrap; /* Opera 7 */ 124 | word-wrap: break-word; /* Internet Explorer 5.5+ */ 125 | } 126 | 127 | /* HAX: Disable all borders from prettify */ 128 | #preview pre *, #preview code { 129 | border:none !important; 130 | } 131 | 132 | #preview code { 133 | display:inline; 134 | } 135 | 136 | #preview ul, #preview ol { 137 | margin-left:20px; 138 | } 139 | 140 | #footer { 141 | height:20px; 142 | width:100%; 143 | 144 | position: fixed; 145 | bottom: 0px; 146 | 147 | padding:10px; 148 | 149 | border-top:1px solid #999; 150 | 151 | background-color:#333; 152 | font-size:0.9em; 153 | color:#aaa; 154 | } 155 | 156 | #footer a { 157 | color: #aaa; 158 | } -------------------------------------------------------------------------------- /css/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin:0; 3 | padding:0; 4 | } 5 | 6 | body { 7 | font-family: "Helvetica Neue", Helvetica, Verdana, sans-serif; 8 | font-size: 12px; 9 | color: #333; 10 | } 11 | 12 | /* LEFT SIDE: EDITOR */ 13 | #editor { 14 | width:580px; 15 | position:fixed; 16 | left:20px; 17 | top:20px; 18 | } 19 | 20 | textarea { 21 | width:580px; 22 | } 23 | 24 | #tools { 25 | margin-top:20px; 26 | } 27 | 28 | #tools .toolbutton { 29 | width: auto; 30 | float:left; 31 | margin-right:5px; 32 | padding: 5px; 33 | border: 1px solid #999; 34 | border-radius:8px; 35 | background-color: #eee; 36 | } 37 | 38 | 39 | /* RIGHT SIDE: PREVIEW */ 40 | #preview { 41 | min-height:450px; 42 | width:600px; 43 | padding: 20px; 44 | border-left:1px solid #999; 45 | position:relative; 46 | left:620px; 47 | color:#000; 48 | } 49 | 50 | 51 | /* CONTENT STYLING */ 52 | #preview p, dt, dd { 53 | font-size: 12px; 54 | line-height: 15px; 55 | text-align: justify; 56 | margin-bottom:10px; 57 | } 58 | 59 | #preview pre, blockquote { 60 | line-height: 1.714em; 61 | margin-bottom: 1.714em; 62 | } 63 | 64 | #preview h1 { 65 | font-size: 24px; 66 | line-height: 30px; 67 | margin-top: 20px; 68 | margin-bottom: 15px; 69 | } 70 | 71 | #preview h2 { 72 | font-size: 20px; 73 | line-height: 24px; 74 | margin-top: 14px; 75 | margin-bottom: 10px; 76 | } 77 | 78 | #preview h3 { 79 | font-size: 14px; 80 | line-height: 18px; 81 | margin-top: 10px; 82 | margin-bottom: 8px; 83 | } 84 | 85 | #preview h4 { 86 | font-size: 12px; 87 | line-height: 14px; 88 | margin-top: 10px; 89 | margin-bottom: 8px; 90 | } 91 | 92 | #preview hr { 93 | margin-top: 1.6em; 94 | } 95 | 96 | #preview input { 97 | font-size: 1.0em; 98 | } 99 | 100 | #preview ul { 101 | list-style: square; 102 | } 103 | 104 | #preview li { 105 | padding-bottom:10px; 106 | } 107 | #preview a { 108 | color:#069; 109 | text-decoration: none; 110 | } 111 | #preview a:hover { 112 | text-decoration: underline; 113 | } 114 | 115 | #preview blockquote { 116 | margin-left:5px; 117 | padding:1px; 118 | padding-left:10px; 119 | padding-right:10px; 120 | border-left:1px solid #999; 121 | background-color:#eee; 122 | } 123 | 124 | #preview code, pre { 125 | font-family: Consolas, 'Bitstream Vera Sans Mono', 'Courier New', Courier, monospace; 126 | background-color:#eee; 127 | font-size:1.0em; 128 | } 129 | 130 | #preview pre { 131 | padding:5px; 132 | margin-left:5px; 133 | display:block; 134 | width:auto; 135 | overflow: auto !important; 136 | } 137 | 138 | /* HAX: Disable all borders from prettify */ 139 | #preview pre *, #preview code { 140 | border:none !important; 141 | } 142 | 143 | #preview code { 144 | display:inline; 145 | } 146 | 147 | #preview ul, #preview ol { 148 | margin-left:20px; 149 | } 150 | 151 | #footer { 152 | height:20px; 153 | width:100%; 154 | 155 | position: fixed; 156 | bottom: 0px; 157 | 158 | padding:10px; 159 | 160 | border-top:1px solid #999; 161 | 162 | background-color:#333; 163 | font-size:0.9em; 164 | color:#aaa; 165 | } 166 | 167 | #footer a { 168 | color: #aaa; 169 | } -------------------------------------------------------------------------------- /css/theme-balupton.css: -------------------------------------------------------------------------------- 1 | .prettyprint { 2 | padding: 0px !important; /* Hack for inline code */ 3 | } 4 | .prettyprint.theme-balupton .com { 5 | color: #008200; /* balupton green */ 6 | } 7 | .prettyprint.theme-balupton .lit { 8 | color: #066; /* google green */ 9 | } 10 | .prettyprint.theme-balupton.lang-html .lit { 11 | /* CSS Property Value */ 12 | color: #066; /* google green */ 13 | } 14 | .prettyprint.theme-balupton.lang-html .kwd { 15 | /* CSS Property Value Keyword */ 16 | color: #066; /* google green */ 17 | font-weight:bold; 18 | } 19 | .prettyprint.theme-balupton.lang-html .atv + .pln, 20 | .prettyprint.theme-balupton.lang-html .pun + .pln { 21 | /* CSS Property Name */ 22 | color: blue; 23 | } 24 | .prettyprint.theme-balupton .atv, 25 | .prettyprint.theme-balupton .str { 26 | color: blue; 27 | } 28 | .prettyprint.theme-balupton .atn { 29 | color: gray; 30 | } 31 | .prettyprint.theme-balupton .pln { 32 | color: black; 33 | } 34 | .prettyprint.theme-balupton .pun { 35 | color: #666; 36 | } 37 | .prettyprint.theme-balupton .typ { 38 | color: #606; 39 | } 40 | .prettyprint.theme-balupton .tag, 41 | .prettyprint.theme-balupton .kwd { 42 | color: #006699; 43 | font-weight: bold; 44 | } -------------------------------------------------------------------------------- /icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slhck/online-markdown-editor/dee57008d5e59940c78eb70be07c0893d2779588/icon_128.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | online markdown editor 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 | 18 |
19 | 20 | 21 | 22 | 23 |
24 |
25 |
26 |
27 | 28 | 29 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /issues.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | online markdown editor - issues 5 | 6 | 7 | 8 | 9 | 10 |
11 |

Report issues on github please. I don't know if I have time to look into them though.

12 |
13 | 14 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /js/functions.js: -------------------------------------------------------------------------------- 1 | // Selects all the preview text 2 | function selectAll() { 3 | selectElementText(document.getElementById("preview")); 4 | } 5 | 6 | // Copies text to clipboard 7 | function copyAll() { 8 | alert("Not implemented yet!"); 9 | } 10 | 11 | // http://stackoverflow.com/questions/985272/jquery-selecting-text-in-an-element-akin-to-highlighting-with-your-mouse/2838358#2838358 12 | function selectElementText(el, win) { 13 | win = win || window; 14 | var doc = win.document, sel, range; 15 | if (win.getSelection && doc.createRange) { 16 | sel = win.getSelection(); 17 | range = doc.createRange(); 18 | range.selectNodeContents(el); 19 | sel.removeAllRanges(); 20 | sel.addRange(range); 21 | } else if (doc.body.createTextRange) { 22 | range = doc.body.createTextRange(); 23 | range.moveToElementText(el); 24 | range.select(); 25 | range.execCommand("Copy"); 26 | } 27 | } 28 | $(document).ready(function(){ 29 | $(".wmd-input").bind('keydown', function(e){ 30 | var TABKEY = 9; 31 | if(e.keyCode == TABKEY) { 32 | this.value += " "; 33 | if(e.preventDefault) { 34 | e.preventDefault(); 35 | } 36 | return false; 37 | } 38 | }); 39 | }); 40 | 41 | function toHtml() { 42 | OpenWindow=window.open("", "newwin", "height=842, width=750,toolbar=no,scrollbars=yes,menubar=no"); 43 | var target = document.getElementById('preview'); 44 | var wrap = document.createElement('body'); 45 | wrap.appendChild(target.cloneNode(true)); 46 | OpenWindow.document.write("\ 47 | \ 48 | online markdown editor\ 49 | \ 50 | \ 51 | \ 52 | \ 53 | "); 54 | OpenWindow.document.write(wrap.innerHTML); 55 | OpenWindow.document.close(); 56 | } 57 | -------------------------------------------------------------------------------- /js/jquery.syntaxhighlighter.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @depends nothing 3 | * @name core.console 4 | * @package jquery-sparkle {@link http://balupton.com/projects/jquery-sparkle} 5 | */ 6 | 7 | /** 8 | * Console Emulator 9 | * We have to convert arguments into arrays, and do this explicitly as webkit (chrome) hates function references, and arguments cannot be passed as is 10 | * @version 1.0.3 11 | * @date August 31, 2010 12 | * @since 0.1.0-dev, December 01, 2009 13 | * @package jquery-sparkle {@link http://balupton.com/projects/jquery-sparkle} 14 | * @author Benjamin "balupton" Lupton {@link http://balupton.com} 15 | * @copyright (c) 2009-2010 Benjamin Arthur Lupton {@link http://balupton.com} 16 | * @license MIT License {@link http://creativecommons.org/licenses/MIT/} 17 | */ 18 | 19 | // Check to see if console exists, if not define it 20 | if ( typeof window.console === 'undefined' ) { 21 | window.console = {}; 22 | } 23 | 24 | // Check to see if we have emulated the console yet 25 | if ( typeof window.console.emulated === 'undefined' ) { 26 | // Emulate Log 27 | if ( typeof window.console.log === 'function' ) { 28 | window.console.hasLog = true; 29 | } 30 | else { 31 | if ( typeof window.console.log === 'undefined' ) { 32 | window.console.log = function(){}; 33 | } 34 | window.console.hasLog = false; 35 | } 36 | 37 | // Emulate Debug 38 | if ( typeof window.console.debug === 'function' ) { 39 | window.console.hasDebug = true; 40 | } 41 | else { 42 | if ( typeof window.console.debug === 'undefined' ) { 43 | window.console.debug = !window.console.hasLog ? function(){} : function(){ 44 | var arr = ['console.debug:']; for(var i = 0; i < arguments.length; i++) { arr.push(arguments[i]); }; 45 | window.console.log.apply(window.console, arr); 46 | }; 47 | } 48 | window.console.hasDebug = false; 49 | } 50 | 51 | // Emulate Warn 52 | if ( typeof window.console.warn === 'function' ) { 53 | window.console.hasWarn = true; 54 | } 55 | else { 56 | if ( typeof window.console.warn === 'undefined' ) { 57 | window.console.warn = !window.console.hasLog ? function(){} : function(){ 58 | var arr = ['console.warn:']; for(var i = 0; i < arguments.length; i++) { arr.push(arguments[i]); }; 59 | window.console.log.apply(window.console, arr); 60 | }; 61 | } 62 | window.console.hasWarn = false; 63 | } 64 | 65 | // Emulate Error 66 | if ( typeof window.console.error === 'function' ) { 67 | window.console.hasError = true; 68 | } 69 | else { 70 | if ( typeof window.console.error === 'undefined' ) { 71 | window.console.error = function(){ 72 | var msg = "An error has occured."; 73 | 74 | // Log 75 | if ( window.console.hasLog ) { 76 | var arr = ['console.error:']; for(var i = 0; i < arguments.length; i++) { arr.push(arguments[i]); }; 77 | window.console.log.apply(window.console, arr); 78 | // Adjust Message 79 | msg = 'An error has occured. More information is available in your browser\'s javascript console.' 80 | } 81 | 82 | // Prepare Arguments 83 | for ( var i = 0; i < arguments.length; ++i ) { 84 | if ( typeof arguments[i] !== 'string' ) { 85 | break; 86 | } 87 | msg += "\n"+arguments[i]; 88 | } 89 | 90 | // Throw Error 91 | if ( typeof Error !== 'undefined' ) { 92 | throw new Error(msg); 93 | } 94 | else { 95 | throw(msg); 96 | } 97 | }; 98 | } 99 | window.console.hasError = false; 100 | } 101 | 102 | // Emulate Trace 103 | if ( typeof window.console.trace === 'function' ) { 104 | window.console.hasTrace = true; 105 | } 106 | else { 107 | if ( typeof window.console.trace === 'undefined' ) { 108 | window.console.trace = function(){ 109 | window.console.error('console.trace does not exist'); 110 | }; 111 | } 112 | window.console.hasTrace = false; 113 | } 114 | 115 | // Done 116 | window.console.emulated = true; 117 | } 118 | /** 119 | * @depends jquery 120 | * @name jquery.appendscriptstyle 121 | * @package jquery-sparkle {@link http://balupton.com/projects/jquery-sparkle} 122 | */ 123 | 124 | /** 125 | * jQuery Aliaser 126 | */ 127 | (function($){ 128 | 129 | /** 130 | * Append a Stylesheet to the DOM 131 | * @version 1.1.0 132 | * @date July 23, 2010 133 | * @since 1.0.0, June 30, 2010 134 | * @package jquery-sparkle {@link http://balupton.com/projects/jquery-sparkle} 135 | * @author Benjamin "balupton" Lupton {@link http://balupton.com} 136 | * @copyright (c) 2009-2010 Benjamin Arthur Lupton {@link http://balupton.com} 137 | * @license MIT License {@link http://creativecommons.org/licenses/MIT/} 138 | */ 139 | $.appendStylesheet = $.appendStylesheet || function(url, overwrite){ 140 | // Check 141 | if ( !(document.body||false) ) { 142 | setTimeout(function(){ 143 | $.appendStylesheet.apply($,[url,overwrite]); 144 | },500); 145 | // Chain 146 | return $; 147 | } 148 | 149 | // Prepare 150 | var id = 'stylesheet-'+url.replace(/[^a-zA-Z0-9]/g, '');; 151 | var $old = $('#'+id); 152 | if ( typeof overwrite === 'undefined' ) { 153 | overwrite = false; 154 | } 155 | 156 | // Check 157 | if ( $old.length === 1 ) { 158 | if ( overwrite ) { 159 | $old.remove(); 160 | } 161 | else { 162 | // Chain 163 | return $; 164 | } 165 | } 166 | 167 | // Create 168 | var bodyEl = document.getElementsByTagName($.browser.safari ? 'head' : 'body')[0]; 169 | var linkEl = document.createElement('link'); 170 | linkEl.type = 'text/css'; 171 | linkEl.rel = 'stylesheet'; 172 | linkEl.media = 'screen'; 173 | linkEl.href = url; 174 | linkEl.id = id; 175 | bodyEl.appendChild(linkEl); 176 | 177 | // Chain 178 | return $; 179 | }; 180 | 181 | /** 182 | * Append a Script to the DOM 183 | * @version 1.1.0 184 | * @date July 23, 2010 185 | * @since 1.0.0, June 30, 2010 186 | * @package jquery-sparkle {@link http://balupton.com/projects/jquery-sparkle} 187 | * @author Benjamin "balupton" Lupton {@link http://balupton.com} 188 | * @copyright (c) 2009-2010 Benjamin Arthur Lupton {@link http://balupton.com} 189 | * @license MIT License {@link http://creativecommons.org/licenses/MIT/} 190 | */ 191 | $.appendScript = $.appendScript || function(url, overwrite){ 192 | // Check 193 | if ( !(document.body||false) ) { 194 | setTimeout(function(){ 195 | $.appendScript.apply($,[url,overwrite]); 196 | },500); 197 | // Chain 198 | return $; 199 | } 200 | 201 | // Prepare 202 | var id = 'script-'+url.replace(/[^a-zA-Z0-9]/g, '');; 203 | var $old = $('#'+id); 204 | if ( typeof overwrite === 'undefined' ) { 205 | overwrite = false; 206 | } 207 | 208 | // Check 209 | if ( $old.length === 1 ) { 210 | if ( overwrite ) { 211 | $old.remove(); 212 | } 213 | else { 214 | // Chain 215 | return $; 216 | } 217 | } 218 | 219 | // Create 220 | var bodyEl = document.getElementsByTagName($.browser.safari ? 'head' : 'body')[0]; 221 | var scriptEl = document.createElement('script'); 222 | scriptEl.type = 'text/javascript'; 223 | scriptEl.src = url; 224 | scriptEl.id = id; 225 | bodyEl.appendChild(scriptEl); 226 | 227 | // Chain 228 | return $; 229 | }; 230 | 231 | 232 | })(jQuery); 233 | /** 234 | * @depends core.console, jquery, jquery.appendscriptstyle 235 | * @name jquery.syntaxhighlighter 236 | * @package jquery-syntaxhighlighter {@link http://balupton.com/projects/jquery-syntaxhighlighter} 237 | */ 238 | 239 | /** 240 | * jQuery Aliaser 241 | */ 242 | (function($){ 243 | 244 | /** 245 | * Get all elements within ourself which match the selector, and include ourself in the search 246 | * @version 1.0.0 247 | * @date June 30, 2010 248 | * @package jquery-sparkle {@link http://balupton.com/projects/jquery-sparkle} 249 | * @author Benjamin "balupton" Lupton {@link http://balupton.com} 250 | * @copyright (c) 2009-2010 Benjamin Arthur Lupton {@link http://balupton.com} 251 | * @license MIT License {@link http://creativecommons.org/licenses/MIT/} 252 | */ 253 | $.fn.findAndSelf = $.fn.findAndSelf || function(selector){ 254 | var $this = $(this); 255 | return $this.find(selector).andSelf().filter(selector); 256 | }; 257 | 258 | /** 259 | * Add the String replace method to the Number prototype 260 | * This is to fix an error with jQuery v1.4.2 when $('#el').val() contains a numeric value on Firefox. 261 | * Error is here: http://getsatisfaction.com/balupton/topics/word_jumbles 262 | * @version 1.0.0 263 | * @date September 01, 2010 264 | * @package jquery-sparkle {@link http://balupton.com/projects/jquery-sparkle} 265 | * @author Benjamin "balupton" Lupton {@link http://balupton.com} 266 | * @copyright (c) 2009-2010 Benjamin Arthur Lupton {@link http://balupton.com} 267 | * @license MIT License {@link http://creativecommons.org/licenses/MIT/} 268 | */ 269 | Number.prototype.replace = Number.prototype.replace || function(){ 270 | var str = String(this); 271 | return str.replace.apply(this,arguments); 272 | } 273 | 274 | /** 275 | * jQuery SyntaxHighlighter 276 | * @version 1.0.1-beta 277 | * @date August 16, 2010 278 | * @since 0.1.0-dev, July 23, 2010 279 | * @package jquery-syntaxhighlighter {@link http://balupton.com/projects/jquery-syntaxhighlighter} 280 | * @author Benjamin "balupton" Lupton {@link http://balupton.com} 281 | * @copyright (c) 2009-2010 Benjamin Arthur Lupton {@link http://balupton.com} 282 | * @license MIT License {@link http://creativecommons.org/licenses/MIT/} 283 | */ 284 | if ( !($.SyntaxHighlighter||false) ) { 285 | $.SyntaxHighlighter = { 286 | // Configuration 287 | 'config': { 288 | /** 289 | * Whether or not we should load in Google Prettify automatically if it was not detected. 290 | */ 291 | 'load': true, 292 | 293 | /** 294 | * Whether or not we should highlight all appropriate code blocks automatically once the page has finished loading. 295 | */ 296 | 'highlight': true, 297 | 298 | /** 299 | * Whether or not we should output debug information in case something is not working correctly. 300 | */ 301 | 'debug': false, 302 | 303 | /** 304 | * Whether or not we should wrap the code blocks lines, or have them scrollable. 305 | */ 306 | 'wrapLines': true, 307 | 308 | /** 309 | * Whether or not we should display line numbers next to the code blocks. 310 | */ 311 | 'lineNumbers': true, 312 | 313 | /** 314 | * Whether or not we should strip empty start and finish lines from the code blocks. 315 | */ 316 | 'stripEmptyStartFinishLines': true, 317 | 318 | /** 319 | * Whether or not we should remove whitespaces/indentations which are only there for HTML formatting of our code block. 320 | */ 321 | 'stripInitialWhitespace': true, 322 | 323 | /** 324 | * Whether or not we should alternate the lines background colours on odd and even rows. 325 | */ 326 | 'alternateLines': false, 327 | 328 | /** 329 | * The default class to look for in case we have not explicitly specified a language. 330 | */ 331 | 'defaultClassname': 'highlight', 332 | 333 | /** 334 | * The theme that should be used by our highlighted code blocks. 335 | */ 336 | 'theme': 'balupton', 337 | 338 | /** 339 | * The themes to load in for use with our highlighted code blocks. 340 | */ 341 | 'themes': ['balupton'], 342 | 343 | /** 344 | * Whether or not we should add a Syntax Highlighter Sparkle extension if jQuery Sparkle is detected. 345 | */ 346 | 'addSparkleExtension': true, 347 | 348 | /** 349 | * The baseUrl to load Google's Prettify from. 350 | * This is used to load in Google's Prettify if the load option is true and it was not found. 351 | */ 352 | 'prettifyBaseUrl': false ? 'http://192.168.1.2/repos/jquery-syntaxhighlighter/prettify' : 'http://balupton.github.com/jquery-syntaxhighlighter/prettify', 353 | 354 | /** 355 | * The baseUrl to load our Syntax Highlighter from. 356 | * This is used to load in the stylesheet and additional themes. 357 | */ 358 | 'baseUrl': false ? 'http://192.168.1.2/repos/jquery-syntaxhighlighter' : 'http://balupton.github.com/jquery-syntaxhighlighter' 359 | }, 360 | 361 | // Init 362 | init: function(options){ 363 | // Prepare 364 | var SyntaxHighlighter = this, 365 | config = SyntaxHighlighter.config; 366 | 367 | // Fix baseUrl 368 | var baseUrl = config.baseUrl; 369 | if ( baseUrl[baseUrl.length-1] === '/' ) { 370 | config.baseUrl = baseUrl.substr(0,baseUrl.length-2); 371 | } 372 | delete baseUrl; 373 | 374 | // Configure 375 | $.extend(true, SyntaxHighlighter.config, options||{}); 376 | 377 | // Sparkle 378 | if ( $.Sparkle||false && config.addSparkleExtension ) { 379 | // Add Syntax Highlighter to Sparkle 380 | $.Sparkle.addExtension('syntaxhighlighter', function(){ 381 | $(this).syntaxHighlight(); 382 | }); 383 | } 384 | 385 | // Attach 386 | $.fn.syntaxHighlight = $.fn.SyntaxHighlight = SyntaxHighlighter.fn; 387 | 388 | // Load 389 | if ( config.load ) SyntaxHighlighter.load(); 390 | 391 | // Highlight 392 | if ( config.highlight ) SyntaxHighlighter.highlight(); 393 | 394 | // Chain 395 | return this; 396 | }, 397 | 398 | // Load 399 | load: function(){ 400 | // Prepare 401 | var SyntaxHighlighter = this, 402 | config = SyntaxHighlighter.config, 403 | prettifyBaseUrl = config.prettifyBaseUrl, 404 | baseUrl = config.baseUrl, 405 | themes = config.themes; 406 | 407 | // Append 408 | if ( !SyntaxHighlighter.loaded() ) { 409 | $.appendScript(prettifyBaseUrl+'/prettify.min.js'); 410 | $.appendStylesheet(prettifyBaseUrl+'/prettify.min.css'); 411 | $.appendStylesheet(baseUrl+'/styles/style.min.css'); 412 | $.each(themes,function(i,theme){ 413 | $.appendStylesheet(baseUrl+'/styles/theme-'+theme+'.min.css'); 414 | }); 415 | if ( $.browser.msie ) { 416 | $.appendStylesheet(baseUrl+'/styles/ie.min.css'); 417 | } 418 | SyntaxHighlighter.loadedExtras = true; 419 | } 420 | 421 | // Chain 422 | return this; 423 | }, 424 | 425 | // Loaded Extras 426 | loadedExtras: false, 427 | 428 | // Loaded 429 | loaded: function(){ 430 | return typeof prettyPrint !== 'undefined' && this.loadedExtras; 431 | }, 432 | 433 | // Determine Language 434 | determineLanguage: function(css){ 435 | // Prepare 436 | var language = null, 437 | regex = /lang(uage)?-([a-z0-9]+)/g, 438 | match = regex.exec(css); 439 | 440 | // Handle 441 | while ( match !== null ) { 442 | language = match[2]; 443 | match = regex.exec(css); 444 | } 445 | 446 | // Return langauge 447 | return language; 448 | }, 449 | 450 | // jQuery Function 451 | fn: function(){ 452 | // Prepare 453 | var SyntaxHighlighter = $.SyntaxHighlighter, 454 | config = SyntaxHighlighter.config, 455 | $el = $(this); 456 | 457 | // Highlight 458 | $.SyntaxHighlighter.highlight({ 459 | 'el': $el 460 | }); 461 | 462 | // Chain 463 | return this; 464 | }, 465 | 466 | // Highlight 467 | highlight: function(params){ 468 | // Prepare 469 | if ( typeof params !== 'object' ) { 470 | params = {}; 471 | } 472 | var SyntaxHighlighter = this, 473 | config = SyntaxHighlighter.config, 474 | $el = params.el||false; 475 | 476 | // Adjust 477 | if ( !($el instanceof jQuery) ) { 478 | $el = $('body'); 479 | } 480 | 481 | // Check 482 | if ( !SyntaxHighlighter.loaded() ) { 483 | if ( config.debug ) window.console.debug('SyntaxHighlighter.highlight: Chosen SyntaxHighlighter is not yet defined. Waiting 1200 ms then trying again.'); 484 | setTimeout(function(){ 485 | SyntaxHighlighter.highlight.apply(SyntaxHighlighter, [params]); 486 | },1200); 487 | return; 488 | } 489 | 490 | // Prepare Classnames 491 | var defaultClassname = config.defaultClassname, 492 | defaultSelector = ''; 493 | if ( typeof defaultClassname === 'array' ) { 494 | defaultSelector = '.'+defaultClassname.join(',.'); 495 | defaultClassname = defaultClassname.join(' '); 496 | } 497 | else { 498 | defaultClassname = String(defaultClassname); 499 | defaultSelector = '.'+defaultClassname.replace(' ',',.'); 500 | } 501 | 502 | // Check Classnames 503 | if ( defaultSelector === '.' || !defaultClassname ) { 504 | window.console.error('SyntaxHighlighter.highlight: Invalid defaultClassname.', [this,arguments], [config.defaultClassname]); 505 | window.console.trace(); 506 | } 507 | 508 | // Fetch 509 | var $codes = $el.findAndSelf('code,pre').filter('[class*=lang],'+defaultSelector).filter(':not(.prettyprint)'); 510 | 511 | // Highlight 512 | $codes.css({ 513 | 'overflow-y': 'visible', 514 | 'overflow-x': 'visible', 515 | 'white-space': 'pre' 516 | }).addClass('prettyprint '+defaultClassname).each(function(){ 517 | // Prepare 518 | var $code = $(this), 519 | css = $code.attr('class'), 520 | language = SyntaxHighlighter.determineLanguage(css); 521 | 522 | // Language 523 | $code.addClass('lang-'+language); 524 | }); 525 | 526 | // WrapLines 527 | if ( config.lineNumbers ) { 528 | $codes.addClass('linenums'); 529 | } 530 | 531 | // Theme 532 | if ( config.theme ) { 533 | $codes.addClass('theme-'+config.theme); 534 | } 535 | 536 | // AlternateLines 537 | if ( config.alternateLines ) { 538 | $codes.addClass('alternate'); 539 | } 540 | 541 | // Fire 542 | prettyPrint(); 543 | 544 | // Adjust HTML: stripEmptyStartFinishLines 545 | // we have to do this here, as before prettyPrint IE has issues with newlines 546 | if ( config.stripEmptyStartFinishLines ) { 547 | $codes.find('li:first-child > :first-child, li:last-child > :first-child').each(function(){ 548 | // Prepare 549 | var $initialText = $(this), 550 | html = $initialText.html(), 551 | empty = /^([\r\n\s\t]|\ )*$/.test(html), 552 | $parent = $initialText.parent(), 553 | $siblings = $initialText.siblings(); 554 | 555 | // Check 556 | if ( empty && ($siblings.length === 0 || ($siblings.length === 1 && $siblings.filter(':last').is('br'))) ) { 557 | // Remove Line 558 | var $parent = $initialText.parent(), 559 | value = $parent.val(); 560 | $parent.next().val(value); 561 | $parent.remove(); 562 | } 563 | }); 564 | } 565 | 566 | // Adjust HTML: stripInitialWhitespace 567 | // we have to do this here, as before prettyPrint IE has issues with newlines 568 | if ( config.stripInitialWhitespace ) { 569 | $codes.find('li:first-child > :first-child').each(function(){ 570 | // Prepare 571 | var $initialText = $(this), 572 | html = $initialText.html(), 573 | match = html.match(/^(([\r\n\s\t]|\ )+)/)||[], 574 | whitespace = (match[1]||''); 575 | 576 | // Check 577 | if ( whitespace.length ) { 578 | // Replace 579 | $initialText.parent().siblings().children(':first-child').add($initialText).each(function(){ 580 | // Prepare 581 | var $nextText = $(this), 582 | html = $nextText.html(); 583 | // Replace 584 | html = html.replace(new RegExp('^'+whitespace,'gm'), ''); 585 | // Apply 586 | $nextText.html(html); 587 | }); 588 | } 589 | }); 590 | } 591 | 592 | // Adjust Lines 593 | if ( config.wrapLines ) { 594 | $codes.css({ 595 | 'overflow-x':'hidden', 596 | 'overflow-y':'hidden', 597 | 'white-space':'pre-wrap', 598 | 'max-height':'none' 599 | }); 600 | } else { 601 | $codes.css({ 602 | 'overflow-x':'auto', 603 | 'overflow-y':'auto', 604 | 'white-space':'normal', 605 | 'max-height':'500px' 606 | }); 607 | } 608 | 609 | // Chain 610 | return this; 611 | } 612 | 613 | }; 614 | } 615 | else { 616 | window.console.warn("SyntaxHighlighter has already been defined..."); 617 | } 618 | 619 | })(jQuery); -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Markdown Editor", 3 | "description": "A very basic Markdown editor that outputs live, selectable HTML and supports syntax highlighting", 4 | "version": "0.0.0.1", 5 | "icons": { 6 | "128": "icon_128.png" 7 | }, 8 | "app": { 9 | "urls": [ 10 | "http://homepage.univie.ac.at/werner.robitza/markdown" 11 | ], 12 | "launch": { 13 | "web_url": "http://homepage.univie.ac.at/werner.robitza/markdown/index.html" 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slhck/online-markdown-editor/dee57008d5e59940c78eb70be07c0893d2779588/screenshot.png -------------------------------------------------------------------------------- /wmd/showdown.js: -------------------------------------------------------------------------------- 1 | // 2 | // showdown.js -- A javascript port of Markdown. 3 | // 4 | // Copyright (c) 2007 John Fraser. 5 | // 6 | // Original Markdown Copyright (c) 2004-2005 John Gruber 7 | // 8 | // 9 | // The full source distribution is at: 10 | // 11 | // A A L 12 | // T C A 13 | // T K B 14 | // 15 | // 16 | // 17 | 18 | // 19 | // Wherever possible, Showdown is a straight, line-by-line port 20 | // of the Perl version of Markdown. 21 | // 22 | // This is not a normal parser design; it's basically just a 23 | // series of string substitutions. It's hard to read and 24 | // maintain this way, but keeping Showdown close to the original 25 | // design makes it easier to port new features. 26 | // 27 | // More importantly, Showdown behaves like markdown.pl in most 28 | // edge cases. So web applications can do client-side preview 29 | // in Javascript, and then build identical HTML on the server. 30 | // 31 | // This port needs the new RegExp functionality of ECMA 262, 32 | // 3rd Edition (i.e. Javascript 1.5). Most modern web browsers 33 | // should do fine. Even with the new regular expression features, 34 | // We do a lot of work to emulate Perl's regex functionality. 35 | // The tricky changes in this file mostly have the "attacklab:" 36 | // label. Major or self-explanatory changes don't. 37 | // 38 | // Smart diff tools like Araxis Merge will be able to match up 39 | // this file with markdown.pl in a useful way. A little tweaking 40 | // helps: in a copy of markdown.pl, replace "#" with "//" and 41 | // replace "$text" with "text". Be sure to ignore whitespace 42 | // and line endings. 43 | // 44 | 45 | 46 | // 47 | // Showdown usage: 48 | // 49 | // var text = "Markdown *rocks*."; 50 | // 51 | // var converter = new Attacklab.showdown.converter(); 52 | // var html = converter.makeHtml(text); 53 | // 54 | // alert(html); 55 | // 56 | // Note: move the sample code to the bottom of this 57 | // file before uncommenting it. 58 | // 59 | 60 | 61 | // 62 | // Attacklab namespace 63 | // 64 | var Attacklab = Attacklab || {} 65 | 66 | // 67 | // Showdown namespace 68 | // 69 | Attacklab.showdown = Attacklab.showdown || {} 70 | 71 | // 72 | // converter 73 | // 74 | // Wraps all "globals" so that the only thing 75 | // exposed is makeHtml(). 76 | // 77 | Attacklab.showdown.converter = function() { 78 | 79 | // 80 | // Globals: 81 | // 82 | 83 | // Global hashes, used by various utility routines 84 | var g_urls; 85 | var g_titles; 86 | var g_html_blocks; 87 | 88 | // Used to track when we're inside an ordered or unordered list 89 | // (see _ProcessListItems() for details): 90 | var g_list_level = 0; 91 | 92 | 93 | this.makeHtml = function(text) { 94 | // 95 | // Main function. The order in which other subs are called here is 96 | // essential. Link and image substitutions need to happen before 97 | // _EscapeSpecialCharsWithinTagAttributes(), so that any *'s or _'s in the 98 | // and tags get encoded. 99 | // 100 | 101 | // Clear the global hashes. If we don't clear these, you get conflicts 102 | // from other articles when generating a page which contains more than 103 | // one article (e.g. an index page that shows the N most recent 104 | // articles): 105 | g_urls = new Array(); 106 | g_titles = new Array(); 107 | g_html_blocks = new Array(); 108 | 109 | // attacklab: Replace ~ with ~T 110 | // This lets us use tilde as an escape char to avoid md5 hashes 111 | // The choice of character is arbitray; anything that isn't 112 | // magic in Markdown will work. 113 | text = text.replace(/~/g,"~T"); 114 | 115 | // attacklab: Replace $ with ~D 116 | // RegExp interprets $ as a special character 117 | // when it's in a replacement string 118 | text = text.replace(/\$/g,"~D"); 119 | 120 | // Standardize line endings 121 | text = text.replace(/\r\n/g,"\n"); // DOS to Unix 122 | text = text.replace(/\r/g,"\n"); // Mac to Unix 123 | 124 | // Make sure text begins and ends with a couple of newlines: 125 | text = "\n\n" + text + "\n\n"; 126 | 127 | // Convert all tabs to spaces. 128 | text = _Detab(text); 129 | 130 | // Strip any lines consisting only of spaces and tabs. 131 | // This makes subsequent regexen easier to write, because we can 132 | // match consecutive blank lines with /\n+/ instead of something 133 | // contorted like /[ \t]*\n+/ . 134 | text = text.replace(/^[ \t]+$/mg,""); 135 | 136 | // Turn block-level HTML blocks into hash entries 137 | text = _HashHTMLBlocks(text); 138 | 139 | // Strip link definitions, store in hashes. 140 | text = _StripLinkDefinitions(text); 141 | 142 | text = _RunBlockGamut(text); 143 | 144 | text = _UnescapeSpecialChars(text); 145 | 146 | // attacklab: Restore dollar signs 147 | text = text.replace(/~D/g,"$$"); 148 | 149 | // attacklab: Restore tildes 150 | text = text.replace(/~T/g,"~"); 151 | 152 | return text; 153 | } 154 | 155 | var _StripLinkDefinitions = function(text) { 156 | // 157 | // Strips link definitions from text, stores the URLs and titles in 158 | // hash references. 159 | // 160 | 161 | // Link defs are in the form: ^[id]: url "optional title" 162 | 163 | /* 164 | var text = text.replace(/ 165 | ^[ ]{0,3}\[(.+)\]: // id = $1 attacklab: g_tab_width - 1 166 | [ \t]* 167 | \n? // maybe *one* newline 168 | [ \t]* 169 | ? // url = $2 170 | [ \t]* 171 | \n? // maybe one newline 172 | [ \t]* 173 | (?: 174 | (\n*) // any lines skipped = $3 attacklab: lookbehind removed 175 | ["(] 176 | (.+?) // title = $4 177 | [")] 178 | [ \t]* 179 | )? // title is optional 180 | (?:\n+|$) 181 | /gm, 182 | function(){...}); 183 | */ 184 | var text = text.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+)/gm, 185 | function (wholeMatch,m1,m2,m3,m4) { 186 | m1 = m1.toLowerCase(); 187 | g_urls[m1] = _EncodeAmpsAndAngles(m2); // Link IDs are case-insensitive 188 | if (m3) { 189 | // Oops, found blank lines, so it's not a title. 190 | // Put back the parenthetical statement we stole. 191 | return m3+m4; 192 | } else if (m4) { 193 | g_titles[m1] = m4.replace(/"/g,"""); 194 | } 195 | 196 | // Completely remove the definition from the text 197 | return ""; 198 | } 199 | ); 200 | 201 | return text; 202 | } 203 | 204 | var _HashHTMLBlocks = function(text) { 205 | // attacklab: Double up blank lines to reduce lookaround 206 | text = text.replace(/\n/g,"\n\n"); 207 | 208 | // Hashify HTML blocks: 209 | // We only want to do this for block-level HTML tags, such as headers, 210 | // lists, and tables. That's because we still want to wrap

s around 211 | // "paragraphs" that are wrapped in non-block-level tags, such as anchors, 212 | // phrase emphasis, and spans. The list of tags we're looking for is 213 | // hard-coded: 214 | var block_tags_a = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del" 215 | var block_tags_b = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math" 216 | 217 | // First, look for nested blocks, e.g.: 218 | //

219 | //
220 | // tags for inner block must be indented. 221 | //
222 | //
223 | // 224 | // The outermost tags must start at the left margin for this to match, and 225 | // the inner nested divs must be indented. 226 | // We need to do this before the next, more liberal match, because the next 227 | // match will start at the first `
` and stop at the first `
`. 228 | 229 | // attacklab: This regex can be expensive when it fails. 230 | /* 231 | var text = text.replace(/ 232 | ( // save in $1 233 | ^ // start of line (with /m) 234 | <($block_tags_a) // start tag = $2 235 | \b // word break 236 | // attacklab: hack around khtml/pcre bug... 237 | [^\r]*?\n // any number of lines, minimally matching 238 | // the matching end tag 239 | [ \t]* // trailing spaces/tabs 240 | (?=\n+) // followed by a newline 241 | ) // attacklab: there are sentinel newlines at end of document 242 | /gm,function(){...}}; 243 | */ 244 | text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del)\b[^\r]*?\n<\/\2>[ \t]*(?=\n+))/gm,hashElement); 245 | 246 | // 247 | // Now match more liberally, simply from `\n` to `\n` 248 | // 249 | 250 | /* 251 | var text = text.replace(/ 252 | ( // save in $1 253 | ^ // start of line (with /m) 254 | <($block_tags_b) // start tag = $2 255 | \b // word break 256 | // attacklab: hack around khtml/pcre bug... 257 | [^\r]*? // any number of lines, minimally matching 258 | .* // the matching end tag 259 | [ \t]* // trailing spaces/tabs 260 | (?=\n+) // followed by a newline 261 | ) // attacklab: there are sentinel newlines at end of document 262 | /gm,function(){...}}; 263 | */ 264 | text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math)\b[^\r]*?.*<\/\2>[ \t]*(?=\n+)\n)/gm,hashElement); 265 | 266 | // Special case just for
. It was easier to make a special case than 267 | // to make the other regex more complicated. 268 | 269 | /* 270 | text = text.replace(/ 271 | ( // save in $1 272 | \n\n // Starting after a blank line 273 | [ ]{0,3} 274 | (<(hr) // start tag = $2 275 | \b // word break 276 | ([^<>])*? // 277 | \/?>) // the matching end tag 278 | [ \t]* 279 | (?=\n{2,}) // followed by a blank line 280 | ) 281 | /g,hashElement); 282 | */ 283 | text = text.replace(/(\n[ ]{0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g,hashElement); 284 | 285 | // Special case for standalone HTML comments: 286 | 287 | /* 288 | text = text.replace(/ 289 | ( // save in $1 290 | \n\n // Starting after a blank line 291 | [ ]{0,3} // attacklab: g_tab_width - 1 292 | 295 | [ \t]* 296 | (?=\n{2,}) // followed by a blank line 297 | ) 298 | /g,hashElement); 299 | */ 300 | text = text.replace(/(\n\n[ ]{0,3}[ \t]*(?=\n{2,}))/g,hashElement); 301 | 302 | // PHP and ASP-style processor instructions ( and <%...%>) 303 | 304 | /* 305 | text = text.replace(/ 306 | (?: 307 | \n\n // Starting after a blank line 308 | ) 309 | ( // save in $1 310 | [ ]{0,3} // attacklab: g_tab_width - 1 311 | (?: 312 | <([?%]) // $2 313 | [^\r]*? 314 | \2> 315 | ) 316 | [ \t]* 317 | (?=\n{2,}) // followed by a blank line 318 | ) 319 | /g,hashElement); 320 | */ 321 | text = text.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g,hashElement); 322 | 323 | // attacklab: Undo double lines (see comment at top of this function) 324 | text = text.replace(/\n\n/g,"\n"); 325 | return text; 326 | } 327 | 328 | var hashElement = function(wholeMatch,m1) { 329 | var blockText = m1; 330 | 331 | // Undo double lines 332 | blockText = blockText.replace(/\n\n/g,"\n"); 333 | blockText = blockText.replace(/^\n/,""); 334 | 335 | // strip trailing blank lines 336 | blockText = blockText.replace(/\n+$/g,""); 337 | 338 | // Replace the element text with a marker ("~KxK" where x is its key) 339 | blockText = "\n\n~K" + (g_html_blocks.push(blockText)-1) + "K\n\n"; 340 | 341 | return blockText; 342 | }; 343 | 344 | var _RunBlockGamut = function(text) { 345 | // 346 | // These are all the transformations that form block-level 347 | // tags like paragraphs, headers, and list items. 348 | // 349 | text = _DoHeaders(text); 350 | 351 | // Do Horizontal Rules: 352 | var key = hashBlock("
"); 353 | text = text.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm,key); 354 | text = text.replace(/^[ ]{0,2}([ ]?-[ ]?){3,}[ \t]*$/gm,key); 355 | text = text.replace(/^[ ]{0,2}([ ]?_[ ]?){3,}[ \t]*$/gm,key); 356 | 357 | text = _DoLists(text); 358 | text = _DoCodeBlocks(text); 359 | text = _DoBlockQuotes(text); 360 | 361 | // We already ran _HashHTMLBlocks() before, in Markdown(), but that 362 | // was to escape raw HTML in the original Markdown source. This time, 363 | // we're escaping the markup we've just created, so that we don't wrap 364 | //

tags around block-level tags. 365 | text = _HashHTMLBlocks(text); 366 | text = _FormParagraphs(text); 367 | 368 | return text; 369 | } 370 | 371 | 372 | var _RunSpanGamut = function(text) { 373 | // 374 | // These are all the transformations that occur *within* block-level 375 | // tags like paragraphs, headers, and list items. 376 | // 377 | 378 | text = _DoCodeSpans(text); 379 | text = _EscapeSpecialCharsWithinTagAttributes(text); 380 | text = _EncodeBackslashEscapes(text); 381 | 382 | // Process anchor and image tags. Images must come first, 383 | // because ![foo][f] looks like an anchor. 384 | text = _DoImages(text); 385 | text = _DoAnchors(text); 386 | 387 | // Make links out of things like `` 388 | // Must come after _DoAnchors(), because you can use < and > 389 | // delimiters in inline links like [this](). 390 | text = _DoAutoLinks(text); 391 | text = _EncodeAmpsAndAngles(text); 392 | text = _DoItalicsAndBold(text); 393 | 394 | // Do hard breaks: 395 | text = text.replace(/ +\n/g,"
\n"); 396 | 397 | return text; 398 | } 399 | 400 | var _EscapeSpecialCharsWithinTagAttributes = function(text) { 401 | // 402 | // Within tags -- meaning between < and > -- encode [\ ` * _] so they 403 | // don't conflict with their use in Markdown for code, italics and strong. 404 | // 405 | 406 | // Build a regex to find HTML tags and comments. See Friedl's 407 | // "Mastering Regular Expressions", 2nd Ed., pp. 200-201. 408 | var regex = /(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|)/gi; 409 | 410 | text = text.replace(regex, function(wholeMatch) { 411 | var tag = wholeMatch.replace(/(.)<\/?code>(?=.)/g,"$1`"); 412 | tag = escapeCharacters(tag,"\\`*_"); 413 | return tag; 414 | }); 415 | 416 | return text; 417 | } 418 | 419 | var _DoAnchors = function(text) { 420 | // 421 | // Turn Markdown link shortcuts into XHTML
tags. 422 | // 423 | // 424 | // First, handle reference-style links: [link text] [id] 425 | // 426 | 427 | /* 428 | text = text.replace(/ 429 | ( // wrap whole match in $1 430 | \[ 431 | ( 432 | (?: 433 | \[[^\]]*\] // allow brackets nested one level 434 | | 435 | [^\[] // or anything else 436 | )* 437 | ) 438 | \] 439 | 440 | [ ]? // one optional space 441 | (?:\n[ ]*)? // one optional newline followed by spaces 442 | 443 | \[ 444 | (.*?) // id = $3 445 | \] 446 | )()()()() // pad remaining backreferences 447 | /g,_DoAnchors_callback); 448 | */ 449 | text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,writeAnchorTag); 450 | 451 | // 452 | // Next, inline-style links: [link text](url "optional title") 453 | // 454 | 455 | /* 456 | text = text.replace(/ 457 | ( // wrap whole match in $1 458 | \[ 459 | ( 460 | (?: 461 | \[[^\]]*\] // allow brackets nested one level 462 | | 463 | [^\[\]] // or anything else 464 | ) 465 | ) 466 | \] 467 | \( // literal paren 468 | [ \t]* 469 | () // no id, so leave $3 empty 470 | ? // href = $4 471 | [ \t]* 472 | ( // $5 473 | (['"]) // quote char = $6 474 | (.*?) // Title = $7 475 | \6 // matching quote 476 | [ \t]* // ignore any spaces/tabs between closing quote and ) 477 | )? // title is optional 478 | \) 479 | ) 480 | /g,writeAnchorTag); 481 | */ 482 | text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,writeAnchorTag); 483 | 484 | // 485 | // Last, handle reference-style shortcuts: [link text] 486 | // These must come last in case you've also got [link test][1] 487 | // or [link test](/foo) 488 | // 489 | 490 | /* 491 | text = text.replace(/ 492 | ( // wrap whole match in $1 493 | \[ 494 | ([^\[\]]+) // link text = $2; can't contain '[' or ']' 495 | \] 496 | )()()()()() // pad rest of backreferences 497 | /g, writeAnchorTag); 498 | */ 499 | text = text.replace(/(\[([^\[\]]+)\])()()()()()/g, writeAnchorTag); 500 | 501 | return text; 502 | } 503 | 504 | var writeAnchorTag = function(wholeMatch,m1,m2,m3,m4,m5,m6,m7) { 505 | if (m7 == undefined) m7 = ""; 506 | var whole_match = m1; 507 | var link_text = m2; 508 | var link_id = m3.toLowerCase(); 509 | var url = m4; 510 | var title = m7; 511 | 512 | if (url == "") { 513 | if (link_id == "") { 514 | // lower-case and turn embedded newlines into spaces 515 | link_id = link_text.toLowerCase().replace(/ ?\n/g," "); 516 | } 517 | url = "#"+link_id; 518 | 519 | if (g_urls[link_id] != undefined) { 520 | url = g_urls[link_id]; 521 | if (g_titles[link_id] != undefined) { 522 | title = g_titles[link_id]; 523 | } 524 | } 525 | else { 526 | if (whole_match.search(/\(\s*\)$/m)>-1) { 527 | // Special case for explicit empty url 528 | url = ""; 529 | } else { 530 | return whole_match; 531 | } 532 | } 533 | } 534 | 535 | url = escapeCharacters(url,"*_"); 536 | var result = ""; 545 | 546 | return result; 547 | } 548 | 549 | 550 | var _DoImages = function(text) { 551 | // 552 | // Turn Markdown image shortcuts into tags. 553 | // 554 | 555 | // 556 | // First, handle reference-style labeled images: ![alt text][id] 557 | // 558 | 559 | /* 560 | text = text.replace(/ 561 | ( // wrap whole match in $1 562 | !\[ 563 | (.*?) // alt text = $2 564 | \] 565 | 566 | [ ]? // one optional space 567 | (?:\n[ ]*)? // one optional newline followed by spaces 568 | 569 | \[ 570 | (.*?) // id = $3 571 | \] 572 | )()()()() // pad rest of backreferences 573 | /g,writeImageTag); 574 | */ 575 | text = text.replace(/(!\[(.*?)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,writeImageTag); 576 | 577 | // 578 | // Next, handle inline images: ![alt text](url "optional title") 579 | // Don't forget: encode * and _ 580 | 581 | /* 582 | text = text.replace(/ 583 | ( // wrap whole match in $1 584 | !\[ 585 | (.*?) // alt text = $2 586 | \] 587 | \s? // One optional whitespace character 588 | \( // literal paren 589 | [ \t]* 590 | () // no id, so leave $3 empty 591 | ? // src url = $4 592 | [ \t]* 593 | ( // $5 594 | (['"]) // quote char = $6 595 | (.*?) // title = $7 596 | \6 // matching quote 597 | [ \t]* 598 | )? // title is optional 599 | \) 600 | ) 601 | /g,writeImageTag); 602 | */ 603 | text = text.replace(/(!\[(.*?)\]\s?\([ \t]*()?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,writeImageTag); 604 | 605 | return text; 606 | } 607 | 608 | var writeImageTag = function(wholeMatch,m1,m2,m3,m4,m5,m6,m7) { 609 | var whole_match = m1; 610 | var alt_text = m2; 611 | var link_id = m3.toLowerCase(); 612 | var url = m4; 613 | var title = m7; 614 | 615 | if (!title) title = ""; 616 | 617 | if (url == "") { 618 | if (link_id == "") { 619 | // lower-case and turn embedded newlines into spaces 620 | link_id = alt_text.toLowerCase().replace(/ ?\n/g," "); 621 | } 622 | url = "#"+link_id; 623 | 624 | if (g_urls[link_id] != undefined) { 625 | url = g_urls[link_id]; 626 | if (g_titles[link_id] != undefined) { 627 | title = g_titles[link_id]; 628 | } 629 | } 630 | else { 631 | return whole_match; 632 | } 633 | } 634 | 635 | alt_text = alt_text.replace(/"/g,"""); 636 | url = escapeCharacters(url,"*_"); 637 | var result = "\""" + _RunSpanGamut(m1) + "");}); 665 | 666 | text = text.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm, 667 | function(matchFound,m1){return hashBlock("

" + _RunSpanGamut(m1) + "

");}); 668 | 669 | // atx-style headers: 670 | // # Header 1 671 | // ## Header 2 672 | // ## Header 2 with closing hashes ## 673 | // ... 674 | // ###### Header 6 675 | // 676 | 677 | /* 678 | text = text.replace(/ 679 | ^(\#{1,6}) // $1 = string of #'s 680 | [ \t]* 681 | (.+?) // $2 = Header text 682 | [ \t]* 683 | \#* // optional closing #'s (not counted) 684 | \n+ 685 | /gm, function() {...}); 686 | */ 687 | 688 | text = text.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm, 689 | function(wholeMatch,m1,m2) { 690 | var h_level = m1.length; 691 | return hashBlock("" + _RunSpanGamut(m2) + ""); 692 | }); 693 | 694 | return text; 695 | } 696 | 697 | // This declaration keeps Dojo compressor from outputting garbage: 698 | var _ProcessListItems; 699 | 700 | var _DoLists = function(text) { 701 | // 702 | // Form HTML ordered (numbered) and unordered (bulleted) lists. 703 | // 704 | 705 | // attacklab: add sentinel to hack around khtml/safari bug: 706 | // http://bugs.webkit.org/show_bug.cgi?id=11231 707 | text += "~0"; 708 | 709 | // Re-usable pattern to match any entirel ul or ol list: 710 | 711 | /* 712 | var whole_list = / 713 | ( // $1 = whole list 714 | ( // $2 715 | [ ]{0,3} // attacklab: g_tab_width - 1 716 | ([*+-]|\d+[.]) // $3 = first list item marker 717 | [ \t]+ 718 | ) 719 | [^\r]+? 720 | ( // $4 721 | ~0 // sentinel for workaround; should be $ 722 | | 723 | \n{2,} 724 | (?=\S) 725 | (?! // Negative lookahead for another list item marker 726 | [ \t]* 727 | (?:[*+-]|\d+[.])[ \t]+ 728 | ) 729 | ) 730 | )/g 731 | */ 732 | var whole_list = /^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm; 733 | 734 | if (g_list_level) { 735 | text = text.replace(whole_list,function(wholeMatch,m1,m2) { 736 | var list = m1; 737 | var list_type = (m2.search(/[*+-]/g)>-1) ? "ul" : "ol"; 738 | 739 | // Turn double returns into triple returns, so that we can make a 740 | // paragraph for the last item in a list, if necessary: 741 | list = list.replace(/\n{2,}/g,"\n\n\n");; 742 | var result = _ProcessListItems(list); 743 | 744 | // Trim any trailing whitespace, to put the closing `` 745 | // up on the preceding line, to get it past the current stupid 746 | // HTML block parser. This is a hack to work around the terrible 747 | // hack that is the HTML block parser. 748 | result = result.replace(/\s+$/,""); 749 | result = "<"+list_type+">" + result + "\n"; 750 | return result; 751 | }); 752 | } else { 753 | whole_list = /(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/g; 754 | text = text.replace(whole_list,function(wholeMatch,m1,m2,m3) { 755 | var runup = m1; 756 | var list = m2; 757 | 758 | var list_type = (m3.search(/[*+-]/g)>-1) ? "ul" : "ol"; 759 | // Turn double returns into triple returns, so that we can make a 760 | // paragraph for the last item in a list, if necessary: 761 | var list = list.replace(/\n{2,}/g,"\n\n\n");; 762 | var result = _ProcessListItems(list); 763 | result = runup + "<"+list_type+">\n" + result + "\n"; 764 | return result; 765 | }); 766 | } 767 | 768 | // attacklab: strip sentinel 769 | text = text.replace(/~0/,""); 770 | 771 | return text; 772 | } 773 | 774 | _ProcessListItems = function(list_str) { 775 | // 776 | // Process the contents of a single ordered or unordered list, splitting it 777 | // into individual list items. 778 | // 779 | // The $g_list_level global keeps track of when we're inside a list. 780 | // Each time we enter a list, we increment it; when we leave a list, 781 | // we decrement. If it's zero, we're not in a list anymore. 782 | // 783 | // We do this because when we're not inside a list, we want to treat 784 | // something like this: 785 | // 786 | // I recommend upgrading to version 787 | // 8. Oops, now this line is treated 788 | // as a sub-list. 789 | // 790 | // As a single paragraph, despite the fact that the second line starts 791 | // with a digit-period-space sequence. 792 | // 793 | // Whereas when we're inside a list (or sub-list), that line will be 794 | // treated as the start of a sub-list. What a kludge, huh? This is 795 | // an aspect of Markdown's syntax that's hard to parse perfectly 796 | // without resorting to mind-reading. Perhaps the solution is to 797 | // change the syntax rules such that sub-lists must start with a 798 | // starting cardinal number; e.g. "1." or "a.". 799 | 800 | g_list_level++; 801 | 802 | // trim trailing blank lines: 803 | list_str = list_str.replace(/\n{2,}$/,"\n"); 804 | 805 | // attacklab: add sentinel to emulate \z 806 | list_str += "~0"; 807 | 808 | /* 809 | list_str = list_str.replace(/ 810 | (\n)? // leading line = $1 811 | (^[ \t]*) // leading whitespace = $2 812 | ([*+-]|\d+[.]) [ \t]+ // list marker = $3 813 | ([^\r]+? // list item text = $4 814 | (\n{1,2})) 815 | (?= \n* (~0 | \2 ([*+-]|\d+[.]) [ \t]+)) 816 | /gm, function(){...}); 817 | */ 818 | list_str = list_str.replace(/(\n)?(^[ \t]*)([*+-]|\d+[.])[ \t]+([^\r]+?(\n{1,2}))(?=\n*(~0|\2([*+-]|\d+[.])[ \t]+))/gm, 819 | function(wholeMatch,m1,m2,m3,m4){ 820 | var item = m4; 821 | var leading_line = m1; 822 | var leading_space = m2; 823 | 824 | if (leading_line || (item.search(/\n{2,}/)>-1)) { 825 | item = _RunBlockGamut(_Outdent(item)); 826 | } 827 | else { 828 | // Recursion for sub-lists: 829 | item = _DoLists(_Outdent(item)); 830 | item = item.replace(/\n$/,""); // chomp(item) 831 | item = _RunSpanGamut(item); 832 | } 833 | 834 | return "
  • " + item + "
  • \n"; 835 | } 836 | ); 837 | 838 | // attacklab: strip sentinel 839 | list_str = list_str.replace(/~0/g,""); 840 | 841 | g_list_level--; 842 | return list_str; 843 | } 844 | 845 | 846 | var _DoCodeBlocks = function(text) { 847 | // 848 | // Process Markdown `
    ` blocks.
     849 | //  
     850 | 
     851 | 	/*
     852 | 		text = text.replace(text,
     853 | 			/(?:\n\n|^)
     854 | 			(								// $1 = the code block -- one or more lines, starting with a space/tab
     855 | 				(?:
     856 | 					(?:[ ]{4}|\t)			// Lines must start with a tab or a tab-width of spaces - attacklab: g_tab_width
     857 | 					.*\n+
     858 | 				)+
     859 | 			)
     860 | 			(\n*[ ]{0,3}[^ \t\n]|(?=~0))	// attacklab: g_tab_width
     861 | 		/g,function(){...});
     862 | 	*/
     863 | 
     864 | 	// attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
     865 | 	text += "~0";
     866 | 	
     867 | 	text = text.replace(/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g,
     868 | 		function(wholeMatch,m1,m2) {
     869 | 			var codeblock = m1;
     870 | 			var nextChar = m2;
     871 | 		
     872 | 			codeblock = _EncodeCode( _Outdent(codeblock));
     873 | 			codeblock = _Detab(codeblock);
     874 | 			codeblock = codeblock.replace(/^\n+/g,""); // trim leading newlines
     875 | 			codeblock = codeblock.replace(/\n+$/g,""); // trim trailing whitespace
     876 | 
     877 | 			codeblock = "
    " + codeblock + "\n
    "; 878 | 879 | return hashBlock(codeblock) + nextChar; 880 | } 881 | ); 882 | 883 | // attacklab: strip sentinel 884 | text = text.replace(/~0/,""); 885 | 886 | return text; 887 | } 888 | 889 | var hashBlock = function(text) { 890 | text = text.replace(/(^\n+|\n+$)/g,""); 891 | return "\n\n~K" + (g_html_blocks.push(text)-1) + "K\n\n"; 892 | } 893 | 894 | 895 | var _DoCodeSpans = function(text) { 896 | // 897 | // * Backtick quotes are used for spans. 898 | // 899 | // * You can use multiple backticks as the delimiters if you want to 900 | // include literal backticks in the code span. So, this input: 901 | // 902 | // Just type ``foo `bar` baz`` at the prompt. 903 | // 904 | // Will translate to: 905 | // 906 | //

    Just type foo `bar` baz at the prompt.

    907 | // 908 | // There's no arbitrary limit to the number of backticks you 909 | // can use as delimters. If you need three consecutive backticks 910 | // in your code, use four for delimiters, etc. 911 | // 912 | // * You can use spaces to get literal backticks at the edges: 913 | // 914 | // ... type `` `bar` `` ... 915 | // 916 | // Turns to: 917 | // 918 | // ... type `bar` ... 919 | // 920 | 921 | /* 922 | text = text.replace(/ 923 | (^|[^\\]) // Character before opening ` can't be a backslash 924 | (`+) // $2 = Opening run of ` 925 | ( // $3 = The code block 926 | [^\r]*? 927 | [^`] // attacklab: work around lack of lookbehind 928 | ) 929 | \2 // Matching closer 930 | (?!`) 931 | /gm, function(){...}); 932 | */ 933 | 934 | text = text.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm, 935 | function(wholeMatch,m1,m2,m3,m4) { 936 | var c = m3; 937 | c = c.replace(/^([ \t]*)/g,""); // leading whitespace 938 | c = c.replace(/[ \t]*$/g,""); // trailing whitespace 939 | c = _EncodeCode(c); 940 | return m1+""+c+""; 941 | }); 942 | 943 | return text; 944 | } 945 | 946 | 947 | var _EncodeCode = function(text) { 948 | // 949 | // Encode/escape certain characters inside Markdown code runs. 950 | // The point is that in code, these characters are literals, 951 | // and lose their special Markdown meanings. 952 | // 953 | // Encode all ampersands; HTML entities are not 954 | // entities within a Markdown code span. 955 | text = text.replace(/&/g,"&"); 956 | 957 | // Do the angle bracket song and dance: 958 | text = text.replace(//g,">"); 960 | 961 | // Now, escape characters that are magic in Markdown: 962 | text = escapeCharacters(text,"\*_{}[]\\",false); 963 | 964 | // jj the line above breaks this: 965 | //--- 966 | 967 | //* Item 968 | 969 | // 1. Subitem 970 | 971 | // special char: * 972 | //--- 973 | 974 | return text; 975 | } 976 | 977 | 978 | var _DoItalicsAndBold = function(text) { 979 | 980 | // must go first: 981 | text = text.replace(/(\*\*|__)(?=\S)([^\r]*?\S[\*_]*)\1/g, 982 | "$2"); 983 | 984 | text = text.replace(/(\*|_)(?=\S)([^\r]*?\S)\1/g, 985 | "$2"); 986 | 987 | return text; 988 | } 989 | 990 | 991 | var _DoBlockQuotes = function(text) { 992 | 993 | /* 994 | text = text.replace(/ 995 | ( // Wrap whole match in $1 996 | ( 997 | ^[ \t]*>[ \t]? // '>' at the start of a line 998 | .+\n // rest of the first line 999 | (.+\n)* // subsequent consecutive lines 1000 | \n* // blanks 1001 | )+ 1002 | ) 1003 | /gm, function(){...}); 1004 | */ 1005 | 1006 | text = text.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm, 1007 | function(wholeMatch,m1) { 1008 | var bq = m1; 1009 | 1010 | // attacklab: hack around Konqueror 3.5.4 bug: 1011 | // "----------bug".replace(/^-/g,"") == "bug" 1012 | 1013 | bq = bq.replace(/^[ \t]*>[ \t]?/gm,"~0"); // trim one level of quoting 1014 | 1015 | // attacklab: clean up hack 1016 | bq = bq.replace(/~0/g,""); 1017 | 1018 | bq = bq.replace(/^[ \t]+$/gm,""); // trim whitespace-only lines 1019 | bq = _RunBlockGamut(bq); // recurse 1020 | 1021 | bq = bq.replace(/(^|\n)/g,"$1 "); 1022 | // These leading spaces screw with
     content, so we need to fix that:
    1023 | 			bq = bq.replace(
    1024 | 					/(\s*
    [^\r]+?<\/pre>)/gm,
    1025 | 				function(wholeMatch,m1) {
    1026 | 					var pre = m1;
    1027 | 					// attacklab: hack around Konqueror 3.5.4 bug:
    1028 | 					pre = pre.replace(/^  /mg,"~0");
    1029 | 					pre = pre.replace(/~0/g,"");
    1030 | 					return pre;
    1031 | 				});
    1032 | 			
    1033 | 			return hashBlock("
    \n" + bq + "\n
    "); 1034 | }); 1035 | return text; 1036 | } 1037 | 1038 | 1039 | var _FormParagraphs = function(text) { 1040 | // 1041 | // Params: 1042 | // $text - string to process with html

    tags 1043 | // 1044 | 1045 | // Strip leading and trailing lines: 1046 | text = text.replace(/^\n+/g,""); 1047 | text = text.replace(/\n+$/g,""); 1048 | 1049 | var grafs = text.split(/\n{2,}/g); 1050 | var grafsOut = new Array(); 1051 | 1052 | // 1053 | // Wrap

    tags. 1054 | // 1055 | var end = grafs.length; 1056 | for (var i=0; i= 0) { 1061 | grafsOut.push(str); 1062 | } 1063 | else if (str.search(/\S/) >= 0) { 1064 | str = _RunSpanGamut(str); 1065 | str = str.replace(/^([ \t]*)/g,"

    "); 1066 | str += "

    " 1067 | grafsOut.push(str); 1068 | } 1069 | 1070 | } 1071 | 1072 | // 1073 | // Unhashify HTML blocks 1074 | // 1075 | end = grafsOut.length; 1076 | for (var i=0; i= 0) { 1079 | var blockText = g_html_blocks[RegExp.$1]; 1080 | blockText = blockText.replace(/\$/g,"$$$$"); // Escape any dollar signs 1081 | grafsOut[i] = grafsOut[i].replace(/~K\d+K/,blockText); 1082 | } 1083 | } 1084 | 1085 | return grafsOut.join("\n\n"); 1086 | } 1087 | 1088 | 1089 | var _EncodeAmpsAndAngles = function(text) { 1090 | // Smart processing for ampersands and angle brackets that need to be encoded. 1091 | 1092 | // Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin: 1093 | // http://bumppo.net/projects/amputator/ 1094 | text = text.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g,"&"); 1095 | 1096 | // Encode naked <'s 1097 | text = text.replace(/<(?![a-z\/?\$!])/gi,"<"); 1098 | 1099 | return text; 1100 | } 1101 | 1102 | 1103 | var _EncodeBackslashEscapes = function(text) { 1104 | // 1105 | // Parameter: String. 1106 | // Returns: The string, with after processing the following backslash 1107 | // escape sequences. 1108 | // 1109 | 1110 | // attacklab: The polite way to do this is with the new 1111 | // escapeCharacters() function: 1112 | // 1113 | // text = escapeCharacters(text,"\\",true); 1114 | // text = escapeCharacters(text,"`*_{}[]()>#+-.!",true); 1115 | // 1116 | // ...but we're sidestepping its use of the (slow) RegExp constructor 1117 | // as an optimization for Firefox. This function gets called a LOT. 1118 | 1119 | text = text.replace(/\\(\\)/g,escapeCharacters_callback); 1120 | text = text.replace(/\\([`*_{}\[\]()>#+-.!])/g,escapeCharacters_callback); 1121 | return text; 1122 | } 1123 | 1124 | 1125 | var _DoAutoLinks = function(text) { 1126 | 1127 | text = text.replace(/<((https?|ftp|dict):[^'">\s]+)>/gi,"
    $1"); 1128 | 1129 | // Email addresses: 1130 | 1131 | /* 1132 | text = text.replace(/ 1133 | < 1134 | (?:mailto:)? 1135 | ( 1136 | [-.\w]+ 1137 | \@ 1138 | [-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+ 1139 | ) 1140 | > 1141 | /gi, _DoAutoLinks_callback()); 1142 | */ 1143 | text = text.replace(/<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi, 1144 | function(wholeMatch,m1) { 1145 | return _EncodeEmailAddress( _UnescapeSpecialChars(m1) ); 1146 | } 1147 | ); 1148 | 1149 | return text; 1150 | } 1151 | 1152 | 1153 | var _EncodeEmailAddress = function(addr) { 1154 | // 1155 | // Input: an email address, e.g. "foo@example.com" 1156 | // 1157 | // Output: the email address as a mailto link, with each character 1158 | // of the address encoded as either a decimal or hex entity, in 1159 | // the hopes of foiling most address harvesting spam bots. E.g.: 1160 | // 1161 | // foo 1163 | // @example.com 1164 | // 1165 | // Based on a filter by Matthew Wickline, posted to the BBEdit-Talk 1166 | // mailing list: 1167 | // 1168 | 1169 | // attacklab: why can't javascript speak hex? 1170 | function char2hex(ch) { 1171 | var hexDigits = '0123456789ABCDEF'; 1172 | var dec = ch.charCodeAt(0); 1173 | return(hexDigits.charAt(dec>>4) + hexDigits.charAt(dec&15)); 1174 | } 1175 | 1176 | var encode = [ 1177 | function(ch){return "&#"+ch.charCodeAt(0)+";";}, 1178 | function(ch){return "&#x"+char2hex(ch)+";";}, 1179 | function(ch){return ch;} 1180 | ]; 1181 | 1182 | addr = "mailto:" + addr; 1183 | 1184 | addr = addr.replace(/./g, function(ch) { 1185 | if (ch == "@") { 1186 | // this *must* be encoded. I insist. 1187 | ch = encode[Math.floor(Math.random()*2)](ch); 1188 | } else if (ch !=":") { 1189 | // leave ':' alone (to spot mailto: later) 1190 | var r = Math.random(); 1191 | // roughly 10% raw, 45% hex, 45% dec 1192 | ch = ( 1193 | r > .9 ? encode[2](ch) : 1194 | r > .45 ? encode[1](ch) : 1195 | encode[0](ch) 1196 | ); 1197 | } 1198 | return ch; 1199 | }); 1200 | 1201 | addr = "" + addr + ""; 1202 | addr = addr.replace(/">.+:/g,"\">"); // strip the mailto: from the visible part 1203 | 1204 | return addr; 1205 | } 1206 | 1207 | 1208 | var _UnescapeSpecialChars = function(text) { 1209 | // 1210 | // Swap back in all the special characters we've hidden. 1211 | // 1212 | text = text.replace(/~E(\d+)E/g, 1213 | function(wholeMatch,m1) { 1214 | var charCodeToReplace = parseInt(m1); 1215 | return String.fromCharCode(charCodeToReplace); 1216 | } 1217 | ); 1218 | return text; 1219 | } 1220 | 1221 | 1222 | var _Outdent = function(text) { 1223 | // 1224 | // Remove one level of line-leading tabs or spaces 1225 | // 1226 | 1227 | // attacklab: hack around Konqueror 3.5.4 bug: 1228 | // "----------bug".replace(/^-/g,"") == "bug" 1229 | 1230 | text = text.replace(/^(\t|[ ]{1,4})/gm,"~0"); // attacklab: g_tab_width 1231 | 1232 | // attacklab: clean up hack 1233 | text = text.replace(/~0/g,"") 1234 | 1235 | return text; 1236 | } 1237 | 1238 | var _Detab = function(text) { 1239 | // attacklab: Detab's completely rewritten for speed. 1240 | // In perl we could fix it by anchoring the regexp with \G. 1241 | // In javascript we're less fortunate. 1242 | 1243 | // expand first n-1 tabs 1244 | text = text.replace(/\t(?=\t)/g," "); // attacklab: g_tab_width 1245 | 1246 | // replace the nth with two sentinels 1247 | text = text.replace(/\t/g,"~A~B"); 1248 | 1249 | // use the sentinel to anchor our regex so it doesn't explode 1250 | text = text.replace(/~B(.+?)~A/g, 1251 | function(wholeMatch,m1,m2) { 1252 | var leadingText = m1; 1253 | var numSpaces = 4 - leadingText.length % 4; // attacklab: g_tab_width 1254 | 1255 | // there *must* be a better way to do this: 1256 | for (var i=0; i-1};WMD=function(input,toolbar,options){options=extend({preview:null,previewEvery:0.5,showdown:null,lineLength:40,commands:"strong em spacer a blockquote code img spacer ol ul h hr",commandTable:{},lang:"EN"},options);chooseTranslator(options.lang);if(typeof input==="string"){input=document.getElementById(input);}if(typeof toolbar==="string"){toolbar=document.getElementById(toolbar);}var obj={},shortcuts={},previewInterval,lastValue="";if(!options.showdown&&typeof Attacklab!=="undefined"&&Attacklab.showdown&&Attacklab.showdown.converter){options.showdown=new Attacklab.showdown.converter().makeHtml;}function buildToolbar(){var ul,i,key,definition,builder,command,commands=options.commands.split(" ");if(toolbar){toolbar.innerHTML="";ul=document.createElement("ul");ul.className="wmd-toolbar";toolbar.appendChild(ul);for(i=0;i0&&typeof options.showdown==="function"){if(typeof options.preview==="string"){options.preview=document.getElementById(options.preview);}function refreshPreview(){if(input.value!==lastValue){options.preview.innerHTML=options.showdown(input.value);lastValue=input.value;}}previewInterval=setInterval(refreshPreview,options.previewEvery*1000);addEvent(input,"keypress",refreshPreview);addEvent(input,"keydown",refreshPreview);}}buildToolbar();createEvents();return extend(obj,{input:input,options:options,ieClicked:false,ieRange:null});};function addClassName(element,className){var elementClassName=element.className;if(!(elementClassName.length>0&&(elementClassName===className||new RegExp("(^|\\s)"+className+"(\\s|$)").test(elementClassName)))){element.className=element.className+(element.className?" ":"")+className;}return element;}function addEvent(element,event,callback,cache){if(element.attachEvent){element.attachEvent("on"+event,callback);}else{element.addEventListener(event,callback,false);}if(cache&&typeof cache.push==="function"){cache.push({element:element,event:event,callback:callback});}else{eventCache.push({element:element,event:event,callback:callback});}}function extend(dest,source){source=source||{};dest=dest||{};var prop;for(prop in source){if(source.hasOwnProperty(prop)&&typeof source[prop]!=="undefined"){dest[prop]=source[prop];}}return dest;}function extendRegExp(regex,pre,post){var pattern=regex.toString(),flags="",result;if(pre===null||pre===undefined){pre="";}if(post===null||post===undefined){post="";}result=pattern.match(/\/([gim]*)$/);if(result){flags=result[1];}else{flags="";}pattern=pattern.replace(/(^\/|\/[gim]*$)/g,"");pattern=pre+pattern+post;return new RegExp(pattern,flags);}function fixEol(text){return(text||"").replace(/\r\n/g,"\n").replace(/\r/g,"\n");}function getViewportDimensions(){if(!documentElement){if(browser.WebKit&&!document.evaluate){documentElement=document;}else{if(browser.Opera&&window.parseFloat(window.opera.version())<9.5){documentElement=document.body;}else{documentElement=document.documentElement;}}}return{width:documentElement.clientWidth,height:documentElement.clientHeight};}function indexOf(array,item){var i,n;if(array){if(typeof array.indexOf!=="undefined"){return array.indexOf(item);}if(typeof array.length!=="undefined"){for(i=0,n=array.length;i/?";var charset="",str="";if(options.numbers){charset+=numbers;}if(options.lower){charset+=lower;}if(options.upper){charset+=upper;}if(options.other){charset+=other;}if(charset.length===0){throw ("There is no character set from which to generate random strings.");}function getCharacter(){return charset.charAt(getIndex(0,charset.length));}function getIndex(lower,upper){return Math.floor(Math.random()*(upper-lower))+lower;}for(var i=0;i0){removeEvent(eventCache[0].element,eventCache[0].event,eventCache[0].callback);}}); 3 | Translator_EN={urlFieldInsertion:'To add a tool-tip, place it in quotes after the URL (e.g., http://google.com "Google")',linkText:"link text",imageAlt:"image alt",listItem:"List item",strongTitle:"Strong Ctl+B",emTitle:"Emphasis Ctl+I",aTitle:"Hyperlink Ctl+L",blockquoteTitle:"Blockquote
    Ctl+Q",codeTitle:"Code Sample
     Ctl+K",imgTitle:"Image  Ctl+G",olTitle:"Numbered List 
      Ctl+O",ulTitle:"Bulleted List