diff --git a/.eslintignore b/.eslintignore index ba48e019fd..f7462f15c3 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,7 +1,8 @@ *.min.* node_modules -packages/core/src/lib/markdown-it/* -packages/core/src/lib/markdown-it-shared/* +packages/core/src/lib/markdown-it/patches/* +packages/core/src/lib/markdown-it/plugins/* +!packages/core/src/lib/markdown-it/plugins/markdown-it-icons.js !.eslintrc.js diff --git a/packages/core/src/lib/markdown-it/highlight/HighlightRule.js b/packages/core/src/lib/markdown-it/highlight/HighlightRule.js index 2602b12353..6abe98fb98 100644 --- a/packages/core/src/lib/markdown-it/highlight/HighlightRule.js +++ b/packages/core/src/lib/markdown-it/highlight/HighlightRule.js @@ -7,16 +7,16 @@ class HighlightRule { */ this.ruleComponents = ruleComponents; } - + static parseRule(ruleString) { const components = ruleString.split('-').map(HighlightRuleComponent.parseRuleComponent); return new HighlightRule(components); } - + offsetLines(offset) { this.ruleComponents.forEach(comp => comp.offsetLineNumber(offset)); } - + shouldApplyHighlight(lineNumber) { const compares = this.ruleComponents.map(comp => comp.compareLine(lineNumber)); if (this.isLineRange()) { @@ -24,29 +24,29 @@ class HighlightRule { const withinRangeEnd = compares[1] >= 0; return withinRangeStart && withinRangeEnd; } - + const atLineNumber = compares[0] === 0; return atLineNumber; } - + applyHighlight(line) { const isLineSlice = this.ruleComponents.length === 1 && this.ruleComponents[0].isSlice; - + if (this.isLineRange()) { const shouldWholeLine = this.ruleComponents.some(comp => comp.isUnboundedSlice()); return shouldWholeLine ? HighlightRule._highlightWholeLine(line) : HighlightRule._highlightTextOnly(line); } - + if (isLineSlice) { // TODO: Implement slice-index based highlighting return HighlightRule._highlightWholeLine(line); } - + return HighlightRule._highlightTextOnly(line); } - + static _highlightWholeLine(codeStr) { return `${codeStr}\n`; } @@ -57,10 +57,10 @@ class HighlightRule { const content = codeStr.substr(codeStartIdx); return [indents, content]; } - + static _highlightTextOnly(codeStr) { const [indents, content] = HighlightRule._splitCodeAndIndentation(codeStr); - return `${indents}${content}\n` + return `${indents}${content}\n`; } isLineRange() { @@ -69,5 +69,5 @@ class HighlightRule { } module.exports = { - HighlightRule + HighlightRule, }; diff --git a/packages/core/src/lib/markdown-it/highlight/HighlightRuleComponent.js b/packages/core/src/lib/markdown-it/highlight/HighlightRuleComponent.js index 63e0073028..36dd0341c6 100644 --- a/packages/core/src/lib/markdown-it/highlight/HighlightRuleComponent.js +++ b/packages/core/src/lib/markdown-it/highlight/HighlightRuleComponent.js @@ -6,20 +6,20 @@ class HighlightRuleComponent { this.isSlice = isSlice || false; this.bounds = bounds || []; } - + static parseRuleComponent(compString) { // tries to match with the line slice pattern const matches = compString.match(LINESLICE_REGEX); if (matches) { const groups = matches.slice(1); // keep the capturing group matches only const lineNumber = parseInt(groups.shift(), 10); - + const isUnbounded = groups.every(x => x === ''); if (isUnbounded) { return new HighlightRuleComponent(lineNumber, true); } - - const bounds = groups.map(x => x !== '' ? parseInt(x, 10) : -1); + + const bounds = groups.map(x => (x !== '' ? parseInt(x, 10) : -1)); return new HighlightRuleComponent(lineNumber, true, bounds); } @@ -27,14 +27,14 @@ class HighlightRuleComponent { const lineNumber = parseInt(compString, 10); return new HighlightRuleComponent(lineNumber); } - + offsetLineNumber(offset) { this.lineNumber += offset; } /** * Compares the component's line number to a given line number. - * + * * @param lineNumber The line number to compare * @returns {number} A negative number, zero, or a positive number when the given line number * is after, at, or before the component's line number @@ -49,5 +49,5 @@ class HighlightRuleComponent { } module.exports = { - HighlightRuleComponent + HighlightRuleComponent, }; diff --git a/packages/core/src/lib/markdown-it/index.js b/packages/core/src/lib/markdown-it/index.js index d2d70c1850..216f3467b9 100644 --- a/packages/core/src/lib/markdown-it/index.js +++ b/packages/core/src/lib/markdown-it/index.js @@ -1,9 +1,14 @@ +const katex = require('katex'); const hljs = require('highlight.js'); const markdownIt = require('markdown-it')({ html: true, - linkify: true + linkify: true, }); -const slugify = require('@sindresorhus/slugify'); + +const _ = {}; +_.constant = require('lodash/constant'); + +const logger = require('../../utils/logger'); const { HighlightRule } = require('./highlight/HighlightRule.js'); @@ -19,11 +24,11 @@ markdownIt.use(createDoubleDelimiterInlineRule('%%', 'dimmed', 'emphasis')) markdownIt.use(require('markdown-it-mark')) .use(require('markdown-it-sub')) .use(require('markdown-it-sup')) - .use(require('markdown-it-imsize'), {autofill: false}) + .use(require('markdown-it-imsize'), { autofill: false }) .use(require('markdown-it-table-of-contents')) - .use(require('markdown-it-task-lists'), {enabled: true}) - .use(require('markdown-it-linkify-images'), {imgClass: 'img-fluid'}) - .use(require('markdown-it-texmath'), {engine: require('katex'), delimiters: 'brackets'}) + .use(require('markdown-it-task-lists'), { enabled: true }) + .use(require('markdown-it-linkify-images'), { imgClass: 'img-fluid' }) + .use(require('markdown-it-texmath'), { engine: katex, delimiters: 'brackets' }) .use(require('./patches/markdown-it-attrs-nunjucks')) .use(require('./plugins/markdown-it-radio-button')) .use(require('./plugins/markdown-it-block-embed')) @@ -31,12 +36,9 @@ markdownIt.use(require('markdown-it-mark')) .use(require('./plugins/markdown-it-footnotes')); // fix table style -markdownIt.renderer.rules.table_open = (tokens, idx) => { - return '
'; -}; -markdownIt.renderer.rules.table_close = (tokens, idx) => { - return '
'; -}; +markdownIt.renderer.rules.table_open = _.constant( + '
'); +markdownIt.renderer.rules.table_close = _.constant('
'); function getAttributeAndDelete(token, attr) { const index = token.attrIndex(attr); @@ -72,14 +74,18 @@ markdownIt.renderer.rules.fence = (tokens, idx, options, env, slf) => { Note the line break contained inside a element. So we have to split by lines THEN syntax highlight. */ - let state = null; // state stores the current parse state of hljs, so that we can pass it on line by line + + // state stores the current parse state of hljs, so that we can pass it on line by line + let state = null; lines = str.split('\n').map((line) => { const highlightedLine = hljs.highlight(lang, line, true, state); state = highlightedLine.top; return highlightedLine.value; }); highlighted = true; - } catch (_) {} + } catch (ex) { + logger.error(`Error processing code block line ${ex}`); + } } if (!highlighted) { lines = markdownIt.utils.escapeHtml(str).split('\n'); @@ -107,7 +113,7 @@ markdownIt.renderer.rules.fence = (tokens, idx, options, env, slf) => { // wrap all lines with so we can number them str = lines.map((line, index) => { const currentLineNumber = index + 1; - const rule = highlightRules.find(rule => rule.shouldApplyHighlight(currentLineNumber)) + const rule = highlightRules.find(highlightRule => highlightRule.shouldApplyHighlight(currentLineNumber)); if (rule) { return rule.applyHighlight(line); } @@ -125,7 +131,9 @@ markdownIt.renderer.rules.fence = (tokens, idx, options, env, slf) => { const codeBlockContent = `
${str}
`; if (heading) { const renderedHeading = markdownIt.renderInline(heading); - const headingStyle = (renderedHeading === heading) ? 'code-block-heading' : 'code-block-heading inline-markdown-heading'; + const headingStyle = (renderedHeading === heading) + ? 'code-block-heading' + : 'code-block-heading inline-markdown-heading'; return '
' + `
${renderedHeading}
` + `
${codeBlockContent}
` @@ -138,24 +146,23 @@ markdownIt.renderer.rules.fence = (tokens, idx, options, env, slf) => { markdownIt.renderer.rules.code_inline = (tokens, idx, options, env, slf) => { const token = tokens[idx]; const lang = token.attrGet('class'); - const inlineClass = `hljs inline`; + const inlineClass = 'hljs inline'; if (lang && hljs.getLanguage(lang)) { token.attrSet('class', `${inlineClass} ${lang}`); - return '' - + hljs.highlight(lang, token.content, true).value - + ''; - } else { - token.attrSet('class', `${inlineClass} no-lang`); - return '' - + markdownIt.utils.escapeHtml(token.content) - + ''; + return `${ + hljs.highlight(lang, token.content, true).value + }`; } + token.attrSet('class', `${inlineClass} no-lang`); + return `${ + markdownIt.utils.escapeHtml(token.content) + }`; }; const fixedNumberEmojiDefs = require('./patches/markdown-it-emoji-fixed'); markdownIt.use(require('markdown-it-emoji'), { - defs: fixedNumberEmojiDefs + defs: fixedNumberEmojiDefs, }); module.exports = markdownIt; diff --git a/packages/core/src/lib/markdown-it/plugins/markdown-it-icons.js b/packages/core/src/lib/markdown-it/plugins/markdown-it-icons.js index 96dde1dcf2..3ee154a89c 100644 --- a/packages/core/src/lib/markdown-it/plugins/markdown-it-icons.js +++ b/packages/core/src/lib/markdown-it/plugins/markdown-it-icons.js @@ -1,32 +1,31 @@ const octicons = require('@primer/octicons'); module.exports = require('markdown-it-regexp')( - /:(fa[brs]|glyphicon|octicon|octiconlight)-([a-z-]+)~?([a-z-]+)?:/, - (match, _) => { - let iconFontType = match[1]; - let iconFontName = match[2]; - let iconClass = match[3]; + /:(fa[brs]|glyphicon|octicon|octiconlight)-([a-z-]+)~?([a-z-]+)?:/, + (match) => { + const iconFontType = match[1]; + const iconFontName = match[2]; + const iconClass = match[3]; - if (iconFontType === 'glyphicon') { - return ``; - } else if (iconFontType === 'octicon') { - // ensure octicons are valid - if (!octicons.hasOwnProperty(iconFontName)) { - return ``; - } - return iconClass - ? octicons[iconFontName].toSVG({"class": iconClass}) - : octicons[iconFontName].toSVG(); - } else if (iconFontType === 'octiconlight') { - // ensure octicons are valid - if (!octicons.hasOwnProperty(iconFontName)) { - return ``; - } - return iconClass - ? octicons[iconFontName].toSVG({"style": "color: #fff;", "class": iconClass}) - : octicons[iconFontName].toSVG({"style": "color: #fff;"}); - } else { // If icon is a Font Awesome icon - return ``; - } - } + if (iconFontType === 'glyphicon') { + return ``; + } else if (iconFontType === 'octicon') { + // ensure octicons are valid + if (!(iconFontName in octicons)) { + return ''; + } + return iconClass + ? octicons[iconFontName].toSVG({ class: iconClass }) + : octicons[iconFontName].toSVG(); + } else if (iconFontType === 'octiconlight') { + // ensure octicons are valid + if (!(iconFontName in octicons)) { + return ''; + } + return iconClass + ? octicons[iconFontName].toSVG({ style: 'color: #fff;', class: iconClass }) + : octicons[iconFontName].toSVG({ style: 'color: #fff;' }); + } // If icon is a Font Awesome icon + return ``; + }, );