From 9f4c0e7490d866f8de58b534f5ed047ab833e06a Mon Sep 17 00:00:00 2001 From: Manuel Tercero Soria Date: Thu, 23 Sep 2021 13:43:00 +0200 Subject: [PATCH] Highlight Lines: Expose `highlightLines` function as `Prism.plugins.highlightLines` (#3086) --- .../line-highlight/prism-line-highlight.js | 221 +++++++++--------- .../prism-line-highlight.min.js | 2 +- 2 files changed, 113 insertions(+), 110 deletions(-) diff --git a/plugins/line-highlight/prism-line-highlight.js b/plugins/line-highlight/prism-line-highlight.js index d6a248d83a..1e615c2f49 100644 --- a/plugins/line-highlight/prism-line-highlight.js +++ b/plugins/line-highlight/prism-line-highlight.js @@ -112,137 +112,140 @@ var scrollIntoView = true; - /** - * 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} - */ - 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)[]} */ ([]); - + Prism.plugins.lineHighlight = { /** - * The top offset between the content box of the element and the content box of the parent element of - * the line highlight element (either `
` or ``).
+		 * Highlights the lines of the given pre.
 		 *
-		 * This offset might not be zero for some themes where the  element has a top margin. Some plugins
-		 * (or users) might also add element above the  element. Because the line highlight is aligned relative
-		 * to the 
 element, we have to take this into account.
+		 * This function is split into a DOM measuring and mutate phase to improve performance.
+		 * The returned function mutates the DOM when called.
 		 *
-		 * This offset will be 0 if the parent element of the line highlight element is the `` element.
+		 * @param {HTMLElement} pre
+		 * @param {string | null} [lines]
+		 * @param {string} [classes='']
+		 * @returns {() => void}
 		 */
-		var codePreOffset = !codeElement || parentElement == codeElement ? 0 : getContentBoxTopOffset(pre, codeElement);
+		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)[]} */ ([]);
+
+			/**
+			 * The top offset between the content box of the  element and the content box of the parent element of
+			 * the line highlight element (either `
` or ``).
+			 *
+			 * This offset might not be zero for some themes where the  element has a top margin. Some plugins
+			 * (or users) might also add element above the  element. Because the line highlight is aligned relative
+			 * to the 
 element, we have to take this into account.
+			 *
+			 * This offset will be 0 if the parent element of the line highlight element is the `` 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;
+
+				/** @type {HTMLElement} */
+				var line = pre.querySelector('.line-highlight[data-range="' + currentRange + '"]') || document.createElement('div');
 
-		ranges.forEach(function (currentRange) {
-			var range = currentRange.split('-');
+				mutateActions.push(function () {
+					line.setAttribute('aria-hidden', 'true');
+					line.setAttribute('data-range', currentRange);
+					line.className = (classes || '') + ' line-highlight';
+				});
 
-			var start = +range[0];
-			var end = +range[1] || start;
+				// 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);
 
-			/** @type {HTMLElement} */
-			var line = pre.querySelector('.line-highlight[data-range="' + currentRange + '"]') || document.createElement('div');
+					if (startNode) {
+						var top = startNode.offsetTop + codePreOffset + 'px';
+						mutateActions.push(function () {
+							line.style.top = top;
+						});
+					}
 
-			mutateActions.push(function () {
-				line.setAttribute('aria-hidden', 'true');
-				line.setAttribute('data-range', currentRange);
-				line.className = (classes || '') + ' line-highlight';
-			});
+					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 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 (end > start) {
+							line.setAttribute('data-end', String(end));
+						}
 
-				if (startNode) {
-					var top = startNode.offsetTop + codePreOffset + 'px';
-					mutateActions.push(function () {
-						line.style.top = top;
-					});
-				}
+						line.style.top = (start - offset - 1) * lineHeight + codePreOffset + 'px';
 
-				if (endNode) {
-					var height = (endNode.offsetTop - startNode.offsetTop) + endNode.offsetHeight + 'px';
-					mutateActions.push(function () {
-						line.style.height = height;
+						line.textContent = new Array(end - start + 2).join(' \n');
 					});
 				}
-			} 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 () {
-				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);
+				});
 			});
 
-			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.
 
-		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);
+					});
+				}
 
-			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);
+					};
 				});
 			}
 
-			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);
+			};
 		}
+	};
 
-		return function () {
-			mutateActions.forEach(callFunction);
-		};
-	}
 
 	function applyHash() {
 		var hash = location.hash.slice(1);
@@ -269,7 +272,7 @@
 			pre.setAttribute('data-line', '');
 		}
 
-		var mutateDom = highlightLines(pre, range, 'temporary ');
+		var mutateDom = Prism.plugins.lineHighlight.highlightLines(pre, range, 'temporary ');
 		mutateDom();
 
 		if (scrollIntoView) {
@@ -317,7 +320,7 @@
 		if (hasClass(pre, LINE_NUMBERS_CLASS) && hasLineNumbers && !isLineNumbersLoaded) {
 			Prism.hooks.add('line-numbers', completeHook);
 		} else {
-			var mutateDom = highlightLines(pre);
+			var mutateDom = Prism.plugins.lineHighlight.highlightLines(pre);
 			mutateDom();
 			fakeTimer = setTimeout(applyHash, 1);
 		}
@@ -328,7 +331,7 @@
 		var actions = $$('pre')
 			.filter(isActiveFor)
 			.map(function (pre) {
-				return highlightLines(pre);
+				return Prism.plugins.lineHighlight.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 8d9a31e3bf..c1f640b45c 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 Prism&&"undefined"!=typeof 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=" 
 ",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(y(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(b)})}function v(e,t){return Array.prototype.slice.call((t||document).querySelectorAll(e))}function y(e,t){return e.classList.contains(t)}function b(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 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(b)}}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 +!function(){if("undefined"!=typeof Prism&&"undefined"!=typeof document&&document.querySelector){var t,o="line-numbers",s="linkable-line-numbers",l=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=" 
 ",document.body.appendChild(e),t=38===e.offsetHeight,document.body.removeChild(e)}return t},a=!0;Prism.plugins.lineHighlight={highlightLines:function(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,h=(l()?parseInt:parseFloat)(getComputedStyle(u).lineHeight),f=Prism.util.isActive(u,o),i=u.querySelector("code"),p=f?u:i||u,g=[],m=i&&p!=i?function(e,t){var i=getComputedStyle(e),n=getComputedStyle(t);function r(e){return+e.substr(0,e.length-2)}return t.offsetTop+r(n.borderTopWidth)+r(n.paddingTop)-r(i.paddingTop)}(u,i):0;t.forEach(function(e){var t=e.split("-"),i=+t[0],n=+t[1]||i,r=u.querySelector('.line-highlight[data-range="'+e+'"]')||document.createElement("div");if(g.push(function(){r.setAttribute("aria-hidden","true"),r.setAttribute("data-range",e),r.className=(c||"")+" line-highlight"}),f&&Prism.plugins.lineNumbers){var o=Prism.plugins.lineNumbers.getLine(u,i),s=Prism.plugins.lineNumbers.getLine(u,n);if(o){var l=o.offsetTop+m+"px";g.push(function(){r.style.top=l})}if(s){var a=s.offsetTop-o.offsetTop+s.offsetHeight+"px";g.push(function(){r.style.height=a})}}else g.push(function(){r.setAttribute("data-start",String(i)),i span",u).forEach(function(e,t){var i=t+r;e.onclick=function(){var e=n+"."+i;a=!1,location.hash=e,setTimeout(function(){a=!0},1)}})}return function(){g.forEach(b)}}};var u=0;Prism.hooks.add("before-sanity-check",function(e){var t=e.element.parentElement;if(c(t)){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 i=t.element.parentElement;if(c(i)){clearTimeout(u);var n=Prism.plugins.lineNumbers,r=t.plugins&&t.plugins.lineNumbers;if(y(i,o)&&n&&!r)Prism.hooks.add("line-numbers",e);else Prism.plugins.lineHighlight.highlightLines(i)(),u=setTimeout(d,1)}}),window.addEventListener("hashchange",d),window.addEventListener("resize",function(){v("pre").filter(c).map(function(e){return Prism.plugins.lineHighlight.highlightLines(e)}).forEach(b)})}function v(e,t){return Array.prototype.slice.call((t||document).querySelectorAll(e))}function y(e,t){return e.classList.contains(t)}function b(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(){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 i=e.slice(0,e.lastIndexOf(".")),n=document.getElementById(i);if(n)n.hasAttribute("data-line")||n.setAttribute("data-line",""),Prism.plugins.lineHighlight.highlightLines(n,t,"temporary ")(),a&&document.querySelector(".temporary.line-highlight").scrollIntoView()}}}(); \ No newline at end of file