Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -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

Expand Down
24 changes: 12 additions & 12 deletions packages/core/src/lib/markdown-it/highlight/HighlightRule.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,46 +7,46 @@ 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()) {
const withinRangeStart = compares[0] <= 0;
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 `<span class="highlighted">${codeStr}\n</span>`;
}
Expand All @@ -57,10 +57,10 @@ class HighlightRule {
const content = codeStr.substr(codeStartIdx);
return [indents, content];
}

static _highlightTextOnly(codeStr) {
const [indents, content] = HighlightRule._splitCodeAndIndentation(codeStr);
return `<span>${indents}<span class="highlighted">${content}</span>\n</span>`
return `<span>${indents}<span class="highlighted">${content}</span>\n</span>`;
}

isLineRange() {
Expand All @@ -69,5 +69,5 @@ class HighlightRule {
}

module.exports = {
HighlightRule
HighlightRule,
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,35 @@ 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);
}

// match fails, so it is just line numbers
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
Expand All @@ -49,5 +49,5 @@ class HighlightRuleComponent {
}

module.exports = {
HighlightRuleComponent
HighlightRuleComponent,
};
59 changes: 33 additions & 26 deletions packages/core/src/lib/markdown-it/index.js
Original file line number Diff line number Diff line change
@@ -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');

Expand All @@ -19,24 +24,21 @@ 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'))
.use(require('./plugins/markdown-it-icons'))
.use(require('./plugins/markdown-it-footnotes'));

// fix table style
markdownIt.renderer.rules.table_open = (tokens, idx) => {
return '<div class="table-responsive"><table class="markbind-table table table-bordered table-striped">';
};
markdownIt.renderer.rules.table_close = (tokens, idx) => {
return '</table></div>';
};
markdownIt.renderer.rules.table_open = _.constant(
'<div class="table-responsive"><table class="markbind-table table table-bordered table-striped">');
markdownIt.renderer.rules.table_close = _.constant('</table></div>');

function getAttributeAndDelete(token, attr) {
const index = token.attrIndex(attr);
Expand Down Expand Up @@ -72,14 +74,18 @@ markdownIt.renderer.rules.fence = (tokens, idx, options, env, slf) => {
Note the line break contained inside a <span> 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');
Expand Down Expand Up @@ -107,7 +113,7 @@ markdownIt.renderer.rules.fence = (tokens, idx, options, env, slf) => {
// wrap all lines with <span> 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);
}
Expand All @@ -125,7 +131,9 @@ markdownIt.renderer.rules.fence = (tokens, idx, options, env, slf) => {
const codeBlockContent = `<pre><code ${slf.renderAttrs(token)}>${str}</code></pre>`;
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 '<div class="code-block">'
+ `<div class="${headingStyle}"><span>${renderedHeading}</span></div>`
+ `<div class="code-block-content">${codeBlockContent}</div>`
Expand All @@ -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 '<code' + slf.renderAttrs(token) + '>'
+ hljs.highlight(lang, token.content, true).value
+ '</code>';
} else {
token.attrSet('class', `${inlineClass} no-lang`);
return '<code' + slf.renderAttrs(token) + '>'
+ markdownIt.utils.escapeHtml(token.content)
+ '</code>';
return `<code${slf.renderAttrs(token)}>${
hljs.highlight(lang, token.content, true).value
}</code>`;
}
token.attrSet('class', `${inlineClass} no-lang`);
return `<code${slf.renderAttrs(token)}>${
markdownIt.utils.escapeHtml(token.content)
}</code>`;
};

const fixedNumberEmojiDefs = require('./patches/markdown-it-emoji-fixed');
markdownIt.use(require('markdown-it-emoji'), {
defs: fixedNumberEmojiDefs
defs: fixedNumberEmojiDefs,
});

module.exports = markdownIt;
53 changes: 26 additions & 27 deletions packages/core/src/lib/markdown-it/plugins/markdown-it-icons.js
Original file line number Diff line number Diff line change
@@ -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 `<span aria-hidden="true" class="glyphicon glyphicon-${iconFontName}"></span>`;
} else if (iconFontType === 'octicon') {
// ensure octicons are valid
if (!octicons.hasOwnProperty(iconFontName)) {
return `<span aria-hidden="true"></span>`;
}
return iconClass
? octicons[iconFontName].toSVG({"class": iconClass})
: octicons[iconFontName].toSVG();
} else if (iconFontType === 'octiconlight') {
// ensure octicons are valid
if (!octicons.hasOwnProperty(iconFontName)) {
return `<span aria-hidden="true"></span>`;
}
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 `<span aria-hidden="true" class="${iconFontType} fa-${iconFontName}"></span>`;
}
}
if (iconFontType === 'glyphicon') {
return `<span aria-hidden="true" class="glyphicon glyphicon-${iconFontName}"></span>`;
} else if (iconFontType === 'octicon') {
// ensure octicons are valid
if (!(iconFontName in octicons)) {
return '<span aria-hidden="true"></span>';
}
return iconClass
? octicons[iconFontName].toSVG({ class: iconClass })
: octicons[iconFontName].toSVG();
} else if (iconFontType === 'octiconlight') {
// ensure octicons are valid
if (!(iconFontName in octicons)) {
return '<span aria-hidden="true"></span>';
}
return iconClass
? octicons[iconFontName].toSVG({ style: 'color: #fff;', class: iconClass })
: octicons[iconFontName].toSVG({ style: 'color: #fff;' });
} // If icon is a Font Awesome icon
return `<span aria-hidden="true" class="${iconFontType} fa-${iconFontName}"></span>`;
},
);