123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346 |
- (function () {
- if (typeof Prism === 'undefined' || typeof document === 'undefined' || !document.querySelector) {
- return;
- }
- var LINE_NUMBERS_CLASS = 'line-numbers';
- var LINKABLE_LINE_NUMBERS_CLASS = 'linkable-line-numbers';
- var NEW_LINE_EXP = /\n(?!$)/g;
- /**
- * @param {string} selector
- * @param {ParentNode} [container]
- * @returns {HTMLElement[]}
- */
- function $$(selector, container) {
- return Array.prototype.slice.call((container || document).querySelectorAll(selector));
- }
- /**
- * Returns whether the given element has the given class.
- *
- * @param {Element} element
- * @param {string} className
- * @returns {boolean}
- */
- function hasClass(element, className) {
- return element.classList.contains(className);
- }
- /**
- * Calls the given function.
- *
- * @param {() => any} func
- * @returns {void}
- */
- function callFunction(func) {
- func();
- }
- // Some browsers round the line-height, others don't.
- // We need to test for it to position the elements properly.
- var isLineHeightRounded = (function () {
- var res;
- return function () {
- if (typeof res === 'undefined') {
- var d = document.createElement('div');
- d.style.fontSize = '13px';
- d.style.lineHeight = '1.5';
- d.style.padding = '0';
- d.style.border = '0';
- d.innerHTML = ' <br /> ';
- document.body.appendChild(d);
- // Browsers that round the line-height should have offsetHeight === 38
- // The others should have 39.
- res = d.offsetHeight === 38;
- document.body.removeChild(d);
- }
- return res;
- };
- }());
- /**
- * Returns the top offset of the content box of the given parent and the content box of one of its children.
- *
- * @param {HTMLElement} parent
- * @param {HTMLElement} child
- */
- function getContentBoxTopOffset(parent, child) {
- var parentStyle = getComputedStyle(parent);
- var childStyle = getComputedStyle(child);
- /**
- * Returns the numeric value of the given pixel value.
- *
- * @param {string} px
- */
- function pxToNumber(px) {
- return +px.substr(0, px.length - 2);
- }
- return child.offsetTop
- + pxToNumber(childStyle.borderTopWidth)
- + pxToNumber(childStyle.paddingTop)
- - pxToNumber(parentStyle.paddingTop);
- }
- /**
- * Returns whether the Line Highlight plugin is active for the given element.
- *
- * If this function returns `false`, do not call `highlightLines` for the given element.
- *
- * @param {HTMLElement | null | undefined} pre
- * @returns {boolean}
- */
- function isActiveFor(pre) {
- if (!pre || !/pre/i.test(pre.nodeName)) {
- return false;
- }
- if (pre.hasAttribute('data-line')) {
- return true;
- }
- if (pre.id && Prism.util.isActive(pre, LINKABLE_LINE_NUMBERS_CLASS)) {
- // Technically, the line numbers plugin is also necessary but this plugin doesn't control the classes of
- // the line numbers plugin, so we can't assume that they are present.
- return true;
- }
- return false;
- }
- var scrollIntoView = true;
- Prism.plugins.lineHighlight = {
- /**
- * Highlights the lines of the given pre.
- *
- * This function is split into a DOM measuring and mutate phase to improve performance.
- * The returned function mutates the DOM when called.
- *
- * @param {HTMLElement} pre
- * @param {string | null} [lines]
- * @param {string} [classes='']
- * @returns {() => void}
- */
- highlightLines: function highlightLines(pre, lines, classes) {
- lines = typeof lines === 'string' ? lines : (pre.getAttribute('data-line') || '');
- var ranges = lines.replace(/\s+/g, '').split(',').filter(Boolean);
- var offset = +pre.getAttribute('data-line-offset') || 0;
- var parseMethod = isLineHeightRounded() ? parseInt : parseFloat;
- var lineHeight = parseMethod(getComputedStyle(pre).lineHeight);
- var hasLineNumbers = Prism.util.isActive(pre, LINE_NUMBERS_CLASS);
- var codeElement = pre.querySelector('code');
- var parentElement = hasLineNumbers ? pre : codeElement || pre;
- var mutateActions = /** @type {(() => void)[]} */ ([]);
- var lineBreakMatch = codeElement.textContent.match(NEW_LINE_EXP);
- var numberOfLines = lineBreakMatch ? lineBreakMatch.length + 1 : 1;
- /**
- * The top offset between the content box of the <code> element and the content box of the parent element of
- * the line highlight element (either `<pre>` or `<code>`).
- *
- * This offset might not be zero for some themes where the <code> element has a top margin. Some plugins
- * (or users) might also add element above the <code> element. Because the line highlight is aligned relative
- * to the <pre> element, we have to take this into account.
- *
- * This offset will be 0 if the parent element of the line highlight element is the `<code>` element.
- */
- var codePreOffset = !codeElement || parentElement == codeElement ? 0 : getContentBoxTopOffset(pre, codeElement);
- ranges.forEach(function (currentRange) {
- var range = currentRange.split('-');
- var start = +range[0];
- var end = +range[1] || start;
- end = Math.min(numberOfLines + offset, end);
- if (end < start) {
- return;
- }
- /** @type {HTMLElement} */
- var line = pre.querySelector('.line-highlight[data-range="' + currentRange + '"]') || document.createElement('div');
- mutateActions.push(function () {
- line.setAttribute('aria-hidden', 'true');
- line.setAttribute('data-range', currentRange);
- line.className = (classes || '') + ' line-highlight';
- });
- // if the line-numbers plugin is enabled, then there is no reason for this plugin to display the line numbers
- if (hasLineNumbers && Prism.plugins.lineNumbers) {
- var startNode = Prism.plugins.lineNumbers.getLine(pre, start);
- var endNode = Prism.plugins.lineNumbers.getLine(pre, end);
- if (startNode) {
- var top = startNode.offsetTop + codePreOffset + 'px';
- mutateActions.push(function () {
- line.style.top = top;
- });
- }
- if (endNode) {
- var height = (endNode.offsetTop - startNode.offsetTop) + endNode.offsetHeight + 'px';
- mutateActions.push(function () {
- line.style.height = height;
- });
- }
- } else {
- mutateActions.push(function () {
- line.setAttribute('data-start', String(start));
- if (end > start) {
- line.setAttribute('data-end', String(end));
- }
- line.style.top = (start - offset - 1) * lineHeight + codePreOffset + 'px';
- line.textContent = new Array(end - start + 2).join(' \n');
- });
- }
- mutateActions.push(function () {
- line.style.width = pre.scrollWidth + 'px';
- });
- mutateActions.push(function () {
- // allow this to play nicely with the line-numbers plugin
- // need to attack to pre as when line-numbers is enabled, the code tag is relatively which screws up the positioning
- parentElement.appendChild(line);
- });
- });
- var id = pre.id;
- if (hasLineNumbers && Prism.util.isActive(pre, LINKABLE_LINE_NUMBERS_CLASS) && id) {
- // This implements linkable line numbers. Linkable line numbers use Line Highlight to create a link to a
- // specific line. For this to work, the pre element has to:
- // 1) have line numbers,
- // 2) have the `linkable-line-numbers` class or an ascendant that has that class, and
- // 3) have an id.
- if (!hasClass(pre, LINKABLE_LINE_NUMBERS_CLASS)) {
- // add class to pre
- mutateActions.push(function () {
- pre.classList.add(LINKABLE_LINE_NUMBERS_CLASS);
- });
- }
- var start = parseInt(pre.getAttribute('data-start') || '1');
- // iterate all line number spans
- $$('.line-numbers-rows > span', pre).forEach(function (lineSpan, i) {
- var lineNumber = i + start;
- lineSpan.onclick = function () {
- var hash = id + '.' + lineNumber;
- // this will prevent scrolling since the span is obviously in view
- scrollIntoView = false;
- location.hash = hash;
- setTimeout(function () {
- scrollIntoView = true;
- }, 1);
- };
- });
- }
- return function () {
- mutateActions.forEach(callFunction);
- };
- }
- };
- function applyHash() {
- var hash = location.hash.slice(1);
- // Remove pre-existing temporary lines
- $$('.temporary.line-highlight').forEach(function (line) {
- line.parentNode.removeChild(line);
- });
- var range = (hash.match(/\.([\d,-]+)$/) || [, ''])[1];
- if (!range || document.getElementById(hash)) {
- return;
- }
- var id = hash.slice(0, hash.lastIndexOf('.'));
- var pre = document.getElementById(id);
- if (!pre) {
- return;
- }
- if (!pre.hasAttribute('data-line')) {
- pre.setAttribute('data-line', '');
- }
- var mutateDom = Prism.plugins.lineHighlight.highlightLines(pre, range, 'temporary ');
- mutateDom();
- if (scrollIntoView) {
- document.querySelector('.temporary.line-highlight').scrollIntoView();
- }
- }
- var fakeTimer = 0; // Hack to limit the number of times applyHash() runs
- Prism.hooks.add('before-sanity-check', function (env) {
- var pre = env.element.parentElement;
- if (!isActiveFor(pre)) {
- return;
- }
- /*
- * Cleanup for other plugins (e.g. autoloader).
- *
- * Sometimes <code> blocks are highlighted multiple times. It is necessary
- * to cleanup any left-over tags, because the whitespace inside of the <div>
- * tags change the content of the <code> tag.
- */
- var num = 0;
- $$('.line-highlight', pre).forEach(function (line) {
- num += line.textContent.length;
- line.parentNode.removeChild(line);
- });
- // Remove extra whitespace
- if (num && /^(?: \n)+$/.test(env.code.slice(-num))) {
- env.code = env.code.slice(0, -num);
- }
- });
- Prism.hooks.add('complete', function completeHook(env) {
- var pre = env.element.parentElement;
- if (!isActiveFor(pre)) {
- return;
- }
- clearTimeout(fakeTimer);
- var hasLineNumbers = Prism.plugins.lineNumbers;
- var isLineNumbersLoaded = env.plugins && env.plugins.lineNumbers;
- if (hasClass(pre, LINE_NUMBERS_CLASS) && hasLineNumbers && !isLineNumbersLoaded) {
- Prism.hooks.add('line-numbers', completeHook);
- } else {
- var mutateDom = Prism.plugins.lineHighlight.highlightLines(pre);
- mutateDom();
- fakeTimer = setTimeout(applyHash, 1);
- }
- });
- window.addEventListener('hashchange', applyHash);
- window.addEventListener('resize', function () {
- var actions = $$('pre')
- .filter(isActiveFor)
- .map(function (pre) {
- return Prism.plugins.lineHighlight.highlightLines(pre);
- });
- actions.forEach(callFunction);
- });
- }());
|