Commit ccc73ab75fb43f0e7d4ed6b6d3145e00e843b0ba

Michael Schmidt 2021-02-07T14:52:25

Line Highlight: Fixed linkable line numbers not being initialized (#2732)

diff --git a/plugins/line-highlight/index.html b/plugins/line-highlight/index.html
index 099e505..8ade641 100644
--- a/plugins/line-highlight/index.html
+++ b/plugins/line-highlight/index.html
@@ -81,7 +81,7 @@
 	<script>document.querySelector('#foo').textContent = document.documentElement.innerHTML;</script>
 
 	<h2>With linkable line numbers</h2>
-	<pre id="foo" class="line-numbers linkable-line-numbers" data-start="20" data-src="plugins/line-highlight/prism-line-highlight.js"></pre>
+	<pre id="linkable" class="line-numbers linkable-line-numbers" data-start="20" data-src="plugins/line-highlight/prism-line-highlight.js"></pre>
 
 </section>
 
diff --git a/plugins/line-highlight/prism-line-highlight.js b/plugins/line-highlight/prism-line-highlight.js
index 7cd9006..e850159 100644
--- a/plugins/line-highlight/prism-line-highlight.js
+++ b/plugins/line-highlight/prism-line-highlight.js
@@ -4,6 +4,9 @@
 		return;
 	}
 
+	var LINE_NUMBERS_CLASS = 'line-numbers';
+	var LINKABLE_LINE_NUMBERS_CLASS = 'linkable-line-numbers';
+
 	/**
 	 * @param {string} selector
 	 * @param {ParentNode} [container]
@@ -21,8 +24,7 @@
 	 * @returns {boolean}
 	 */
 	function hasClass(element, className) {
-		className = " " + className + " ";
-		return (" " + element.className + " ").replace(/[\n\t]/g, " ").indexOf(className) > -1
+		return element.classList.contains(className);
 	}
 
 	/**
@@ -83,6 +85,32 @@
 	}
 
 	/**
+	 * 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;
+	}
+
+	/**
 	 * Highlights the lines of the given pre.
 	 *
 	 * This function is split into a DOM measuring and mutate phase to improve performance.
@@ -94,14 +122,14 @@
 	 * @returns {() => void}
 	 */
 	function highlightLines(pre, lines, classes) {
-		lines = typeof lines === 'string' ? lines : pre.getAttribute('data-line');
+		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 = hasClass(pre, 'line-numbers');
+		var hasLineNumbers = Prism.util.isActive(pre, LINE_NUMBERS_CLASS);
 		var codeElement = pre.querySelector('code');
 		var parentElement = hasLineNumbers ? pre : codeElement || pre;
 		var mutateActions = /** @type {(() => void)[]} */ ([]);
@@ -173,49 +201,36 @@
 		});
 
 		var id = pre.id;
-		if (hasLineNumbers && 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.
 
-			var linkableLineNumbersClass = 'linkable-line-numbers';
-			var linkableLineNumbers = false;
-			var node = pre;
-			while (node) {
-				if (hasClass(node, linkableLineNumbersClass)) {
-					linkableLineNumbers = true;
-					break;
-				}
-				node = node.parentElement;
-			}
-
-			if (linkableLineNumbers) {
-				if (!hasClass(pre, linkableLineNumbersClass)) {
-					// add class to pre
-					mutateActions.push(function () {
-						pre.className = (pre.className + ' ' + linkableLineNumbersClass).trim();
-					});
-				}
-
-				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);
-					};
+			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 () {
@@ -261,9 +276,7 @@
 
 	Prism.hooks.add('before-sanity-check', function (env) {
 		var pre = env.element.parentElement;
-		var lines = pre && pre.getAttribute('data-line');
-
-		if (!pre || !lines || !/pre/i.test(pre.nodeName)) {
+		if (!isActiveFor(pre)) {
 			return;
 		}
 
@@ -287,9 +300,7 @@
 
 	Prism.hooks.add('complete', function completeHook(env) {
 		var pre = env.element.parentElement;
-		var lines = pre && pre.getAttribute('data-line');
-
-		if (!pre || !lines || !/pre/i.test(pre.nodeName)) {
+		if (!isActiveFor(pre)) {
 			return;
 		}
 
@@ -298,10 +309,10 @@
 		var hasLineNumbers = Prism.plugins.lineNumbers;
 		var isLineNumbersLoaded = env.plugins && env.plugins.lineNumbers;
 
-		if (hasClass(pre, 'line-numbers') && hasLineNumbers && !isLineNumbersLoaded) {
+		if (hasClass(pre, LINE_NUMBERS_CLASS) && hasLineNumbers && !isLineNumbersLoaded) {
 			Prism.hooks.add('line-numbers', completeHook);
 		} else {
-			var mutateDom = highlightLines(pre, lines);
+			var mutateDom = highlightLines(pre);
 			mutateDom();
 			fakeTimer = setTimeout(applyHash, 1);
 		}
@@ -309,9 +320,11 @@
 
 	window.addEventListener('hashchange', applyHash);
 	window.addEventListener('resize', function () {
-		var actions = $$('pre[data-line]').map(function (pre) {
-			return highlightLines(pre);
-		});
+		var actions = $$('pre')
+			.filter(isActiveFor)
+			.map(function (pre) {
+				return highlightLines(pre);
+			});
 		actions.forEach(callFunction);
 	});
 
diff --git a/plugins/line-highlight/prism-line-highlight.min.js b/plugins/line-highlight/prism-line-highlight.min.js
index d2b049e..d3707e2 100644
--- a/plugins/line-highlight/prism-line-highlight.min.js
+++ b/plugins/line-highlight/prism-line-highlight.min.js
@@ -1 +1 @@
-!function(){if("undefined"!=typeof self&&self.Prism&&self.document&&document.querySelector){var t,s=function(){if(void 0===t){var e=document.createElement("div");e.style.fontSize="13px",e.style.lineHeight="1.5",e.style.padding="0",e.style.border="0",e.innerHTML="&nbsp;<br />&nbsp;",document.body.appendChild(e),t=38===e.offsetHeight,document.body.removeChild(e)}return t},b=!0,a=0;Prism.hooks.add("before-sanity-check",function(e){var t=e.element.parentElement,n=t&&t.getAttribute("data-line");if(t&&n&&/pre/i.test(t.nodeName)){var i=0;v(".line-highlight",t).forEach(function(e){i+=e.textContent.length,e.parentNode.removeChild(e)}),i&&/^( \n)+$/.test(e.code.slice(-i))&&(e.code=e.code.slice(0,-i))}}),Prism.hooks.add("complete",function e(t){var n=t.element.parentElement,i=n&&n.getAttribute("data-line");if(n&&i&&/pre/i.test(n.nodeName)){clearTimeout(a);var r=Prism.plugins.lineNumbers,o=t.plugins&&t.plugins.lineNumbers;if(y(n,"line-numbers")&&r&&!o)Prism.hooks.add("line-numbers",e);else l(n,i)(),a=setTimeout(u,1)}}),window.addEventListener("hashchange",u),window.addEventListener("resize",function(){v("pre[data-line]").map(function(e){return l(e)}).forEach(E)})}function v(e,t){return Array.prototype.slice.call((t||document).querySelectorAll(e))}function y(e,t){return t=" "+t+" ",-1<(" "+e.className+" ").replace(/[\n\t]/g," ").indexOf(t)}function E(e){e()}function l(u,e,d){var t=(e="string"==typeof e?e:u.getAttribute("data-line")).replace(/\s+/g,"").split(",").filter(Boolean),c=+u.getAttribute("data-line-offset")||0,f=(s()?parseInt:parseFloat)(getComputedStyle(u).lineHeight),p=y(u,"line-numbers"),n=u.querySelector("code"),m=p?u:n||u,h=[],g=n&&m!=n?function(e,t){var n=getComputedStyle(e),i=getComputedStyle(t);function r(e){return+e.substr(0,e.length-2)}return t.offsetTop+r(i.borderTopWidth)+r(i.paddingTop)-r(n.paddingTop)}(u,n):0;t.forEach(function(e){var t=e.split("-"),n=+t[0],i=+t[1]||n,r=u.querySelector('.line-highlight[data-range="'+e+'"]')||document.createElement("div");if(h.push(function(){r.setAttribute("aria-hidden","true"),r.setAttribute("data-range",e),r.className=(d||"")+" line-highlight"}),p&&Prism.plugins.lineNumbers){var o=Prism.plugins.lineNumbers.getLine(u,n),a=Prism.plugins.lineNumbers.getLine(u,i);if(o){var l=o.offsetTop+g+"px";h.push(function(){r.style.top=l})}if(a){var s=a.offsetTop-o.offsetTop+a.offsetHeight+"px";h.push(function(){r.style.height=s})}}else h.push(function(){r.setAttribute("data-start",String(n)),n<i&&r.setAttribute("data-end",String(i)),r.style.top=(n-c-1)*f+g+"px",r.textContent=new Array(i-n+2).join(" \n")});h.push(function(){m.appendChild(r)})});var i=u.id;if(p&&i){for(var r="linkable-line-numbers",o=!1,a=u;a;){if(y(a,r)){o=!0;break}a=a.parentElement}if(o){y(u,r)||h.push(function(){u.className=(u.className+" "+r).trim()});var l=parseInt(u.getAttribute("data-start")||"1");v(".line-numbers-rows > span",u).forEach(function(e,t){var n=t+l;e.onclick=function(){var e=i+"."+n;b=!1,location.hash=e,setTimeout(function(){b=!0},1)}})}}return function(){h.forEach(E)}}function u(){var e=location.hash.slice(1);v(".temporary.line-highlight").forEach(function(e){e.parentNode.removeChild(e)});var t=(e.match(/\.([\d,-]+)$/)||[,""])[1];if(t&&!document.getElementById(e)){var n=e.slice(0,e.lastIndexOf(".")),i=document.getElementById(n);if(i)i.hasAttribute("data-line")||i.setAttribute("data-line",""),l(i,t,"temporary ")(),b&&document.querySelector(".temporary.line-highlight").scrollIntoView()}}}();
\ No newline at end of file
+!function(){if("undefined"!=typeof self&&self.Prism&&self.document&&document.querySelector){var t,o="line-numbers",s="linkable-line-numbers",a=function(){if(void 0===t){var e=document.createElement("div");e.style.fontSize="13px",e.style.lineHeight="1.5",e.style.padding="0",e.style.border="0",e.innerHTML="&nbsp;<br />&nbsp;",document.body.appendChild(e),t=38===e.offsetHeight,document.body.removeChild(e)}return t},l=!0,u=0;Prism.hooks.add("before-sanity-check",function(e){var t=e.element.parentElement;if(c(t)){var n=0;v(".line-highlight",t).forEach(function(e){n+=e.textContent.length,e.parentNode.removeChild(e)}),n&&/^( \n)+$/.test(e.code.slice(-n))&&(e.code=e.code.slice(0,-n))}}),Prism.hooks.add("complete",function e(t){var n=t.element.parentElement;if(c(n)){clearTimeout(u);var i=Prism.plugins.lineNumbers,r=t.plugins&&t.plugins.lineNumbers;if(b(n,o)&&i&&!r)Prism.hooks.add("line-numbers",e);else d(n)(),u=setTimeout(f,1)}}),window.addEventListener("hashchange",f),window.addEventListener("resize",function(){v("pre").filter(c).map(function(e){return d(e)}).forEach(y)})}function v(e,t){return Array.prototype.slice.call((t||document).querySelectorAll(e))}function b(e,t){return e.classList.contains(t)}function y(e){e()}function c(e){return!(!e||!/pre/i.test(e.nodeName))&&(!!e.hasAttribute("data-line")||!(!e.id||!Prism.util.isActive(e,s)))}function d(u,e,c){var t=(e="string"==typeof e?e:u.getAttribute("data-line")||"").replace(/\s+/g,"").split(",").filter(Boolean),d=+u.getAttribute("data-line-offset")||0,f=(a()?parseInt:parseFloat)(getComputedStyle(u).lineHeight),p=Prism.util.isActive(u,o),n=u.querySelector("code"),h=p?u:n||u,m=[],g=n&&h!=n?function(e,t){var n=getComputedStyle(e),i=getComputedStyle(t);function r(e){return+e.substr(0,e.length-2)}return t.offsetTop+r(i.borderTopWidth)+r(i.paddingTop)-r(n.paddingTop)}(u,n):0;t.forEach(function(e){var t=e.split("-"),n=+t[0],i=+t[1]||n,r=u.querySelector('.line-highlight[data-range="'+e+'"]')||document.createElement("div");if(m.push(function(){r.setAttribute("aria-hidden","true"),r.setAttribute("data-range",e),r.className=(c||"")+" line-highlight"}),p&&Prism.plugins.lineNumbers){var o=Prism.plugins.lineNumbers.getLine(u,n),s=Prism.plugins.lineNumbers.getLine(u,i);if(o){var a=o.offsetTop+g+"px";m.push(function(){r.style.top=a})}if(s){var l=s.offsetTop-o.offsetTop+s.offsetHeight+"px";m.push(function(){r.style.height=l})}}else m.push(function(){r.setAttribute("data-start",String(n)),n<i&&r.setAttribute("data-end",String(i)),r.style.top=(n-d-1)*f+g+"px",r.textContent=new Array(i-n+2).join(" \n")});m.push(function(){h.appendChild(r)})});var i=u.id;if(p&&Prism.util.isActive(u,s)&&i){b(u,s)||m.push(function(){u.classList.add(s)});var r=parseInt(u.getAttribute("data-start")||"1");v(".line-numbers-rows > span",u).forEach(function(e,t){var n=t+r;e.onclick=function(){var e=i+"."+n;l=!1,location.hash=e,setTimeout(function(){l=!0},1)}})}return function(){m.forEach(y)}}function f(){var e=location.hash.slice(1);v(".temporary.line-highlight").forEach(function(e){e.parentNode.removeChild(e)});var t=(e.match(/\.([\d,-]+)$/)||[,""])[1];if(t&&!document.getElementById(e)){var n=e.slice(0,e.lastIndexOf(".")),i=document.getElementById(n);if(i)i.hasAttribute("data-line")||i.setAttribute("data-line",""),d(i,t,"temporary ")(),l&&document.querySelector(".temporary.line-highlight").scrollIntoView()}}}();
\ No newline at end of file