Skip to content

Instantly share code, notes, and snippets.

@justinbmeyer
Created April 23, 2019 18:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save justinbmeyer/1230ae64918372ea9bf124f6dce74326 to your computer and use it in GitHub Desktop.
Save justinbmeyer/1230ae64918372ea9bf124f6dce74326 to your computer and use it in GitHub Desktop.
A faster version of prism line highlight.
(function(){
if (typeof self === 'undefined' || !self.Prism || !self.document || !document.querySelector) {
return;
}
function $$(expr, con) {
return Array.prototype.slice.call((con || document).querySelectorAll(expr));
}
function hasClass(element, className) {
className = " " + className + " ";
return (" " + element.className + " ").replace(/[\n\t]/g, " ").indexOf(className) > -1
}
// Some browsers round the line-height, others don't.
// We need to test for it to position the elements properly.
var isLineHeightRounded = (function() {
var res;
return function() {
if(typeof res === 'undefined') {
var d = document.createElement('div');
d.style.fontSize = '13px';
d.style.lineHeight = '1.5';
d.style.padding = 0;
d.style.border = 0;
d.innerHTML = '&nbsp;<br />&nbsp;';
document.body.appendChild(d);
// Browsers that round the line-height should have offsetHeight === 38
// The others should have 39.
res = d.offsetHeight === 38;
document.body.removeChild(d);
}
return res;
}
}());
function makeActions() {
var actions = [];
return {
actions: actions,
apply: function(fn, context, args){
actions.push(function(){
fn.apply(context, args);
});
},
set: function(obj, key, value) {
actions.push(function(){
obj[key] = value;
});
}
};
}
function highlightLines(pre, lines, classes) {
lines = typeof lines === 'string' ? lines : pre.getAttribute('data-line');
var ranges = lines.replace(/\s+/g, '').split(','),
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 action = makeActions();
for (var i=0, currentRange; currentRange = ranges[i++];) {
var range = currentRange.split('-');
var start = +range[0],
end = +range[1] || start;
var line = pre.querySelector('.line-highlight[data-range="' + currentRange + '"]') || document.createElement('div');
// line.setAttribute('aria-hidden', 'true');
action.apply(line.setAttribute, line,['aria-hidden', 'true']);
// line.setAttribute('data-range', currentRange);
action.apply(line.setAttribute, line,['data-range', currentRange]);
line.className = (classes || '') + ' line-highlight';
//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 (startNode) {
// line.style.top = startNode.offsetTop + 'px';
action.set(line.style, "top", startNode.offsetTop + 'px');
}
if (endNode) {
//line.style.height = (endNode.offsetTop - startNode.offsetTop) + endNode.offsetHeight + 'px';
action.set(line.style, "height", (endNode.offsetTop - startNode.offsetTop) + endNode.offsetHeight + 'px' );
}
} else {
// line.setAttribute('data-start', start);
action.apply(line.setAttribute, line, ['data-start', start]);
if(end > start) {
// line.setAttribute('data-end', end);
action.apply(line.setAttribute, line, ['data-end', end]);
}
//line.style.top = (start - offset - 1) * lineHeight + 'px';
action.set(line.style, "top", (start - offset - 1) * lineHeight + 'px' );
action.set(line, "textContent", new Array(end - start + 2).join(' \n') );
}
//allow this to play nicely with the line-numbers plugin
if(hasLineNumbers) {
//need to attack to pre as when line-numbers is enabled, the code tag is relatively which screws up the positioning
// pre.appendChild(line);
action.apply( pre.appendChild, pre, [line] );
} else {
action(pre.appendChild, pre.querySelector('code') || pre, [line]);
}
}
return action.actions;
}
function applyHash() {
var hash = location.hash.slice(1);
// Remove pre-existing temporary lines
$$('.temporary.line-highlight').forEach(function (line) {
line.parentNode.removeChild(line);
});
var range = (hash.match(/\.([\d,-]+)$/) || [,''])[1];
if (!range || document.getElementById(hash)) {
return;
}
var id = hash.slice(0, hash.lastIndexOf('.')),
pre = document.getElementById(id);
if (!pre) {
return;
}
if (!pre.hasAttribute('data-line')) {
pre.setAttribute('data-line', '');
}
var actions = highlightLines(pre, range, 'temporary ');
actions.forEach(function(action){
action();
})
document.querySelector('.temporary.line-highlight').scrollIntoView();
}
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 lines = pre && pre.getAttribute('data-line');
if (!pre || !lines || !/pre/i.test(pre.nodeName)) {
return;
}
/*
* Cleanup for other plugins (e.g. autoloader).
*
* Sometimes <code> blocks are highlighted multiple times. It is necessary
* to cleanup any left-over tags, because the whitespace inside of the <div>
* tags change the content of the <code> tag.
*/
var num = 0;
$$('.line-highlight', pre).forEach(function (line) {
num += line.textContent.length;
line.parentNode.removeChild(line);
});
// Remove extra whitespace
if (num && /^( \n)+$/.test(env.code.slice(-num))) {
env.code = env.code.slice(0, -num);
}
});
Prism.hooks.add('complete', function completeHook(env) {
var pre = env.element.parentNode;
var lines = pre && pre.getAttribute('data-line');
if (!pre || !lines || !/pre/i.test(pre.nodeName)) {
return;
}
clearTimeout(fakeTimer);
var hasLineNumbers = Prism.plugins.lineNumbers;
var isLineNumbersLoaded = env.plugins && env.plugins.lineNumbers;
if (hasClass(pre, 'line-numbers') && hasLineNumbers && !isLineNumbersLoaded) {
Prism.hooks.add('line-numbers', completeHook);
} else {
var actions = highlightLines(pre, lines);
actions.forEach(function(action){
action();
})
fakeTimer = setTimeout(applyHash, 1);
}
});
window.addEventListener('hashchange', applyHash);
window.addEventListener('resize', function () {
var preElements = document.querySelectorAll('pre[data-line]');
var now = new Date();
var actions = [];
Array.prototype.forEach.call(preElements, function (pre) {
actions.push.apply(actions, highlightLines(pre) );
});
actions.forEach(function(action){
action();
});
console.log("time", new Date() - now);
});
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment