Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose highlightLines as Prism.plugins.highlightLines in that plugin #3086

Merged
merged 2 commits into from Sep 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
221 changes: 112 additions & 109 deletions plugins/line-highlight/prism-line-highlight.js
Expand Up @@ -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 <code> element and the content box of the parent element of
* the line highlight element (either `<pre>` or `<code>`).
* Highlights the lines of the given pre.
*
* 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 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 `<code>` 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 <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;

/** @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);
Expand All @@ -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) {
Expand Down Expand Up @@ -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);
}
Expand All @@ -328,7 +331,7 @@
var actions = $$('pre')
.filter(isActiveFor)
.map(function (pre) {
return highlightLines(pre);
return Prism.plugins.lineHighlight.highlightLines(pre);
});
actions.forEach(callFunction);
});
Expand Down
2 changes: 1 addition & 1 deletion plugins/line-highlight/prism-line-highlight.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.