2013-12-13 19:58:31 +01:00
|
|
|
/**
|
|
|
|
* marked - a markdown parser
|
2016-11-04 15:55:33 +01:00
|
|
|
* Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed)
|
2013-12-13 19:58:31 +01:00
|
|
|
* https://github.com/chjj/marked
|
|
|
|
*/
|
|
|
|
|
|
|
|
;(function() {
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Block-Level Grammar
|
|
|
|
*/
|
|
|
|
|
|
|
|
var block = {
|
|
|
|
newline: /^\n+/,
|
|
|
|
code: /^( {4}[^\n]+\n*)+/,
|
|
|
|
fences: noop,
|
|
|
|
hr: /^( *[-*_]){3,} *(?:\n+|$)/,
|
2019-07-31 08:04:32 +02:00
|
|
|
heading: /^ {0,3}(#{1,6}) +([^\n]*?)(?: +#+)? *(?:\n+|$)/,
|
2013-12-13 19:58:31 +01:00
|
|
|
nptable: noop,
|
2020-10-30 20:10:29 +01:00
|
|
|
blockquote: /^(?!( *>\s*($|\n))*($|\n))( *>[^\n]*(\n(?!def)[^\n]+)*)+/,
|
2016-11-04 15:55:33 +01:00
|
|
|
list: /^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,
|
|
|
|
html: /^ *(?:comment *(?:\n|\s*$)|closed *(?:\n{2,}|\s*$)|closing *(?:\n{2,}|\s*$))/,
|
2013-12-13 19:58:31 +01:00
|
|
|
def: /^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/,
|
|
|
|
table: noop,
|
2019-07-31 08:04:32 +02:00
|
|
|
paragraph: /^((?:[^\n]+\n?(?!hr|heading|blockquote|tag|def))+)\n*/,
|
2013-12-13 19:58:31 +01:00
|
|
|
text: /^[^\n]+/
|
|
|
|
};
|
|
|
|
|
2019-08-11 07:41:34 +02:00
|
|
|
block.bullet = /(?:[*+-]|\d{1,9}\.)/;
|
2013-12-13 19:58:31 +01:00
|
|
|
block.item = /^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/;
|
|
|
|
block.item = replace(block.item, 'gm')
|
|
|
|
(/bull/g, block.bullet)
|
|
|
|
();
|
|
|
|
|
|
|
|
block.list = replace(block.list)
|
|
|
|
(/bull/g, block.bullet)
|
2016-11-04 15:55:33 +01:00
|
|
|
('hr', '\\n+(?=\\1?(?:[-*_] *){3,}(?:\\n+|$))')
|
|
|
|
('def', '\\n+(?=' + block.def.source + ')')
|
|
|
|
();
|
|
|
|
|
|
|
|
block.blockquote = replace(block.blockquote)
|
|
|
|
('def', block.def)
|
2013-12-13 19:58:31 +01:00
|
|
|
();
|
|
|
|
|
|
|
|
block._tag = '(?!(?:'
|
|
|
|
+ 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code'
|
|
|
|
+ '|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo'
|
2014-01-27 16:37:46 +01:00
|
|
|
+ '|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|[^\\w\\s@]*@)\\b';
|
2013-12-13 19:58:31 +01:00
|
|
|
|
|
|
|
block.html = replace(block.html)
|
|
|
|
('comment', /<!--[\s\S]*?-->/)
|
|
|
|
('closed', /<(tag)[\s\S]+?<\/\1>/)
|
|
|
|
('closing', /<tag(?:"[^"]*"|'[^']*'|[^'">])*?>/)
|
|
|
|
(/tag/g, block._tag)
|
|
|
|
();
|
|
|
|
|
|
|
|
block.paragraph = replace(block.paragraph)
|
|
|
|
('hr', block.hr)
|
2019-07-31 08:04:32 +02:00
|
|
|
('heading', ' {0,3}#{1,6} +')
|
2013-12-13 19:58:31 +01:00
|
|
|
('blockquote', block.blockquote)
|
|
|
|
('tag', '<' + block._tag)
|
|
|
|
('def', block.def)
|
|
|
|
();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Normal Block Grammar
|
|
|
|
*/
|
|
|
|
|
|
|
|
block.normal = merge({}, block);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* GFM Block Grammar
|
|
|
|
*/
|
|
|
|
|
|
|
|
block.gfm = merge({}, block.normal, {
|
2016-11-04 15:55:33 +01:00
|
|
|
fences: /^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\s*\1 *(?:\n+|$)/,
|
|
|
|
paragraph: /^/,
|
2013-12-13 19:58:31 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
block.gfm.paragraph = replace(block.paragraph)
|
|
|
|
('(?!', '(?!'
|
|
|
|
+ block.gfm.fences.source.replace('\\1', '\\2') + '|'
|
|
|
|
+ block.list.source.replace('\\1', '\\3') + '|')
|
|
|
|
();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* GFM + Tables Block Grammar
|
|
|
|
*/
|
|
|
|
|
|
|
|
block.tables = merge({}, block.gfm, {
|
|
|
|
nptable: /^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/,
|
|
|
|
table: /^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/
|
|
|
|
});
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Block Lexer
|
|
|
|
*/
|
|
|
|
|
|
|
|
function Lexer(options) {
|
|
|
|
this.tokens = [];
|
|
|
|
this.tokens.links = {};
|
|
|
|
this.options = options || marked.defaults;
|
2016-11-04 15:55:33 +01:00
|
|
|
this.rules = block.normal;
|
2013-12-13 19:58:31 +01:00
|
|
|
|
|
|
|
if (this.options.gfm) {
|
|
|
|
if (this.options.tables) {
|
2016-11-04 15:55:33 +01:00
|
|
|
this.rules = block.tables;
|
2013-12-13 19:58:31 +01:00
|
|
|
} else {
|
2016-11-04 15:55:33 +01:00
|
|
|
this.rules = block.gfm;
|
2013-12-13 19:58:31 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Expose Block Rules
|
|
|
|
*/
|
|
|
|
|
|
|
|
Lexer.rules = block;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Static Lex Method
|
|
|
|
*/
|
|
|
|
|
|
|
|
Lexer.lex = function(src, options) {
|
|
|
|
var lexer = new Lexer(options);
|
|
|
|
return lexer.lex(src);
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Preprocessing
|
|
|
|
*/
|
|
|
|
|
|
|
|
Lexer.prototype.lex = function(src) {
|
|
|
|
src = src
|
|
|
|
.replace(/\r\n|\r/g, '\n')
|
|
|
|
.replace(/\t/g, ' ')
|
|
|
|
.replace(/\u00a0/g, ' ')
|
|
|
|
.replace(/\u2424/g, '\n');
|
|
|
|
|
|
|
|
return this.token(src, true);
|
|
|
|
};
|
|
|
|
|
2014-01-27 23:21:28 +01:00
|
|
|
var htmlStashCounter = 0;
|
|
|
|
var htmlStashTemplate = "klzzwxhzsd:";
|
|
|
|
var htmlStashRegex = /\bklzzwxhzsd:[0-9]+\b/;
|
|
|
|
var htmlStash = [];
|
|
|
|
|
|
|
|
function stashHtml(html, safe) {
|
|
|
|
var key = htmlStashTemplate + htmlStashCounter;
|
|
|
|
htmlStashCounter++;
|
|
|
|
|
|
|
|
htmlStash.push([key, html, safe]);
|
|
|
|
|
|
|
|
return key;
|
|
|
|
}
|
|
|
|
|
2013-12-13 19:58:31 +01:00
|
|
|
/**
|
|
|
|
* Lexing
|
|
|
|
*/
|
|
|
|
|
2016-11-04 15:55:33 +01:00
|
|
|
Lexer.prototype.token = function(src, top, bq) {
|
2013-12-13 19:58:31 +01:00
|
|
|
var src = src.replace(/^ +$/gm, '')
|
|
|
|
, next
|
|
|
|
, loose
|
|
|
|
, cap
|
|
|
|
, bull
|
|
|
|
, b
|
|
|
|
, item
|
2019-08-11 07:41:34 +02:00
|
|
|
, listStart
|
|
|
|
, listItems
|
2013-12-13 19:58:31 +01:00
|
|
|
, space
|
|
|
|
, i
|
2019-08-11 07:41:34 +02:00
|
|
|
, l
|
|
|
|
, isordered;
|
2013-12-13 19:58:31 +01:00
|
|
|
|
|
|
|
while (src) {
|
|
|
|
// newline
|
|
|
|
if (cap = this.rules.newline.exec(src)) {
|
|
|
|
src = src.substring(cap[0].length);
|
|
|
|
if (cap[0].length > 1) {
|
|
|
|
this.tokens.push({
|
|
|
|
type: 'space'
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// code
|
|
|
|
if (cap = this.rules.code.exec(src)) {
|
|
|
|
src = src.substring(cap[0].length);
|
|
|
|
cap = cap[0].replace(/^ {4}/gm, '');
|
|
|
|
this.tokens.push({
|
|
|
|
type: 'code',
|
|
|
|
text: !this.options.pedantic
|
|
|
|
? cap.replace(/\n+$/, '')
|
|
|
|
: cap
|
|
|
|
});
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// fences (gfm)
|
|
|
|
if (cap = this.rules.fences.exec(src)) {
|
|
|
|
src = src.substring(cap[0].length);
|
|
|
|
this.tokens.push({
|
|
|
|
type: 'code',
|
|
|
|
lang: cap[2],
|
2016-11-04 15:55:33 +01:00
|
|
|
text: cap[3] || ''
|
2013-12-13 19:58:31 +01:00
|
|
|
});
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2019-07-31 08:04:32 +02:00
|
|
|
// heading
|
|
|
|
if (cap = this.rules.heading.exec(src)) {
|
|
|
|
src = src.substring(cap[0].length);
|
|
|
|
this.tokens.push({
|
|
|
|
type: 'heading',
|
|
|
|
depth: cap[1].length,
|
|
|
|
text: cap[2]
|
|
|
|
});
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2013-12-13 19:58:31 +01:00
|
|
|
// table no leading pipe (gfm)
|
|
|
|
if (top && (cap = this.rules.nptable.exec(src))) {
|
|
|
|
src = src.substring(cap[0].length);
|
|
|
|
|
|
|
|
item = {
|
|
|
|
type: 'table',
|
|
|
|
header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */),
|
|
|
|
align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
|
|
|
|
cells: cap[3].replace(/\n$/, '').split('\n')
|
|
|
|
};
|
|
|
|
|
|
|
|
for (i = 0; i < item.align.length; i++) {
|
|
|
|
if (/^ *-+: *$/.test(item.align[i])) {
|
|
|
|
item.align[i] = 'right';
|
|
|
|
} else if (/^ *:-+: *$/.test(item.align[i])) {
|
|
|
|
item.align[i] = 'center';
|
|
|
|
} else if (/^ *:-+ *$/.test(item.align[i])) {
|
|
|
|
item.align[i] = 'left';
|
|
|
|
} else {
|
|
|
|
item.align[i] = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < item.cells.length; i++) {
|
|
|
|
item.cells[i] = item.cells[i].split(/ *\| */);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.tokens.push(item);
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// hr
|
|
|
|
if (cap = this.rules.hr.exec(src)) {
|
|
|
|
src = src.substring(cap[0].length);
|
|
|
|
this.tokens.push({
|
|
|
|
type: 'hr'
|
|
|
|
});
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// blockquote
|
|
|
|
if (cap = this.rules.blockquote.exec(src)) {
|
|
|
|
src = src.substring(cap[0].length);
|
|
|
|
|
|
|
|
this.tokens.push({
|
|
|
|
type: 'blockquote_start'
|
|
|
|
});
|
|
|
|
|
|
|
|
cap = cap[0].replace(/^ *> ?/gm, '');
|
|
|
|
|
|
|
|
// Pass `top` to keep the current
|
|
|
|
// "toplevel" state. This is exactly
|
|
|
|
// how markdown.pl works.
|
2016-11-04 15:55:33 +01:00
|
|
|
this.token(cap, top, true);
|
2013-12-13 19:58:31 +01:00
|
|
|
|
|
|
|
this.tokens.push({
|
|
|
|
type: 'blockquote_end'
|
|
|
|
});
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// list
|
|
|
|
if (cap = this.rules.list.exec(src)) {
|
|
|
|
src = src.substring(cap[0].length);
|
|
|
|
bull = cap[2];
|
2019-08-11 07:41:34 +02:00
|
|
|
isordered = bull.length > 1;
|
2013-12-13 19:58:31 +01:00
|
|
|
|
2019-08-11 07:41:34 +02:00
|
|
|
listStart = {
|
2013-12-13 19:58:31 +01:00
|
|
|
type: 'list_start',
|
2019-08-11 07:41:34 +02:00
|
|
|
ordered: isordered,
|
|
|
|
start: isordered ? +bull : '',
|
|
|
|
loose: false
|
|
|
|
};
|
|
|
|
|
|
|
|
this.tokens.push(listStart);
|
2013-12-13 19:58:31 +01:00
|
|
|
|
|
|
|
// Get each top-level item.
|
|
|
|
cap = cap[0].match(this.rules.item);
|
|
|
|
|
2019-08-11 07:41:34 +02:00
|
|
|
listItems = [];
|
2013-12-13 19:58:31 +01:00
|
|
|
next = false;
|
|
|
|
l = cap.length;
|
|
|
|
i = 0;
|
|
|
|
|
|
|
|
for (; i < l; i++) {
|
|
|
|
item = cap[i];
|
|
|
|
|
|
|
|
// Remove the list item's bullet
|
|
|
|
// so it is seen as the next token.
|
|
|
|
space = item.length;
|
2019-08-11 07:41:34 +02:00
|
|
|
item = item.replace(/^ *([*+-]|\d+\.) */, '');
|
2013-12-13 19:58:31 +01:00
|
|
|
|
|
|
|
// Outdent whatever the
|
|
|
|
// list item contains. Hacky.
|
|
|
|
if (~item.indexOf('\n ')) {
|
|
|
|
space -= item.length;
|
|
|
|
item = !this.options.pedantic
|
|
|
|
? item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '')
|
|
|
|
: item.replace(/^ {1,4}/gm, '');
|
|
|
|
}
|
|
|
|
|
|
|
|
// Determine whether the next list item belongs here.
|
|
|
|
// Backpedal if it does not belong in this list.
|
2019-08-11 07:41:34 +02:00
|
|
|
if (i !== l - 1) {
|
2013-12-13 19:58:31 +01:00
|
|
|
b = block.bullet.exec(cap[i + 1])[0];
|
2019-08-11 07:41:34 +02:00
|
|
|
if (bull.length > 1 ? b.length === 1
|
|
|
|
: (b.length > 1 || (this.options.smartLists && b !== bull))) {
|
2013-12-13 19:58:31 +01:00
|
|
|
src = cap.slice(i + 1).join('\n') + src;
|
|
|
|
i = l - 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Determine whether item is loose or not.
|
|
|
|
// Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/
|
|
|
|
// for discount behavior.
|
|
|
|
loose = next || /\n\n(?!\s*$)/.test(item);
|
|
|
|
if (i !== l - 1) {
|
|
|
|
next = item.charAt(item.length - 1) === '\n';
|
|
|
|
if (!loose) loose = next;
|
|
|
|
}
|
|
|
|
|
2019-08-11 07:41:34 +02:00
|
|
|
if (loose) {
|
|
|
|
listStart.loose = true;
|
|
|
|
}
|
|
|
|
|
2019-12-06 09:38:50 +01:00
|
|
|
var t = {
|
2019-08-11 07:41:34 +02:00
|
|
|
type: 'list_item_start',
|
|
|
|
loose: loose
|
|
|
|
};
|
|
|
|
|
|
|
|
listItems.push(t);
|
|
|
|
this.tokens.push(t);
|
2013-12-13 19:58:31 +01:00
|
|
|
|
|
|
|
// Recurse.
|
2019-08-11 07:41:34 +02:00
|
|
|
this.token(item, false);
|
2013-12-13 19:58:31 +01:00
|
|
|
|
|
|
|
this.tokens.push({
|
|
|
|
type: 'list_item_end'
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-08-11 07:41:34 +02:00
|
|
|
if (listStart.loose) {
|
|
|
|
l = listItems.length;
|
|
|
|
i = 0;
|
|
|
|
for (; i < l; i++) {
|
|
|
|
listItems[i].loose = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-12-13 19:58:31 +01:00
|
|
|
this.tokens.push({
|
|
|
|
type: 'list_end'
|
|
|
|
});
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// html
|
|
|
|
if (cap = this.rules.html.exec(src)) {
|
|
|
|
src = src.substring(cap[0].length);
|
|
|
|
this.tokens.push({
|
|
|
|
type: this.options.sanitize
|
|
|
|
? 'paragraph'
|
|
|
|
: 'html',
|
2016-11-04 15:55:33 +01:00
|
|
|
pre: !this.options.sanitizer
|
|
|
|
&& (cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style'),
|
2013-12-13 19:58:31 +01:00
|
|
|
text: cap[0]
|
|
|
|
});
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// def
|
2019-02-04 11:22:13 +01:00
|
|
|
// We disable definition style links in Zulip.
|
2013-12-13 19:58:31 +01:00
|
|
|
|
|
|
|
// table (gfm)
|
|
|
|
if (top && (cap = this.rules.table.exec(src))) {
|
|
|
|
src = src.substring(cap[0].length);
|
|
|
|
|
|
|
|
item = {
|
|
|
|
type: 'table',
|
|
|
|
header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */),
|
|
|
|
align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
|
|
|
|
cells: cap[3].replace(/(?: *\| *)?\n$/, '').split('\n')
|
|
|
|
};
|
|
|
|
|
|
|
|
for (i = 0; i < item.align.length; i++) {
|
|
|
|
if (/^ *-+: *$/.test(item.align[i])) {
|
|
|
|
item.align[i] = 'right';
|
|
|
|
} else if (/^ *:-+: *$/.test(item.align[i])) {
|
|
|
|
item.align[i] = 'center';
|
|
|
|
} else if (/^ *:-+ *$/.test(item.align[i])) {
|
|
|
|
item.align[i] = 'left';
|
|
|
|
} else {
|
|
|
|
item.align[i] = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < item.cells.length; i++) {
|
|
|
|
item.cells[i] = item.cells[i]
|
|
|
|
.replace(/^ *\| *| *\| *$/g, '')
|
|
|
|
.split(/ *\| */);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.tokens.push(item);
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// top-level paragraph
|
|
|
|
if (top && (cap = this.rules.paragraph.exec(src))) {
|
|
|
|
src = src.substring(cap[0].length);
|
|
|
|
this.tokens.push({
|
|
|
|
type: 'paragraph',
|
|
|
|
text: cap[1].charAt(cap[1].length - 1) === '\n'
|
|
|
|
? cap[1].slice(0, -1)
|
|
|
|
: cap[1]
|
|
|
|
});
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// text
|
|
|
|
if (cap = this.rules.text.exec(src)) {
|
|
|
|
// Top-level should never reach here.
|
|
|
|
src = src.substring(cap[0].length);
|
|
|
|
this.tokens.push({
|
|
|
|
type: 'text',
|
|
|
|
text: cap[0]
|
|
|
|
});
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (src) {
|
|
|
|
throw new
|
|
|
|
Error('Infinite loop on byte: ' + src.charCodeAt(0));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return this.tokens;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Inline-Level Grammar
|
|
|
|
*/
|
|
|
|
|
|
|
|
var inline = {
|
|
|
|
escape: /^\\([\\`*{}\[\]()#+\-.!_>])/,
|
|
|
|
autolink: /^<([^ >]+(@|:\/)[^ >]+)>/,
|
|
|
|
url: noop,
|
|
|
|
tag: /^<!--[\s\S]*?-->|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/,
|
|
|
|
link: /^!?\[(inside)\]\(href\)/,
|
|
|
|
reflink: /^!?\[(inside)\]\s*\[([^\]]*)\]/,
|
|
|
|
nolink: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/,
|
|
|
|
strong: /^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/,
|
2016-11-04 15:55:33 +01:00
|
|
|
em: /^\b_((?:[^_]|__)+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/,
|
2017-11-22 02:27:19 +01:00
|
|
|
code: /^(`+)(\s*[\s\S]*?[^`]\s*)\1(?!`)/,
|
2013-12-13 19:58:31 +01:00
|
|
|
br: /^ {2,}\n(?!\s*$)/,
|
|
|
|
del: noop,
|
2014-01-04 00:16:40 +01:00
|
|
|
emoji: noop,
|
2016-06-24 22:39:44 +02:00
|
|
|
unicodeemoji: noop,
|
2014-01-04 00:16:40 +01:00
|
|
|
usermention: noop,
|
2017-11-22 09:11:07 +01:00
|
|
|
groupmention: noop,
|
2016-11-10 20:20:40 +01:00
|
|
|
stream: noop,
|
2017-03-20 16:56:39 +01:00
|
|
|
tex: noop,
|
2018-07-18 14:36:04 +02:00
|
|
|
timestamp: noop,
|
2021-03-13 18:15:14 +01:00
|
|
|
linkifiers: [],
|
2017-03-20 16:56:39 +01:00
|
|
|
text: /^[\s\S]+?(?=[\\<!\[_*`$]| {2,}\n|$)/
|
2013-12-13 19:58:31 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
inline._inside = /(?:\[[^\]]*\]|[^\[\]]|\](?=[^\[]*\]))*/;
|
|
|
|
inline._href = /\s*<?([\s\S]*?)>?(?:\s+['"]([\s\S]*?)['"])?\s*/;
|
|
|
|
|
|
|
|
inline.link = replace(inline.link)
|
|
|
|
('inside', inline._inside)
|
|
|
|
('href', inline._href)
|
|
|
|
();
|
|
|
|
|
|
|
|
inline.reflink = replace(inline.reflink)
|
|
|
|
('inside', inline._inside)
|
|
|
|
();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Normal Inline Grammar
|
|
|
|
*/
|
|
|
|
|
|
|
|
inline.normal = merge({}, inline);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Pedantic Inline Grammar
|
|
|
|
*/
|
|
|
|
|
|
|
|
inline.pedantic = merge({}, inline.normal, {
|
|
|
|
strong: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,
|
|
|
|
em: /^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/
|
|
|
|
});
|
|
|
|
|
|
|
|
/**
|
|
|
|
* GFM Inline Grammar
|
|
|
|
*/
|
|
|
|
|
|
|
|
inline.gfm = merge({}, inline.normal, {
|
|
|
|
escape: replace(inline.escape)('])', '~|])')(),
|
|
|
|
url: /^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/,
|
|
|
|
del: /^~~(?=\S)([\s\S]*?\S)~~/,
|
|
|
|
text: replace(inline.text)
|
|
|
|
(']|', '~]|')
|
|
|
|
('|', '|https?://|')
|
|
|
|
()
|
|
|
|
});
|
|
|
|
|
|
|
|
/**
|
|
|
|
* GFM + Line Breaks Inline Grammar
|
|
|
|
*/
|
|
|
|
|
|
|
|
inline.breaks = merge({}, inline.gfm, {
|
|
|
|
br: replace(inline.br)('{2,}', '*')(),
|
|
|
|
text: replace(inline.gfm.text)('{2,}', '*')()
|
|
|
|
});
|
|
|
|
|
2014-01-04 00:16:40 +01:00
|
|
|
inline.zulip = merge({}, inline.breaks, {
|
|
|
|
emoji: /^:([A-Za-z0-9_\-\+]+?):/,
|
2017-05-15 11:24:01 +02:00
|
|
|
unicodeemoji: RegExp('^(\ud83c[\udd00-\udfff]|\ud83d[\udc00-\ude4f]|' +
|
|
|
|
'\ud83d[\ude80-\udeff]|\ud83e[\udd00-\uddff]|' +
|
|
|
|
'[\u2000-\u206F]|[\u2300-\u27BF]|[\u2B00-\u2BFF]|' +
|
|
|
|
'[\u3000-\u303F]|[\u3200-\u32FF])'),
|
2019-02-20 10:15:33 +01:00
|
|
|
usermention: /^(@(_?)(?:\*\*([^\*]+)\*\*))/, // Match potentially multi-word string between @** **
|
2017-11-22 09:11:07 +01:00
|
|
|
groupmention: /^@\*([^\*]+)\*/, // Match multi-word string between @* *
|
2019-06-21 20:47:09 +02:00
|
|
|
stream_topic: /^#\*\*([^\*>]+)>([^\*]+)\*\*/,
|
2017-07-18 10:33:19 +02:00
|
|
|
stream: /^#\*\*([^\*]+)\*\*/,
|
2017-11-03 10:15:14 +01:00
|
|
|
tex: /^(\$\$([^\n_$](\\\$|[^\n$])*)\$\$(?!\$))\B/,
|
2020-07-06 16:33:14 +02:00
|
|
|
timestamp: /^<time:([^>]+)>/,
|
2021-03-13 18:15:14 +01:00
|
|
|
linkifiers: [],
|
2014-01-08 20:40:54 +01:00
|
|
|
text: replace(inline.breaks.text)
|
2017-05-15 11:24:01 +02:00
|
|
|
('|', '|(\ud83c[\udd00-\udfff]|\ud83d[\udc00-\ude4f]|' +
|
|
|
|
'\ud83d[\ude80-\udeff]|\ud83e[\udd00-\uddff]|' +
|
|
|
|
'[\u2000-\u206F]|[\u2300-\u27BF]|[\u2B00-\u2BFF]|' +
|
|
|
|
'[\u3000-\u303F]|[\u3200-\u32FF])|')
|
2016-11-10 20:20:40 +01:00
|
|
|
(']|', '#@:]|')
|
2017-11-03 10:15:14 +01:00
|
|
|
('^[', '^^\\${3,}|^^[')
|
2014-01-04 00:16:40 +01:00
|
|
|
()
|
|
|
|
});
|
|
|
|
|
2013-12-13 19:58:31 +01:00
|
|
|
/**
|
|
|
|
* Inline Lexer & Compiler
|
|
|
|
*/
|
|
|
|
|
|
|
|
function InlineLexer(links, options) {
|
|
|
|
this.options = options || marked.defaults;
|
|
|
|
this.links = links;
|
|
|
|
this.rules = inline.normal;
|
|
|
|
this.renderer = this.options.renderer || new Renderer;
|
2014-01-27 16:37:46 +01:00
|
|
|
this.renderer.options = this.options;
|
2013-12-13 19:58:31 +01:00
|
|
|
|
|
|
|
if (!this.links) {
|
|
|
|
throw new
|
|
|
|
Error('Tokens array requires a `links` property.');
|
|
|
|
}
|
|
|
|
|
2014-01-04 00:16:40 +01:00
|
|
|
if (this.options.zulip) {
|
|
|
|
this.rules = inline.zulip;
|
|
|
|
} else {
|
|
|
|
if (this.options.gfm) {
|
|
|
|
if (this.options.breaks) {
|
|
|
|
this.rules = inline.breaks;
|
|
|
|
} else {
|
|
|
|
this.rules = inline.gfm;
|
|
|
|
}
|
|
|
|
} else if (this.options.pedantic) {
|
|
|
|
this.rules = inline.pedantic;
|
2013-12-13 19:58:31 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Expose Inline Rules
|
|
|
|
*/
|
|
|
|
|
|
|
|
InlineLexer.rules = inline;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Static Lexing/Compiling Method
|
|
|
|
*/
|
|
|
|
|
|
|
|
InlineLexer.output = function(src, links, options) {
|
|
|
|
var inline = new InlineLexer(links, options);
|
|
|
|
return inline.output(src);
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Lexing/Compiling
|
|
|
|
*/
|
|
|
|
|
2014-01-06 23:41:08 +01:00
|
|
|
InlineLexer.prototype.inlineReplacement = function(regex, src, replace_func) {
|
|
|
|
var cap, out = "";
|
|
|
|
regex.lastIndex = 0;
|
|
|
|
if (cap = regex.exec(src)) {
|
|
|
|
// Split before-match into its own segment and handle it separately
|
|
|
|
var match_idx = regex.lastIndex;
|
|
|
|
var before = src.substring(0, match_idx - cap[0].length);
|
|
|
|
before = this.output(before);
|
|
|
|
out += before;
|
|
|
|
|
|
|
|
// Consume all of the matched text
|
|
|
|
src = src.substring(match_idx);
|
|
|
|
|
|
|
|
out += replace_func(regex, cap.slice(1), cap[0]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return [src, out];
|
|
|
|
};
|
|
|
|
|
2013-12-13 19:58:31 +01:00
|
|
|
InlineLexer.prototype.output = function(src) {
|
|
|
|
var out = ''
|
|
|
|
, link
|
|
|
|
, text
|
|
|
|
, href
|
|
|
|
, cap;
|
|
|
|
|
|
|
|
while (src) {
|
|
|
|
// escape
|
|
|
|
if (cap = this.rules.escape.exec(src)) {
|
|
|
|
src = src.substring(cap[0].length);
|
|
|
|
out += cap[1];
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2021-03-13 18:15:14 +01:00
|
|
|
// linkifier (Zulip)
|
2014-01-06 23:41:08 +01:00
|
|
|
var self = this;
|
2021-03-13 18:15:14 +01:00
|
|
|
this.rules.linkifiers.forEach(function (linkifier) {
|
|
|
|
var ret = self.inlineReplacement(linkifier, src, function(regex, groups, match) {
|
2014-01-06 23:41:08 +01:00
|
|
|
// Insert the created URL
|
2021-03-13 18:15:14 +01:00
|
|
|
href = self.linkifier(regex, groups, match);
|
2014-01-06 23:41:08 +01:00
|
|
|
if (href !== undefined) {
|
|
|
|
href = escape(href);
|
|
|
|
return self.renderer.link(href, href, match);
|
|
|
|
} else {
|
|
|
|
return match;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
src = ret[0];
|
|
|
|
out += ret[1];
|
|
|
|
});
|
|
|
|
|
2013-12-13 19:58:31 +01:00
|
|
|
// autolink
|
|
|
|
if (cap = this.rules.autolink.exec(src)) {
|
|
|
|
src = src.substring(cap[0].length);
|
|
|
|
if (cap[2] === '@') {
|
|
|
|
text = cap[1].charAt(6) === ':'
|
|
|
|
? this.mangle(cap[1].substring(7))
|
|
|
|
: this.mangle(cap[1]);
|
|
|
|
href = this.mangle('mailto:') + text;
|
|
|
|
} else {
|
|
|
|
text = escape(cap[1]);
|
|
|
|
href = text;
|
|
|
|
}
|
|
|
|
out += this.renderer.link(href, null, text);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// url (gfm)
|
2016-11-04 15:55:33 +01:00
|
|
|
if (!this.inLink && (cap = this.rules.url.exec(src))) {
|
2013-12-13 19:58:31 +01:00
|
|
|
src = src.substring(cap[0].length);
|
|
|
|
text = escape(cap[1]);
|
|
|
|
href = text;
|
|
|
|
out += this.renderer.link(href, null, text);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-07-06 16:33:14 +02:00
|
|
|
// timestamp
|
|
|
|
if (cap = this.rules.timestamp.exec(src)) {
|
|
|
|
src = src.substring(cap[0].length);
|
|
|
|
out += this.timestamp(cap[1]);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2013-12-13 19:58:31 +01:00
|
|
|
// tag
|
|
|
|
if (cap = this.rules.tag.exec(src)) {
|
2016-11-04 15:55:33 +01:00
|
|
|
if (!this.inLink && /^<a /i.test(cap[0])) {
|
|
|
|
this.inLink = true;
|
|
|
|
} else if (this.inLink && /^<\/a>/i.test(cap[0])) {
|
|
|
|
this.inLink = false;
|
|
|
|
}
|
2013-12-13 19:58:31 +01:00
|
|
|
src = src.substring(cap[0].length);
|
|
|
|
out += this.options.sanitize
|
2016-11-04 15:55:33 +01:00
|
|
|
? this.options.sanitizer
|
|
|
|
? this.options.sanitizer(cap[0])
|
|
|
|
: escape(cap[0])
|
|
|
|
: cap[0]
|
2013-12-13 19:58:31 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// link
|
|
|
|
if (cap = this.rules.link.exec(src)) {
|
|
|
|
src = src.substring(cap[0].length);
|
2016-11-04 15:55:33 +01:00
|
|
|
this.inLink = true;
|
2013-12-13 19:58:31 +01:00
|
|
|
out += this.outputLink(cap, {
|
|
|
|
href: cap[2],
|
|
|
|
title: cap[3]
|
|
|
|
});
|
2016-11-04 15:55:33 +01:00
|
|
|
this.inLink = false;
|
2013-12-13 19:58:31 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// reflink, nolink
|
|
|
|
if ((cap = this.rules.reflink.exec(src))
|
|
|
|
|| (cap = this.rules.nolink.exec(src))) {
|
|
|
|
src = src.substring(cap[0].length);
|
|
|
|
link = (cap[2] || cap[1]).replace(/\s+/g, ' ');
|
|
|
|
link = this.links[link.toLowerCase()];
|
|
|
|
if (!link || !link.href) {
|
|
|
|
out += cap[0].charAt(0);
|
|
|
|
src = cap[0].substring(1) + src;
|
|
|
|
continue;
|
|
|
|
}
|
2016-11-04 15:55:33 +01:00
|
|
|
this.inLink = true;
|
2013-12-13 19:58:31 +01:00
|
|
|
out += this.outputLink(cap, link);
|
2016-11-04 15:55:33 +01:00
|
|
|
this.inLink = false;
|
2013-12-13 19:58:31 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-10-23 02:43:28 +02:00
|
|
|
// usermention (Zulip)
|
2014-01-04 00:16:40 +01:00
|
|
|
if (cap = this.rules.usermention.exec(src)) {
|
|
|
|
src = src.substring(cap[0].length);
|
2019-12-13 01:38:49 +01:00
|
|
|
out += this.usermention(unescape(cap[3] || cap[4]), cap[1], cap[2]);
|
2014-01-04 00:16:40 +01:00
|
|
|
continue;
|
|
|
|
}
|
2016-11-10 20:20:40 +01:00
|
|
|
|
2020-10-23 02:43:28 +02:00
|
|
|
// groupmention (Zulip)
|
2017-11-22 09:11:07 +01:00
|
|
|
if (cap = this.rules.groupmention.exec(src)) {
|
|
|
|
src = src.substring(cap[0].length);
|
2019-12-13 01:38:49 +01:00
|
|
|
out += this.groupmention(unescape(cap[1]), cap[0]);
|
2017-11-22 09:11:07 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-10-23 02:43:28 +02:00
|
|
|
// stream_topic (Zulip)
|
2019-06-21 20:47:09 +02:00
|
|
|
if (cap = this.rules.stream_topic.exec(src)) {
|
|
|
|
src = src.substring(cap[0].length);
|
2019-12-13 01:38:49 +01:00
|
|
|
out += this.stream_topic(unescape(cap[1]), unescape(cap[2]), cap[0]);
|
2019-06-21 20:47:09 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-10-23 02:43:28 +02:00
|
|
|
// stream (Zulip)
|
2016-11-10 20:20:40 +01:00
|
|
|
if (cap = this.rules.stream.exec(src)) {
|
2016-11-05 21:35:00 +01:00
|
|
|
src = src.substring(cap[0].length);
|
2019-12-13 01:38:49 +01:00
|
|
|
out += this.stream(unescape(cap[1]), cap[0]);
|
2016-11-05 21:35:00 +01:00
|
|
|
continue;
|
|
|
|
}
|
2014-01-04 00:16:40 +01:00
|
|
|
|
2013-12-13 19:58:31 +01:00
|
|
|
// strong
|
|
|
|
if (cap = this.rules.strong.exec(src)) {
|
|
|
|
src = src.substring(cap[0].length);
|
|
|
|
out += this.renderer.strong(this.output(cap[2] || cap[1]));
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// em
|
|
|
|
if (cap = this.rules.em.exec(src)) {
|
|
|
|
src = src.substring(cap[0].length);
|
2018-03-29 00:25:58 +02:00
|
|
|
out += this.renderer.em(this.output(cap[1] + cap[2]));
|
2013-12-13 19:58:31 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// code
|
|
|
|
if (cap = this.rules.code.exec(src)) {
|
|
|
|
src = src.substring(cap[0].length);
|
|
|
|
out += this.renderer.codespan(escape(cap[2], true));
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// br
|
|
|
|
if (cap = this.rules.br.exec(src)) {
|
|
|
|
src = src.substring(cap[0].length);
|
|
|
|
out += this.renderer.br();
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// del (gfm)
|
|
|
|
if (cap = this.rules.del.exec(src)) {
|
|
|
|
src = src.substring(cap[0].length);
|
|
|
|
out += this.renderer.del(this.output(cap[1]));
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2014-01-04 00:16:40 +01:00
|
|
|
// emoji (gfm)
|
|
|
|
if (cap = this.rules.emoji.exec(src)) {
|
|
|
|
src = src.substring(cap[0].length);
|
|
|
|
out += this.emoji(cap[1]);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-10-23 02:43:28 +02:00
|
|
|
// Unicode emoji
|
2016-06-24 22:39:44 +02:00
|
|
|
if (cap = this.rules.unicodeemoji.exec(src)) {
|
|
|
|
src = src.substring(cap[0].length);
|
|
|
|
out += this.unicodeEmoji(cap[1]);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
markdown: Remove !avatar() and !gravatar() syntax.
This particular commit has been a long time coming. For reference,
!avatar(email) was an undocumented syntax that simply rendered an
inline 50px avatar for a user in a message, essentially allowing
you to create a user pill like:
`!avatar(alice@example.com) Alice: hey!`
---
Reimplementation
If we decide to reimplement this or a similar feature in the future,
we could use something like `<avatar:userid>` syntax which is more
in line with creating links in markdown. Even then, it would not be
a good idea to add this instead of supporting inline images directly.
Since any usecases of such a syntax are in automation, we do not need
to make it userfriendly and something like the following is a better
implementation that doesn't need a custom syntax:
`![avatar for Alice](/avatar/1234?s=50) Alice: hey!`
---
History
We initially added this syntax back in 2012 and it was 'deprecated'
from the get go. Here's what the original commit had to say about
the new syntax:
> We'll use this internally for the commit bot. We might eventually
> disable it for external users.
We eventually did start using this for our github integrations in 2013
but since then, those integrations have been neglected in favor of
our GitHub webhooks which do not use this syntax.
When we copied `!gravatar` to add the `!avatar` syntax, we also noted
that we want to deprecate the `!gravatar` syntax entirely - in 2013!
Since then, we haven't advertised either of these syntaxes anywhere
in our docs, and the only two places where this syntax remains is
our game bots that could easily do without these, and the git commit
integration that we have deprecated anyway.
We do not have any evidence of someone asking about this syntax on
chat.zulip.org when developing an integration and rightfully so- only
the people who work on Zulip (and specifically, markdown) are likely
to stumble upon it and try it out.
This is also the only peice of code due to which we had to look up
emails -> userid mapping in our backend markdown. By removing this,
we entirely remove the backend markdown's dependency on user emails
to render messages.
---
Relevant commits:
- Oct 2012, Initial commit c31462c2782a33886e737cf33424a36a95c81f97
- Nov 2013, Update commit bot 968c393826f8846065c5c880427328f6e534c2f5
- Nov 2013, Add avatar syntax 761c0a0266669aca82d134716a4d6b6e33d541fc
- Sep 2017, Avoid email use c3032a7fe8ed49b011e0d242f4b8a7d756b9f647
- Apr 2019, Remove from webhook 674fcfcce1fcf35bdc57031a1025ef169d495d36
2020-07-06 23:01:38 +02:00
|
|
|
// timestamp
|
|
|
|
if (cap = this.rules.timestamp.exec(src)) {
|
2016-10-20 21:42:55 +02:00
|
|
|
src = src.substring(cap[0].length);
|
markdown: Remove !avatar() and !gravatar() syntax.
This particular commit has been a long time coming. For reference,
!avatar(email) was an undocumented syntax that simply rendered an
inline 50px avatar for a user in a message, essentially allowing
you to create a user pill like:
`!avatar(alice@example.com) Alice: hey!`
---
Reimplementation
If we decide to reimplement this or a similar feature in the future,
we could use something like `<avatar:userid>` syntax which is more
in line with creating links in markdown. Even then, it would not be
a good idea to add this instead of supporting inline images directly.
Since any usecases of such a syntax are in automation, we do not need
to make it userfriendly and something like the following is a better
implementation that doesn't need a custom syntax:
`![avatar for Alice](/avatar/1234?s=50) Alice: hey!`
---
History
We initially added this syntax back in 2012 and it was 'deprecated'
from the get go. Here's what the original commit had to say about
the new syntax:
> We'll use this internally for the commit bot. We might eventually
> disable it for external users.
We eventually did start using this for our github integrations in 2013
but since then, those integrations have been neglected in favor of
our GitHub webhooks which do not use this syntax.
When we copied `!gravatar` to add the `!avatar` syntax, we also noted
that we want to deprecate the `!gravatar` syntax entirely - in 2013!
Since then, we haven't advertised either of these syntaxes anywhere
in our docs, and the only two places where this syntax remains is
our game bots that could easily do without these, and the git commit
integration that we have deprecated anyway.
We do not have any evidence of someone asking about this syntax on
chat.zulip.org when developing an integration and rightfully so- only
the people who work on Zulip (and specifically, markdown) are likely
to stumble upon it and try it out.
This is also the only peice of code due to which we had to look up
emails -> userid mapping in our backend markdown. By removing this,
we entirely remove the backend markdown's dependency on user emails
to render messages.
---
Relevant commits:
- Oct 2012, Initial commit c31462c2782a33886e737cf33424a36a95c81f97
- Nov 2013, Update commit bot 968c393826f8846065c5c880427328f6e534c2f5
- Nov 2013, Add avatar syntax 761c0a0266669aca82d134716a4d6b6e33d541fc
- Sep 2017, Avoid email use c3032a7fe8ed49b011e0d242f4b8a7d756b9f647
- Apr 2019, Remove from webhook 674fcfcce1fcf35bdc57031a1025ef169d495d36
2020-07-06 23:01:38 +02:00
|
|
|
out += this.timestamp(cap[1]);
|
2016-10-20 21:42:55 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2017-03-20 16:56:39 +01:00
|
|
|
// tex
|
|
|
|
if (cap = this.rules.tex.exec(src)) {
|
|
|
|
src = src.substring(cap[0].length);
|
|
|
|
out += this.tex(cap[2], cap[0]);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2018-07-14 20:34:31 +02:00
|
|
|
// WARNING: Do not place any parsing logic below this comment.
|
|
|
|
// Any parsing logic place after the `text` block below will most
|
|
|
|
// likely be silently never executed.
|
|
|
|
|
2013-12-13 19:58:31 +01:00
|
|
|
// text
|
|
|
|
if (cap = this.rules.text.exec(src)) {
|
|
|
|
src = src.substring(cap[0].length);
|
2016-11-04 15:55:33 +01:00
|
|
|
out += this.renderer.text(escape(this.smartypants(cap[0])));
|
2013-12-13 19:58:31 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (src) {
|
|
|
|
throw new
|
|
|
|
Error('Infinite loop on byte: ' + src.charCodeAt(0));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return out;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Compile Link
|
|
|
|
*/
|
|
|
|
|
|
|
|
InlineLexer.prototype.outputLink = function(cap, link) {
|
|
|
|
var href = escape(link.href)
|
|
|
|
, title = link.title ? escape(link.title) : null;
|
|
|
|
|
2016-11-04 15:55:33 +01:00
|
|
|
return cap[0].charAt(0) !== '!'
|
|
|
|
? this.renderer.link(href, title, this.output(cap[1]))
|
|
|
|
: this.renderer.image(href, title, escape(cap[1]));
|
2013-12-13 19:58:31 +01:00
|
|
|
};
|
2014-01-04 00:16:40 +01:00
|
|
|
InlineLexer.prototype.emoji = function (name) {
|
2018-03-29 00:25:58 +02:00
|
|
|
name = escape(name)
|
2014-01-04 00:16:40 +01:00
|
|
|
if (typeof this.options.emojiHandler !== 'function')
|
|
|
|
return ':' + name + ':';
|
|
|
|
|
|
|
|
return this.options.emojiHandler(name);
|
|
|
|
};
|
|
|
|
|
2016-06-24 22:39:44 +02:00
|
|
|
InlineLexer.prototype.unicodeEmoji = function (name) {
|
2018-03-29 00:25:58 +02:00
|
|
|
name = escape(name)
|
2016-06-24 22:39:44 +02:00
|
|
|
if (typeof this.options.unicodeEmojiHandler !== 'function')
|
|
|
|
return name;
|
|
|
|
return this.options.unicodeEmojiHandler(name);
|
|
|
|
};
|
|
|
|
|
2017-03-20 16:56:39 +01:00
|
|
|
InlineLexer.prototype.tex = function (tex, fullmatch) {
|
|
|
|
if (typeof this.options.texHandler !== 'function')
|
|
|
|
return fullmatch;
|
|
|
|
return this.options.texHandler(tex, fullmatch);
|
|
|
|
};
|
|
|
|
|
2018-07-18 14:36:04 +02:00
|
|
|
InlineLexer.prototype.timestamp = function (time) {
|
|
|
|
if (typeof this.options.timestampHandler !== 'function')
|
2020-07-06 16:33:14 +02:00
|
|
|
return '<time:' + time + '>';
|
2018-07-18 14:36:04 +02:00
|
|
|
return this.options.timestampHandler(time);
|
|
|
|
};
|
|
|
|
|
2021-03-13 18:15:14 +01:00
|
|
|
InlineLexer.prototype.linkifier = function (linkifier, matches, orig) {
|
|
|
|
if (typeof this.options.linkifierHandler !== 'function')
|
2014-01-06 23:41:08 +01:00
|
|
|
return;
|
|
|
|
|
2021-03-13 18:15:14 +01:00
|
|
|
return this.options.linkifierHandler(linkifier, matches);
|
2014-01-06 23:41:08 +01:00
|
|
|
};
|
|
|
|
|
2019-01-08 09:30:19 +01:00
|
|
|
InlineLexer.prototype.usermention = function (username, orig, silent) {
|
2018-03-29 00:25:58 +02:00
|
|
|
orig = escape(orig);
|
2014-01-04 00:16:40 +01:00
|
|
|
if (typeof this.options.userMentionHandler !== 'function')
|
|
|
|
{
|
|
|
|
return orig;
|
|
|
|
}
|
2019-01-08 09:30:19 +01:00
|
|
|
var handled = this.options.userMentionHandler(username, silent === '_');
|
2014-01-04 00:16:40 +01:00
|
|
|
if (handled !== undefined) {
|
|
|
|
return handled;
|
|
|
|
}
|
|
|
|
|
|
|
|
return orig;
|
|
|
|
};
|
|
|
|
|
2017-11-22 09:11:07 +01:00
|
|
|
InlineLexer.prototype.groupmention = function (groupname, orig) {
|
2018-03-29 00:25:58 +02:00
|
|
|
orig = escape(orig);
|
2017-11-22 09:11:07 +01:00
|
|
|
if (typeof this.options.groupMentionHandler !== 'function')
|
|
|
|
{
|
|
|
|
return orig;
|
|
|
|
}
|
|
|
|
|
|
|
|
var handled = this.options.groupMentionHandler(groupname);
|
|
|
|
if (handled !== undefined) {
|
|
|
|
return handled;
|
|
|
|
}
|
|
|
|
|
|
|
|
return orig;
|
|
|
|
};
|
|
|
|
|
2016-11-10 20:20:40 +01:00
|
|
|
InlineLexer.prototype.stream = function (streamName, orig) {
|
2018-03-29 00:25:58 +02:00
|
|
|
orig = escape(orig);
|
2016-11-10 20:20:40 +01:00
|
|
|
if (typeof this.options.streamHandler !== 'function')
|
2016-11-05 21:35:00 +01:00
|
|
|
return orig;
|
|
|
|
|
2016-11-10 20:20:40 +01:00
|
|
|
var handled = this.options.streamHandler(streamName);
|
2016-11-05 21:35:00 +01:00
|
|
|
if (handled !== undefined) {
|
|
|
|
return handled;
|
|
|
|
}
|
|
|
|
return orig;
|
2016-11-10 20:20:40 +01:00
|
|
|
};
|
2016-11-05 21:35:00 +01:00
|
|
|
|
2019-06-21 20:47:09 +02:00
|
|
|
InlineLexer.prototype.stream_topic = function (streamName, topic, orig) {
|
|
|
|
orig = escape(orig);
|
|
|
|
if (typeof this.options.streamHandler !== 'function')
|
|
|
|
return orig;
|
|
|
|
|
|
|
|
var handled = this.options.streamTopicHandler(streamName, topic);
|
|
|
|
if (handled !== undefined) {
|
|
|
|
return handled;
|
|
|
|
}
|
|
|
|
return orig;
|
|
|
|
};
|
|
|
|
|
2013-12-13 19:58:31 +01:00
|
|
|
/**
|
|
|
|
* Smartypants Transformations
|
|
|
|
*/
|
|
|
|
|
|
|
|
InlineLexer.prototype.smartypants = function(text) {
|
|
|
|
if (!this.options.smartypants) return text;
|
|
|
|
return text
|
|
|
|
// em-dashes
|
2016-11-04 15:55:33 +01:00
|
|
|
.replace(/---/g, '\u2014')
|
|
|
|
// en-dashes
|
|
|
|
.replace(/--/g, '\u2013')
|
2013-12-13 19:58:31 +01:00
|
|
|
// opening singles
|
|
|
|
.replace(/(^|[-\u2014/(\[{"\s])'/g, '$1\u2018')
|
|
|
|
// closing singles & apostrophes
|
|
|
|
.replace(/'/g, '\u2019')
|
|
|
|
// opening doubles
|
|
|
|
.replace(/(^|[-\u2014/(\[{\u2018\s])"/g, '$1\u201c')
|
|
|
|
// closing doubles
|
|
|
|
.replace(/"/g, '\u201d')
|
|
|
|
// ellipses
|
|
|
|
.replace(/\.{3}/g, '\u2026');
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Mangle Links
|
|
|
|
*/
|
|
|
|
|
|
|
|
InlineLexer.prototype.mangle = function(text) {
|
2016-11-04 15:55:33 +01:00
|
|
|
if (!this.options.mangle) return text;
|
2013-12-13 19:58:31 +01:00
|
|
|
var out = ''
|
|
|
|
, l = text.length
|
|
|
|
, i = 0
|
|
|
|
, ch;
|
|
|
|
|
|
|
|
for (; i < l; i++) {
|
|
|
|
ch = text.charCodeAt(i);
|
|
|
|
if (Math.random() > 0.5) {
|
|
|
|
ch = 'x' + ch.toString(16);
|
|
|
|
}
|
|
|
|
out += '&#' + ch + ';';
|
|
|
|
}
|
|
|
|
|
|
|
|
return out;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Renderer
|
|
|
|
*/
|
|
|
|
|
2014-01-27 16:37:46 +01:00
|
|
|
function Renderer(options) {
|
|
|
|
this.options = options || {};
|
|
|
|
}
|
|
|
|
|
|
|
|
Renderer.prototype.code = function(code, lang, escaped) {
|
|
|
|
if (this.options.highlight) {
|
|
|
|
var out = this.options.highlight(code, lang);
|
|
|
|
if (out != null && out !== code) {
|
|
|
|
escaped = true;
|
|
|
|
code = out;
|
|
|
|
}
|
|
|
|
}
|
2013-12-13 19:58:31 +01:00
|
|
|
|
|
|
|
if (!lang) {
|
|
|
|
return '<pre><code>'
|
2014-01-27 16:37:46 +01:00
|
|
|
+ (escaped ? code : escape(code, true))
|
2013-12-13 19:58:31 +01:00
|
|
|
+ '\n</code></pre>';
|
|
|
|
}
|
|
|
|
|
|
|
|
return '<pre><code class="'
|
2014-01-27 16:37:46 +01:00
|
|
|
+ this.options.langPrefix
|
|
|
|
+ escape(lang, true)
|
2013-12-13 19:58:31 +01:00
|
|
|
+ '">'
|
2014-01-27 16:37:46 +01:00
|
|
|
+ (escaped ? code : escape(code, true))
|
2013-12-13 19:58:31 +01:00
|
|
|
+ '\n</code></pre>\n';
|
|
|
|
};
|
|
|
|
|
|
|
|
Renderer.prototype.blockquote = function(quote) {
|
2019-01-08 11:30:13 +01:00
|
|
|
quote = this.options.silencedMentionHandler(quote);
|
2013-12-13 19:58:31 +01:00
|
|
|
return '<blockquote>\n' + quote + '</blockquote>\n';
|
|
|
|
};
|
|
|
|
|
|
|
|
Renderer.prototype.html = function(html) {
|
|
|
|
return html;
|
|
|
|
};
|
|
|
|
|
2019-07-31 08:04:32 +02:00
|
|
|
Renderer.prototype.heading = function(text, level, raw, slugger) {
|
|
|
|
// ignore IDs
|
|
|
|
return '<h' + level + '>' + text + '</h' + level + '>\n';
|
|
|
|
};
|
|
|
|
|
2013-12-13 19:58:31 +01:00
|
|
|
Renderer.prototype.hr = function() {
|
2016-11-04 15:55:33 +01:00
|
|
|
return this.options.xhtml ? '<hr/>\n' : '<hr>\n';
|
2013-12-13 19:58:31 +01:00
|
|
|
};
|
|
|
|
|
2019-08-11 07:41:34 +02:00
|
|
|
Renderer.prototype.list = function(body, ordered, start) {
|
|
|
|
var type = ordered ? 'ol' : 'ul',
|
|
|
|
startatt = (ordered && start !== 1) ? (' start="' + start + '"') : '';
|
|
|
|
return '<' + type + startatt + '>\n' + body + '</' + type + '>\n';
|
2013-12-13 19:58:31 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
Renderer.prototype.listitem = function(text) {
|
|
|
|
return '<li>' + text + '</li>\n';
|
|
|
|
};
|
|
|
|
|
|
|
|
Renderer.prototype.paragraph = function(text) {
|
|
|
|
return '<p>' + text + '</p>\n';
|
|
|
|
};
|
|
|
|
|
|
|
|
Renderer.prototype.table = function(header, body) {
|
|
|
|
return '<table>\n'
|
|
|
|
+ '<thead>\n'
|
|
|
|
+ header
|
|
|
|
+ '</thead>\n'
|
|
|
|
+ '<tbody>\n'
|
|
|
|
+ body
|
|
|
|
+ '</tbody>\n'
|
|
|
|
+ '</table>\n';
|
|
|
|
};
|
|
|
|
|
|
|
|
Renderer.prototype.tablerow = function(content) {
|
|
|
|
return '<tr>\n' + content + '</tr>\n';
|
|
|
|
};
|
|
|
|
|
|
|
|
Renderer.prototype.tablecell = function(content, flags) {
|
|
|
|
var type = flags.header ? 'th' : 'td';
|
|
|
|
var tag = flags.align
|
|
|
|
? '<' + type + ' style="text-align:' + flags.align + '">'
|
|
|
|
: '<' + type + '>';
|
|
|
|
return tag + content + '</' + type + '>\n';
|
|
|
|
};
|
|
|
|
|
|
|
|
// span level renderer
|
|
|
|
Renderer.prototype.strong = function(text) {
|
|
|
|
return '<strong>' + text + '</strong>';
|
|
|
|
};
|
|
|
|
|
|
|
|
Renderer.prototype.em = function(text) {
|
|
|
|
return '<em>' + text + '</em>';
|
|
|
|
};
|
|
|
|
|
|
|
|
Renderer.prototype.codespan = function(text) {
|
|
|
|
return '<code>' + text + '</code>';
|
|
|
|
};
|
|
|
|
|
|
|
|
Renderer.prototype.br = function() {
|
2016-11-04 15:55:33 +01:00
|
|
|
return this.options.xhtml ? '<br/>' : '<br>';
|
2013-12-13 19:58:31 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
Renderer.prototype.del = function(text) {
|
|
|
|
return '<del>' + text + '</del>';
|
|
|
|
};
|
|
|
|
|
|
|
|
Renderer.prototype.link = function(href, title, text) {
|
2014-01-27 16:37:46 +01:00
|
|
|
if (this.options.sanitize) {
|
|
|
|
try {
|
|
|
|
var prot = decodeURIComponent(unescape(href))
|
|
|
|
.replace(/[^\w:]/g, '')
|
|
|
|
.toLowerCase();
|
|
|
|
} catch (e) {
|
|
|
|
return '';
|
|
|
|
}
|
2020-01-28 15:29:31 +01:00
|
|
|
if (prot.startsWith('javascript:') || prot.startsWith('vbscript:')) {
|
2014-01-27 16:37:46 +01:00
|
|
|
return '';
|
|
|
|
}
|
|
|
|
}
|
2013-12-13 19:58:31 +01:00
|
|
|
var out = '<a href="' + href + '"';
|
|
|
|
if (title) {
|
|
|
|
out += ' title="' + title + '"';
|
|
|
|
}
|
|
|
|
out += '>' + text + '</a>';
|
|
|
|
return out;
|
|
|
|
};
|
|
|
|
|
|
|
|
Renderer.prototype.image = function(href, title, text) {
|
|
|
|
var out = '<img src="' + href + '" alt="' + text + '"';
|
|
|
|
if (title) {
|
|
|
|
out += ' title="' + title + '"';
|
|
|
|
}
|
2016-11-04 15:55:33 +01:00
|
|
|
out += this.options.xhtml ? '/>' : '>';
|
2013-12-13 19:58:31 +01:00
|
|
|
return out;
|
|
|
|
};
|
|
|
|
|
2016-11-04 15:55:33 +01:00
|
|
|
Renderer.prototype.text = function(text) {
|
|
|
|
return text;
|
|
|
|
};
|
|
|
|
|
2013-12-13 19:58:31 +01:00
|
|
|
/**
|
|
|
|
* Parsing & Compiling
|
|
|
|
*/
|
|
|
|
|
|
|
|
function Parser(options) {
|
|
|
|
this.tokens = [];
|
|
|
|
this.token = null;
|
|
|
|
this.options = options || marked.defaults;
|
|
|
|
this.options.renderer = this.options.renderer || new Renderer;
|
|
|
|
this.renderer = this.options.renderer;
|
2014-01-27 16:37:46 +01:00
|
|
|
this.renderer.options = this.options;
|
2013-12-13 19:58:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Static Parse Method
|
|
|
|
*/
|
|
|
|
|
|
|
|
Parser.parse = function(src, options, renderer) {
|
|
|
|
var parser = new Parser(options, renderer);
|
2014-01-27 23:21:28 +01:00
|
|
|
var out = parser.parse(src);
|
|
|
|
out = parser.postprocess(out);
|
|
|
|
return out;
|
2013-12-13 19:58:31 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Parse Loop
|
|
|
|
*/
|
|
|
|
|
|
|
|
Parser.prototype.parse = function(src) {
|
|
|
|
this.inline = new InlineLexer(src.links, this.options, this.renderer);
|
|
|
|
this.tokens = src.reverse();
|
|
|
|
|
|
|
|
var out = '';
|
|
|
|
while (this.next()) {
|
|
|
|
out += this.tok();
|
|
|
|
}
|
|
|
|
|
|
|
|
return out;
|
|
|
|
};
|
|
|
|
|
2014-01-27 23:21:28 +01:00
|
|
|
/**
|
|
|
|
* Post Processing - replace stashed HTML
|
|
|
|
**/
|
|
|
|
Parser.prototype.postprocess = function(output) {
|
|
|
|
for (var i = 0; i < htmlStash.length; i++) {
|
|
|
|
var stash = htmlStash[i];
|
|
|
|
var key = stash[0],
|
|
|
|
html = stash[1],
|
|
|
|
safe = stash[2];
|
|
|
|
if (!safe) {
|
|
|
|
html = escape(html);
|
|
|
|
}
|
|
|
|
output = output.replace('<p>' + key + '</p>', html)
|
|
|
|
}
|
|
|
|
return output;
|
|
|
|
};
|
|
|
|
|
2013-12-13 19:58:31 +01:00
|
|
|
/**
|
|
|
|
* Next Token
|
|
|
|
*/
|
|
|
|
|
|
|
|
Parser.prototype.next = function() {
|
|
|
|
return this.token = this.tokens.pop();
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Preview Next Token
|
|
|
|
*/
|
|
|
|
|
|
|
|
Parser.prototype.peek = function() {
|
|
|
|
return this.tokens[this.tokens.length - 1] || 0;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Parse Text Tokens
|
|
|
|
*/
|
|
|
|
|
|
|
|
Parser.prototype.parseText = function() {
|
|
|
|
var body = this.token.text;
|
|
|
|
|
|
|
|
while (this.peek().type === 'text') {
|
|
|
|
body += '\n' + this.next().text;
|
|
|
|
}
|
|
|
|
|
|
|
|
return this.inline.output(body);
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Parse Current Token
|
|
|
|
*/
|
|
|
|
|
|
|
|
Parser.prototype.tok = function() {
|
|
|
|
switch (this.token.type) {
|
|
|
|
case 'space': {
|
|
|
|
return '';
|
|
|
|
}
|
2019-07-31 08:04:32 +02:00
|
|
|
case 'heading': {
|
|
|
|
return this.renderer.heading(
|
|
|
|
this.inline.output(this.token.text),
|
|
|
|
this.token.depth,
|
|
|
|
unescape(this.token.text),
|
|
|
|
this.slugger);
|
|
|
|
}
|
2013-12-13 19:58:31 +01:00
|
|
|
case 'hr': {
|
|
|
|
return this.renderer.hr();
|
|
|
|
}
|
|
|
|
case 'code': {
|
2014-01-27 16:37:46 +01:00
|
|
|
return this.renderer.code(this.token.text,
|
|
|
|
this.token.lang,
|
|
|
|
this.token.escaped);
|
2013-12-13 19:58:31 +01:00
|
|
|
}
|
|
|
|
case 'table': {
|
|
|
|
var header = ''
|
|
|
|
, body = ''
|
|
|
|
, i
|
|
|
|
, row
|
|
|
|
, cell
|
|
|
|
, flags
|
|
|
|
, j;
|
|
|
|
|
|
|
|
// header
|
|
|
|
cell = '';
|
|
|
|
for (i = 0; i < this.token.header.length; i++) {
|
|
|
|
flags = { header: true, align: this.token.align[i] };
|
|
|
|
cell += this.renderer.tablecell(
|
|
|
|
this.inline.output(this.token.header[i]),
|
|
|
|
{ header: true, align: this.token.align[i] }
|
|
|
|
);
|
|
|
|
}
|
|
|
|
header += this.renderer.tablerow(cell);
|
|
|
|
|
|
|
|
for (i = 0; i < this.token.cells.length; i++) {
|
|
|
|
row = this.token.cells[i];
|
|
|
|
|
|
|
|
cell = '';
|
|
|
|
for (j = 0; j < row.length; j++) {
|
|
|
|
cell += this.renderer.tablecell(
|
|
|
|
this.inline.output(row[j]),
|
|
|
|
{ header: false, align: this.token.align[j] }
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
body += this.renderer.tablerow(cell);
|
|
|
|
}
|
|
|
|
return this.renderer.table(header, body);
|
|
|
|
}
|
|
|
|
case 'blockquote_start': {
|
|
|
|
var body = '';
|
|
|
|
|
|
|
|
while (this.next().type !== 'blockquote_end') {
|
|
|
|
body += this.tok();
|
|
|
|
}
|
|
|
|
|
|
|
|
return this.renderer.blockquote(body);
|
|
|
|
}
|
|
|
|
case 'list_start': {
|
|
|
|
var body = ''
|
2019-08-11 07:41:34 +02:00
|
|
|
, ordered = this.token.ordered
|
|
|
|
, start = this.token.start;
|
2013-12-13 19:58:31 +01:00
|
|
|
|
|
|
|
while (this.next().type !== 'list_end') {
|
|
|
|
body += this.tok();
|
|
|
|
}
|
|
|
|
|
2019-08-11 07:41:34 +02:00
|
|
|
return this.renderer.list(body, ordered, start);
|
2013-12-13 19:58:31 +01:00
|
|
|
}
|
|
|
|
case 'list_item_start': {
|
2019-08-11 07:41:34 +02:00
|
|
|
var body = ''
|
|
|
|
, loose = this.token.loose;
|
2013-12-13 19:58:31 +01:00
|
|
|
|
|
|
|
while (this.next().type !== 'list_item_end') {
|
2019-08-11 07:41:34 +02:00
|
|
|
body += !loose && this.token.type === 'text'
|
2013-12-13 19:58:31 +01:00
|
|
|
? this.parseText()
|
|
|
|
: this.tok();
|
|
|
|
}
|
|
|
|
|
|
|
|
return this.renderer.listitem(body);
|
|
|
|
}
|
|
|
|
case 'html': {
|
|
|
|
var html = !this.token.pre && !this.options.pedantic
|
|
|
|
? this.inline.output(this.token.text)
|
|
|
|
: this.token.text;
|
|
|
|
return this.renderer.html(html);
|
|
|
|
}
|
|
|
|
case 'paragraph': {
|
|
|
|
return this.renderer.paragraph(this.inline.output(this.token.text));
|
|
|
|
}
|
|
|
|
case 'text': {
|
|
|
|
return this.renderer.paragraph(this.parseText());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Helpers
|
|
|
|
*/
|
|
|
|
|
|
|
|
function escape(html, encode) {
|
|
|
|
return html
|
|
|
|
.replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&')
|
|
|
|
.replace(/</g, '<')
|
|
|
|
.replace(/>/g, '>')
|
|
|
|
.replace(/"/g, '"')
|
|
|
|
.replace(/'/g, ''');
|
|
|
|
}
|
|
|
|
|
2019-12-13 01:38:49 +01:00
|
|
|
var unescapeReplacements = {
|
|
|
|
amp: '&',
|
|
|
|
lt: '<',
|
|
|
|
gt: '>',
|
|
|
|
quot: '"',
|
|
|
|
colon: ':',
|
|
|
|
};
|
|
|
|
|
2014-01-27 16:37:46 +01:00
|
|
|
function unescape(html) {
|
2016-11-05 21:35:00 +01:00
|
|
|
// explicitly match decimal, hex, and named HTML entities
|
2019-12-13 01:38:49 +01:00
|
|
|
return html.replace(/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:[0-9A-Za-z]+));?/g, function(_, n) {
|
2014-01-27 16:37:46 +01:00
|
|
|
n = n.toLowerCase();
|
|
|
|
if (n.charAt(0) === '#') {
|
|
|
|
return n.charAt(1) === 'x'
|
|
|
|
? String.fromCharCode(parseInt(n.substring(2), 16))
|
|
|
|
: String.fromCharCode(+n.substring(1));
|
|
|
|
}
|
2019-12-13 01:38:49 +01:00
|
|
|
return unescapeReplacements[n] || '';
|
2014-01-27 16:37:46 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2013-12-13 19:58:31 +01:00
|
|
|
function replace(regex, opt) {
|
|
|
|
regex = regex.source;
|
|
|
|
opt = opt || '';
|
|
|
|
return function self(name, val) {
|
|
|
|
if (!name) return new RegExp(regex, opt);
|
|
|
|
val = val.source || val;
|
|
|
|
val = val.replace(/(^|[^\[])\^/g, '$1');
|
|
|
|
regex = regex.replace(name, val);
|
|
|
|
return self;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function noop() {}
|
|
|
|
noop.exec = noop;
|
|
|
|
|
|
|
|
function merge(obj) {
|
|
|
|
var i = 1
|
|
|
|
, target
|
|
|
|
, key;
|
|
|
|
|
|
|
|
for (; i < arguments.length; i++) {
|
|
|
|
target = arguments[i];
|
|
|
|
for (key in target) {
|
|
|
|
if (Object.prototype.hasOwnProperty.call(target, key)) {
|
|
|
|
obj[key] = target[key];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return obj;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Marked
|
|
|
|
*/
|
|
|
|
|
|
|
|
function marked(src, opt, callback) {
|
|
|
|
if (callback || typeof opt === 'function') {
|
|
|
|
if (!callback) {
|
|
|
|
callback = opt;
|
|
|
|
opt = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
opt = merge({}, marked.defaults, opt || {});
|
|
|
|
|
|
|
|
var highlight = opt.highlight
|
|
|
|
, tokens
|
|
|
|
, pending
|
|
|
|
, i = 0;
|
|
|
|
|
2014-01-27 23:21:28 +01:00
|
|
|
htmlStashCounter = 0;
|
|
|
|
htmlStash = [];
|
|
|
|
for (var k = 0; k < opt.preprocessors.length; k++) {
|
|
|
|
src = opt.preprocessors[k](src);
|
|
|
|
}
|
|
|
|
|
2013-12-13 19:58:31 +01:00
|
|
|
try {
|
|
|
|
tokens = Lexer.lex(src, opt)
|
|
|
|
} catch (e) {
|
|
|
|
return callback(e);
|
|
|
|
}
|
|
|
|
|
|
|
|
pending = tokens.length;
|
|
|
|
|
2016-11-04 15:55:33 +01:00
|
|
|
var done = function(err) {
|
|
|
|
if (err) {
|
|
|
|
opt.highlight = highlight;
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
var out;
|
2013-12-13 19:58:31 +01:00
|
|
|
|
|
|
|
try {
|
|
|
|
out = Parser.parse(tokens, opt);
|
|
|
|
} catch (e) {
|
|
|
|
err = e;
|
|
|
|
}
|
|
|
|
|
|
|
|
opt.highlight = highlight;
|
|
|
|
|
|
|
|
return err
|
|
|
|
? callback(err)
|
|
|
|
: callback(null, out);
|
|
|
|
};
|
|
|
|
|
2014-01-27 16:37:46 +01:00
|
|
|
if (!highlight || highlight.length < 3) {
|
|
|
|
return done();
|
|
|
|
}
|
|
|
|
|
|
|
|
delete opt.highlight;
|
|
|
|
|
|
|
|
if (!pending) return done();
|
|
|
|
|
|
|
|
for (; i < tokens.length; i++) {
|
|
|
|
(function(token) {
|
|
|
|
if (token.type !== 'code') {
|
|
|
|
return --pending || done();
|
|
|
|
}
|
|
|
|
return highlight(token.text, token.lang, function(err, code) {
|
2016-11-04 15:55:33 +01:00
|
|
|
if (err) return done(err);
|
2014-01-27 16:37:46 +01:00
|
|
|
if (code == null || code === token.text) {
|
|
|
|
return --pending || done();
|
|
|
|
}
|
|
|
|
token.text = code;
|
|
|
|
token.escaped = true;
|
|
|
|
--pending || done();
|
|
|
|
});
|
|
|
|
})(tokens[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
2013-12-13 19:58:31 +01:00
|
|
|
}
|
|
|
|
try {
|
|
|
|
if (opt) opt = merge({}, marked.defaults, opt);
|
2014-01-27 23:21:28 +01:00
|
|
|
htmlStashCounter = 0;
|
|
|
|
htmlStash = [];
|
|
|
|
for (var i = 0; i < marked.defaults.preprocessors.length; i++) {
|
|
|
|
src = marked.defaults.preprocessors[i](src);
|
|
|
|
}
|
2013-12-13 19:58:31 +01:00
|
|
|
return Parser.parse(Lexer.lex(src, opt), opt);
|
|
|
|
} catch (e) {
|
|
|
|
e.message += '\nPlease report this to https://github.com/chjj/marked.';
|
|
|
|
if ((opt || marked.defaults).silent) {
|
2017-04-18 04:55:19 +02:00
|
|
|
return '<p>An error occurred:</p><pre>'
|
2013-12-13 19:58:31 +01:00
|
|
|
+ escape(e.message + '', true)
|
|
|
|
+ '</pre>';
|
|
|
|
}
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Options
|
|
|
|
*/
|
|
|
|
|
|
|
|
marked.options =
|
|
|
|
marked.setOptions = function(opt) {
|
|
|
|
merge(marked.defaults, opt);
|
|
|
|
return marked;
|
|
|
|
};
|
|
|
|
|
|
|
|
marked.defaults = {
|
|
|
|
gfm: true,
|
2014-01-04 00:16:40 +01:00
|
|
|
emoji: false,
|
2016-06-24 22:39:44 +02:00
|
|
|
unicodeemoji: false,
|
2018-07-18 14:36:04 +02:00
|
|
|
timestamp: true,
|
2013-12-13 19:58:31 +01:00
|
|
|
tables: true,
|
|
|
|
breaks: false,
|
|
|
|
pedantic: false,
|
|
|
|
sanitize: false,
|
2016-11-04 15:55:33 +01:00
|
|
|
sanitizer: null,
|
|
|
|
mangle: true,
|
2013-12-13 19:58:31 +01:00
|
|
|
smartLists: false,
|
|
|
|
silent: false,
|
2014-01-27 16:37:46 +01:00
|
|
|
highlight: null,
|
|
|
|
langPrefix: 'lang-',
|
2013-12-13 19:58:31 +01:00
|
|
|
smartypants: false,
|
2014-01-27 16:37:46 +01:00
|
|
|
headerPrefix: '',
|
2014-01-27 23:21:28 +01:00
|
|
|
renderer: new Renderer,
|
2016-11-04 15:55:33 +01:00
|
|
|
preprocessors: [],
|
|
|
|
xhtml: false
|
2013-12-13 19:58:31 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Expose
|
|
|
|
*/
|
|
|
|
|
|
|
|
marked.Parser = Parser;
|
|
|
|
marked.parser = Parser.parse;
|
|
|
|
|
|
|
|
marked.Renderer = Renderer;
|
|
|
|
|
|
|
|
marked.Lexer = Lexer;
|
|
|
|
marked.lexer = Lexer.lex;
|
|
|
|
|
|
|
|
marked.InlineLexer = InlineLexer;
|
|
|
|
marked.inlineLexer = InlineLexer.output;
|
|
|
|
|
|
|
|
marked.parse = marked;
|
|
|
|
|
2014-01-27 23:21:28 +01:00
|
|
|
marked.stashHtml = stashHtml;
|
|
|
|
|
2016-11-04 15:55:33 +01:00
|
|
|
if (typeof module !== 'undefined' && typeof exports === 'object') {
|
2013-12-13 19:58:31 +01:00
|
|
|
module.exports = marked;
|
|
|
|
} else if (typeof define === 'function' && define.amd) {
|
|
|
|
define(function() { return marked; });
|
|
|
|
} else {
|
|
|
|
this.marked = marked;
|
|
|
|
}
|
|
|
|
|
|
|
|
}).call(function() {
|
|
|
|
return this || (typeof window !== 'undefined' ? window : global);
|
2014-01-04 00:16:40 +01:00
|
|
|
}());
|