├── .gitignore ├── assets ├── fonts │ ├── icomoon.eot │ ├── icomoon.ttf │ ├── icomoon.woff │ ├── icomoon.svg │ └── icomoon.dev.svg ├── css │ └── editor.css └── js │ └── marked.js ├── composer.json ├── MarkdowneditorAssets.php ├── LICENSE ├── Markdowneditor.php └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea -------------------------------------------------------------------------------- /assets/fonts/icomoon.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iJackUA/yii2-lepture-markdown-editor-widget/HEAD/assets/fonts/icomoon.eot -------------------------------------------------------------------------------- /assets/fonts/icomoon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iJackUA/yii2-lepture-markdown-editor-widget/HEAD/assets/fonts/icomoon.ttf -------------------------------------------------------------------------------- /assets/fonts/icomoon.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iJackUA/yii2-lepture-markdown-editor-widget/HEAD/assets/fonts/icomoon.woff -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ijackua/yii2-lepture-markdown-editor-widget", 3 | "description": "Yii2 widget for Lepture Markdown Editor (https://github.com/lepture/editor) - A markdown editor you really want", 4 | "keywords": ["markdown", "editor", "lepture", "yii", "extension", "widget"], 5 | "homepage": "https://github.com/iJackUA/yii2-lepture-markdown-editor-widget", 6 | "type": "yii2-extension", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Evgeniy Kuzminov", 11 | "email": "kyzminov@gmail.com", 12 | "homepage" : "http://stdout.in" 13 | } 14 | ], 15 | "require": { 16 | "yiisoft/yii2": "*" 17 | }, 18 | "autoload": { 19 | "psr-4": { 20 | "ijackua\\lepture\\": "" 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /MarkdowneditorAssets.php: -------------------------------------------------------------------------------- 1 | id)) { 46 | $this->id = $this->hasModel() ? Html::getInputId($this->model, $this->attribute) : $this->getId(); 47 | } 48 | 49 | if (empty($this->leptureOptions['element'])) { 50 | $this->leptureOptions['element'] = new JsExpression('$("#' . $this->id . '")[0]'); 51 | } 52 | 53 | $jsonOptionsMarked = Json::encode($this->markedOptions); 54 | $script = 'marked.setOptions(' . $jsonOptionsMarked . ');'; 55 | $this->view->registerJs($script); 56 | } 57 | 58 | public function run() 59 | { 60 | MarkdowneditorAssets::register($this->view); 61 | $this->registerScripts(); 62 | 63 | $this->options['id'] = $this->id; 64 | if ($this->hasModel()) { 65 | $textarea = Html::activeTextArea($this->model, $this->attribute, $this->options); 66 | } else { 67 | $textarea = Html::textArea($this->name, $this->value, $this->options); 68 | } 69 | echo '
' . $textarea . '
'; 70 | } 71 | 72 | public function registerScripts() 73 | { 74 | $jsonOptions = Json::encode($this->leptureOptions); 75 | $varName = Inflector::classify('editor' . $this->id); 76 | 77 | $script = "var {$varName} = new Editor(" . $jsonOptions . "); {$varName}.render();"; 78 | $this->view->registerJs($script); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Yii2 Lepture Markdown Editor widget 2 | --- 3 | [![Latest Version on Packagist][ico-version]][link-packagist] 4 | [![Software License][ico-license]](LICENSE.md) 5 | [![Total Downloads][ico-downloads]][link-downloads] 6 | 7 | Yii2 widget for Lepture Markdown Editor (https://github.com/lepture/editor) - A markdown editor you really want 8 | 9 | ## Demo 10 | 11 | on [http://lab.lepture.com/editor/](http://lab.lepture.com/editor/) 12 | 13 | ## Installation via Composer 14 | add to `require` section of your `composer.json` 15 | `"ijackua/yii2-lepture-markdown-editor-widget"` 16 | and run composer update 17 | 18 | ## Usage example 19 | 20 | ### Active widget 21 | 22 | ```php 23 | use ijackua\lepture\Markdowneditor; 24 | 25 | Markdowneditor::widget( 26 | [ 27 | 'model' => $model, 28 | 'attribute' => 'full_text', 29 | ]) 30 | ``` 31 | 32 | ### Simple widget 33 | 34 | ```php 35 | use ijackua\lepture\Markdowneditor; 36 | 37 | Markdowneditor::widget( 38 | [ 39 | 'name' => 'editor', 40 | 'value' => '# Hello world' 41 | ]) 42 | ``` 43 | 44 | ## Editor options 45 | 46 | see on [official site](https://github.com/lepture/editor) 47 | 48 | ```php 49 | use ijackua\lepture\Markdowneditor; 50 | 51 | Markdowneditor::widget( 52 | [ 53 | 'model' => $model, 54 | 'attribute' => 'full_text', 55 | 'leptureOptions' => [ 56 | 'toolbar' => false 57 | ] 58 | ]) 59 | ``` 60 | 61 | ## Marked options (markdown parser used by Lepture Editor) 62 | see on [official Marked site](https://github.com/chjj/marked) 63 | 64 | ```php 65 | use ijackua\lepture\Markdowneditor; 66 | 67 | Markdowneditor::widget( 68 | [ 69 | 'model' => $model, 70 | 'attribute' => 'full_text', 71 | 'markedOptions' => [ 72 | 'tables' => false 73 | ] 74 | ]) 75 | ``` 76 | 77 | ## Credits 78 | 79 | - [Ievgen Kuzminov][link-author] 80 | - [All Contributors][link-contributors] 81 | 82 | ## License 83 | 84 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 85 | 86 | [ico-version]: https://img.shields.io/packagist/v/ijackua/yii2-lepture-markdown-editor-widget.svg?style=flat-square 87 | [ico-license]: https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square 88 | [ico-downloads]: https://img.shields.io/packagist/dt/ijackua/yii2-lepture-markdown-editor-widget.svg?style=flat-square 89 | 90 | [link-packagist]: https://packagist.org/packages/ijackua/yii2-lepture-markdown-editor-widget 91 | [link-downloads]: https://packagist.org/packages/ijackua/yii2-lepture-markdown-editor-widget 92 | [link-author]: https://github.com/iJackUA 93 | [link-contributors]: ../../contributors 94 | -------------------------------------------------------------------------------- /assets/fonts/icomoon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | This is a custom SVG font generated by IcoMoon. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 18 | 19 | 28 | 29 | 33 | 34 | 35 | 36 | 37 | 39 | 41 | 43 | 44 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /assets/fonts/icomoon.dev.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | This is a custom SVG font generated by IcoMoon. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 18 | 19 | 28 | 29 | 33 | 34 | 35 | 36 | 37 | 39 | 41 | 43 | 44 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /assets/css/editor.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'icomoon'; 3 | src:url('../fonts/icomoon.eot'); 4 | src:url('../fonts/icomoon.eot?#iefix') format('embedded-opentype'), 5 | url('../fonts/icomoon.woff') format('woff'), 6 | url('../fonts/icomoon.ttf') format('truetype'), 7 | url('../fonts/icomoon.svg#icomoon') format('svg'); 8 | font-weight: normal; 9 | font-style: normal; 10 | } 11 | 12 | /* Use the following CSS code if you want to use data attributes for inserting your icons */ 13 | .lepture [data-icon]:before { 14 | font-family: 'icomoon' !important; 15 | content: attr(data-icon); 16 | speak: none; 17 | font-weight: normal; 18 | font-variant: normal; 19 | text-transform: none; 20 | line-height: 1; 21 | -webkit-font-smoothing: antialiased; 22 | } 23 | 24 | /* Use the following CSS code if you want to have a class per icon */ 25 | 26 | .lepture [class*="icon-"] 27 | { font-family: 'icomoon' !important; 28 | speak: none; 29 | font-style: normal; 30 | font-weight: normal; 31 | font-variant: normal; 32 | text-transform: none; 33 | line-height: 1; 34 | -webkit-font-smoothing: antialiased; 35 | } 36 | 37 | .lepture [class^="icon-"]:before, .lepture [class*="icon-"]:before,.lepture [class*=" icon-"]:before { 38 | font-family: 'icomoon' !important; 39 | } 40 | 41 | .icon-bold:before { 42 | content: "\e000"; 43 | } 44 | .icon-italic:before { 45 | content: "\e001"; 46 | } 47 | .icon-quote:before { 48 | content: "\e003"; 49 | } 50 | .icon-unordered-list:before { 51 | content: "\e004"; 52 | } 53 | .icon-ordered-list:before { 54 | content: "\e005"; 55 | } 56 | .icon-link:before { 57 | content: "\e006"; 58 | } 59 | .icon-image:before { 60 | content: "\e007"; 61 | } 62 | .icon-play:before { 63 | content: "\e008"; 64 | } 65 | .icon-music:before { 66 | content: "\e009"; 67 | } 68 | .icon-contract:before { 69 | content: "\e00a"; 70 | } 71 | .icon-fullscreen:before { 72 | content: "\e00b"; 73 | } 74 | .icon-question:before { 75 | content: "\e00c"; 76 | } 77 | .icon-info:before { 78 | content: "\e00d"; 79 | } 80 | .icon-undo:before { 81 | content: "\e00e"; 82 | } 83 | .icon-redo:before { 84 | content: "\e00f"; 85 | } 86 | .icon-code:before { 87 | content: "\e011"; 88 | } 89 | .icon-preview:before { 90 | content: "\e002"; 91 | } 92 | /* BASICS */ 93 | 94 | .CodeMirror { 95 | height: 300px; 96 | } 97 | .CodeMirror-scroll { 98 | /* Set scrolling behaviour here */ 99 | overflow: auto; 100 | } 101 | 102 | /* PADDING */ 103 | 104 | .CodeMirror-lines { 105 | padding: 4px 0; /* Vertical padding around content */ 106 | } 107 | .CodeMirror pre { 108 | padding: 0 4px; /* Horizontal padding of content */ 109 | } 110 | 111 | .CodeMirror-scrollbar-filler { 112 | background-color: white; /* The little square between H and V scrollbars */ 113 | } 114 | 115 | /* CURSOR */ 116 | .CodeMirror div.CodeMirror-cursor { 117 | border-left: 1px solid black; 118 | z-index: 3; 119 | } 120 | /* Shown when moving in bi-directional text */ 121 | .CodeMirror div.CodeMirror-secondarycursor { 122 | border-left: 1px solid silver; 123 | } 124 | .CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor { 125 | width: auto; 126 | border: 0; 127 | background: #7e7; 128 | z-index: 1; 129 | } 130 | /* Can style cursor different in overwrite (non-insert) mode */ 131 | .CodeMirror div.CodeMirror-cursor.CodeMirror-overwrite {} 132 | 133 | /* DEFAULT THEME */ 134 | 135 | .cm-s-paper .cm-keyword {color: #555;} 136 | .cm-s-paper .cm-atom {color: #7f8c8d;} 137 | .cm-s-paper .cm-number {color: #7f8c8d;} 138 | .cm-s-paper .cm-def {color: #00f;} 139 | .cm-s-paper .cm-variable {color: black;} 140 | .cm-s-paper .cm-variable-2 {color: #555;} 141 | .cm-s-paper .cm-variable-3 {color: #085;} 142 | .cm-s-paper .cm-property {color: black;} 143 | .cm-s-paper .cm-operator {color: black;} 144 | .cm-s-paper .cm-comment {color: #959595;} 145 | .cm-s-paper .cm-string {color: #7f8c8d;} 146 | .cm-s-paper .cm-string-2 {color: #f50;} 147 | .cm-s-paper .cm-meta {color: #555;} 148 | .cm-s-paper .cm-error {color: #f00;} 149 | .cm-s-paper .cm-qualifier {color: #555;} 150 | .cm-s-paper .cm-builtin {color: #555;} 151 | .cm-s-paper .cm-bracket {color: #997;} 152 | .cm-s-paper .cm-tag {color: #7f8c8d;} 153 | .cm-s-paper .cm-attribute {color: #7f8c8d;} 154 | .cm-s-paper .cm-header {color: #000;} 155 | .cm-s-paper .cm-quote {color: #888;} 156 | .cm-s-paper .cm-hr {color: #999;} 157 | .cm-s-paper .cm-link {color: #7f8c8d;} 158 | 159 | .cm-negative {color: #d44;} 160 | .cm-positive {color: #292;} 161 | .cm-header, .cm-strong {font-weight: bold;} 162 | .cm-em {font-style: italic;} 163 | .cm-link {text-decoration: underline;} 164 | 165 | .cm-invalidchar {color: #f00;} 166 | 167 | div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} 168 | div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} 169 | 170 | 171 | /* STOP */ 172 | 173 | /* The rest of this file contains styles related to the mechanics of 174 | the editor. You probably shouldn't touch them. */ 175 | 176 | .CodeMirror { 177 | position: relative; 178 | overflow: hidden; 179 | } 180 | 181 | .CodeMirror-scroll { 182 | /* 30px is the magic margin used to hide the element's real scrollbars */ 183 | /* See overflow: hidden in .CodeMirror, and the paddings in .CodeMirror-sizer */ 184 | margin-bottom: -30px; margin-right: -30px; 185 | padding-bottom: 30px; padding-right: 30px; 186 | height: 100%; 187 | outline: none; /* Prevent dragging from highlighting the element */ 188 | position: relative; 189 | } 190 | .CodeMirror-sizer { 191 | position: relative; 192 | } 193 | 194 | /* The fake, visible scrollbars. Used to force redraw during scrolling 195 | before actuall scrolling happens, thus preventing shaking and 196 | flickering artifacts. */ 197 | .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler { 198 | position: absolute; 199 | z-index: 6; 200 | display: none; 201 | } 202 | .CodeMirror-vscrollbar { 203 | right: 0; top: 0; 204 | overflow-x: hidden; 205 | overflow-y: scroll; 206 | } 207 | .CodeMirror-hscrollbar { 208 | bottom: 0; left: 0; 209 | overflow-y: hidden; 210 | overflow-x: scroll; 211 | } 212 | .CodeMirror-scrollbar-filler { 213 | right: 0; bottom: 0; 214 | z-index: 6; 215 | } 216 | 217 | .CodeMirror-lines { 218 | cursor: text; 219 | } 220 | .CodeMirror pre { 221 | /* Reset some styles that the rest of the page might have set */ 222 | -moz-border-radius: 0; -webkit-border-radius: 0; -o-border-radius: 0; border-radius: 0; 223 | border-width: 0; 224 | background: transparent; 225 | font-family: inherit; 226 | font-size: inherit; 227 | margin: 0; 228 | white-space: pre-wrap; 229 | word-wrap: normal; 230 | line-height: inherit; 231 | color: inherit; 232 | z-index: 2; 233 | position: relative; 234 | overflow: visible; 235 | } 236 | .CodeMirror-wrap pre { 237 | word-wrap: break-word; 238 | white-space: pre-wrap; 239 | word-break: normal; 240 | } 241 | .CodeMirror-linebackground { 242 | position: absolute; 243 | left: 0; right: 0; top: 0; bottom: 0; 244 | z-index: 0; 245 | } 246 | 247 | .CodeMirror-linewidget { 248 | position: relative; 249 | z-index: 2; 250 | overflow: auto; 251 | } 252 | 253 | .CodeMirror-widget { 254 | display: inline-block; 255 | } 256 | 257 | .CodeMirror-wrap .CodeMirror-scroll { 258 | overflow-x: hidden; 259 | } 260 | 261 | .CodeMirror-measure { 262 | position: absolute; 263 | width: 100%; height: 0px; 264 | overflow: hidden; 265 | visibility: hidden; 266 | } 267 | .CodeMirror-measure pre { position: static; } 268 | 269 | .CodeMirror div.CodeMirror-cursor { 270 | position: absolute; 271 | visibility: hidden; 272 | border-right: none; 273 | width: 0; 274 | } 275 | .CodeMirror-focused div.CodeMirror-cursor { 276 | visibility: visible; 277 | } 278 | 279 | .CodeMirror-selected { background: #d9d9d9; } 280 | .CodeMirror-focused .CodeMirror-selected { background: #BDC3C7; } 281 | 282 | .cm-searching { 283 | background: #ffa; 284 | background: rgba(255, 255, 0, .4); 285 | } 286 | 287 | /* IE7 hack to prevent it from returning funny offsetTops on the spans */ 288 | .CodeMirror span { *vertical-align: text-bottom; } 289 | 290 | @media print { 291 | /* Hide the cursor when printing */ 292 | .CodeMirror div.CodeMirror-cursor { 293 | visibility: hidden; 294 | } 295 | } 296 | .CodeMirror { 297 | height: 450px; 298 | } 299 | :-webkit-full-screen { 300 | background: #f9f9f5; 301 | padding: 0.5em 1em; 302 | width: 100% !important; 303 | height: 100% !important; 304 | } 305 | :-moz-full-screen { 306 | padding: 0.5em 1em; 307 | background: #f9f9f5; 308 | width: 100% !important; 309 | height: 100% !important; 310 | } 311 | .editor-wrapper { 312 | font: 16px/1.62 "Helvetica Neue", "Xin Gothic", "Hiragino Sans GB", "WenQuanYi Micro Hei", "Microsoft YaHei", sans-serif; 313 | color: #2c3e50; 314 | } 315 | /* this is the title */ 316 | .editor-wrapper input.title { 317 | font: 18px "Helvetica Neue", "Xin Gothic", "Hiragino Sans GB", "WenQuanYi Micro Hei", "Microsoft YaHei", sans-serif; 318 | background: transparent; 319 | padding: 4px; 320 | width: 100%; 321 | border: none; 322 | outline: none; 323 | opacity: 0.6; 324 | } 325 | .editor-toolbar { 326 | position: relative; 327 | opacity: 0.6; 328 | } 329 | .editor-toolbar:before, .editor-toolbar:after { 330 | display: block; 331 | content: ' '; 332 | height: 1px; 333 | background-color: #bdc3c7; 334 | background: -moz-linear-gradient(45deg, #f9f9f9, #bdc3c7, #f9f9f9); 335 | background: -webkit-linear-gradient(45deg, #f9f9f9, #bdc3c7, #f9f9f9); 336 | background: -ms-linear-gradient(45deg, #f9f9f9, #bdc3c7, #f9f9f9); 337 | background: linear-gradient(45deg, #f9f9f9, #bdc3c7, #f9f9f9); 338 | } 339 | .editor-toolbar:before { 340 | margin-bottom: 8px; 341 | } 342 | .editor-toolbar:after { 343 | margin-top: 8px; 344 | } 345 | .editor-wrapper input.title:hover, .editor-wrapper input.title:focus, .editor-toolbar:hover { 346 | opacity: 0.8; 347 | } 348 | .editor-toolbar a { 349 | display: inline-block; 350 | text-align: center; 351 | text-decoration: none !important; 352 | color: #2c3e50 !important; 353 | width: 24px; 354 | height: 24px; 355 | margin: 2px; 356 | border: 1px solid transparent; 357 | border-radius: 3px; 358 | cursor: pointer; 359 | } 360 | .editor-toolbar a:hover, .editor-toolbar a.active { 361 | background: #fcfcfc; 362 | border-color: #95a5a6; 363 | } 364 | .editor-toolbar a:before { 365 | line-height: 24px; 366 | } 367 | .editor-toolbar i.separator { 368 | display: inline-block; 369 | width: 0; 370 | border-left: 1px solid #d9d9d9; 371 | border-right: 1px solid white; 372 | color: transparent; 373 | text-indent: -10px; 374 | margin: 0 6px; 375 | } 376 | .editor-toolbar a.icon-fullscreen { 377 | position: absolute; 378 | right: 0; 379 | } 380 | .editor-statusbar { 381 | border-top: 1px solid #ece9e9; 382 | padding: 8px 10px; 383 | font-size: 12px; 384 | color: #959694; 385 | text-align: right; 386 | } 387 | .editor-statusbar span { 388 | display: inline-block; 389 | min-width: 4em; 390 | margin-left: 1em; 391 | } 392 | .editor-statusbar .lines:before { 393 | content: 'lines: '; 394 | } 395 | .editor-statusbar .words:before { 396 | content: 'words: '; 397 | } 398 | .editor-preview { 399 | position: absolute; 400 | width: 100%; 401 | height: 100%; 402 | top: 0; 403 | left: 100%; 404 | background: #f9f9f5; 405 | z-index: 9999; 406 | overflow: auto; 407 | -webkit-transition: left 0.2s ease; 408 | -moz-transition: left 0.2s ease; 409 | -ms-transition: left 0.2s ease; 410 | transition: left 0.2s ease; 411 | } 412 | .editor-preview-active { 413 | left: 0; 414 | } 415 | .editor-preview > p { 416 | margin-top: 0; 417 | } 418 | -------------------------------------------------------------------------------- /assets/js/marked.js: -------------------------------------------------------------------------------- 1 | /** 2 | * marked - a markdown parser 3 | * Copyright (c) 2011-2013, Christopher Jeffrey. (MIT Licensed) 4 | * https://github.com/chjj/marked 5 | */ 6 | 7 | ;(function() { 8 | 9 | /** 10 | * Block-Level Grammar 11 | */ 12 | 13 | var block = { 14 | newline: /^\n+/, 15 | code: /^( {4}[^\n]+\n*)+/, 16 | fences: noop, 17 | hr: /^( *[-*_]){3,} *(?:\n+|$)/, 18 | heading: /^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/, 19 | nptable: noop, 20 | lheading: /^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/, 21 | blockquote: /^( *>[^\n]+(\n[^\n]+)*\n*)+/, 22 | list: /^( *)(bull) [\s\S]+?(?:hr|\n{2,}(?! )(?!\1bull )\n*|\s*$)/, 23 | html: /^ *(?:comment|closed|closing) *(?:\n{2,}|\s*$)/, 24 | def: /^ *\[([^\]]+)\]: *]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/, 25 | table: noop, 26 | paragraph: /^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/, 27 | text: /^[^\n]+/ 28 | }; 29 | 30 | block.bullet = /(?:[*+-]|\d+\.)/; 31 | block.item = /^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/; 32 | block.item = replace(block.item, 'gm') 33 | (/bull/g, block.bullet) 34 | (); 35 | 36 | block.list = replace(block.list) 37 | (/bull/g, block.bullet) 38 | ('hr', /\n+(?=(?: *[-*_]){3,} *(?:\n+|$))/) 39 | (); 40 | 41 | block._tag = '(?!(?:' 42 | + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code' 43 | + '|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo' 44 | + '|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|@)\\b'; 45 | 46 | block.html = replace(block.html) 47 | ('comment', //) 48 | ('closed', /<(tag)[\s\S]+?<\/\1>/) 49 | ('closing', /])*?>/) 50 | (/tag/g, block._tag) 51 | (); 52 | 53 | block.paragraph = replace(block.paragraph) 54 | ('hr', block.hr) 55 | ('heading', block.heading) 56 | ('lheading', block.lheading) 57 | ('blockquote', block.blockquote) 58 | ('tag', '<' + block._tag) 59 | ('def', block.def) 60 | (); 61 | 62 | /** 63 | * Normal Block Grammar 64 | */ 65 | 66 | block.normal = merge({}, block); 67 | 68 | /** 69 | * GFM Block Grammar 70 | */ 71 | 72 | block.gfm = merge({}, block.normal, { 73 | fences: /^ *(`{3,}|~{3,}) *(\S+)? *\n([\s\S]+?)\s*\1 *(?:\n+|$)/, 74 | paragraph: /^/ 75 | }); 76 | 77 | block.gfm.paragraph = replace(block.paragraph) 78 | ('(?!', '(?!' 79 | + block.gfm.fences.source.replace('\\1', '\\2') + '|' 80 | + block.list.source.replace('\\1', '\\3') + '|') 81 | (); 82 | 83 | /** 84 | * GFM + Tables Block Grammar 85 | */ 86 | 87 | block.tables = merge({}, block.gfm, { 88 | nptable: /^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/, 89 | table: /^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/ 90 | }); 91 | 92 | /** 93 | * Block Lexer 94 | */ 95 | 96 | function Lexer(options) { 97 | this.tokens = []; 98 | this.tokens.links = {}; 99 | this.options = options || marked.defaults; 100 | this.rules = block.normal; 101 | 102 | if (this.options.gfm) { 103 | if (this.options.tables) { 104 | this.rules = block.tables; 105 | } else { 106 | this.rules = block.gfm; 107 | } 108 | } 109 | } 110 | 111 | /** 112 | * Expose Block Rules 113 | */ 114 | 115 | Lexer.rules = block; 116 | 117 | /** 118 | * Static Lex Method 119 | */ 120 | 121 | Lexer.lex = function(src, options) { 122 | var lexer = new Lexer(options); 123 | return lexer.lex(src); 124 | }; 125 | 126 | /** 127 | * Preprocessing 128 | */ 129 | 130 | Lexer.prototype.lex = function(src) { 131 | src = src 132 | .replace(/\r\n|\r/g, '\n') 133 | .replace(/\t/g, ' ') 134 | .replace(/\u00a0/g, ' ') 135 | .replace(/\u2424/g, '\n'); 136 | 137 | return this.token(src, true); 138 | }; 139 | 140 | /** 141 | * Lexing 142 | */ 143 | 144 | Lexer.prototype.token = function(src, top) { 145 | var src = src.replace(/^ +$/gm, '') 146 | , next 147 | , loose 148 | , cap 149 | , bull 150 | , b 151 | , item 152 | , space 153 | , i 154 | , l; 155 | 156 | while (src) { 157 | // newline 158 | if (cap = this.rules.newline.exec(src)) { 159 | src = src.substring(cap[0].length); 160 | if (cap[0].length > 1) { 161 | this.tokens.push({ 162 | type: 'space' 163 | }); 164 | } 165 | } 166 | 167 | // code 168 | if (cap = this.rules.code.exec(src)) { 169 | src = src.substring(cap[0].length); 170 | cap = cap[0].replace(/^ {4}/gm, ''); 171 | this.tokens.push({ 172 | type: 'code', 173 | text: !this.options.pedantic 174 | ? cap.replace(/\n+$/, '') 175 | : cap 176 | }); 177 | continue; 178 | } 179 | 180 | // fences (gfm) 181 | if (cap = this.rules.fences.exec(src)) { 182 | src = src.substring(cap[0].length); 183 | this.tokens.push({ 184 | type: 'code', 185 | lang: cap[2], 186 | text: cap[3] 187 | }); 188 | continue; 189 | } 190 | 191 | // heading 192 | if (cap = this.rules.heading.exec(src)) { 193 | src = src.substring(cap[0].length); 194 | this.tokens.push({ 195 | type: 'heading', 196 | depth: cap[1].length, 197 | text: cap[2] 198 | }); 199 | continue; 200 | } 201 | 202 | // table no leading pipe (gfm) 203 | if (top && (cap = this.rules.nptable.exec(src))) { 204 | src = src.substring(cap[0].length); 205 | 206 | item = { 207 | type: 'table', 208 | header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */), 209 | align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */), 210 | cells: cap[3].replace(/\n$/, '').split('\n') 211 | }; 212 | 213 | for (i = 0; i < item.align.length; i++) { 214 | if (/^ *-+: *$/.test(item.align[i])) { 215 | item.align[i] = 'right'; 216 | } else if (/^ *:-+: *$/.test(item.align[i])) { 217 | item.align[i] = 'center'; 218 | } else if (/^ *:-+ *$/.test(item.align[i])) { 219 | item.align[i] = 'left'; 220 | } else { 221 | item.align[i] = null; 222 | } 223 | } 224 | 225 | for (i = 0; i < item.cells.length; i++) { 226 | item.cells[i] = item.cells[i].split(/ *\| */); 227 | } 228 | 229 | this.tokens.push(item); 230 | 231 | continue; 232 | } 233 | 234 | // lheading 235 | if (cap = this.rules.lheading.exec(src)) { 236 | src = src.substring(cap[0].length); 237 | this.tokens.push({ 238 | type: 'heading', 239 | depth: cap[2] === '=' ? 1 : 2, 240 | text: cap[1] 241 | }); 242 | continue; 243 | } 244 | 245 | // hr 246 | if (cap = this.rules.hr.exec(src)) { 247 | src = src.substring(cap[0].length); 248 | this.tokens.push({ 249 | type: 'hr' 250 | }); 251 | continue; 252 | } 253 | 254 | // blockquote 255 | if (cap = this.rules.blockquote.exec(src)) { 256 | src = src.substring(cap[0].length); 257 | 258 | this.tokens.push({ 259 | type: 'blockquote_start' 260 | }); 261 | 262 | cap = cap[0].replace(/^ *> ?/gm, ''); 263 | 264 | // Pass `top` to keep the current 265 | // "toplevel" state. This is exactly 266 | // how markdown.pl works. 267 | this.token(cap, top); 268 | 269 | this.tokens.push({ 270 | type: 'blockquote_end' 271 | }); 272 | 273 | continue; 274 | } 275 | 276 | // list 277 | if (cap = this.rules.list.exec(src)) { 278 | src = src.substring(cap[0].length); 279 | bull = cap[2]; 280 | 281 | this.tokens.push({ 282 | type: 'list_start', 283 | ordered: bull.length > 1 284 | }); 285 | 286 | // Get each top-level item. 287 | cap = cap[0].match(this.rules.item); 288 | 289 | next = false; 290 | l = cap.length; 291 | i = 0; 292 | 293 | for (; i < l; i++) { 294 | item = cap[i]; 295 | 296 | // Remove the list item's bullet 297 | // so it is seen as the next token. 298 | space = item.length; 299 | item = item.replace(/^ *([*+-]|\d+\.) +/, ''); 300 | 301 | // Outdent whatever the 302 | // list item contains. Hacky. 303 | if (~item.indexOf('\n ')) { 304 | space -= item.length; 305 | item = !this.options.pedantic 306 | ? item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '') 307 | : item.replace(/^ {1,4}/gm, ''); 308 | } 309 | 310 | // Determine whether the next list item belongs here. 311 | // Backpedal if it does not belong in this list. 312 | if (this.options.smartLists && i !== l - 1) { 313 | b = block.bullet.exec(cap[i + 1])[0]; 314 | if (bull !== b && !(bull.length > 1 && b.length > 1)) { 315 | src = cap.slice(i + 1).join('\n') + src; 316 | i = l - 1; 317 | } 318 | } 319 | 320 | // Determine whether item is loose or not. 321 | // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/ 322 | // for discount behavior. 323 | loose = next || /\n\n(?!\s*$)/.test(item); 324 | if (i !== l - 1) { 325 | next = item.charAt(item.length - 1) === '\n'; 326 | if (!loose) loose = next; 327 | } 328 | 329 | this.tokens.push({ 330 | type: loose 331 | ? 'loose_item_start' 332 | : 'list_item_start' 333 | }); 334 | 335 | // Recurse. 336 | this.token(item, false); 337 | 338 | this.tokens.push({ 339 | type: 'list_item_end' 340 | }); 341 | } 342 | 343 | this.tokens.push({ 344 | type: 'list_end' 345 | }); 346 | 347 | continue; 348 | } 349 | 350 | // html 351 | if (cap = this.rules.html.exec(src)) { 352 | src = src.substring(cap[0].length); 353 | this.tokens.push({ 354 | type: this.options.sanitize 355 | ? 'paragraph' 356 | : 'html', 357 | pre: cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style', 358 | text: cap[0] 359 | }); 360 | continue; 361 | } 362 | 363 | // def 364 | if (top && (cap = this.rules.def.exec(src))) { 365 | src = src.substring(cap[0].length); 366 | this.tokens.links[cap[1].toLowerCase()] = { 367 | href: cap[2], 368 | title: cap[3] 369 | }; 370 | continue; 371 | } 372 | 373 | // table (gfm) 374 | if (top && (cap = this.rules.table.exec(src))) { 375 | src = src.substring(cap[0].length); 376 | 377 | item = { 378 | type: 'table', 379 | header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */), 380 | align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */), 381 | cells: cap[3].replace(/(?: *\| *)?\n$/, '').split('\n') 382 | }; 383 | 384 | for (i = 0; i < item.align.length; i++) { 385 | if (/^ *-+: *$/.test(item.align[i])) { 386 | item.align[i] = 'right'; 387 | } else if (/^ *:-+: *$/.test(item.align[i])) { 388 | item.align[i] = 'center'; 389 | } else if (/^ *:-+ *$/.test(item.align[i])) { 390 | item.align[i] = 'left'; 391 | } else { 392 | item.align[i] = null; 393 | } 394 | } 395 | 396 | for (i = 0; i < item.cells.length; i++) { 397 | item.cells[i] = item.cells[i] 398 | .replace(/^ *\| *| *\| *$/g, '') 399 | .split(/ *\| */); 400 | } 401 | 402 | this.tokens.push(item); 403 | 404 | continue; 405 | } 406 | 407 | // top-level paragraph 408 | if (top && (cap = this.rules.paragraph.exec(src))) { 409 | src = src.substring(cap[0].length); 410 | this.tokens.push({ 411 | type: 'paragraph', 412 | text: cap[1].charAt(cap[1].length - 1) === '\n' 413 | ? cap[1].slice(0, -1) 414 | : cap[1] 415 | }); 416 | continue; 417 | } 418 | 419 | // text 420 | if (cap = this.rules.text.exec(src)) { 421 | // Top-level should never reach here. 422 | src = src.substring(cap[0].length); 423 | this.tokens.push({ 424 | type: 'text', 425 | text: cap[0] 426 | }); 427 | continue; 428 | } 429 | 430 | if (src) { 431 | throw new 432 | Error('Infinite loop on byte: ' + src.charCodeAt(0)); 433 | } 434 | } 435 | 436 | return this.tokens; 437 | }; 438 | 439 | /** 440 | * Inline-Level Grammar 441 | */ 442 | 443 | var inline = { 444 | escape: /^\\([\\`*{}\[\]()#+\-.!_>])/, 445 | autolink: /^<([^ >]+(@|:\/)[^ >]+)>/, 446 | url: noop, 447 | tag: /^|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/, 448 | link: /^!?\[(inside)\]\(href\)/, 449 | reflink: /^!?\[(inside)\]\s*\[([^\]]*)\]/, 450 | nolink: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/, 451 | strong: /^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/, 452 | em: /^\b_((?:__|[\s\S])+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/, 453 | code: /^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/, 454 | br: /^ {2,}\n(?!\s*$)/, 455 | del: noop, 456 | text: /^[\s\S]+?(?=[\\?(?:\s+['"]([\s\S]*?)['"])?\s*/; 461 | 462 | inline.link = replace(inline.link) 463 | ('inside', inline._inside) 464 | ('href', inline._href) 465 | (); 466 | 467 | inline.reflink = replace(inline.reflink) 468 | ('inside', inline._inside) 469 | (); 470 | 471 | /** 472 | * Normal Inline Grammar 473 | */ 474 | 475 | inline.normal = merge({}, inline); 476 | 477 | /** 478 | * Pedantic Inline Grammar 479 | */ 480 | 481 | inline.pedantic = merge({}, inline.normal, { 482 | strong: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/, 483 | em: /^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/ 484 | }); 485 | 486 | /** 487 | * GFM Inline Grammar 488 | */ 489 | 490 | inline.gfm = merge({}, inline.normal, { 491 | escape: replace(inline.escape)('])', '~|])')(), 492 | url: /^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/, 493 | del: /^~~(?=\S)([\s\S]*?\S)~~/, 494 | text: replace(inline.text) 495 | (']|', '~]|') 496 | ('|', '|https?://|') 497 | () 498 | }); 499 | 500 | /** 501 | * GFM + Line Breaks Inline Grammar 502 | */ 503 | 504 | inline.breaks = merge({}, inline.gfm, { 505 | br: replace(inline.br)('{2,}', '*')(), 506 | text: replace(inline.gfm.text)('{2,}', '*')() 507 | }); 508 | 509 | /** 510 | * Inline Lexer & Compiler 511 | */ 512 | 513 | function InlineLexer(links, options) { 514 | this.options = options || marked.defaults; 515 | this.links = links; 516 | this.rules = inline.normal; 517 | 518 | if (!this.links) { 519 | throw new 520 | Error('Tokens array requires a `links` property.'); 521 | } 522 | 523 | if (this.options.gfm) { 524 | if (this.options.breaks) { 525 | this.rules = inline.breaks; 526 | } else { 527 | this.rules = inline.gfm; 528 | } 529 | } else if (this.options.pedantic) { 530 | this.rules = inline.pedantic; 531 | } 532 | } 533 | 534 | /** 535 | * Expose Inline Rules 536 | */ 537 | 538 | InlineLexer.rules = inline; 539 | 540 | /** 541 | * Static Lexing/Compiling Method 542 | */ 543 | 544 | InlineLexer.output = function(src, links, options) { 545 | var inline = new InlineLexer(links, options); 546 | return inline.output(src); 547 | }; 548 | 549 | /** 550 | * Lexing/Compiling 551 | */ 552 | 553 | InlineLexer.prototype.output = function(src) { 554 | var out = '' 555 | , link 556 | , text 557 | , href 558 | , cap; 559 | 560 | while (src) { 561 | // escape 562 | if (cap = this.rules.escape.exec(src)) { 563 | src = src.substring(cap[0].length); 564 | out += cap[1]; 565 | continue; 566 | } 567 | 568 | // autolink 569 | if (cap = this.rules.autolink.exec(src)) { 570 | src = src.substring(cap[0].length); 571 | if (cap[2] === '@') { 572 | text = cap[1].charAt(6) === ':' 573 | ? this.mangle(cap[1].substring(7)) 574 | : this.mangle(cap[1]); 575 | href = this.mangle('mailto:') + text; 576 | } else { 577 | text = escape(cap[1]); 578 | href = text; 579 | } 580 | out += '' 583 | + text 584 | + ''; 585 | continue; 586 | } 587 | 588 | // url (gfm) 589 | if (cap = this.rules.url.exec(src)) { 590 | src = src.substring(cap[0].length); 591 | text = escape(cap[1]); 592 | href = text; 593 | out += '' 596 | + text 597 | + ''; 598 | continue; 599 | } 600 | 601 | // tag 602 | if (cap = this.rules.tag.exec(src)) { 603 | src = src.substring(cap[0].length); 604 | out += this.options.sanitize 605 | ? escape(cap[0]) 606 | : cap[0]; 607 | continue; 608 | } 609 | 610 | // link 611 | if (cap = this.rules.link.exec(src)) { 612 | src = src.substring(cap[0].length); 613 | out += this.outputLink(cap, { 614 | href: cap[2], 615 | title: cap[3] 616 | }); 617 | continue; 618 | } 619 | 620 | // reflink, nolink 621 | if ((cap = this.rules.reflink.exec(src)) 622 | || (cap = this.rules.nolink.exec(src))) { 623 | src = src.substring(cap[0].length); 624 | link = (cap[2] || cap[1]).replace(/\s+/g, ' '); 625 | link = this.links[link.toLowerCase()]; 626 | if (!link || !link.href) { 627 | out += cap[0].charAt(0); 628 | src = cap[0].substring(1) + src; 629 | continue; 630 | } 631 | out += this.outputLink(cap, link); 632 | continue; 633 | } 634 | 635 | // strong 636 | if (cap = this.rules.strong.exec(src)) { 637 | src = src.substring(cap[0].length); 638 | out += '' 639 | + this.output(cap[2] || cap[1]) 640 | + ''; 641 | continue; 642 | } 643 | 644 | // em 645 | if (cap = this.rules.em.exec(src)) { 646 | src = src.substring(cap[0].length); 647 | out += '' 648 | + this.output(cap[2] || cap[1]) 649 | + ''; 650 | continue; 651 | } 652 | 653 | // code 654 | if (cap = this.rules.code.exec(src)) { 655 | src = src.substring(cap[0].length); 656 | out += '' 657 | + escape(cap[2], true) 658 | + ''; 659 | continue; 660 | } 661 | 662 | // br 663 | if (cap = this.rules.br.exec(src)) { 664 | src = src.substring(cap[0].length); 665 | out += '
'; 666 | continue; 667 | } 668 | 669 | // del (gfm) 670 | if (cap = this.rules.del.exec(src)) { 671 | src = src.substring(cap[0].length); 672 | out += '' 673 | + this.output(cap[1]) 674 | + ''; 675 | continue; 676 | } 677 | 678 | // text 679 | if (cap = this.rules.text.exec(src)) { 680 | src = src.substring(cap[0].length); 681 | out += escape(this.smartypants(cap[0])); 682 | continue; 683 | } 684 | 685 | if (src) { 686 | throw new 687 | Error('Infinite loop on byte: ' + src.charCodeAt(0)); 688 | } 689 | } 690 | 691 | return out; 692 | }; 693 | 694 | /** 695 | * Compile Link 696 | */ 697 | 698 | InlineLexer.prototype.outputLink = function(cap, link) { 699 | if (cap[0].charAt(0) !== '!') { 700 | return '' 709 | + this.output(cap[1]) 710 | + ''; 711 | } else { 712 | return ''
 715 |       + escape(cap[1])
 716 |       + ''; 723 | } 724 | }; 725 | 726 | /** 727 | * Smartypants Transformations 728 | */ 729 | 730 | InlineLexer.prototype.smartypants = function(text) { 731 | if (!this.options.smartypants) return text; 732 | return text 733 | // em-dashes 734 | .replace(/--/g, '\u2014') 735 | // opening singles 736 | .replace(/(^|[-\u2014/(\[{"\s])'/g, '$1\u2018') 737 | // closing singles & apostrophes 738 | .replace(/'/g, '\u2019') 739 | // opening doubles 740 | .replace(/(^|[-\u2014/(\[{\u2018\s])"/g, '$1\u201c') 741 | // closing doubles 742 | .replace(/"/g, '\u201d') 743 | // ellipses 744 | .replace(/\.{3}/g, '\u2026'); 745 | }; 746 | 747 | /** 748 | * Mangle Links 749 | */ 750 | 751 | InlineLexer.prototype.mangle = function(text) { 752 | var out = '' 753 | , l = text.length 754 | , i = 0 755 | , ch; 756 | 757 | for (; i < l; i++) { 758 | ch = text.charCodeAt(i); 759 | if (Math.random() > 0.5) { 760 | ch = 'x' + ch.toString(16); 761 | } 762 | out += '&#' + ch + ';'; 763 | } 764 | 765 | return out; 766 | }; 767 | 768 | /** 769 | * Parsing & Compiling 770 | */ 771 | 772 | function Parser(options) { 773 | this.tokens = []; 774 | this.token = null; 775 | this.options = options || marked.defaults; 776 | } 777 | 778 | /** 779 | * Static Parse Method 780 | */ 781 | 782 | Parser.parse = function(src, options) { 783 | var parser = new Parser(options); 784 | return parser.parse(src); 785 | }; 786 | 787 | /** 788 | * Parse Loop 789 | */ 790 | 791 | Parser.prototype.parse = function(src) { 792 | this.inline = new InlineLexer(src.links, this.options); 793 | this.tokens = src.reverse(); 794 | 795 | var out = ''; 796 | while (this.next()) { 797 | out += this.tok(); 798 | } 799 | 800 | return out; 801 | }; 802 | 803 | /** 804 | * Next Token 805 | */ 806 | 807 | Parser.prototype.next = function() { 808 | return this.token = this.tokens.pop(); 809 | }; 810 | 811 | /** 812 | * Preview Next Token 813 | */ 814 | 815 | Parser.prototype.peek = function() { 816 | return this.tokens[this.tokens.length - 1] || 0; 817 | }; 818 | 819 | /** 820 | * Parse Text Tokens 821 | */ 822 | 823 | Parser.prototype.parseText = function() { 824 | var body = this.token.text; 825 | 826 | while (this.peek().type === 'text') { 827 | body += '\n' + this.next().text; 828 | } 829 | 830 | return this.inline.output(body); 831 | }; 832 | 833 | /** 834 | * Parse Current Token 835 | */ 836 | 837 | Parser.prototype.tok = function() { 838 | switch (this.token.type) { 839 | case 'space': { 840 | return ''; 841 | } 842 | case 'hr': { 843 | return '
\n'; 844 | } 845 | case 'heading': { 846 | return '' 851 | + this.inline.output(this.token.text) 852 | + '\n'; 855 | } 856 | case 'code': { 857 | if (this.options.highlight) { 858 | var code = this.options.highlight(this.token.text, this.token.lang); 859 | if (code != null && code !== this.token.text) { 860 | this.token.escaped = true; 861 | this.token.text = code; 862 | } 863 | } 864 | 865 | if (!this.token.escaped) { 866 | this.token.text = escape(this.token.text, true); 867 | } 868 | 869 | return '
'
 877 |         + this.token.text
 878 |         + '
\n'; 879 | } 880 | case 'table': { 881 | var body = '' 882 | , heading 883 | , i 884 | , row 885 | , cell 886 | , j; 887 | 888 | // header 889 | body += '\n\n'; 890 | for (i = 0; i < this.token.header.length; i++) { 891 | heading = this.inline.output(this.token.header[i]); 892 | body += '\n'; 897 | } 898 | body += '\n\n'; 899 | 900 | // body 901 | body += '\n' 902 | for (i = 0; i < this.token.cells.length; i++) { 903 | row = this.token.cells[i]; 904 | body += '\n'; 905 | for (j = 0; j < row.length; j++) { 906 | cell = this.inline.output(row[j]); 907 | body += '\n'; 912 | } 913 | body += '\n'; 914 | } 915 | body += '\n'; 916 | 917 | return '\n' 918 | + body 919 | + '
\n'; 920 | } 921 | case 'blockquote_start': { 922 | var body = ''; 923 | 924 | while (this.next().type !== 'blockquote_end') { 925 | body += this.tok(); 926 | } 927 | 928 | return '
\n' 929 | + body 930 | + '
\n'; 931 | } 932 | case 'list_start': { 933 | var type = this.token.ordered ? 'ol' : 'ul' 934 | , body = ''; 935 | 936 | while (this.next().type !== 'list_end') { 937 | body += this.tok(); 938 | } 939 | 940 | return '<' 941 | + type 942 | + '>\n' 943 | + body 944 | + '\n'; 947 | } 948 | case 'list_item_start': { 949 | var body = ''; 950 | 951 | while (this.next().type !== 'list_item_end') { 952 | body += this.token.type === 'text' 953 | ? this.parseText() 954 | : this.tok(); 955 | } 956 | 957 | return '
  • ' 958 | + body 959 | + '
  • \n'; 960 | } 961 | case 'loose_item_start': { 962 | var body = ''; 963 | 964 | while (this.next().type !== 'list_item_end') { 965 | body += this.tok(); 966 | } 967 | 968 | return '
  • ' 969 | + body 970 | + '
  • \n'; 971 | } 972 | case 'html': { 973 | return !this.token.pre && !this.options.pedantic 974 | ? this.inline.output(this.token.text) 975 | : this.token.text; 976 | } 977 | case 'paragraph': { 978 | return '

    ' 979 | + this.inline.output(this.token.text) 980 | + '

    \n'; 981 | } 982 | case 'text': { 983 | return '

    ' 984 | + this.parseText() 985 | + '

    \n'; 986 | } 987 | } 988 | }; 989 | 990 | /** 991 | * Helpers 992 | */ 993 | 994 | function escape(html, encode) { 995 | return html 996 | .replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&') 997 | .replace(//g, '>') 999 | .replace(/"/g, '"') 1000 | .replace(/'/g, '''); 1001 | } 1002 | 1003 | function replace(regex, opt) { 1004 | regex = regex.source; 1005 | opt = opt || ''; 1006 | return function self(name, val) { 1007 | if (!name) return new RegExp(regex, opt); 1008 | val = val.source || val; 1009 | val = val.replace(/(^|[^\[])\^/g, '$1'); 1010 | regex = regex.replace(name, val); 1011 | return self; 1012 | }; 1013 | } 1014 | 1015 | function noop() {} 1016 | noop.exec = noop; 1017 | 1018 | function merge(obj) { 1019 | var i = 1 1020 | , target 1021 | , key; 1022 | 1023 | for (; i < arguments.length; i++) { 1024 | target = arguments[i]; 1025 | for (key in target) { 1026 | if (Object.prototype.hasOwnProperty.call(target, key)) { 1027 | obj[key] = target[key]; 1028 | } 1029 | } 1030 | } 1031 | 1032 | return obj; 1033 | } 1034 | 1035 | /** 1036 | * Marked 1037 | */ 1038 | 1039 | function marked(src, opt, callback) { 1040 | if (callback || typeof opt === 'function') { 1041 | if (!callback) { 1042 | callback = opt; 1043 | opt = null; 1044 | } 1045 | 1046 | opt = merge({}, marked.defaults, opt || {}); 1047 | 1048 | var highlight = opt.highlight 1049 | , tokens 1050 | , pending 1051 | , i = 0; 1052 | 1053 | try { 1054 | tokens = Lexer.lex(src, opt) 1055 | } catch (e) { 1056 | return callback(e); 1057 | } 1058 | 1059 | pending = tokens.length; 1060 | 1061 | var done = function() { 1062 | var out, err; 1063 | 1064 | try { 1065 | out = Parser.parse(tokens, opt); 1066 | } catch (e) { 1067 | err = e; 1068 | } 1069 | 1070 | opt.highlight = highlight; 1071 | 1072 | return err 1073 | ? callback(err) 1074 | : callback(null, out); 1075 | }; 1076 | 1077 | if (!highlight || highlight.length < 3) { 1078 | return done(); 1079 | } 1080 | 1081 | delete opt.highlight; 1082 | 1083 | if (!pending) return done(); 1084 | 1085 | for (; i < tokens.length; i++) { 1086 | (function(token) { 1087 | if (token.type !== 'code') { 1088 | return --pending || done(); 1089 | } 1090 | return highlight(token.text, token.lang, function(err, code) { 1091 | if (code == null || code === token.text) { 1092 | return --pending || done(); 1093 | } 1094 | token.text = code; 1095 | token.escaped = true; 1096 | --pending || done(); 1097 | }); 1098 | })(tokens[i]); 1099 | } 1100 | 1101 | return; 1102 | } 1103 | try { 1104 | if (opt) opt = merge({}, marked.defaults, opt); 1105 | return Parser.parse(Lexer.lex(src, opt), opt); 1106 | } catch (e) { 1107 | e.message += '\nPlease report this to https://github.com/chjj/marked.'; 1108 | if ((opt || marked.defaults).silent) { 1109 | return '

    An error occured:

    '
    1110 |         + escape(e.message + '', true)
    1111 |         + '
    '; 1112 | } 1113 | throw e; 1114 | } 1115 | } 1116 | 1117 | /** 1118 | * Options 1119 | */ 1120 | 1121 | marked.options = 1122 | marked.setOptions = function(opt) { 1123 | merge(marked.defaults, opt); 1124 | return marked; 1125 | }; 1126 | 1127 | marked.defaults = { 1128 | gfm: true, 1129 | tables: true, 1130 | breaks: false, 1131 | pedantic: false, 1132 | sanitize: false, 1133 | smartLists: false, 1134 | silent: false, 1135 | highlight: null, 1136 | langPrefix: 'lang-', 1137 | smartypants: false 1138 | }; 1139 | 1140 | /** 1141 | * Expose 1142 | */ 1143 | 1144 | marked.Parser = Parser; 1145 | marked.parser = Parser.parse; 1146 | 1147 | marked.Lexer = Lexer; 1148 | marked.lexer = Lexer.lex; 1149 | 1150 | marked.InlineLexer = InlineLexer; 1151 | marked.inlineLexer = InlineLexer.output; 1152 | 1153 | marked.parse = marked; 1154 | 1155 | if (typeof exports === 'object') { 1156 | module.exports = marked; 1157 | } else if (typeof define === 'function' && define.amd) { 1158 | define(function() { return marked; }); 1159 | } else { 1160 | this.marked = marked; 1161 | } 1162 | 1163 | }).call(function() { 1164 | return this || (typeof window !== 'undefined' ? window : global); 1165 | }()); 1166 | --------------------------------------------------------------------------------