I am using Prism on my blog for my syntax highlighting and I am very happy and thankful about it. Sometimes I wanted to show the users of my blog a specific function of a class, but without decoupling the function from the class. So I needed something like a Prism plugin that can hide specific lines of the code in a foldable area.
A small demo of my first prototype of a Prism code folding plugin that hides all functions except the function
getFoldingeFunction
. Here the current source of prism-code-folding.js
with
data-folding-open="1-103,7-16"
and
data-folding-closed="3-5,18-55,57-83,85-101"
on the pre
element.
(function(){
if (typeof self === 'undefined' || !self.Prism || !self.document || !document.querySelector) {
return;
}
function getFoldingeFunction(container) {
return function() {
if (container.className === "prism-folding prism-folding-closed") {
container.className = "prism-folding";
return false;
}
container.className = "prism-folding prism-folding-closed";
return false;
};
};
function createFoldings(newLinesNodes, lines, closed) {
var ranges = lines.replace(/\s+/g, '').split(',');
for (var i = 0; i < ranges.length; i++) {
var range = ranges[i].split('-');
var start = range[0];
var end = range[1] - 1;
var divFoldingContainer = document.createElement('span');
divFoldingContainer.className = "prism-folding";
if (closed) {
divFoldingContainer.className = "prism-folding prism-folding-closed";
}
var divFoldingNode = document.createElement('span');
divFoldingNode.className = "prism-folding-node";
divFoldingContainer.appendChild(divFoldingNode);
var parent = newLinesNodes[start].nextSibling.parentNode;
parent.insertBefore(divFoldingContainer, newLinesNodes[start].nextSibling);
var currentNode = divFoldingContainer.nextSibling;
while (currentNode !== newLinesNodes[end]) {
movingNode = currentNode;
currentNode = currentNode.nextSibling;
divFoldingNode.appendChild(movingNode);
}
divFoldingNode.appendChild(newLinesNodes[end]);
var foldingFunction = getFoldingeFunction(divFoldingContainer);
var arrowDown = document.createElement("span");
arrowDown.appendChild(document.createTextNode("\u25BD"));
arrowDown.className = "prism-folding-arrow prism-folding-arrow-down";
divFoldingContainer.appendChild(arrowDown);
arrowDown.addEventListener('click', foldingFunction);
var arrowUp = document.createElement("span");
arrowUp.appendChild(document.createTextNode("\u25B3"));
arrowUp.className = "prism-folding-arrow prism-folding-arrow-up";
divFoldingContainer.appendChild(arrowUp);
arrowUp.addEventListener('click', foldingFunction);
}
}
function splitAndReturnNewLineElements(element) {
var children = element.childNodes;
var newLinesNodes = [];
var newLineCounter = 0;
for (var i = 0; i < children.length; i++) {
var child = children[i];
if (child.nodeType === Node.TEXT_NODE) {
var textNodeValue = child.nodeValue;
var splitOnNewLine = textNodeValue.split("\n");
if (splitOnNewLine.length > 1) {
var siblingBefore = child;
var parent = child.parentNode;
child.nodeValue = splitOnNewLine[0];
for (var j = 1; j < splitOnNewLine.length; j++) {
var newLineNode = document.createTextNode("\n");
parent.insertBefore(newLineNode, siblingBefore.nextSibling);
newLinesNodes[++newLineCounter] = newLineNode;
var splitResultNode = document.createTextNode(splitOnNewLine[j]);
parent.insertBefore(splitResultNode, newLineNode.nextSibling);
i += 2;
siblingBefore = splitResultNode;
}
}
}
}
return newLinesNodes;
}
Prism.hooks.add('complete', function(env) {
var pre = env.element.parentNode;
var foldingOpenLines = pre && pre.getAttribute('data-folding-open');
var foldingClosedLines = pre && pre.getAttribute('data-folding-closed');
if (!pre || !(foldingOpenLines || foldingClosedLines) || !/pre/i.test(pre.nodeName)) {
return;
}
var element = env.element;
var newLinesNodes = splitAndReturnNewLineElements(element);
if (foldingOpenLines) {
createFoldings(newLinesNodes, foldingOpenLines);
}
if (foldingClosedLines) {
createFoldings(newLinesNodes, foldingClosedLines, true);
}
});
})();
An other small example is the source of prism-code-folding.css
with
data-folding-open="1-3,4-10,11-14,15-17,18-20,21-23,24-26"
on the pre
tag.
.prism-folding {
position: relative;
}
.prism-folding > .prism-folding-arrow {
position: absolute;
left: -0.5em;
text-decoration: none;
opacity: 0.5;
line-height: 1em;
}
.prism-folding > .prism-folding-arrow:hover {
cursor: pointer;
opacity: 1;
}
.prism-folding > .prism-folding-arrow-down {
top: -1em;
}
.prism-folding-closed > .prism-folding-node {
display: none;
}
.prism-folding-closed > .prism-folding-arrow {
transform: rotate(270deg);
}
Prism is highlighting code inside of a code
tag with span
tokens that have specific
classes. The character for a new line \n
is often inside of a text node. The main idea is to split
the next nodes that contain a new line character into three parts. The part before the new line character, the new
line character itself and the part behind the new line character. Afterwards I take all nodes between a given
starting line and a given ending line and wrap them in a div
element. This div
can change
the display
style from none
to block
with the help of two arrows at the top
and the bottom of the div
.
Obviously, this is supposed to work only for code blocks (<pre><code>
) and not for inline
code.
You specify the lines to be folded through the data-folding-open
attribute for an open
folding or the data-folding-closed
attribute for an closed folding on the <pre>
element, in the following simple format:
Examples:
As mentioned above this is just a prototype to see if it can get an offical Prism plugin in the future. There are some known issues at the moment.