diff --git a/plugins/line-highlight/index.html b/plugins/line-highlight/index.html index f609a6fee1..099e505d9d 100644 --- a/plugins/line-highlight/index.html +++ b/plugins/line-highlight/index.html @@ -73,8 +73,12 @@

Line 43, starting from line 41

Linking example

-

With line numbers

-

+	

Compatible with Line numbers

+

+
+	

Even with some extra content before the code element.

+
Some content

+

With linkable line numbers


diff --git a/plugins/line-highlight/prism-line-highlight.js b/plugins/line-highlight/prism-line-highlight.js
index 5f0a5e0ef1..7cd9006ff9 100644
--- a/plugins/line-highlight/prism-line-highlight.js
+++ b/plugins/line-highlight/prism-line-highlight.js
@@ -57,6 +57,31 @@
 		}
 	}());
 
+	/**
+	 * 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);
+	}
+
 	/**
 	 * Highlights the lines of the given pre.
 	 *
@@ -64,7 +89,7 @@
 	 * The returned function mutates the DOM when called.
 	 *
 	 * @param {HTMLElement} pre
-	 * @param {string} [lines]
+	 * @param {string | null} [lines]
 	 * @param {string} [classes='']
 	 * @returns {() => void}
 	 */
@@ -77,9 +102,22 @@
 		var parseMethod = isLineHeightRounded() ? parseInt : parseFloat;
 		var lineHeight = parseMethod(getComputedStyle(pre).lineHeight);
 		var hasLineNumbers = hasClass(pre, 'line-numbers');
-		var parentElement = hasLineNumbers ? pre : pre.querySelector('code') || pre;
+		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('-');
 
@@ -101,7 +139,7 @@
 				var endNode = Prism.plugins.lineNumbers.getLine(pre, end);
 
 				if (startNode) {
-					var top = startNode.offsetTop + 'px';
+					var top = startNode.offsetTop + codePreOffset + 'px';
 					mutateActions.push(function () {
 						line.style.top = top;
 					});
@@ -115,13 +153,13 @@
 				}
 			} else {
 				mutateActions.push(function () {
-					line.setAttribute('data-start', start);
+					line.setAttribute('data-start', String(start));
 
 					if (end > start) {
-						line.setAttribute('data-end', end);
+						line.setAttribute('data-end', String(end));
 					}
 
-					line.style.top = (start - offset - 1) * lineHeight + 'px';
+					line.style.top = (start - offset - 1) * lineHeight + codePreOffset + 'px';
 
 					line.textContent = new Array(end - start + 2).join(' \n');
 				});
@@ -222,7 +260,7 @@
 	var fakeTimer = 0; // Hack to limit the number of times applyHash() runs
 
 	Prism.hooks.add('before-sanity-check', function (env) {
-		var pre = env.element.parentNode;
+		var pre = env.element.parentElement;
 		var lines = pre && pre.getAttribute('data-line');
 
 		if (!pre || !lines || !/pre/i.test(pre.nodeName)) {
@@ -248,7 +286,7 @@
 	});
 
 	Prism.hooks.add('complete', function completeHook(env) {
-		var pre = env.element.parentNode;
+		var pre = env.element.parentElement;
 		var lines = pre && pre.getAttribute('data-line');
 
 		if (!pre || !lines || !/pre/i.test(pre.nodeName)) {
diff --git a/plugins/line-highlight/prism-line-highlight.min.js b/plugins/line-highlight/prism-line-highlight.min.js
index adf8d37755..d2b049ec86 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=" 
 ",document.body.appendChild(e),t=38===e.offsetHeight,document.body.removeChild(e)}return t},l=!0,a=0;Prism.hooks.add("before-sanity-check",function(e){var t=e.element.parentNode,n=t&&t.getAttribute("data-line");if(t&&n&&/pre/i.test(t.nodeName)){var i=0;g(".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.parentNode,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(b(n,"line-numbers")&&r&&!o)Prism.hooks.add("line-numbers",e);else u(n,i)(),a=setTimeout(c,1)}}),window.addEventListener("hashchange",c),window.addEventListener("resize",function(){g("pre[data-line]").map(function(e){return u(e)}).forEach(v)})}function g(e,t){return Array.prototype.slice.call((t||document).querySelectorAll(e))}function b(e,t){return t=" "+t+" ",-1<(" "+e.className+" ").replace(/[\n\t]/g," ").indexOf(t)}function v(e){e()}function u(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=(s()?parseInt:parseFloat)(getComputedStyle(u).lineHeight),m=b(u,"line-numbers"),p=m?u:u.querySelector("code")||u,h=[];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=(c||"")+" line-highlight"}),m&&Prism.plugins.lineNumbers){var o=Prism.plugins.lineNumbers.getLine(u,n),a=Prism.plugins.lineNumbers.getLine(u,i);if(o){var s=o.offsetTop+"px";h.push(function(){r.style.top=s})}if(a){var l=a.offsetTop-o.offsetTop+a.offsetHeight+"px";h.push(function(){r.style.height=l})}}else h.push(function(){r.setAttribute("data-start",n),n span",u).forEach(function(e,t){var n=t+a;e.onclick=function(){var e=i+"."+n;l=!1,location.hash=e,setTimeout(function(){l=!0},1)}})}}return function(){h.forEach(v)}}function c(){var e=location.hash.slice(1);g(".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",""),u(i,t,"temporary ")(),l&&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,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=" 
 ",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 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