From 413e03f10099d7c341bd76337b6892e0893ae445 Mon Sep 17 00:00:00 2001
From: weru <fromweru@gmail.com>
Date: Sun, 27 Dec 2020 16:56:34 +0000
Subject: [PATCH] add helpers
---
assets/js/index.js | 23 ---
assets/js/functions.js | 44 +++++++
assets/js/code.js | 277 ++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 322 insertions(+), 22 deletions(-)
diff --git a/assets/js/code.js b/assets/js/code.js
new file mode 100644
index 0000000..7116657
--- /dev/null
+++ b/assets/js/code.js
@@ -0,0 +1,277 @@
+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'
+
+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 hasClasses(block) && !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.parentNode.parentNode.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 (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);
+ }
+ });
+ })();
+})();
diff --git a/assets/js/functions.js b/assets/js/functions.js
index 33a15e9..d5bba50 100644
--- a/assets/js/functions.js
+++ b/assets/js/functions.js
@@ -151,4 +151,48 @@
link.href = href;
});
}
+}
+
+function parseBoolean(string) {
+ let bool;
+ string = string.trim().toLowerCase();
+ switch (string) {
+ case 'true':
+ return true;
+ case 'false':
+ return false;
+ default:
+ return undefined;
+ }
+};
+
+function loadSvg(file, parent, path = 'icons/') {
+ const link = `${parentURL}${path}${file}.svg`;
+ fetch(link)
+ .then((response) => {
+ return response.text();
+ })
+ .then((data) => {
+ parent.innerHTML = data;
+ });
+}
+
+function copyToClipboard(str) {
+ let copy, selection, selected;
+ copy = createEl('textarea');
+ copy.value = str;
+ copy.setAttribute('readonly', '');
+ copy.style.position = 'absolute';
+ copy.style.left = '-9999px';
+ selection = document.getSelection();
+ doc.appendChild(copy);
+ // check if there is any selected content
+ selected = selection.rangeCount > 0 ? selection.getRangeAt(0) : false;
+ copy.select();
+ document.execCommand('copy');
+ doc.removeChild(copy);
+ if (selected) { // if a selection existed before copying
+ selection.removeAllRanges(); // unselect existing selection
+ selection.addRange(selected); // restore the original selection
+ }
}
\ No newline at end of file
diff --git a/assets/js/index.js b/assets/js/index.js
index c5f5842..a8e2792 100644
--- a/assets/js/index.js
+++ b/assets/js/index.js
@@ -183,27 +183,6 @@
}
});
- const copyToClipboard = str => {
- let copy, selection, selected;
- copy = createEl('textarea');
- copy.value = str;
- copy.setAttribute('readonly', '');
- copy.style.position = 'absolute';
- copy.style.left = '-9999px';
- selection = document.getSelection();
- doc.appendChild(copy);
- // check if there is any selected content
- selected = selection.rangeCount > 0 ? selection.getRangeAt(0) : false;
- copy.select();
- document.execCommand('copy');
- doc.removeChild(copy);
- if (selected) { // if a selection existed before copying
- selection.removeAllRanges(); // unselect existing selection
- selection.addRange(selected); // restore the original selection
- }
- }
-
-
function copyFeedback(parent) {
const copyText = document.createElement('div');
const yanked = 'link_yanked';
@@ -355,4 +334,4 @@
}
-window.addEventListener('load', loadActions());
+window.addEventListener('load', loadActions());
\ No newline at end of file
--
Gitblit v1.10.0