├── .node-version ├── test ├── .eslintrc.yml ├── markdown-it-incremental-dom.js └── renderer.js ├── .eslintignore ├── .prettierrc.yml ├── docs ├── .eslintrc.yml ├── images │ ├── repainting-innerhtml.gif │ └── repainting-incremental-dom.gif ├── index.js ├── index.css ├── index.html └── docs.md ├── .prettierignore ├── entry.js ├── .eslintrc.yml ├── .travis.yml ├── babel.config.js ├── version.js ├── src ├── markdown-it-incremental-dom.js └── mixins │ ├── rules.js │ └── renderer.js ├── LICENSE ├── webpack.config.js ├── package.json ├── .gitignore ├── CHANGELOG.md └── README.md /.node-version: -------------------------------------------------------------------------------- 1 | v10.13.0 2 | -------------------------------------------------------------------------------- /test/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | mocha: true 3 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | lib/ 3 | coverage/ 4 | node_modules 5 | -------------------------------------------------------------------------------- /.prettierrc.yml: -------------------------------------------------------------------------------- 1 | semi: false 2 | singleQuote: true 3 | trailingComma: es5 4 | -------------------------------------------------------------------------------- /docs/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | browser: true 3 | node: false 4 | es6: false 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .git/ 2 | .nyc_output/ 3 | dist/ 4 | lib/ 5 | coverage/ 6 | node_modules 7 | package.json 8 | -------------------------------------------------------------------------------- /docs/images/repainting-innerhtml.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yhatt/markdown-it-incremental-dom/HEAD/docs/images/repainting-innerhtml.gif -------------------------------------------------------------------------------- /docs/images/repainting-incremental-dom.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yhatt/markdown-it-incremental-dom/HEAD/docs/images/repainting-incremental-dom.gif -------------------------------------------------------------------------------- /entry.js: -------------------------------------------------------------------------------- 1 | import markdownitIncrementalDOM from './src/markdown-it-incremental-dom' 2 | 3 | export { markdownitIncrementalDOM } 4 | export default markdownitIncrementalDOM 5 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | node: true 3 | es6: true 4 | 5 | extends: 6 | - airbnb-base 7 | - prettier 8 | 9 | rules: 10 | class-methods-use-this: off 11 | func-names: off 12 | no-param-reassign: off 13 | no-undef: off 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 10.13.0 4 | - 8.13.0 5 | - 6.14.4 6 | 7 | cache: yarn 8 | sudo: false 9 | 10 | script: 11 | - yarn lint 12 | - yarn format:check 13 | - yarn test:coverage --ci --maxWorkers=2 --verbose 14 | 15 | after_success: yarn coveralls 16 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [['@babel/preset-env', { targets: { node: '6.14' } }]], 3 | plugins: [ 4 | ['@babel/plugin-proposal-object-rest-spread', { useBuiltIns: true }], 5 | ], 6 | env: { 7 | test: { 8 | presets: [['@babel/preset-env', { targets: { node: 'current' } }]], 9 | }, 10 | }, 11 | } 12 | -------------------------------------------------------------------------------- /version.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | 4 | const unreleased = '## [Unreleased]' 5 | const [date] = new Date().toISOString().split('T') 6 | const version = `## v${process.env.npm_package_version} - ${date}` 7 | 8 | const changelog = path.resolve(__dirname, 'CHANGELOG.md') 9 | const content = fs.readFileSync(changelog, 'utf8') 10 | 11 | fs.writeFileSync( 12 | changelog, 13 | content.replace(unreleased, `${unreleased}\n\n${version}`) 14 | ) 15 | -------------------------------------------------------------------------------- /src/markdown-it-incremental-dom.js: -------------------------------------------------------------------------------- 1 | import renderer from './mixins/renderer' 2 | import rules from './mixins/rules' 3 | 4 | export default function(md, target, opts = {}) { 5 | const options = { incrementalizeDefaultRules: true, ...opts } 6 | const incrementalDOM = !target && window ? window.IncrementalDOM : target 7 | const mixin = renderer(incrementalDOM) 8 | 9 | Object.defineProperty(md, 'IncrementalDOMRenderer', { 10 | get() { 11 | const extended = Object.assign( 12 | Object.create(Object.getPrototypeOf(md.renderer)), 13 | md.renderer, 14 | mixin 15 | ) 16 | 17 | if (options.incrementalizeDefaultRules) { 18 | extended.rules = { ...extended.rules, ...rules(incrementalDOM) } 19 | } 20 | 21 | return extended 22 | }, 23 | }) 24 | 25 | md.renderToIncrementalDOM = (src, env = {}) => 26 | md.IncrementalDOMRenderer.render(md.parse(src, env), md.options, env) 27 | 28 | md.renderInlineToIncrementalDOM = (src, env = {}) => 29 | md.IncrementalDOMRenderer.render(md.parseInline(src, env), md.options, env) 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017-2018 Yuki Hattori (yukihattori1116@gmail.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/mixins/rules.js: -------------------------------------------------------------------------------- 1 | export default function(incrementalDom) { 2 | const { elementClose, elementOpen, elementVoid, text } = incrementalDom 3 | 4 | return { 5 | code_inline(tokens, idx, options, env, slf) { 6 | return () => { 7 | elementOpen.apply( 8 | this, 9 | ['code', '', []].concat(slf.renderAttrsToArray(tokens[idx])) 10 | ) 11 | text(tokens[idx].content) 12 | elementClose('code') 13 | } 14 | }, 15 | 16 | code_block(tokens, idx, options, env, slf) { 17 | return () => { 18 | elementOpen.apply( 19 | this, 20 | ['pre', '', []].concat(slf.renderAttrsToArray(tokens[idx])) 21 | ) 22 | elementOpen('code') 23 | text(tokens[idx].content) 24 | elementClose('code') 25 | elementClose('pre') 26 | } 27 | }, 28 | 29 | hardbreak() { 30 | return () => elementVoid('br') 31 | }, 32 | 33 | softbreak(tokens, idx, options) { 34 | return () => (options.breaks ? elementVoid('br') : text('\n')) 35 | }, 36 | 37 | text(tokens, idx) { 38 | return () => text(tokens[idx].content) 39 | }, 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /docs/index.js: -------------------------------------------------------------------------------- 1 | ;(() => { 2 | document.addEventListener('DOMContentLoaded', () => { 3 | const md = markdownit().use(markdownitIncrementalDOM) 4 | 5 | const text = document.querySelector('#text') 6 | const target = document.querySelector('#target') 7 | const options = document.querySelectorAll('.markdown-options') 8 | let method = document.querySelector('.markdown-options:checked').value 9 | 10 | const render = function() { 11 | if (method === 'incrementalDOM') { 12 | IncrementalDOM.patch(target, md.renderToIncrementalDOM(text.value)) 13 | } else { 14 | target.innerHTML = md.render(text.value) 15 | } 16 | } 17 | 18 | const initializeRendering = function() { 19 | text.removeAttribute('disabled') 20 | text.removeAttribute('placeholder') 21 | text.addEventListener('input', render) 22 | 23 | Array.prototype.forEach.call(options, elm => { 24 | elm.addEventListener('change', function onChange() { 25 | method = this.value 26 | render() 27 | }) 28 | }) 29 | 30 | render() 31 | } 32 | 33 | text.setAttribute('disabled', 'disabled') 34 | 35 | fetch('./docs.md', { headers: { 'Content-Type': 'text/plain' } }) 36 | .then(res => res.text()) 37 | .then(t => { 38 | text.value = t 39 | initializeRendering() 40 | }) 41 | .catch(() => { 42 | text.value = '*Failed initializing docs.*' 43 | initializeRendering() 44 | }) 45 | }) 46 | })() 47 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const objectRestSpread = require('@babel/plugin-proposal-object-rest-spread') 2 | const path = require('path') 3 | const webpack = require('webpack') 4 | const packageConfig = require('./package.json') 5 | 6 | const banner = `${packageConfig.name} ${packageConfig.version} 7 | ${packageConfig.repository.url} 8 | 9 | Includes htmlparser2 10 | https://github.com/fb55/htmlparser2/ 11 | https://github.com/fb55/htmlparser2/raw/master/LICENSE 12 | 13 | @license ${packageConfig.license} 14 | ${packageConfig.repository.url}/raw/master/LICENSE` 15 | 16 | const basename = path.basename(packageConfig.main, '.js') 17 | const browsers = ['> 1%', 'last 2 versions', 'Firefox ESR', 'IE >= 9'] 18 | const configuration = { 19 | output: { 20 | path: path.resolve(__dirname, 'dist'), 21 | filename: '[name].js', 22 | libraryTarget: 'umd', 23 | umdNamedDefine: true, 24 | }, 25 | plugins: [new webpack.BannerPlugin(banner)], 26 | module: { 27 | rules: [ 28 | { 29 | test: /\.js$/, 30 | exclude: /node_modules/, 31 | loader: 'babel-loader', 32 | options: { 33 | babelrc: false, 34 | presets: [ 35 | [ 36 | '@babel/preset-env', 37 | { modules: false, targets: { browsers }, useBuiltIns: 'usage' }, 38 | ], 39 | ], 40 | plugins: [[objectRestSpread, { useBuiltIns: true }]], 41 | }, 42 | }, 43 | ], 44 | }, 45 | } 46 | 47 | exports.default = [ 48 | { 49 | ...configuration, 50 | entry: { [basename]: './entry.js' }, 51 | optimization: { minimize: false }, 52 | }, 53 | { ...configuration, entry: { [`${basename}.min`]: './entry.js' } }, 54 | ] 55 | -------------------------------------------------------------------------------- /docs/index.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | body, 4 | html { 5 | height: 100%; 6 | margin: 0; 7 | padding: 0; 8 | } 9 | 10 | a { 11 | text-decoration: none; 12 | } 13 | 14 | a:hover { 15 | text-decoration: underline; 16 | } 17 | 18 | header { 19 | background: #666; 20 | color: #fff; 21 | height: 70px; 22 | line-height: 70px; 23 | padding: 0 0.5em; 24 | } 25 | 26 | header a { 27 | color: #fff; 28 | } 29 | 30 | h1 { 31 | margin: 0; 32 | } 33 | 34 | .nav { 35 | display: block; 36 | float: right; 37 | margin: 0; 38 | padding: 0; 39 | } 40 | 41 | .nav > li { 42 | display: inline; 43 | } 44 | 45 | .nav > li > a { 46 | display: inline-block; 47 | padding: 0 1em; 48 | } 49 | 50 | .container { 51 | height: 100%; 52 | position: relative; 53 | } 54 | 55 | .options { 56 | background: #ddd; 57 | color: #333; 58 | 59 | position: absolute; 60 | left: 0; 61 | top: 70px; 62 | right: 0; 63 | 64 | height: 30px; 65 | line-height: 30px; 66 | padding: 0 0.5em; 67 | } 68 | 69 | .text-container { 70 | background: #f8f8f8; 71 | 72 | position: absolute; 73 | left: 0; 74 | top: 100px; 75 | right: 50%; 76 | bottom: 0; 77 | } 78 | 79 | .text-wrapper { 80 | position: absolute; 81 | top: 0; 82 | left: 0; 83 | right: 0; 84 | bottom: 0; 85 | margin: 20px; 86 | } 87 | 88 | .markdown-container { 89 | position: absolute; 90 | left: 50%; 91 | top: 100px; 92 | right: 0; 93 | bottom: 0; 94 | 95 | overflow: auto; 96 | } 97 | 98 | #target { 99 | margin: 20px; 100 | } 101 | 102 | #text { 103 | background: transparent; 104 | border: 0; 105 | outline: 0; 106 | display: block; 107 | font-size: 16px; 108 | height: 100%; 109 | margin: 0; 110 | padding: 0; 111 | resize: none; 112 | width: 100%; 113 | } 114 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 |code_inline'
35 |
36 | context('when it is false', () => {
37 | it('parses HTML string by htmlparser2', () => {
38 | const func = md({
39 | incrementalizeDefaultRules: false,
40 | }).renderInlineToIncrementalDOM(mdString)
41 |
42 | IncrementalDOM.patch(document.body, func)
43 | expect(spy).toHaveBeenCalledWith(expectedHTML)
44 | })
45 | })
46 |
47 | context('when it is true', () => {
48 | it('does not parse HTML string in overridden rule', () => {
49 | const func = md({
50 | incrementalizeDefaultRules: true,
51 | }).renderInlineToIncrementalDOM(mdString)
52 |
53 | IncrementalDOM.patch(document.body, func)
54 | expect(spy).not.toHaveBeenCalledWith(expectedHTML)
55 | })
56 | })
57 | })
58 | })
59 | })
60 |
61 | describe('get IncrementalDOMRenderer', () => {
62 | it('returns IncrementalDOM renderer that is injected into current state', () => {
63 | const instance = md()
64 | const { options } = instance
65 | const tokens = instance.parse('# test')
66 |
67 | instance.renderer.rules.heading_open = () => '['
68 | instance.renderer.rules.heading_close = () => ']'
69 |
70 | const rendererOne = instance.IncrementalDOMRenderer
71 | IncrementalDOM.patch(document.body, rendererOne.render(tokens, options))
72 | expect(document.body.innerHTML).toBe('[test]')
73 |
74 | instance.renderer.rules.heading_open = () => '^'
75 | instance.renderer.rules.heading_close = () => '$'
76 |
77 | const rendererTwo = instance.IncrementalDOMRenderer
78 | IncrementalDOM.patch(document.body, rendererTwo.render(tokens, options))
79 | expect(document.body.innerHTML).toBe('^test$')
80 | })
81 | })
82 |
83 | describe('.renderToIncrementalDOM', () => {
84 | it('returns patchable function by specified Incremental DOM', () => {
85 | const func = md().renderToIncrementalDOM('markdown-it-incremental-dom')
86 |
87 | IncrementalDOM.patch(document.body, func)
88 | expect(document.body.innerHTML).toBe('markdown-it-incremental-dom
') 89 | }) 90 | }) 91 | 92 | describe('.renderInlineToIncrementalDOM', () => { 93 | it('returns patchable function by specified Incremental DOM', () => { 94 | const func = md().renderInlineToIncrementalDOM( 95 | 'markdown-it-incremental-dom' 96 | ) 97 | 98 | IncrementalDOM.patch(document.body, func) 99 | expect(document.body.innerHTML).toBe('markdown-it-incremental-dom') 100 | }) 101 | }) 102 | }) 103 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib 2 | dist 3 | 4 | ########## gitignore.io ########## 5 | # Created by https://www.gitignore.io/api/node,windows,macos,linux,sublimetext,emacs,vim,visualstudiocode 6 | 7 | ### Node ### 8 | # Logs 9 | logs 10 | *.log 11 | npm-debug.log* 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # node-waf configuration 32 | .lock-wscript 33 | 34 | # Compiled binary addons (http://nodejs.org/api/addons.html) 35 | build/Release 36 | 37 | # Dependency directories 38 | node_modules 39 | jspm_packages 40 | 41 | # Optional npm cache directory 42 | .npm 43 | 44 | # Optional eslint cache 45 | .eslintcache 46 | 47 | # Optional REPL history 48 | .node_repl_history 49 | 50 | # Output of 'npm pack' 51 | *.tgz 52 | 53 | # Yarn Integrity file 54 | .yarn-integrity 55 | 56 | ### Windows ### 57 | # Windows image file caches 58 | Thumbs.db 59 | ehthumbs.db 60 | 61 | # Folder config file 62 | Desktop.ini 63 | 64 | # Recycle Bin used on file shares 65 | $RECYCLE.BIN/ 66 | 67 | # Windows Installer files 68 | *.cab 69 | *.msi 70 | *.msm 71 | *.msp 72 | 73 | # Windows shortcuts 74 | *.lnk 75 | 76 | ### macOS ### 77 | *.DS_Store 78 | .AppleDouble 79 | .LSOverride 80 | 81 | # Icon must end with two \r 82 | Icon 83 | # Thumbnails 84 | ._* 85 | # Files that might appear in the root of a volume 86 | .DocumentRevisions-V100 87 | .fseventsd 88 | .Spotlight-V100 89 | .TemporaryItems 90 | .Trashes 91 | .VolumeIcon.icns 92 | .com.apple.timemachine.donotpresent 93 | # Directories potentially created on remote AFP share 94 | .AppleDB 95 | .AppleDesktop 96 | Network Trash Folder 97 | Temporary Items 98 | .apdisk 99 | 100 | ### Linux ### 101 | *~ 102 | 103 | # temporary files which can be created if a process still has a handle open of a deleted file 104 | .fuse_hidden* 105 | 106 | # KDE directory preferences 107 | .directory 108 | 109 | # Linux trash folder which might appear on any partition or disk 110 | .Trash-* 111 | 112 | # .nfs files are created when an open file is removed but is still being accessed 113 | .nfs* 114 | 115 | ### SublimeText ### 116 | # cache files for sublime text 117 | *.tmlanguage.cache 118 | *.tmPreferences.cache 119 | *.stTheme.cache 120 | 121 | # project files are user-specific 122 | *.sublime-project 123 | *.sublime-workspace 124 | 125 | # sftp configuration file 126 | sftp-config.json 127 | 128 | # Package control specific files 129 | Package Control.last-run 130 | Package Control.ca-list 131 | Package Control.ca-bundle 132 | Package Control.system-ca-bundle 133 | Package Control.cache/ 134 | Package Control.ca-certs/ 135 | bh_unicode_properties.cache 136 | 137 | # Sublime-github package stores a github token in this file 138 | # https://packagecontrol.io/packages/sublime-github 139 | GitHub.sublime-settings 140 | 141 | ### Emacs ### 142 | # -*- mode: gitignore; -*- 143 | \#*\# 144 | /.emacs.desktop 145 | /.emacs.desktop.lock 146 | *.elc 147 | auto-save-list 148 | tramp 149 | .\#* 150 | 151 | # Org-mode 152 | .org-id-locations 153 | *_archive 154 | 155 | # flymake-mode 156 | *_flymake.* 157 | 158 | # eshell files 159 | /eshell/history 160 | /eshell/lastdir 161 | 162 | # elpa packages 163 | /elpa/ 164 | 165 | # reftex files 166 | *.rel 167 | 168 | # AUCTeX auto folder 169 | /auto/ 170 | 171 | # cask packages 172 | .cask/ 173 | 174 | # Flycheck 175 | flycheck_*.el 176 | 177 | # server auth directory 178 | /server/ 179 | 180 | # projectiles files 181 | .projectile 182 | 183 | # directory configuration 184 | .dir-locals.el 185 | 186 | ### Vim ### 187 | # swap 188 | [._]*.s[a-w][a-z] 189 | [._]s[a-w][a-z] 190 | # session 191 | Session.vim 192 | # temporary 193 | .netrwhist 194 | # auto-generated tag files 195 | tags 196 | 197 | ### VisualStudioCode ### 198 | .vscode/* 199 | !.vscode/settings.json 200 | !.vscode/tasks.json 201 | !.vscode/launch.json 202 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [Unreleased] 4 | 5 | ### Security 6 | 7 | - Upgrade `npm-run-all` package to get rid of the malicious attack by `flatmap-stream` ([#42](https://github.com/yhatt/markdown-it-incremental-dom/pull/42)) 8 | 9 | ## v2.1.0 - 2018-11-21 10 | 11 | ### Added 12 | 13 | - Support Node 10 LTS 14 | 15 | ### Changed 16 | 17 | - Upgrade dependent packges to latest, includes [incremental-dom 0.6.0](https://github.com/google/incremental-dom/releases/tag/0.6.0) 18 | - Update incremental-dom CDN to use [jsDelivr](https://cdn.jsdelivr.net/npm/incremental-dom@0.6.0/dist/incremental-dom-min.js) instead of Google (0.6.0 is not provided) 19 | - Modernize example codes and demo JS 20 | - Apply Prettier code formatting for configuration files and demo HTML 21 | 22 | ## v2.0.2 - 2018-10-23 23 | 24 | ### Fixed 25 | 26 | - Fix the self-closing tags of SVG and MathML ([#40](https://github.com/yhatt/markdown-it-incremental-dom/pull/40) by [@m93a](https://github.com/m93a)) 27 | 28 | ## v2.0.1 - 2018-08-31 29 | 30 | ### Fixed 31 | 32 | - Fix twice element closing by htmlparser2 ([#34](https://github.com/yhatt/markdown-it-incremental-dom/pull/34)) 33 | 34 | ### Added 35 | 36 | - Add versioning script ([#35](https://github.com/yhatt/markdown-it-incremental-dom/pull/35)) 37 | 38 | ## v2.0.0 - 2018-08-17 39 | 40 | ### Breaking 41 | 42 | - No longer support Node < v6.14.2 (Boron Maintenance LTS release). 43 | 44 | ### Changed 45 | 46 | - Upgrade Node LTS to v8.11.4 and dependent packages to latest version 47 | - Migrate test framework from mocha to Jest 48 | 49 | ## v1.3.0 - 2018-02-15 50 | 51 | ### Added 52 | 53 | - Define `IncrementalDOMRenderer` as public getter ([#28](https://github.com/yhatt/markdown-it-incremental-dom/pull/28)) 54 | 55 | ### Fixed 56 | 57 | - Fix build entry and module path for browser ([#27](https://github.com/yhatt/markdown-it-incremental-dom/pull/27)) 58 | - Support inline SVG correctly ([#29](https://github.com/yhatt/markdown-it-incremental-dom/pull/29)) 59 | - Fix format script to work globstar correctly ([#26](https://github.com/yhatt/markdown-it-incremental-dom/pull/26)) 60 | 61 | ### Changed 62 | 63 | - Upgrade dependencies to latest version ([#30](https://github.com/yhatt/markdown-it-incremental-dom/pull/30)) 64 | 65 | ## v1.2.0 - 2018-01-15 66 | 67 | ### Fixed 68 | 69 | - Fix incrementalized softbreak rule to keep break as text ([#25](https://github.com/yhatt/markdown-it-incremental-dom/pull/25)) 70 | 71 | ### Changed 72 | 73 | - Upgrade Babel to v7 beta ([#23](https://github.com/yhatt/markdown-it-incremental-dom/pull/23)) 74 | - Update README.md and demo page to use jsDelivr CDN ([#24](https://github.com/yhatt/markdown-it-incremental-dom/pull/24)) 75 | 76 | ## v1.1.2 - 2018-01-11 77 | 78 | ### Fixed 79 | 80 | - Reduce bundle size of built for browser ([#22](https://github.com/yhatt/markdown-it-incremental-dom/pull/22)) 81 | 82 | ## v1.1.1 - 2018-01-06 83 | 84 | ### Fixed 85 | 86 | - Sanitize HTML element name and attributes to avoid occurring errors while rendering invalid HTML ([#18](https://github.com/yhatt/markdown-it-incremental-dom/pull/18)) 87 | 88 | ### Changed 89 | 90 | - Upgrade dependencies to latest version ([#12](https://github.com/yhatt/markdown-it-incremental-dom/pull/12)) 91 | - Upgrade node version to v8.9.3 LTS ([#16](https://github.com/yhatt/markdown-it-incremental-dom/pull/16)) 92 | - Use babel-preset-env + polyfill instead of babel-preset-es2015 ([#13](https://github.com/yhatt/markdown-it-incremental-dom/pull/13)) 93 | - Add `incremental-dom >=0.5.0` to peerDependencies ([#15](https://github.com/yhatt/markdown-it-incremental-dom/pull/15)) 94 | - Format source code by prettier ([#17](https://github.com/yhatt/markdown-it-incremental-dom/pull/17)) 95 | 96 | ## v1.0.0 - 2017-03-14 97 | 98 | ### Added 99 | 100 | - For browser: Add banner to show license ([#9](https://github.com/yhatt/markdown-it-incremental-dom/pull/9)) 101 | - For browser: Provide uncompressed version ([#9](https://github.com/yhatt/markdown-it-incremental-dom/pull/9)) 102 | - Override markdown-it's default renderer rules by incrementalized functions for better performance ([#7](https://github.com/yhatt/markdown-it-incremental-dom/pull/7)) 103 | - Option argument on initialize that supported `incrementalizeDefaultRules` ([#7](https://github.com/yhatt/markdown-it-incremental-dom/pull/7)) 104 | - Badges on README.md: Coverage (powered by Coveralls), npm version, and LICENSE ([#6](https://github.com/yhatt/markdown-it-incremental-dom/pull/6)) 105 | - [Demo page](https://yhatt.github.io/markdown-it-incremental-dom/) with explaining of key features ([#3](https://github.com/yhatt/markdown-it-incremental-dom/issue/3)) 106 | 107 | ## v0.1.0 - 2017-02-22 108 | 109 | - Initial release. 110 | -------------------------------------------------------------------------------- /test/renderer.js: -------------------------------------------------------------------------------- 1 | import { stripIndents } from 'common-tags' 2 | import context from 'jest-plugin-context' 3 | import MarkdownIt from 'markdown-it' 4 | import MarkdownItFootnote from 'markdown-it-footnote' 5 | import MarkdownItSub from 'markdown-it-sub' 6 | import * as IncrementalDOM from 'incremental-dom' 7 | import MarkdownItIncrementalDOM from '../src/markdown-it-incremental-dom' 8 | 9 | describe('Renderer', () => { 10 | const md = (opts = {}) => { 11 | const instance = MarkdownIt(opts).use( 12 | MarkdownItIncrementalDOM, 13 | IncrementalDOM 14 | ) 15 | 16 | // returns rendered string 17 | const renderWithIncrementalDOM = (func, args) => { 18 | IncrementalDOM.patch(document.body, func(...args)) 19 | return document.body.innerHTML 20 | } 21 | 22 | instance.idom = (...args) => 23 | renderWithIncrementalDOM(instance.renderToIncrementalDOM, args) 24 | 25 | instance.iidom = (...args) => 26 | renderWithIncrementalDOM(instance.renderInlineToIncrementalDOM, args) 27 | 28 | return instance 29 | } 30 | 31 | const has = q => expect(document.querySelector(q)).toBeTruthy() 32 | 33 | context('with rendering image (tag + attributes)', () => { 34 | it('renders correctly', () =>
69 | expect(md().iidom('This is `Inline` rendering')).toBe(
70 | 'This is <b>Inline</b> rendering'
71 | ))
72 | )
73 |
74 | context('with rendering hardbreak (overrided rule)', () =>
75 | it('renders
correctly', () =>
76 | expect(md().iidom('hardbreak \ntest')).toBe('hardbreak
test'))
77 | )
78 |
79 | context('with html option', () => {
80 | const markdown = 'test'
81 |
82 | context('with false', () =>
83 | it('sanitizes HTML tag', () =>
84 | expect(md({ html: false }).idom(markdown)).toBe(
85 | '<b class="test">test</b>
'
86 | ))
87 | )
88 |
89 | context('with true', () => {
90 | it('renders HTML tag', () =>
91 | expect(md({ html: true }).idom(markdown)).toBe(`${markdown}
`))
92 |
93 | it('renders empty element without slash', () => {
94 | md({ html: true }).idom('
')
95 | has('hr + img')
96 | })
97 |
98 | it('renders invalid HTML', () => {
99 | md({ html: true }).idom('inva')
100 | expect(document.querySelector('div').textContent).toBe('inva')
101 | })
102 |
103 | it('renders invalid nesting HTML', () => {
104 | md({ html: true }).idom('\n')
105 | has('table > tr')
106 | })
107 |
108 | it('renders inline SVG', () => {
109 | const svg = stripIndents`
110 |
119 | `
120 |
121 | md({ html: true }).idom(svg)
122 |
123 | has('svg[xmlns][viewBox="0 0 32 32"]')
124 | has('svg > defs > linearGradient#gradation')
125 | has('#gradation > stop + stop')
126 | has('svg > rect[x][y][width][height][fill]')
127 | })
128 | })
129 | })
130 |
131 | context('with breaks option', () => {
132 | const markdown = 'break\ntest'
133 |
134 | context('with false', () => {
135 | it('keeps breaks as text', () => {
136 | md({ breaks: false }).idom(markdown)
137 |
138 | expect(document.querySelector('br')).toBeFalsy()
139 | expect(document.querySelector('p').textContent).toBe(markdown)
140 | })
141 | })
142 |
143 | context('with true', () => {
144 | it('renders
on breaks', () => {
145 | md({ breaks: true }).idom(markdown)
146 | has('br')
147 | })
148 | })
149 | })
150 |
151 | context('with overriden renderers', () => {
152 | context('when HTML parser is used only in opening element', () => {
153 | it('renders correctly', () => {
154 | const markdown = md()
155 | markdown.renderer.rules.paragraph_open = () => ''
156 | markdown.idom('Render correctly')
157 |
158 | expect(document.querySelector('p.overriden')).toBeTruthy()
159 | })
160 | })
161 | })
162 |
163 | context('with other plugins', () => {
164 | context('when markdown-it-sub is injected (simple plugin)', () => {
165 | const instance = md().use(MarkdownItSub)
166 |
167 | it('renders tag correctly', () =>
168 | expect(instance.idom('H~2~O')).toBe('H2O
'))
169 | })
170 |
171 | context(
172 | 'when markdown-it-footnote is injected (overriding renderer rules)',
173 | () => {
174 | const instance = md().use(MarkdownItFootnote)
175 | const markdown = 'Footnote[^1]\n\n[^1]: test'
176 |
177 | it('renders footnote correctly', () => {
178 | instance.idom(markdown)
179 |
180 | has('sup.footnote-ref > a#fnref1[href="#fn1"]')
181 | has('hr.footnotes-sep')
182 | has('section.footnotes > ol.footnotes-list > li#fn1.footnote-item')
183 | has('#fn1 a.footnote-backref[href="#fnref1"]')
184 | })
185 | }
186 | )
187 | })
188 | })
189 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # markdown-it-incremental-dom
2 |
3 | [](https://travis-ci.org/yhatt/markdown-it-incremental-dom)
4 | [](https://coveralls.io/github/yhatt/markdown-it-incremental-dom?branch=master)
5 | [](https://www.npmjs.com/package/markdown-it-incremental-dom)
6 | [](./LICENSE)
7 |
8 | A [markdown-it](https://github.com/markdown-it/markdown-it) renderer plugin by using [Incremental DOM](https://github.com/google/incremental-dom).
9 |
10 | Let's see key features: **[https://yhatt.github.io/markdown-it-incremental-dom/](https://yhatt.github.io/markdown-it-incremental-dom/)** or [`docs.md`](docs/docs.md)
11 |
12 | [](https://yhatt.github.io/markdown-it-incremental-dom/)
13 |
14 | ## Requirement
15 |
16 | - [markdown-it](https://github.com/markdown-it/markdown-it) >= 4.0.0 (Recommend latest version >= 8.4.0, that this plugin use it)
17 | - [Incremental DOM](https://github.com/google/incremental-dom) >= 0.5.x
18 |
19 | ## Examples
20 |
21 | ### Node
22 |
23 | ```javascript
24 | import * as IncrementalDOM from 'incremental-dom'
25 | import MarkdownIt from 'markdown-it'
26 | import MarkdownItIncrementalDOM from 'markdown-it-incremental-dom'
27 |
28 | const md = new MarkdownIt().use(MarkdownItIncrementalDOM, IncrementalDOM)
29 |
30 | IncrementalDOM.patch(
31 | document.getElementById('target'),
32 | md.renderToIncrementalDOM('# Hello, Incremental DOM!')
33 | )
34 | ```
35 |
36 | ### Browser
37 |
38 | Define as `window.markdownitIncrementalDOM`.
39 |
40 | ```html
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
59 |
60 |
61 | ```
62 |
63 | #### CDN
64 |
65 | You can use [the recent version through CDN](https://cdn.jsdelivr.net/npm/markdown-it-incremental-dom@2/dist/markdown-it-incremental-dom.min.js) provides by [jsDelivr](https://www.jsdelivr.com/).
66 |
67 | ```html
68 |
69 | ```
70 |
71 | - **[Compressed (Recommend)](https://cdn.jsdelivr.net/npm/markdown-it-incremental-dom@2/dist/markdown-it-incremental-dom.min.js)**
72 | - [Uncompressed](https://cdn.jsdelivr.net/npm/markdown-it-incremental-dom@2/dist/markdown-it-incremental-dom.js)
73 |
74 | ## Installation
75 |
76 | We recommend using [yarn](https://yarnpkg.com/) to install.
77 |
78 | ```bash
79 | $ yarn add incremental-dom markdown-it
80 | $ yarn add markdown-it-incremental-dom
81 | ```
82 |
83 | If you wanna use npm, try this:
84 |
85 | ```bash
86 | $ npm install incremental-dom markdown-it --save
87 | $ npm install markdown-it-incremental-dom --save
88 | ```
89 |
90 | ## Usage
91 |
92 | When injecting this plugin by `.use()`, _you should pass Incremental DOM class as second argument._ (`window.IncrementalDOM` by default)
93 |
94 | ```javascript
95 | import * as IncrementalDOM from 'incremental-dom'
96 | import MarkdownIt from 'markdown-it'
97 | import MarkdownItIncrementalDOM from 'markdown-it-incremental-dom'
98 |
99 | const md = new MarkdownIt().use(MarkdownItIncrementalDOM, IncrementalDOM)
100 | ```
101 |
102 | If it is succeed, [2 new rendering methods](#rendering-methods) would be injected to instance.
103 |
104 | > **_TIPS:_** This plugin keeps default rendering methods [`render()`](https://markdown-it.github.io/markdown-it/#MarkdownIt.render) and [`renderInline()`](https://markdown-it.github.io/markdown-it/#MarkdownIt.renderInline).
105 |
106 | ### Option
107 |
108 | You can pass option object as third argument. See below:
109 |
110 | ```javascript
111 | new MarkdownIt().use(MarkdownItIncrementalDOM, IncrementalDOM, {
112 | incrementalizeDefaultRules: false,
113 | })
114 | ```
115 |
116 | - **`incrementalizeDefaultRules`**: For better performance, this plugin would override a few default renderer rules only when you calls injected methods. If the other plugins that override default rules have occurred any problem, You can disable overriding by setting `false`. _(`true` by default)_
117 |
118 | ### Rendering methods
119 |
120 | #### `MarkdownIt.renderToIncrementalDOM(src[, env])` => `Function`
121 |
122 | Similar to [`MarkdownIt.render(src[, env])`](https://markdown-it.github.io/markdown-it/#MarkdownIt.render) but _it returns a function for Incremental DOM_. It means doesn't render Markdown immediately.
123 |
124 | You must render to DOM by using [`IncrementalDOM.patch(node, description)`](http://google.github.io/incremental-dom/#api/patch). Please pass the returned function to the description argument. For example:
125 |
126 | ```javascript
127 | const node = document.getElementById('#target')
128 | const func = md.renderToIncrementalDOM('# Hello, Incremental DOM!')
129 |
130 | // It would render "Hello, Incremental DOM!
" to
131 | IncrementalDOM.patch(node, func)
132 | ```
133 |
134 | #### `MarkdownIt.renderInlineToIncrementalDOM(src[, env])` => `Function`
135 |
136 | Similar to `MarkdownIt.renderToIncrementalDOM` but it wraps [`MarkdownIt.renderInline(src[, env])`](https://markdown-it.github.io/markdown-it/#MarkdownIt.renderInline).
137 |
138 | ### Renderer property
139 |
140 | #### _get_ `MarkdownIt.IncrementalDOMRenderer` => [`Renderer`](https://markdown-it.github.io/markdown-it/#Renderer)
141 |
142 | Returns [`Renderer`](https://markdown-it.github.io/markdown-it/#Renderer) instance that includes Incremental DOM support.
143 |
144 | It will inject Incremental DOM features into the current state of [`MarkdownIt.renderer`](https://markdown-it.github.io/markdown-it/#MarkdownIt.prototype.renderer) at getting this property.
145 |
146 | > **_NOTE:_** This property is provided for the expert. Normally you should use `renderToIncrementalDOM()`.
147 | >
148 | > But it might be useful if you have to parse Markdown and operate tokens manually.
149 | >
150 | > ```javascript
151 | > const md = new MarkdownIt()
152 | > const tokens = md.parse('# Hello')
153 | >
154 | > // ...You can operate tokens here...
155 | >
156 | > const patch = md.IncrementalDOMRenderer.render(tokens, md.options)
157 | > IncrementalDOM.patch(document.body, patch)
158 | > ```
159 |
160 | ## Development
161 |
162 | ```bash
163 | $ git clone https://github.com/yhatt/markdown-it-incremental-dom
164 |
165 | $ yarn install
166 | $ yarn build
167 | ```
168 |
169 | ### Lint & Format
170 |
171 | ```bash
172 | $ yarn lint # Run ESLint
173 | $ yarn lint --fix # Fix lint
174 |
175 | $ yarn format:check # Run Prettier
176 | $ yarn format --write # Fix formatting by Prettier
177 | ```
178 |
179 | ### Publish to npm
180 |
181 | ```bash
182 | $ npm publish
183 | ```
184 |
185 | :warning: Use npm >= 5.0.0.
186 |
187 | ## Author
188 |
189 | Yuki Hattori ([@yhatt](https://github.com/yhatt/))
190 |
191 | ## License
192 |
193 | This plugin releases under the [MIT License](https://github.com/yhatt/markdown-it-incremental-dom/blob/master/LICENSE).
194 |
--------------------------------------------------------------------------------