const codeActionButtons = [
|
{
|
icon: 'copy',
|
id: 'copy',
|
title: 'Copy Code',
|
show: true
|
},
|
{
|
icon: 'order',
|
id: 'lines',
|
title: 'Toggle Line Numbers',
|
show: true
|
},
|
{
|
icon: 'carly',
|
id: 'wrap',
|
title: 'Toggle Line Wrap',
|
show: false
|
},
|
{
|
icon: 'expand',
|
id: 'expand',
|
title: 'Toggle code block expand',
|
show: false
|
}
|
];
|
|
const body = elem('body');
|
const maxLines = parseInt(body.dataset.code);
|
const copyId = 'panel_copy';
|
const wrapId = 'panel_wrap';
|
const linesId = 'panel_lines';
|
const panelExpand = 'panel_expand';
|
const panelExpanded = 'panel_expanded';
|
const panelHide = 'panel_hide';
|
const panelFrom = 'panel_from';
|
const panelBox = 'panel_box';
|
const fullHeight = 'initial';
|
const highlightWrap = 'highlight_wrap'
|
const highlight = 'highlight';
|
|
function wrapOrphanedPreElements() {
|
const pres = elems('pre');
|
Array.from(pres).forEach(function(pre){
|
const parent = pre.parentNode;
|
const isOrpaned = !containsClass(parent, highlight) ;
|
if(isOrpaned) {
|
const preWrapper = createEl();
|
preWrapper.className = highlight;
|
const outerWrapper = createEl();
|
outerWrapper.className = highlightWrap;
|
wrapEl(pre, preWrapper);
|
wrapEl(preWrapper, outerWrapper);
|
}
|
})
|
/*
|
@Todo
|
1. Add UI control to orphaned blocks
|
*/
|
}
|
|
wrapOrphanedPreElements();
|
|
function codeBlocks() {
|
const markedCodeBlocks = elems('code');
|
const blocks = Array.from(markedCodeBlocks).filter(function(block){
|
return block.closest("pre") && !Array.from(block.classList).includes('noClass');
|
}).map(function(block){
|
return block
|
});
|
return blocks;
|
}
|
|
function codeBlockFits(block) {
|
// return false if codeblock overflows
|
const blockWidth = block.offsetWidth;
|
const highlightBlockWidth = block.closest(`.${highlight}`).offsetWidth;
|
return blockWidth <= highlightBlockWidth ? true : false;
|
}
|
|
function maxHeightIsSet(elem) {
|
let maxHeight = elem.style.maxHeight;
|
return maxHeight.includes('px')
|
}
|
|
function restrainCodeBlockHeight(lines) {
|
const lastLine = lines[maxLines-1];
|
let maxCodeBlockHeight = fullHeight;
|
if(lastLine) {
|
const lastLinePos = lastLine.offsetTop;
|
if(lastLinePos !== 0) {
|
maxCodeBlockHeight = `${lastLinePos}px`;
|
const codeBlock = lines[0].parentNode;
|
const outerBlock = codeBlock.closest('.highlight');
|
const isExpanded = containsClass(outerBlock, panelExpanded);
|
if(!isExpanded) {
|
codeBlock.dataset.height = maxCodeBlockHeight;
|
codeBlock.style.maxHeight = maxCodeBlockHeight;
|
}
|
}
|
}
|
}
|
|
const blocks = codeBlocks();
|
|
function collapseCodeBlock(block) {
|
const lines = elems('.ln', block);
|
const codeLines = lines.length;
|
if(!lines) {
|
// hide lines toggle button for blocks that have no lines.
|
setTimeout(() => {
|
let panelLinesParentEl = block.closest(`.${highlightWrap}`);
|
let panelLinesEl = elem(`.${linesId}`, panelLinesParentEl);
|
pushClass(panelLinesEl, panelHide);
|
}, 2000)
|
}
|
if (codeLines > maxLines) {
|
const expandDot = createEl()
|
pushClass(expandDot, panelExpand);
|
pushClass(expandDot, panelFrom);
|
expandDot.title = "Toggle code block expand";
|
expandDot.textContent = "...";
|
const outerBlock = block.closest('.highlight');
|
window.setTimeout(function(){
|
const expandIcon = outerBlock.nextElementSibling.lastElementChild;
|
deleteClass(expandIcon, panelHide);
|
}, 150)
|
|
restrainCodeBlockHeight(lines);
|
const highlightElement = block.parentNode.parentNode;
|
highlightElement.appendChild(expandDot);
|
}
|
}
|
|
blocks.forEach(function(block){
|
collapseCodeBlock(block);
|
})
|
|
function actionPanel() {
|
const panel = createEl();
|
panel.className = panelBox;
|
|
codeActionButtons.forEach(function(button) {
|
// create button
|
const btn = createEl('a');
|
btn.href = '#';
|
btn.title = button.title;
|
btn.className = `icon panel_icon panel_${button.id}`;
|
button.show ? false : pushClass(btn, panelHide);
|
// load icon inside button
|
loadSvg(button.icon, btn);
|
// append button on panel
|
panel.appendChild(btn);
|
});
|
|
return panel;
|
}
|
|
function toggleLineNumbers(elems) {
|
elems.forEach(function (elem, index) {
|
// mark the code element when there are no lines
|
modifyClass(elem, 'pre_nolines')
|
});
|
restrainCodeBlockHeight(elems);
|
}
|
|
function toggleLineWrap(elem) {
|
modifyClass(elem, 'pre_wrap');
|
// retain max number of code lines on line wrap
|
const lines = elems('.ln', elem);
|
restrainCodeBlockHeight(lines);
|
}
|
|
function copyCode(codeElement) {
|
lineNumbers = elems('.ln', codeElement);
|
// remove line numbers before copying
|
if(lineNumbers.length) {
|
lineNumbers.forEach(function(line){
|
line.remove();
|
});
|
}
|
|
const codeToCopy = codeElement.textContent;
|
// copy code
|
copyToClipboard(codeToCopy);
|
}
|
|
function disableCodeLineNumbers(block){
|
const lines = elems('.ln', block)
|
toggleLineNumbers(lines);
|
}
|
|
(function codeActions(){
|
const blocks = codeBlocks();
|
|
const highlightWrapId = highlightWrap;
|
blocks.forEach(function(block){
|
// disable line numbers if disabled globally
|
const showLines = elem('body').dataset.lines;
|
parseBoolean(showLines) === false ? disableCodeLineNumbers(block) : false;
|
|
const highlightElement = block.parentNode.parentNode;
|
// wrap code block in a div
|
const highlightWrapper = createEl();
|
highlightWrapper.className = highlightWrapId;
|
|
wrapEl(highlightElement, highlightWrapper);
|
|
const panel = actionPanel();
|
// show wrap icon only if the code block needs wrapping
|
const wrapIcon = elem(`.${wrapId}`, panel);
|
codeBlockFits(block) ? false : deleteClass(wrapIcon, panelHide);
|
|
// append buttons
|
highlightWrapper.appendChild(panel);
|
});
|
|
function isItem(target, id) {
|
// if is item or within item
|
return target.matches(`.${id}`) || target.closest(`.${id}`);
|
}
|
|
function showActive(target, targetClass,activeClass = 'active') {
|
const active = activeClass;
|
const targetElement = target.matches(`.${targetClass}`) ? target : target.closest(`.${targetClass}`);
|
|
deleteClass(targetElement, active);
|
setTimeout(function() {
|
modifyClass(targetElement, active)
|
}, 50)
|
}
|
|
doc.addEventListener('click', function(event){
|
// copy code block
|
const target = event.target;
|
const isCopyIcon = isItem(target, copyId);
|
const isWrapIcon = isItem(target, wrapId);
|
const isLinesIcon = isItem(target, linesId);
|
const isExpandIcon = isItem(target, panelExpand);
|
const isActionable = isCopyIcon || isWrapIcon || isLinesIcon || isExpandIcon;
|
|
if(isActionable) {
|
event.preventDefault();
|
showActive(target, 'icon');
|
const codeElement = target.closest(`.${highlightWrapId}`).firstElementChild.firstElementChild;
|
let lineNumbers = elems('.ln', codeElement);
|
|
isWrapIcon ? toggleLineWrap(codeElement) : false;
|
|
isLinesIcon ? toggleLineNumbers(lineNumbers) : false;
|
|
if (isExpandIcon) {
|
let thisCodeBlock = codeElement.firstElementChild;
|
const outerBlock = thisCodeBlock.closest('.highlight');
|
if(maxHeightIsSet(thisCodeBlock)) {
|
thisCodeBlock.style.maxHeight = fullHeight;
|
// mark code block as expanded
|
pushClass(outerBlock, panelExpanded)
|
} else {
|
thisCodeBlock.style.maxHeight = thisCodeBlock.dataset.height;
|
// unmark code block as expanded
|
deleteClass(outerBlock, panelExpanded)
|
}
|
}
|
|
if(isCopyIcon) {
|
// clone code element
|
const codeElementClone = codeElement.cloneNode(true);
|
copyCode(codeElementClone);
|
}
|
}
|
});
|
|
(function addLangLabel() {
|
const blocks = codeBlocks();
|
blocks.forEach(function(block){
|
let label = block.dataset.lang;
|
label = label === 'sh' ? 'bash' : label;
|
if(label !== "fallback") {
|
const labelEl = createEl();
|
labelEl.textContent = label;
|
pushClass(labelEl, 'lang');
|
block.closest(`.${highlightWrap}`).appendChild(labelEl);
|
}
|
});
|
})();
|
})();
|