347 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			347 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
(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);
 | 
						|
	});
 | 
						|
 | 
						|
}());
 |