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.