├── .gitignore ├── README.md ├── assets ├── css │ ├── font-awesome.min.css │ └── style.css ├── fonts │ ├── FontAwesome.otf │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.svg │ ├── fontawesome-webfont.ttf │ └── fontawesome-webfont.woff └── js │ └── default.js ├── favicon.ico ├── favicon.png ├── index.html ├── lib ├── jquery-1.11.1.min.js └── markdown │ ├── Markdown.Converter.js │ ├── Markdown.Editor.js │ ├── Markdown.Extra.js │ ├── Markdown.Sanitizer.js │ └── prettify.js └── preview.png /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Markdown online editor 2 | ====================== 3 | This is a simple markdown editor. You can type in one of the page boxes and on the other side you will see the respective markdown translation to our normal perspective. 4 | 5 | ## Demo 6 | ![HowItWork](preview.png) 7 | 8 | ## Contributing 9 | This is a simple project that I built some time ago to learn more about markdown parsers in javascript. 10 | At this point, there is no active development on this project. 11 | But, if you feel like you can add/contribute something, feel free: Create an Issue and we will work together on the PullRequest. 12 | 13 | ## Acknowledgements 14 | This project would not be possible without some libraries as: 15 | 16 | + [jmcmanus/pagedown-extra](https://github.com/jmcmanus/pagedown-extra) 17 | + [FortAwesome/Font-Awesome](https://github.com/FortAwesome/Font-Awesome) 18 | -------------------------------------------------------------------------------- /assets/css/font-awesome.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.1.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.1.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.1.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff?v=4.1.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.1.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.1.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:normal;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:spin 2s infinite linear;-moz-animation:spin 2s infinite linear;-o-animation:spin 2s infinite linear;animation:spin 2s infinite linear}@-moz-keyframes spin{0%{-moz-transform:rotate(0deg)}100%{-moz-transform:rotate(359deg)}}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg)}}@-o-keyframes spin{0%{-o-transform:rotate(0deg)}100%{-o-transform:rotate(359deg)}}@keyframes spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);-webkit-transform:scale(-1, 1);-moz-transform:scale(-1, 1);-ms-transform:scale(-1, 1);-o-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);-webkit-transform:scale(1, -1);-moz-transform:scale(1, -1);-ms-transform:scale(1, -1);-o-transform:scale(1, -1);transform:scale(1, -1)}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-square:before,.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"} -------------------------------------------------------------------------------- /assets/css/style.css: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/css?family=Open+Sans|Special+Elite|Raleway); 2 | 3 | body, html {border: 0px; margin: 0px; padding: 0px; 4 | height:100%; position:relative; width:100%;} 5 | 6 | a, a:link { 7 | text-decoration: none; 8 | color: inherit; 9 | } 10 | 11 | a:hover { 12 | color: #787878; 13 | } 14 | 15 | .options { 16 | color: rgb(0,0,0,1); 17 | cursor: pointer; 18 | } 19 | 20 | .options i { 21 | margin-left: 10px; 22 | } 23 | 24 | .options i:hover { 25 | color: #787878; 26 | } 27 | 28 | .left{ 29 | float: left; 30 | } 31 | 32 | .right{ 33 | float: right; 34 | } 35 | 36 | .container { 37 | width: 95%; 38 | height: 90%; 39 | margin: 10px auto; 40 | } 41 | 42 | .container #text-holder { 43 | width: 47%; 44 | height: 100%; 45 | } 46 | 47 | .container #text { 48 | width: 100%; 49 | height: 85%; 50 | border: none; 51 | overflow: auto; 52 | outline: none; 53 | resize:none; 54 | 55 | -webkit-box-shadow: none; 56 | -moz-box-shadow: none; 57 | box-shadow: none; 58 | 59 | color: inherit; 60 | background-color: inherit; 61 | 62 | padding: 22px 5px 0 5px; 63 | font-family: 'Raleway', sans-serif; 64 | 65 | font-size: 18px; 66 | line-height: 30px; 67 | font-weight: 300; 68 | text-align: left; 69 | } 70 | 71 | .container #preview { 72 | width: 47%; 73 | height: 85%; 74 | float: right; 75 | display: inline-block; 76 | overflow-y: auto; 77 | overflow-x: hidden; 78 | word-wrap: break-word; 79 | 80 | 81 | font-family: 'Open Sans', sans-serif; 82 | font-size: 18px; 83 | line-height: 30px; 84 | font-weight: 300; 85 | text-align: left; 86 | } 87 | 88 | .help-wrapper { 89 | position: absolute; 90 | bottom: 5px; 91 | left: 0; 92 | width: 100%; 93 | text-align: center; 94 | } 95 | 96 | .help-wrapper .hellper{ 97 | position: relative; 98 | width: 80%; 99 | margin:0 auto; 100 | background-color: rgba(0,0,0,0.1); 101 | border-radius: 5px; 102 | padding: 3px 5px 3px 5px; 103 | font-family: 'Open Sans', sans-serif; 104 | text-align: right; 105 | } 106 | 107 | .help-wrapper .hellper table { 108 | width: 100%; 109 | font-size: 12px; 110 | text-align: left; 111 | border: 0; 112 | } 113 | 114 | .help-wrapper .hellper table .symbol { 115 | display: inline-block; 116 | text-align: right; 117 | width: 100px; 118 | overflow: hidden; 119 | margin-right: 15px; 120 | } 121 | 122 | .help-wrapper .hellper table .info { 123 | display: inline-block; 124 | text-align: left; 125 | margin-left: 15px; 126 | } 127 | 128 | .help-wrapper .hellper table .symbol {font-family: 'Open Sans', sans-serif;} 129 | .help-wrapper .hellper table .info {font-family: 'Special Elite', cursive;} 130 | 131 | .help-wrapper .hellper .close { 132 | position: absolute; 133 | right: 7px; 134 | cursor: pointer; 135 | } 136 | 137 | .help-wrapper .hellper .close:hover {color: #787878;} 138 | 139 | @media (max-width: 1080px) { 140 | .help-wrapper .hellper table .symbol {margin-right: 0;} 141 | .help-wrapper .hellper table .info {margin-left: 0;} 142 | } 143 | 144 | @media (max-width: 920px) { 145 | .help-wrapper .hellper {width: 97%} 146 | } 147 | 148 | @media (max-width: 750px) { 149 | .help-wrapper .hellper {display: none;} 150 | .options .fa-info-circle {display: none;} 151 | .container #text, .container #preview {float: none;width: 100%;height: 40%;} 152 | } 153 | -------------------------------------------------------------------------------- /assets/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariogarridopt/markdown-editor-html/b7091169514e97a1db1b7797dba63462435018c0/assets/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /assets/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariogarridopt/markdown-editor-html/b7091169514e97a1db1b7797dba63462435018c0/assets/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /assets/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariogarridopt/markdown-editor-html/b7091169514e97a1db1b7797dba63462435018c0/assets/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /assets/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariogarridopt/markdown-editor-html/b7091169514e97a1db1b7797dba63462435018c0/assets/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /assets/js/default.js: -------------------------------------------------------------------------------- 1 | var colorStatus = true, 2 | infoStatus = true; 3 | 4 | $(function() { 5 | // When using more than one `textarea` on your page, change the following line to match the one you’re after 6 | var $textarea = $('textarea'), 7 | $preview = $('
').insertAfter('#text-holder'), 8 | converter = new Markdown.getSanitizingConverter(); 9 | Markdown.Extra.init(converter); 10 | convert = converter.makeHtml; 11 | 12 | var text = sessionStorage.getItem("mkdowninfo"); 13 | if(text == null || text == "null" || text == "") 14 | text = "";//"Hi\n==\nYou can type your text **here**."; 15 | 16 | // instead of `keyup`, consider using `input` using this plugin: http://mathiasbynens.be/notes/oninput#comment-1 17 | $textarea.keyup(function() { 18 | if(text == null){ 19 | $preview.html(convert($textarea.val())); 20 | sessionStorage.setItem("mkdowninfo", $textarea.val()); 21 | }else{ 22 | $textarea.val(text); 23 | $preview.html(convert(text)); 24 | text = null; 25 | } 26 | }).trigger('keyup'); 27 | }); 28 | 29 | function toggleInfo() { 30 | if(infoStatus == false) { 31 | var height = $(document.body).height() - 150; 32 | $( ".help-wrapper" ).fadeIn( "fast" ); 33 | $('textarea').css("height", height + "px"); 34 | $('#preview').css("height", height + "px"); 35 | } else { 36 | $( ".help-wrapper" ).fadeOut( "fast" ); 37 | $('textarea').css("height", "100%"); 38 | $('#preview').css("height", "100%"); 39 | } 40 | infoStatus = !infoStatus; 41 | } 42 | 43 | function toggleColor(){ 44 | if(colorStatus == false) { 45 | $('body').css("background-color", "white"); 46 | $('body').css("color", "black"); 47 | $('.help-wrapper .hellper').css("background-color", "rgba(0,0,0,0.1)"); 48 | $('textarea').css("background-color", "white"); 49 | $('textarea').css("color", "black"); 50 | $('#preview').css("background-color", "white"); 51 | $('#preview').css("color", "black"); 52 | } else { 53 | $('body').css("background-color", "black"); 54 | $('body').css("color", "white"); 55 | $('.help-wrapper .hellper').css("background-color", "rgba(255,255,255,0.1)"); 56 | $('textarea').css("background-color", "black"); 57 | $('textarea').css("color", "white"); 58 | $('#preview').css("background-color", "black"); 59 | $('#preview').css("color", "white"); 60 | } 61 | colorStatus = !colorStatus; 62 | } 63 | 64 | function clearPage() { 65 | $('textarea').val(""); 66 | $('#preview').html(""); 67 | sessionStorage.setItem("mkdowninfo", ""); 68 | } 69 | 70 | function copyToClipboard() { 71 | window.prompt("Copy to clipboard: Ctrl+C, Enter", $('textarea').val()); 72 | } 73 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariogarridopt/markdown-editor-html/b7091169514e97a1db1b7797dba63462435018c0/favicon.ico -------------------------------------------------------------------------------- /favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariogarridopt/markdown-editor-html/b7091169514e97a1db1b7797dba63462435018c0/favicon.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Markdown Text 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 |
30 |
31 | 32 | 33 |
34 | 35 |
36 | 37 |
38 |
39 |
40 |
41 | 42 | 43 | 47 | 51 | 55 | 59 | 60 | 61 | 65 | 69 | 73 | 77 | 78 | 79 | 80 | 84 | 88 | 92 | 95 | 96 |
44 | # ## ...> 45 | h1 h2 ... 46 | 48 | *text*> 49 | Italic 50 | 52 | 1. text 2. text> 53 | List 54 | 56 | `code`> 57 | In-line code 58 |
62 | [text](https://link)> 63 | Link 64 | 66 | **text**> 67 | Bold 68 | 70 | + bla + bla> 71 | List 72 | 74 | ***> 75 | Horizontal Rule 76 |
81 | ![alt text](link)> 82 | Image 83 | 85 | ~~text~~> 86 | Strikethrough 87 | 89 | > text> 90 | Text quote 91 | 93 | more... 94 |
97 |
98 |
99 |
100 | 101 | 102 | -------------------------------------------------------------------------------- /lib/markdown/Markdown.Converter.js: -------------------------------------------------------------------------------- 1 | var Markdown; 2 | 3 | if (typeof exports === "object" && typeof require === "function") // we're in a CommonJS (e.g. Node.js) module 4 | Markdown = exports; 5 | else 6 | Markdown = {}; 7 | 8 | // The following text is included for historical reasons, but should 9 | // be taken with a pinch of salt; it's not all true anymore. 10 | 11 | // 12 | // Wherever possible, Showdown is a straight, line-by-line port 13 | // of the Perl version of Markdown. 14 | // 15 | // This is not a normal parser design; it's basically just a 16 | // series of string substitutions. It's hard to read and 17 | // maintain this way, but keeping Showdown close to the original 18 | // design makes it easier to port new features. 19 | // 20 | // More importantly, Showdown behaves like markdown.pl in most 21 | // edge cases. So web applications can do client-side preview 22 | // in Javascript, and then build identical HTML on the server. 23 | // 24 | // This port needs the new RegExp functionality of ECMA 262, 25 | // 3rd Edition (i.e. Javascript 1.5). Most modern web browsers 26 | // should do fine. Even with the new regular expression features, 27 | // We do a lot of work to emulate Perl's regex functionality. 28 | // The tricky changes in this file mostly have the "attacklab:" 29 | // label. Major or self-explanatory changes don't. 30 | // 31 | // Smart diff tools like Araxis Merge will be able to match up 32 | // this file with markdown.pl in a useful way. A little tweaking 33 | // helps: in a copy of markdown.pl, replace "#" with "//" and 34 | // replace "$text" with "text". Be sure to ignore whitespace 35 | // and line endings. 36 | // 37 | 38 | 39 | // 40 | // Usage: 41 | // 42 | // var text = "Markdown *rocks*."; 43 | // 44 | // var converter = new Markdown.Converter(); 45 | // var html = converter.makeHtml(text); 46 | // 47 | // alert(html); 48 | // 49 | // Note: move the sample code to the bottom of this 50 | // file before uncommenting it. 51 | // 52 | 53 | (function () { 54 | 55 | function identity(x) { return x; } 56 | function returnFalse(x) { return false; } 57 | 58 | function HookCollection() { } 59 | 60 | HookCollection.prototype = { 61 | 62 | chain: function (hookname, func) { 63 | var original = this[hookname]; 64 | if (!original) 65 | throw new Error("unknown hook " + hookname); 66 | 67 | if (original === identity) 68 | this[hookname] = func; 69 | else 70 | this[hookname] = function (text) { 71 | var args = Array.prototype.slice.call(arguments, 0); 72 | args[0] = original.apply(null, args); 73 | return func.apply(null, args); 74 | }; 75 | }, 76 | set: function (hookname, func) { 77 | if (!this[hookname]) 78 | throw new Error("unknown hook " + hookname); 79 | this[hookname] = func; 80 | }, 81 | addNoop: function (hookname) { 82 | this[hookname] = identity; 83 | }, 84 | addFalse: function (hookname) { 85 | this[hookname] = returnFalse; 86 | } 87 | }; 88 | 89 | Markdown.HookCollection = HookCollection; 90 | 91 | // g_urls and g_titles allow arbitrary user-entered strings as keys. This 92 | // caused an exception (and hence stopped the rendering) when the user entered 93 | // e.g. [push] or [__proto__]. Adding a prefix to the actual key prevents this 94 | // (since no builtin property starts with "s_"). See 95 | // http://meta.stackoverflow.com/questions/64655/strange-wmd-bug 96 | // (granted, switching from Array() to Object() alone would have left only __proto__ 97 | // to be a problem) 98 | function SaveHash() { } 99 | SaveHash.prototype = { 100 | set: function (key, value) { 101 | this["s_" + key] = value; 102 | }, 103 | get: function (key) { 104 | return this["s_" + key]; 105 | } 106 | }; 107 | 108 | Markdown.Converter = function () { 109 | var pluginHooks = this.hooks = new HookCollection(); 110 | 111 | // given a URL that was encountered by itself (without markup), should return the link text that's to be given to this link 112 | pluginHooks.addNoop("plainLinkText"); 113 | 114 | // called with the orignal text as given to makeHtml. The result of this plugin hook is the actual markdown source that will be cooked 115 | pluginHooks.addNoop("preConversion"); 116 | 117 | // called with the text once all normalizations have been completed (tabs to spaces, line endings, etc.), but before any conversions have 118 | pluginHooks.addNoop("postNormalization"); 119 | 120 | // Called with the text before / after creating block elements like code blocks and lists. Note that this is called recursively 121 | // with inner content, e.g. it's called with the full text, and then only with the content of a blockquote. The inner 122 | // call will receive outdented text. 123 | pluginHooks.addNoop("preBlockGamut"); 124 | pluginHooks.addNoop("postBlockGamut"); 125 | 126 | // called with the text of a single block element before / after the span-level conversions (bold, code spans, etc.) have been made 127 | pluginHooks.addNoop("preSpanGamut"); 128 | pluginHooks.addNoop("postSpanGamut"); 129 | 130 | // called with the final cooked HTML code. The result of this plugin hook is the actual output of makeHtml 131 | pluginHooks.addNoop("postConversion"); 132 | 133 | // 134 | // Private state of the converter instance: 135 | // 136 | 137 | // Global hashes, used by various utility routines 138 | var g_urls; 139 | var g_titles; 140 | var g_html_blocks; 141 | 142 | // Used to track when we're inside an ordered or unordered list 143 | // (see _ProcessListItems() for details): 144 | var g_list_level; 145 | 146 | this.makeHtml = function (text) { 147 | 148 | // 149 | // Main function. The order in which other subs are called here is 150 | // essential. Link and image substitutions need to happen before 151 | // _EscapeSpecialCharsWithinTagAttributes(), so that any *'s or _'s in the 152 | // and tags get encoded. 153 | // 154 | 155 | // This will only happen if makeHtml on the same converter instance is called from a plugin hook. 156 | // Don't do that. 157 | if (g_urls) 158 | throw new Error("Recursive call to converter.makeHtml"); 159 | 160 | // Create the private state objects. 161 | g_urls = new SaveHash(); 162 | g_titles = new SaveHash(); 163 | g_html_blocks = []; 164 | g_list_level = 0; 165 | 166 | text = pluginHooks.preConversion(text); 167 | 168 | // attacklab: Replace ~ with ~T 169 | // This lets us use tilde as an escape char to avoid md5 hashes 170 | // The choice of character is arbitray; anything that isn't 171 | // magic in Markdown will work. 172 | text = text.replace(/~/g, "~T"); 173 | 174 | // attacklab: Replace $ with ~D 175 | // RegExp interprets $ as a special character 176 | // when it's in a replacement string 177 | text = text.replace(/\$/g, "~D"); 178 | 179 | // Standardize line endings 180 | text = text.replace(/\r\n/g, "\n"); // DOS to Unix 181 | text = text.replace(/\r/g, "\n"); // Mac to Unix 182 | 183 | // Make sure text begins and ends with a couple of newlines: 184 | text = "\n\n" + text + "\n\n"; 185 | 186 | // Convert all tabs to spaces. 187 | text = _Detab(text); 188 | 189 | // Strip any lines consisting only of spaces and tabs. 190 | // This makes subsequent regexen easier to write, because we can 191 | // match consecutive blank lines with /\n+/ instead of something 192 | // contorted like /[ \t]*\n+/ . 193 | text = text.replace(/^[ \t]+$/mg, ""); 194 | 195 | text = pluginHooks.postNormalization(text); 196 | 197 | // Turn block-level HTML blocks into hash entries 198 | text = _HashHTMLBlocks(text); 199 | 200 | // Strip link definitions, store in hashes. 201 | text = _StripLinkDefinitions(text); 202 | 203 | text = _RunBlockGamut(text); 204 | 205 | text = _UnescapeSpecialChars(text); 206 | 207 | // attacklab: Restore dollar signs 208 | text = text.replace(/~D/g, "$$"); 209 | 210 | // attacklab: Restore tildes 211 | text = text.replace(/~T/g, "~"); 212 | 213 | text = pluginHooks.postConversion(text); 214 | 215 | g_html_blocks = g_titles = g_urls = null; 216 | 217 | return text; 218 | }; 219 | 220 | function _StripLinkDefinitions(text) { 221 | // 222 | // Strips link definitions from text, stores the URLs and titles in 223 | // hash references. 224 | // 225 | 226 | // Link defs are in the form: ^[id]: url "optional title" 227 | 228 | /* 229 | text = text.replace(/ 230 | ^[ ]{0,3}\[(.+)\]: // id = $1 attacklab: g_tab_width - 1 231 | [ \t]* 232 | \n? // maybe *one* newline 233 | [ \t]* 234 | ? // url = $2 235 | (?=\s|$) // lookahead for whitespace instead of the lookbehind removed below 236 | [ \t]* 237 | \n? // maybe one newline 238 | [ \t]* 239 | ( // (potential) title = $3 240 | (\n*) // any lines skipped = $4 attacklab: lookbehind removed 241 | [ \t]+ 242 | ["(] 243 | (.+?) // title = $5 244 | [")] 245 | [ \t]* 246 | )? // title is optional 247 | (?:\n+|$) 248 | /gm, function(){...}); 249 | */ 250 | 251 | text = text.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*?(?=\s|$)[ \t]*\n?[ \t]*((\n*)["(](.+?)[")][ \t]*)?(?:\n+)/gm, 252 | function (wholeMatch, m1, m2, m3, m4, m5) { 253 | m1 = m1.toLowerCase(); 254 | g_urls.set(m1, _EncodeAmpsAndAngles(m2)); // Link IDs are case-insensitive 255 | if (m4) { 256 | // Oops, found blank lines, so it's not a title. 257 | // Put back the parenthetical statement we stole. 258 | return m3; 259 | } else if (m5) { 260 | g_titles.set(m1, m5.replace(/"/g, """)); 261 | } 262 | 263 | // Completely remove the definition from the text 264 | return ""; 265 | } 266 | ); 267 | 268 | return text; 269 | } 270 | 271 | function _HashHTMLBlocks(text) { 272 | 273 | // Hashify HTML blocks: 274 | // We only want to do this for block-level HTML tags, such as headers, 275 | // lists, and tables. That's because we still want to wrap

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

284 | //
285 | // tags for inner block must be indented. 286 | //
287 | //
288 | // 289 | // The outermost tags must start at the left margin for this to match, and 290 | // the inner nested divs must be indented. 291 | // We need to do this before the next, more liberal match, because the next 292 | // match will start at the first `
` and stop at the first `
`. 293 | 294 | // attacklab: This regex can be expensive when it fails. 295 | 296 | /* 297 | text = text.replace(/ 298 | ( // save in $1 299 | ^ // start of line (with /m) 300 | <($block_tags_a) // start tag = $2 301 | \b // word break 302 | // attacklab: hack around khtml/pcre bug... 303 | [^\r]*?\n // any number of lines, minimally matching 304 | // the matching end tag 305 | [ \t]* // trailing spaces/tabs 306 | (?=\n+) // followed by a newline 307 | ) // attacklab: there are sentinel newlines at end of document 308 | /gm,function(){...}}; 309 | */ 310 | 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); 311 | 312 | // 313 | // Now match more liberally, simply from `\n` to `\n` 314 | // 315 | 316 | /* 317 | text = text.replace(/ 318 | ( // save in $1 319 | ^ // start of line (with /m) 320 | <($block_tags_b) // start tag = $2 321 | \b // word break 322 | // attacklab: hack around khtml/pcre bug... 323 | [^\r]*? // any number of lines, minimally matching 324 | .* // the matching end tag 325 | [ \t]* // trailing spaces/tabs 326 | (?=\n+) // followed by a newline 327 | ) // attacklab: there are sentinel newlines at end of document 328 | /gm,function(){...}}; 329 | */ 330 | 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); 331 | 332 | // Special case just for
. It was easier to make a special case than 333 | // to make the other regex more complicated. 334 | 335 | /* 336 | text = text.replace(/ 337 | \n // Starting after a blank line 338 | [ ]{0,3} 339 | ( // save in $1 340 | (<(hr) // start tag = $2 341 | \b // word break 342 | ([^<>])*? 343 | \/?>) // the matching end tag 344 | [ \t]* 345 | (?=\n{2,}) // followed by a blank line 346 | ) 347 | /g,hashElement); 348 | */ 349 | text = text.replace(/\n[ ]{0,3}((<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g, hashElement); 350 | 351 | // Special case for standalone HTML comments: 352 | 353 | /* 354 | text = text.replace(/ 355 | \n\n // Starting after a blank line 356 | [ ]{0,3} // attacklab: g_tab_width - 1 357 | ( // save in $1 358 | -]|-[^>])(?:[^-]|-[^-])*)--) // see http://www.w3.org/TR/html-markup/syntax.html#comments and http://meta.stackoverflow.com/q/95256 360 | > 361 | [ \t]* 362 | (?=\n{2,}) // followed by a blank line 363 | ) 364 | /g,hashElement); 365 | */ 366 | text = text.replace(/\n\n[ ]{0,3}(-]|-[^>])(?:[^-]|-[^-])*)--)>[ \t]*(?=\n{2,}))/g, hashElement); 367 | 368 | // PHP and ASP-style processor instructions ( and <%...%>) 369 | 370 | /* 371 | text = text.replace(/ 372 | (?: 373 | \n\n // Starting after a blank line 374 | ) 375 | ( // save in $1 376 | [ ]{0,3} // attacklab: g_tab_width - 1 377 | (?: 378 | <([?%]) // $2 379 | [^\r]*? 380 | \2> 381 | ) 382 | [ \t]* 383 | (?=\n{2,}) // followed by a blank line 384 | ) 385 | /g,hashElement); 386 | */ 387 | text = text.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g, hashElement); 388 | 389 | return text; 390 | } 391 | 392 | function hashElement(wholeMatch, m1) { 393 | var blockText = m1; 394 | 395 | // Undo double lines 396 | blockText = blockText.replace(/^\n+/, ""); 397 | 398 | // strip trailing blank lines 399 | blockText = blockText.replace(/\n+$/g, ""); 400 | 401 | // Replace the element text with a marker ("~KxK" where x is its key) 402 | blockText = "\n\n~K" + (g_html_blocks.push(blockText) - 1) + "K\n\n"; 403 | 404 | return blockText; 405 | } 406 | 407 | var blockGamutHookCallback = function (t) { return _RunBlockGamut(t); } 408 | 409 | function _RunBlockGamut(text, doNotUnhash) { 410 | // 411 | // These are all the transformations that form block-level 412 | // tags like paragraphs, headers, and list items. 413 | // 414 | 415 | text = pluginHooks.preBlockGamut(text, blockGamutHookCallback); 416 | 417 | text = _DoHeaders(text); 418 | 419 | // Do Horizontal Rules: 420 | var replacement = "
\n"; 421 | text = text.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm, replacement); 422 | text = text.replace(/^[ ]{0,2}([ ]?-[ ]?){3,}[ \t]*$/gm, replacement); 423 | text = text.replace(/^[ ]{0,2}([ ]?_[ ]?){3,}[ \t]*$/gm, replacement); 424 | 425 | text = _DoLists(text); 426 | text = _DoCodeBlocks(text); 427 | text = _DoBlockQuotes(text); 428 | 429 | text = pluginHooks.postBlockGamut(text, blockGamutHookCallback); 430 | 431 | // We already ran _HashHTMLBlocks() before, in Markdown(), but that 432 | // was to escape raw HTML in the original Markdown source. This time, 433 | // we're escaping the markup we've just created, so that we don't wrap 434 | //

tags around block-level tags. 435 | text = _HashHTMLBlocks(text); 436 | text = _FormParagraphs(text, doNotUnhash); 437 | 438 | return text; 439 | } 440 | 441 | function _RunSpanGamut(text) { 442 | // 443 | // These are all the transformations that occur *within* block-level 444 | // tags like paragraphs, headers, and list items. 445 | // 446 | 447 | text = pluginHooks.preSpanGamut(text); 448 | 449 | text = _DoCodeSpans(text); 450 | text = _EscapeSpecialCharsWithinTagAttributes(text); 451 | text = _EncodeBackslashEscapes(text); 452 | 453 | // Process anchor and image tags. Images must come first, 454 | // because ![foo][f] looks like an anchor. 455 | text = _DoImages(text); 456 | text = _DoAnchors(text); 457 | 458 | // Make links out of things like `` 459 | // Must come after _DoAnchors(), because you can use < and > 460 | // delimiters in inline links like [this](). 461 | text = _DoAutoLinks(text); 462 | 463 | text = text.replace(/~P/g, "://"); // put in place to prevent autolinking; reset now 464 | 465 | text = _EncodeAmpsAndAngles(text); 466 | text = _DoItalicsAndBold(text); 467 | 468 | // Do hard breaks: 469 | text = text.replace(/ +\n/g, "
\n"); 470 | 471 | text = pluginHooks.postSpanGamut(text); 472 | 473 | return text; 474 | } 475 | 476 | function _EscapeSpecialCharsWithinTagAttributes(text) { 477 | // 478 | // Within tags -- meaning between < and > -- encode [\ ` * _] so they 479 | // don't conflict with their use in Markdown for code, italics and strong. 480 | // 481 | 482 | // Build a regex to find HTML tags and comments. See Friedl's 483 | // "Mastering Regular Expressions", 2nd Ed., pp. 200-201. 484 | 485 | // SE: changed the comment part of the regex 486 | 487 | var regex = /(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|-]|-[^>])(?:[^-]|-[^-])*)--)>)/gi; 488 | 489 | text = text.replace(regex, function (wholeMatch) { 490 | var tag = wholeMatch.replace(/(.)<\/?code>(?=.)/g, "$1`"); 491 | tag = escapeCharacters(tag, wholeMatch.charAt(1) == "!" ? "\\`*_/" : "\\`*_"); // also escape slashes in comments to prevent autolinking there -- http://meta.stackoverflow.com/questions/95987 492 | return tag; 493 | }); 494 | 495 | return text; 496 | } 497 | 498 | function _DoAnchors(text) { 499 | // 500 | // Turn Markdown link shortcuts into XHTML
tags. 501 | // 502 | // 503 | // First, handle reference-style links: [link text] [id] 504 | // 505 | 506 | /* 507 | text = text.replace(/ 508 | ( // wrap whole match in $1 509 | \[ 510 | ( 511 | (?: 512 | \[[^\]]*\] // allow brackets nested one level 513 | | 514 | [^\[] // or anything else 515 | )* 516 | ) 517 | \] 518 | 519 | [ ]? // one optional space 520 | (?:\n[ ]*)? // one optional newline followed by spaces 521 | 522 | \[ 523 | (.*?) // id = $3 524 | \] 525 | ) 526 | ()()()() // pad remaining backreferences 527 | /g, writeAnchorTag); 528 | */ 529 | text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g, writeAnchorTag); 530 | 531 | // 532 | // Next, inline-style links: [link text](url "optional title") 533 | // 534 | 535 | /* 536 | text = text.replace(/ 537 | ( // wrap whole match in $1 538 | \[ 539 | ( 540 | (?: 541 | \[[^\]]*\] // allow brackets nested one level 542 | | 543 | [^\[\]] // or anything else 544 | )* 545 | ) 546 | \] 547 | \( // literal paren 548 | [ \t]* 549 | () // no id, so leave $3 empty 550 | ? 557 | [ \t]* 558 | ( // $5 559 | (['"]) // quote char = $6 560 | (.*?) // Title = $7 561 | \6 // matching quote 562 | [ \t]* // ignore any spaces/tabs between closing quote and ) 563 | )? // title is optional 564 | \) 565 | ) 566 | /g, writeAnchorTag); 567 | */ 568 | 569 | text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g, writeAnchorTag); 570 | 571 | // 572 | // Last, handle reference-style shortcuts: [link text] 573 | // These must come last in case you've also got [link test][1] 574 | // or [link test](/foo) 575 | // 576 | 577 | /* 578 | text = text.replace(/ 579 | ( // wrap whole match in $1 580 | \[ 581 | ([^\[\]]+) // link text = $2; can't contain '[' or ']' 582 | \] 583 | ) 584 | ()()()()() // pad rest of backreferences 585 | /g, writeAnchorTag); 586 | */ 587 | text = text.replace(/(\[([^\[\]]+)\])()()()()()/g, writeAnchorTag); 588 | 589 | return text; 590 | } 591 | 592 | function writeAnchorTag(wholeMatch, m1, m2, m3, m4, m5, m6, m7) { 593 | if (m7 == undefined) m7 = ""; 594 | var whole_match = m1; 595 | var link_text = m2.replace(/:\/\//g, "~P"); // to prevent auto-linking withing the link. will be converted back after the auto-linker runs 596 | var link_id = m3.toLowerCase(); 597 | var url = m4; 598 | var title = m7; 599 | 600 | if (url == "") { 601 | if (link_id == "") { 602 | // lower-case and turn embedded newlines into spaces 603 | link_id = link_text.toLowerCase().replace(/ ?\n/g, " "); 604 | } 605 | url = "#" + link_id; 606 | 607 | if (g_urls.get(link_id) != undefined) { 608 | url = g_urls.get(link_id); 609 | if (g_titles.get(link_id) != undefined) { 610 | title = g_titles.get(link_id); 611 | } 612 | } 613 | else { 614 | if (whole_match.search(/\(\s*\)$/m) > -1) { 615 | // Special case for explicit empty url 616 | url = ""; 617 | } else { 618 | return whole_match; 619 | } 620 | } 621 | } 622 | url = encodeProblemUrlChars(url); 623 | url = escapeCharacters(url, "*_"); 624 | var result = ""; 633 | 634 | return result; 635 | } 636 | 637 | function _DoImages(text) { 638 | // 639 | // Turn Markdown image shortcuts into tags. 640 | // 641 | 642 | // 643 | // First, handle reference-style labeled images: ![alt text][id] 644 | // 645 | 646 | /* 647 | text = text.replace(/ 648 | ( // wrap whole match in $1 649 | !\[ 650 | (.*?) // alt text = $2 651 | \] 652 | 653 | [ ]? // one optional space 654 | (?:\n[ ]*)? // one optional newline followed by spaces 655 | 656 | \[ 657 | (.*?) // id = $3 658 | \] 659 | ) 660 | ()()()() // pad rest of backreferences 661 | /g, writeImageTag); 662 | */ 663 | text = text.replace(/(!\[(.*?)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g, writeImageTag); 664 | 665 | // 666 | // Next, handle inline images: ![alt text](url "optional title") 667 | // Don't forget: encode * and _ 668 | 669 | /* 670 | text = text.replace(/ 671 | ( // wrap whole match in $1 672 | !\[ 673 | (.*?) // alt text = $2 674 | \] 675 | \s? // One optional whitespace character 676 | \( // literal paren 677 | [ \t]* 678 | () // no id, so leave $3 empty 679 | ? // src url = $4 680 | [ \t]* 681 | ( // $5 682 | (['"]) // quote char = $6 683 | (.*?) // title = $7 684 | \6 // matching quote 685 | [ \t]* 686 | )? // title is optional 687 | \) 688 | ) 689 | /g, writeImageTag); 690 | */ 691 | text = text.replace(/(!\[(.*?)\]\s?\([ \t]*()?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g, writeImageTag); 692 | 693 | return text; 694 | } 695 | 696 | function attributeEncode(text) { 697 | // unconditionally replace angle brackets here -- what ends up in an attribute (e.g. alt or title) 698 | // never makes sense to have verbatim HTML in it (and the sanitizer would totally break it) 699 | return text.replace(/>/g, ">").replace(/" + _RunSpanGamut(m1) + "\n\n"; } 758 | ); 759 | 760 | text = text.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm, 761 | function (matchFound, m1) { return "

" + _RunSpanGamut(m1) + "

\n\n"; } 762 | ); 763 | 764 | // atx-style headers: 765 | // # Header 1 766 | // ## Header 2 767 | // ## Header 2 with closing hashes ## 768 | // ... 769 | // ###### Header 6 770 | // 771 | 772 | /* 773 | text = text.replace(/ 774 | ^(\#{1,6}) // $1 = string of #'s 775 | [ \t]* 776 | (.+?) // $2 = Header text 777 | [ \t]* 778 | \#* // optional closing #'s (not counted) 779 | \n+ 780 | /gm, function() {...}); 781 | */ 782 | 783 | text = text.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm, 784 | function (wholeMatch, m1, m2) { 785 | var h_level = m1.length; 786 | return "" + _RunSpanGamut(m2) + "\n\n"; 787 | } 788 | ); 789 | 790 | return text; 791 | } 792 | 793 | function _DoLists(text) { 794 | // 795 | // Form HTML ordered (numbered) and unordered (bulleted) lists. 796 | // 797 | 798 | // attacklab: add sentinel to hack around khtml/safari bug: 799 | // http://bugs.webkit.org/show_bug.cgi?id=11231 800 | text += "~0"; 801 | 802 | // Re-usable pattern to match any entirel ul or ol list: 803 | 804 | /* 805 | var whole_list = / 806 | ( // $1 = whole list 807 | ( // $2 808 | [ ]{0,3} // attacklab: g_tab_width - 1 809 | ([*+-]|\d+[.]) // $3 = first list item marker 810 | [ \t]+ 811 | ) 812 | [^\r]+? 813 | ( // $4 814 | ~0 // sentinel for workaround; should be $ 815 | | 816 | \n{2,} 817 | (?=\S) 818 | (?! // Negative lookahead for another list item marker 819 | [ \t]* 820 | (?:[*+-]|\d+[.])[ \t]+ 821 | ) 822 | ) 823 | ) 824 | /g 825 | */ 826 | var whole_list = /^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm; 827 | 828 | if (g_list_level) { 829 | text = text.replace(whole_list, function (wholeMatch, m1, m2) { 830 | var list = m1; 831 | var list_type = (m2.search(/[*+-]/g) > -1) ? "ul" : "ol"; 832 | 833 | var result = _ProcessListItems(list, list_type); 834 | 835 | // Trim any trailing whitespace, to put the closing `` 836 | // up on the preceding line, to get it past the current stupid 837 | // HTML block parser. This is a hack to work around the terrible 838 | // hack that is the HTML block parser. 839 | result = result.replace(/\s+$/, ""); 840 | result = "<" + list_type + ">" + result + "\n"; 841 | return result; 842 | }); 843 | } else { 844 | whole_list = /(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/g; 845 | text = text.replace(whole_list, function (wholeMatch, m1, m2, m3) { 846 | var runup = m1; 847 | var list = m2; 848 | 849 | var list_type = (m3.search(/[*+-]/g) > -1) ? "ul" : "ol"; 850 | var result = _ProcessListItems(list, list_type); 851 | result = runup + "<" + list_type + ">\n" + result + "\n"; 852 | return result; 853 | }); 854 | } 855 | 856 | // attacklab: strip sentinel 857 | text = text.replace(/~0/, ""); 858 | 859 | return text; 860 | } 861 | 862 | var _listItemMarkers = { ol: "\\d+[.]", ul: "[*+-]" }; 863 | 864 | function _ProcessListItems(list_str, list_type) { 865 | // 866 | // Process the contents of a single ordered or unordered list, splitting it 867 | // into individual list items. 868 | // 869 | // list_type is either "ul" or "ol". 870 | 871 | // The $g_list_level global keeps track of when we're inside a list. 872 | // Each time we enter a list, we increment it; when we leave a list, 873 | // we decrement. If it's zero, we're not in a list anymore. 874 | // 875 | // We do this because when we're not inside a list, we want to treat 876 | // something like this: 877 | // 878 | // I recommend upgrading to version 879 | // 8. Oops, now this line is treated 880 | // as a sub-list. 881 | // 882 | // As a single paragraph, despite the fact that the second line starts 883 | // with a digit-period-space sequence. 884 | // 885 | // Whereas when we're inside a list (or sub-list), that line will be 886 | // treated as the start of a sub-list. What a kludge, huh? This is 887 | // an aspect of Markdown's syntax that's hard to parse perfectly 888 | // without resorting to mind-reading. Perhaps the solution is to 889 | // change the syntax rules such that sub-lists must start with a 890 | // starting cardinal number; e.g. "1." or "a.". 891 | 892 | g_list_level++; 893 | 894 | // trim trailing blank lines: 895 | list_str = list_str.replace(/\n{2,}$/, "\n"); 896 | 897 | // attacklab: add sentinel to emulate \z 898 | list_str += "~0"; 899 | 900 | // In the original attacklab showdown, list_type was not given to this function, and anything 901 | // that matched /[*+-]|\d+[.]/ would just create the next
  • , causing this mismatch: 902 | // 903 | // Markdown rendered by WMD rendered by MarkdownSharp 904 | // ------------------------------------------------------------------ 905 | // 1. first 1. first 1. first 906 | // 2. second 2. second 2. second 907 | // - third 3. third * third 908 | // 909 | // We changed this to behave identical to MarkdownSharp. This is the constructed RegEx, 910 | // with {MARKER} being one of \d+[.] or [*+-], depending on list_type: 911 | 912 | /* 913 | list_str = list_str.replace(/ 914 | (^[ \t]*) // leading whitespace = $1 915 | ({MARKER}) [ \t]+ // list marker = $2 916 | ([^\r]+? // list item text = $3 917 | (\n+) 918 | ) 919 | (?= 920 | (~0 | \2 ({MARKER}) [ \t]+) 921 | ) 922 | /gm, function(){...}); 923 | */ 924 | 925 | var marker = _listItemMarkers[list_type]; 926 | var re = new RegExp("(^[ \\t]*)(" + marker + ")[ \\t]+([^\\r]+?(\\n+))(?=(~0|\\1(" + marker + ")[ \\t]+))", "gm"); 927 | var last_item_had_a_double_newline = false; 928 | list_str = list_str.replace(re, 929 | function (wholeMatch, m1, m2, m3) { 930 | var item = m3; 931 | var leading_space = m1; 932 | var ends_with_double_newline = /\n\n$/.test(item); 933 | var contains_double_newline = ends_with_double_newline || item.search(/\n{2,}/) > -1; 934 | 935 | if (contains_double_newline || last_item_had_a_double_newline) { 936 | item = _RunBlockGamut(_Outdent(item), /* doNotUnhash = */true); 937 | } 938 | else { 939 | // Recursion for sub-lists: 940 | item = _DoLists(_Outdent(item)); 941 | item = item.replace(/\n$/, ""); // chomp(item) 942 | item = _RunSpanGamut(item); 943 | } 944 | last_item_had_a_double_newline = ends_with_double_newline; 945 | return "
  • " + item + "
  • \n"; 946 | } 947 | ); 948 | 949 | // attacklab: strip sentinel 950 | list_str = list_str.replace(/~0/g, ""); 951 | 952 | g_list_level--; 953 | return list_str; 954 | } 955 | 956 | function _DoCodeBlocks(text) { 957 | // 958 | // Process Markdown `
    ` blocks.
     959 |             //  
     960 | 
     961 |             /*
     962 |             text = text.replace(/
     963 |                 (?:\n\n|^)
     964 |                 (                               // $1 = the code block -- one or more lines, starting with a space/tab
     965 |                     (?:
     966 |                         (?:[ ]{4}|\t)           // Lines must start with a tab or a tab-width of spaces - attacklab: g_tab_width
     967 |                         .*\n+
     968 |                     )+
     969 |                 )
     970 |                 (\n*[ ]{0,3}[^ \t\n]|(?=~0))    // attacklab: g_tab_width
     971 |             /g ,function(){...});
     972 |             */
     973 | 
     974 |             // attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
     975 |             text += "~0";
     976 | 
     977 |             text = text.replace(/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g,
     978 |                 function (wholeMatch, m1, m2) {
     979 |                     var codeblock = m1;
     980 |                     var nextChar = m2;
     981 | 
     982 |                     codeblock = _EncodeCode(_Outdent(codeblock));
     983 |                     codeblock = _Detab(codeblock);
     984 |                     codeblock = codeblock.replace(/^\n+/g, ""); // trim leading newlines
     985 |                     codeblock = codeblock.replace(/\n+$/g, ""); // trim trailing whitespace
     986 | 
     987 |                     codeblock = "
    " + codeblock + "\n
    "; 988 | 989 | return "\n\n" + codeblock + "\n\n" + nextChar; 990 | } 991 | ); 992 | 993 | // attacklab: strip sentinel 994 | text = text.replace(/~0/, ""); 995 | 996 | return text; 997 | } 998 | 999 | function hashBlock(text) { 1000 | text = text.replace(/(^\n+|\n+$)/g, ""); 1001 | return "\n\n~K" + (g_html_blocks.push(text) - 1) + "K\n\n"; 1002 | } 1003 | 1004 | function _DoCodeSpans(text) { 1005 | // 1006 | // * Backtick quotes are used for spans. 1007 | // 1008 | // * You can use multiple backticks as the delimiters if you want to 1009 | // include literal backticks in the code span. So, this input: 1010 | // 1011 | // Just type ``foo `bar` baz`` at the prompt. 1012 | // 1013 | // Will translate to: 1014 | // 1015 | //

    Just type foo `bar` baz at the prompt.

    1016 | // 1017 | // There's no arbitrary limit to the number of backticks you 1018 | // can use as delimters. If you need three consecutive backticks 1019 | // in your code, use four for delimiters, etc. 1020 | // 1021 | // * You can use spaces to get literal backticks at the edges: 1022 | // 1023 | // ... type `` `bar` `` ... 1024 | // 1025 | // Turns to: 1026 | // 1027 | // ... type `bar` ... 1028 | // 1029 | 1030 | /* 1031 | text = text.replace(/ 1032 | (^|[^\\]) // Character before opening ` can't be a backslash 1033 | (`+) // $2 = Opening run of ` 1034 | ( // $3 = The code block 1035 | [^\r]*? 1036 | [^`] // attacklab: work around lack of lookbehind 1037 | ) 1038 | \2 // Matching closer 1039 | (?!`) 1040 | /gm, function(){...}); 1041 | */ 1042 | 1043 | text = text.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm, 1044 | function (wholeMatch, m1, m2, m3, m4) { 1045 | var c = m3; 1046 | c = c.replace(/^([ \t]*)/g, ""); // leading whitespace 1047 | c = c.replace(/[ \t]*$/g, ""); // trailing whitespace 1048 | c = _EncodeCode(c); 1049 | c = c.replace(/:\/\//g, "~P"); // to prevent auto-linking. Not necessary in code *blocks*, but in code spans. Will be converted back after the auto-linker runs. 1050 | return m1 + "" + c + ""; 1051 | } 1052 | ); 1053 | 1054 | return text; 1055 | } 1056 | 1057 | function _EncodeCode(text) { 1058 | // 1059 | // Encode/escape certain characters inside Markdown code runs. 1060 | // The point is that in code, these characters are literals, 1061 | // and lose their special Markdown meanings. 1062 | // 1063 | // Encode all ampersands; HTML entities are not 1064 | // entities within a Markdown code span. 1065 | text = text.replace(/&/g, "&"); 1066 | 1067 | // Do the angle bracket song and dance: 1068 | text = text.replace(//g, ">"); 1070 | 1071 | // Now, escape characters that are magic in Markdown: 1072 | text = escapeCharacters(text, "\*_{}[]\\", false); 1073 | 1074 | // jj the line above breaks this: 1075 | //--- 1076 | 1077 | //* Item 1078 | 1079 | // 1. Subitem 1080 | 1081 | // special char: * 1082 | //--- 1083 | 1084 | return text; 1085 | } 1086 | 1087 | function _DoItalicsAndBold(text) { 1088 | 1089 | // must go first: 1090 | text = text.replace(/([\W_]|^)(\*\*|__)(?=\S)([^\r]*?\S[\*_]*)\2([\W_]|$)/g, 1091 | "$1$3$4"); 1092 | 1093 | text = text.replace(/([\W_]|^)(\*|_)(?=\S)([^\r\*_]*?\S)\2([\W_]|$)/g, 1094 | "$1$3$4"); 1095 | 1096 | return text; 1097 | } 1098 | 1099 | function _DoBlockQuotes(text) { 1100 | 1101 | /* 1102 | text = text.replace(/ 1103 | ( // Wrap whole match in $1 1104 | ( 1105 | ^[ \t]*>[ \t]? // '>' at the start of a line 1106 | .+\n // rest of the first line 1107 | (.+\n)* // subsequent consecutive lines 1108 | \n* // blanks 1109 | )+ 1110 | ) 1111 | /gm, function(){...}); 1112 | */ 1113 | 1114 | text = text.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm, 1115 | function (wholeMatch, m1) { 1116 | var bq = m1; 1117 | 1118 | // attacklab: hack around Konqueror 3.5.4 bug: 1119 | // "----------bug".replace(/^-/g,"") == "bug" 1120 | 1121 | bq = bq.replace(/^[ \t]*>[ \t]?/gm, "~0"); // trim one level of quoting 1122 | 1123 | // attacklab: clean up hack 1124 | bq = bq.replace(/~0/g, ""); 1125 | 1126 | bq = bq.replace(/^[ \t]+$/gm, ""); // trim whitespace-only lines 1127 | bq = _RunBlockGamut(bq); // recurse 1128 | 1129 | bq = bq.replace(/(^|\n)/g, "$1 "); 1130 | // These leading spaces screw with
     content, so we need to fix that:
    1131 |                     bq = bq.replace(
    1132 |                             /(\s*
    [^\r]+?<\/pre>)/gm,
    1133 |                         function (wholeMatch, m1) {
    1134 |                             var pre = m1;
    1135 |                             // attacklab: hack around Konqueror 3.5.4 bug:
    1136 |                             pre = pre.replace(/^  /mg, "~0");
    1137 |                             pre = pre.replace(/~0/g, "");
    1138 |                             return pre;
    1139 |                         });
    1140 | 
    1141 |                     return hashBlock("
    \n" + bq + "\n
    "); 1142 | } 1143 | ); 1144 | return text; 1145 | } 1146 | 1147 | function _FormParagraphs(text, doNotUnhash) { 1148 | // 1149 | // Params: 1150 | // $text - string to process with html

    tags 1151 | // 1152 | 1153 | // Strip leading and trailing lines: 1154 | text = text.replace(/^\n+/g, ""); 1155 | text = text.replace(/\n+$/g, ""); 1156 | 1157 | var grafs = text.split(/\n{2,}/g); 1158 | var grafsOut = []; 1159 | 1160 | var markerRe = /~K(\d+)K/; 1161 | 1162 | // 1163 | // Wrap

    tags. 1164 | // 1165 | var end = grafs.length; 1166 | for (var i = 0; i < end; i++) { 1167 | var str = grafs[i]; 1168 | 1169 | // if this is an HTML marker, copy it 1170 | if (markerRe.test(str)) { 1171 | grafsOut.push(str); 1172 | } 1173 | else if (/\S/.test(str)) { 1174 | str = _RunSpanGamut(str); 1175 | str = str.replace(/^([ \t]*)/g, "

    "); 1176 | str += "

    " 1177 | grafsOut.push(str); 1178 | } 1179 | 1180 | } 1181 | // 1182 | // Unhashify HTML blocks 1183 | // 1184 | if (!doNotUnhash) { 1185 | end = grafsOut.length; 1186 | for (var i = 0; i < end; i++) { 1187 | var foundAny = true; 1188 | while (foundAny) { // we may need several runs, since the data may be nested 1189 | foundAny = false; 1190 | grafsOut[i] = grafsOut[i].replace(/~K(\d+)K/g, function (wholeMatch, id) { 1191 | foundAny = true; 1192 | return g_html_blocks[id]; 1193 | }); 1194 | } 1195 | } 1196 | } 1197 | return grafsOut.join("\n\n"); 1198 | } 1199 | 1200 | function _EncodeAmpsAndAngles(text) { 1201 | // Smart processing for ampersands and angle brackets that need to be encoded. 1202 | 1203 | // Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin: 1204 | // http://bumppo.net/projects/amputator/ 1205 | text = text.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g, "&"); 1206 | 1207 | // Encode naked <'s 1208 | text = text.replace(/<(?![a-z\/?!]|~D)/gi, "<"); 1209 | 1210 | return text; 1211 | } 1212 | 1213 | function _EncodeBackslashEscapes(text) { 1214 | // 1215 | // Parameter: String. 1216 | // Returns: The string, with after processing the following backslash 1217 | // escape sequences. 1218 | // 1219 | 1220 | // attacklab: The polite way to do this is with the new 1221 | // escapeCharacters() function: 1222 | // 1223 | // text = escapeCharacters(text,"\\",true); 1224 | // text = escapeCharacters(text,"`*_{}[]()>#+-.!",true); 1225 | // 1226 | // ...but we're sidestepping its use of the (slow) RegExp constructor 1227 | // as an optimization for Firefox. This function gets called a LOT. 1228 | 1229 | text = text.replace(/\\(\\)/g, escapeCharacters_callback); 1230 | text = text.replace(/\\([`*_{}\[\]()>#+-.!])/g, escapeCharacters_callback); 1231 | return text; 1232 | } 1233 | 1234 | function handleTrailingParens(wholeMatch, lookbehind, protocol, link) { 1235 | if (lookbehind) 1236 | return wholeMatch; 1237 | if (link.charAt(link.length - 1) !== ")") 1238 | return "<" + protocol + link + ">"; 1239 | var parens = link.match(/[()]/g); 1240 | var level = 0; 1241 | for (var i = 0; i < parens.length; i++) { 1242 | if (parens[i] === "(") { 1243 | if (level <= 0) 1244 | level = 1; 1245 | else 1246 | level++; 1247 | } 1248 | else { 1249 | level--; 1250 | } 1251 | } 1252 | var tail = ""; 1253 | if (level < 0) { 1254 | var re = new RegExp("\\){1," + (-level) + "}$"); 1255 | link = link.replace(re, function (trailingParens) { 1256 | tail = trailingParens; 1257 | return ""; 1258 | }); 1259 | } 1260 | 1261 | return "<" + protocol + link + ">" + tail; 1262 | } 1263 | 1264 | function _DoAutoLinks(text) { 1265 | 1266 | // note that at this point, all other URL in the text are already hyperlinked as
    1267 | // *except* for the case 1268 | 1269 | // automatically add < and > around unadorned raw hyperlinks 1270 | // must be preceded by a non-word character (and not by =" or <) and followed by non-word/EOF character 1271 | // simulating the lookbehind in a consuming way is okay here, since a URL can neither and with a " nor 1272 | // with a <, so there is no risk of overlapping matches. 1273 | text = text.replace(/(="|<)?\b(https?|ftp)(:\/\/[-A-Z0-9+&@#\/%?=~_|\[\]\(\)!:,\.;]*[-A-Z0-9+&@#\/%=~_|\[\])])(?=$|\W)/gi, handleTrailingParens); 1274 | 1275 | // autolink anything like 1276 | 1277 | var replacer = function (wholematch, m1) { return "" + pluginHooks.plainLinkText(m1) + ""; } 1278 | text = text.replace(/<((https?|ftp):[^'">\s]+)>/gi, replacer); 1279 | 1280 | // Email addresses: 1281 | /* 1282 | text = text.replace(/ 1283 | < 1284 | (?:mailto:)? 1285 | ( 1286 | [-.\w]+ 1287 | \@ 1288 | [-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+ 1289 | ) 1290 | > 1291 | /gi, _DoAutoLinks_callback()); 1292 | */ 1293 | 1294 | /* disabling email autolinking, since we don't do that on the server, either 1295 | text = text.replace(/<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi, 1296 | function(wholeMatch,m1) { 1297 | return _EncodeEmailAddress( _UnescapeSpecialChars(m1) ); 1298 | } 1299 | ); 1300 | */ 1301 | return text; 1302 | } 1303 | 1304 | function _UnescapeSpecialChars(text) { 1305 | // 1306 | // Swap back in all the special characters we've hidden. 1307 | // 1308 | text = text.replace(/~E(\d+)E/g, 1309 | function (wholeMatch, m1) { 1310 | var charCodeToReplace = parseInt(m1); 1311 | return String.fromCharCode(charCodeToReplace); 1312 | } 1313 | ); 1314 | return text; 1315 | } 1316 | 1317 | function _Outdent(text) { 1318 | // 1319 | // Remove one level of line-leading tabs or spaces 1320 | // 1321 | 1322 | // attacklab: hack around Konqueror 3.5.4 bug: 1323 | // "----------bug".replace(/^-/g,"") == "bug" 1324 | 1325 | text = text.replace(/^(\t|[ ]{1,4})/gm, "~0"); // attacklab: g_tab_width 1326 | 1327 | // attacklab: clean up hack 1328 | text = text.replace(/~0/g, "") 1329 | 1330 | return text; 1331 | } 1332 | 1333 | function _Detab(text) { 1334 | if (!/\t/.test(text)) 1335 | return text; 1336 | 1337 | var spaces = [" ", " ", " ", " "], 1338 | skew = 0, 1339 | v; 1340 | 1341 | return text.replace(/[\n\t]/g, function (match, offset) { 1342 | if (match === "\n") { 1343 | skew = offset + 1; 1344 | return match; 1345 | } 1346 | v = (offset - skew) % 4; 1347 | skew = offset + 1; 1348 | return spaces[v]; 1349 | }); 1350 | } 1351 | 1352 | // 1353 | // attacklab: Utility functions 1354 | // 1355 | 1356 | var _problemUrlChars = /(?:["'*()[\]:]|~D)/g; 1357 | 1358 | // hex-encodes some unusual "problem" chars in URLs to avoid URL detection problems 1359 | function encodeProblemUrlChars(url) { 1360 | if (!url) 1361 | return ""; 1362 | 1363 | var len = url.length; 1364 | 1365 | return url.replace(_problemUrlChars, function (match, offset) { 1366 | if (match == "~D") // escape for dollar 1367 | return "%24"; 1368 | if (match == ":") { 1369 | if (offset == len - 1 || /[0-9\/]/.test(url.charAt(offset + 1))) 1370 | return ":" 1371 | } 1372 | return "%" + match.charCodeAt(0).toString(16); 1373 | }); 1374 | } 1375 | 1376 | 1377 | function escapeCharacters(text, charsToEscape, afterBackslash) { 1378 | // First we have to escape the escape characters so that 1379 | // we can build a character class out of them 1380 | var regexString = "([" + charsToEscape.replace(/([\[\]\\])/g, "\\$1") + "])"; 1381 | 1382 | if (afterBackslash) { 1383 | regexString = "\\\\" + regexString; 1384 | } 1385 | 1386 | var regex = new RegExp(regexString, "g"); 1387 | text = text.replace(regex, escapeCharacters_callback); 1388 | 1389 | return text; 1390 | } 1391 | 1392 | 1393 | function escapeCharacters_callback(wholeMatch, m1) { 1394 | var charCodeToEscape = m1.charCodeAt(0); 1395 | return "~E" + charCodeToEscape + "E"; 1396 | } 1397 | 1398 | }; // end of the Markdown.Converter constructor 1399 | 1400 | })(); 1401 | -------------------------------------------------------------------------------- /lib/markdown/Markdown.Extra.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | // A quick way to make sure we're only keeping span-level tags when we need to. 3 | // This isn't supposed to be foolproof. It's just a quick way to make sure we 4 | // keep all span-level tags returned by a pagedown converter. It should allow 5 | // all span-level tags through, with or without attributes. 6 | var inlineTags = new RegExp(['^(<\\/?(a|abbr|acronym|applet|area|b|basefont|', 7 | 'bdo|big|button|cite|code|del|dfn|em|figcaption|', 8 | 'font|i|iframe|img|input|ins|kbd|label|map|', 9 | 'mark|meter|object|param|progress|q|ruby|rp|rt|s|', 10 | 'samp|script|select|small|span|strike|strong|', 11 | 'sub|sup|textarea|time|tt|u|var|wbr)[^>]*>|', 12 | '<(br)\\s?\\/?>)$'].join(''), 'i'); 13 | 14 | /****************************************************************** 15 | * Utility Functions * 16 | *****************************************************************/ 17 | 18 | // patch for ie7 19 | if (!Array.indexOf) { 20 | Array.prototype.indexOf = function(obj) { 21 | for (var i = 0; i < this.length; i++) { 22 | if (this[i] == obj) { 23 | return i; 24 | } 25 | } 26 | return -1; 27 | }; 28 | } 29 | 30 | function trim(str) { 31 | return str.replace(/^\s+|\s+$/g, ''); 32 | } 33 | 34 | function rtrim(str) { 35 | return str.replace(/\s+$/g, ''); 36 | } 37 | 38 | // Remove one level of indentation from text. Indent is 4 spaces. 39 | function outdent(text) { 40 | return text.replace(new RegExp('^(\\t|[ ]{1,4})', 'gm'), ''); 41 | } 42 | 43 | function contains(str, substr) { 44 | return str.indexOf(substr) != -1; 45 | } 46 | 47 | // Sanitize html, removing tags that aren't in the whitelist 48 | function sanitizeHtml(html, whitelist) { 49 | return html.replace(/<[^>]*>?/gi, function(tag) { 50 | return tag.match(whitelist) ? tag : ''; 51 | }); 52 | } 53 | 54 | // Merge two arrays, keeping only unique elements. 55 | function union(x, y) { 56 | var obj = {}; 57 | for (var i = 0; i < x.length; i++) 58 | obj[x[i]] = x[i]; 59 | for (i = 0; i < y.length; i++) 60 | obj[y[i]] = y[i]; 61 | var res = []; 62 | for (var k in obj) { 63 | if (obj.hasOwnProperty(k)) 64 | res.push(obj[k]); 65 | } 66 | return res; 67 | } 68 | 69 | // JS regexes don't support \A or \Z, so we add sentinels, as Pagedown 70 | // does. In this case, we add the ascii codes for start of text (STX) and 71 | // end of text (ETX), an idea borrowed from: 72 | // https://github.com/tanakahisateru/js-markdown-extra 73 | function addAnchors(text) { 74 | if(text.charAt(0) != '\x02') 75 | text = '\x02' + text; 76 | if(text.charAt(text.length - 1) != '\x03') 77 | text = text + '\x03'; 78 | return text; 79 | } 80 | 81 | // Remove STX and ETX sentinels. 82 | function removeAnchors(text) { 83 | if(text.charAt(0) == '\x02') 84 | text = text.substr(1); 85 | if(text.charAt(text.length - 1) == '\x03') 86 | text = text.substr(0, text.length - 1); 87 | return text; 88 | } 89 | 90 | // Convert markdown within an element, retaining only span-level tags 91 | function convertSpans(text, extra) { 92 | return sanitizeHtml(convertAll(text, extra), inlineTags); 93 | } 94 | 95 | // Convert internal markdown using the stock pagedown converter 96 | function convertAll(text, extra) { 97 | var result = extra.blockGamutHookCallback(text); 98 | // We need to perform these operations since we skip the steps in the converter 99 | result = unescapeSpecialChars(result); 100 | result = result.replace(/~D/g, "$$").replace(/~T/g, "~"); 101 | result = extra.previousPostConversion(result); 102 | return result; 103 | } 104 | 105 | // Convert escaped special characters 106 | function processEscapesStep1(text) { 107 | // Markdown extra adds two escapable characters, `:` and `|` 108 | return text.replace(/\\\|/g, '~I').replace(/\\:/g, '~i'); 109 | } 110 | function processEscapesStep2(text) { 111 | return text.replace(/~I/g, '|').replace(/~i/g, ':'); 112 | } 113 | 114 | // Duplicated from PageDown converter 115 | function unescapeSpecialChars(text) { 116 | // Swap back in all the special characters we've hidden. 117 | text = text.replace(/~E(\d+)E/g, function(wholeMatch, m1) { 118 | var charCodeToReplace = parseInt(m1); 119 | return String.fromCharCode(charCodeToReplace); 120 | }); 121 | return text; 122 | } 123 | 124 | function slugify(text) { 125 | return text.toLowerCase() 126 | .replace(/\s+/g, '-') // Replace spaces with - 127 | .replace(/[^\w\-]+/g, '') // Remove all non-word chars 128 | .replace(/\-\-+/g, '-') // Replace multiple - with single - 129 | .replace(/^-+/, '') // Trim - from start of text 130 | .replace(/-+$/, ''); // Trim - from end of text 131 | } 132 | 133 | /***************************************************************************** 134 | * Markdown.Extra * 135 | ****************************************************************************/ 136 | 137 | Markdown.Extra = function() { 138 | // For converting internal markdown (in tables for instance). 139 | // This is necessary since these methods are meant to be called as 140 | // preConversion hooks, and the Markdown converter passed to init() 141 | // won't convert any markdown contained in the html tags we return. 142 | this.converter = null; 143 | 144 | // Stores html blocks we generate in hooks so that 145 | // they're not destroyed if the user is using a sanitizing converter 146 | this.hashBlocks = []; 147 | 148 | // Stores footnotes 149 | this.footnotes = {}; 150 | this.usedFootnotes = []; 151 | 152 | // Special attribute blocks for fenced code blocks and headers enabled. 153 | this.attributeBlocks = false; 154 | 155 | // Fenced code block options 156 | this.googleCodePrettify = false; 157 | this.highlightJs = false; 158 | 159 | // Table options 160 | this.tableClass = ''; 161 | 162 | this.tabWidth = 4; 163 | }; 164 | 165 | Markdown.Extra.init = function(converter, options) { 166 | // Each call to init creates a new instance of Markdown.Extra so it's 167 | // safe to have multiple converters, with different options, on a single page 168 | var extra = new Markdown.Extra(); 169 | var postNormalizationTransformations = []; 170 | var preBlockGamutTransformations = []; 171 | var postSpanGamutTransformations = []; 172 | var postConversionTransformations = ["unHashExtraBlocks"]; 173 | 174 | options = options || {}; 175 | options.extensions = options.extensions || ["all"]; 176 | if (contains(options.extensions, "all")) { 177 | options.extensions = ["tables", "fenced_code_gfm", "def_list", "attr_list", "footnotes", "smartypants", "strikethrough", "newlines"]; 178 | } 179 | preBlockGamutTransformations.push("wrapHeaders"); 180 | if (contains(options.extensions, "attr_list")) { 181 | postNormalizationTransformations.push("hashFcbAttributeBlocks"); 182 | preBlockGamutTransformations.push("hashHeaderAttributeBlocks"); 183 | postConversionTransformations.push("applyAttributeBlocks"); 184 | extra.attributeBlocks = true; 185 | } 186 | if (contains(options.extensions, "fenced_code_gfm")) { 187 | // This step will convert fcb inside list items and blockquotes 188 | preBlockGamutTransformations.push("fencedCodeBlocks"); 189 | // This extra step is to prevent html blocks hashing and link definition/footnotes stripping inside fcb 190 | postNormalizationTransformations.push("fencedCodeBlocks"); 191 | } 192 | if (contains(options.extensions, "tables")) { 193 | preBlockGamutTransformations.push("tables"); 194 | } 195 | if (contains(options.extensions, "def_list")) { 196 | preBlockGamutTransformations.push("definitionLists"); 197 | } 198 | if (contains(options.extensions, "footnotes")) { 199 | postNormalizationTransformations.push("stripFootnoteDefinitions"); 200 | preBlockGamutTransformations.push("doFootnotes"); 201 | postConversionTransformations.push("printFootnotes"); 202 | } 203 | if (contains(options.extensions, "smartypants")) { 204 | postConversionTransformations.push("runSmartyPants"); 205 | } 206 | if (contains(options.extensions, "strikethrough")) { 207 | postSpanGamutTransformations.push("strikethrough"); 208 | } 209 | if (contains(options.extensions, "newlines")) { 210 | postSpanGamutTransformations.push("newlines"); 211 | } 212 | 213 | converter.hooks.chain("postNormalization", function(text) { 214 | return extra.doTransform(postNormalizationTransformations, text) + '\n'; 215 | }); 216 | 217 | converter.hooks.chain("preBlockGamut", function(text, blockGamutHookCallback) { 218 | // Keep a reference to the block gamut callback to run recursively 219 | extra.blockGamutHookCallback = blockGamutHookCallback; 220 | text = processEscapesStep1(text); 221 | text = extra.doTransform(preBlockGamutTransformations, text) + '\n'; 222 | text = processEscapesStep2(text); 223 | return text; 224 | }); 225 | 226 | converter.hooks.chain("postSpanGamut", function(text) { 227 | return extra.doTransform(postSpanGamutTransformations, text); 228 | }); 229 | 230 | // Keep a reference to the hook chain running before doPostConversion to apply on hashed extra blocks 231 | extra.previousPostConversion = converter.hooks.postConversion; 232 | converter.hooks.chain("postConversion", function(text) { 233 | text = extra.doTransform(postConversionTransformations, text); 234 | // Clear state vars that may use unnecessary memory 235 | extra.hashBlocks = []; 236 | extra.footnotes = {}; 237 | extra.usedFootnotes = []; 238 | return text; 239 | }); 240 | 241 | if ("highlighter" in options) { 242 | extra.googleCodePrettify = options.highlighter === 'prettify'; 243 | extra.highlightJs = options.highlighter === 'highlight'; 244 | } 245 | 246 | if ("table_class" in options) { 247 | extra.tableClass = options.table_class; 248 | } 249 | 250 | extra.converter = converter; 251 | 252 | // Caller usually won't need this, but it's handy for testing. 253 | return extra; 254 | }; 255 | 256 | // Do transformations 257 | Markdown.Extra.prototype.doTransform = function(transformations, text) { 258 | for(var i = 0; i < transformations.length; i++) 259 | text = this[transformations[i]](text); 260 | return text; 261 | }; 262 | 263 | // Return a placeholder containing a key, which is the block's index in the 264 | // hashBlocks array. We wrap our output in a

    tag here so Pagedown won't. 265 | Markdown.Extra.prototype.hashExtraBlock = function(block) { 266 | return '\n

    ~X' + (this.hashBlocks.push(block) - 1) + 'X

    \n'; 267 | }; 268 | Markdown.Extra.prototype.hashExtraInline = function(block) { 269 | return '~X' + (this.hashBlocks.push(block) - 1) + 'X'; 270 | }; 271 | 272 | // Replace placeholder blocks in `text` with their corresponding 273 | // html blocks in the hashBlocks array. 274 | Markdown.Extra.prototype.unHashExtraBlocks = function(text) { 275 | var self = this; 276 | function recursiveUnHash() { 277 | var hasHash = false; 278 | text = text.replace(/(?:

    )?~X(\d+)X(?:<\/p>)?/g, function(wholeMatch, m1) { 279 | hasHash = true; 280 | var key = parseInt(m1, 10); 281 | return self.hashBlocks[key]; 282 | }); 283 | if(hasHash === true) { 284 | recursiveUnHash(); 285 | } 286 | } 287 | recursiveUnHash(); 288 | return text; 289 | }; 290 | 291 | // Wrap headers to make sure they won't be in def lists 292 | Markdown.Extra.prototype.wrapHeaders = function(text) { 293 | function wrap(text) { 294 | return '\n' + text + '\n'; 295 | } 296 | text = text.replace(/^.+[ \t]*\n=+[ \t]*\n+/gm, wrap); 297 | text = text.replace(/^.+[ \t]*\n-+[ \t]*\n+/gm, wrap); 298 | text = text.replace(/^\#{1,6}[ \t]*.+?[ \t]*\#*\n+/gm, wrap); 299 | return text; 300 | }; 301 | 302 | 303 | /****************************************************************** 304 | * Attribute Blocks * 305 | *****************************************************************/ 306 | 307 | // TODO: use sentinels. Should we just add/remove them in doConversion? 308 | // TODO: better matches for id / class attributes 309 | var attrBlock = "\\{[ \\t]*((?:[#.][-_:a-zA-Z0-9]+[ \\t]*)+)\\}"; 310 | var hdrAttributesA = new RegExp("^(#{1,6}.*#{0,6})[ \\t]+" + attrBlock + "[ \\t]*(?:\\n|0x03)", "gm"); 311 | var hdrAttributesB = new RegExp("^(.*)[ \\t]+" + attrBlock + "[ \\t]*\\n" + 312 | "(?=[\\-|=]+\\s*(?:\\n|0x03))", "gm"); // underline lookahead 313 | var fcbAttributes = new RegExp("^(```[ \\t]*[^{\\s]*)[ \\t]+" + attrBlock + "[ \\t]*\\n" + 314 | "(?=([\\s\\S]*?)\\n```[ \\t]*(\\n|0x03))", "gm"); 315 | 316 | // Extract headers attribute blocks, move them above the element they will be 317 | // applied to, and hash them for later. 318 | Markdown.Extra.prototype.hashHeaderAttributeBlocks = function(text) { 319 | 320 | var self = this; 321 | function attributeCallback(wholeMatch, pre, attr) { 322 | return '

    ~XX' + (self.hashBlocks.push(attr) - 1) + 'XX

    \n' + pre + "\n"; 323 | } 324 | 325 | text = text.replace(hdrAttributesA, attributeCallback); // ## headers 326 | text = text.replace(hdrAttributesB, attributeCallback); // underline headers 327 | return text; 328 | }; 329 | 330 | // Extract FCB attribute blocks, move them above the element they will be 331 | // applied to, and hash them for later. 332 | Markdown.Extra.prototype.hashFcbAttributeBlocks = function(text) { 333 | // TODO: use sentinels. Should we just add/remove them in doConversion? 334 | // TODO: better matches for id / class attributes 335 | 336 | var self = this; 337 | function attributeCallback(wholeMatch, pre, attr) { 338 | return '

    ~XX' + (self.hashBlocks.push(attr) - 1) + 'XX

    \n' + pre + "\n"; 339 | } 340 | 341 | return text.replace(fcbAttributes, attributeCallback); 342 | }; 343 | 344 | Markdown.Extra.prototype.applyAttributeBlocks = function(text) { 345 | var self = this; 346 | var blockRe = new RegExp('

    ~XX(\\d+)XX

    [\\s]*' + 347 | '(?:<(h[1-6]|pre)(?: +class="(\\S+)")?(>[\\s\\S]*?))', "gm"); 348 | text = text.replace(blockRe, function(wholeMatch, k, tag, cls, rest) { 349 | if (!tag) // no following header or fenced code block. 350 | return ''; 351 | 352 | // get attributes list from hash 353 | var key = parseInt(k, 10); 354 | var attributes = self.hashBlocks[key]; 355 | 356 | // get id 357 | var id = attributes.match(/#[^\s#.]+/g) || []; 358 | var idStr = id[0] ? ' id="' + id[0].substr(1, id[0].length - 1) + '"' : ''; 359 | 360 | // get classes and merge with existing classes 361 | var classes = attributes.match(/\.[^\s#.]+/g) || []; 362 | for (var i = 0; i < classes.length; i++) // Remove leading dot 363 | classes[i] = classes[i].substr(1, classes[i].length - 1); 364 | 365 | var classStr = ''; 366 | if (cls) 367 | classes = union(classes, [cls]); 368 | 369 | if (classes.length > 0) 370 | classStr = ' class="' + classes.join(' ') + '"'; 371 | 372 | return "<" + tag + idStr + classStr + rest; 373 | }); 374 | 375 | return text; 376 | }; 377 | 378 | /****************************************************************** 379 | * Tables * 380 | *****************************************************************/ 381 | 382 | // Find and convert Markdown Extra tables into html. 383 | Markdown.Extra.prototype.tables = function(text) { 384 | var self = this; 385 | 386 | var leadingPipe = new RegExp( 387 | ['^' , 388 | '[ ]{0,3}' , // Allowed whitespace 389 | '[|]' , // Initial pipe 390 | '(.+)\\n' , // $1: Header Row 391 | 392 | '[ ]{0,3}' , // Allowed whitespace 393 | '[|]([ ]*[-:]+[-| :]*)\\n' , // $2: Separator 394 | 395 | '(' , // $3: Table Body 396 | '(?:[ ]*[|].*\\n?)*' , // Table rows 397 | ')', 398 | '(?:\\n|$)' // Stop at final newline 399 | ].join(''), 400 | 'gm' 401 | ); 402 | 403 | var noLeadingPipe = new RegExp( 404 | ['^' , 405 | '[ ]{0,3}' , // Allowed whitespace 406 | '(\\S.*[|].*)\\n' , // $1: Header Row 407 | 408 | '[ ]{0,3}' , // Allowed whitespace 409 | '([-:]+[ ]*[|][-| :]*)\\n' , // $2: Separator 410 | 411 | '(' , // $3: Table Body 412 | '(?:.*[|].*\\n?)*' , // Table rows 413 | ')' , 414 | '(?:\\n|$)' // Stop at final newline 415 | ].join(''), 416 | 'gm' 417 | ); 418 | 419 | text = text.replace(leadingPipe, doTable); 420 | text = text.replace(noLeadingPipe, doTable); 421 | 422 | // $1 = header, $2 = separator, $3 = body 423 | function doTable(match, header, separator, body, offset, string) { 424 | // remove any leading pipes and whitespace 425 | header = header.replace(/^ *[|]/m, ''); 426 | separator = separator.replace(/^ *[|]/m, ''); 427 | body = body.replace(/^ *[|]/gm, ''); 428 | 429 | // remove trailing pipes and whitespace 430 | header = header.replace(/[|] *$/m, ''); 431 | separator = separator.replace(/[|] *$/m, ''); 432 | body = body.replace(/[|] *$/gm, ''); 433 | 434 | // determine column alignments 435 | alignspecs = separator.split(/ *[|] */); 436 | align = []; 437 | for (var i = 0; i < alignspecs.length; i++) { 438 | var spec = alignspecs[i]; 439 | if (spec.match(/^ *-+: *$/m)) 440 | align[i] = ' style="text-align:right;"'; 441 | else if (spec.match(/^ *:-+: *$/m)) 442 | align[i] = ' style="text-align:center;"'; 443 | else if (spec.match(/^ *:-+ *$/m)) 444 | align[i] = ' style="text-align:left;"'; 445 | else align[i] = ''; 446 | } 447 | 448 | // TODO: parse spans in header and rows before splitting, so that pipes 449 | // inside of tags are not interpreted as separators 450 | var headers = header.split(/ *[|] */); 451 | var colCount = headers.length; 452 | 453 | // build html 454 | var cls = self.tableClass ? ' class="' + self.tableClass + '"' : ''; 455 | var html = ['\n', '\n', '\n'].join(''); 456 | 457 | // build column headers. 458 | for (i = 0; i < colCount; i++) { 459 | var headerHtml = convertSpans(trim(headers[i]), self); 460 | html += [" ", headerHtml, "\n"].join(''); 461 | } 462 | html += "\n\n"; 463 | 464 | // build rows 465 | var rows = body.split('\n'); 466 | for (i = 0; i < rows.length; i++) { 467 | if (rows[i].match(/^\s*$/)) // can apply to final row 468 | continue; 469 | 470 | // ensure number of rowCells matches colCount 471 | var rowCells = rows[i].split(/ *[|] */); 472 | var lenDiff = colCount - rowCells.length; 473 | for (var j = 0; j < lenDiff; j++) 474 | rowCells.push(''); 475 | 476 | html += "\n"; 477 | for (j = 0; j < colCount; j++) { 478 | var colHtml = convertSpans(trim(rowCells[j]), self); 479 | html += [" ", colHtml, "\n"].join(''); 480 | } 481 | html += "\n"; 482 | } 483 | 484 | html += "\n"; 485 | 486 | // replace html with placeholder until postConversion step 487 | return self.hashExtraBlock(html); 488 | } 489 | 490 | return text; 491 | }; 492 | 493 | 494 | /****************************************************************** 495 | * Footnotes * 496 | *****************************************************************/ 497 | 498 | // Strip footnote, store in hashes. 499 | Markdown.Extra.prototype.stripFootnoteDefinitions = function(text) { 500 | var self = this; 501 | 502 | text = text.replace( 503 | /\n[ ]{0,3}\[\^(.+?)\]\:[ \t]*\n?([\s\S]*?)\n{1,2}((?=\n[ ]{0,3}\S)|$)/g, 504 | function(wholeMatch, m1, m2) { 505 | m1 = slugify(m1); 506 | m2 += "\n"; 507 | m2 = m2.replace(/^[ ]{0,3}/g, ""); 508 | self.footnotes[m1] = m2; 509 | return "\n"; 510 | }); 511 | 512 | return text; 513 | }; 514 | 515 | 516 | // Find and convert footnotes references. 517 | Markdown.Extra.prototype.doFootnotes = function(text) { 518 | var self = this; 519 | if(self.isConvertingFootnote === true) { 520 | return text; 521 | } 522 | 523 | var footnoteCounter = 0; 524 | text = text.replace(/\[\^(.+?)\]/g, function(wholeMatch, m1) { 525 | var id = slugify(m1); 526 | var footnote = self.footnotes[id]; 527 | if (footnote === undefined) { 528 | return wholeMatch; 529 | } 530 | footnoteCounter++; 531 | self.usedFootnotes.push(id); 532 | var html = '' + footnoteCounter 534 | + ''; 535 | return self.hashExtraInline(html); 536 | }); 537 | 538 | return text; 539 | }; 540 | 541 | // Print footnotes at the end of the document 542 | Markdown.Extra.prototype.printFootnotes = function(text) { 543 | var self = this; 544 | 545 | if (self.usedFootnotes.length === 0) { 546 | return text; 547 | } 548 | 549 | text += '\n\n
    \n
    \n
      \n\n'; 550 | for(var i=0; i' 559 | + formattedfootnote 560 | + ' \n\n'; 563 | } 564 | text += '
    \n
    '; 565 | return text; 566 | }; 567 | 568 | 569 | /****************************************************************** 570 | * Fenced Code Blocks (gfm) * 571 | ******************************************************************/ 572 | 573 | // Find and convert gfm-inspired fenced code blocks into html. 574 | Markdown.Extra.prototype.fencedCodeBlocks = function(text) { 575 | function encodeCode(code) { 576 | code = code.replace(/&/g, "&"); 577 | code = code.replace(//g, ">"); 579 | // These were escaped by PageDown before postNormalization 580 | code = code.replace(/~D/g, "$$"); 581 | code = code.replace(/~T/g, "~"); 582 | return code; 583 | } 584 | 585 | var self = this; 586 | text = text.replace(/(?:^|\n)```[ \t]*(\S*)[ \t]*\n([\s\S]*?)\n```[ \t]*(?=\n)/g, function(match, m1, m2) { 587 | var language = m1, codeblock = m2; 588 | 589 | // adhere to specified options 590 | var preclass = self.googleCodePrettify ? ' class="prettyprint"' : ''; 591 | var codeclass = ''; 592 | if (language) { 593 | if (self.googleCodePrettify || self.highlightJs) { 594 | // use html5 language- class names. supported by both prettify and highlight.js 595 | codeclass = ' class="language-' + language + '"'; 596 | } else { 597 | codeclass = ' class="' + language + '"'; 598 | } 599 | } 600 | 601 | var html = ['', 602 | encodeCode(codeblock), '
    '].join(''); 603 | 604 | // replace codeblock with placeholder until postConversion step 605 | return self.hashExtraBlock(html); 606 | }); 607 | 608 | return text; 609 | }; 610 | 611 | 612 | /****************************************************************** 613 | * SmartyPants * 614 | ******************************************************************/ 615 | 616 | Markdown.Extra.prototype.educatePants = function(text) { 617 | var self = this; 618 | var result = ''; 619 | var blockOffset = 0; 620 | // Here we parse HTML in a very bad manner 621 | text.replace(/(?:)|(<)([a-zA-Z1-6]+)([^\n]*?>)([\s\S]*?)(<\/\2>)/g, function(wholeMatch, m1, m2, m3, m4, m5, offset) { 622 | var token = text.substring(blockOffset, offset); 623 | result += self.applyPants(token); 624 | self.smartyPantsLastChar = result.substring(result.length - 1); 625 | blockOffset = offset + wholeMatch.length; 626 | if(!m1) { 627 | // Skip commentary 628 | result += wholeMatch; 629 | return; 630 | } 631 | // Skip special tags 632 | if(!/code|kbd|pre|script|noscript|iframe|math|ins|del|pre/i.test(m2)) { 633 | m4 = self.educatePants(m4); 634 | } 635 | else { 636 | self.smartyPantsLastChar = m4.substring(m4.length - 1); 637 | } 638 | result += m1 + m2 + m3 + m4 + m5; 639 | }); 640 | var lastToken = text.substring(blockOffset); 641 | result += self.applyPants(lastToken); 642 | self.smartyPantsLastChar = result.substring(result.length - 1); 643 | return result; 644 | }; 645 | 646 | function revertPants(wholeMatch, m1) { 647 | var blockText = m1; 648 | blockText = blockText.replace(/&\#8220;/g, "\""); 649 | blockText = blockText.replace(/&\#8221;/g, "\""); 650 | blockText = blockText.replace(/&\#8216;/g, "'"); 651 | blockText = blockText.replace(/&\#8217;/g, "'"); 652 | blockText = blockText.replace(/&\#8212;/g, "---"); 653 | blockText = blockText.replace(/&\#8211;/g, "--"); 654 | blockText = blockText.replace(/&\#8230;/g, "..."); 655 | return blockText; 656 | } 657 | 658 | Markdown.Extra.prototype.applyPants = function(text) { 659 | // Dashes 660 | text = text.replace(/---/g, "—").replace(/--/g, "–"); 661 | // Ellipses 662 | text = text.replace(/\.\.\./g, "…").replace(/\.\s\.\s\./g, "…"); 663 | // Backticks 664 | text = text.replace(/``/g, "“").replace (/''/g, "”"); 665 | 666 | if(/^'$/.test(text)) { 667 | // Special case: single-character ' token 668 | if(/\S/.test(this.smartyPantsLastChar)) { 669 | return "’"; 670 | } 671 | return "‘"; 672 | } 673 | if(/^"$/.test(text)) { 674 | // Special case: single-character " token 675 | if(/\S/.test(this.smartyPantsLastChar)) { 676 | return "”"; 677 | } 678 | return "“"; 679 | } 680 | 681 | // Special case if the very first character is a quote 682 | // followed by punctuation at a non-word-break. Close the quotes by brute force: 683 | text = text.replace (/^'(?=[!"#\$\%'()*+,\-.\/:;<=>?\@\[\\]\^_`{|}~]\B)/, "’"); 684 | text = text.replace (/^"(?=[!"#\$\%'()*+,\-.\/:;<=>?\@\[\\]\^_`{|}~]\B)/, "”"); 685 | 686 | // Special case for double sets of quotes, e.g.: 687 | //

    He said, "'Quoted' words in a larger quote."

    688 | text = text.replace(/"'(?=\w)/g, "“‘"); 689 | text = text.replace(/'"(?=\w)/g, "‘“"); 690 | 691 | // Special case for decade abbreviations (the '80s): 692 | text = text.replace(/'(?=\d{2}s)/g, "’"); 693 | 694 | // Get most opening single quotes: 695 | text = text.replace(/(\s| |--|&[mn]dash;|&\#8211;|&\#8212;|&\#x201[34];)'(?=\w)/g, "$1‘"); 696 | 697 | // Single closing quotes: 698 | text = text.replace(/([^\s\[\{\(\-])'/g, "$1’"); 699 | text = text.replace(/'(?=\s|s\b)/g, "’"); 700 | 701 | // Any remaining single quotes should be opening ones: 702 | text = text.replace(/'/g, "‘"); 703 | 704 | // Get most opening double quotes: 705 | text = text.replace(/(\s| |--|&[mn]dash;|&\#8211;|&\#8212;|&\#x201[34];)"(?=\w)/g, "$1“"); 706 | 707 | // Double closing quotes: 708 | text = text.replace(/([^\s\[\{\(\-])"/g, "$1”"); 709 | text = text.replace(/"(?=\s)/g, "”"); 710 | 711 | // Any remaining quotes should be opening ones. 712 | text = text.replace(/"/ig, "“"); 713 | return text; 714 | }; 715 | 716 | // Find and convert markdown extra definition lists into html. 717 | Markdown.Extra.prototype.runSmartyPants = function(text) { 718 | this.smartyPantsLastChar = ''; 719 | text = this.educatePants(text); 720 | // Clean everything inside html tags (some of them may have been converted due to our rough html parsing) 721 | text = text.replace(/(<([a-zA-Z1-6]+)\b([^\n>]*?)(\/)?>)/g, revertPants); 722 | return text; 723 | }; 724 | 725 | /****************************************************************** 726 | * Definition Lists * 727 | ******************************************************************/ 728 | 729 | // Find and convert markdown extra definition lists into html. 730 | Markdown.Extra.prototype.definitionLists = function(text) { 731 | var wholeList = new RegExp( 732 | ['(\\x02\\n?|\\n\\n)' , 733 | '(?:' , 734 | '(' , // $1 = whole list 735 | '(' , // $2 736 | '[ ]{0,3}' , 737 | '((?:[ \\t]*\\S.*\\n)+)', // $3 = defined term 738 | '\\n?' , 739 | '[ ]{0,3}:[ ]+' , // colon starting definition 740 | ')' , 741 | '([\\s\\S]+?)' , 742 | '(' , // $4 743 | '(?=\\0x03)' , // \z 744 | '|' , 745 | '(?=' , 746 | '\\n{2,}' , 747 | '(?=\\S)' , 748 | '(?!' , // Negative lookahead for another term 749 | '[ ]{0,3}' , 750 | '(?:\\S.*\\n)+?' , // defined term 751 | '\\n?' , 752 | '[ ]{0,3}:[ ]+' , // colon starting definition 753 | ')' , 754 | '(?!' , // Negative lookahead for another definition 755 | '[ ]{0,3}:[ ]+' , // colon starting definition 756 | ')' , 757 | ')' , 758 | ')' , 759 | ')' , 760 | ')' 761 | ].join(''), 762 | 'gm' 763 | ); 764 | 765 | var self = this; 766 | text = addAnchors(text); 767 | 768 | text = text.replace(wholeList, function(match, pre, list) { 769 | var result = trim(self.processDefListItems(list)); 770 | result = "
    \n" + result + "\n
    "; 771 | return pre + self.hashExtraBlock(result) + "\n\n"; 772 | }); 773 | 774 | return removeAnchors(text); 775 | }; 776 | 777 | // Process the contents of a single definition list, splitting it 778 | // into individual term and definition list items. 779 | Markdown.Extra.prototype.processDefListItems = function(listStr) { 780 | var self = this; 781 | 782 | var dt = new RegExp( 783 | ['(\\x02\\n?|\\n\\n+)' , // leading line 784 | '(' , // definition terms = $1 785 | '[ ]{0,3}' , // leading whitespace 786 | '(?![:][ ]|[ ])' , // negative lookahead for a definition 787 | // mark (colon) or more whitespace 788 | '(?:\\S.*\\n)+?' , // actual term (not whitespace) 789 | ')' , 790 | '(?=\\n?[ ]{0,3}:[ ])' // lookahead for following line feed 791 | ].join(''), // with a definition mark 792 | 'gm' 793 | ); 794 | 795 | var dd = new RegExp( 796 | ['\\n(\\n+)?' , // leading line = $1 797 | '(' , // marker space = $2 798 | '[ ]{0,3}' , // whitespace before colon 799 | '[:][ ]+' , // definition mark (colon) 800 | ')' , 801 | '([\\s\\S]+?)' , // definition text = $3 802 | '(?=\\n*' , // stop at next definition mark, 803 | '(?:' , // next term or end of text 804 | '\\n[ ]{0,3}[:][ ]|' , 805 | '
    |\\x03' , // \z 806 | ')' , 807 | ')' 808 | ].join(''), 809 | 'gm' 810 | ); 811 | 812 | listStr = addAnchors(listStr); 813 | // trim trailing blank lines: 814 | listStr = listStr.replace(/\n{2,}(?=\\x03)/, "\n"); 815 | 816 | // Process definition terms. 817 | listStr = listStr.replace(dt, function(match, pre, termsStr) { 818 | var terms = trim(termsStr).split("\n"); 819 | var text = ''; 820 | for (var i = 0; i < terms.length; i++) { 821 | var term = terms[i]; 822 | // process spans inside dt 823 | term = convertSpans(trim(term), self); 824 | text += "\n
    " + term + "
    "; 825 | } 826 | return text + "\n"; 827 | }); 828 | 829 | // Process actual definitions. 830 | listStr = listStr.replace(dd, function(match, leadingLine, markerSpace, def) { 831 | if (leadingLine || def.match(/\n{2,}/)) { 832 | // replace marker with the appropriate whitespace indentation 833 | def = Array(markerSpace.length + 1).join(' ') + def; 834 | // process markdown inside definition 835 | // TODO?: currently doesn't apply extensions 836 | def = outdent(def) + "\n\n"; 837 | def = "\n" + convertAll(def, self) + "\n"; 838 | } else { 839 | // convert span-level markdown inside definition 840 | def = rtrim(def); 841 | def = convertSpans(outdent(def), self); 842 | } 843 | 844 | return "\n
    " + def + "
    \n"; 845 | }); 846 | 847 | return removeAnchors(listStr); 848 | }; 849 | 850 | 851 | /*********************************************************** 852 | * Strikethrough * 853 | ************************************************************/ 854 | 855 | Markdown.Extra.prototype.strikethrough = function(text) { 856 | // Pretty much duplicated from _DoItalicsAndBold 857 | return text.replace(/([\W_]|^)~T~T(?=\S)([^\r]*?\S[\*_]*)~T~T([\W_]|$)/g, 858 | "$1$2$3"); 859 | }; 860 | 861 | 862 | /*********************************************************** 863 | * New lines * 864 | ************************************************************/ 865 | 866 | Markdown.Extra.prototype.newlines = function(text) { 867 | // We have to ignore already converted newlines and line breaks in sub-list items 868 | return text.replace(/(<(?:br|\/li)>)?\n/g, function(wholeMatch, previousTag) { 869 | return previousTag ? wholeMatch : "
    \n"; 870 | }); 871 | }; 872 | 873 | })(); 874 | 875 | -------------------------------------------------------------------------------- /lib/markdown/Markdown.Sanitizer.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var output, Converter; 3 | if (typeof exports === "object" && typeof require === "function") { // we're in a CommonJS (e.g. Node.js) module 4 | output = exports; 5 | Converter = require("./Markdown.Converter").Converter; 6 | } else { 7 | output = window.Markdown; 8 | Converter = output.Converter; 9 | } 10 | 11 | output.getSanitizingConverter = function () { 12 | var converter = new Converter(); 13 | converter.hooks.chain("postConversion", sanitizeHtml); 14 | converter.hooks.chain("postConversion", balanceTags); 15 | return converter; 16 | } 17 | 18 | function sanitizeHtml(html) { 19 | return html.replace(/<[^>]*>?/gi, sanitizeTag); 20 | } 21 | 22 | // (tags that can be opened/closed) | (tags that stand alone) 23 | var basic_tag_whitelist = /^(<\/?(b|blockquote|code|del|dd|dl|dt|em|h1|h2|h3|i|kbd|li|ol|p|pre|s|sup|sub|strong|strike|ul)>|<(br|hr)\s?\/?>)$/i; 24 | // | 25 | var a_white = /^(]+")?\s?>|<\/a>)$/i; 26 | 27 | // ]*")?(\stitle="[^"<>]*")?\s?\/?>)$/i; 29 | 30 | function sanitizeTag(tag) { 31 | if (tag.match(basic_tag_whitelist) || tag.match(a_white) || tag.match(img_white)) 32 | return tag; 33 | else 34 | return ""; 35 | } 36 | 37 | /// 38 | /// attempt to balance HTML tags in the html string 39 | /// by removing any unmatched opening or closing tags 40 | /// IMPORTANT: we *assume* HTML has *already* been 41 | /// sanitized and is safe/sane before balancing! 42 | /// 43 | /// adapted from CODESNIPPET: A8591DBA-D1D3-11DE-947C-BA5556D89593 44 | /// 45 | function balanceTags(html) { 46 | 47 | if (html == "") 48 | return ""; 49 | 50 | var re = /<\/?\w+[^>]*(\s|$|>)/g; 51 | // convert everything to lower case; this makes 52 | // our case insensitive comparisons easier 53 | var tags = html.toLowerCase().match(re); 54 | 55 | // no HTML tags present? nothing to do; exit now 56 | var tagcount = (tags || []).length; 57 | if (tagcount == 0) 58 | return html; 59 | 60 | var tagname, tag; 61 | var ignoredtags = "



  • "; 62 | var match; 63 | var tagpaired = []; 64 | var tagremove = []; 65 | var needsRemoval = false; 66 | 67 | // loop through matched tags in forward order 68 | for (var ctag = 0; ctag < tagcount; ctag++) { 69 | tagname = tags[ctag].replace(/<\/?(\w+).*/, "$1"); 70 | // skip any already paired tags 71 | // and skip tags in our ignore list; assume they're self-closed 72 | if (tagpaired[ctag] || ignoredtags.search("<" + tagname + ">") > -1) 73 | continue; 74 | 75 | tag = tags[ctag]; 76 | match = -1; 77 | 78 | if (!/^<\//.test(tag)) { 79 | // this is an opening tag 80 | // search forwards (next tags), look for closing tags 81 | for (var ntag = ctag + 1; ntag < tagcount; ntag++) { 82 | if (!tagpaired[ntag] && tags[ntag] == "") { 83 | match = ntag; 84 | break; 85 | } 86 | } 87 | } 88 | 89 | if (match == -1) 90 | needsRemoval = tagremove[ctag] = true; // mark for removal 91 | else 92 | tagpaired[match] = true; // mark paired 93 | } 94 | 95 | if (!needsRemoval) 96 | return html; 97 | 98 | // delete all orphaned tags from the string 99 | 100 | var ctag = 0; 101 | html = html.replace(re, function (match) { 102 | var res = tagremove[ctag] ? "" : match; 103 | ctag++; 104 | return res; 105 | }); 106 | return html; 107 | } 108 | })(); 109 | -------------------------------------------------------------------------------- /lib/markdown/prettify.js: -------------------------------------------------------------------------------- 1 | var q=null;window.PR_SHOULD_USE_CONTINUATION=!0; 2 | (function(){function L(a){function m(a){var f=a.charCodeAt(0);if(f!==92)return f;var b=a.charAt(1);return(f=r[b])?f:"0"<=b&&b<="7"?parseInt(a.substring(1),8):b==="u"||b==="x"?parseInt(a.substring(2),16):a.charCodeAt(1)}function e(a){if(a<32)return(a<16?"\\x0":"\\x")+a.toString(16);a=String.fromCharCode(a);if(a==="\\"||a==="-"||a==="["||a==="]")a="\\"+a;return a}function h(a){for(var f=a.substring(1,a.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),a= 3 | [],b=[],o=f[0]==="^",c=o?1:0,i=f.length;c122||(d<65||j>90||b.push([Math.max(65,j)|32,Math.min(d,90)|32]),d<97||j>122||b.push([Math.max(97,j)&-33,Math.min(d,122)&-33]))}}b.sort(function(a,f){return a[0]-f[0]||f[1]-a[1]});f=[];j=[NaN,NaN];for(c=0;ci[0]&&(i[1]+1>i[0]&&b.push("-"),b.push(e(i[1])));b.push("]");return b.join("")}function y(a){for(var f=a.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),b=f.length,d=[],c=0,i=0;c=2&&a==="["?f[c]=h(j):a!=="\\"&&(f[c]=j.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return f.join("")}for(var t=0,s=!1,l=!1,p=0,d=a.length;p=5&&"lang-"===b.substring(0,5))&&!(o&&typeof o[1]==="string"))c=!1,b="src";c||(r[f]=b)}i=d;d+=f.length;if(c){c=o[1];var j=f.indexOf(c),k=j+c.length;o[2]&&(k=f.length-o[2].length,j=k-c.length);b=b.substring(5);B(l+i,f.substring(0,j),e,p);B(l+i+j,c,C(b,c),p);B(l+i+k,f.substring(k),e,p)}else p.push(l+i,b)}a.e=p}var h={},y;(function(){for(var e=a.concat(m), 9 | l=[],p={},d=0,g=e.length;d=0;)h[n.charAt(k)]=r;r=r[1];n=""+r;p.hasOwnProperty(n)||(l.push(r),p[n]=q)}l.push(/[\S\s]/);y=L(l)})();var t=m.length;return e}function u(a){var m=[],e=[];a.tripleQuotedStrings?m.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?m.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/, 10 | q,"'\"`"]):m.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&e.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var h=a.hashComments;h&&(a.cStyleComments?(h>1?m.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):m.push(["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),e.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,q])):m.push(["com",/^#[^\n\r]*/, 11 | q,"#"]));a.cStyleComments&&(e.push(["com",/^\/\/[^\n\r]*/,q]),e.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));a.regexLiterals&&e.push(["lang-regex",/^(?:^^\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\(|\*|\*=|\+=|,|-=|->|\/|\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\^=|\^\^|\^\^=|{|\||\|=|\|\||\|\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/]);(h=a.types)&&e.push(["typ",h]);a=(""+a.keywords).replace(/^ | $/g, 12 | "");a.length&&e.push(["kwd",RegExp("^(?:"+a.replace(/[\s,]+/g,"|")+")\\b"),q]);m.push(["pln",/^\s+/,q," \r\n\t\xa0"]);e.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,q],["pun",/^.[^\s\w"-$'./@\\`]*/,q]);return x(m,e)}function D(a,m){function e(a){switch(a.nodeType){case 1:if(k.test(a.className))break;if("BR"===a.nodeName)h(a), 13 | a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)e(a);break;case 3:case 4:if(p){var b=a.nodeValue,d=b.match(t);if(d){var c=b.substring(0,d.index);a.nodeValue=c;(b=b.substring(d.index+d[0].length))&&a.parentNode.insertBefore(s.createTextNode(b),a.nextSibling);h(a);c||a.parentNode.removeChild(a)}}}}function h(a){function b(a,d){var e=d?a.cloneNode(!1):a,f=a.parentNode;if(f){var f=b(f,1),g=a.nextSibling;f.appendChild(e);for(var h=g;h;h=g)g=h.nextSibling,f.appendChild(h)}return e} 14 | for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),e;(e=a.parentNode)&&e.nodeType===1;)a=e;d.push(a)}var k=/(?:^|\s)nocode(?:\s|$)/,t=/\r\n?|\n/,s=a.ownerDocument,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=s.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);for(l=s.createElement("LI");a.firstChild;)l.appendChild(a.firstChild);for(var d=[l],g=0;g=0;){var h=m[e];A.hasOwnProperty(h)?window.console&&console.warn("cannot override language handler %s",h):A[h]=a}}function C(a,m){if(!a||!A.hasOwnProperty(a))a=/^\s*=o&&(h+=2);e>=c&&(a+=2)}}catch(w){"console"in window&&console.log(w&&w.stack?w.stack:w)}}var v=["break,continue,do,else,for,if,return,while"],w=[[v,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"], 18 | "catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],F=[w,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],G=[w,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"], 19 | H=[G,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"],w=[w,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],I=[v,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"], 20 | J=[v,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],v=[v,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],K=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/,N=/\S/,O=u({keywords:[F,H,w,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END"+ 21 | I,J,v],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),A={};k(O,["default-code"]);k(x([],[["pln",/^[^]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]), 22 | ["default-markup","htm","html","mxml","xhtml","xml","xsl"]);k(x([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css", 23 | /^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);k(x([],[["atv",/^[\S\s]+/]]),["uq.val"]);k(u({keywords:F,hashComments:!0,cStyleComments:!0,types:K}),["c","cc","cpp","cxx","cyc","m"]);k(u({keywords:"null,true,false"}),["json"]);k(u({keywords:H,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:K}),["cs"]);k(u({keywords:G,cStyleComments:!0}),["java"]);k(u({keywords:v,hashComments:!0,multiLineStrings:!0}),["bsh","csh","sh"]);k(u({keywords:I,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}), 24 | ["cv","py"]);k(u({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["perl","pl","pm"]);k(u({keywords:J,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb"]);k(u({keywords:w,cStyleComments:!0,regexLiterals:!0}),["js"]);k(u({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes", 25 | hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);k(x([],[["str",/^[\S\s]+/]]),["regex"]);window.prettyPrintOne=function(a,m,e){var h=document.createElement("PRE");h.innerHTML=a;e&&D(h,e);E({g:m,i:e,h:h});return h.innerHTML};window.prettyPrint=function(a){function m(){for(var e=window.PR_SHOULD_USE_CONTINUATION?l.now()+250:Infinity;p=0){var k=k.match(g),f,b;if(b= 26 | !k){b=n;for(var o=void 0,c=b.firstChild;c;c=c.nextSibling)var i=c.nodeType,o=i===1?o?b:c:i===3?N.test(c.nodeValue)?b:o:o;b=(f=o===b?void 0:o)&&"CODE"===f.tagName}b&&(k=f.className.match(g));k&&(k=k[1]);b=!1;for(o=n.parentNode;o;o=o.parentNode)if((o.tagName==="pre"||o.tagName==="code"||o.tagName==="xmp")&&o.className&&o.className.indexOf("prettyprint")>=0){b=!0;break}b||((b=(b=n.className.match(/\blinenums\b(?::(\d+))?/))?b[1]&&b[1].length?+b[1]:!0:!1)&&D(n,b),d={g:k,h:n,i:b},E(d))}}p