markdown-it
Advanced tools
Comparing version 2.1.3 to 2.2.0
@@ -20,2 +20,3 @@ { | ||
"docs", | ||
"apidoc", | ||
"lib", | ||
@@ -22,0 +23,0 @@ "node_modules", |
@@ -0,1 +1,11 @@ | ||
2.2.0 / 2014-12-28 | ||
------------------ | ||
- Updated CM spec conformance to v0.13. | ||
- API docs. | ||
- Added 'zero' preset. | ||
- Fixed several crashes, when some basic rules are disabled | ||
(block termination check, references check). | ||
2.1.3 / 2014-12-24 | ||
@@ -2,0 +12,0 @@ ------------------ |
@@ -8,11 +8,8 @@ // Parse link label | ||
module.exports = function parseLinkLabel(state, start) { | ||
var level, found, marker, | ||
module.exports = function parseLinkLabel(state, start, disableNested) { | ||
var level, found, marker, prevPos, | ||
labelEnd = -1, | ||
max = state.posMax, | ||
oldPos = state.pos, | ||
oldFlag = state.isInLabel; | ||
oldPos = state.pos; | ||
if (state.isInLabel) { return -1; } | ||
if (state.labelUnmatchedScopes) { | ||
@@ -24,3 +21,2 @@ state.labelUnmatchedScopes--; | ||
state.pos = start + 1; | ||
state.isInLabel = true; | ||
level = 1; | ||
@@ -30,5 +26,3 @@ | ||
marker = state.src.charCodeAt(state.pos); | ||
if (marker === 0x5B /* [ */) { | ||
level++; | ||
} else if (marker === 0x5D /* ] */) { | ||
if (marker === 0x5D /* ] */) { | ||
level--; | ||
@@ -41,3 +35,13 @@ if (level === 0) { | ||
prevPos = state.pos; | ||
state.parser.skipToken(state); | ||
if (marker === 0x5B /* [ */) { | ||
if (prevPos === state.pos - 1) { | ||
// increase level if we find text `[`, which is not a part of any token | ||
level++; | ||
} else if (disableNested) { | ||
state.pos = oldPos; | ||
return -1; | ||
} | ||
} | ||
} | ||
@@ -54,5 +58,4 @@ | ||
state.pos = oldPos; | ||
state.isInLabel = oldFlag; | ||
return labelEnd; | ||
}; |
319
lib/index.js
@@ -8,4 +8,2 @@ // Main perser class | ||
var helpers = require('./helpers'); | ||
var assign = require('./common/utils').assign; | ||
var isString = require('./common/utils').isString; | ||
var Renderer = require('./renderer'); | ||
@@ -15,6 +13,6 @@ var ParserCore = require('./parser_core'); | ||
var ParserInline = require('./parser_inline'); | ||
var Ruler = require('./ruler'); | ||
var config = { | ||
'default': require('./presets/default'), | ||
zero: require('./presets/zero'), | ||
full: require('./presets/full'), | ||
@@ -38,4 +36,116 @@ commonmark: require('./presets/commonmark') | ||
// Main class | ||
// | ||
/** | ||
* class MarkdownIt | ||
* | ||
* Main parser/renderer class. | ||
* | ||
* ##### Usage | ||
* | ||
* ```javascript | ||
* // node.js, "classic" way: | ||
* var MarkdownIt = require('markdown-it'), | ||
* md = new MarkdownIt(); | ||
* var result = md.render('# markdown-it rulezz!'); | ||
* | ||
* // node.js, the same, but with sugar: | ||
* var md = require('markdown-it')(); | ||
* var result = md.render('# markdown-it rulezz!'); | ||
* | ||
* // browser without AMD, added to "window" on script load | ||
* // Note, there are no dash. | ||
* var md = window.markdownit(); | ||
* var result = md.render('# markdown-it rulezz!'); | ||
* ``` | ||
* | ||
* Single line rendering, without paragraph wrap: | ||
* | ||
* ```javascript | ||
* var md = require('markdown-it')(); | ||
* var result = md.renderInline('__markdown-it__ rulezz!'); | ||
* ``` | ||
**/ | ||
/** | ||
* new MarkdownIt([presetName, options]) | ||
* - presetName (String): optional, `commonmark` / `full` / `zero` | ||
* - options (Object) | ||
* | ||
* Creates parser instanse with given config. Can be called without `new`. | ||
* | ||
* ##### presetName | ||
* | ||
* MarkdownIt provides named presets as a convenience to quickly | ||
* enable/disable active syntax rules and options for common use cases. | ||
* | ||
* - ["commonmark"](https://github.com/markdown-it/markdown-it/blob/master/lib/presets/commonmark.js) - | ||
* configures parser to strict [CommonMark](http://commonmark.org/) mode. | ||
* - ["full"](https://github.com/markdown-it/markdown-it/blob/master/lib/presets/full.js) - | ||
* enables all available rules, but still without html, typographer & autolinker. | ||
* - [default](https://github.com/markdown-it/markdown-it/blob/master/lib/presets/default.js) - | ||
* similar to GFM, used when no preset name given. | ||
* - ["zero"](https://github.com/markdown-it/markdown-it/blob/master/lib/presets/zero.js) - | ||
* all rules disabled. Useful to quickly setup your config via `.enable()`. | ||
* For example, when you need only `bold` and `italic` markup and nothing else. | ||
* | ||
* ##### options: | ||
* | ||
* - __html__ - `false`. Set `true` to enable HTML tags in source. Be careful! | ||
* That's not safe! You may need external sanitizer to protect output from XSS. | ||
* It's better to extend features via plugins, instead of enabling HTML. | ||
* - __xhtmlOut__ - `false`. Set `true` to add '/' when closing single tags | ||
* (`<br />`). This is needed only for full CommonMark compatibility. In real | ||
* world you will need HTML output. | ||
* - __breaks__ - `false`. Set `true` to convert `\n` in paragraphs into `<br>`. | ||
* - __langPrefix__ - `language-`. CSS language class prefix for fenced blocks. | ||
* Can be useful for external highlighters. | ||
* - __linkify__ - `false`. Set `true` to autoconvert URL-like text to links. | ||
* - __typographer__ - `false`. Set `true` to enable [some language-neutral | ||
* replacement](https://github.com/markdown-it/markdown-it/blob/master/lib/rules_core/replacements.js) + | ||
* quotes beautification (smartquotes). | ||
* - __quotes__ - `“”‘’`, string. Double + single quotes replacement pairs, when | ||
* typographer enabled and smartquotes on. Set doubles to '«»' for Russian, | ||
* '„“' for German. | ||
* - __highlight__ - `null`. Highlighter function for fenced code blocks. | ||
* Highlighter `function (str, lang)` should return escaped HTML. It can also | ||
* return empty string if the source was not changed and should be escaped externaly. | ||
* | ||
* ##### Example | ||
* | ||
* ```javascript | ||
* // commonmark mode | ||
* var md = require('markdown-it')('commonmark'); | ||
* | ||
* // default mode | ||
* var md = require('markdown-it')(); | ||
* | ||
* // enable everything | ||
* var md = require('markdown-it')('full', { | ||
* html: true, | ||
* linkify: true, | ||
* typographer: true | ||
* }); | ||
* ``` | ||
* | ||
* ##### Syntax highlighting | ||
* | ||
* ```js | ||
* var hljs = require('highlight.js') // https://highlightjs.org/ | ||
* | ||
* var md = require('markdown-it')({ | ||
* highlight: function (str, lang) { | ||
* if (lang && hljs.getLanguage(lang)) { | ||
* try { | ||
* return hljs.highlight(lang, str).value; | ||
* } catch (__) {} | ||
* } | ||
* | ||
* try { | ||
* return hljs.highlightAuto(str).value; | ||
* } catch (__) {} | ||
* | ||
* return ''; // use external default escaping | ||
* } | ||
* }); | ||
* ``` | ||
**/ | ||
function MarkdownIt(presetName, options) { | ||
@@ -47,3 +157,3 @@ if (!(this instanceof MarkdownIt)) { | ||
if (!options) { | ||
if (!isString(presetName)) { | ||
if (!utils.isString(presetName)) { | ||
options = presetName || {}; | ||
@@ -54,12 +164,71 @@ presetName = 'default'; | ||
this.inline = new ParserInline(); | ||
this.block = new ParserBlock(); | ||
this.core = new ParserCore(); | ||
/** | ||
* MarkdownIt#inline -> ParserInline | ||
* | ||
* Instance of [[ParserInline]]. You may need it to add new rules when | ||
* writing plugins. For simple rules control use [[MarkdownIt.disable]] and | ||
* [[MarkdownIt.enable]]. | ||
**/ | ||
this.inline = new ParserInline(); | ||
/** | ||
* MarkdownIt#block -> ParserBlock | ||
* | ||
* Instance of [[ParserBlock]]. You may need it to add new rules when | ||
* writing plugins. For simple rules control use [[MarkdownIt.disable]] and | ||
* [[MarkdownIt.enable]]. | ||
**/ | ||
this.block = new ParserBlock(); | ||
/** | ||
* MarkdownIt#core -> Core | ||
* | ||
* Instance of [[Core]] chain executor. You may need it to add new rules when | ||
* writing plugins. For simple rules control use [[MarkdownIt.disable]] and | ||
* [[MarkdownIt.enable]]. | ||
**/ | ||
this.core = new ParserCore(); | ||
/** | ||
* MarkdownIt#renderer -> Renderer | ||
* | ||
* Instance of [[Renderer]]. Use it to modify output look. Or to add rendering | ||
* rules for new token types, generated by plugins. | ||
* | ||
* ##### Example | ||
* | ||
* ```javascript | ||
* var md = require('markdown-it')(); | ||
* | ||
* function myToken(tokens, idx, options, env, self) { | ||
* //... | ||
* return result; | ||
* }; | ||
* | ||
* md.renderer.rules['my_token'] = myToken | ||
* ``` | ||
* | ||
* See [[Renderer]] docs and [source code](https://github.com/markdown-it/markdown-it/blob/master/lib/renderer.js). | ||
**/ | ||
this.renderer = new Renderer(); | ||
this.ruler = new Ruler(); | ||
// Expose utils & helpers for easy acces from plugins | ||
/** | ||
* MarkdownIt#utils -> utils | ||
* | ||
* Assorted utility functions, useful to write plugins. See details | ||
* [here](https://github.com/markdown-it/markdown-it/blob/master/lib/common/utils.js). | ||
**/ | ||
this.utils = utils; | ||
/** | ||
* MarkdownIt#helpers -> helpers | ||
* | ||
* Link components parser functions, useful to write plugins. See details | ||
* [here](https://github.com/markdown-it/markdown-it/blob/master/lib/helpers). | ||
**/ | ||
this.helpers = helpers; | ||
this.options = {}; | ||
@@ -72,6 +241,23 @@ this.configure(config[presetName]); | ||
// Set options, if you did not passed those to constructor | ||
// | ||
/** chainable | ||
* MarkdownIt.set(options) | ||
* | ||
* Set parser options (in the same format as in constructor). Probably, you | ||
* will never need it, but you can change options after constructor call. | ||
* | ||
* ##### Example | ||
* | ||
* ```javascript | ||
* var md = require('markdown-it')() | ||
* .set({ html: true, breaks: true }) | ||
* .set({ typographer, true }); | ||
* ``` | ||
* | ||
* __Note:__ To achieve the best possible performance, don't modify a | ||
* `markdown-it` instance options on the fly. If you need multiple configurations | ||
* it's best to create multiple instances and initialize each with separate | ||
* config. | ||
**/ | ||
MarkdownIt.prototype.set = function (options) { | ||
assign(this.options, options); | ||
utils.assign(this.options, options); | ||
return this; | ||
@@ -81,4 +267,9 @@ }; | ||
// Batch loader for components rules states & options | ||
// | ||
/** chainable, internal | ||
* MarkdownIt.configure(presets) | ||
* | ||
* Batch load of all options and compenent settings. This is internal method, | ||
* and you probably will not need it. But if you with - see available presets | ||
* and data structure [here](https://github.com/markdown-it/markdown-it/tree/master/lib/presets) | ||
**/ | ||
MarkdownIt.prototype.configure = function (presets) { | ||
@@ -102,4 +293,17 @@ var self = this; | ||
// Sugar to enable rules by names in all chains at once | ||
// | ||
/** chainable | ||
* MarkdownIt.enable(list) | ||
* - list (String|Array): rule name or list of rule names to enable | ||
* | ||
* Enable list or rules. It will automatically find appropriate components, | ||
* containing rules with given names. | ||
* | ||
* ##### Example | ||
* | ||
* ```javascript | ||
* var md = require('markdown-it')() | ||
* .enable(['sub', 'sup']) | ||
* .disable('smartquotes'); | ||
* ``` | ||
**/ | ||
MarkdownIt.prototype.enable = function (list) { | ||
@@ -113,4 +317,8 @@ [ 'core', 'block', 'inline' ].forEach(function (chain) { | ||
// Sugar to disable rules by names in all chains at once | ||
// | ||
/** chainable | ||
* MarkdownIt.disable(list) | ||
* - list (String|Array): rule name or list of rule names to disable. | ||
* | ||
* The same as [[MarkdownIt.enable]], but turn specified rules off. | ||
**/ | ||
MarkdownIt.prototype.disable = function (list) { | ||
@@ -124,12 +332,16 @@ [ 'core', 'block', 'inline' ].forEach(function (chain) { | ||
// Sugar for curried plugins init: | ||
// | ||
// var md = new MarkdownIt(); | ||
// | ||
// md.use(plugin1) | ||
// .use(plugin2, opts) | ||
// .use(plugin3); | ||
// | ||
MarkdownIt.prototype.use = function (plugin, opts) { | ||
plugin(this, opts); | ||
/** chainable | ||
* MarkdownIt.use(plugin, options) | ||
* | ||
* Load specified plugin with given options into current parser instance. | ||
* | ||
* ##### Example | ||
* | ||
* ```javascript | ||
* var md = require('markdown-it')() | ||
* .use(require('makkdown-it-emoji')); | ||
* ``` | ||
**/ | ||
MarkdownIt.prototype.use = function (plugin, options) { | ||
plugin(this, options); | ||
return this; | ||
@@ -139,5 +351,15 @@ }; | ||
// Parse input string, returns tokens array. Modify `env` with | ||
// definitions data. | ||
// | ||
/** internal | ||
* MarkdownIt.parse(src, env) -> Array | ||
* - src (String): source string | ||
* - env (Object): enviroment variables | ||
* | ||
* Parse input string and returns list of block tokens (special token type | ||
* "inline" will contain list of inline tokens). You should not call this | ||
* method directly, until you write custom renderer (for example, to produce | ||
* AST). | ||
* | ||
* `env` is modified with additional info. For example, with references data. | ||
* Also `env` can be used to pass external info to plugins. | ||
**/ | ||
MarkdownIt.prototype.parse = function (src, env) { | ||
@@ -152,4 +374,12 @@ var state = new StateCore(this, src, env); | ||
// Main method that does all magic :) | ||
// | ||
/** | ||
* MarkdownIt.render(src [, env]) -> String | ||
* - src (String): source string | ||
* - env (Object): optional, enviroment variables | ||
* | ||
* Render markdown string into html. It does all magic for you :). | ||
* | ||
* `env` is `{}` by default. It's not used now directly, but you can pass | ||
* with it any additional data to plugins. | ||
**/ | ||
MarkdownIt.prototype.render = function (src, env) { | ||
@@ -162,4 +392,11 @@ env = env || {}; | ||
// Parse content as single string | ||
// | ||
/** internal | ||
* MarkdownIt.parseInline(src, env) -> Array | ||
* - src (String): source string | ||
* - env (Object): enviroment variables | ||
* | ||
* The same as [[MarkdownIt.parse]] but skip all block rules. It returns the | ||
* block tokens list with th single `inline` element, containing parsed inline | ||
* tokens in `children` property. | ||
**/ | ||
MarkdownIt.prototype.parseInline = function (src, env) { | ||
@@ -175,4 +412,10 @@ var state = new StateCore(this, src, env); | ||
// Render single string, without wrapping it to paragraphs | ||
// | ||
/** | ||
* MarkdownIt.renderInline(src [, env]) -> String | ||
* - src (String): source string | ||
* - env (Object): optional, enviroment variables | ||
* | ||
* Similar to [[MarkdownIt.render]] but for single paragraph content. Result | ||
* will NOT be wrapped into `<p>` tags. | ||
**/ | ||
MarkdownIt.prototype.renderInline = function (src, env) { | ||
@@ -179,0 +422,0 @@ env = env || {}; |
@@ -1,4 +0,6 @@ | ||
// Block parser | ||
/** internal | ||
* class ParserBlock | ||
* | ||
* Block-level tokenizer. | ||
**/ | ||
'use strict'; | ||
@@ -27,5 +29,11 @@ | ||
// Block Parser class | ||
// | ||
/** | ||
* new ParserBlock() | ||
**/ | ||
function ParserBlock() { | ||
/** | ||
* ParserBlock#ruler -> Ruler | ||
* | ||
* [[Ruler]] instance. Keep configuration of block rules. | ||
**/ | ||
this.ruler = new Ruler(); | ||
@@ -93,3 +101,9 @@ | ||
var SPACES_RE = /\u00a0/g; | ||
var NULL_RE = /\u0000/g; | ||
/** | ||
* ParserBlock.parse(str, options, env, outTokens) | ||
* | ||
* Process input string and push block tokens into `outTokens` | ||
**/ | ||
ParserBlock.prototype.parse = function (src, options, env, outTokens) { | ||
@@ -106,2 +120,5 @@ var state, lineStart = 0, lastTabPos = 0; | ||
// Strin NULL characters | ||
src = src.replace(NULL_RE, ''); | ||
// Replace tabs with proper number of spaces (1..4) | ||
@@ -108,0 +125,0 @@ if (src.indexOf('\t') >= 0) { |
@@ -1,3 +0,7 @@ | ||
// Class of top level (`core`) rules | ||
// | ||
/** internal | ||
* class Core | ||
* | ||
* Top-level rules executor. Glues block/inline parsers and does intermediate | ||
* transformations. | ||
**/ | ||
'use strict'; | ||
@@ -22,5 +26,11 @@ | ||
/** | ||
* new Core() | ||
**/ | ||
function Core() { | ||
this.options = {}; | ||
/** | ||
* Core#ruler -> Ruler | ||
* | ||
* [[Ruler]] instance. Keep configuration of core rules. | ||
**/ | ||
this.ruler = new Ruler(); | ||
@@ -34,2 +44,7 @@ | ||
/** | ||
* Core.process(state) | ||
* | ||
* Executes core chain rules. | ||
**/ | ||
Core.prototype.process = function (state) { | ||
@@ -36,0 +51,0 @@ var i, l, rules; |
@@ -1,3 +0,6 @@ | ||
// Inline parser | ||
/** internal | ||
* class ParserInline | ||
* | ||
* Tokenizes paragraph content. | ||
**/ | ||
'use strict'; | ||
@@ -47,9 +50,27 @@ | ||
// Inline Parser class | ||
// | ||
/** | ||
* new ParserInline() | ||
**/ | ||
function ParserInline() { | ||
// By default CommonMark allows too much in links | ||
// If you need to restrict it - override this with your validator. | ||
/** | ||
* ParserInline#validateLink(url) -> Boolean | ||
* | ||
* Link validation function. CommonMark allows too much in links. By default | ||
* we disable `javascript:` and `vbscript:` schemas. You can change this | ||
* behaviour. | ||
* | ||
* ```javascript | ||
* var md = require('markdown-it')(); | ||
* // enable everything | ||
* md.inline.validateLink = function () { return true; } | ||
* ``` | ||
**/ | ||
this.validateLink = validateLink; | ||
/** | ||
* ParserInline#ruler -> Ruler | ||
* | ||
* [[Ruler]] instance. Keep configuration of inline rules. | ||
**/ | ||
this.ruler = new Ruler(); | ||
@@ -124,4 +145,7 @@ | ||
// Parse input string. | ||
// | ||
/** | ||
* ParserInline.parse(str, options, env, outTokens) | ||
* | ||
* Process input string and push inline tokens into `outTokens` | ||
**/ | ||
ParserInline.prototype.parse = function (str, options, env, outTokens) { | ||
@@ -128,0 +152,0 @@ var state = new StateInline(str, this, options, env, outTokens); |
@@ -0,1 +1,8 @@ | ||
/** | ||
* class Renderer | ||
* | ||
* Generates HTML from parsed token stream. Each instance has independent | ||
* copy of rules. Those can be rewritten with ease. Also, you can add new | ||
* rules if you create plugin and adds new token types. | ||
**/ | ||
'use strict'; | ||
@@ -12,28 +19,3 @@ | ||
//////////////////////////////////////////////////////////////////////////////// | ||
// Helpers | ||
function nextToken(tokens, idx) { | ||
if (++idx >= tokens.length - 2) { return idx; } | ||
if ((tokens[idx].type === 'paragraph_open' && tokens[idx].tight) && | ||
(tokens[idx + 1].type === 'inline' && tokens[idx + 1].content.length === 0) && | ||
(tokens[idx + 2].type === 'paragraph_close' && tokens[idx + 2].tight)) { | ||
return nextToken(tokens, idx + 2); | ||
} | ||
return idx; | ||
} | ||
// check if we need to hide '\n' before next token | ||
function getBreak(tokens, idx) { | ||
idx = nextToken(tokens, idx); | ||
if (idx < tokens.length && | ||
tokens[idx].type === 'list_item_close') { | ||
return ''; | ||
} | ||
return '\n'; | ||
} | ||
//////////////////////////////////////////////////////////////////////////////// | ||
var rules = {}; | ||
@@ -46,4 +28,4 @@ | ||
}; | ||
rules.blockquote_close = function (tokens, idx /*, options, env */) { | ||
return '</blockquote>' + getBreak(tokens, idx); | ||
rules.blockquote_close = function (/* tokens, idx, options, env */) { | ||
return '</blockquote>\n'; | ||
}; | ||
@@ -54,3 +36,3 @@ | ||
if (tokens[idx].block) { | ||
return '<pre><code>' + escapeHtml(tokens[idx].content) + '</code></pre>' + getBreak(tokens, idx); | ||
return '<pre><code>' + escapeHtml(tokens[idx].content) + '</code></pre>\n'; | ||
} | ||
@@ -97,3 +79,3 @@ | ||
+ highlighted | ||
+ '</code></pre>' + getBreak(tokens, idx); | ||
+ '</code></pre>\n'; | ||
}; | ||
@@ -112,3 +94,3 @@ | ||
rules.hr = function (tokens, idx, options /*, env */) { | ||
return (options.xhtmlOut ? '<hr />' : '<hr>') + getBreak(tokens, idx); | ||
return (options.xhtmlOut ? '<hr />\n' : '<hr>\n'); | ||
}; | ||
@@ -120,7 +102,12 @@ | ||
}; | ||
rules.bullet_list_close = function (tokens, idx /*, options, env */) { | ||
return '</ul>' + getBreak(tokens, idx); | ||
rules.bullet_list_close = function (/* tokens, idx, options, env */) { | ||
return '</ul>\n'; | ||
}; | ||
rules.list_item_open = function (/* tokens, idx, options, env */) { | ||
return '<li>'; | ||
rules.list_item_open = function (tokens, idx /*, options, env */) { | ||
var next = tokens[idx + 1]; | ||
if ((next.type === 'list_item_close') || | ||
(next.type === 'paragraph_open' && next.tight)) { | ||
return '<li>'; | ||
} | ||
return '<li>\n'; | ||
}; | ||
@@ -131,9 +118,9 @@ rules.list_item_close = function (/* tokens, idx, options, env */) { | ||
rules.ordered_list_open = function (tokens, idx /*, options, env */) { | ||
var token = tokens[idx]; | ||
return '<ol' | ||
+ (token.order > 1 ? ' start="' + token.order + '"' : '') | ||
+ '>\n'; | ||
if (tokens[idx].order > 1) { | ||
return '<ol start="' + tokens[idx].order + '">\n'; | ||
} | ||
return '<ol>\n'; | ||
}; | ||
rules.ordered_list_close = function (tokens, idx /*, options, env */) { | ||
return '</ol>' + getBreak(tokens, idx); | ||
rules.ordered_list_close = function (/* tokens, idx, options, env */) { | ||
return '</ol>\n'; | ||
}; | ||
@@ -146,4 +133,17 @@ | ||
rules.paragraph_close = function (tokens, idx /*, options, env */) { | ||
var addBreak = !(tokens[idx].tight && idx && tokens[idx - 1].type === 'inline' && !tokens[idx - 1].content); | ||
return (tokens[idx].tight ? '' : '</p>') + (addBreak ? getBreak(tokens, idx) : ''); | ||
// We have 2 cases of "hidden" paragraphs | ||
// | ||
// 1. In tight lists | ||
// 2. When content was stripped (reference definition, for example) | ||
// | ||
if (tokens[idx].tight === true) { | ||
if (!tokens[idx - 1].content) { | ||
return ''; | ||
} | ||
if (tokens[idx + 1].type === 'list_item_close') { | ||
return ''; | ||
} | ||
return '\n'; | ||
} | ||
return '</p>\n'; | ||
}; | ||
@@ -161,6 +161,6 @@ | ||
rules.image = function (tokens, idx, options /*, env */) { | ||
rules.image = function (tokens, idx, options, env, self) { | ||
var src = ' src="' + escapeHtml(tokens[idx].src) + '"'; | ||
var title = tokens[idx].title ? (' title="' + escapeHtml(replaceEntities(tokens[idx].title)) + '"') : ''; | ||
var alt = ' alt="' + (tokens[idx].alt ? escapeHtml(replaceEntities(tokens[idx].alt)) : '') + '"'; | ||
var alt = ' alt="' + self.renderInlineAsText(tokens[idx].tokens, options, env) + '"'; | ||
var suffix = options.xhtmlOut ? ' /' : ''; | ||
@@ -196,6 +196,6 @@ return '<img' + src + alt + title + suffix + '>'; | ||
rules.th_open = function (tokens, idx /*, options, env */) { | ||
var token = tokens[idx]; | ||
return '<th' | ||
+ (token.align ? ' style="text-align:' + token.align + '"' : '') | ||
+ '>'; | ||
if (tokens[idx].align) { | ||
return '<th style="text-align:' + tokens[idx].align + '">'; | ||
} | ||
return '<th>'; | ||
}; | ||
@@ -206,6 +206,6 @@ rules.th_close = function (/* tokens, idx, options, env */) { | ||
rules.td_open = function (tokens, idx /*, options, env */) { | ||
var token = tokens[idx]; | ||
return '<td' | ||
+ (token.align ? ' style="text-align:' + token.align + '"' : '') | ||
+ '>'; | ||
if (tokens[idx].align) { | ||
return '<td style="text-align:' + tokens[idx].align + '">'; | ||
} | ||
return '<td>'; | ||
}; | ||
@@ -345,11 +345,49 @@ rules.td_close = function (/* tokens, idx, options, env */) { | ||
// Renderer class | ||
/** | ||
* new Renderer() | ||
* | ||
* Creates new [[Renderer]] instance and fill [[Renderer#rules]] with defaults. | ||
**/ | ||
function Renderer() { | ||
// Clone rules object to allow local modifications | ||
/** | ||
* Renderer#rules -> Object | ||
* | ||
* Contains render rules for tokens. Can be updated and extended. | ||
* | ||
* ##### Example | ||
* | ||
* ```javascript | ||
* var md = require('markdown-it')(); | ||
* | ||
* md.renderer.rules.strong_open = function () { return '<b>'; }; | ||
* md.renderer.rules.strong_close = function () { return '</b>'; }; | ||
* | ||
* var result = md.renderInline(...); | ||
* ``` | ||
* | ||
* Each rule is called as independed static function with fixed signature: | ||
* | ||
* ```javascript | ||
* function my_token_render(tokens, idx, options, env, self) { | ||
* // ... | ||
* return renderedHTML; | ||
* } | ||
* ``` | ||
* | ||
* See [source code](https://github.com/markdown-it/markdown-it/blob/master/lib/renderer.js) | ||
* for more details and examples. | ||
**/ | ||
this.rules = assign({}, rules); | ||
// exported helper, for custom rules only | ||
this.getBreak = getBreak; | ||
} | ||
/** | ||
* Renderer.renderInline(tokens, options, env) -> String | ||
* - tokens (Array): list on block tokens to renter | ||
* - options (Object): params of parser instance | ||
* - env (Object): additional data from parsed input (references, for example) | ||
* | ||
* The same as [[Renderer.render]], but for single token of `inline` type. | ||
**/ | ||
Renderer.prototype.renderInline = function (tokens, options, env) { | ||
@@ -367,2 +405,37 @@ var result = '', | ||
/** internal | ||
* Renderer.renderInlineAsText(tokens, options, env) -> String | ||
* - tokens (Array): list on block tokens to renter | ||
* - options (Object): params of parser instance | ||
* - env (Object): additional data from parsed input (references, for example) | ||
* | ||
* Special kludge for image `alt` attributes to conform CommonMark spec. | ||
* Don't try to use it! Spec requires to show `alt` content with stripped markup, | ||
* instead of simple escaping. | ||
**/ | ||
Renderer.prototype.renderInlineAsText = function (tokens, options, env) { | ||
var result = '', | ||
_rules = this.rules; | ||
for (var i = 0, len = tokens.length; i < len; i++) { | ||
if (tokens[i].type === 'text') { | ||
result += _rules.text(tokens, i, options, env, this); | ||
} else if (tokens[i].type === 'image') { | ||
result += this.renderInlineAsText(tokens[i].tokens, options, env); | ||
} | ||
} | ||
return result; | ||
}; | ||
/** | ||
* Renderer.render(tokens, options, env) -> String | ||
* - tokens (Array): list on block tokens to renter | ||
* - options (Object): params of parser instance | ||
* - env (Object): additional data from parsed input (references, for example) | ||
* | ||
* Takes token stream and generates HTML. Probably, you will never need to call | ||
* this method directly. | ||
**/ | ||
Renderer.prototype.render = function (tokens, options, env) { | ||
@@ -369,0 +442,0 @@ var i, len, |
186
lib/ruler.js
@@ -1,12 +0,24 @@ | ||
// Ruler is helper class to build responsibility chains from parse rules. | ||
// It allows: | ||
// | ||
// - easy stack rules chains | ||
// - getting main chain and named chains content (as arrays of functions) | ||
// | ||
/** | ||
* class Ruler | ||
* | ||
* Helper class, used by [[MarkdownIt#core]], [[MarkdownIt#block]] and | ||
* [[MarkdownIt#inline]] to manage sequences of functions (rules): | ||
* | ||
* - keep rules in defined order | ||
* - assign the name to each rule | ||
* - enable/disable rules | ||
* - add/replace rules | ||
* - allow assign rules to additional named chains (in the same) | ||
* - cacheing lists of active rules | ||
* | ||
* You will not need use this class directly until write plugins. For simple | ||
* rules control use [[MarkdownIt.disable]], [[MarkdownIt.enable]] and | ||
* [[MarkdownIt.use]]. | ||
**/ | ||
'use strict'; | ||
//////////////////////////////////////////////////////////////////////////////// | ||
/** | ||
* new Ruler() | ||
**/ | ||
function Ruler() { | ||
@@ -80,8 +92,27 @@ // List of added rules. Each element is: | ||
//////////////////////////////////////////////////////////////////////////////// | ||
// Public methods | ||
// Replace rule function | ||
// | ||
/** | ||
* Ruler.at(name, fn [, options]) | ||
* - name (String): rule name to replace. | ||
* - fn (Function): new rule function. | ||
* - options (Object): new rule options (not mandatory). | ||
* | ||
* Replace rule by name with new function & options. Throws error if name not | ||
* found. | ||
* | ||
* ##### Options: | ||
* | ||
* - __alt__ - array with names of "alternate" chains. | ||
* | ||
* ##### Example | ||
* | ||
* Replace existing typorgapher replacement rule with new one: | ||
* | ||
* ```javascript | ||
* var md = require('markdown-it')(); | ||
* | ||
* md.core.ruler.at('replacements', function replace(state) { | ||
* //... | ||
* }); | ||
* ``` | ||
**/ | ||
Ruler.prototype.at = function (name, fn, options) { | ||
@@ -99,4 +130,26 @@ var index = this.__find__(name); | ||
// Add rule to chain before one with given name. | ||
// | ||
/** | ||
* Ruler.before(beforeName, ruleName, fn [, options]) | ||
* - beforeName (String): new rule will be added before this one. | ||
* - ruleName (String): name of added rule. | ||
* - fn (Function): rule function. | ||
* - options (Object): rule options (not mandatory). | ||
* | ||
* Add new rule to chain before one with given name. See also | ||
* [[Ruler.after]], [[Ruler.push]]. | ||
* | ||
* ##### Options: | ||
* | ||
* - __alt__ - array with names of "alternate" chains. | ||
* | ||
* ##### Example | ||
* | ||
* ```javascript | ||
* var md = require('markdown-it')(); | ||
* | ||
* md.block.ruler.before('paragraph', 'my_rule', function replace(state) { | ||
* //... | ||
* }); | ||
* ``` | ||
**/ | ||
Ruler.prototype.before = function (beforeName, ruleName, fn, options) { | ||
@@ -119,4 +172,26 @@ var index = this.__find__(beforeName); | ||
// Add rule to chain after one with given name. | ||
// | ||
/** | ||
* Ruler.after(afterName, ruleName, fn [, options]) | ||
* - afterName (String): new rule will be added after this one. | ||
* - ruleName (String): name of added rule. | ||
* - fn (Function): rule function. | ||
* - options (Object): rule options (not mandatory). | ||
* | ||
* Add new rule to chain after one with given name. See also | ||
* [[Ruler.before]], [[Ruler.push]]. | ||
* | ||
* ##### Options: | ||
* | ||
* - __alt__ - array with names of "alternate" chains. | ||
* | ||
* ##### Example | ||
* | ||
* ```javascript | ||
* var md = require('markdown-it')(); | ||
* | ||
* md.inline.ruler.after('text', 'my_rule', function replace(state) { | ||
* //... | ||
* }); | ||
* ``` | ||
**/ | ||
Ruler.prototype.after = function (afterName, ruleName, fn, options) { | ||
@@ -138,4 +213,25 @@ var index = this.__find__(afterName); | ||
// Add rule to the end of chain. | ||
// | ||
/** | ||
* Ruler.push(ruleName, fn [, options]) | ||
* - ruleName (String): name of added rule. | ||
* - fn (Function): rule function. | ||
* - options (Object): rule options (not mandatory). | ||
* | ||
* Push new rule to the end of chain. See also | ||
* [[Ruler.before]], [[Ruler.after]]. | ||
* | ||
* ##### Options: | ||
* | ||
* - __alt__ - array with names of "alternate" chains. | ||
* | ||
* ##### Example | ||
* | ||
* ```javascript | ||
* var md = require('markdown-it')(); | ||
* | ||
* md.core.ruler.push('emphasis', 'my_rule', function replace(state) { | ||
* //... | ||
* }); | ||
* ``` | ||
**/ | ||
Ruler.prototype.push = function (ruleName, fn, options) { | ||
@@ -155,4 +251,12 @@ var opt = options || {}; | ||
// Enable rules by names. | ||
// | ||
/** | ||
* Ruler.enable(list [, ignoreInvalid]) | ||
* - list (String|Array): list of rule names to enable. | ||
* - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found. | ||
* | ||
* Enable rules with given names. If any rule name not found - throw Error. | ||
* Errors can be disabled by second param. | ||
* | ||
* See also [[Ruler.disable]], [[Ruler.enableOnly]]. | ||
**/ | ||
Ruler.prototype.enable = function (list, ignoreInvalid) { | ||
@@ -176,4 +280,12 @@ if (!Array.isArray(list)) { list = [ list ]; } | ||
// Enable rules by whitelisted names (others will be disables). | ||
// | ||
/** | ||
* Ruler.enableOnly(list [, ignoreInvalid]) | ||
* - list (String|Array): list of rule names to enable (whitelist). | ||
* - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found. | ||
* | ||
* Enable rules with given names, and disable everything else. If any rule name | ||
* not found - throw Error. Errors can be disabled by second param. | ||
* | ||
* See also [[Ruler.disable]], [[Ruler.enable]]. | ||
**/ | ||
Ruler.prototype.enableOnly = function (list, ignoreInvalid) { | ||
@@ -188,4 +300,12 @@ if (!Array.isArray(list)) { list = [ list ]; } | ||
// Disable rules by names. | ||
// | ||
/** | ||
* Ruler.disable(list [, ignoreInvalid]) | ||
* - list (String|Array): list of rule names to disable. | ||
* - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found. | ||
* | ||
* Disable rules with given names. If any rule name not found - throw Error. | ||
* Errors can be disabled by second param. | ||
* | ||
* See also [[Ruler.enable]], [[Ruler.enableOnly]]. | ||
**/ | ||
Ruler.prototype.disable = function (list, ignoreInvalid) { | ||
@@ -211,4 +331,11 @@ if (!Array.isArray(list)) { | ||
// Get rules list as array of functions. | ||
// | ||
/** | ||
* Ruler.getRules(chainName) -> Array | ||
* | ||
* Return array of active functions (rules) for given chain name. It analyzes | ||
* rules configuration, compiles caches if not exists and returns result. | ||
* | ||
* Default chain name is `''` (empty string). It can't be skipped. That's | ||
* done intentionally, to keep signature monomorphic for high speed. | ||
**/ | ||
Ruler.prototype.getRules = function (chainName) { | ||
@@ -219,5 +346,6 @@ if (this.__cache__ === null) { | ||
return this.__cache__[chainName]; | ||
// Chain can be empty, if rules disabled. But we still have to return Array. | ||
return this.__cache__[chainName] || []; | ||
}; | ||
module.exports = Ruler; |
@@ -7,8 +7,6 @@ // Parse abbreviation definitions, i.e. `*[abbr]: description` | ||
var StateInline = require('../rules_inline/state_inline'); | ||
var parseLinkLabel = require('../helpers/parse_link_label'); | ||
function parseAbbr(str, parserInline, options, env) { | ||
var state, labelEnd, pos, max, label, title; | ||
var pos, label, title, ch, | ||
max = str.length, | ||
labelEnd = -1; | ||
@@ -20,15 +18,22 @@ if (str.charCodeAt(0) !== 0x2A/* * */) { return -1; } | ||
state = new StateInline(str, parserInline, options, env, []); | ||
labelEnd = parseLinkLabel(state, 1); | ||
for (pos = 2; pos < max; pos++) { | ||
ch = str.charCodeAt(pos); | ||
if (ch === 0x5B /* [ */) { | ||
return -1; | ||
} else if (ch === 0x5D /* ] */) { | ||
labelEnd = pos; | ||
break; | ||
} else if (ch === 0x5C /* \ */) { | ||
pos++; | ||
} | ||
} | ||
if (labelEnd < 0 || str.charCodeAt(labelEnd + 1) !== 0x3A/* : */) { return -1; } | ||
max = state.posMax; | ||
// abbr title is always one line, so looking for ending "\n" here | ||
for (pos = labelEnd + 2; pos < max; pos++) { | ||
if (state.src.charCodeAt(pos) === 0x0A) { break; } | ||
if (str.charCodeAt(pos) === 0x0A) { break; } | ||
} | ||
label = str.slice(2, labelEnd); | ||
label = str.slice(2, labelEnd).replace(/\\(.)/g, '$1'); | ||
title = str.slice(labelEnd + 2, pos).trim(); | ||
@@ -35,0 +40,0 @@ if (title.length === 0) { return -1; } |
@@ -5,3 +5,2 @@ 'use strict'; | ||
var StateInline = require('../rules_inline/state_inline'); | ||
var parseLinkLabel = require('../helpers/parse_link_label'); | ||
var parseLinkDestination = require('../helpers/parse_link_destination'); | ||
@@ -13,3 +12,4 @@ var parseLinkTitle = require('../helpers/parse_link_title'); | ||
function parseReference(str, parser, options, env) { | ||
var state, labelEnd, pos, max, code, start, href, title, label; | ||
var state, pos, code, start, href, title, label, ch, max, | ||
labelEnd = -1; | ||
@@ -21,8 +21,18 @@ if (str.charCodeAt(0) !== 0x5B/* [ */) { return -1; } | ||
state = new StateInline(str, parser, options, env, []); | ||
labelEnd = parseLinkLabel(state, 0); | ||
max = state.posMax; | ||
for (pos = 1; pos < max; pos++) { | ||
ch = str.charCodeAt(pos); | ||
if (ch === 0x5B /* [ */) { | ||
return -1; | ||
} else if (ch === 0x5D /* ] */) { | ||
labelEnd = pos; | ||
break; | ||
} else if (ch === 0x5C /* \ */) { | ||
pos++; | ||
} | ||
} | ||
if (labelEnd < 0 || str.charCodeAt(labelEnd + 1) !== 0x3A/* : */) { return -1; } | ||
max = state.posMax; | ||
// [label]: destination 'title' | ||
@@ -29,0 +39,0 @@ // ^^^ skip optional whitespace here |
// Simple typographyc replacements | ||
// | ||
// '' → ‘’ | ||
// "" → “”. Set '«»' for Russian, '„“' for German, empty to disable | ||
// (c) (C) → © | ||
// (tm) (TM) → ™ | ||
// (r) (R) → ® | ||
// +- → ± | ||
// (p) (P) -> § | ||
// ... → … (also ?.... → ?.., !.... → !..) | ||
// ???????? → ???, !!!!! → !!!, `,,` → `,` | ||
// -- → –, --- → — | ||
// | ||
'use strict'; | ||
@@ -4,0 +15,0 @@ |
@@ -1,60 +0,78 @@ | ||
// Process ~~deleted text~~ | ||
'use strict'; | ||
module.exports = function del(state, silent) { | ||
var found, | ||
pos, | ||
// parse sequence of markers, | ||
// "start" should point at a valid marker | ||
function scanDelims(state, start) { | ||
var pos = start, lastChar, nextChar, count, | ||
can_open = true, | ||
can_close = true, | ||
max = state.posMax, | ||
marker = state.src.charCodeAt(start); | ||
lastChar = start > 0 ? state.src.charCodeAt(start - 1) : -1; | ||
while (pos < max && state.src.charCodeAt(pos) === marker) { pos++; } | ||
if (pos >= max) { can_open = false; } | ||
count = pos - start; | ||
nextChar = pos < max ? state.src.charCodeAt(pos) : -1; | ||
// check whitespace conditions | ||
if (nextChar === 0x20 || nextChar === 0x0A) { can_open = false; } | ||
if (lastChar === 0x20 || lastChar === 0x0A) { can_close = false; } | ||
return { | ||
can_open: can_open, | ||
can_close: can_close, | ||
delims: count | ||
}; | ||
} | ||
module.exports = function(state, silent) { | ||
var startCount, | ||
count, | ||
tagCount, | ||
found, | ||
stack, | ||
res, | ||
max = state.posMax, | ||
start = state.pos, | ||
lastChar, | ||
nextChar; | ||
marker = state.src.charCodeAt(start); | ||
if (state.src.charCodeAt(start) !== 0x7E/* ~ */) { return false; } | ||
if (marker !== 0x7E/* ~ */) { return false; } | ||
if (silent) { return false; } // don't run any pairs in validation mode | ||
if (start + 4 >= max) { return false; } | ||
if (state.src.charCodeAt(start + 1) !== 0x7E/* ~ */) { return false; } | ||
if (state.level >= state.options.maxNesting) { return false; } | ||
lastChar = start > 0 ? state.src.charCodeAt(start - 1) : -1; | ||
nextChar = state.src.charCodeAt(start + 2); | ||
if (lastChar === 0x7E/* ~ */) { return false; } | ||
if (nextChar === 0x7E/* ~ */) { return false; } | ||
if (nextChar === 0x20 || nextChar === 0x0A) { return false; } | ||
pos = start + 2; | ||
while (pos < max && state.src.charCodeAt(pos) === 0x7E/* ~ */) { pos++; } | ||
if (pos > start + 3) { | ||
// sequence of 4+ markers taking as literal, same as in a emphasis | ||
state.pos += pos - start; | ||
if (!silent) { state.pending += state.src.slice(start, pos); } | ||
res = scanDelims(state, start); | ||
startCount = res.delims; | ||
if (!res.can_open) { | ||
state.pos += startCount; | ||
if (!silent) { state.pending += state.src.slice(start, state.pos); } | ||
return true; | ||
} | ||
state.pos = start + 2; | ||
stack = 1; | ||
if (state.level >= state.options.maxNesting) { return false; } | ||
stack = Math.floor(startCount / 2); | ||
if (stack <= 0) { return false; } | ||
state.pos = start + startCount; | ||
while (state.pos + 1 < max) { | ||
if (state.src.charCodeAt(state.pos) === 0x7E/* ~ */) { | ||
if (state.src.charCodeAt(state.pos + 1) === 0x7E/* ~ */) { | ||
lastChar = state.src.charCodeAt(state.pos - 1); | ||
nextChar = state.pos + 2 < max ? state.src.charCodeAt(state.pos + 2) : -1; | ||
if (nextChar !== 0x7E/* ~ */ && lastChar !== 0x7E/* ~ */) { | ||
if (lastChar !== 0x20 && lastChar !== 0x0A) { | ||
// closing '~~' | ||
stack--; | ||
} else if (nextChar !== 0x20 && nextChar !== 0x0A) { | ||
// opening '~~' | ||
stack++; | ||
} // else { | ||
// // standalone ' ~~ ' indented with spaces | ||
// } | ||
if (stack <= 0) { | ||
found = true; | ||
break; | ||
} | ||
while (state.pos < max) { | ||
if (state.src.charCodeAt(state.pos) === marker) { | ||
res = scanDelims(state, state.pos); | ||
count = res.delims; | ||
tagCount = Math.floor(count / 2); | ||
if (res.can_close) { | ||
if (tagCount >= stack) { | ||
state.pos += count - 2; | ||
found = true; | ||
break; | ||
} | ||
stack -= tagCount; | ||
state.pos += count; | ||
continue; | ||
} | ||
if (res.can_open) { stack += tagCount; } | ||
state.pos += count; | ||
continue; | ||
} | ||
@@ -61,0 +79,0 @@ |
@@ -27,17 +27,12 @@ // Process *this* and _that_ | ||
if (count >= 4) { | ||
// sequence of four or more unescaped markers can't start/end an emphasis | ||
can_open = can_close = false; | ||
} else { | ||
nextChar = pos < max ? state.src.charCodeAt(pos) : -1; | ||
nextChar = pos < max ? state.src.charCodeAt(pos) : -1; | ||
// check whitespace conditions | ||
if (nextChar === 0x20 || nextChar === 0x0A) { can_open = false; } | ||
if (lastChar === 0x20 || lastChar === 0x0A) { can_close = false; } | ||
// check whitespace conditions | ||
if (nextChar === 0x20 || nextChar === 0x0A) { can_open = false; } | ||
if (lastChar === 0x20 || lastChar === 0x0A) { can_close = false; } | ||
if (marker === 0x5F /* _ */) { | ||
// check if we aren't inside the word | ||
if (isAlphaNum(lastChar)) { can_open = false; } | ||
if (isAlphaNum(nextChar)) { can_close = false; } | ||
} | ||
if (marker === 0x5F /* _ */) { | ||
// check if we aren't inside the word | ||
if (isAlphaNum(lastChar)) { can_open = false; } | ||
if (isAlphaNum(nextChar)) { can_close = false; } | ||
} | ||
@@ -130,15 +125,13 @@ | ||
if (!silent) { | ||
if (startCount === 2 || startCount === 3) { | ||
// we have `startCount` starting and ending markers, | ||
// now trying to serialize them into tokens | ||
for (count = startCount; count > 1; count -= 2) { | ||
state.push({ type: 'strong_open', level: state.level++ }); | ||
} | ||
if (startCount === 1 || startCount === 3) { | ||
state.push({ type: 'em_open', level: state.level++ }); | ||
} | ||
if (count % 2) { state.push({ type: 'em_open', level: state.level++ }); } | ||
state.parser.tokenize(state); | ||
if (startCount === 1 || startCount === 3) { | ||
state.push({ type: 'em_close', level: --state.level }); | ||
} | ||
if (startCount === 2 || startCount === 3) { | ||
if (count % 2) { state.push({ type: 'em_close', level: --state.level }); } | ||
for (count = startCount; count > 1; count -= 2) { | ||
state.push({ type: 'strong_close', level: --state.level }); | ||
@@ -145,0 +138,0 @@ } |
@@ -43,7 +43,5 @@ // Process inline footnotes (^[...]) | ||
}); | ||
state.linkLevel++; | ||
oldLength = state.tokens.length; | ||
state.parser.tokenize(state); | ||
state.env.footnotes.list[footnoteId] = { tokens: state.tokens.splice(oldLength) }; | ||
state.linkLevel--; | ||
} | ||
@@ -50,0 +48,0 @@ |
@@ -1,60 +0,78 @@ | ||
// Process ++inserted text++ | ||
'use strict'; | ||
module.exports = function ins(state, silent) { | ||
var found, | ||
pos, | ||
// parse sequence of markers, | ||
// "start" should point at a valid marker | ||
function scanDelims(state, start) { | ||
var pos = start, lastChar, nextChar, count, | ||
can_open = true, | ||
can_close = true, | ||
max = state.posMax, | ||
marker = state.src.charCodeAt(start); | ||
lastChar = start > 0 ? state.src.charCodeAt(start - 1) : -1; | ||
while (pos < max && state.src.charCodeAt(pos) === marker) { pos++; } | ||
if (pos >= max) { can_open = false; } | ||
count = pos - start; | ||
nextChar = pos < max ? state.src.charCodeAt(pos) : -1; | ||
// check whitespace conditions | ||
if (nextChar === 0x20 || nextChar === 0x0A) { can_open = false; } | ||
if (lastChar === 0x20 || lastChar === 0x0A) { can_close = false; } | ||
return { | ||
can_open: can_open, | ||
can_close: can_close, | ||
delims: count | ||
}; | ||
} | ||
module.exports = function(state, silent) { | ||
var startCount, | ||
count, | ||
tagCount, | ||
found, | ||
stack, | ||
res, | ||
max = state.posMax, | ||
start = state.pos, | ||
lastChar, | ||
nextChar; | ||
marker = state.src.charCodeAt(start); | ||
if (state.src.charCodeAt(start) !== 0x2B/* + */) { return false; } | ||
if (marker !== 0x2B/* + */) { return false; } | ||
if (silent) { return false; } // don't run any pairs in validation mode | ||
if (start + 4 >= max) { return false; } | ||
if (state.src.charCodeAt(start + 1) !== 0x2B/* + */) { return false; } | ||
if (state.level >= state.options.maxNesting) { return false; } | ||
lastChar = start > 0 ? state.src.charCodeAt(start - 1) : -1; | ||
nextChar = state.src.charCodeAt(start + 2); | ||
if (lastChar === 0x2B/* + */) { return false; } | ||
if (nextChar === 0x2B/* + */) { return false; } | ||
if (nextChar === 0x20 || nextChar === 0x0A) { return false; } | ||
pos = start + 2; | ||
while (pos < max && state.src.charCodeAt(pos) === 0x2B/* + */) { pos++; } | ||
if (pos !== start + 2) { | ||
// sequence of 3+ markers taking as literal, same as in a emphasis | ||
state.pos += pos - start; | ||
if (!silent) { state.pending += state.src.slice(start, pos); } | ||
res = scanDelims(state, start); | ||
startCount = res.delims; | ||
if (!res.can_open) { | ||
state.pos += startCount; | ||
if (!silent) { state.pending += state.src.slice(start, state.pos); } | ||
return true; | ||
} | ||
state.pos = start + 2; | ||
stack = 1; | ||
if (state.level >= state.options.maxNesting) { return false; } | ||
stack = Math.floor(startCount / 2); | ||
if (stack <= 0) { return false; } | ||
state.pos = start + startCount; | ||
while (state.pos + 1 < max) { | ||
if (state.src.charCodeAt(state.pos) === 0x2B/* + */) { | ||
if (state.src.charCodeAt(state.pos + 1) === 0x2B/* + */) { | ||
lastChar = state.src.charCodeAt(state.pos - 1); | ||
nextChar = state.pos + 2 < max ? state.src.charCodeAt(state.pos + 2) : -1; | ||
if (nextChar !== 0x2B/* + */ && lastChar !== 0x2B/* + */) { | ||
if (lastChar !== 0x20 && lastChar !== 0x0A) { | ||
// closing '++' | ||
stack--; | ||
} else if (nextChar !== 0x20 && nextChar !== 0x0A) { | ||
// opening '++' | ||
stack++; | ||
} // else { | ||
// // standalone ' ++ ' indented with spaces | ||
// } | ||
if (stack <= 0) { | ||
found = true; | ||
break; | ||
} | ||
while (state.pos < max) { | ||
if (state.src.charCodeAt(state.pos) === marker) { | ||
res = scanDelims(state, state.pos); | ||
count = res.delims; | ||
tagCount = Math.floor(count / 2); | ||
if (res.can_close) { | ||
if (tagCount >= stack) { | ||
state.pos += count - 2; | ||
found = true; | ||
break; | ||
} | ||
stack -= tagCount; | ||
state.pos += count; | ||
continue; | ||
} | ||
if (res.can_open) { stack += tagCount; } | ||
state.pos += count; | ||
continue; | ||
} | ||
@@ -61,0 +79,0 @@ |
@@ -9,13 +9,15 @@ // Process [links](<to> "stuff") | ||
var normalizeReference = require('../helpers/normalize_reference'); | ||
var StateInline = require('../rules_inline/state_inline'); | ||
module.exports = function links(state, silent) { | ||
var labelStart, | ||
var code, | ||
href, | ||
label, | ||
labelEnd, | ||
label, | ||
href, | ||
title, | ||
labelStart, | ||
pos, | ||
ref, | ||
code, | ||
title, | ||
tokens, | ||
isImage = false, | ||
@@ -36,3 +38,3 @@ oldPos = state.pos, | ||
labelStart = start + 1; | ||
labelEnd = parseLinkLabel(state, start); | ||
labelEnd = parseLinkLabel(state, start, !isImage); | ||
@@ -100,6 +102,4 @@ // parser failed to find ']', so it's not a valid link | ||
// | ||
if (typeof state.env.references === 'undefined') { return false; } | ||
// do not allow nested reference links | ||
if (state.linkLevel > 0) { return false; } | ||
// [foo] [bar] | ||
@@ -118,4 +118,6 @@ // ^^ optional whitespace (can include newlines) | ||
} else { | ||
pos = start - 1; | ||
pos = labelEnd + 1; | ||
} | ||
} else { | ||
pos = labelEnd + 1; | ||
} | ||
@@ -145,2 +147,11 @@ | ||
if (isImage) { | ||
var newState = new StateInline( | ||
state.src.slice(labelStart, labelEnd), | ||
state.parser, | ||
state.options, | ||
state.env, | ||
tokens = [] | ||
); | ||
newState.parser.tokenize(newState); | ||
state.push({ | ||
@@ -150,3 +161,3 @@ type: 'image', | ||
title: title, | ||
alt: state.src.substr(labelStart, labelEnd - labelStart), | ||
tokens: tokens, | ||
level: state.level | ||
@@ -161,5 +172,3 @@ }); | ||
}); | ||
state.linkLevel++; | ||
state.parser.tokenize(state); | ||
state.linkLevel--; | ||
state.push({ type: 'link_close', level: --state.level }); | ||
@@ -166,0 +175,0 @@ } |
@@ -1,60 +0,78 @@ | ||
// Process ==highlighted text== | ||
'use strict'; | ||
module.exports = function del(state, silent) { | ||
var found, | ||
pos, | ||
// parse sequence of markers, | ||
// "start" should point at a valid marker | ||
function scanDelims(state, start) { | ||
var pos = start, lastChar, nextChar, count, | ||
can_open = true, | ||
can_close = true, | ||
max = state.posMax, | ||
marker = state.src.charCodeAt(start); | ||
lastChar = start > 0 ? state.src.charCodeAt(start - 1) : -1; | ||
while (pos < max && state.src.charCodeAt(pos) === marker) { pos++; } | ||
if (pos >= max) { can_open = false; } | ||
count = pos - start; | ||
nextChar = pos < max ? state.src.charCodeAt(pos) : -1; | ||
// check whitespace conditions | ||
if (nextChar === 0x20 || nextChar === 0x0A) { can_open = false; } | ||
if (lastChar === 0x20 || lastChar === 0x0A) { can_close = false; } | ||
return { | ||
can_open: can_open, | ||
can_close: can_close, | ||
delims: count | ||
}; | ||
} | ||
module.exports = function(state, silent) { | ||
var startCount, | ||
count, | ||
tagCount, | ||
found, | ||
stack, | ||
res, | ||
max = state.posMax, | ||
start = state.pos, | ||
lastChar, | ||
nextChar; | ||
marker = state.src.charCodeAt(start); | ||
if (state.src.charCodeAt(start) !== 0x3D/* = */) { return false; } | ||
if (marker !== 0x3D/* = */) { return false; } | ||
if (silent) { return false; } // don't run any pairs in validation mode | ||
if (start + 4 >= max) { return false; } | ||
if (state.src.charCodeAt(start + 1) !== 0x3D/* = */) { return false; } | ||
if (state.level >= state.options.maxNesting) { return false; } | ||
lastChar = start > 0 ? state.src.charCodeAt(start - 1) : -1; | ||
nextChar = state.src.charCodeAt(start + 2); | ||
if (lastChar === 0x3D/* = */) { return false; } | ||
if (nextChar === 0x3D/* = */) { return false; } | ||
if (nextChar === 0x20 || nextChar === 0x0A) { return false; } | ||
pos = start + 2; | ||
while (pos < max && state.src.charCodeAt(pos) === 0x3D/* = */) { pos++; } | ||
if (pos !== start + 2) { | ||
// sequence of 3+ markers taking as literal, same as in a emphasis | ||
state.pos += pos - start; | ||
if (!silent) { state.pending += state.src.slice(start, pos); } | ||
res = scanDelims(state, start); | ||
startCount = res.delims; | ||
if (!res.can_open) { | ||
state.pos += startCount; | ||
if (!silent) { state.pending += state.src.slice(start, state.pos); } | ||
return true; | ||
} | ||
state.pos = start + 2; | ||
stack = 1; | ||
if (state.level >= state.options.maxNesting) { return false; } | ||
stack = Math.floor(startCount / 2); | ||
if (stack <= 0) { return false; } | ||
state.pos = start + startCount; | ||
while (state.pos + 1 < max) { | ||
if (state.src.charCodeAt(state.pos) === 0x3D/* = */) { | ||
if (state.src.charCodeAt(state.pos + 1) === 0x3D/* = */) { | ||
lastChar = state.src.charCodeAt(state.pos - 1); | ||
nextChar = state.pos + 2 < max ? state.src.charCodeAt(state.pos + 2) : -1; | ||
if (nextChar !== 0x3D/* = */ && lastChar !== 0x3D/* = */) { | ||
if (lastChar !== 0x20 && lastChar !== 0x0A) { | ||
// closing '==' | ||
stack--; | ||
} else if (nextChar !== 0x20 && nextChar !== 0x0A) { | ||
// opening '==' | ||
stack++; | ||
} // else { | ||
// // standalone ' == ' indented with spaces | ||
// } | ||
if (stack <= 0) { | ||
found = true; | ||
break; | ||
} | ||
while (state.pos < max) { | ||
if (state.src.charCodeAt(state.pos) === marker) { | ||
res = scanDelims(state, state.pos); | ||
count = res.delims; | ||
tagCount = Math.floor(count / 2); | ||
if (res.can_close) { | ||
if (tagCount >= stack) { | ||
state.pos += count - 2; | ||
found = true; | ||
break; | ||
} | ||
stack -= tagCount; | ||
state.pos += count; | ||
continue; | ||
} | ||
if (res.can_open) { stack += tagCount; } | ||
state.pos += count; | ||
continue; | ||
} | ||
@@ -61,0 +79,0 @@ |
@@ -23,9 +23,2 @@ // Inline parser state | ||
this.isInLabel = false; // Set true when seek link label - we should disable | ||
// "paired" rules (emphasis, strikes) to not skip | ||
// tailing `]` | ||
this.linkLevel = 0; // Increment for each nesting link. Used to prevent | ||
// nesting in definitions | ||
this.linkContent = ''; // Temporary storage for link url | ||
@@ -32,0 +25,0 @@ |
{ | ||
"name": "markdown-it", | ||
"version": "2.1.3", | ||
"version": "2.2.0", | ||
"description": "Markdown-it - modern pluggable markdown parser.", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
123
README.md
@@ -14,3 +14,3 @@ # markdown-it | ||
- High speed! | ||
- 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. | ||
- 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. | ||
@@ -20,11 +20,5 @@ __Table of content__ | ||
- [Install](#install) | ||
- [Usage](#usage) | ||
- [Configuring](#configuring) | ||
- [constructor(preset, options)](#constructorpreset-options) | ||
- [.set({ keys: values })](#set-keys-values-) | ||
- [.use(plugin, options)](#useplugin-options) | ||
- [Syntax highlighting](#syntax-highlighting) | ||
- [Typographer](#typographer) | ||
- [Syntax extensions](#syntax-extensions) | ||
- [Manage rules](#manage-rules) | ||
- [Usage examples](#usage-examples) | ||
- [API](#api) | ||
- [Syntax extensions](#syntax-extensions) | ||
- [Benchmark](#benchmark) | ||
@@ -49,4 +43,10 @@ - [Authors](#authors) | ||
## Usage | ||
## Usage examples | ||
See __[API documentation](https://markdown-it.github.io/markdown-it/)__ for more | ||
info and examples. | ||
### Simple | ||
```js | ||
@@ -56,7 +56,7 @@ // node.js, "classic" way: | ||
md = new MarkdownIt(); | ||
console.log(md.render('# markdown-it rulezz!')); | ||
var result = md.render('# markdown-it rulezz!'); | ||
// node.js, the same, but with sugar: | ||
var md = require('markdown-it')(); | ||
console.log(md.render('# markdown-it rulezz!')); | ||
var result = md.render('# markdown-it rulezz!'); | ||
@@ -66,27 +66,18 @@ // browser without AMD, added to "window" on script load | ||
var md = window.markdownit(); | ||
console.log(md.render('# markdown-it rulezz!')); | ||
var result = md.render('# markdown-it rulezz!'); | ||
``` | ||
Single line rendering, without paragraph wrap: | ||
### Configuring | ||
```js | ||
var md = require('markdown-it')(); | ||
var result = md.renderInline('__markdown-it__ rulezz!'); | ||
``` | ||
By default `markdown-it` is configured to be similar to GFM, but with HTML disabled. | ||
This is easy to change if you prefer different settings. | ||
Usually, you will define everything via constructor. | ||
### Init with presets and options | ||
#### constructor(preset, options) | ||
(*) preset define combination of active rules and options. Can be | ||
`"full"`|`"commonmark"`|`"zero"` or `"default"` if skipped. | ||
__preset__ (String) - `"full"`|`"commonmark"`, optional. | ||
`markdown-it` offers some presets as a convenience to quickly enable/disable | ||
active syntax rules and options for common use cases. | ||
- ["commonmark"](https://github.com/markdown-it/markdown-it/blob/master/lib/presets/commonmark.js) - enable strict [CommonMark](http://commonmark.org/) mode. | ||
- ["full"](https://github.com/markdown-it/markdown-it/blob/master/lib/presets/full.js) - | ||
all rules enabled, but still without html, typographer & autolinker. | ||
- [default](https://github.com/markdown-it/markdown-it/blob/master/lib/presets/default.js) - | ||
when no preset name given. | ||
```js | ||
@@ -105,8 +96,4 @@ // commonmark mode | ||
}); | ||
``` | ||
__options__ | ||
```js | ||
// Actual default values | ||
// full options list (defaults) | ||
var md = require('markdown-it')({ | ||
@@ -134,25 +121,6 @@ html: false, // Enable HTML tags in source | ||
### Plugins load | ||
#### .set({ keys: values }) | ||
Probably, you will never need it. But you can change options after | ||
constructor call. | ||
```js | ||
var md = require('markdown-it')() | ||
.set({ html: true, breaks: true }) | ||
.set({ typographer, true }); | ||
``` | ||
**Note:** To achieve the best possible performance, don't modify a `markdown-it` | ||
instance on the fly. If you need multiple configurations it's best to create | ||
multiple instances and initialize each with separate config. | ||
#### .use(plugin, options) | ||
Sugar to activate plugins. | ||
```js | ||
var md = require('markdown-it')() | ||
.use(plugin1) | ||
@@ -177,3 +145,3 @@ .use(plugin2, opts) | ||
return hljs.highlight(lang, str).value; | ||
} catch (err) {} | ||
} catch (__) {} | ||
} | ||
@@ -183,3 +151,3 @@ | ||
return hljs.highlightAuto(str).value; | ||
} catch (err) {} | ||
} catch (__) {} | ||
@@ -192,37 +160,9 @@ return ''; // use external default escaping | ||
### Typographer | ||
## API | ||
Although full-weight typographical replacements are language specific, `markdown-it` | ||
provides coverage for the most common and universal use cases: | ||
[API documentation](https://markdown-it.github.io/markdown-it/) | ||
```js | ||
var md = require('markdown-it')({ | ||
typographer: true, | ||
quotes: '“”‘’' | ||
}); | ||
// Disable rules at all: | ||
md.disable([ 'replacements', 'smartquotes' ]); | ||
## Syntax extensions | ||
// Actual default replacements: | ||
// | ||
// '' → ‘’ | ||
// "" → “”. Set '«»' for Russian, '„“' for German, empty to disable | ||
// (c) (C) → © | ||
// (tm) (TM) → ™ | ||
// (r) (R) → ® | ||
// +- → ± | ||
// (p) (P) -> § | ||
// ... → … (also ?.... → ?.., !.... → !..) | ||
// ???????? → ???, !!!!! → !!!, `,,` → `,` | ||
// -- → –, --- → — | ||
// | ||
``` | ||
Of course, you can also add your own rules or replace the defaults with something | ||
more advanced or specific to your language. | ||
### Syntax extensions | ||
Enabled by default: | ||
@@ -298,3 +238,3 @@ | ||
As you can see, `markdown-it` doesn't pay with speed for it's flexibility. | ||
Because it's written in monomorphyc style and use JIT inline caches effectively. | ||
Because it's written in monomorphyc style and uses JIT inline caches effectively. | ||
@@ -307,2 +247,5 @@ | ||
_markdown-it_ is the result of the decision of the authors who contributed to | ||
99% of the _Remarkable_ code to move to a project with the same authorship but | ||
new leadership (Vitaly and Alex). It's not a fork. | ||
@@ -309,0 +252,0 @@ ## References / Thanks |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
591099
69
16123
256