highlightjs-badge
Version:
This small JavaScript library that complements the [highlighJs Syntax Highligher](https://highlightjs.org/) by providing an overlay badge that lets you copy code to the clipboard and display the active Syntax language.
444 lines (370 loc) • 14.4 kB
JavaScript
/*
----------------------------------------
highlightJs Badge
----------------------------------------
A copy code and language display badge
for the highlightJs Syntax highlighter.
by Rick Strahl, 2019-2020
License: MIT
Make sure this script is loaded last in your
script loading.
Usage:
------
Load `highlightjs-badge.js` after `highlight.js`:
```js
<link href="highlightjs/styles/vs2015.css" rel="stylesheet">
<script src="highlighjs/highlight.pack.js"></script>
<script src="highlightjs-badge.js"></script>
<script>
setTimeout(function () {
var pres = document.querySelectorAll("pre>code");
for (var i = 0; i < pres.length; i++) {
hljs.highlightBlock(pres[i]);
}
var options = {
contentSelector: "#ArticleBody",
// Delay in ms used for `setTimeout` before badging is applied
// Use if you need to time highlighting and badge application
// since the badges need to be applied afterwards.
// 0 - direct execution (ie. you handle timing
loadDelay:0,
// CSS class(es) used to render the copy icon.
copyIconClass: "fa fa-copy",
// CSS class(es) used to render the done icon.
checkIconClass: "fa fa-check text-success"
};
window.highlightJsBadge(options);
},10);
</script>
```
The script contains the template and CSS so nothing
else is needed to run it.
Customization:
--------------
This code automatically embeds styling and the template.
If you want to customize you can either create a template
in your HTML **using the code at the end of this file**.
Alternately you can customize the `getTemplate()` function
that renders the code from a string and keep it self contained
within this script.
Requirements:
-------------
Uses some ES6 features so won't work in IE without shims:
* Object.assign
* String.trim
*/
// module header
(function( global, factory ) {
if ( typeof module === "object" && typeof module.exports === "object" ) {
// For CommonJS and CommonJS-like environments where a proper `window`
// is present, execute the factory
module.exports = global.document ?
factory( global, true ) :
function( w ) {
if ( !w.document ) {
throw new Error( "A window with a document is required" );
}
return factory( w );
};
} else {
factory( global );
}
// Pass this if window is not defined yet
}(typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
if (typeof highlightJsBadgeAutoLoad !== 'boolean')
var highlightJsBadgeAutoLoad = false;
function highlightJsBadge(opt) {
var options = {
// the selector for the badge template
templateSelector: "#CodeBadgeTemplate",
// base content selector that is searched for snippets
contentSelector: "body",
// Delay in ms used for `setTimeout` before badging is applied
// Use if you need to time highlighting and badge application
// since the badges need to be applied afterwards.
// 0 - direct execution (ie. you handle timing
loadDelay: 0,
// CSS class(es) used to render the copy icon.
copyIconClass: "fa fa-copy",
// optional content for icons class (<i class="fa fa-copy"></i> or <i class="material-icons">file_copy</i>)
copyIconContent: "",
// CSS class(es) used to render the done icon.
checkIconClass: "fa fa-check text-success",
checkIconContent: "",
// function called before code is placed on clipboard
// Passed in text and returns back text function(text, codeElement) { return text; }
onBeforeCodeCopied: null
};
function initialize(opt) {
Object.assign(options, opt);
if (document.readyState == 'loading')
document.addEventListener("DOMContentLoaded", load);
else
load();
}
function load() {
if (options.loadDelay)
setTimeout(addCodeBadge, loadDelay);
else
addCodeBadge();
}
function addCodeBadge() {
// first make sure the template exists - if not we embed it
if (!document.querySelector(options.templateSelector)) {
var node = document.createElement("div");
node.innerHTML = getTemplate();
var style = node.querySelector("style");
var template = node.querySelector(options.templateSelector);
document.body.appendChild(style);
document.body.appendChild(template);
}
var hudText = document.querySelector(options.templateSelector).innerHTML;
var $codes = document.querySelectorAll("pre>code.hljs");
for (var index = 0; index < $codes.length; index++) {
var el = $codes[index];
if (el.querySelector(".code-badge"))
continue; // already exists
var lang = "";
for (var i = 0; i < el.classList.length; i++) {
var cl = el.classList[i];
// class="hljs language-csharp"
if (cl.substr(0, 9) === 'language-') {
lang = el.classList[i].replace('language-', '');
break;
}
// class="hljs lang-cs" // docFx
else if (cl.substr(0, 5) === 'lang-') {
lang = el.classList[i].replace('lang-', '');
break;
}
// class="kotlin hljs" (auto detected)
if (!lang) {
for (var j = 0; j < el.classList.length; j++) {
if (el.classList[j] == 'hljs')
continue;
lang = el.classList[j];
break;
}
}
}
if (lang)
lang = lang.toLowerCase();
else
lang = "text";
// Language Name overrides so it displays nicer
if (lang == "ps")
lang = "powershell";
else if (lang == "cs")
lang = "csharp";
else if (lang == "js")
lang = "javascript";
else if (lang == "ts")
lang = "typescript";
else if (lang == "fox")
lang = "foxpro";
else if (lang == "txt")
lang = "text"
var html = hudText.replace("{{language}}", lang)
.replace("{{copyIconClass}}",options.copyIconClass)
.trim();
// insert the Hud panel
var $newHud = document.createElement("div");
$newHud.innerHTML = html;
$newHud = $newHud.querySelector(".code-badge");
// make <pre> tag position:relative so positioning keeps pinned right
// even with scroll bar scrolled
var pre = el.parentElement;
pre.classList.add("code-badge-pre")
if(options.copyIconContent)
$newHud.querySelector(".code-badge-copy-icon").innerText = options.copyIconContent;
pre.insertBefore($newHud, el);
}
var $content = document.querySelector(options.contentSelector);
// single copy click handler
$content.addEventListener("click",
function (e) {
var $clicked = e.srcElement;
if ($clicked.classList.contains("code-badge-copy-icon")) {
e.preventDefault();
e.cancelBubble = true;
copyCodeToClipboard(e);
}
return false;
});
}
function copyCodeToClipboard(e) {
// walk back up to <pre> tag
var $origCode = e.srcElement.parentElement.parentElement.parentElement;
// select the <code> tag and grab text
var $code = $origCode.querySelector("pre>code");
var text = $code.textContent || $code.innerText;
if (options.onBeforeCodeCopied)
text = options.onBeforeCodeCopied(text, $code);
// Create a textblock and assign the text and add to document
var el = document.createElement('textarea');
el.value = text.trim();
document.body.appendChild(el);
el.style.display = "block";
// select the entire textblock
if (window.document.documentMode)
el.setSelectionRange(0, el.value.length);
else
el.select();
// copy to clipboard
document.execCommand('copy');
// clean up element
document.body.removeChild(el);
// show the check icon (copied) briefly
swapIcons($origCode);
}
function swapIcons($code) {
var copyIcons = options.copyIconClass.split(' ');
var checkIcons = options.checkIconClass.split(' ');
var $fa = $code.querySelector(".code-badge-copy-icon");
$fa.innerText = options.checkIconContent;
for (var i = 0; i < copyIcons.length; i++)
$fa.classList.remove(copyIcons[i]);
for (var i = 0; i < checkIcons.length; i++)
$fa.classList.add(checkIcons[i]);
setTimeout(function () {
$fa.innerText = options.copyIconContent;
for (var i = 0; i < checkIcons.length; i++)
$fa.classList.remove(checkIcons[i]);
for (var i = 0; i < copyIcons.length; i++)
$fa.classList.add(copyIcons[i]);
}, 2000);
}
function getTemplate() {
var stringArray =
[
"<style>",
"@media print {",
" .code-badge { display: none; }",
"}",
" .code-badge-pre {",
" position: relative;",
" }",
" .code-badge {",
" display: flex;",
" flex-direction: row;",
" white-space: normal;",
" background: transparent;",
" background: #333;",
" color: white;",
" font-size: 0.875em;",
" opacity: 0.5;",
" transition: opacity linear 0.5s;",
" border-radius: 0 0 0 7px;",
" padding: 5px 8px 5px 8px;",
" position: absolute;",
" right: 0;",
" top: 0;",
" }",
" .code-badge.active {",
" opacity: 0.8;",
" }",
"",
" .code-badge:hover {",
" opacity: .95;",
" }",
"",
" .code-badge a,",
" .code-badge a:hover {",
" text-decoration: none;",
" }",
"",
" .code-badge-language {",
" margin-right: 10px;",
" font-weight: 600;",
" color: goldenrod;",
" }",
" .code-badge-copy-icon {",
" font-size: 1.2em;",
" cursor: pointer;",
" padding: 0 7px;",
" margin-top:2;",
" }",
" .fa.text-success:{ color: limegreen !important }",
"</style>",
"<div id=\"CodeBadgeTemplate\" style=\"display:none\">",
" <div class=\"code-badge\">",
" <div class=\"code-badge-language\" >{{language}}</div>",
" <div title=\"Copy to clipboard\">",
" <i class=\"{{copyIconClass}} code-badge-copy-icon\"></i></i></a>",
" </div>",
" </div>",
"</div>"
];
var t = "";
for (var i = 0; i < stringArray.length; i++)
t += stringArray[i] + "\n";
return t;
}
initialize(opt);
}
// global reference Window
window.highlightJsBadge = highlightJsBadge;
// module export
if (window.module && window.module.exports)
window.module.exports.highlightJsBadge = highlightJsBadge;
if (highlightJsBadgeAutoLoad)
highlightJsBadge();
}));
// You can embed the following into your HTML document
// to provide your own custom styling.
/*
<style>
"@media print {
.code-badge { display: none; }
}
.code-badge-pre {
position: relative;
}
.code-badge {
display: flex;
flex-direction: row;
white-space: normal;
background: transparent;
background: #333;
color: white;
font-size: 0.875em;
opacity: 0.5;
border-radius: 0 0 0 7px;
padding: 5px 8px 5px 8px;
position: absolute;
right: 0;
top: 0;
}
.code-badge.active {
opacity: 0.8;
}
.code-badge:hover {
opacity: .95;
}
.code-badge a,
.code-badge a:hover {
text-decoration: none;
}
.code-badge-language {
margin-right: 10px;
font-weight: 600;
color: goldenrod;
}
.code-badge-copy-icon {
font-size: 1.2em;
cursor: pointer;
padding: 0 7px;
margin-top:2;
}
.fa.text-success:{ color: limegreen !important}
</style>
<div id="CodeBadgeTemplate" style="display:none">
<div class="code-badge">
<div class="code-badge-language">{{language}}</div>
<div title="Copy to clipboard">
<i class="{{copyIconClass}} code-badge-copy-icon"></i>
</div>
</div>
</div>
*/
;