├── .gitignore ├── test ├── cjs.js ├── test.mjs └── fixtures │ └── ins.txt ├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── .eslintrc.yml ├── CHANGELOG.md ├── LICENSE ├── package.json ├── README.md ├── rollup.config.mjs └── index.mjs /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | coverage/ 3 | dist/ 4 | -------------------------------------------------------------------------------- /test/cjs.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /* eslint-env mocha */ 3 | 4 | const assert = require('node:assert') 5 | const fn = require('../') 6 | 7 | describe('CJS', () => { 8 | it('require', () => { 9 | assert.ok(typeof fn === 'function') 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | schedule: 6 | interval: daily 7 | 8 | - package-ecosystem: npm 9 | directory: / 10 | schedule: 11 | interval: daily 12 | allow: 13 | - dependency-type: production 14 | -------------------------------------------------------------------------------- /test/test.mjs: -------------------------------------------------------------------------------- 1 | import { fileURLToPath } from 'node:url' 2 | import markdownit from 'markdown-it' 3 | import generate from 'markdown-it-testgen' 4 | 5 | import ins from '../index.mjs' 6 | 7 | describe('markdown-it-ins', function () { 8 | const md = markdownit().use(ins) 9 | 10 | generate(fileURLToPath(new URL('fixtures/ins.txt', import.meta.url)), md) 11 | }) 12 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | extends: standard 2 | 3 | overrides: 4 | - 5 | files: [ '*.mjs' ] 6 | rules: 7 | no-restricted-globals: [ 2, require, __dirname ] 8 | - 9 | files: [ 'test/**' ] 10 | env: { mocha: true } 11 | - 12 | files: [ 'lib/**', 'index.mjs' ] 13 | parserOptions: { ecmaVersion: 2015 } 14 | 15 | ignorePatterns: 16 | - demo/ 17 | - dist/ 18 | - benchmark/extra/ 19 | 20 | rules: 21 | camelcase: 0 22 | no-multi-spaces: 0 23 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | schedule: 7 | - cron: '0 0 * * 3' 8 | 9 | jobs: 10 | test: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | strategy: 15 | matrix: 16 | node-version: [ '18' ] 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | 21 | - name: Use Node.js ${{ matrix.node-version }} 22 | uses: actions/setup-node@v4 23 | with: 24 | node-version: ${{ matrix.node-version }} 25 | 26 | - run: npm install 27 | 28 | - name: Test 29 | run: npm test 30 | 31 | - name: Upload coverage report to coveralls.io 32 | uses: coverallsapp/github-action@master 33 | with: 34 | github-token: ${{ secrets.GITHUB_TOKEN }} 35 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 4.0.0 / 2023-12-05 2 | ------------------ 3 | 4 | - Rewrite to ESM. 5 | - Remove `dist/` from repo (build on package publish). 6 | 7 | 8 | 3.0.1 / 2020-12-20 9 | ------------------ 10 | 11 | - Fix incorrect parsing of long (4+ length) markers. 12 | - Upgrade build process. 13 | 14 | 15 | 3.0.0 / 2019-09-11 16 | ------------------ 17 | 18 | - Markdown-it 10.0.0 support + related fixes in pairs parse. 19 | - uglify-js => terser. 20 | 21 | 22 | 2.0.0 / 2015-10-05 23 | ------------------ 24 | 25 | - Markdown-it 5.0.0 support. Use 1.x version for 4.x. 26 | 27 | 28 | 1.0.0 / 2015-03-12 29 | ------------------ 30 | 31 | - Markdown-it 4.0.0 support. Use previous version for 2.x-3.x. 32 | 33 | 34 | 0.1.0 / 2015-01-04 35 | ------------------ 36 | 37 | - First release. 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2015 Vitaly Puzrin, Alex Kocharin. 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "markdown-it-ins", 3 | "version": "4.0.0", 4 | "description": " tag for markdown-it markdown parser.", 5 | "keywords": [ 6 | "markdown-it-plugin", 7 | "markdown-it", 8 | "markdown", 9 | "insert", 10 | "ins" 11 | ], 12 | "repository": "markdown-it/markdown-it-ins", 13 | "license": "MIT", 14 | "main": "dist/index.cjs.js", 15 | "module": "index.mjs", 16 | "exports": { 17 | ".": { 18 | "require": "./dist/index.cjs.js", 19 | "import": "./index.mjs" 20 | }, 21 | "./*": { 22 | "require": "./*", 23 | "import": "./*" 24 | } 25 | }, 26 | "files": [ 27 | "index.mjs", 28 | "lib/", 29 | "dist/" 30 | ], 31 | "scripts": { 32 | "lint": "eslint .", 33 | "build": "rollup -c", 34 | "test": "npm run lint && npm run build && c8 --exclude dist --exclude test -r text -r html -r lcov mocha", 35 | "prepublishOnly": "npm run lint && npm run build" 36 | }, 37 | "devDependencies": { 38 | "@rollup/plugin-babel": "^6.0.4", 39 | "@rollup/plugin-node-resolve": "^15.2.3", 40 | "@rollup/plugin-terser": "^0.4.4", 41 | "c8": "^8.0.1", 42 | "eslint": "^8.55.0", 43 | "eslint-config-standard": "^17.1.0", 44 | "markdown-it": "^13.0.2", 45 | "markdown-it-testgen": "^0.1.6", 46 | "mocha": "^10.2.0", 47 | "rollup": "^4.6.1" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # markdown-it-ins 2 | 3 | [![CI](https://github.com/markdown-it/markdown-it-ins/workflows/CI/badge.svg?branch=master)](https://github.com/markdown-it/markdown-it-ins/actions) 4 | [![NPM version](https://img.shields.io/npm/v/markdown-it-ins.svg?style=flat)](https://www.npmjs.org/package/markdown-it-ins) 5 | [![Coverage Status](https://img.shields.io/coveralls/markdown-it/markdown-it-ins/master.svg?style=flat)](https://coveralls.io/r/markdown-it/markdown-it-ins?branch=master) 6 | 7 | > `` tag plugin for [markdown-it](https://github.com/markdown-it/markdown-it) markdown parser. 8 | 9 | __v3.+ requires `markdown-it` v10.+, see changelog.__ 10 | 11 | `++inserted++` => `inserted` 12 | 13 | Markup uses the same conditions as CommonMark [emphasis](http://spec.commonmark.org/0.15/#emphasis-and-strong-emphasis). 14 | 15 | 16 | ## Install 17 | 18 | node.js, browser: 19 | 20 | ```bash 21 | npm install markdown-it-ins --save 22 | bower install markdown-it-ins --save 23 | ``` 24 | 25 | ## Use 26 | 27 | ```js 28 | var md = require('markdown-it')() 29 | .use(require('markdown-it-ins')); 30 | 31 | md.render('++inserted++') // => '

inserted

' 32 | ``` 33 | 34 | _Differences in browser._ If you load script directly into the page, without 35 | package system, module will add itself globally as `window.markdownitIns`. 36 | 37 | 38 | ## License 39 | 40 | [MIT](https://github.com/markdown-it/markdown-it-ins/blob/master/LICENSE) 41 | -------------------------------------------------------------------------------- /rollup.config.mjs: -------------------------------------------------------------------------------- 1 | import resolve from '@rollup/plugin-node-resolve' 2 | import terser from '@rollup/plugin-terser' 3 | import { babel } from '@rollup/plugin-babel' 4 | import { readFileSync } from 'fs' 5 | 6 | const pkg = JSON.parse(readFileSync(new URL('package.json', import.meta.url))) 7 | 8 | function globalName (name) { 9 | const parts = name.split('-') 10 | for (let i = 2; i < parts.length; i++) { 11 | parts[i] = parts[i][0].toUpperCase() + parts[i].slice(1) 12 | } 13 | return parts.join('') 14 | } 15 | 16 | const config_umd_full = { 17 | input: 'index.mjs', 18 | output: [ 19 | { 20 | file: `dist/${pkg.name}.js`, 21 | format: 'umd', 22 | name: globalName(pkg.name), 23 | plugins: [ 24 | // Here terser is used only to force ascii output 25 | terser({ 26 | mangle: false, 27 | compress: false, 28 | format: { comments: 'all', beautify: true, ascii_only: true, indent_level: 2 } 29 | }) 30 | ] 31 | }, 32 | { 33 | file: `dist/${pkg.name}.min.js`, 34 | format: 'umd', 35 | name: globalName(pkg.name), 36 | plugins: [ 37 | terser({ 38 | format: { ascii_only: true } 39 | }) 40 | ] 41 | } 42 | ], 43 | plugins: [ 44 | resolve(), 45 | babel({ babelHelpers: 'bundled' }), 46 | { 47 | banner () { 48 | return `/*! ${pkg.name} ${pkg.version} https://github.com/${pkg.repository} @license ${pkg.license} */` 49 | } 50 | } 51 | ] 52 | } 53 | 54 | const config_cjs_no_deps = { 55 | input: 'index.mjs', 56 | output: { 57 | file: 'dist/index.cjs.js', 58 | format: 'cjs' 59 | }, 60 | external: Object.keys(pkg.dependencies || {}), 61 | plugins: [ 62 | resolve(), 63 | babel({ babelHelpers: 'bundled' }) 64 | ] 65 | } 66 | 67 | let config = [ 68 | config_umd_full, 69 | config_cjs_no_deps 70 | ] 71 | 72 | if (process.env.CJS_ONLY) config = [config_cjs_no_deps] 73 | 74 | export default config 75 | -------------------------------------------------------------------------------- /test/fixtures/ins.txt: -------------------------------------------------------------------------------- 1 | . 2 | ++Insert++ 3 | . 4 |

Insert

5 | . 6 | 7 | 8 | . 9 | x ++++foo++ bar++ 10 | . 11 |

x foo bar

12 | . 13 | 14 | . 15 | x ++foo ++bar++++ 16 | . 17 |

x foo bar

18 | . 19 | 20 | . 21 | x ++++foo++++ 22 | . 23 |

x foo

24 | . 25 | 26 | . 27 | x +++foo+++ 28 | . 29 |

x +foo+

30 | . 31 | 32 | 33 | Inserts have the same priority as emphases: 34 | . 35 | **++test**++ 36 | 37 | ++**test++** 38 | . 39 |

++test++

40 |

**test**

41 | . 42 | 43 | 44 | Inserts have the same priority as emphases with respect to links: 45 | . 46 | [++link]()++ 47 | 48 | ++[link++]() 49 | . 50 |

++link++

51 |

++link++

52 | . 53 | 54 | 55 | Inserts have the same priority as emphases with respect to backticks: 56 | . 57 | ++`code++` 58 | 59 | `++code`++ 60 | . 61 |

++code++

62 |

++code++

63 | . 64 | 65 | 66 | Nested inserts: 67 | . 68 | ++foo ++bar++ baz++ 69 | . 70 |

foo bar baz

71 | . 72 | 73 | 74 | Nested inserts with emphasis: 75 | . 76 | ++f **o ++o b++ a** r++ 77 | . 78 |

f o o b a r

79 | . 80 | 81 | 82 | Should not have a whitespace between text and "++": 83 | . 84 | foo ++ bar ++ baz 85 | . 86 |

foo ++ bar ++ baz

87 | . 88 | 89 | 90 | Newline should be considered a whitespace: 91 | . 92 | ++test 93 | ++ 94 | 95 | ++ 96 | test++ 97 | 98 | ++ 99 | test 100 | ++ 101 | . 102 |

++test 103 | ++

104 |

++ 105 | test++

106 |

++ 107 | test 108 | ++

109 | . 110 | 111 | 112 | . 113 | x ++a ++foo+++++++++++bar++ b++ 114 | 115 | x ++a ++foo++++++++++++bar++ b++ 116 | . 117 |

x a foo+++bar b

118 |

x a foo++++bar b

119 | . 120 | 121 | 122 | From CommonMark test suite, replacing `**` with our marker: 123 | 124 | . 125 | a++"foo"++ 126 | . 127 |

a++"foo"++

128 | . 129 | 130 | 131 | Should parse delimiters inside links: 132 | . 133 | [++foo++]() 134 | . 135 |

foo

136 | . 137 | 138 | 139 | Regression test for markdown-it/markdown-it#742: 140 | . 141 | -++++;++++++ 142 | . 143 |

-;++

144 | . 145 | -------------------------------------------------------------------------------- /index.mjs: -------------------------------------------------------------------------------- 1 | export default function ins_plugin (md) { 2 | // Insert each marker as a separate text token, and add it to delimiter list 3 | // 4 | function tokenize (state, silent) { 5 | const start = state.pos 6 | const marker = state.src.charCodeAt(start) 7 | 8 | if (silent) { return false } 9 | 10 | if (marker !== 0x2B/* + */) { return false } 11 | 12 | const scanned = state.scanDelims(state.pos, true) 13 | let len = scanned.length 14 | const ch = String.fromCharCode(marker) 15 | 16 | if (len < 2) { return false } 17 | 18 | if (len % 2) { 19 | const token = state.push('text', '', 0) 20 | token.content = ch 21 | len-- 22 | } 23 | 24 | for (let i = 0; i < len; i += 2) { 25 | const token = state.push('text', '', 0) 26 | token.content = ch + ch 27 | 28 | if (!scanned.can_open && !scanned.can_close) { continue } 29 | 30 | state.delimiters.push({ 31 | marker, 32 | length: 0, // disable "rule of 3" length checks meant for emphasis 33 | jump: i / 2, // 1 delimiter = 2 characters 34 | token: state.tokens.length - 1, 35 | end: -1, 36 | open: scanned.can_open, 37 | close: scanned.can_close 38 | }) 39 | } 40 | 41 | state.pos += scanned.length 42 | 43 | return true 44 | } 45 | 46 | // Walk through delimiter list and replace text tokens with tags 47 | // 48 | function postProcess (state, delimiters) { 49 | let token 50 | const loneMarkers = [] 51 | const max = delimiters.length 52 | 53 | for (let i = 0; i < max; i++) { 54 | const startDelim = delimiters[i] 55 | 56 | if (startDelim.marker !== 0x2B/* + */) { 57 | continue 58 | } 59 | 60 | if (startDelim.end === -1) { 61 | continue 62 | } 63 | 64 | const endDelim = delimiters[startDelim.end] 65 | 66 | token = state.tokens[startDelim.token] 67 | token.type = 'ins_open' 68 | token.tag = 'ins' 69 | token.nesting = 1 70 | token.markup = '++' 71 | token.content = '' 72 | 73 | token = state.tokens[endDelim.token] 74 | token.type = 'ins_close' 75 | token.tag = 'ins' 76 | token.nesting = -1 77 | token.markup = '++' 78 | token.content = '' 79 | 80 | if (state.tokens[endDelim.token - 1].type === 'text' && 81 | state.tokens[endDelim.token - 1].content === '+') { 82 | loneMarkers.push(endDelim.token - 1) 83 | } 84 | } 85 | 86 | // If a marker sequence has an odd number of characters, it's splitted 87 | // like this: `~~~~~` -> `~` + `~~` + `~~`, leaving one marker at the 88 | // start of the sequence. 89 | // 90 | // So, we have to move all those markers after subsequent s_close tags. 91 | // 92 | while (loneMarkers.length) { 93 | const i = loneMarkers.pop() 94 | let j = i + 1 95 | 96 | while (j < state.tokens.length && state.tokens[j].type === 'ins_close') { 97 | j++ 98 | } 99 | 100 | j-- 101 | 102 | if (i !== j) { 103 | token = state.tokens[j] 104 | state.tokens[j] = state.tokens[i] 105 | state.tokens[i] = token 106 | } 107 | } 108 | } 109 | 110 | md.inline.ruler.before('emphasis', 'ins', tokenize) 111 | md.inline.ruler2.before('emphasis', 'ins', function (state) { 112 | const tokens_meta = state.tokens_meta 113 | const max = (state.tokens_meta || []).length 114 | 115 | postProcess(state, state.delimiters) 116 | 117 | for (let curr = 0; curr < max; curr++) { 118 | if (tokens_meta[curr] && tokens_meta[curr].delimiters) { 119 | postProcess(state, tokens_meta[curr].delimiters) 120 | } 121 | } 122 | }) 123 | }; 124 | --------------------------------------------------------------------------------