markdown-it
Advanced tools
+30
-1
@@ -182,2 +182,6 @@ // Utilities | ||
| function isPunctCharCode (code) { | ||
| return isPunctChar(fromCodePoint(code)) | ||
| } | ||
| // Markdown ASCII punctuation characters. | ||
@@ -244,2 +248,3 @@ // | ||
| if ('ẞ'.toLowerCase() === 'Ṿ') { | ||
| /* c8 ignore next 2 */ | ||
| str = str.replace(/ẞ/g, 'ß') | ||
@@ -283,2 +288,24 @@ } | ||
| function isAsciiTrimmable (c) { | ||
| return c === 0x20 || c === 0x09 || c === 0x0a || c === 0x0d | ||
| } | ||
| // "Light" .trim() for blocks (headers, paragraphs), where unicode spaces | ||
| // should be preserved. | ||
| function asciiTrim (str) { | ||
| let start = 0 | ||
| for (; start < str.length; start++) { | ||
| if (!isAsciiTrimmable(str.charCodeAt(start))) { | ||
| break | ||
| } | ||
| } | ||
| let end = str.length - 1 | ||
| for (; end >= start; end--) { | ||
| if (!isAsciiTrimmable(str.charCodeAt(end))) { | ||
| break | ||
| } | ||
| } | ||
| return str.slice(start, end + 1) | ||
| } | ||
| // Re-export libraries commonly used in both markdown-it and its plugins, | ||
@@ -305,4 +332,6 @@ // so plugins won't have to depend on them explicitly, which reduces their | ||
| isPunctChar, | ||
| isPunctCharCode, | ||
| escapeRE, | ||
| normalizeReference | ||
| normalizeReference, | ||
| asciiTrim | ||
| } |
+1
-1
@@ -355,3 +355,3 @@ // Main parser class | ||
| * .set({ html: true, breaks: true }) | ||
| * .set({ typographer, true }); | ||
| * .set({ typographer: true }); | ||
| * ``` | ||
@@ -358,0 +358,0 @@ * |
| // heading (#, ##, ...) | ||
| import { isSpace } from '../common/utils.mjs' | ||
| import { isSpace, asciiTrim } from '../common/utils.mjs' | ||
@@ -43,3 +43,3 @@ export default function heading (state, startLine, endLine, silent) { | ||
| const token_i = state.push('inline', '', 0) | ||
| token_i.content = state.src.slice(pos, max).trim() | ||
| token_i.content = asciiTrim(state.src.slice(pos, max)) | ||
| token_i.map = [startLine, state.line] | ||
@@ -46,0 +46,0 @@ token_i.children = [] |
@@ -45,2 +45,8 @@ // HTML block | ||
| // Block types 6 and 7 (the only ones whose end condition is a blank line) | ||
| // have `/^$/` as their closing regexp. For all other types (1-5, e.g. | ||
| // `<!--` comments), a blank line is regular content and must not terminate | ||
| // the block - it ends only when its closing sequence is found. | ||
| const endsOnBlankLine = HTML_SEQUENCES[i][1].test('') | ||
| // If we are here - we detected HTML block. | ||
@@ -50,3 +56,8 @@ // Let's roll down till block end. | ||
| for (; nextLine < endLine; nextLine++) { | ||
| if (state.sCount[nextLine] < state.blkIndent) { break } | ||
| if (state.sCount[nextLine] < state.blkIndent) { | ||
| // An outdented blank line shouldn't end a block that doesn't end on a | ||
| // blank line (e.g. a `<!--` comment inside a list item). Such blocks | ||
| // must continue until their closing sequence regardless of indent. | ||
| if (endsOnBlankLine || !state.isEmpty(nextLine)) { break } | ||
| } | ||
@@ -53,0 +64,0 @@ pos = state.bMarks[nextLine] + state.tShift[nextLine] |
| // lheading (---, ===) | ||
| import { asciiTrim } from '../common/utils.mjs' | ||
| export default function lheading (state, startLine, endLine/*, silent */) { | ||
@@ -60,6 +62,7 @@ const terminatorRules = state.md.block.ruler.getRules('paragraph') | ||
| // Didn't find valid underline | ||
| state.parentType = oldParentType | ||
| return false | ||
| } | ||
| const content = state.getLines(startLine, nextLine, state.blkIndent, false).trim() | ||
| const content = asciiTrim(state.getLines(startLine, nextLine, state.blkIndent, false)) | ||
@@ -66,0 +69,0 @@ state.line = nextLine + 1 |
| // Paragraph | ||
| import { asciiTrim } from '../common/utils.mjs' | ||
| export default function paragraph (state, startLine, endLine) { | ||
@@ -29,3 +31,3 @@ const terminatorRules = state.md.block.ruler.getRules('paragraph') | ||
| const content = state.getLines(startLine, nextLine, state.blkIndent, false).trim() | ||
| const content = asciiTrim(state.getLines(startLine, nextLine, state.blkIndent, false)) | ||
@@ -32,0 +34,0 @@ state.line = nextLine |
| // Convert straight quotation marks to typographic ones | ||
| // | ||
| import { isWhiteSpace, isPunctChar, isMdAsciiPunct } from '../common/utils.mjs' | ||
| import { isWhiteSpace, isPunctCharCode, isMdAsciiPunct } from '../common/utils.mjs' | ||
@@ -10,6 +10,26 @@ const QUOTE_TEST_RE = /['"]/ | ||
| function replaceAt (str, index, ch) { | ||
| return str.slice(0, index) + ch + str.slice(index + 1) | ||
| function addReplacement (replacements, tokenIdx, pos, ch) { | ||
| if (!replacements[tokenIdx]) { | ||
| replacements[tokenIdx] = [] | ||
| } | ||
| replacements[tokenIdx].push({ pos, ch }) | ||
| } | ||
| function applyReplacements (str, replacements) { | ||
| let result = '' | ||
| let lastPos = 0 | ||
| replacements.sort((a, b) => a.pos - b.pos) | ||
| for (let i = 0; i < replacements.length; i++) { | ||
| const replacement = replacements[i] | ||
| result += str.slice(lastPos, replacement.pos) + replacement.ch | ||
| lastPos = replacement.pos + 1 | ||
| } | ||
| return result + str.slice(lastPos) | ||
| } | ||
| function process_inlines (tokens, state) { | ||
@@ -19,2 +39,4 @@ let j | ||
| const stack = [] | ||
| // token index -> list of replacements in the original token content | ||
| const replacements = {} | ||
@@ -33,5 +55,5 @@ for (let i = 0; i < tokens.length; i++) { | ||
| let text = token.content | ||
| const text = token.content | ||
| let pos = 0 | ||
| let max = text.length | ||
| const max = text.length | ||
@@ -84,4 +106,4 @@ /* eslint no-labels:0,block-scoped-var:0 */ | ||
| const isLastPunctChar = isMdAsciiPunct(lastChar) || isPunctChar(String.fromCharCode(lastChar)) | ||
| const isNextPunctChar = isMdAsciiPunct(nextChar) || isPunctChar(String.fromCharCode(nextChar)) | ||
| const isLastPunctChar = isMdAsciiPunct(lastChar) || isPunctCharCode(lastChar) | ||
| const isNextPunctChar = isMdAsciiPunct(nextChar) || isPunctCharCode(nextChar) | ||
@@ -129,3 +151,3 @@ const isLastWhiteSpace = isWhiteSpace(lastChar) | ||
| if (isSingle) { | ||
| token.content = replaceAt(token.content, t.index, APOSTROPHE) | ||
| addReplacement(replacements, i, t.index, APOSTROPHE) | ||
| } | ||
@@ -153,15 +175,5 @@ continue | ||
| // replace token.content *before* tokens[item.token].content, | ||
| // because, if they are pointing at the same token, replaceAt | ||
| // could mess up indices when quote length != 1 | ||
| token.content = replaceAt(token.content, t.index, closeQuote) | ||
| tokens[item.token].content = replaceAt( | ||
| tokens[item.token].content, item.pos, openQuote) | ||
| addReplacement(replacements, i, t.index, closeQuote) | ||
| addReplacement(replacements, item.token, item.pos, openQuote) | ||
| pos += closeQuote.length - 1 | ||
| if (item.token === i) { pos += openQuote.length - 1 } | ||
| text = token.content | ||
| max = text.length | ||
| stack.length = j | ||
@@ -181,6 +193,10 @@ continue OUTER | ||
| } else if (canClose && isSingle) { | ||
| token.content = replaceAt(token.content, t.index, APOSTROPHE) | ||
| addReplacement(replacements, i, t.index, APOSTROPHE) | ||
| } | ||
| } | ||
| } | ||
| Object.keys(replacements).forEach(function (tokenIdx) { | ||
| tokens[tokenIdx].content = applyReplacements(tokens[tokenIdx].content, replacements[tokenIdx]) | ||
| }) | ||
| } | ||
@@ -187,0 +203,0 @@ |
| // Process html entity - {, ¯, ", ... | ||
| import { decodeHTML } from 'entities' | ||
| import { decodeHTMLStrict } from 'entities' | ||
| import { isValidEntityCode, fromCodePoint } from '../common/utils.mjs' | ||
@@ -36,3 +36,3 @@ | ||
| if (match) { | ||
| const decoded = decodeHTML(match[0]) | ||
| const decoded = decodeHTMLStrict(match[0]) | ||
| if (decoded !== match[0]) { | ||
@@ -39,0 +39,0 @@ if (!silent) { |
| // Inline parser state | ||
| import Token from '../token.mjs' | ||
| import { isWhiteSpace, isPunctChar, isMdAsciiPunct } from '../common/utils.mjs' | ||
| import { isWhiteSpace, isPunctCharCode, isMdAsciiPunct } from '../common/utils.mjs' | ||
@@ -92,5 +92,27 @@ function StateInline (src, md, env, outTokens) { | ||
| // treat beginning of the line as a whitespace | ||
| const lastChar = start > 0 ? this.src.charCodeAt(start - 1) : 0x20 | ||
| // Astral characters below are combined manually, because .codePointAt() | ||
| // does not guarantee numeric type output. And we don't wish JIT cache issues. | ||
| // The broken surrogate pairs are evaluated as U+FFFD to prevent possible | ||
| // crashes. | ||
| let lastChar | ||
| if (start === 0) { | ||
| // treat beginning of the line as a whitespace | ||
| lastChar = 0x20 | ||
| } else if (start === 1) { | ||
| lastChar = this.src.charCodeAt(0) | ||
| if ((lastChar & 0xF800) === 0xD800) { lastChar = 0xFFFD } | ||
| } else { | ||
| lastChar = this.src.charCodeAt(start - 1) | ||
| if ((lastChar & 0xFC00) === 0xDC00) { | ||
| // low surrogate => add high one, replace broken pair with U+FFFD | ||
| const highSurr = this.src.charCodeAt(start - 2) | ||
| lastChar = (highSurr & 0xFC00) === 0xD800 | ||
| ? 0x10000 + ((highSurr - 0xD800) << 10) + (lastChar - 0xDC00) | ||
| : 0xFFFD | ||
| } else if ((lastChar & 0xFC00) === 0xD800) { | ||
| lastChar = 0xFFFD | ||
| } | ||
| } | ||
| let pos = start | ||
@@ -102,6 +124,15 @@ while (pos < max && this.src.charCodeAt(pos) === marker) { pos++ } | ||
| // treat end of the line as a whitespace | ||
| const nextChar = pos < max ? this.src.charCodeAt(pos) : 0x20 | ||
| let nextChar = pos < max ? this.src.charCodeAt(pos) : 0x20 | ||
| if ((nextChar & 0xFC00) === 0xD800) { | ||
| // high surrogate => add low one, replace broken pair with U+FFFD | ||
| const lowSurr = this.src.charCodeAt(pos + 1) | ||
| nextChar = (lowSurr & 0xFC00) === 0xDC00 | ||
| ? 0x10000 + ((nextChar - 0xD800) << 10) + (lowSurr - 0xDC00) | ||
| : 0xFFFD | ||
| } else if ((nextChar & 0xFC00) === 0xDC00) { | ||
| nextChar = 0xFFFD | ||
| } | ||
| const isLastPunctChar = isMdAsciiPunct(lastChar) || isPunctChar(String.fromCharCode(lastChar)) | ||
| const isNextPunctChar = isMdAsciiPunct(nextChar) || isPunctChar(String.fromCharCode(nextChar)) | ||
| const isLastPunctChar = isMdAsciiPunct(lastChar) || isPunctCharCode(lastChar) | ||
| const isNextPunctChar = isMdAsciiPunct(nextChar) || isPunctCharCode(nextChar) | ||
@@ -108,0 +139,0 @@ const isLastWhiteSpace = isWhiteSpace(lastChar) |
+14
-3
| { | ||
| "name": "markdown-it", | ||
| "version": "14.1.1", | ||
| "version": "14.2.0", | ||
| "description": "Markdown-it - modern pluggable markdown parser.", | ||
@@ -14,2 +14,12 @@ "keywords": [ | ||
| "license": "MIT", | ||
| "funding": [ | ||
| { | ||
| "type": "github", | ||
| "url": "https://github.com/sponsors/puzrin" | ||
| }, | ||
| { | ||
| "type": "github", | ||
| "url": "https://github.com/sponsors/markdown-it" | ||
| } | ||
| ], | ||
| "main": "dist/index.cjs.js", | ||
@@ -32,3 +42,3 @@ "module": "index.mjs", | ||
| "lint": "eslint .", | ||
| "test": "npm run lint && CJS_ONLY=1 npm run build && c8 --exclude dist --exclude test -r text -r html -r lcov mocha && node support/specsplit.mjs", | ||
| "test": "npm run lint && cross-env CJS_ONLY=1 npm run build && c8 --exclude dist --exclude test -r text -r html -r lcov mocha && node support/specsplit.mjs", | ||
| "doc": "node support/build_doc.mjs", | ||
@@ -52,3 +62,3 @@ "gh-doc": "npm run doc && gh-pages -d apidoc -f", | ||
| "entities": "^4.4.0", | ||
| "linkify-it": "^5.0.0", | ||
| "linkify-it": "^5.0.1", | ||
| "mdurl": "^2.0.0", | ||
@@ -67,2 +77,3 @@ "punycode.js": "^2.3.1", | ||
| "chai": "^4.2.0", | ||
| "cross-env": "^7.0.3", | ||
| "eslint": "^8.4.1", | ||
@@ -69,0 +80,0 @@ "eslint-config-standard": "^17.1.0", |
+13
-17
@@ -6,3 +6,2 @@ # markdown-it <!-- omit in toc --> | ||
| [](https://coveralls.io/github/markdown-it/markdown-it?branch=master) | ||
| [](https://gitter.im/markdown-it/markdown-it) | ||
@@ -16,3 +15,3 @@ > Markdown parser done right. Fast and easy to extend. | ||
| - High speed. | ||
| - [Safe](https://github.com/markdown-it/markdown-it/tree/master/docs/security.md) by default. | ||
| - [Safe](https://github.com/markdown-it/markdown-it/tree/master/docs/safety.md) by default. | ||
| - Community-written __[plugins](https://www.npmjs.org/browse/keyword/markdown-it-plugin)__ and [other packages](https://www.npmjs.org/browse/keyword/markdown-it) on npm. | ||
@@ -33,3 +32,2 @@ | ||
| - [Benchmark](#benchmark) | ||
| - [markdown-it for enterprise](#markdown-it-for-enterprise) | ||
| - [Authors](#authors) | ||
@@ -46,8 +44,11 @@ - [References / Thanks](#references--thanks) | ||
| **browser (CDN):** | ||
| > [!NOTE] | ||
| > | ||
| > For a quick look at `dist/` folder contents, see | ||
| > <https://unpkg.com/markdown-it/>. | ||
| > | ||
| > For browser you can use unpkg.com, esm.sh or any other CDN, wich mirror npm | ||
| > registry | ||
| - [jsDeliver CDN](http://www.jsdelivr.com/#!markdown-it "jsDelivr CDN") | ||
| - [cdnjs.com CDN](https://cdnjs.com/libraries/markdown-it "cdnjs.com") | ||
| ## Usage examples | ||
@@ -151,3 +152,3 @@ | ||
| const md = markdownit | ||
| const md = markdownit() | ||
| .use(plugin1) | ||
@@ -289,4 +290,6 @@ .use(plugin2, opts, ...) | ||
| __Note.__ CommonMark version runs with [simplified link normalizers](https://github.com/markdown-it/markdown-it/blob/master/benchmark/implementations/current-commonmark/index.mjs) | ||
| for more "honest" compare. Difference is ≈1.5×. | ||
| > [!NOTE] | ||
| > | ||
| > CommonMark version runs with [simplified link normalizers](https://github.com/markdown-it/markdown-it/blob/master/benchmark/implementations/current-commonmark/index.mjs) | ||
| > for more "honest" compare. Difference is ≈1.5×. | ||
@@ -298,9 +301,2 @@ As you can see, `markdown-it` doesn't pay with speed for its flexibility. | ||
| ## markdown-it for enterprise | ||
| Available as part of the Tidelift Subscription. | ||
| The maintainers of `markdown-it` and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/npm-markdown-it?utm_source=npm-markdown-it&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) | ||
| ## Authors | ||
@@ -307,0 +303,0 @@ |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
776952
1.17%18001
1.39%32
3.23%321
-1.23%Updated