import { Lexer } from './Lexer.js'; import { Parser } from './Parser.js'; import { Tokenizer } from './Tokenizer.js'; import { Renderer } from './Renderer.js'; import { TextRenderer } from './TextRenderer.js'; import { Slugger } from './Slugger.js'; import { Hooks } from './Hooks.js'; import { checkSanitizeDeprecation, escape } from './helpers.js'; import { getDefaults, changeDefaults, defaults } from './defaults.js'; function onError(silent, async, callback) { return (e) => { e.message += '\nPlease report this to https://github.com/markedjs/marked.'; if (silent) { const msg = '
An error occurred:
' + escape(e.message + '', true) + ''; if (async) { return Promise.resolve(msg); } if (callback) { callback(null, msg); return; } return msg; } if (async) { return Promise.reject(e); } if (callback) { callback(e); return; } throw e; }; } function parseMarkdown(lexer, parser) { return (src, opt, callback) => { if (typeof opt === 'function') { callback = opt; opt = null; } const origOpt = { ...opt }; opt = { ...marked.defaults, ...origOpt }; const throwError = onError(opt.silent, opt.async, callback); // throw error in case of non string input if (typeof src === 'undefined' || src === null) { return throwError(new Error('marked(): input parameter is undefined or null')); } if (typeof src !== 'string') { return throwError(new Error('marked(): input parameter is of type ' + Object.prototype.toString.call(src) + ', string expected')); } checkSanitizeDeprecation(opt); if (opt.hooks) { opt.hooks.options = opt; } if (callback) { const highlight = opt.highlight; let tokens; try { if (opt.hooks) { src = opt.hooks.preprocess(src); } tokens = lexer(src, opt); } catch (e) { return throwError(e); } const done = function(err) { let out; if (!err) { try { if (opt.walkTokens) { marked.walkTokens(tokens, opt.walkTokens); } out = parser(tokens, opt); if (opt.hooks) { out = opt.hooks.postprocess(out); } } catch (e) { err = e; } } opt.highlight = highlight; return err ? throwError(err) : callback(null, out); }; if (!highlight || highlight.length < 3) { return done(); } delete opt.highlight; if (!tokens.length) return done(); let pending = 0; marked.walkTokens(tokens, function(token) { if (token.type === 'code') { pending++; setTimeout(() => { highlight(token.text, token.lang, function(err, code) { if (err) { return done(err); } if (code != null && code !== token.text) { token.text = code; token.escaped = true; } pending--; if (pending === 0) { done(); } }); }, 0); } }); if (pending === 0) { done(); } return; } if (opt.async) { return Promise.resolve(opt.hooks ? opt.hooks.preprocess(src) : src) .then(src => lexer(src, opt)) .then(tokens => opt.walkTokens ? Promise.all(marked.walkTokens(tokens, opt.walkTokens)).then(() => tokens) : tokens) .then(tokens => parser(tokens, opt)) .then(html => opt.hooks ? opt.hooks.postprocess(html) : html) .catch(throwError); } try { if (opt.hooks) { src = opt.hooks.preprocess(src); } const tokens = lexer(src, opt); if (opt.walkTokens) { marked.walkTokens(tokens, opt.walkTokens); } let html = parser(tokens, opt); if (opt.hooks) { html = opt.hooks.postprocess(html); } return html; } catch (e) { return throwError(e); } }; } /** * Marked */ export function marked(src, opt, callback) { return parseMarkdown(Lexer.lex, Parser.parse)(src, opt, callback); } /** * Options */ marked.options = marked.setOptions = function(opt) { marked.defaults = { ...marked.defaults, ...opt }; changeDefaults(marked.defaults); return marked; }; marked.getDefaults = getDefaults; marked.defaults = defaults; /** * Use Extension */ marked.use = function(...args) { const extensions = marked.defaults.extensions || { renderers: {}, childTokens: {} }; args.forEach((pack) => { // copy options to new object const opts = { ...pack }; // set async to true if it was set to true before opts.async = marked.defaults.async || opts.async || false; // ==-- Parse "addon" extensions --== // if (pack.extensions) { pack.extensions.forEach((ext) => { if (!ext.name) { throw new Error('extension name required'); } if (ext.renderer) { // Renderer extensions const prevRenderer = extensions.renderers[ext.name]; if (prevRenderer) { // Replace extension with func to run new extension but fall back if false extensions.renderers[ext.name] = function(...args) { let ret = ext.renderer.apply(this, args); if (ret === false) { ret = prevRenderer.apply(this, args); } return ret; }; } else { extensions.renderers[ext.name] = ext.renderer; } } if (ext.tokenizer) { // Tokenizer Extensions if (!ext.level || (ext.level !== 'block' && ext.level !== 'inline')) { throw new Error("extension level must be 'block' or 'inline'"); } if (extensions[ext.level]) { extensions[ext.level].unshift(ext.tokenizer); } else { extensions[ext.level] = [ext.tokenizer]; } if (ext.start) { // Function to check for start of token if (ext.level === 'block') { if (extensions.startBlock) { extensions.startBlock.push(ext.start); } else { extensions.startBlock = [ext.start]; } } else if (ext.level === 'inline') { if (extensions.startInline) { extensions.startInline.push(ext.start); } else { extensions.startInline = [ext.start]; } } } } if (ext.childTokens) { // Child tokens to be visited by walkTokens extensions.childTokens[ext.name] = ext.childTokens; } }); opts.extensions = extensions; } // ==-- Parse "overwrite" extensions --== // if (pack.renderer) { const renderer = marked.defaults.renderer || new Renderer(); for (const prop in pack.renderer) { const prevRenderer = renderer[prop]; // Replace renderer with func to run extension, but fall back if false renderer[prop] = (...args) => { let ret = pack.renderer[prop].apply(renderer, args); if (ret === false) { ret = prevRenderer.apply(renderer, args); } return ret; }; } opts.renderer = renderer; } if (pack.tokenizer) { const tokenizer = marked.defaults.tokenizer || new Tokenizer(); for (const prop in pack.tokenizer) { const prevTokenizer = tokenizer[prop]; // Replace tokenizer with func to run extension, but fall back if false tokenizer[prop] = (...args) => { let ret = pack.tokenizer[prop].apply(tokenizer, args); if (ret === false) { ret = prevTokenizer.apply(tokenizer, args); } return ret; }; } opts.tokenizer = tokenizer; } // ==-- Parse Hooks extensions --== // if (pack.hooks) { const hooks = marked.defaults.hooks || new Hooks(); for (const prop in pack.hooks) { const prevHook = hooks[prop]; if (Hooks.passThroughHooks.has(prop)) { hooks[prop] = (arg) => { if (marked.defaults.async) { return Promise.resolve(pack.hooks[prop].call(hooks, arg)).then(ret => { return prevHook.call(hooks, ret); }); } const ret = pack.hooks[prop].call(hooks, arg); return prevHook.call(hooks, ret); }; } else { hooks[prop] = (...args) => { let ret = pack.hooks[prop].apply(hooks, args); if (ret === false) { ret = prevHook.apply(hooks, args); } return ret; }; } } opts.hooks = hooks; } // ==-- Parse WalkTokens extensions --== // if (pack.walkTokens) { const walkTokens = marked.defaults.walkTokens; opts.walkTokens = function(token) { let values = []; values.push(pack.walkTokens.call(this, token)); if (walkTokens) { values = values.concat(walkTokens.call(this, token)); } return values; }; } marked.setOptions(opts); }); }; /** * Run callback for every token */ marked.walkTokens = function(tokens, callback) { let values = []; for (const token of tokens) { values = values.concat(callback.call(marked, token)); switch (token.type) { case 'table': { for (const cell of token.header) { values = values.concat(marked.walkTokens(cell.tokens, callback)); } for (const row of token.rows) { for (const cell of row) { values = values.concat(marked.walkTokens(cell.tokens, callback)); } } break; } case 'list': { values = values.concat(marked.walkTokens(token.items, callback)); break; } default: { if (marked.defaults.extensions && marked.defaults.extensions.childTokens && marked.defaults.extensions.childTokens[token.type]) { // Walk any extensions marked.defaults.extensions.childTokens[token.type].forEach(function(childTokens) { values = values.concat(marked.walkTokens(token[childTokens], callback)); }); } else if (token.tokens) { values = values.concat(marked.walkTokens(token.tokens, callback)); } } } } return values; }; /** * Parse Inline * @param {string} src */ marked.parseInline = parseMarkdown(Lexer.lexInline, Parser.parseInline); /** * Expose */ marked.Parser = Parser; marked.parser = Parser.parse; marked.Renderer = Renderer; marked.TextRenderer = TextRenderer; marked.Lexer = Lexer; marked.lexer = Lexer.lex; marked.Tokenizer = Tokenizer; marked.Slugger = Slugger; marked.Hooks = Hooks; marked.parse = marked; export const options = marked.options; export const setOptions = marked.setOptions; export const use = marked.use; export const walkTokens = marked.walkTokens; export const parseInline = marked.parseInline; export const parse = marked; export const parser = Parser.parse; export const lexer = Lexer.lex; export { defaults, getDefaults } from './defaults.js'; export { Lexer } from './Lexer.js'; export { Parser } from './Parser.js'; export { Tokenizer } from './Tokenizer.js'; export { Renderer } from './Renderer.js'; export { TextRenderer } from './TextRenderer.js'; export { Slugger } from './Slugger.js'; export { Hooks } from './Hooks.js';