├── .gitignore ├── test ├── cjs.js ├── test.mjs └── fixtures │ └── mark.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 mark from '../index.mjs' 6 | 7 | describe('markdown-it-mark', function () { 8 | const md = markdownit().use(mark) 9 | 10 | generate(fileURLToPath(new URL('fixtures/mark.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-mark", 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 | "mark" 10 | ], 11 | "repository": "markdown-it/markdown-it-mark", 12 | "license": "MIT", 13 | "main": "dist/index.cjs.js", 14 | "module": "index.mjs", 15 | "exports": { 16 | ".": { 17 | "require": "./dist/index.cjs.js", 18 | "import": "./index.mjs" 19 | }, 20 | "./*": { 21 | "require": "./*", 22 | "import": "./*" 23 | } 24 | }, 25 | "files": [ 26 | "index.mjs", 27 | "lib/", 28 | "dist/" 29 | ], 30 | "scripts": { 31 | "lint": "eslint .", 32 | "build": "rollup -c", 33 | "test": "npm run lint && npm run build && c8 --exclude dist --exclude test -r text -r html -r lcov mocha", 34 | "prepublishOnly": "npm run lint && npm run build" 35 | }, 36 | "devDependencies": { 37 | "@rollup/plugin-babel": "^6.0.4", 38 | "@rollup/plugin-node-resolve": "^15.2.3", 39 | "@rollup/plugin-terser": "^0.4.4", 40 | "c8": "^8.0.1", 41 | "eslint": "^8.55.0", 42 | "eslint-config-standard": "^17.1.0", 43 | "markdown-it": "^13.0.2", 44 | "markdown-it-testgen": "^0.1.6", 45 | "mocha": "^10.2.0", 46 | "rollup": "^4.6.1" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # markdown-it-mark 2 | 3 | [![CI](https://github.com/markdown-it/markdown-it-mark/workflows/CI/badge.svg?branch=master)](https://github.com/markdown-it/markdown-it-mark/actions) 4 | [![NPM version](https://img.shields.io/npm/v/markdown-it-mark.svg?style=flat)](https://www.npmjs.org/package/markdown-it-mark) 5 | [![Coverage Status](https://img.shields.io/coveralls/markdown-it/markdown-it-mark/master.svg?style=flat)](https://coveralls.io/r/markdown-it/markdown-it-mark?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 | `==marked==` => `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-mark --save 22 | bower install markdown-it-mark --save 23 | ``` 24 | 25 | ## Use 26 | 27 | ```js 28 | var md = require('markdown-it')() 29 | .use(require('markdown-it-mark')); 30 | 31 | md.render('==marked==') // => '

marked

' 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.markdownitMark`. 36 | 37 | 38 | ## License 39 | 40 | [MIT](https://github.com/markdown-it/markdown-it-mark/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/mark.txt: -------------------------------------------------------------------------------- 1 | . 2 | ==Mark== 3 | . 4 |

Mark

5 | . 6 | 7 | . 8 | x ====foo== bar== 9 | . 10 |

x foo bar

11 | . 12 | 13 | . 14 | x ==foo ==bar==== 15 | . 16 |

x foo bar

17 | . 18 | 19 | . 20 | x ====foo==== 21 | . 22 |

x foo

23 | . 24 | 25 | . 26 | x ===foo=== 27 | . 28 |

x =foo=

29 | . 30 | 31 | Marks have the same priority as emphases: 32 | 33 | . 34 | **==test**== 35 | 36 | ==**test==** 37 | . 38 |

==test==

39 |

**test**

40 | . 41 | 42 | 43 | Marks have the same priority as emphases with respect to links 44 | . 45 | [==link]()== 46 | 47 | ==[link==]() 48 | . 49 |

==link==

50 |

==link==

51 | . 52 | 53 | 54 | Marks have the same priority as emphases with respect to backticks 55 | . 56 | ==`code==` 57 | 58 | `==code`== 59 | . 60 |

==code==

61 |

==code==

62 | . 63 | 64 | 65 | Nested marks 66 | . 67 | ==foo ==bar== baz== 68 | . 69 |

foo bar baz

70 | . 71 | 72 | 73 | Nested marks with emphasis 74 | . 75 | ==f **o ==o b== a** r== 76 | . 77 |

f o o b a r

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

foo == bar == baz

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

==test 102 | == a

103 |

== 104 | test==

105 |

== 106 | test

107 | . 108 | 109 | 110 | . 111 | x ==a ==foo===========bar== b== 112 | 113 | x ==a ==foo============bar== b== 114 | . 115 |

x a foo===bar b

116 |

x a foo====bar b

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

a=="foo"==

126 | . 127 | 128 | 129 | Should parse delimiters inside links: 130 | . 131 | [==foo==]() 132 | . 133 |

foo

134 | . 135 | 136 | 137 | Regression test for markdown-it/markdown-it#742: 138 | . 139 | -====;====== 140 | . 141 |

-;==

142 | . 143 | -------------------------------------------------------------------------------- /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 !== 0x3D/* = */) { 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 | const loneMarkers = [] 50 | const max = delimiters.length 51 | 52 | for (let i = 0; i < max; i++) { 53 | const startDelim = delimiters[i] 54 | 55 | if (startDelim.marker !== 0x3D/* = */) { 56 | continue 57 | } 58 | 59 | if (startDelim.end === -1) { 60 | continue 61 | } 62 | 63 | const endDelim = delimiters[startDelim.end] 64 | 65 | const token_o = state.tokens[startDelim.token] 66 | token_o.type = 'mark_open' 67 | token_o.tag = 'mark' 68 | token_o.nesting = 1 69 | token_o.markup = '==' 70 | token_o.content = '' 71 | 72 | const token_c = state.tokens[endDelim.token] 73 | token_c.type = 'mark_close' 74 | token_c.tag = 'mark' 75 | token_c.nesting = -1 76 | token_c.markup = '==' 77 | token_c.content = '' 78 | 79 | if (state.tokens[endDelim.token - 1].type === 'text' && 80 | state.tokens[endDelim.token - 1].content === '=') { 81 | loneMarkers.push(endDelim.token - 1) 82 | } 83 | } 84 | 85 | // If a marker sequence has an odd number of characters, it's splitted 86 | // like this: `~~~~~` -> `~` + `~~` + `~~`, leaving one marker at the 87 | // start of the sequence. 88 | // 89 | // So, we have to move all those markers after subsequent s_close tags. 90 | // 91 | while (loneMarkers.length) { 92 | const i = loneMarkers.pop() 93 | let j = i + 1 94 | 95 | while (j < state.tokens.length && state.tokens[j].type === 'mark_close') { 96 | j++ 97 | } 98 | 99 | j-- 100 | 101 | if (i !== j) { 102 | const token = state.tokens[j] 103 | state.tokens[j] = state.tokens[i] 104 | state.tokens[i] = token 105 | } 106 | } 107 | } 108 | 109 | md.inline.ruler.before('emphasis', 'mark', tokenize) 110 | md.inline.ruler2.before('emphasis', 'mark', function (state) { 111 | let curr 112 | const tokens_meta = state.tokens_meta 113 | const max = (state.tokens_meta || []).length 114 | 115 | postProcess(state, state.delimiters) 116 | 117 | for (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 | --------------------------------------------------------------------------------