├── .gitignore ├── test ├── cjs.js ├── test.mjs └── fixtures │ └── deflist.txt ├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── .eslintrc.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── package.json ├── rollup.config.mjs └── index.mjs /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | coverage/ 3 | dist/ 4 | *.log 5 | -------------------------------------------------------------------------------- /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 deflist from '../index.mjs' 6 | 7 | /* eslint-env mocha */ 8 | 9 | describe('markdown-it-deflist', function () { 10 | const md = markdownit().use(deflist) 11 | 12 | generate(fileURLToPath(new URL('fixtures/deflist.txt', import.meta.url)), md) 13 | }) 14 | -------------------------------------------------------------------------------- /.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 | 3.0.0 / 2023-12-05 2 | ------------------ 3 | 4 | - Rewrite to ESM. 5 | - Remove `dist/` from repo (build on package publish). 6 | 7 | 8 | 2.1.0 / 2020-09-10 9 | ------------------ 10 | 11 | - Deps bump. 12 | - Configs cleanup. 13 | - Fixed #8. 14 | 15 | 16 | 2.0.3 / 2017-07-12 17 | ------------------ 18 | 19 | - Fix freezing on blockquotes inside definitions, #5. 20 | 21 | 22 | 2.0.2 / 2017-05-22 23 | ------------------ 24 | 25 | - Missed browser files rebuild in prev release. 26 | 27 | 28 | 2.0.1 / 2016-02-23 29 | ------------------ 30 | 31 | - Fixed tightness, #2. 32 | 33 | 34 | 2.0.0 / 2015-10-05 35 | ------------------ 36 | 37 | - Markdown-it 5.0.0 support. Use 1.x version for 4.x. 38 | 39 | 40 | 1.0.0 / 2015-03-12 41 | ------------------ 42 | 43 | - Markdown-it 4.0.0 support. Use previous version for 2.x-3.x. 44 | 45 | 46 | 0.1.0 / 2015-01-04 47 | ------------------ 48 | 49 | - First release. 50 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # markdown-it-deflist 2 | 3 | [](https://github.com/markdown-it/markdown-it-deflist/actions/workflows/ci.yml) 4 | [](https://www.npmjs.org/package/markdown-it-deflist) 5 | [](https://coveralls.io/r/markdown-it/markdown-it-deflist?branch=master) 6 | 7 | > Definition list (`
Definition 1
21 |Definition 2
25 |{ some code, part of Definition 2 }
26 |
27 | Third paragraph of definition 2.
28 |Definition 46 | with lazy continuation.
47 |Second paragraph of the definition.
48 |code block
86 |
87 | Non-term 1 101 | :
102 |Non-term 2 103 | :
104 | . 105 | 106 | 107 | List is tight iff all dts are tight: 108 | . 109 | Term 1 110 | : foo 111 | : bar 112 | 113 | Term 2 114 | : foo 115 | 116 | : bar 117 | . 118 |foo
122 |bar
125 |foo
129 |bar
132 |foo
150 |bar 151 | Term 2
152 |foo
155 |: just a paragraph with a colon
167 | . 168 | 169 | Nested definition lists: 170 | 171 | . 172 | test 173 | : foo 174 | : bar 175 | : baz 176 | : bar 177 | : foo 178 | . 179 |code block
207 |
208 | 223 |225 |bar
224 |
test
237 | . 238 | 239 | 240 | Coverage, 2 blank lines 241 | . 242 | test 243 | 244 | 245 | . 246 |test
247 | . 248 | -------------------------------------------------------------------------------- /index.mjs: -------------------------------------------------------------------------------- 1 | // Process definition lists 2 | // 3 | export default function deflist_plugin (md) { 4 | const isSpace = md.utils.isSpace 5 | 6 | // Search `[:~][\n ]`, returns next pos after marker on success 7 | // or -1 on fail. 8 | function skipMarker (state, line) { 9 | let start = state.bMarks[line] + state.tShift[line] 10 | const max = state.eMarks[line] 11 | 12 | if (start >= max) { return -1 } 13 | 14 | // Check bullet 15 | const marker = state.src.charCodeAt(start++) 16 | if (marker !== 0x7E/* ~ */ && marker !== 0x3A/* : */) { return -1 } 17 | 18 | const pos = state.skipSpaces(start) 19 | 20 | // require space after ":" 21 | if (start === pos) { return -1 } 22 | 23 | // no empty definitions, e.g. " : " 24 | if (pos >= max) { return -1 } 25 | 26 | return start 27 | } 28 | 29 | function markTightParagraphs (state, idx) { 30 | const level = state.level + 2 31 | 32 | for (let i = idx + 2, l = state.tokens.length - 2; i < l; i++) { 33 | if (state.tokens[i].level === level && state.tokens[i].type === 'paragraph_open') { 34 | state.tokens[i + 2].hidden = true 35 | state.tokens[i].hidden = true 36 | i += 2 37 | } 38 | } 39 | } 40 | 41 | function deflist (state, startLine, endLine, silent) { 42 | if (silent) { 43 | // quirk: validation mode validates a dd block only, not a whole deflist 44 | if (state.ddIndent < 0) { return false } 45 | return skipMarker(state, startLine) >= 0 46 | } 47 | 48 | let nextLine = startLine + 1 49 | if (nextLine >= endLine) { return false } 50 | 51 | if (state.isEmpty(nextLine)) { 52 | nextLine++ 53 | if (nextLine >= endLine) { return false } 54 | } 55 | 56 | if (state.sCount[nextLine] < state.blkIndent) { return false } 57 | let contentStart = skipMarker(state, nextLine) 58 | if (contentStart < 0) { return false } 59 | 60 | // Start list 61 | const listTokIdx = state.tokens.length 62 | let tight = true 63 | 64 | const token_dl_o = state.push('dl_open', 'dl', 1) 65 | const listLines = [startLine, 0] 66 | token_dl_o.map = listLines 67 | 68 | // 69 | // Iterate list items 70 | // 71 | 72 | let dtLine = startLine 73 | let ddLine = nextLine 74 | 75 | // One definition list can contain multiple DTs, 76 | // and one DT can be followed by multiple DDs. 77 | // 78 | // Thus, there is two loops here, and label is 79 | // needed to break out of the second one 80 | // 81 | /* eslint no-labels:0,block-scoped-var:0 */ 82 | OUTER: 83 | for (;;) { 84 | let prevEmptyEnd = false 85 | 86 | const token_dt_o = state.push('dt_open', 'dt', 1) 87 | token_dt_o.map = [dtLine, dtLine] 88 | 89 | const token_i = state.push('inline', '', 0) 90 | token_i.map = [dtLine, dtLine] 91 | token_i.content = state.getLines(dtLine, dtLine + 1, state.blkIndent, false).trim() 92 | token_i.children = [] 93 | 94 | state.push('dt_close', 'dt', -1) 95 | 96 | for (;;) { 97 | const token_dd_o = state.push('dd_open', 'dd', 1) 98 | const itemLines = [nextLine, 0] 99 | token_dd_o.map = itemLines 100 | 101 | let pos = contentStart 102 | const max = state.eMarks[ddLine] 103 | let offset = state.sCount[ddLine] + contentStart - (state.bMarks[ddLine] + state.tShift[ddLine]) 104 | 105 | while (pos < max) { 106 | const ch = state.src.charCodeAt(pos) 107 | 108 | if (isSpace(ch)) { 109 | if (ch === 0x09) { 110 | offset += 4 - offset % 4 111 | } else { 112 | offset++ 113 | } 114 | } else { 115 | break 116 | } 117 | 118 | pos++ 119 | } 120 | 121 | contentStart = pos 122 | 123 | const oldTight = state.tight 124 | const oldDDIndent = state.ddIndent 125 | const oldIndent = state.blkIndent 126 | const oldTShift = state.tShift[ddLine] 127 | const oldSCount = state.sCount[ddLine] 128 | const oldParentType = state.parentType 129 | state.blkIndent = state.ddIndent = state.sCount[ddLine] + 2 130 | state.tShift[ddLine] = contentStart - state.bMarks[ddLine] 131 | state.sCount[ddLine] = offset 132 | state.tight = true 133 | state.parentType = 'deflist' 134 | 135 | state.md.block.tokenize(state, ddLine, endLine, true) 136 | 137 | // If any of list item is tight, mark list as tight 138 | if (!state.tight || prevEmptyEnd) { 139 | tight = false 140 | } 141 | // Item become loose if finish with empty line, 142 | // but we should filter last element, because it means list finish 143 | prevEmptyEnd = (state.line - ddLine) > 1 && state.isEmpty(state.line - 1) 144 | 145 | state.tShift[ddLine] = oldTShift 146 | state.sCount[ddLine] = oldSCount 147 | state.tight = oldTight 148 | state.parentType = oldParentType 149 | state.blkIndent = oldIndent 150 | state.ddIndent = oldDDIndent 151 | 152 | state.push('dd_close', 'dd', -1) 153 | 154 | itemLines[1] = nextLine = state.line 155 | 156 | if (nextLine >= endLine) { break OUTER } 157 | 158 | if (state.sCount[nextLine] < state.blkIndent) { break OUTER } 159 | contentStart = skipMarker(state, nextLine) 160 | if (contentStart < 0) { break } 161 | 162 | ddLine = nextLine 163 | 164 | // go to the next loop iteration: 165 | // insert DD tag and repeat checking 166 | } 167 | 168 | if (nextLine >= endLine) { break } 169 | dtLine = nextLine 170 | 171 | if (state.isEmpty(dtLine)) { break } 172 | if (state.sCount[dtLine] < state.blkIndent) { break } 173 | 174 | ddLine = dtLine + 1 175 | if (ddLine >= endLine) { break } 176 | if (state.isEmpty(ddLine)) { ddLine++ } 177 | if (ddLine >= endLine) { break } 178 | 179 | if (state.sCount[ddLine] < state.blkIndent) { break } 180 | contentStart = skipMarker(state, ddLine) 181 | if (contentStart < 0) { break } 182 | 183 | // go to the next loop iteration: 184 | // insert DT and DD tags and repeat checking 185 | } 186 | 187 | // Finilize list 188 | state.push('dl_close', 'dl', -1) 189 | 190 | listLines[1] = nextLine 191 | 192 | state.line = nextLine 193 | 194 | // mark paragraphs tight if needed 195 | if (tight) { 196 | markTightParagraphs(state, listTokIdx) 197 | } 198 | 199 | return true 200 | } 201 | 202 | md.block.ruler.before('paragraph', 'deflist', deflist, { alt: ['paragraph', 'reference', 'blockquote'] }) 203 | }; 204 | --------------------------------------------------------------------------------