UNPKG

14.4 kBJavaScriptView Raw
1"use strict";
2/*
3----------------------------------------
4highlightJs Badge
5----------------------------------------
6
7A copy code and language display badge
8for the highlightJs Syntax highlighter.
9
10by Rick Strahl, 2019-2020
11License: MIT
12
13Make sure this script is loaded last in your
14script loading.
15
16Usage:
17------
18Load `highlightjs-badge.js` after `highlight.js`:
19
20```js
21<link href="highlightjs/styles/vs2015.css" rel="stylesheet">
22<script src="highlighjs/highlight.pack.js"></script>
23
24<script src="highlightjs-badge.js"></script>
25<script>
26 setTimeout(function () {
27 var pres = document.querySelectorAll("pre>code");
28 for (var i = 0; i < pres.length; i++) {
29 hljs.highlightBlock(pres[i]);
30 }
31 var options = {
32 contentSelector: "#ArticleBody",
33 // Delay in ms used for `setTimeout` before badging is applied
34 // Use if you need to time highlighting and badge application
35 // since the badges need to be applied afterwards.
36 // 0 - direct execution (ie. you handle timing
37 loadDelay:0,
38
39 // CSS class(es) used to render the copy icon.
40 copyIconClass: "fa fa-copy",
41 // CSS class(es) used to render the done icon.
42 checkIconClass: "fa fa-check text-success"
43 };
44 window.highlightJsBadge(options);
45 },10);
46</script>
47```
48
49The script contains the template and CSS so nothing
50else is needed to run it.
51
52Customization:
53--------------
54This code automatically embeds styling and the template.
55
56If you want to customize you can either create a template
57in your HTML **using the code at the end of this file**.
58
59Alternately you can customize the `getTemplate()` function
60that renders the code from a string and keep it self contained
61within this script.
62
63Requirements:
64-------------
65Uses some ES6 features so won't work in IE without shims:
66
67* Object.assign
68* String.trim
69
70*/
71
72// module header
73(function( global, factory ) {
74
75 if ( typeof module === "object" && typeof module.exports === "object" ) {
76 // For CommonJS and CommonJS-like environments where a proper `window`
77 // is present, execute the factory
78 module.exports = global.document ?
79 factory( global, true ) :
80 function( w ) {
81 if ( !w.document ) {
82 throw new Error( "A window with a document is required" );
83 }
84 return factory( w );
85 };
86 } else {
87 factory( global );
88 }
89
90// Pass this if window is not defined yet
91}(typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
92
93if (typeof highlightJsBadgeAutoLoad !== 'boolean')
94 var highlightJsBadgeAutoLoad = false;
95
96function highlightJsBadge(opt) {
97 var options = {
98 // the selector for the badge template
99 templateSelector: "#CodeBadgeTemplate",
100
101 // base content selector that is searched for snippets
102 contentSelector: "body",
103
104 // Delay in ms used for `setTimeout` before badging is applied
105 // Use if you need to time highlighting and badge application
106 // since the badges need to be applied afterwards.
107 // 0 - direct execution (ie. you handle timing
108 loadDelay: 0,
109
110 // CSS class(es) used to render the copy icon.
111 copyIconClass: "fa fa-copy",
112 // optional content for icons class (<i class="fa fa-copy"></i> or <i class="material-icons">file_copy</i>)
113 copyIconContent: "",
114
115 // CSS class(es) used to render the done icon.
116 checkIconClass: "fa fa-check text-success",
117 checkIconContent: "",
118
119 // function called before code is placed on clipboard
120 // Passed in text and returns back text function(text, codeElement) { return text; }
121 onBeforeCodeCopied: null
122 };
123
124 function initialize(opt) {
125 Object.assign(options, opt);
126
127 if (document.readyState == 'loading')
128 document.addEventListener("DOMContentLoaded", load);
129 else
130 load();
131 }
132
133
134 function load() {
135 if (options.loadDelay)
136 setTimeout(addCodeBadge, loadDelay);
137 else
138 addCodeBadge();
139 }
140
141 function addCodeBadge() {
142 // first make sure the template exists - if not we embed it
143 if (!document.querySelector(options.templateSelector)) {
144 var node = document.createElement("div");
145 node.innerHTML = getTemplate();
146 var style = node.querySelector("style");
147 var template = node.querySelector(options.templateSelector);
148 document.body.appendChild(style);
149 document.body.appendChild(template);
150 }
151
152 var hudText = document.querySelector(options.templateSelector).innerHTML;
153
154 var $codes = document.querySelectorAll("pre>code.hljs");
155 for (var index = 0; index < $codes.length; index++) {
156 var el = $codes[index];
157 if (el.querySelector(".code-badge"))
158 continue; // already exists
159
160 var lang = "";
161
162 for (var i = 0; i < el.classList.length; i++) {
163 var cl = el.classList[i];
164 // class="hljs language-csharp"
165 if (cl.substr(0, 9) === 'language-') {
166 lang = el.classList[i].replace('language-', '');
167 break;
168 }
169 // class="hljs lang-cs" // docFx
170 else if (cl.substr(0, 5) === 'lang-') {
171 lang = el.classList[i].replace('lang-', '');
172 break;
173 }
174 // class="kotlin hljs" (auto detected)
175 if (!lang) {
176 for (var j = 0; j < el.classList.length; j++) {
177 if (el.classList[j] == 'hljs')
178 continue;
179 lang = el.classList[j];
180 break;
181 }
182 }
183 }
184
185 if (lang)
186 lang = lang.toLowerCase();
187 else
188 lang = "text";
189
190 // Language Name overrides so it displays nicer
191 if (lang == "ps")
192 lang = "powershell";
193 else if (lang == "cs")
194 lang = "csharp";
195 else if (lang == "js")
196 lang = "javascript";
197 else if (lang == "ts")
198 lang = "typescript";
199 else if (lang == "fox")
200 lang = "foxpro";
201 else if (lang == "txt")
202 lang = "text"
203
204
205 var html = hudText.replace("{{language}}", lang)
206 .replace("{{copyIconClass}}",options.copyIconClass)
207 .trim();
208
209 // insert the Hud panel
210 var $newHud = document.createElement("div");
211 $newHud.innerHTML = html;
212 $newHud = $newHud.querySelector(".code-badge");
213
214 // make <pre> tag position:relative so positioning keeps pinned right
215 // even with scroll bar scrolled
216 var pre = el.parentElement;
217 pre.classList.add("code-badge-pre")
218
219 if(options.copyIconContent)
220 $newHud.querySelector(".code-badge-copy-icon").innerText = options.copyIconContent;
221
222 pre.insertBefore($newHud, el);
223 }
224
225 var $content = document.querySelector(options.contentSelector);
226
227 // single copy click handler
228 $content.addEventListener("click",
229 function (e) {
230 var $clicked = e.srcElement;
231 if ($clicked.classList.contains("code-badge-copy-icon")) {
232 e.preventDefault();
233 e.cancelBubble = true;
234 copyCodeToClipboard(e);
235 }
236 return false;
237 });
238 }
239
240
241 function copyCodeToClipboard(e) {
242 // walk back up to <pre> tag
243 var $origCode = e.srcElement.parentElement.parentElement.parentElement;
244
245 // select the <code> tag and grab text
246 var $code = $origCode.querySelector("pre>code");
247 var text = $code.textContent || $code.innerText;
248
249 if (options.onBeforeCodeCopied)
250 text = options.onBeforeCodeCopied(text, $code);
251
252 // Create a textblock and assign the text and add to document
253 var el = document.createElement('textarea');
254 el.value = text.trim();
255 document.body.appendChild(el);
256 el.style.display = "block";
257
258 // select the entire textblock
259 if (window.document.documentMode)
260 el.setSelectionRange(0, el.value.length);
261 else
262 el.select();
263
264 // copy to clipboard
265 document.execCommand('copy');
266
267 // clean up element
268 document.body.removeChild(el);
269
270 // show the check icon (copied) briefly
271 swapIcons($origCode);
272 }
273
274 function swapIcons($code) {
275 var copyIcons = options.copyIconClass.split(' ');
276 var checkIcons = options.checkIconClass.split(' ');
277
278 var $fa = $code.querySelector(".code-badge-copy-icon");
279 $fa.innerText = options.checkIconContent;
280
281 for (var i = 0; i < copyIcons.length; i++)
282 $fa.classList.remove(copyIcons[i]);
283
284 for (var i = 0; i < checkIcons.length; i++)
285 $fa.classList.add(checkIcons[i]);
286
287
288 setTimeout(function () {
289 $fa.innerText = options.copyIconContent;
290
291 for (var i = 0; i < checkIcons.length; i++)
292 $fa.classList.remove(checkIcons[i]);
293 for (var i = 0; i < copyIcons.length; i++)
294 $fa.classList.add(copyIcons[i]);
295 }, 2000);
296 }
297
298 function getTemplate() {
299 var stringArray =
300 [
301 "<style>",
302 "@media print {",
303 " .code-badge { display: none; }",
304 "}",
305 " .code-badge-pre {",
306 " position: relative;",
307 " }",
308 " .code-badge {",
309 " display: flex;",
310 " flex-direction: row;",
311 " white-space: normal;",
312 " background: transparent;",
313 " background: #333;",
314 " color: white;",
315 " font-size: 0.875em;",
316 " opacity: 0.5;",
317 " transition: opacity linear 0.5s;",
318 " border-radius: 0 0 0 7px;",
319 " padding: 5px 8px 5px 8px;",
320 " position: absolute;",
321 " right: 0;",
322 " top: 0;",
323 " }",
324 " .code-badge.active {",
325 " opacity: 0.8;",
326 " }",
327 "",
328 " .code-badge:hover {",
329 " opacity: .95;",
330 " }",
331 "",
332 " .code-badge a,",
333 " .code-badge a:hover {",
334 " text-decoration: none;",
335 " }",
336 "",
337 " .code-badge-language {",
338 " margin-right: 10px;",
339 " font-weight: 600;",
340 " color: goldenrod;",
341 " }",
342 " .code-badge-copy-icon {",
343 " font-size: 1.2em;",
344 " cursor: pointer;",
345 " padding: 0 7px;",
346 " margin-top:2;",
347 " }",
348 " .fa.text-success:{ color: limegreen !important }",
349 "</style>",
350 "<div id=\"CodeBadgeTemplate\" style=\"display:none\">",
351 " <div class=\"code-badge\">",
352 " <div class=\"code-badge-language\" >{{language}}</div>",
353 " <div title=\"Copy to clipboard\">",
354 " <i class=\"{{copyIconClass}} code-badge-copy-icon\"></i></i></a>",
355 " </div>",
356 " </div>",
357 "</div>"
358 ];
359
360 var t = "";
361 for (var i = 0; i < stringArray.length; i++)
362 t += stringArray[i] + "\n";
363
364 return t;
365 }
366
367 initialize(opt);
368}
369
370
371// global reference Window
372window.highlightJsBadge = highlightJsBadge;
373
374
375// module export
376if (window.module && window.module.exports)
377 window.module.exports.highlightJsBadge = highlightJsBadge;
378
379if (highlightJsBadgeAutoLoad)
380 highlightJsBadge();
381
382}));
383
384
385// You can embed the following into your HTML document
386// to provide your own custom styling.
387
388/*
389<style>
390 "@media print {
391 .code-badge { display: none; }
392 }
393 .code-badge-pre {
394 position: relative;
395 }
396 .code-badge {
397 display: flex;
398 flex-direction: row;
399 white-space: normal;
400 background: transparent;
401 background: #333;
402 color: white;
403 font-size: 0.875em;
404 opacity: 0.5;
405 border-radius: 0 0 0 7px;
406 padding: 5px 8px 5px 8px;
407 position: absolute;
408 right: 0;
409 top: 0;
410 }
411 .code-badge.active {
412 opacity: 0.8;
413 }
414 .code-badge:hover {
415 opacity: .95;
416 }
417 .code-badge a,
418 .code-badge a:hover {
419 text-decoration: none;
420 }
421
422 .code-badge-language {
423 margin-right: 10px;
424 font-weight: 600;
425 color: goldenrod;
426 }
427 .code-badge-copy-icon {
428 font-size: 1.2em;
429 cursor: pointer;
430 padding: 0 7px;
431 margin-top:2;
432 }
433 .fa.text-success:{ color: limegreen !important}
434</style>
435<div id="CodeBadgeTemplate" style="display:none">
436 <div class="code-badge">
437 <div class="code-badge-language">{{language}}</div>
438 <div title="Copy to clipboard">
439 <i class="{{copyIconClass}} code-badge-copy-icon"></i>
440 </div>
441 </div>
442</div>
443*/