Skip to content

Commit b40f8f4

Browse files
Line highlight: Fixed top offset in combination with Line numbers (#2237)
1 parent 2af3e2c commit b40f8f4

File tree

3 files changed

+53
-11
lines changed

3 files changed

+53
-11
lines changed

plugins/line-highlight/index.html

+6-2
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,12 @@ <h2>Line 43, starting from line 41</h2>
7373

7474
<p><a href="plugins/line-highlight/#play.50-55,60">Linking example</a></p>
7575

76-
<h2>With line numbers</h2>
77-
<pre class="line-numbers" data-line="43" data-start="41" data-src="plugins/line-highlight/prism-line-highlight.js"></pre>
76+
<h2>Compatible with <a href="plugins/line-numbers">Line numbers</a></h2>
77+
<pre class="line-numbers" data-src="plugins/line-numbers/index.html" data-line="1" data-start="-5" style="white-space:pre-wrap;"></pre>
78+
79+
<p>Even with some extra content before the <code>code</code> element.</p>
80+
<pre class="line-numbers" data-line="7"><div style="padding: .25em">Some content</div><hr/><code class="language-markup" id="foo"></code></pre>
81+
<script>document.querySelector('#foo').textContent = document.documentElement.innerHTML;</script>
7882

7983
<h2>With linkable line numbers</h2>
8084
<pre id="foo" class="line-numbers linkable-line-numbers" data-start="20" data-src="plugins/line-highlight/prism-line-highlight.js"></pre>

plugins/line-highlight/prism-line-highlight.js

+46-8
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,39 @@
5757
}
5858
}());
5959

60+
/**
61+
* Returns the top offset of the content box of the given parent and the content box of one of its children.
62+
*
63+
* @param {HTMLElement} parent
64+
* @param {HTMLElement} child
65+
*/
66+
function getContentBoxTopOffset(parent, child) {
67+
var parentStyle = getComputedStyle(parent);
68+
var childStyle = getComputedStyle(child);
69+
70+
/**
71+
* Returns the numeric value of the given pixel value.
72+
*
73+
* @param {string} px
74+
*/
75+
function pxToNumber(px) {
76+
return +px.substr(0, px.length - 2);
77+
}
78+
79+
return child.offsetTop
80+
+ pxToNumber(childStyle.borderTopWidth)
81+
+ pxToNumber(childStyle.paddingTop)
82+
- pxToNumber(parentStyle.paddingTop);
83+
}
84+
6085
/**
6186
* Highlights the lines of the given pre.
6287
*
6388
* This function is split into a DOM measuring and mutate phase to improve performance.
6489
* The returned function mutates the DOM when called.
6590
*
6691
* @param {HTMLElement} pre
67-
* @param {string} [lines]
92+
* @param {string | null} [lines]
6893
* @param {string} [classes='']
6994
* @returns {() => void}
7095
*/
@@ -77,9 +102,22 @@
77102
var parseMethod = isLineHeightRounded() ? parseInt : parseFloat;
78103
var lineHeight = parseMethod(getComputedStyle(pre).lineHeight);
79104
var hasLineNumbers = hasClass(pre, 'line-numbers');
80-
var parentElement = hasLineNumbers ? pre : pre.querySelector('code') || pre;
105+
var codeElement = pre.querySelector('code');
106+
var parentElement = hasLineNumbers ? pre : codeElement || pre;
81107
var mutateActions = /** @type {(() => void)[]} */ ([]);
82108

109+
/**
110+
* The top offset between the content box of the <code> element and the content box of the parent element of
111+
* the line highlight element (either `<pre>` or `<code>`).
112+
*
113+
* This offset might not be zero for some themes where the <code> element has a top margin. Some plugins
114+
* (or users) might also add element above the <code> element. Because the line highlight is aligned relative
115+
* to the <pre> element, we have to take this into account.
116+
*
117+
* This offset will be 0 if the parent element of the line highlight element is the `<code>` element.
118+
*/
119+
var codePreOffset = !codeElement || parentElement == codeElement ? 0 : getContentBoxTopOffset(pre, codeElement);
120+
83121
ranges.forEach(function (currentRange) {
84122
var range = currentRange.split('-');
85123

@@ -101,7 +139,7 @@
101139
var endNode = Prism.plugins.lineNumbers.getLine(pre, end);
102140

103141
if (startNode) {
104-
var top = startNode.offsetTop + 'px';
142+
var top = startNode.offsetTop + codePreOffset + 'px';
105143
mutateActions.push(function () {
106144
line.style.top = top;
107145
});
@@ -115,13 +153,13 @@
115153
}
116154
} else {
117155
mutateActions.push(function () {
118-
line.setAttribute('data-start', start);
156+
line.setAttribute('data-start', String(start));
119157

120158
if (end > start) {
121-
line.setAttribute('data-end', end);
159+
line.setAttribute('data-end', String(end));
122160
}
123161

124-
line.style.top = (start - offset - 1) * lineHeight + 'px';
162+
line.style.top = (start - offset - 1) * lineHeight + codePreOffset + 'px';
125163

126164
line.textContent = new Array(end - start + 2).join(' \n');
127165
});
@@ -222,7 +260,7 @@
222260
var fakeTimer = 0; // Hack to limit the number of times applyHash() runs
223261

224262
Prism.hooks.add('before-sanity-check', function (env) {
225-
var pre = env.element.parentNode;
263+
var pre = env.element.parentElement;
226264
var lines = pre && pre.getAttribute('data-line');
227265

228266
if (!pre || !lines || !/pre/i.test(pre.nodeName)) {
@@ -248,7 +286,7 @@
248286
});
249287

250288
Prism.hooks.add('complete', function completeHook(env) {
251-
var pre = env.element.parentNode;
289+
var pre = env.element.parentElement;
252290
var lines = pre && pre.getAttribute('data-line');
253291

254292
if (!pre || !lines || !/pre/i.test(pre.nodeName)) {

plugins/line-highlight/prism-line-highlight.min.js

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)