import hljs from "highlight.js"; /* highlightjs-line-numbers.js 2.8.0 | (C) 2018 Yauheni Pakala | MIT License | github.com/wcoder/highlightjs-line-numbers.js */ !(function (r, o) { "use strict"; var e, i = "hljs-ln", l = "hljs-ln-line", h = "hljs-ln-code", s = "hljs-ln-numbers", c = "hljs-ln-n", m = "data-line-number", a = /\r\n|\r|\n/g; function u(e) { for (var n = e.toString(), t = e.anchorNode; "TD" !== t.nodeName; ) t = t.parentNode; for (var r = e.focusNode; "TD" !== r.nodeName; ) r = r.parentNode; var o = parseInt(t.dataset.lineNumber), a = parseInt(r.dataset.lineNumber); if (o == a) return n; var i, l = t.textContent, s = r.textContent; for ( a < o && ((i = o), (o = a), (a = i), (i = l), (l = s), (s = i)); 0 !== n.indexOf(l); ) l = l.slice(1); for (; -1 === n.lastIndexOf(s); ) s = s.slice(0, -1); for ( var c = l, u = (function (e) { for (var n = e; "TABLE" !== n.nodeName; ) n = n.parentNode; return n; })(t), d = o + 1; d < a; ++d ) { var f = p('.{0}[{1}="{2}"]', [h, m, d]); c += "\n" + u.querySelector(f).textContent; } return (c += "\n" + s); } function n(e) { try { var n = o.querySelectorAll("code.hljs,code.nohighlight"); for (var t in n) n.hasOwnProperty(t) && (n[t].classList.contains("nohljsln") || d(n[t], e)); } catch (e) { r.console.error("LineNumbers error: ", e); } } function d(e, n) { if ("object" == typeof e) e.innerHTML = f(e, n); } function f(e, n) { var t, r, o = ((t = e), { singleLine: (function (e) { return !!e.singleLine && e.singleLine; })((r = (r = n) || {})), startFrom: (function (e, n) { var t = 1; isFinite(n.startFrom) && (t = n.startFrom); var r = (function (e, n) { return e.hasAttribute(n) ? e.getAttribute(n) : null; })(e, "data-ln-start-from"); return ( null !== r && (t = (function (e, n) { if (!e) return n; var t = Number(e); return isFinite(t) ? t : n; })(r, 1)), t ); })(t, r), }); return ( (function e(n) { var t = n.childNodes; for (var r in t) { var o; t.hasOwnProperty(r) && ((o = t[r]), 0 < (o.textContent.trim().match(a) || []).length && (0 < o.childNodes.length ? e(o) : v(o.parentNode))); } })(e), (function (e, n) { var t = g(e); "" === t[t.length - 1].trim() && t.pop(); if (1 < t.length || n.singleLine) { for (var r = "", o = 0, a = t.length; o < a; o++) r += p( '
node has the
* 'data-line-numbers' attribute we also generate slide
* numbers.
*
* If the block contains multiple line highlight steps,
* we clone the block and create a fragment for each step.
*/
highlightBlock: function (block) {
hljs.highlightElement(block);
// Don't generate line numbers for empty code blocks
if (block.innerHTML.trim().length === 0) return;
if (block.hasAttribute("data-line-numbers")) {
hljs.lineNumbersBlock(block, { singleLine: true });
var scrollState = { currentBlock: block };
// If there is more than one highlight step, generate
// fragments
var highlightSteps = Plugin.deserializeHighlightSteps(
block.getAttribute("data-line-numbers")
);
if (highlightSteps.length > 1) {
// If the original code block has a fragment-index,
// each clone should follow in an incremental sequence
var fragmentIndex = parseInt(
block.getAttribute("data-fragment-index"),
10
);
if (typeof fragmentIndex !== "number" || isNaN(fragmentIndex)) {
fragmentIndex = null;
}
// Generate fragments for all steps except the original block
highlightSteps.slice(1).forEach(function (highlight) {
var fragmentBlock = block.cloneNode(true);
fragmentBlock.setAttribute(
"data-line-numbers",
Plugin.serializeHighlightSteps([highlight])
);
fragmentBlock.classList.add("fragment");
block.parentNode.appendChild(fragmentBlock);
Plugin.highlightLines(fragmentBlock);
if (typeof fragmentIndex === "number") {
fragmentBlock.setAttribute("data-fragment-index", fragmentIndex);
fragmentIndex += 1;
} else {
fragmentBlock.removeAttribute("data-fragment-index");
}
// Scroll highlights into view as we step through them
fragmentBlock.addEventListener(
"visible",
Plugin.scrollHighlightedLineIntoView.bind(
Plugin,
fragmentBlock,
scrollState
)
);
fragmentBlock.addEventListener(
"hidden",
Plugin.scrollHighlightedLineIntoView.bind(
Plugin,
fragmentBlock.previousSibling,
scrollState
)
);
});
block.removeAttribute("data-fragment-index");
block.setAttribute(
"data-line-numbers",
Plugin.serializeHighlightSteps([highlightSteps[0]])
);
}
// Scroll the first highlight into view when the slide
// becomes visible. Note supported in IE11 since it lacks
// support for Element.closest.
var slide =
typeof block.closest === "function"
? block.closest("section:not(.stack)")
: null;
if (slide) {
var scrollFirstHighlightIntoView = function () {
Plugin.scrollHighlightedLineIntoView(block, scrollState, true);
slide.removeEventListener("visible", scrollFirstHighlightIntoView);
};
slide.addEventListener("visible", scrollFirstHighlightIntoView);
}
Plugin.highlightLines(block);
}
},
/**
* Animates scrolling to the first highlighted line
* in the given code block.
*/
scrollHighlightedLineIntoView: function (block, scrollState, skipAnimation) {
cancelAnimationFrame(scrollState.animationFrameID);
// Match the scroll position of the currently visible
// code block
if (scrollState.currentBlock) {
block.scrollTop = scrollState.currentBlock.scrollTop;
}
// Remember the current code block so that we can match
// its scroll position when showing/hiding fragments
scrollState.currentBlock = block;
var highlightBounds = this.getHighlightedLineBounds(block);
var viewportHeight = block.offsetHeight;
// Subtract padding from the viewport height
var blockStyles = getComputedStyle(block);
viewportHeight -=
parseInt(blockStyles.paddingTop) + parseInt(blockStyles.paddingBottom);
// Scroll position which centers all highlights
var startTop = block.scrollTop;
var targetTop =
highlightBounds.top +
(Math.min(highlightBounds.bottom - highlightBounds.top, viewportHeight) -
viewportHeight) /
2;
// Account for offsets in position applied to the
// that holds our lines of code
var lineTable = block.querySelector(".hljs-ln");
if (lineTable)
targetTop += lineTable.offsetTop - parseInt(blockStyles.paddingTop);
// Make sure the scroll target is within bounds
targetTop = Math.max(
Math.min(targetTop, block.scrollHeight - viewportHeight),
0
);
if (skipAnimation === true || startTop === targetTop) {
block.scrollTop = targetTop;
} else {
// Don't attempt to scroll if there is no overflow
if (block.scrollHeight <= viewportHeight) return;
var time = 0;
var animate = function () {
time = Math.min(time + 0.02, 1);
// Update our eased scroll position
block.scrollTop =
startTop + (targetTop - startTop) * Plugin.easeInOutQuart(time);
// Keep animating unless we've reached the end
if (time < 1) {
scrollState.animationFrameID = requestAnimationFrame(animate);
}
};
animate();
}
},
/**
* The easing function used when scrolling.
*/
easeInOutQuart: function (t) {
// easeInOutQuart
return t < 0.5 ? 8 * t * t * t * t : 1 - 8 * --t * t * t * t;
},
getHighlightedLineBounds: function (block) {
var highlightedLines = block.querySelectorAll(".highlight-line");
if (highlightedLines.length === 0) {
return { top: 0, bottom: 0 };
} else {
var firstHighlight = highlightedLines[0];
var lastHighlight = highlightedLines[highlightedLines.length - 1];
return {
top: firstHighlight.offsetTop,
bottom: lastHighlight.offsetTop + lastHighlight.offsetHeight,
};
}
},
/**
* Visually emphasize specific lines within a code block.
* This only works on blocks with line numbering turned on.
*
* @param {HTMLElement} block a block
* @param {String} [linesToHighlight] The lines that should be
* highlighted in this format:
* "1" = highlights line 1
* "2,5" = highlights lines 2 & 5
* "2,5-7" = highlights lines 2, 5, 6 & 7
*/
highlightLines: function (block, linesToHighlight) {
var highlightSteps = Plugin.deserializeHighlightSteps(
linesToHighlight || block.getAttribute("data-line-numbers")
);
if (highlightSteps.length) {
highlightSteps[0].forEach(function (highlight) {
var elementsToHighlight = [];
// Highlight a range
if (typeof highlight.end === "number") {
elementsToHighlight = [].slice.call(
block.querySelectorAll(
"table tr:nth-child(n+" +
highlight.start +
"):nth-child(-n+" +
highlight.end +
")"
)
);
}
// Highlight a single line
else if (typeof highlight.start === "number") {
elementsToHighlight = [].slice.call(
block.querySelectorAll(
"table tr:nth-child(" + highlight.start + ")"
)
);
}
if (elementsToHighlight.length) {
elementsToHighlight.forEach(function (lineElement) {
lineElement.classList.add("highlight-line");
});
block.classList.add("has-highlights");
}
});
}
},
/**
* Parses and formats a user-defined string of line
* numbers to highlight.
*
* @example
* Plugin.deserializeHighlightSteps( '1,2|3,5-10' )
* // [
* // [ { start: 1 }, { start: 2 } ],
* // [ { start: 3 }, { start: 5, end: 10 } ]
* // ]
*/
deserializeHighlightSteps: function (highlightSteps) {
// Remove whitespace
highlightSteps = highlightSteps.replace(/\s/g, "");
// Divide up our line number groups
highlightSteps = highlightSteps.split(Plugin.HIGHLIGHT_STEP_DELIMITER);
return highlightSteps.map(function (highlights) {
return highlights
.split(Plugin.HIGHLIGHT_LINE_DELIMITER)
.map(function (highlight) {
// Parse valid line numbers
if (/^[\d-]+$/.test(highlight)) {
highlight = highlight.split(Plugin.HIGHLIGHT_LINE_RANGE_DELIMITER);
var lineStart = parseInt(highlight[0], 10),
lineEnd = parseInt(highlight[1], 10);
if (isNaN(lineEnd)) {
return {
start: lineStart,
};
} else {
return {
start: lineStart,
end: lineEnd,
};
}
}
// If no line numbers are provided, no code will be highlighted
else {
return {};
}
});
});
},
/**
* Serializes parsed line number data into a string so
* that we can store it in the DOM.
*/
serializeHighlightSteps: function (highlightSteps) {
return highlightSteps
.map(function (highlights) {
return highlights
.map(function (highlight) {
// Line range
if (typeof highlight.end === "number") {
return (
highlight.start +
Plugin.HIGHLIGHT_LINE_RANGE_DELIMITER +
highlight.end
);
}
// Single line
else if (typeof highlight.start === "number") {
return highlight.start;
}
// All lines
else {
return "";
}
})
.join(Plugin.HIGHLIGHT_LINE_DELIMITER);
})
.join(Plugin.HIGHLIGHT_STEP_DELIMITER);
},
};
// Function to perform a better "data-trim" on code snippets
// Will slice an indentation amount on each line of the snippet (amount based on the line having the lowest indentation length)
function betterTrim(snippetEl) {
// Helper functions
function trimLeft(val) {
// Adapted from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/Trim#Polyfill
return val.replace(/^[\s\uFEFF\xA0]+/g, "");
}
function trimLineBreaks(input) {
var lines = input.split("\n");
// Trim line-breaks from the beginning
for (var i = 0; i < lines.length; i++) {
if (lines[i].trim() === "") {
lines.splice(i--, 1);
} else break;
}
// Trim line-breaks from the end
for (var i = lines.length - 1; i >= 0; i--) {
if (lines[i].trim() === "") {
lines.splice(i, 1);
} else break;
}
return lines.join("\n");
}
// Main function for betterTrim()
return (function (snippetEl) {
var content = trimLineBreaks(snippetEl.innerHTML);
var lines = content.split("\n");
// Calculate the minimum amount to remove on each line start of the snippet (can be 0)
var pad = lines.reduce(function (acc, line) {
if (
line.length > 0 &&
trimLeft(line).length > 0 &&
acc > line.length - trimLeft(line).length
) {
return line.length - trimLeft(line).length;
}
return acc;
}, Number.POSITIVE_INFINITY);
// Slice each line with this amount
return lines
.map(function (line, index) {
return line.slice(pad);
})
.join("\n");
})(snippetEl);
}
export default () => Plugin;