├── scss ├── _colors.scss ├── _mixins.scss ├── huh.scss ├── _animations.scss ├── _launcher.scss └── _container.scss ├── package.json ├── Gruntfile.js ├── README.md ├── huh.php ├── js ├── huh.js └── marked.js ├── huh.css └── LICENSE /scss/_colors.scss: -------------------------------------------------------------------------------- 1 | $main-accent: #009688; 2 | -------------------------------------------------------------------------------- /scss/_mixins.scss: -------------------------------------------------------------------------------- 1 | // huh mixins 2 | @mixin calc( $property, $expression ) { 3 | #{$property}: calc( #{$expression} ); 4 | } 5 | -------------------------------------------------------------------------------- /scss/huh.scss: -------------------------------------------------------------------------------- 1 | // huh 2 | @import "_mixins.scss"; 3 | @import "_colors.scss"; 4 | @import "_animations.scss"; 5 | 6 | // launcher button 7 | @import "_launcher.scss"; 8 | 9 | // content container 10 | @import "_container.scss"; 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "huh", 3 | "title": "huh", 4 | "version": "1.0.0", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/secretpizzaparty/huh" 8 | }, 9 | "license": "GPL-2.0+", 10 | "main": "Gruntfile.js", 11 | "devDependencies": { 12 | "grunt": "~1.0.1", 13 | "grunt-checktextdomain": "~1.0.0", 14 | "grunt-contrib-clean": "~1.0.0", 15 | "grunt-contrib-cssmin": "~1.0.0", 16 | "grunt-contrib-sass": "~1.0.0", 17 | "grunt-contrib-watch": "~1.0.0", 18 | "grunt-wp-i18n": "~0.5.4", 19 | "node-bourbon": "~4.2.3" 20 | }, 21 | "engines": { 22 | "node": ">=0.8.0", 23 | "npm": ">=1.1.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /scss/_animations.scss: -------------------------------------------------------------------------------- 1 | // huh animations 2 | 3 | @keyframes popIn { 4 | 0% { 5 | opacity: 0; 6 | transform: scale( 0 ); 7 | } 8 | 55% { 9 | opacity: 0; 10 | transform: scale( 0 ); 11 | } 12 | 75% { 13 | opacity: 1; 14 | transform: scale( 1.1 ); 15 | } 16 | 90% { 17 | opacity: 1; 18 | transform: scale( 0.9 ); 19 | } 20 | 100% { 21 | opacity: 1; 22 | transform: scale( 1 ); 23 | } 24 | } 25 | 26 | @keyframes sonarEffect { 27 | 0% { 28 | opacity: 1; 29 | } 30 | 40% { 31 | opacity: 0.5; 32 | box-shadow: 0 0 4px 2px rgba( 0, 0, 0 , 0.2 ); 33 | } 34 | 100% { 35 | box-shadow: 0 0 4px 2px rgba( 0, 0, 0 , 0.2 ); 36 | transform: scale(1.5); 37 | opacity: 0; 38 | } 39 | } 40 | 41 | @keyframes fadeIn { 42 | 0% { 43 | opacity: 0; 44 | transform: translateY( 20px ); 45 | } 46 | 47 | 100% { 48 | opacity: 1; 49 | transform: translateY( 0 ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function( grunt ) { 2 | 'use strict'; 3 | 4 | grunt.initConfig({ 5 | 6 | // Setting folder templates. 7 | dirs: { 8 | css: 'scss' 9 | }, 10 | 11 | // Compile all .scss files. 12 | sass: { 13 | compile: { 14 | options: { 15 | sourcemap: 'none', 16 | loadPath: require( 'node-bourbon' ).includePaths 17 | }, 18 | files: [{ 19 | expand: true, 20 | cwd: '<%= dirs.css %>/', 21 | src: ['*.scss'], 22 | dest: './', 23 | ext: '.css' 24 | }] 25 | } 26 | }, 27 | 28 | // Minify all .css files. 29 | cssmin: { 30 | minify: { 31 | expand: true, 32 | cwd: './', 33 | src: [ 34 | '*.css', 35 | '!*.min.css' 36 | ], 37 | dest: './', 38 | ext: '.min.css' 39 | } 40 | }, 41 | 42 | // Watch changes for assets. 43 | watch: { 44 | css: { 45 | files: ['<%= dirs.css %>/*.scss'], 46 | tasks: ['sass', 'cssmin'] 47 | } 48 | } 49 | }); 50 | 51 | // Load NPM tasks to be used here 52 | grunt.loadNpmTasks( 'grunt-contrib-sass' ); 53 | grunt.loadNpmTasks( 'grunt-contrib-cssmin' ); 54 | grunt.loadNpmTasks( 'grunt-contrib-watch' ); 55 | 56 | // Register tasks 57 | grunt.registerTask( 'default', [ 58 | 'css' 59 | ]); 60 | 61 | grunt.registerTask( 'css', [ 62 | 'sass', 63 | 'cssmin' 64 | ]); 65 | }; 66 | -------------------------------------------------------------------------------- /scss/_launcher.scss: -------------------------------------------------------------------------------- 1 | // huh launcher button 2 | .huh-launcher { 3 | animation: popIn 0.8s ease-in-out; 4 | bottom: 30px; 5 | position: fixed; 6 | right: 20px; 7 | z-index: 500001; 8 | } 9 | 10 | .huh-launcher--button { 11 | -webkit-appearance: none; 12 | background: $main-accent; 13 | border: none; 14 | border-radius: 50px; 15 | box-shadow: 0 2px 10px rgba( 0, 0, 0, 0.2 ); 16 | box-sizing: border-box; 17 | cursor: pointer; 18 | height: 50px; 19 | padding: 0; 20 | position: relative; 21 | transition: transform ease-out 0.1s; 22 | width: 50px; 23 | z-index: 1; 24 | 25 | svg { 26 | fill: #fff; 27 | float: right; 28 | height: 50px; 29 | left: 0; 30 | position: absolute; 31 | top: 0; 32 | transition: transform 0.2s; 33 | width: 50px; 34 | } 35 | 36 | .huh-launcher--icon-close { 37 | height: 30px; 38 | opacity: 0; 39 | padding: 10px; 40 | transform: scale( 0.2 ); 41 | width: 30px; 42 | } 43 | 44 | &.active { 45 | .huh-launcher--icon-enable { 46 | opacity: 0; 47 | transform: scale( 0.2 ); 48 | } 49 | 50 | .huh-launcher--icon-close { 51 | opacity: 1; 52 | transform: scale( 1); 53 | } 54 | } 55 | 56 | &::after, 57 | &::before { 58 | border-radius: 50%; 59 | box-sizing: content-box; 60 | content: ''; 61 | left: 0; 62 | height: 100%; 63 | pointer-events: none; 64 | position: absolute; 65 | top: 0; 66 | transform: scale( 0.9 ); 67 | width: 100%; 68 | } 69 | 70 | &:hover, 71 | &:focus { 72 | outline: none; 73 | transform: scale( 0.9 ); 74 | 75 | &::after { 76 | animation: sonarEffect 1.3s ease-out; 77 | } 78 | } 79 | } 80 | 81 | .huh-launcher--label { 82 | display: block; 83 | font-size: 0; 84 | height: 0; 85 | width: 0; 86 | } 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Who? 2 | _huh_ was dreamt up by [secret pizza party](https://secretpizza.party) and brought to life by the amazing [Dan Hauk](https://danhauk.com/). Seriously, Dan is the best. He took a broad idea and really brought it to life like only he could. The SPP offices are open on Christmas Day but we close on May 5th (Dan's birthday) as it's a company wide holiday. 3 | 4 | # What? 5 | _huh_ is the best way to offer in dashboard documentation for all your WordPress projects. The content is generated from a markdown file which makes it super quick & easy to update your documentation whenever you want. You can learn more from the launch post [here](https://secretpizza.party/huh-making-documentation-easier/). 6 | 7 | # Where? 8 | We think _huh_ is awesome and we really want you to use it in all your projects. It's totally free/open source and you can find it on [github](https://github.com/secretpizzaparty/huh/). 9 | 10 | ## Wanna Contribute? 11 | If you found a bug, [report it here](https://github.com/secretpizzaparty/huh/issues/new). If you're a developer, we welcome pull requests of all types! 12 | 13 | ### Development Workflow 14 | 1. Make sure you have `git`, `node`, and `npm` installed and a working WordPress installation. 15 | 2. Clone this repository inside your theme directory. 16 | 17 | ``` 18 | $ git clone https://github.com/secretpizzaparty/huh.git 19 | $ cd huh 20 | ``` 21 | 22 | 3. Watch the front-end CSS/Sass for changes and rebuild accordingly with [Grunt](https://github.com/gruntjs/grunt). Please only modify the Sass files to keep the CSS consistent and clean. 23 | 24 | ``` 25 | $ npm install 26 | $ grunt watch 27 | ``` 28 | 29 | 4. Open `/wp-admin/` in your browser. 30 | 5. Have fun! 31 | 32 | 33 | # Why? 34 | [secret pizza party](https://secretpizza.party) is in the process of developing a bunch of new WordPress themes and while they are quite simple there is still a need for a wee bit of documentation. External documentation is dumb and everything should be contained in the dashboard. We created _huh_ to make that happen. 35 | 36 | # How? 37 | Adding _huh_ to your theme is incredibly easy. 38 | 39 | ## Formatting your markdown 40 | _huh_ pulls all of your `

` tags to use as a table of contents. Each section of your documentation will be contained between these `

` tags. For example: 41 | 42 | ``` 43 | # First section 44 | The content of the first section of your documentation would go here. You can include links, bullets, images, anything! 45 | 46 | # Second section 47 | This would be the next section. 48 | 49 | ## You can even use subheadings 50 | It will all be formatted correctly, but only the first-level headings will show on the table of contents. 51 | ``` 52 | 53 | ## Adding _huh_ to your theme 54 | Once you have your documentation formatted correctly, adding _huh_ to your theme is simple. 55 | 56 | Just download the zipped plugin and extract it to your theme directory. At the bottom of your theme's `functions.php` file add the following lines: 57 | 58 | ``` php 59 | require get_stylesheet_directory() . '/huh/huh.php'; 60 | function secretpizzaparty_huh() { 61 | // Enter the URL of your markdown file below 62 | $markdown_url = 'https://raw.githubusercontent.com/secretpizzaparty/huh/master/README.md'; 63 | $huh = new WP_Huh(); 64 | $huh->init( $markdown_url ); 65 | } 66 | add_action( 'admin_init', 'secretpizzaparty_huh' ); 67 | ``` 68 | 69 | Make sure you change the URL of the `$markdown_url` variable to point to your markdown file. It's that easy! 70 | -------------------------------------------------------------------------------- /huh.php: -------------------------------------------------------------------------------- 1 | markdown_doc_url = $markdown_doc_url; 24 | 25 | if ( is_admin() || is_customize_preview() ) { 26 | add_action( 'admin_enqueue_scripts', array( $this, 'huh_load_scripts' ) ); 27 | add_action( 'admin_footer', array( $this, 'display_huh' ) ); 28 | } 29 | } 30 | 31 | /** 32 | * Enqueue CSS and JS. 33 | */ 34 | public function huh_load_scripts() { 35 | wp_register_style( 'huh_admin_css', get_stylesheet_directory_uri().'/huh/huh.css', false ); 36 | wp_enqueue_style( 'huh_admin_css' ); 37 | 38 | wp_register_script( 'huh_admin_js', get_stylesheet_directory_uri().'/huh/js/huh.js', false ); 39 | wp_enqueue_script( 'huh_admin_js' ); 40 | 41 | wp_register_script( 'huh_markdown_js', get_stylesheet_directory_uri().'/huh/js/marked.js', false ); 42 | wp_enqueue_script( 'huh_markdown_js' ); 43 | } 44 | 45 | /** 46 | * Get admin color scheme. 47 | */ 48 | public function huh_get_admin_colors() { 49 | global $_wp_admin_css_colors; 50 | $current_color_scheme = get_user_meta( get_current_user_id(), 'admin_color', true ); 51 | $colors = $_wp_admin_css_colors[ $current_color_scheme ]->colors; 52 | 53 | return $colors; 54 | } 55 | 56 | /** 57 | * Display the HTML. 58 | * @param $markdown_doc_url URL of the raw markdown file. 59 | */ 60 | public function display_huh() { 61 | $colors = $this->huh_get_admin_colors(); 62 | $huh_accent_color = $colors[2]; 63 | 64 | ?> 65 | 66 |
67 | 72 |
73 | 74 |
75 |
76 |

Need help?

77 | 78 | 79 | Back 80 | 81 | 82 |
83 |
84 |
85 | svg { 90 | display: inline-block; 91 | fill: #444; 92 | height: 16px; 93 | position: relative; 94 | top: 2px; 95 | width: 16px; 96 | } 97 | } 98 | 99 | .huh-container--content { 100 | overflow: scroll; 101 | padding: 0 20px 20px; 102 | 103 | &::after { 104 | background: -moz-linear-gradient(top, rgba( #fff, 0 ) 0%, #fff 55%, #fff 100%); 105 | background: -webkit-linear-gradient(top, rgba( #fff, 0 ) 0%, #fff 55%, #fff 100%); 106 | background: linear-gradient(to bottom, rgba( #fff, 0 ) 0%, #fff 55%, #fff 100%); 107 | bottom: 0; 108 | content: ''; 109 | display: block; 110 | height: 20px; 111 | left: 0; 112 | position: absolute; 113 | width: 100%; 114 | } 115 | 116 | p, 117 | ul, 118 | ol { 119 | font-size: 14px; 120 | } 121 | 122 | img { 123 | max-width: 100%; 124 | } 125 | 126 | code, 127 | pre { 128 | max-width: 100%; 129 | overflow: scroll; 130 | white-space: pre-wrap; 131 | word-wrap: break-word; 132 | } 133 | } 134 | 135 | .huh-container--close-mobile { 136 | cursor: pointer; 137 | display: none; 138 | fill: #fff; 139 | height: 24px; 140 | padding: 17px 10px; 141 | position: absolute; 142 | right: 0; 143 | top: 0; 144 | width: 24px; 145 | 146 | @media all and ( max-width: 600px ) { 147 | display: block; 148 | } 149 | } 150 | 151 | .huh-toc--trigger { 152 | border-bottom: 1px solid #eee; 153 | cursor: pointer; 154 | display: block; 155 | font-size: 16px; 156 | font-weight: 600; 157 | margin: 0 -20px; 158 | padding: 20px; 159 | position: relative; 160 | transition: background 0.3s, box-shadow 0.1s; 161 | 162 | > span { 163 | color: #666; 164 | float: right; 165 | opacity: 0; 166 | transform: translateX( 10px ); 167 | transition: opacity 0.3s, transform 0.3s; 168 | } 169 | 170 | &:hover { 171 | background: #f9f9f9; 172 | 173 | > span { 174 | opacity: 1; 175 | transform: translateX( 0 ); 176 | } 177 | } 178 | 179 | &.hidden { 180 | display: none; 181 | } 182 | 183 | &.current { 184 | animation: fadeIn 0.3s ease-in-out; 185 | border: 0; 186 | color: #222; 187 | cursor: auto; 188 | display: block; 189 | font-size: 18px; 190 | padding-bottom: 0; 191 | 192 | > span { 193 | display: none; 194 | } 195 | 196 | &:hover { 197 | background: 0; 198 | box-shadow: none; 199 | } 200 | } 201 | 202 | &.show { 203 | animation: fadeIn 0.3s ease-in-out; 204 | } 205 | } 206 | 207 | .huh-toc--content { 208 | display: none; 209 | 210 | &.open { 211 | animation: fadeIn 0.3s ease-in-out; 212 | display: block; 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /js/huh.js: -------------------------------------------------------------------------------- 1 | // const docUrl = 'https://raw.githubusercontent.com/secretpizzaparty/huh/master/README.md'; 2 | 3 | let huhLauncher = '', 4 | huhMobileClose = '', 5 | huhContainer = '', 6 | huhContent = '', 7 | huhHeader = '', 8 | huhBackButton = '', 9 | huhAccentColor = '', 10 | huhTocTriggers = ''; 11 | 12 | // init 13 | function huhInit() { 14 | huhLauncher = document.querySelector( '#huh-launcher--button' ); 15 | huhMobileClose = document.querySelector( '#huh-mobile-close' ); 16 | huhContainer = document.querySelector( '#huh-container' ); 17 | huhContent = document.querySelector( '#huh-content' ); 18 | huhHeader = document.querySelector( '#huh-header'); 19 | huhBackButton = document.querySelector( '#huh-back-to-toc' ); 20 | huhAccentColor = huhLauncher.getAttribute( 'data-accent-color' ); 21 | 22 | // fetch the markdown file (set in huh.php) 23 | // then load the content into the container 24 | fetch( huhDocUrl ) 25 | .then( blob => blob.text() ) 26 | .then( data => loadContent( data ) ); 27 | } 28 | 29 | function loadContent( data ) { 30 | // first we format the content 31 | const dataFormat = marked( data ); 32 | 33 | // then we create our custom content structure 34 | const content = createContent( dataFormat ); 35 | 36 | // then we insert content into the content box 37 | huhContent.innerHTML = content; 38 | 39 | // apply accent color 40 | applyAccentColor( huhAccentColor ); 41 | 42 | // bind interaction events after all content is loaded 43 | huhBindEvents(); 44 | } 45 | 46 | function createContent( data ) { 47 | let sections = data.split( ' { return n != '' } ); // remove empty elements 49 | 50 | const content = sections.map( section => { 51 | const splitIndex = section.indexOf( '

' ); // split into two blocks after

52 | const headingSplit = section.slice( 0, splitIndex ); 53 | const heading = headingSplit.slice( headingSplit.indexOf( '>' ) + 1 ); // content after `id="*">`` 54 | const body = section.slice( splitIndex + 5 ); // content after closing `

` 55 | 56 | return { 57 | heading: heading, 58 | body: body 59 | }; 60 | } ); 61 | 62 | const contentHtml = formatContent( content ); 63 | 64 | return contentHtml; 65 | } 66 | 67 | function formatContent( content ) { 68 | const html = content.map( item => { 69 | return ` 70 | ${ item.heading } 71 |
72 | ${ item.body } 73 |
74 | `; 75 | } ).join( '' ); 76 | 77 | return html; 78 | } 79 | 80 | function showHideContainer( e ) { 81 | huhLauncher.classList.toggle( 'active' ); 82 | huhContainer.classList.toggle( 'open' ); 83 | } 84 | 85 | function showContent( e ) { 86 | // hide all triggers 87 | for ( i = 0; i < huhTocTriggers.length; i++ ) { 88 | huhTocTriggers[i].classList.add( 'hidden' ); 89 | huhTocTriggers[i].classList.remove( 'show' ); 90 | } 91 | 92 | // add a class to indicate current selection 93 | e.target.classList.add( 'current' ); 94 | 95 | // add a class to content block of the current selection 96 | // so we can show just that one 97 | const content = e.target.nextElementSibling; 98 | content.classList.add( 'open' ); 99 | 100 | // show back button 101 | huhHeader.classList.add( 'with-content' ); 102 | } 103 | 104 | function backToToc() { 105 | // show all triggers 106 | for ( i = 0; i < huhTocTriggers.length; i++ ) { 107 | huhTocTriggers[i].classList.remove( 'hidden', 'current' ); 108 | huhTocTriggers[i].classList.add( 'show' ); 109 | } 110 | 111 | // hide all content blocks 112 | const contentBlocks = document.querySelectorAll( '.huh-toc--content' ); 113 | for ( i = 0; i < contentBlocks.length; i++ ) { 114 | contentBlocks[i].classList.remove( 'open' ); 115 | } 116 | 117 | // show main header 118 | huhHeader.classList.remove( 'with-content' ); 119 | } 120 | 121 | function applyAccentColor( color ) { 122 | huhLauncher.setAttribute( 'style', 'background:' + color ); 123 | huhHeader.setAttribute( 'style', 'background:' + color ); 124 | } 125 | 126 | function huhBindEvents() { 127 | huhLauncher.addEventListener( 'click', showHideContainer ); 128 | huhMobileClose.addEventListener( 'click', showHideContainer ); 129 | huhBackButton.addEventListener( 'click', backToToc ); 130 | 131 | huhTocTriggers = document.querySelectorAll( '.huh-toc--trigger' ); 132 | for ( i = 0; i < huhTocTriggers.length; i++ ) { 133 | huhTocTriggers[i].addEventListener( 'click', showContent ); 134 | } 135 | } 136 | 137 | // init after page has loaded to make sure 138 | // we can find the DOM nodes to modify 139 | window.addEventListener( 'load', huhInit ); 140 | -------------------------------------------------------------------------------- /huh.css: -------------------------------------------------------------------------------- 1 | @keyframes popIn { 2 | 0% { 3 | opacity: 0; 4 | transform: scale(0); } 5 | 55% { 6 | opacity: 0; 7 | transform: scale(0); } 8 | 75% { 9 | opacity: 1; 10 | transform: scale(1.1); } 11 | 90% { 12 | opacity: 1; 13 | transform: scale(0.9); } 14 | 100% { 15 | opacity: 1; 16 | transform: scale(1); } } 17 | @keyframes sonarEffect { 18 | 0% { 19 | opacity: 1; } 20 | 40% { 21 | opacity: 0.5; 22 | box-shadow: 0 0 4px 2px rgba(0, 0, 0, 0.2); } 23 | 100% { 24 | box-shadow: 0 0 4px 2px rgba(0, 0, 0, 0.2); 25 | transform: scale(1.5); 26 | opacity: 0; } } 27 | @keyframes fadeIn { 28 | 0% { 29 | opacity: 0; 30 | transform: translateY(20px); } 31 | 100% { 32 | opacity: 1; 33 | transform: translateY(0); } } 34 | .huh-launcher { 35 | animation: popIn 0.8s ease-in-out; 36 | bottom: 30px; 37 | position: fixed; 38 | right: 20px; 39 | z-index: 500001; } 40 | 41 | .huh-launcher--button { 42 | -webkit-appearance: none; 43 | background: #009688; 44 | border: none; 45 | border-radius: 50px; 46 | box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); 47 | box-sizing: border-box; 48 | cursor: pointer; 49 | height: 50px; 50 | padding: 0; 51 | position: relative; 52 | transition: transform ease-out 0.1s; 53 | width: 50px; 54 | z-index: 1; } 55 | .huh-launcher--button svg { 56 | fill: #fff; 57 | float: right; 58 | height: 50px; 59 | left: 0; 60 | position: absolute; 61 | top: 0; 62 | transition: transform 0.2s; 63 | width: 50px; } 64 | .huh-launcher--button .huh-launcher--icon-close { 65 | height: 30px; 66 | opacity: 0; 67 | padding: 10px; 68 | transform: scale(0.2); 69 | width: 30px; } 70 | .huh-launcher--button.active .huh-launcher--icon-enable { 71 | opacity: 0; 72 | transform: scale(0.2); } 73 | .huh-launcher--button.active .huh-launcher--icon-close { 74 | opacity: 1; 75 | transform: scale(1); } 76 | .huh-launcher--button::after, .huh-launcher--button::before { 77 | border-radius: 50%; 78 | box-sizing: content-box; 79 | content: ''; 80 | left: 0; 81 | height: 100%; 82 | pointer-events: none; 83 | position: absolute; 84 | top: 0; 85 | transform: scale(0.9); 86 | width: 100%; } 87 | .huh-launcher--button:hover, .huh-launcher--button:focus { 88 | outline: none; 89 | transform: scale(0.9); } 90 | .huh-launcher--button:hover::after, .huh-launcher--button:focus::after { 91 | animation: sonarEffect 1.3s ease-out; } 92 | 93 | .huh-launcher--label { 94 | display: block; 95 | font-size: 0; 96 | height: 0; 97 | width: 0; } 98 | 99 | .huh-container { 100 | background: #fff; 101 | border-radius: 4px; 102 | bottom: 90px; 103 | box-shadow: 0 5px 20px rgba(0, 0, 0, 0.15); 104 | display: none; 105 | height: calc( 100% - 150px ); 106 | max-height: 550px; 107 | max-width: 350px; 108 | position: fixed; 109 | right: 20px; 110 | width: 100%; 111 | z-index: 500001; } 112 | .huh-container.open { 113 | animation: fadeIn 0.3s ease-in-out; 114 | display: flex; 115 | flex-direction: column; } 116 | @media screen and (max-width: 600px) { 117 | .huh-container { 118 | bottom: 0; 119 | border-radius: 0; 120 | height: 100%; 121 | max-width: none; 122 | right: 0; 123 | top: 46px; } } 124 | 125 | .huh-container--head { 126 | background: #009688; 127 | border-radius: 4px 4px 0 0; 128 | height: 60px; 129 | flex-shrink: 0; 130 | overflow: hidden; 131 | position: relative; 132 | transition: background 0.3s; } 133 | @media screen and (max-width: 600px) { 134 | .huh-container--head { 135 | border-radius: 0; } } 136 | .huh-container--head.with-content { 137 | background: #f5f5f5 !important; 138 | border-bottom: 1px solid #ddd; } 139 | .huh-container--head.with-content .huh-container--heading { 140 | transform: translateX(-100%); } 141 | .huh-container--head.with-content .huh-container--back { 142 | transform: translateX(0); } 143 | 144 | .huh-container--heading, 145 | .huh-container--back { 146 | box-sizing: border-box; 147 | font-size: 16px; 148 | left: 0; 149 | line-height: 60px; 150 | margin: 0; 151 | padding: 0 20px; 152 | position: absolute; 153 | top: 0; 154 | transition: transform 0.2s; 155 | width: 100%; } 156 | 157 | .huh-container--heading { 158 | color: #fff; 159 | font-weight: 400; 160 | text-align: center; 161 | transform: translateX(0); 162 | width: 100%; } 163 | @media all and (max-width: 600px) { 164 | .huh-container--heading { 165 | text-align: left; } } 166 | 167 | .huh-container--back { 168 | text-decoration: none; 169 | transform: translateX(100%); } 170 | .huh-container--back > svg { 171 | display: inline-block; 172 | fill: #444; 173 | height: 16px; 174 | position: relative; 175 | top: 2px; 176 | width: 16px; } 177 | 178 | .huh-container--content { 179 | overflow: scroll; 180 | padding: 0 20px 20px; } 181 | .huh-container--content::after { 182 | background: -moz-linear-gradient(top, rgba(255, 255, 255, 0) 0%, #fff 55%, #fff 100%); 183 | background: -webkit-linear-gradient(top, rgba(255, 255, 255, 0) 0%, #fff 55%, #fff 100%); 184 | background: linear-gradient(to bottom, rgba(255, 255, 255, 0) 0%, #fff 55%, #fff 100%); 185 | bottom: 0; 186 | content: ''; 187 | display: block; 188 | height: 20px; 189 | left: 0; 190 | position: absolute; 191 | width: 100%; } 192 | .huh-container--content p, 193 | .huh-container--content ul, 194 | .huh-container--content ol { 195 | font-size: 14px; } 196 | .huh-container--content img { 197 | max-width: 100%; } 198 | .huh-container--content code, 199 | .huh-container--content pre { 200 | max-width: 100%; 201 | overflow: scroll; 202 | white-space: pre-wrap; 203 | word-wrap: break-word; } 204 | 205 | .huh-container--close-mobile { 206 | cursor: pointer; 207 | display: none; 208 | fill: #fff; 209 | height: 24px; 210 | padding: 17px 10px; 211 | position: absolute; 212 | right: 0; 213 | top: 0; 214 | width: 24px; } 215 | @media all and (max-width: 600px) { 216 | .huh-container--close-mobile { 217 | display: block; } } 218 | 219 | .huh-toc--trigger { 220 | border-bottom: 1px solid #eee; 221 | cursor: pointer; 222 | display: block; 223 | font-size: 16px; 224 | font-weight: 600; 225 | margin: 0 -20px; 226 | padding: 20px; 227 | position: relative; 228 | transition: background 0.3s, box-shadow 0.1s; } 229 | .huh-toc--trigger > span { 230 | color: #666; 231 | float: right; 232 | opacity: 0; 233 | transform: translateX(10px); 234 | transition: opacity 0.3s, transform 0.3s; } 235 | .huh-toc--trigger:hover { 236 | background: #f9f9f9; } 237 | .huh-toc--trigger:hover > span { 238 | opacity: 1; 239 | transform: translateX(0); } 240 | .huh-toc--trigger.hidden { 241 | display: none; } 242 | .huh-toc--trigger.current { 243 | animation: fadeIn 0.3s ease-in-out; 244 | border: 0; 245 | color: #222; 246 | cursor: auto; 247 | display: block; 248 | font-size: 18px; 249 | padding-bottom: 0; } 250 | .huh-toc--trigger.current > span { 251 | display: none; } 252 | .huh-toc--trigger.current:hover { 253 | background: 0; 254 | box-shadow: none; } 255 | .huh-toc--trigger.show { 256 | animation: fadeIn 0.3s ease-in-out; } 257 | 258 | .huh-toc--content { 259 | display: none; } 260 | .huh-toc--content.open { 261 | animation: fadeIn 0.3s ease-in-out; 262 | display: block; } 263 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /js/marked.js: -------------------------------------------------------------------------------- 1 | /** 2 | * marked - a markdown parser 3 | * Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed) 4 | * https://github.com/chjj/marked 5 | */ 6 | 7 | ;(function() { 8 | 9 | /** 10 | * Block-Level Grammar 11 | */ 12 | 13 | var block = { 14 | newline: /^\n+/, 15 | code: /^( {4}[^\n]+\n*)+/, 16 | fences: noop, 17 | hr: /^( *[-*_]){3,} *(?:\n+|$)/, 18 | heading: /^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/, 19 | nptable: noop, 20 | lheading: /^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/, 21 | blockquote: /^( *>[^\n]+(\n(?!def)[^\n]+)*\n*)+/, 22 | list: /^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/, 23 | html: /^ *(?:comment *(?:\n|\s*$)|closed *(?:\n{2,}|\s*$)|closing *(?:\n{2,}|\s*$))/, 24 | def: /^ *\[([^\]]+)\]: *]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/, 25 | table: noop, 26 | paragraph: /^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/, 27 | text: /^[^\n]+/ 28 | }; 29 | 30 | block.bullet = /(?:[*+-]|\d+\.)/; 31 | block.item = /^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/; 32 | block.item = replace(block.item, 'gm') 33 | (/bull/g, block.bullet) 34 | (); 35 | 36 | block.list = replace(block.list) 37 | (/bull/g, block.bullet) 38 | ('hr', '\\n+(?=\\1?(?:[-*_] *){3,}(?:\\n+|$))') 39 | ('def', '\\n+(?=' + block.def.source + ')') 40 | (); 41 | 42 | block.blockquote = replace(block.blockquote) 43 | ('def', block.def) 44 | (); 45 | 46 | block._tag = '(?!(?:' 47 | + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code' 48 | + '|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo' 49 | + '|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|[^\\w\\s@]*@)\\b'; 50 | 51 | block.html = replace(block.html) 52 | ('comment', //) 53 | ('closed', /<(tag)[\s\S]+?<\/\1>/) 54 | ('closing', /])*?>/) 55 | (/tag/g, block._tag) 56 | (); 57 | 58 | block.paragraph = replace(block.paragraph) 59 | ('hr', block.hr) 60 | ('heading', block.heading) 61 | ('lheading', block.lheading) 62 | ('blockquote', block.blockquote) 63 | ('tag', '<' + block._tag) 64 | ('def', block.def) 65 | (); 66 | 67 | /** 68 | * Normal Block Grammar 69 | */ 70 | 71 | block.normal = merge({}, block); 72 | 73 | /** 74 | * GFM Block Grammar 75 | */ 76 | 77 | block.gfm = merge({}, block.normal, { 78 | fences: /^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\s*\1 *(?:\n+|$)/, 79 | paragraph: /^/, 80 | heading: /^ *(#{1,6}) +([^\n]+?) *#* *(?:\n+|$)/ 81 | }); 82 | 83 | block.gfm.paragraph = replace(block.paragraph) 84 | ('(?!', '(?!' 85 | + block.gfm.fences.source.replace('\\1', '\\2') + '|' 86 | + block.list.source.replace('\\1', '\\3') + '|') 87 | (); 88 | 89 | /** 90 | * GFM + Tables Block Grammar 91 | */ 92 | 93 | block.tables = merge({}, block.gfm, { 94 | nptable: /^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/, 95 | table: /^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/ 96 | }); 97 | 98 | /** 99 | * Block Lexer 100 | */ 101 | 102 | function Lexer(options) { 103 | this.tokens = []; 104 | this.tokens.links = {}; 105 | this.options = options || marked.defaults; 106 | this.rules = block.normal; 107 | 108 | if (this.options.gfm) { 109 | if (this.options.tables) { 110 | this.rules = block.tables; 111 | } else { 112 | this.rules = block.gfm; 113 | } 114 | } 115 | } 116 | 117 | /** 118 | * Expose Block Rules 119 | */ 120 | 121 | Lexer.rules = block; 122 | 123 | /** 124 | * Static Lex Method 125 | */ 126 | 127 | Lexer.lex = function(src, options) { 128 | var lexer = new Lexer(options); 129 | return lexer.lex(src); 130 | }; 131 | 132 | /** 133 | * Preprocessing 134 | */ 135 | 136 | Lexer.prototype.lex = function(src) { 137 | src = src 138 | .replace(/\r\n|\r/g, '\n') 139 | .replace(/\t/g, ' ') 140 | .replace(/\u00a0/g, ' ') 141 | .replace(/\u2424/g, '\n'); 142 | 143 | return this.token(src, true); 144 | }; 145 | 146 | /** 147 | * Lexing 148 | */ 149 | 150 | Lexer.prototype.token = function(src, top, bq) { 151 | var src = src.replace(/^ +$/gm, '') 152 | , next 153 | , loose 154 | , cap 155 | , bull 156 | , b 157 | , item 158 | , space 159 | , i 160 | , l; 161 | 162 | while (src) { 163 | // newline 164 | if (cap = this.rules.newline.exec(src)) { 165 | src = src.substring(cap[0].length); 166 | if (cap[0].length > 1) { 167 | this.tokens.push({ 168 | type: 'space' 169 | }); 170 | } 171 | } 172 | 173 | // code 174 | if (cap = this.rules.code.exec(src)) { 175 | src = src.substring(cap[0].length); 176 | cap = cap[0].replace(/^ {4}/gm, ''); 177 | this.tokens.push({ 178 | type: 'code', 179 | text: !this.options.pedantic 180 | ? cap.replace(/\n+$/, '') 181 | : cap 182 | }); 183 | continue; 184 | } 185 | 186 | // fences (gfm) 187 | if (cap = this.rules.fences.exec(src)) { 188 | src = src.substring(cap[0].length); 189 | this.tokens.push({ 190 | type: 'code', 191 | lang: cap[2], 192 | text: cap[3] || '' 193 | }); 194 | continue; 195 | } 196 | 197 | // heading 198 | if (cap = this.rules.heading.exec(src)) { 199 | src = src.substring(cap[0].length); 200 | this.tokens.push({ 201 | type: 'heading', 202 | depth: cap[1].length, 203 | text: cap[2] 204 | }); 205 | continue; 206 | } 207 | 208 | // table no leading pipe (gfm) 209 | if (top && (cap = this.rules.nptable.exec(src))) { 210 | src = src.substring(cap[0].length); 211 | 212 | item = { 213 | type: 'table', 214 | header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */), 215 | align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */), 216 | cells: cap[3].replace(/\n$/, '').split('\n') 217 | }; 218 | 219 | for (i = 0; i < item.align.length; i++) { 220 | if (/^ *-+: *$/.test(item.align[i])) { 221 | item.align[i] = 'right'; 222 | } else if (/^ *:-+: *$/.test(item.align[i])) { 223 | item.align[i] = 'center'; 224 | } else if (/^ *:-+ *$/.test(item.align[i])) { 225 | item.align[i] = 'left'; 226 | } else { 227 | item.align[i] = null; 228 | } 229 | } 230 | 231 | for (i = 0; i < item.cells.length; i++) { 232 | item.cells[i] = item.cells[i].split(/ *\| */); 233 | } 234 | 235 | this.tokens.push(item); 236 | 237 | continue; 238 | } 239 | 240 | // lheading 241 | if (cap = this.rules.lheading.exec(src)) { 242 | src = src.substring(cap[0].length); 243 | this.tokens.push({ 244 | type: 'heading', 245 | depth: cap[2] === '=' ? 1 : 2, 246 | text: cap[1] 247 | }); 248 | continue; 249 | } 250 | 251 | // hr 252 | if (cap = this.rules.hr.exec(src)) { 253 | src = src.substring(cap[0].length); 254 | this.tokens.push({ 255 | type: 'hr' 256 | }); 257 | continue; 258 | } 259 | 260 | // blockquote 261 | if (cap = this.rules.blockquote.exec(src)) { 262 | src = src.substring(cap[0].length); 263 | 264 | this.tokens.push({ 265 | type: 'blockquote_start' 266 | }); 267 | 268 | cap = cap[0].replace(/^ *> ?/gm, ''); 269 | 270 | // Pass `top` to keep the current 271 | // "toplevel" state. This is exactly 272 | // how markdown.pl works. 273 | this.token(cap, top, true); 274 | 275 | this.tokens.push({ 276 | type: 'blockquote_end' 277 | }); 278 | 279 | continue; 280 | } 281 | 282 | // list 283 | if (cap = this.rules.list.exec(src)) { 284 | src = src.substring(cap[0].length); 285 | bull = cap[2]; 286 | 287 | this.tokens.push({ 288 | type: 'list_start', 289 | ordered: bull.length > 1 290 | }); 291 | 292 | // Get each top-level item. 293 | cap = cap[0].match(this.rules.item); 294 | 295 | next = false; 296 | l = cap.length; 297 | i = 0; 298 | 299 | for (; i < l; i++) { 300 | item = cap[i]; 301 | 302 | // Remove the list item's bullet 303 | // so it is seen as the next token. 304 | space = item.length; 305 | item = item.replace(/^ *([*+-]|\d+\.) +/, ''); 306 | 307 | // Outdent whatever the 308 | // list item contains. Hacky. 309 | if (~item.indexOf('\n ')) { 310 | space -= item.length; 311 | item = !this.options.pedantic 312 | ? item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '') 313 | : item.replace(/^ {1,4}/gm, ''); 314 | } 315 | 316 | // Determine whether the next list item belongs here. 317 | // Backpedal if it does not belong in this list. 318 | if (this.options.smartLists && i !== l - 1) { 319 | b = block.bullet.exec(cap[i + 1])[0]; 320 | if (bull !== b && !(bull.length > 1 && b.length > 1)) { 321 | src = cap.slice(i + 1).join('\n') + src; 322 | i = l - 1; 323 | } 324 | } 325 | 326 | // Determine whether item is loose or not. 327 | // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/ 328 | // for discount behavior. 329 | loose = next || /\n\n(?!\s*$)/.test(item); 330 | if (i !== l - 1) { 331 | next = item.charAt(item.length - 1) === '\n'; 332 | if (!loose) loose = next; 333 | } 334 | 335 | this.tokens.push({ 336 | type: loose 337 | ? 'loose_item_start' 338 | : 'list_item_start' 339 | }); 340 | 341 | // Recurse. 342 | this.token(item, false, bq); 343 | 344 | this.tokens.push({ 345 | type: 'list_item_end' 346 | }); 347 | } 348 | 349 | this.tokens.push({ 350 | type: 'list_end' 351 | }); 352 | 353 | continue; 354 | } 355 | 356 | // html 357 | if (cap = this.rules.html.exec(src)) { 358 | src = src.substring(cap[0].length); 359 | this.tokens.push({ 360 | type: this.options.sanitize 361 | ? 'paragraph' 362 | : 'html', 363 | pre: !this.options.sanitizer 364 | && (cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style'), 365 | text: cap[0] 366 | }); 367 | continue; 368 | } 369 | 370 | // def 371 | if ((!bq && top) && (cap = this.rules.def.exec(src))) { 372 | src = src.substring(cap[0].length); 373 | this.tokens.links[cap[1].toLowerCase()] = { 374 | href: cap[2], 375 | title: cap[3] 376 | }; 377 | continue; 378 | } 379 | 380 | // table (gfm) 381 | if (top && (cap = this.rules.table.exec(src))) { 382 | src = src.substring(cap[0].length); 383 | 384 | item = { 385 | type: 'table', 386 | header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */), 387 | align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */), 388 | cells: cap[3].replace(/(?: *\| *)?\n$/, '').split('\n') 389 | }; 390 | 391 | for (i = 0; i < item.align.length; i++) { 392 | if (/^ *-+: *$/.test(item.align[i])) { 393 | item.align[i] = 'right'; 394 | } else if (/^ *:-+: *$/.test(item.align[i])) { 395 | item.align[i] = 'center'; 396 | } else if (/^ *:-+ *$/.test(item.align[i])) { 397 | item.align[i] = 'left'; 398 | } else { 399 | item.align[i] = null; 400 | } 401 | } 402 | 403 | for (i = 0; i < item.cells.length; i++) { 404 | item.cells[i] = item.cells[i] 405 | .replace(/^ *\| *| *\| *$/g, '') 406 | .split(/ *\| */); 407 | } 408 | 409 | this.tokens.push(item); 410 | 411 | continue; 412 | } 413 | 414 | // top-level paragraph 415 | if (top && (cap = this.rules.paragraph.exec(src))) { 416 | src = src.substring(cap[0].length); 417 | this.tokens.push({ 418 | type: 'paragraph', 419 | text: cap[1].charAt(cap[1].length - 1) === '\n' 420 | ? cap[1].slice(0, -1) 421 | : cap[1] 422 | }); 423 | continue; 424 | } 425 | 426 | // text 427 | if (cap = this.rules.text.exec(src)) { 428 | // Top-level should never reach here. 429 | src = src.substring(cap[0].length); 430 | this.tokens.push({ 431 | type: 'text', 432 | text: cap[0] 433 | }); 434 | continue; 435 | } 436 | 437 | if (src) { 438 | throw new 439 | Error('Infinite loop on byte: ' + src.charCodeAt(0)); 440 | } 441 | } 442 | 443 | return this.tokens; 444 | }; 445 | 446 | /** 447 | * Inline-Level Grammar 448 | */ 449 | 450 | var inline = { 451 | escape: /^\\([\\`*{}\[\]()#+\-.!_>])/, 452 | autolink: /^<([^ >]+(@|:\/)[^ >]+)>/, 453 | url: noop, 454 | tag: /^|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/, 455 | link: /^!?\[(inside)\]\(href\)/, 456 | reflink: /^!?\[(inside)\]\s*\[([^\]]*)\]/, 457 | nolink: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/, 458 | strong: /^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/, 459 | em: /^\b_((?:[^_]|__)+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/, 460 | code: /^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/, 461 | br: /^ {2,}\n(?!\s*$)/, 462 | del: noop, 463 | text: /^[\s\S]+?(?=[\\?(?:\s+['"]([\s\S]*?)['"])?\s*/; 468 | 469 | inline.link = replace(inline.link) 470 | ('inside', inline._inside) 471 | ('href', inline._href) 472 | (); 473 | 474 | inline.reflink = replace(inline.reflink) 475 | ('inside', inline._inside) 476 | (); 477 | 478 | /** 479 | * Normal Inline Grammar 480 | */ 481 | 482 | inline.normal = merge({}, inline); 483 | 484 | /** 485 | * Pedantic Inline Grammar 486 | */ 487 | 488 | inline.pedantic = merge({}, inline.normal, { 489 | strong: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/, 490 | em: /^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/ 491 | }); 492 | 493 | /** 494 | * GFM Inline Grammar 495 | */ 496 | 497 | inline.gfm = merge({}, inline.normal, { 498 | escape: replace(inline.escape)('])', '~|])')(), 499 | url: /^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/, 500 | del: /^~~(?=\S)([\s\S]*?\S)~~/, 501 | text: replace(inline.text) 502 | (']|', '~]|') 503 | ('|', '|https?://|') 504 | () 505 | }); 506 | 507 | /** 508 | * GFM + Line Breaks Inline Grammar 509 | */ 510 | 511 | inline.breaks = merge({}, inline.gfm, { 512 | br: replace(inline.br)('{2,}', '*')(), 513 | text: replace(inline.gfm.text)('{2,}', '*')() 514 | }); 515 | 516 | /** 517 | * Inline Lexer & Compiler 518 | */ 519 | 520 | function InlineLexer(links, options) { 521 | this.options = options || marked.defaults; 522 | this.links = links; 523 | this.rules = inline.normal; 524 | this.renderer = this.options.renderer || new Renderer; 525 | this.renderer.options = this.options; 526 | 527 | if (!this.links) { 528 | throw new 529 | Error('Tokens array requires a `links` property.'); 530 | } 531 | 532 | if (this.options.gfm) { 533 | if (this.options.breaks) { 534 | this.rules = inline.breaks; 535 | } else { 536 | this.rules = inline.gfm; 537 | } 538 | } else if (this.options.pedantic) { 539 | this.rules = inline.pedantic; 540 | } 541 | } 542 | 543 | /** 544 | * Expose Inline Rules 545 | */ 546 | 547 | InlineLexer.rules = inline; 548 | 549 | /** 550 | * Static Lexing/Compiling Method 551 | */ 552 | 553 | InlineLexer.output = function(src, links, options) { 554 | var inline = new InlineLexer(links, options); 555 | return inline.output(src); 556 | }; 557 | 558 | /** 559 | * Lexing/Compiling 560 | */ 561 | 562 | InlineLexer.prototype.output = function(src) { 563 | var out = '' 564 | , link 565 | , text 566 | , href 567 | , cap; 568 | 569 | while (src) { 570 | // escape 571 | if (cap = this.rules.escape.exec(src)) { 572 | src = src.substring(cap[0].length); 573 | out += cap[1]; 574 | continue; 575 | } 576 | 577 | // autolink 578 | if (cap = this.rules.autolink.exec(src)) { 579 | src = src.substring(cap[0].length); 580 | if (cap[2] === '@') { 581 | text = cap[1].charAt(6) === ':' 582 | ? this.mangle(cap[1].substring(7)) 583 | : this.mangle(cap[1]); 584 | href = this.mangle('mailto:') + text; 585 | } else { 586 | text = escape(cap[1]); 587 | href = text; 588 | } 589 | out += this.renderer.link(href, null, text); 590 | continue; 591 | } 592 | 593 | // url (gfm) 594 | if (!this.inLink && (cap = this.rules.url.exec(src))) { 595 | src = src.substring(cap[0].length); 596 | text = escape(cap[1]); 597 | href = text; 598 | out += this.renderer.link(href, null, text); 599 | continue; 600 | } 601 | 602 | // tag 603 | if (cap = this.rules.tag.exec(src)) { 604 | if (!this.inLink && /^/i.test(cap[0])) { 607 | this.inLink = false; 608 | } 609 | src = src.substring(cap[0].length); 610 | out += this.options.sanitize 611 | ? this.options.sanitizer 612 | ? this.options.sanitizer(cap[0]) 613 | : escape(cap[0]) 614 | : cap[0] 615 | continue; 616 | } 617 | 618 | // link 619 | if (cap = this.rules.link.exec(src)) { 620 | src = src.substring(cap[0].length); 621 | this.inLink = true; 622 | out += this.outputLink(cap, { 623 | href: cap[2], 624 | title: cap[3] 625 | }); 626 | this.inLink = false; 627 | continue; 628 | } 629 | 630 | // reflink, nolink 631 | if ((cap = this.rules.reflink.exec(src)) 632 | || (cap = this.rules.nolink.exec(src))) { 633 | src = src.substring(cap[0].length); 634 | link = (cap[2] || cap[1]).replace(/\s+/g, ' '); 635 | link = this.links[link.toLowerCase()]; 636 | if (!link || !link.href) { 637 | out += cap[0].charAt(0); 638 | src = cap[0].substring(1) + src; 639 | continue; 640 | } 641 | this.inLink = true; 642 | out += this.outputLink(cap, link); 643 | this.inLink = false; 644 | continue; 645 | } 646 | 647 | // strong 648 | if (cap = this.rules.strong.exec(src)) { 649 | src = src.substring(cap[0].length); 650 | out += this.renderer.strong(this.output(cap[2] || cap[1])); 651 | continue; 652 | } 653 | 654 | // em 655 | if (cap = this.rules.em.exec(src)) { 656 | src = src.substring(cap[0].length); 657 | out += this.renderer.em(this.output(cap[2] || cap[1])); 658 | continue; 659 | } 660 | 661 | // code 662 | if (cap = this.rules.code.exec(src)) { 663 | src = src.substring(cap[0].length); 664 | out += this.renderer.codespan(escape(cap[2], true)); 665 | continue; 666 | } 667 | 668 | // br 669 | if (cap = this.rules.br.exec(src)) { 670 | src = src.substring(cap[0].length); 671 | out += this.renderer.br(); 672 | continue; 673 | } 674 | 675 | // del (gfm) 676 | if (cap = this.rules.del.exec(src)) { 677 | src = src.substring(cap[0].length); 678 | out += this.renderer.del(this.output(cap[1])); 679 | continue; 680 | } 681 | 682 | // text 683 | if (cap = this.rules.text.exec(src)) { 684 | src = src.substring(cap[0].length); 685 | out += this.renderer.text(escape(this.smartypants(cap[0]))); 686 | continue; 687 | } 688 | 689 | if (src) { 690 | throw new 691 | Error('Infinite loop on byte: ' + src.charCodeAt(0)); 692 | } 693 | } 694 | 695 | return out; 696 | }; 697 | 698 | /** 699 | * Compile Link 700 | */ 701 | 702 | InlineLexer.prototype.outputLink = function(cap, link) { 703 | var href = escape(link.href) 704 | , title = link.title ? escape(link.title) : null; 705 | 706 | return cap[0].charAt(0) !== '!' 707 | ? this.renderer.link(href, title, this.output(cap[1])) 708 | : this.renderer.image(href, title, escape(cap[1])); 709 | }; 710 | 711 | /** 712 | * Smartypants Transformations 713 | */ 714 | 715 | InlineLexer.prototype.smartypants = function(text) { 716 | if (!this.options.smartypants) return text; 717 | return text 718 | // em-dashes 719 | .replace(/---/g, '\u2014') 720 | // en-dashes 721 | .replace(/--/g, '\u2013') 722 | // opening singles 723 | .replace(/(^|[-\u2014/(\[{"\s])'/g, '$1\u2018') 724 | // closing singles & apostrophes 725 | .replace(/'/g, '\u2019') 726 | // opening doubles 727 | .replace(/(^|[-\u2014/(\[{\u2018\s])"/g, '$1\u201c') 728 | // closing doubles 729 | .replace(/"/g, '\u201d') 730 | // ellipses 731 | .replace(/\.{3}/g, '\u2026'); 732 | }; 733 | 734 | /** 735 | * Mangle Links 736 | */ 737 | 738 | InlineLexer.prototype.mangle = function(text) { 739 | if (!this.options.mangle) return text; 740 | var out = '' 741 | , l = text.length 742 | , i = 0 743 | , ch; 744 | 745 | for (; i < l; i++) { 746 | ch = text.charCodeAt(i); 747 | if (Math.random() > 0.5) { 748 | ch = 'x' + ch.toString(16); 749 | } 750 | out += '&#' + ch + ';'; 751 | } 752 | 753 | return out; 754 | }; 755 | 756 | /** 757 | * Renderer 758 | */ 759 | 760 | function Renderer(options) { 761 | this.options = options || {}; 762 | } 763 | 764 | Renderer.prototype.code = function(code, lang, escaped) { 765 | if (this.options.highlight) { 766 | var out = this.options.highlight(code, lang); 767 | if (out != null && out !== code) { 768 | escaped = true; 769 | code = out; 770 | } 771 | } 772 | 773 | if (!lang) { 774 | return '
'
 775 |       + (escaped ? code : escape(code, true))
 776 |       + '\n
'; 777 | } 778 | 779 | return '
'
 783 |     + (escaped ? code : escape(code, true))
 784 |     + '\n
\n'; 785 | }; 786 | 787 | Renderer.prototype.blockquote = function(quote) { 788 | return '
\n' + quote + '
\n'; 789 | }; 790 | 791 | Renderer.prototype.html = function(html) { 792 | return html; 793 | }; 794 | 795 | Renderer.prototype.heading = function(text, level, raw) { 796 | return '' 802 | + text 803 | + '\n'; 806 | }; 807 | 808 | Renderer.prototype.hr = function() { 809 | return this.options.xhtml ? '
\n' : '
\n'; 810 | }; 811 | 812 | Renderer.prototype.list = function(body, ordered) { 813 | var type = ordered ? 'ol' : 'ul'; 814 | return '<' + type + '>\n' + body + '\n'; 815 | }; 816 | 817 | Renderer.prototype.listitem = function(text) { 818 | return '
  • ' + text + '
  • \n'; 819 | }; 820 | 821 | Renderer.prototype.paragraph = function(text) { 822 | return '

    ' + text + '

    \n'; 823 | }; 824 | 825 | Renderer.prototype.table = function(header, body) { 826 | return '\n' 827 | + '\n' 828 | + header 829 | + '\n' 830 | + '\n' 831 | + body 832 | + '\n' 833 | + '
    \n'; 834 | }; 835 | 836 | Renderer.prototype.tablerow = function(content) { 837 | return '\n' + content + '\n'; 838 | }; 839 | 840 | Renderer.prototype.tablecell = function(content, flags) { 841 | var type = flags.header ? 'th' : 'td'; 842 | var tag = flags.align 843 | ? '<' + type + ' style="text-align:' + flags.align + '">' 844 | : '<' + type + '>'; 845 | return tag + content + '\n'; 846 | }; 847 | 848 | // span level renderer 849 | Renderer.prototype.strong = function(text) { 850 | return '' + text + ''; 851 | }; 852 | 853 | Renderer.prototype.em = function(text) { 854 | return '' + text + ''; 855 | }; 856 | 857 | Renderer.prototype.codespan = function(text) { 858 | return '' + text + ''; 859 | }; 860 | 861 | Renderer.prototype.br = function() { 862 | return this.options.xhtml ? '
    ' : '
    '; 863 | }; 864 | 865 | Renderer.prototype.del = function(text) { 866 | return '' + text + ''; 867 | }; 868 | 869 | Renderer.prototype.link = function(href, title, text) { 870 | if (this.options.sanitize) { 871 | try { 872 | var prot = decodeURIComponent(unescape(href)) 873 | .replace(/[^\w:]/g, '') 874 | .toLowerCase(); 875 | } catch (e) { 876 | return ''; 877 | } 878 | if (prot.indexOf('javascript:') === 0 || prot.indexOf('vbscript:') === 0) { 879 | return ''; 880 | } 881 | } 882 | var out = '
    '; 887 | return out; 888 | }; 889 | 890 | Renderer.prototype.image = function(href, title, text) { 891 | var out = '' + text + '' : '>'; 896 | return out; 897 | }; 898 | 899 | Renderer.prototype.text = function(text) { 900 | return text; 901 | }; 902 | 903 | /** 904 | * Parsing & Compiling 905 | */ 906 | 907 | function Parser(options) { 908 | this.tokens = []; 909 | this.token = null; 910 | this.options = options || marked.defaults; 911 | this.options.renderer = this.options.renderer || new Renderer; 912 | this.renderer = this.options.renderer; 913 | this.renderer.options = this.options; 914 | } 915 | 916 | /** 917 | * Static Parse Method 918 | */ 919 | 920 | Parser.parse = function(src, options, renderer) { 921 | var parser = new Parser(options, renderer); 922 | return parser.parse(src); 923 | }; 924 | 925 | /** 926 | * Parse Loop 927 | */ 928 | 929 | Parser.prototype.parse = function(src) { 930 | this.inline = new InlineLexer(src.links, this.options, this.renderer); 931 | this.tokens = src.reverse(); 932 | 933 | var out = ''; 934 | while (this.next()) { 935 | out += this.tok(); 936 | } 937 | 938 | return out; 939 | }; 940 | 941 | /** 942 | * Next Token 943 | */ 944 | 945 | Parser.prototype.next = function() { 946 | return this.token = this.tokens.pop(); 947 | }; 948 | 949 | /** 950 | * Preview Next Token 951 | */ 952 | 953 | Parser.prototype.peek = function() { 954 | return this.tokens[this.tokens.length - 1] || 0; 955 | }; 956 | 957 | /** 958 | * Parse Text Tokens 959 | */ 960 | 961 | Parser.prototype.parseText = function() { 962 | var body = this.token.text; 963 | 964 | while (this.peek().type === 'text') { 965 | body += '\n' + this.next().text; 966 | } 967 | 968 | return this.inline.output(body); 969 | }; 970 | 971 | /** 972 | * Parse Current Token 973 | */ 974 | 975 | Parser.prototype.tok = function() { 976 | switch (this.token.type) { 977 | case 'space': { 978 | return ''; 979 | } 980 | case 'hr': { 981 | return this.renderer.hr(); 982 | } 983 | case 'heading': { 984 | return this.renderer.heading( 985 | this.inline.output(this.token.text), 986 | this.token.depth, 987 | this.token.text); 988 | } 989 | case 'code': { 990 | return this.renderer.code(this.token.text, 991 | this.token.lang, 992 | this.token.escaped); 993 | } 994 | case 'table': { 995 | var header = '' 996 | , body = '' 997 | , i 998 | , row 999 | , cell 1000 | , flags 1001 | , j; 1002 | 1003 | // header 1004 | cell = ''; 1005 | for (i = 0; i < this.token.header.length; i++) { 1006 | flags = { header: true, align: this.token.align[i] }; 1007 | cell += this.renderer.tablecell( 1008 | this.inline.output(this.token.header[i]), 1009 | { header: true, align: this.token.align[i] } 1010 | ); 1011 | } 1012 | header += this.renderer.tablerow(cell); 1013 | 1014 | for (i = 0; i < this.token.cells.length; i++) { 1015 | row = this.token.cells[i]; 1016 | 1017 | cell = ''; 1018 | for (j = 0; j < row.length; j++) { 1019 | cell += this.renderer.tablecell( 1020 | this.inline.output(row[j]), 1021 | { header: false, align: this.token.align[j] } 1022 | ); 1023 | } 1024 | 1025 | body += this.renderer.tablerow(cell); 1026 | } 1027 | return this.renderer.table(header, body); 1028 | } 1029 | case 'blockquote_start': { 1030 | var body = ''; 1031 | 1032 | while (this.next().type !== 'blockquote_end') { 1033 | body += this.tok(); 1034 | } 1035 | 1036 | return this.renderer.blockquote(body); 1037 | } 1038 | case 'list_start': { 1039 | var body = '' 1040 | , ordered = this.token.ordered; 1041 | 1042 | while (this.next().type !== 'list_end') { 1043 | body += this.tok(); 1044 | } 1045 | 1046 | return this.renderer.list(body, ordered); 1047 | } 1048 | case 'list_item_start': { 1049 | var body = ''; 1050 | 1051 | while (this.next().type !== 'list_item_end') { 1052 | body += this.token.type === 'text' 1053 | ? this.parseText() 1054 | : this.tok(); 1055 | } 1056 | 1057 | return this.renderer.listitem(body); 1058 | } 1059 | case 'loose_item_start': { 1060 | var body = ''; 1061 | 1062 | while (this.next().type !== 'list_item_end') { 1063 | body += this.tok(); 1064 | } 1065 | 1066 | return this.renderer.listitem(body); 1067 | } 1068 | case 'html': { 1069 | var html = !this.token.pre && !this.options.pedantic 1070 | ? this.inline.output(this.token.text) 1071 | : this.token.text; 1072 | return this.renderer.html(html); 1073 | } 1074 | case 'paragraph': { 1075 | return this.renderer.paragraph(this.inline.output(this.token.text)); 1076 | } 1077 | case 'text': { 1078 | return this.renderer.paragraph(this.parseText()); 1079 | } 1080 | } 1081 | }; 1082 | 1083 | /** 1084 | * Helpers 1085 | */ 1086 | 1087 | function escape(html, encode) { 1088 | return html 1089 | .replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&') 1090 | .replace(//g, '>') 1092 | .replace(/"/g, '"') 1093 | .replace(/'/g, '''); 1094 | } 1095 | 1096 | function unescape(html) { 1097 | // explicitly match decimal, hex, and named HTML entities 1098 | return html.replace(/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/g, function(_, n) { 1099 | n = n.toLowerCase(); 1100 | if (n === 'colon') return ':'; 1101 | if (n.charAt(0) === '#') { 1102 | return n.charAt(1) === 'x' 1103 | ? String.fromCharCode(parseInt(n.substring(2), 16)) 1104 | : String.fromCharCode(+n.substring(1)); 1105 | } 1106 | return ''; 1107 | }); 1108 | } 1109 | 1110 | function replace(regex, opt) { 1111 | regex = regex.source; 1112 | opt = opt || ''; 1113 | return function self(name, val) { 1114 | if (!name) return new RegExp(regex, opt); 1115 | val = val.source || val; 1116 | val = val.replace(/(^|[^\[])\^/g, '$1'); 1117 | regex = regex.replace(name, val); 1118 | return self; 1119 | }; 1120 | } 1121 | 1122 | function noop() {} 1123 | noop.exec = noop; 1124 | 1125 | function merge(obj) { 1126 | var i = 1 1127 | , target 1128 | , key; 1129 | 1130 | for (; i < arguments.length; i++) { 1131 | target = arguments[i]; 1132 | for (key in target) { 1133 | if (Object.prototype.hasOwnProperty.call(target, key)) { 1134 | obj[key] = target[key]; 1135 | } 1136 | } 1137 | } 1138 | 1139 | return obj; 1140 | } 1141 | 1142 | 1143 | /** 1144 | * Marked 1145 | */ 1146 | 1147 | function marked(src, opt, callback) { 1148 | if (callback || typeof opt === 'function') { 1149 | if (!callback) { 1150 | callback = opt; 1151 | opt = null; 1152 | } 1153 | 1154 | opt = merge({}, marked.defaults, opt || {}); 1155 | 1156 | var highlight = opt.highlight 1157 | , tokens 1158 | , pending 1159 | , i = 0; 1160 | 1161 | try { 1162 | tokens = Lexer.lex(src, opt) 1163 | } catch (e) { 1164 | return callback(e); 1165 | } 1166 | 1167 | pending = tokens.length; 1168 | 1169 | var done = function(err) { 1170 | if (err) { 1171 | opt.highlight = highlight; 1172 | return callback(err); 1173 | } 1174 | 1175 | var out; 1176 | 1177 | try { 1178 | out = Parser.parse(tokens, opt); 1179 | } catch (e) { 1180 | err = e; 1181 | } 1182 | 1183 | opt.highlight = highlight; 1184 | 1185 | return err 1186 | ? callback(err) 1187 | : callback(null, out); 1188 | }; 1189 | 1190 | if (!highlight || highlight.length < 3) { 1191 | return done(); 1192 | } 1193 | 1194 | delete opt.highlight; 1195 | 1196 | if (!pending) return done(); 1197 | 1198 | for (; i < tokens.length; i++) { 1199 | (function(token) { 1200 | if (token.type !== 'code') { 1201 | return --pending || done(); 1202 | } 1203 | return highlight(token.text, token.lang, function(err, code) { 1204 | if (err) return done(err); 1205 | if (code == null || code === token.text) { 1206 | return --pending || done(); 1207 | } 1208 | token.text = code; 1209 | token.escaped = true; 1210 | --pending || done(); 1211 | }); 1212 | })(tokens[i]); 1213 | } 1214 | 1215 | return; 1216 | } 1217 | try { 1218 | if (opt) opt = merge({}, marked.defaults, opt); 1219 | return Parser.parse(Lexer.lex(src, opt), opt); 1220 | } catch (e) { 1221 | e.message += '\nPlease report this to https://github.com/chjj/marked.'; 1222 | if ((opt || marked.defaults).silent) { 1223 | return '

    An error occured:

    '
    1224 |         + escape(e.message + '', true)
    1225 |         + '
    '; 1226 | } 1227 | throw e; 1228 | } 1229 | } 1230 | 1231 | /** 1232 | * Options 1233 | */ 1234 | 1235 | marked.options = 1236 | marked.setOptions = function(opt) { 1237 | merge(marked.defaults, opt); 1238 | return marked; 1239 | }; 1240 | 1241 | marked.defaults = { 1242 | gfm: true, 1243 | tables: true, 1244 | breaks: false, 1245 | pedantic: false, 1246 | sanitize: false, 1247 | sanitizer: null, 1248 | mangle: true, 1249 | smartLists: false, 1250 | silent: false, 1251 | highlight: null, 1252 | langPrefix: 'lang-', 1253 | smartypants: false, 1254 | headerPrefix: '', 1255 | renderer: new Renderer, 1256 | xhtml: false 1257 | }; 1258 | 1259 | /** 1260 | * Expose 1261 | */ 1262 | 1263 | marked.Parser = Parser; 1264 | marked.parser = Parser.parse; 1265 | 1266 | marked.Renderer = Renderer; 1267 | 1268 | marked.Lexer = Lexer; 1269 | marked.lexer = Lexer.lex; 1270 | 1271 | marked.InlineLexer = InlineLexer; 1272 | marked.inlineLexer = InlineLexer.output; 1273 | 1274 | marked.parse = marked; 1275 | 1276 | if (typeof module !== 'undefined' && typeof exports === 'object') { 1277 | module.exports = marked; 1278 | } else if (typeof define === 'function' && define.amd) { 1279 | define(function() { return marked; }); 1280 | } else { 1281 | this.marked = marked; 1282 | } 1283 | 1284 | }).call(function() { 1285 | return this || (typeof window !== 'undefined' ? window : global); 1286 | }()); 1287 | --------------------------------------------------------------------------------