Created
April 23, 2019 18:54
-
-
Save justinbmeyer/1230ae64918372ea9bf124f6dce74326 to your computer and use it in GitHub Desktop.
A faster version of prism line highlight.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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 = ' <br /> '; | |
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