1 | ;
|
2 | /*
|
3 | ----------------------------------------
|
4 | highlightJs Badge
|
5 | ----------------------------------------
|
6 |
|
7 | A copy code and language display badge
|
8 | for the highlightJs Syntax highlighter.
|
9 |
|
10 | by Rick Strahl, 2019-2020
|
11 | License: MIT
|
12 |
|
13 | Make sure this script is loaded last in your
|
14 | script loading.
|
15 |
|
16 | Usage:
|
17 | ------
|
18 | Load `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 |
|
49 | The script contains the template and CSS so nothing
|
50 | else is needed to run it.
|
51 |
|
52 | Customization:
|
53 | --------------
|
54 | This code automatically embeds styling and the template.
|
55 |
|
56 | If you want to customize you can either create a template
|
57 | in your HTML **using the code at the end of this file**.
|
58 |
|
59 | Alternately you can customize the `getTemplate()` function
|
60 | that renders the code from a string and keep it self contained
|
61 | within this script.
|
62 |
|
63 | Requirements:
|
64 | -------------
|
65 | Uses 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 |
|
93 | if (typeof highlightJsBadgeAutoLoad !== 'boolean')
|
94 | var highlightJsBadgeAutoLoad = false;
|
95 |
|
96 | function 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
|
372 | window.highlightJsBadge = highlightJsBadge;
|
373 |
|
374 |
|
375 | // module export
|
376 | if (window.module && window.module.exports)
|
377 | window.module.exports.highlightJsBadge = highlightJsBadge;
|
378 |
|
379 | if (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 | */
|