[d24f17c] | 1 | (function () {
|
---|
| 2 |
|
---|
| 3 | if (typeof Prism === 'undefined' || typeof document === 'undefined' || !document.querySelector) {
|
---|
| 4 | return;
|
---|
| 5 | }
|
---|
| 6 |
|
---|
| 7 | var LINE_NUMBERS_CLASS = 'line-numbers';
|
---|
| 8 | var LINKABLE_LINE_NUMBERS_CLASS = 'linkable-line-numbers';
|
---|
| 9 | var NEW_LINE_EXP = /\n(?!$)/g;
|
---|
| 10 |
|
---|
| 11 | /**
|
---|
| 12 | * @param {string} selector
|
---|
| 13 | * @param {ParentNode} [container]
|
---|
| 14 | * @returns {HTMLElement[]}
|
---|
| 15 | */
|
---|
| 16 | function $$(selector, container) {
|
---|
| 17 | return Array.prototype.slice.call((container || document).querySelectorAll(selector));
|
---|
| 18 | }
|
---|
| 19 |
|
---|
| 20 | /**
|
---|
| 21 | * Returns whether the given element has the given class.
|
---|
| 22 | *
|
---|
| 23 | * @param {Element} element
|
---|
| 24 | * @param {string} className
|
---|
| 25 | * @returns {boolean}
|
---|
| 26 | */
|
---|
| 27 | function hasClass(element, className) {
|
---|
| 28 | return element.classList.contains(className);
|
---|
| 29 | }
|
---|
| 30 |
|
---|
| 31 | /**
|
---|
| 32 | * Calls the given function.
|
---|
| 33 | *
|
---|
| 34 | * @param {() => any} func
|
---|
| 35 | * @returns {void}
|
---|
| 36 | */
|
---|
| 37 | function callFunction(func) {
|
---|
| 38 | func();
|
---|
| 39 | }
|
---|
| 40 |
|
---|
| 41 | // Some browsers round the line-height, others don't.
|
---|
| 42 | // We need to test for it to position the elements properly.
|
---|
| 43 | var isLineHeightRounded = (function () {
|
---|
| 44 | var res;
|
---|
| 45 | return function () {
|
---|
| 46 | if (typeof res === 'undefined') {
|
---|
| 47 | var d = document.createElement('div');
|
---|
| 48 | d.style.fontSize = '13px';
|
---|
| 49 | d.style.lineHeight = '1.5';
|
---|
| 50 | d.style.padding = '0';
|
---|
| 51 | d.style.border = '0';
|
---|
| 52 | d.innerHTML = ' <br /> ';
|
---|
| 53 | document.body.appendChild(d);
|
---|
| 54 | // Browsers that round the line-height should have offsetHeight === 38
|
---|
| 55 | // The others should have 39.
|
---|
| 56 | res = d.offsetHeight === 38;
|
---|
| 57 | document.body.removeChild(d);
|
---|
| 58 | }
|
---|
| 59 | return res;
|
---|
| 60 | };
|
---|
| 61 | }());
|
---|
| 62 |
|
---|
| 63 | /**
|
---|
| 64 | * Returns the top offset of the content box of the given parent and the content box of one of its children.
|
---|
| 65 | *
|
---|
| 66 | * @param {HTMLElement} parent
|
---|
| 67 | * @param {HTMLElement} child
|
---|
| 68 | */
|
---|
| 69 | function getContentBoxTopOffset(parent, child) {
|
---|
| 70 | var parentStyle = getComputedStyle(parent);
|
---|
| 71 | var childStyle = getComputedStyle(child);
|
---|
| 72 |
|
---|
| 73 | /**
|
---|
| 74 | * Returns the numeric value of the given pixel value.
|
---|
| 75 | *
|
---|
| 76 | * @param {string} px
|
---|
| 77 | */
|
---|
| 78 | function pxToNumber(px) {
|
---|
| 79 | return +px.substr(0, px.length - 2);
|
---|
| 80 | }
|
---|
| 81 |
|
---|
| 82 | return child.offsetTop
|
---|
| 83 | + pxToNumber(childStyle.borderTopWidth)
|
---|
| 84 | + pxToNumber(childStyle.paddingTop)
|
---|
| 85 | - pxToNumber(parentStyle.paddingTop);
|
---|
| 86 | }
|
---|
| 87 |
|
---|
| 88 | /**
|
---|
| 89 | * Returns whether the Line Highlight plugin is active for the given element.
|
---|
| 90 | *
|
---|
| 91 | * If this function returns `false`, do not call `highlightLines` for the given element.
|
---|
| 92 | *
|
---|
| 93 | * @param {HTMLElement | null | undefined} pre
|
---|
| 94 | * @returns {boolean}
|
---|
| 95 | */
|
---|
| 96 | function isActiveFor(pre) {
|
---|
| 97 | if (!pre || !/pre/i.test(pre.nodeName)) {
|
---|
| 98 | return false;
|
---|
| 99 | }
|
---|
| 100 |
|
---|
| 101 | if (pre.hasAttribute('data-line')) {
|
---|
| 102 | return true;
|
---|
| 103 | }
|
---|
| 104 |
|
---|
| 105 | if (pre.id && Prism.util.isActive(pre, LINKABLE_LINE_NUMBERS_CLASS)) {
|
---|
| 106 | // Technically, the line numbers plugin is also necessary but this plugin doesn't control the classes of
|
---|
| 107 | // the line numbers plugin, so we can't assume that they are present.
|
---|
| 108 | return true;
|
---|
| 109 | }
|
---|
| 110 |
|
---|
| 111 | return false;
|
---|
| 112 | }
|
---|
| 113 |
|
---|
| 114 | var scrollIntoView = true;
|
---|
| 115 |
|
---|
| 116 | Prism.plugins.lineHighlight = {
|
---|
| 117 | /**
|
---|
| 118 | * Highlights the lines of the given pre.
|
---|
| 119 | *
|
---|
| 120 | * This function is split into a DOM measuring and mutate phase to improve performance.
|
---|
| 121 | * The returned function mutates the DOM when called.
|
---|
| 122 | *
|
---|
| 123 | * @param {HTMLElement} pre
|
---|
| 124 | * @param {string | null} [lines]
|
---|
| 125 | * @param {string} [classes='']
|
---|
| 126 | * @returns {() => void}
|
---|
| 127 | */
|
---|
| 128 | highlightLines: function highlightLines(pre, lines, classes) {
|
---|
| 129 | lines = typeof lines === 'string' ? lines : (pre.getAttribute('data-line') || '');
|
---|
| 130 |
|
---|
| 131 | var ranges = lines.replace(/\s+/g, '').split(',').filter(Boolean);
|
---|
| 132 | var offset = +pre.getAttribute('data-line-offset') || 0;
|
---|
| 133 |
|
---|
| 134 | var parseMethod = isLineHeightRounded() ? parseInt : parseFloat;
|
---|
| 135 | var lineHeight = parseMethod(getComputedStyle(pre).lineHeight);
|
---|
| 136 | var hasLineNumbers = Prism.util.isActive(pre, LINE_NUMBERS_CLASS);
|
---|
| 137 | var codeElement = pre.querySelector('code');
|
---|
| 138 | var parentElement = hasLineNumbers ? pre : codeElement || pre;
|
---|
| 139 | var mutateActions = /** @type {(() => void)[]} */ ([]);
|
---|
| 140 | var lineBreakMatch = codeElement.textContent.match(NEW_LINE_EXP);
|
---|
| 141 | var numberOfLines = lineBreakMatch ? lineBreakMatch.length + 1 : 1;
|
---|
| 142 | /**
|
---|
| 143 | * The top offset between the content box of the <code> element and the content box of the parent element of
|
---|
| 144 | * the line highlight element (either `<pre>` or `<code>`).
|
---|
| 145 | *
|
---|
| 146 | * This offset might not be zero for some themes where the <code> element has a top margin. Some plugins
|
---|
| 147 | * (or users) might also add element above the <code> element. Because the line highlight is aligned relative
|
---|
| 148 | * to the <pre> element, we have to take this into account.
|
---|
| 149 | *
|
---|
| 150 | * This offset will be 0 if the parent element of the line highlight element is the `<code>` element.
|
---|
| 151 | */
|
---|
| 152 | var codePreOffset = !codeElement || parentElement == codeElement ? 0 : getContentBoxTopOffset(pre, codeElement);
|
---|
| 153 |
|
---|
| 154 | ranges.forEach(function (currentRange) {
|
---|
| 155 | var range = currentRange.split('-');
|
---|
| 156 |
|
---|
| 157 | var start = +range[0];
|
---|
| 158 | var end = +range[1] || start;
|
---|
| 159 | end = Math.min(numberOfLines + offset, end);
|
---|
| 160 |
|
---|
| 161 | if (end < start) {
|
---|
| 162 | return;
|
---|
| 163 | }
|
---|
| 164 |
|
---|
| 165 | /** @type {HTMLElement} */
|
---|
| 166 | var line = pre.querySelector('.line-highlight[data-range="' + currentRange + '"]') || document.createElement('div');
|
---|
| 167 |
|
---|
| 168 | mutateActions.push(function () {
|
---|
| 169 | line.setAttribute('aria-hidden', 'true');
|
---|
| 170 | line.setAttribute('data-range', currentRange);
|
---|
| 171 | line.className = (classes || '') + ' line-highlight';
|
---|
| 172 | });
|
---|
| 173 |
|
---|
| 174 | // if the line-numbers plugin is enabled, then there is no reason for this plugin to display the line numbers
|
---|
| 175 | if (hasLineNumbers && Prism.plugins.lineNumbers) {
|
---|
| 176 | var startNode = Prism.plugins.lineNumbers.getLine(pre, start);
|
---|
| 177 | var endNode = Prism.plugins.lineNumbers.getLine(pre, end);
|
---|
| 178 |
|
---|
| 179 | if (startNode) {
|
---|
| 180 | var top = startNode.offsetTop + codePreOffset + 'px';
|
---|
| 181 | mutateActions.push(function () {
|
---|
| 182 | line.style.top = top;
|
---|
| 183 | });
|
---|
| 184 | }
|
---|
| 185 |
|
---|
| 186 | if (endNode) {
|
---|
| 187 | var height = (endNode.offsetTop - startNode.offsetTop) + endNode.offsetHeight + 'px';
|
---|
| 188 | mutateActions.push(function () {
|
---|
| 189 | line.style.height = height;
|
---|
| 190 | });
|
---|
| 191 | }
|
---|
| 192 | } else {
|
---|
| 193 | mutateActions.push(function () {
|
---|
| 194 | line.setAttribute('data-start', String(start));
|
---|
| 195 |
|
---|
| 196 | if (end > start) {
|
---|
| 197 | line.setAttribute('data-end', String(end));
|
---|
| 198 | }
|
---|
| 199 |
|
---|
| 200 | line.style.top = (start - offset - 1) * lineHeight + codePreOffset + 'px';
|
---|
| 201 |
|
---|
| 202 | line.textContent = new Array(end - start + 2).join(' \n');
|
---|
| 203 | });
|
---|
| 204 | }
|
---|
| 205 |
|
---|
| 206 | mutateActions.push(function () {
|
---|
| 207 | line.style.width = pre.scrollWidth + 'px';
|
---|
| 208 | });
|
---|
| 209 |
|
---|
| 210 | mutateActions.push(function () {
|
---|
| 211 | // allow this to play nicely with the line-numbers plugin
|
---|
| 212 | // need to attack to pre as when line-numbers is enabled, the code tag is relatively which screws up the positioning
|
---|
| 213 | parentElement.appendChild(line);
|
---|
| 214 | });
|
---|
| 215 | });
|
---|
| 216 |
|
---|
| 217 | var id = pre.id;
|
---|
| 218 | if (hasLineNumbers && Prism.util.isActive(pre, LINKABLE_LINE_NUMBERS_CLASS) && id) {
|
---|
| 219 | // This implements linkable line numbers. Linkable line numbers use Line Highlight to create a link to a
|
---|
| 220 | // specific line. For this to work, the pre element has to:
|
---|
| 221 | // 1) have line numbers,
|
---|
| 222 | // 2) have the `linkable-line-numbers` class or an ascendant that has that class, and
|
---|
| 223 | // 3) have an id.
|
---|
| 224 |
|
---|
| 225 | if (!hasClass(pre, LINKABLE_LINE_NUMBERS_CLASS)) {
|
---|
| 226 | // add class to pre
|
---|
| 227 | mutateActions.push(function () {
|
---|
| 228 | pre.classList.add(LINKABLE_LINE_NUMBERS_CLASS);
|
---|
| 229 | });
|
---|
| 230 | }
|
---|
| 231 |
|
---|
| 232 | var start = parseInt(pre.getAttribute('data-start') || '1');
|
---|
| 233 |
|
---|
| 234 | // iterate all line number spans
|
---|
| 235 | $$('.line-numbers-rows > span', pre).forEach(function (lineSpan, i) {
|
---|
| 236 | var lineNumber = i + start;
|
---|
| 237 | lineSpan.onclick = function () {
|
---|
| 238 | var hash = id + '.' + lineNumber;
|
---|
| 239 |
|
---|
| 240 | // this will prevent scrolling since the span is obviously in view
|
---|
| 241 | scrollIntoView = false;
|
---|
| 242 | location.hash = hash;
|
---|
| 243 | setTimeout(function () {
|
---|
| 244 | scrollIntoView = true;
|
---|
| 245 | }, 1);
|
---|
| 246 | };
|
---|
| 247 | });
|
---|
| 248 | }
|
---|
| 249 |
|
---|
| 250 | return function () {
|
---|
| 251 | mutateActions.forEach(callFunction);
|
---|
| 252 | };
|
---|
| 253 | }
|
---|
| 254 | };
|
---|
| 255 |
|
---|
| 256 |
|
---|
| 257 | function applyHash() {
|
---|
| 258 | var hash = location.hash.slice(1);
|
---|
| 259 |
|
---|
| 260 | // Remove pre-existing temporary lines
|
---|
| 261 | $$('.temporary.line-highlight').forEach(function (line) {
|
---|
| 262 | line.parentNode.removeChild(line);
|
---|
| 263 | });
|
---|
| 264 |
|
---|
| 265 | var range = (hash.match(/\.([\d,-]+)$/) || [, ''])[1];
|
---|
| 266 |
|
---|
| 267 | if (!range || document.getElementById(hash)) {
|
---|
| 268 | return;
|
---|
| 269 | }
|
---|
| 270 |
|
---|
| 271 | var id = hash.slice(0, hash.lastIndexOf('.'));
|
---|
| 272 | var pre = document.getElementById(id);
|
---|
| 273 |
|
---|
| 274 | if (!pre) {
|
---|
| 275 | return;
|
---|
| 276 | }
|
---|
| 277 |
|
---|
| 278 | if (!pre.hasAttribute('data-line')) {
|
---|
| 279 | pre.setAttribute('data-line', '');
|
---|
| 280 | }
|
---|
| 281 |
|
---|
| 282 | var mutateDom = Prism.plugins.lineHighlight.highlightLines(pre, range, 'temporary ');
|
---|
| 283 | mutateDom();
|
---|
| 284 |
|
---|
| 285 | if (scrollIntoView) {
|
---|
| 286 | document.querySelector('.temporary.line-highlight').scrollIntoView();
|
---|
| 287 | }
|
---|
| 288 | }
|
---|
| 289 |
|
---|
| 290 | var fakeTimer = 0; // Hack to limit the number of times applyHash() runs
|
---|
| 291 |
|
---|
| 292 | Prism.hooks.add('before-sanity-check', function (env) {
|
---|
| 293 | var pre = env.element.parentElement;
|
---|
| 294 | if (!isActiveFor(pre)) {
|
---|
| 295 | return;
|
---|
| 296 | }
|
---|
| 297 |
|
---|
| 298 | /*
|
---|
| 299 | * Cleanup for other plugins (e.g. autoloader).
|
---|
| 300 | *
|
---|
| 301 | * Sometimes <code> blocks are highlighted multiple times. It is necessary
|
---|
| 302 | * to cleanup any left-over tags, because the whitespace inside of the <div>
|
---|
| 303 | * tags change the content of the <code> tag.
|
---|
| 304 | */
|
---|
| 305 | var num = 0;
|
---|
| 306 | $$('.line-highlight', pre).forEach(function (line) {
|
---|
| 307 | num += line.textContent.length;
|
---|
| 308 | line.parentNode.removeChild(line);
|
---|
| 309 | });
|
---|
| 310 | // Remove extra whitespace
|
---|
| 311 | if (num && /^(?: \n)+$/.test(env.code.slice(-num))) {
|
---|
| 312 | env.code = env.code.slice(0, -num);
|
---|
| 313 | }
|
---|
| 314 | });
|
---|
| 315 |
|
---|
| 316 | Prism.hooks.add('complete', function completeHook(env) {
|
---|
| 317 | var pre = env.element.parentElement;
|
---|
| 318 | if (!isActiveFor(pre)) {
|
---|
| 319 | return;
|
---|
| 320 | }
|
---|
| 321 |
|
---|
| 322 | clearTimeout(fakeTimer);
|
---|
| 323 |
|
---|
| 324 | var hasLineNumbers = Prism.plugins.lineNumbers;
|
---|
| 325 | var isLineNumbersLoaded = env.plugins && env.plugins.lineNumbers;
|
---|
| 326 |
|
---|
| 327 | if (hasClass(pre, LINE_NUMBERS_CLASS) && hasLineNumbers && !isLineNumbersLoaded) {
|
---|
| 328 | Prism.hooks.add('line-numbers', completeHook);
|
---|
| 329 | } else {
|
---|
| 330 | var mutateDom = Prism.plugins.lineHighlight.highlightLines(pre);
|
---|
| 331 | mutateDom();
|
---|
| 332 | fakeTimer = setTimeout(applyHash, 1);
|
---|
| 333 | }
|
---|
| 334 | });
|
---|
| 335 |
|
---|
| 336 | window.addEventListener('hashchange', applyHash);
|
---|
| 337 | window.addEventListener('resize', function () {
|
---|
| 338 | var actions = $$('pre')
|
---|
| 339 | .filter(isActiveFor)
|
---|
| 340 | .map(function (pre) {
|
---|
| 341 | return Prism.plugins.lineHighlight.highlightLines(pre);
|
---|
| 342 | });
|
---|
| 343 | actions.forEach(callFunction);
|
---|
| 344 | });
|
---|
| 345 |
|
---|
| 346 | }());
|
---|