highlight-it
Version:
A lightweight syntax highlighting library with themes, line numbers, and copy functionality
1,061 lines (918 loc) • 124 kB
JavaScript
/*!
* HighlightIt v0.2.7
* https://github.com/TN3W/highlight-it
* (c) 2025 TN3W
* Released under the Apache 2.0 License
*/
(function (global) {
const STYLES_CSS =
':root{--hl-background:#1e1e1e;--hl-border:#2d2d2d;--hl-header-bg:#2d2d2d;--hl-text:#d4d4d4;--hl-badge-bg:#3d3d3d;--hl-hover-bg:#3d3d3d;--hl-copied-color:#4ec9b0;--hl-keyword:#569cd6;--hl-string:#ce9178;--hl-comment:#6a9955;--hl-function:#dcdcaa;--hl-number:#b5cea8;--hl-class:#4ec9b0;--hl-params:#9cdcfe;--hl-built-in:#4ec9b0;--hl-tag:#569cd6;--hl-attr:#9cdcfe;--hl-variable:#9cdcfe;--hl-property:#9cdcfe;--hl-operator:#d4d4d4;--hl-punctuation:#d4d4d4;--hl-regexp:#d16969;--hl-doctype:grey;--hl-meta:grey;--hl-name:#569cd6;--hl-selector:#d7ba7d;--hl-attr-value:#ce9178;--hl-constant:#4fc1ff;--hl-symbol:#b5cea8;--hl-important:#569cd6;--hl-deleted:#ce9178;--hl-inserted:#b5cea8;--hl-type:#4ec9b0;--hl-literal:#569cd6;--hl-link:#9cdcfe;--hl-title-function:#dcdcaa;--hl-title-class:#4ec9b0;--hl-title-namespace:#4ec9b0;--hl-text-rgb:212,212,212}.highlightit-theme-light{--hl-background:#fff;--hl-border:#ccc;--hl-header-bg:#e0e0e0;--hl-text:#333;--hl-badge-bg:silver;--hl-hover-bg:#d0d0d0;--hl-copied-color:#0b8043;--hl-keyword:#00f;--hl-string:#a31515;--hl-comment:green;--hl-function:#795e26;--hl-number:#098658;--hl-class:#267f99;--hl-params:#001080;--hl-built-in:#0070c1;--hl-tag:maroon;--hl-attr:red;--hl-variable:#001080;--hl-property:#001080;--hl-operator:#000;--hl-punctuation:#000;--hl-regexp:#811f3f;--hl-doctype:grey;--hl-meta:grey;--hl-name:maroon;--hl-selector:maroon;--hl-attr-value:#00f;--hl-constant:#0070c1;--hl-symbol:#000;--hl-important:#00f;--hl-deleted:#a31515;--hl-inserted:#098658;--hl-type:#267f99;--hl-literal:#00f;--hl-link:#00f;--hl-title-function:#795e26;--hl-title-class:#267f99;--hl-title-namespace:#267f99;--hl-text-rgb:51,51,51}@media (prefers-color-scheme:light){:root.highlightit-theme-auto{--hl-background:#fff;--hl-border:#ccc;--hl-header-bg:#e0e0e0;--hl-text:#333;--hl-badge-bg:silver;--hl-hover-bg:#d0d0d0;--hl-copied-color:#0b8043;--hl-keyword:#00f;--hl-string:#a31515;--hl-comment:green;--hl-function:#795e26;--hl-number:#098658;--hl-class:#267f99;--hl-params:#001080;--hl-built-in:#0070c1;--hl-tag:maroon;--hl-attr:red;--hl-variable:#001080;--hl-property:#001080;--hl-operator:#000;--hl-punctuation:#000;--hl-regexp:#811f3f;--hl-doctype:grey;--hl-meta:grey;--hl-name:maroon;--hl-selector:maroon;--hl-attr-value:#00f;--hl-constant:#0070c1;--hl-symbol:#000;--hl-important:#00f;--hl-deleted:#a31515;--hl-inserted:#098658;--hl-type:#267f99;--hl-literal:#00f;--hl-link:#00f;--hl-title-function:#795e26;--hl-title-class:#267f99;--hl-title-namespace:#267f99;--hl-text-rgb:51,51,51}}.highlightit-container{border:1px solid var(--hl-border);border-radius:6px;overflow:hidden;position:relative}.highlightit-container code[data-theme=light],.highlightit-container pre[data-theme=light],.highlightit-container[data-theme=light]{--hl-background:#fff!important;--hl-border:#ccc!important;--hl-header-bg:#e0e0e0!important;--hl-text:#333!important;--hl-badge-bg:silver!important;--hl-hover-bg:#d0d0d0!important;--hl-copied-color:#0b8043!important;--hl-keyword:#00f!important;--hl-string:#a31515!important;--hl-comment:green!important;--hl-function:#795e26!important;--hl-number:#098658!important;--hl-class:#267f99!important;--hl-params:#001080!important;--hl-built-in:#0070c1!important;--hl-tag:maroon!important;--hl-attr:red!important;--hl-variable:#001080!important;--hl-property:#001080!important;--hl-operator:#000!important;--hl-punctuation:#000!important;--hl-regexp:#811f3f!important;--hl-doctype:grey!important;--hl-meta:grey!important;--hl-name:maroon!important;--hl-selector:maroon!important;--hl-attr-value:#00f!important;--hl-constant:#0070c1!important;--hl-symbol:#000!important;--hl-important:#00f!important;--hl-deleted:#a31515!important;--hl-inserted:#098658!important;--hl-type:#267f99!important;--hl-literal:#00f!important;--hl-link:#00f!important;--hl-title-function:#795e26!important;--hl-title-class:#267f99!important;--hl-title-namespace:#267f99!important}.highlightit-container code[data-theme=dark],.highlightit-container pre[data-theme=dark],.highlightit-container[data-theme=dark]{--hl-background:#1e1e1e!important;--hl-border:#2d2d2d!important;--hl-header-bg:#2d2d2d!important;--hl-text:#d4d4d4!important;--hl-badge-bg:#3d3d3d!important;--hl-hover-bg:#3d3d3d!important;--hl-copied-color:#4ec9b0!important;--hl-keyword:#569cd6!important;--hl-string:#ce9178!important;--hl-comment:#6a9955!important;--hl-function:#dcdcaa!important;--hl-number:#b5cea8!important;--hl-class:#4ec9b0!important;--hl-params:#9cdcfe!important;--hl-built-in:#4ec9b0!important;--hl-tag:#569cd6!important;--hl-attr:#9cdcfe!important;--hl-variable:#9cdcfe!important;--hl-property:#9cdcfe!important;--hl-operator:#d4d4d4!important;--hl-punctuation:#d4d4d4!important;--hl-regexp:#d16969!important;--hl-doctype:grey!important;--hl-meta:grey!important;--hl-name:#569cd6!important;--hl-selector:#d7ba7d!important;--hl-attr-value:#ce9178!important;--hl-constant:#4fc1ff!important;--hl-symbol:#b5cea8!important;--hl-important:#569cd6!important;--hl-deleted:#ce9178!important;--hl-inserted:#b5cea8!important;--hl-type:#4ec9b0!important;--hl-literal:#569cd6!important;--hl-link:#9cdcfe!important;--hl-title-function:#dcdcaa!important;--hl-title-class:#4ec9b0!important;--hl-title-namespace:#4ec9b0!important}.highlightit-header{align-items:center;background:var(--hl-header-bg);color:var(--hl-text);display:flex;font-size:14px;font-weight:600;justify-content:space-between;padding:8px 15px}.highlightit-language{background:var(--hl-badge-bg);border-radius:4px;color:var(--hl-text);font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;font-size:12px;padding:2px 8px}.highlightit-button{align-items:center;background:transparent;border:none;border-radius:4px;color:var(--hl-text);cursor:pointer;display:flex;height:28px;justify-content:center;padding:4px 6px;transition:all .2s ease}.highlightit-button:hover{background:var(--hl-hover-bg)}.highlightit-button.copied{color:var(--hl-copied-color)}.highlightit-check-icon,.highlightit-copy-icon,.highlightit-download-icon,.highlightit-share-icon{height:16px;vertical-align:middle;width:16px}.highlightit-original,[data-with-reload][style*="display: none"]{border:0!important;height:0!important;margin:0!important;opacity:0!important;overflow:hidden!important;padding:0!important;pointer-events:none!important;position:absolute!important;visibility:hidden!important;width:0!important;z-index:-9999!important;clip:rect(1px,1px,1px,1px)!important;clip-path:inset(50%)!important}.highlightit-floating-buttons{display:flex;gap:4px;opacity:0;position:absolute;right:8px;top:8px;transition:opacity .2s ease;z-index:10}.highlightit-button.highlightit-floating{background-color:rgba(45,45,45,.8);transition:background-color .2s ease,transform .2s ease}.highlightit-container[data-theme=light] .highlightit-button.highlightit-floating,.highlightit-theme-light .highlightit-button.highlightit-floating,:root.highlightit-theme-auto.system-light-theme .highlightit-button.highlightit-floating{background-color:hsla(0,0%,78%,.8)}.highlightit-no-header:hover .highlightit-floating-buttons,:root.highlightit-touch-device .highlightit-floating-buttons{opacity:1}.highlightit-button.highlightit-floating:hover{background-color:rgba(65,65,65,.9);transform:scale(1.05)}.highlightit-container[data-theme=light] .highlightit-button.highlightit-floating:hover,.highlightit-theme-light .highlightit-button.highlightit-floating:hover,:root.highlightit-theme-auto.system-light-theme .highlightit-button.highlightit-floating:hover{background-color:hsla(0,0%,67%,.9);transform:scale(1.05)}.highlightit-container pre{background-color:var(--hl-background)!important;border-radius:0 0 6px 6px;margin:0;overflow:auto;padding:16px}.highlightit-container pre::-webkit-scrollbar{height:10px;width:10px}.highlightit-theme-dark .highlightit-container pre::-webkit-scrollbar-track,:root:not(.highlightit-theme-light) .highlightit-container pre::-webkit-scrollbar-track{background:#2d2d2d;border-radius:4px}.highlightit-theme-dark .highlightit-container pre::-webkit-scrollbar-thumb,:root:not(.highlightit-theme-light) .highlightit-container pre::-webkit-scrollbar-thumb{background:#3d3d3d;border-radius:4px}.highlightit-theme-dark .highlightit-container pre::-webkit-scrollbar-thumb:hover,:root:not(.highlightit-theme-light) .highlightit-container pre::-webkit-scrollbar-thumb:hover{background:#4d4d4d}:root.highlightit-theme-light .highlightit-container pre::-webkit-scrollbar-track{background:#e0e0e0;border-radius:4px}:root.highlightit-theme-light .highlightit-container pre::-webkit-scrollbar-thumb{background:silver;border-radius:4px}.highlightit-container[data-theme=light] pre::-webkit-scrollbar-thumb:hover,:root.highlightit-theme-light .highlightit-container pre::-webkit-scrollbar-thumb:hover{background:#a0a0a0}.highlightit-theme-dark .highlightit-container pre,:root:not(.highlightit-theme-light) .highlightit-container pre{scrollbar-color:#3d3d3d #2d2d2d;scrollbar-width:thin}:root.highlightit-theme-light .highlightit-container pre{scrollbar-color:silver #e0e0e0;scrollbar-width:thin}html .highlightit-container[data-theme=dark] pre::-webkit-scrollbar-track{background:#2d2d2d!important;border-radius:4px}html .highlightit-container[data-theme=dark] pre::-webkit-scrollbar-thumb{background:#3d3d3d!important;border-radius:4px}html .highlightit-container[data-theme=dark] pre::-webkit-scrollbar-thumb:hover{background:#4d4d4d!important}html .highlightit-container[data-theme=dark] pre{scrollbar-color:#3d3d3d #2d2d2d!important;scrollbar-width:thin!important}html .highlightit-container[data-theme=light] pre::-webkit-scrollbar-track{background:#e0e0e0!important;border-radius:4px}html .highlightit-container[data-theme=light] pre::-webkit-scrollbar-thumb{background:silver!important;border-radius:4px}html .highlightit-container[data-theme=light] pre::-webkit-scrollbar-thumb:hover{background:#a0a0a0!important}html .highlightit-container[data-theme=light] pre{scrollbar-color:silver #e0e0e0!important;scrollbar-width:thin!important}.highlightit-no-header pre{border-radius:6px}.highlightit-container code{color:var(--hl-text);font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;font-size:14px;line-height:1.5;margin-left:0;padding:0!important;tab-size:4;-moz-tab-size:4;white-space:pre}.highlightit-has-line-numbers code{margin-left:1em}.highlightit-anchor-highlight{animation:highlightit-anchor-pulse 2s ease-in-out}@keyframes highlightit-anchor-pulse{0%{box-shadow:0 0 0 0 rgba(var(--hl-text-rgb),.2)}70%{box-shadow:0 0 0 10px rgba(var(--hl-text-rgb),0)}to{box-shadow:0 0 0 0 rgba(var(--hl-text-rgb),0)}}.hljs-keyword{color:var(--hl-keyword)}.hljs-string{color:var(--hl-string)}.hljs-comment{color:var(--hl-comment)}.hljs-function{color:var(--hl-function)}.hljs-number{color:var(--hl-number)}.hljs-class,.hljs-title{color:var(--hl-class)}.hljs-params{color:var(--hl-params)}.hljs-built_in{color:var(--hl-built-in)}.hljs-tag{color:var(--hl-tag)}.hljs-name{color:var(--hl-name)}.hljs-attribute{color:var(--hl-attr)}.hljs-attr-value{color:var(--hl-attr-value)}.hljs-doctype{font-style:italic}.hljs-doctag,.hljs-doctype{color:var(--hl-doctype)}.hljs-variable{color:var(--hl-variable)}.hljs-property{color:var(--hl-property)}.hljs-operator{color:var(--hl-operator)}.hljs-punctuation{color:var(--hl-punctuation)}.hljs-regexp{color:var(--hl-regexp)}.hljs-meta{color:var(--hl-meta);font-style:italic}.hljs-selector{color:var(--hl-selector)}.hljs-constant{color:var(--hl-constant);font-weight:700}.hljs-symbol{color:var(--hl-symbol)}.hljs-important{color:var(--hl-important);font-weight:700}.hljs-type{color:var(--hl-type)}.hljs-literal{color:var(--hl-literal)}.hljs-link{color:var(--hl-link);text-decoration:underline}.hljs-deleted{background-color:rgba(255,0,0,.1);color:var(--hl-deleted)}.hljs-inserted{background-color:rgba(0,255,0,.1);color:var(--hl-inserted)}.hljs-title.function_{color:var(--hl-title-function)}.hljs-title.class_{color:var(--hl-title-class)}.hljs-title.namespace{color:var(--hl-title-namespace)}.hljs-selector-tag{color:var(--hl-tag)}.hljs-selector-class,.hljs-selector-id{color:var(--hl-selector)}.hljs-selector-attr{color:var(--hl-attr)}.hljs-selector-pseudo{color:var(--hl-selector);font-style:italic}.hljs-template-variable{font-style:italic}.hljs-subst,.hljs-template-variable{color:var(--hl-variable)}.hljs-section{color:var(--hl-keyword);font-weight:700}.hljs-bullet{color:var(--hl-operator)}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}.hljs-quote{color:var(--hl-string);font-style:italic}.hljs-code{background-color:rgba(0,0,0,.05);border-radius:3px;color:var(--hl-class);padding:.1em .2em}.hljs-keyword.operator{color:var(--hl-operator)}.hljs-attr{color:var(--hl-attr)}.hljs-variable.language_{color:var(--hl-params);font-style:italic}.hljs-built_in.shell{color:var(--hl-keyword)}.hljs-symbol.instance{color:var(--hl-symbol)}.hljs-symbol.class_{color:var(--hl-class)}.language-html .hljs-tag,.language-xml .hljs-tag{color:var(--hl-tag)}.language-css .hljs-property{color:var(--hl-property)}.language-javascript .hljs-keyword,.language-js .hljs-keyword{color:var(--hl-keyword)}.language-ts .hljs-type,.language-typescript .hljs-type{color:var(--hl-type)}.language-php .hljs-variable{color:var(--hl-variable)}.language-diff .hljs-deletion{background-color:rgba(255,0,0,.1);color:var(--hl-deleted)}.language-diff .hljs-addition{background-color:rgba(0,255,0,.1);color:var(--hl-inserted)}.highlightit-has-line-numbers{display:flex;isolation:isolate;overflow:hidden;position:relative}.highlightit-line-numbers{border-right:1px solid var(--hl-border);color:var(--hl-text);display:flex;flex-direction:column;font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;font-size:14px;line-height:1.5;opacity:.7;padding-right:.5em;position:relative;text-align:right;user-select:none;-webkit-user-select:none;-moz-user-select:none}.highlightit-line-numbers:after{background-color:var(--hl-header-bg);bottom:0;content:"";left:0;opacity:.05;pointer-events:none;position:absolute;right:0;top:0;z-index:-1}.highlightit-has-line-numbers pre{border-radius:0!important;flex:1;margin:0;padding-left:.5em;position:relative}.highlightit-container[data-theme=light] .highlightit-line-numbers,.highlightit-theme-light .highlightit-line-numbers,:root.highlightit-theme-auto.system-light-theme .highlightit-line-numbers{border-right-color:#ccc!important}.highlightit-visually-hidden{height:1px!important;margin:-1px!important;overflow:hidden!important;padding:0!important;position:absolute!important;width:1px!important;clip:rect(0,0,0,0)!important;border:0!important;opacity:.01!important;pointer-events:none!important;white-space:nowrap!important}.highlightit-line-number-container,.highlightit-with-lines .highlightit-line-number{align-items:center;display:flex;height:1.5em;justify-content:flex-end;margin-bottom:0;position:relative}.highlightit-with-lines .highlightit-line-number{padding-right:1em;transition:opacity .2s ease;z-index:1}.highlightit-line-number-shareable{cursor:pointer}.highlightit-container[data-with-share] .highlightit-line-number-container:hover .highlightit-line-number,[data-with-share] .highlightit-line-number-container:hover .highlightit-line-number{opacity:0}.highlightit-line-share{align-items:center;background:transparent;border:none;border-radius:3px;color:var(--hl-text);cursor:pointer;display:flex;height:auto;justify-content:center;margin-right:1em;opacity:0;padding:4px;position:absolute;right:0;top:50%;transform:translateY(-50%);transition:opacity .2s ease;width:auto;z-index:2}.highlightit-line-number-container:hover .highlightit-line-share{opacity:.7}.highlightit-line-share:hover{background:var(--hl-hover-bg);opacity:1!important}.highlightit-line-share.copied{background:var(--hl-hover-bg);color:var(--hl-copied-color);opacity:1}.highlightit-line-number-container:has(.highlightit-line-share.copied) .highlightit-line-number{opacity:.3}.highlightit-line-number-container.has-copied-button .highlightit-line-number{opacity:.3}.highlightit-line-share svg{height:14px;width:14px}.highlightit-line-highlight{position:relative}.highlightit-line-highlight-overlay{animation:highlightit-line-pulse 2s ease-in-out;background-color:rgba(var(--hl-text-rgb),.1);bottom:0;left:0;pointer-events:none;position:absolute;right:0;top:0;z-index:1}@keyframes highlightit-line-pulse{0%{background-color:rgba(var(--hl-text-rgb),.2)}70%{background-color:rgba(var(--hl-text-rgb),.1)}to{background-color:rgba(var(--hl-text-rgb),.1)}}';
function injectCSS(css) {
const style = document.createElement('style');
style.textContent = css;
document.head.appendChild(style);
}
// Begin bundled module: polyfills.js
/**
* Polyfills and browser compatibility helpers
*/
const polyfills = {
supports: {
requestAnimationFrame: typeof requestAnimationFrame === 'function',
ResizeObserver: typeof ResizeObserver === 'function',
MutationObserver: typeof MutationObserver === 'function',
classList:
'classList' in document.documentElement &&
typeof document.documentElement.classList !== 'undefined',
dataset:
'dataset' in document.documentElement &&
typeof document.documentElement.dataset !== 'undefined',
clipboard: 'clipboard' in navigator,
clipboardItem: typeof ClipboardItem !== 'undefined',
touch:
'ontouchstart' in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0,
getBoundingClientRect: 'getBoundingClientRect' in document.documentElement,
animation: 'Animation' in window && 'animate' in document.documentElement,
cssHas: (function () {
try {
document.querySelector(':has(*)');
return true;
} catch {
return false;
}
})(),
download: typeof document.createElement('a').download !== 'undefined',
blob: typeof Blob !== 'undefined',
URL: typeof URL !== 'undefined' && typeof URL.createObjectURL === 'function',
TextEncoder: typeof TextEncoder !== 'undefined',
crypto: typeof crypto !== 'undefined' && typeof crypto.subtle !== 'undefined',
BigInt: typeof BigInt !== 'undefined',
padStart: typeof String.prototype.padStart === 'function',
},
initStringPadding: function () {
if (!this.supports.padStart) {
String.prototype.padStart = function padStart(targetLength, padString) {
targetLength = targetLength >> 0;
padString = String(typeof padString !== 'undefined' ? padString : ' ');
if (this.length >= targetLength) {
return String(this);
} else {
targetLength = targetLength - this.length;
if (targetLength > padString.length) {
padString += padString.repeat(targetLength / padString.length);
}
return padString.slice(0, targetLength) + String(this);
}
};
}
},
objectEntries: function (obj) {
if (typeof Object.entries === 'function') {
return Object.entries(obj);
}
const entries = [];
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
entries.push([key, obj[key]]);
}
}
return entries;
},
init: function () {
this.initStringPadding();
},
requestAnimationFrame: (function () {
return (
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function (callback) {
return window.setTimeout(callback, 1000 / 60);
}
);
})(),
TextEncoder: (function () {
if (typeof TextEncoder !== 'undefined') return TextEncoder;
return class TextEncoder {
constructor() {
this.encoding = 'utf-8';
}
encode(str) {
if (str === null || str === undefined) return new Uint8Array();
const string = String(str);
let resPos = -1;
const len = string.length;
let byteSize = 0;
for (let i = 0; i < len; i++) {
const code = string.charCodeAt(i);
if (code < 0x80) {
byteSize += 1;
} else if (code < 0x800) {
byteSize += 2;
} else if (code < 0xd800 || code >= 0xe000) {
byteSize += 3;
} else {
i++;
byteSize += 4;
}
}
const buffer = new Uint8Array(byteSize);
for (let i = 0; i < len; i++) {
let codePoint = string.charCodeAt(i);
if (codePoint >= 0xd800 && codePoint < 0xe000) {
if (++i >= len) {
break;
}
const second = string.charCodeAt(i);
if (second >= 0xdc00 && second < 0xe000) {
codePoint = 0x10000 + ((codePoint - 0xd800) << 10) + (second - 0xdc00);
} else {
i--;
}
}
if (codePoint < 0x80) {
buffer[++resPos] = codePoint;
} else if (codePoint < 0x800) {
buffer[++resPos] = 0xc0 | (codePoint >> 6);
buffer[++resPos] = 0x80 | (codePoint & 0x3f);
} else if (codePoint < 0x10000) {
buffer[++resPos] = 0xe0 | (codePoint >> 12);
buffer[++resPos] = 0x80 | ((codePoint >> 6) & 0x3f);
buffer[++resPos] = 0x80 | (codePoint & 0x3f);
} else {
buffer[++resPos] = 0xf0 | (codePoint >> 18);
buffer[++resPos] = 0x80 | ((codePoint >> 12) & 0x3f);
buffer[++resPos] = 0x80 | ((codePoint >> 6) & 0x3f);
buffer[++resPos] = 0x80 | (codePoint & 0x3f);
}
}
return buffer;
}
};
})(),
simpleHash: function (str) {
if (!str) return 'h-000000000000';
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = (hash << 5) - hash + char;
hash = hash & hash;
}
const hashStr = Math.abs(hash).toString(16).padStart(12, '0');
return 'h-' + hashStr.substring(0, 12);
},
BigIntPolyfill: (function () {
if (typeof BigInt !== 'undefined') return null;
return class BigIntSimulation {
constructor(value) {
this.value = String(value);
}
static from(value) {
return new BigIntSimulation(value);
}
divide(other) {
let otherVal = typeof other === 'object' ? parseInt(other.value) : parseInt(other);
let val = parseInt(this.value);
let result = Math.floor(val / otherVal);
return new BigIntSimulation(result);
}
modulo(other) {
let otherVal = typeof other === 'object' ? parseInt(other.value) : parseInt(other);
let val = parseInt(this.value);
let result = val % otherVal;
return result;
}
shiftLeft(bits) {
let val = parseInt(this.value);
let result = val << bits;
return new BigIntSimulation(result);
}
or(other) {
let otherVal = typeof other === 'object' ? parseInt(other.value) : parseInt(other);
let val = parseInt(this.value);
let result = val | otherVal;
return new BigIntSimulation(result);
}
toString() {
return this.value;
}
valueOf() {
return parseInt(this.value);
}
};
})(),
/**
* MutationObserver polyfill for older browsers
* This is a simplified version that only supports the options needed by HighlightIt
*/
MutationObserver: (function () {
if (typeof MutationObserver !== 'undefined') return MutationObserver;
return class MutationObserverPolyfill {
constructor(callback) {
this.callback = callback;
this.observed = new Map();
this.timeout = null;
this.pollInterval = 100;
}
observe(target, options) {
if (!target || !options) return;
const snapshot = {
element: target,
options: options,
attributes: this._getAttributes(target),
characterData: options.characterData ? target.textContent : null,
childList: options.childList ? this._getChildList(target) : null,
};
this.observed.set(target, snapshot);
if (!this.timeout) {
this._startPolling();
}
}
disconnect() {
if (this.timeout) {
clearTimeout(this.timeout);
this.timeout = null;
}
this.observed.clear();
}
takeRecords() {
return [];
}
_startPolling() {
this.timeout = setTimeout(() => {
const mutations = [];
this.observed.forEach((snapshot, target) => {
if (!document.contains(target)) {
this.observed.delete(target);
return;
}
if (snapshot.options.attributes) {
const currentAttributes = this._getAttributes(target);
const oldAttributes = snapshot.attributes;
for (const entry of polyfills.objectEntries(oldAttributes)) {
const name = entry[0];
const value = entry[1];
if (!(name in currentAttributes) || currentAttributes[name] !== value) {
mutations.push({
type: 'attributes',
target: target,
attributeName: name,
oldValue: snapshot.options.attributeOldValue ? value : null,
});
}
}
for (const name in currentAttributes) {
if (!(name in oldAttributes)) {
mutations.push({
type: 'attributes',
target: target,
attributeName: name,
oldValue: null,
});
}
}
snapshot.attributes = currentAttributes;
}
if (snapshot.options.characterData) {
const currentText = target.textContent;
if (currentText !== snapshot.characterData) {
mutations.push({
type: 'characterData',
target: target,
oldValue: snapshot.options.characterDataOldValue
? snapshot.characterData
: null,
});
snapshot.characterData = currentText;
}
}
if (snapshot.options.childList) {
const currentChildren = this._getChildList(target);
const oldChildren = snapshot.childList;
if (currentChildren.length !== oldChildren.length) {
mutations.push({
type: 'childList',
target: target,
addedNodes: [],
removedNodes: [],
});
}
snapshot.childList = currentChildren;
}
});
if (mutations.length > 0) {
this.callback(mutations);
}
if (this.observed.size > 0) {
this._startPolling();
} else {
this.timeout = null;
}
}, this.pollInterval);
}
_getAttributes(element) {
const result = {};
const attributes = element.attributes;
if (attributes) {
for (let i = 0; i < attributes.length; i++) {
const attr = attributes[i];
result[attr.name] = attr.value;
}
}
return result;
}
_getChildList(element) {
return Array.from(element.childNodes);
}
};
})(),
ResizeObserver: (function () {
if (typeof ResizeObserver === 'function') return ResizeObserver;
return class ResizeObserver {
constructor(callback) {
this.callback = callback;
this.observedElements = new Set();
this.observer = new MutationObserver(this.handleMutation.bind(this));
}
observe(element) {
if (this.observedElements.has(element)) return;
this.observedElements.add(element);
this.observer.observe(element, {
attributes: true,
attributeFilter: ['style', 'class'],
});
this.checkSize(element);
}
unobserve(element) {
this.observedElements.delete(element);
this.observer.unobserve(element);
}
disconnect() {
this.observer.disconnect();
this.observedElements.clear();
}
handleMutation(mutations) {
mutations.forEach((mutation) => {
if (mutation.target && this.observedElements.has(mutation.target)) {
this.checkSize(mutation.target);
}
});
}
checkSize(element) {
const size = {
width: element.offsetWidth,
height: element.offsetHeight,
};
this.callback([{ target: element, contentRect: size }]);
}
};
})(),
getBoundingClientRect: function (el) {
if (!el) return { top: 0, right: 0, bottom: 0, left: 0, width: 0, height: 0 };
if (this.supports.getBoundingClientRect) {
try {
return el.getBoundingClientRect();
} catch {
//
}
}
const rect = {
top: el.offsetTop,
left: el.offsetLeft,
width: el.offsetWidth,
height: el.offsetHeight,
};
rect.right = rect.left + rect.width;
rect.bottom = rect.top + rect.height;
let parent = el.offsetParent;
while (parent) {
rect.top += parent.offsetTop;
rect.left += parent.offsetLeft;
parent = parent.offsetParent;
}
rect.top -= window.scrollY || document.documentElement.scrollTop || 0;
rect.left -= window.scrollX || document.documentElement.scrollLeft || 0;
return rect;
},
classList: {
add: function (element, className) {
if (!element) return;
try {
if (this.supports.classList) {
element.classList.add(className);
} else {
const classes = (element.className || '').split(' ');
if (!classes.includes(className)) {
classes.push(className);
}
element.className = classes.join(' ');
}
} catch {
const classes = (element.className || '').split(' ');
if (!classes.includes(className)) {
classes.push(className);
}
element.className = classes.join(' ');
}
},
remove: function (element, className) {
if (!element) return;
try {
if (this.supports.classList) {
element.classList.remove(className);
} else {
element.className = (element.className || '')
.split(' ')
.filter((c) => c !== className)
.join(' ');
}
} catch {
element.className = (element.className || '')
.split(' ')
.filter((c) => c !== className)
.join(' ');
}
},
contains: function (element, className) {
if (!element) return false;
try {
if (this.supports.classList) {
return element.classList.contains(className);
}
return (element.className || '').split(' ').includes(className);
} catch {
return (element.className || '').split(' ').includes(className);
}
},
},
dataset: {
get: function (element, key) {
if (!element) return undefined;
try {
if (this.supports.dataset) {
return element.dataset[key];
}
return element.getAttribute(`data-${key}`);
} catch {
return element.getAttribute(`data-${key}`);
}
},
set: function (element, key, value) {
if (!element) return;
try {
if (this.supports.dataset) {
element.dataset[key] = value;
} else {
element.setAttribute(`data-${key}`, value);
}
} catch {
element.setAttribute(`data-${key}`, value);
}
},
},
copyToClipboard: async function (text) {
if (this.supports.clipboard) {
try {
await navigator.clipboard.writeText(text);
return true;
} catch {
//
}
}
if (this.supports.clipboard && this.supports.clipboardItem) {
try {
const clipboardItem = new ClipboardItem({
'text/plain': new Blob([text], { type: 'text/plain' }),
});
await navigator.clipboard.write([clipboardItem]);
return true;
} catch {
//
}
}
const textArea = document.createElement('textarea');
textArea.value = text;
textArea.style.position = 'fixed';
textArea.style.left = '-9999px';
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
const success = document.execCommand('copy');
document.body.removeChild(textArea);
return success;
} catch {
document.body.removeChild(textArea);
return false;
}
},
downloadFile: function (filename, content, mimeType = 'text/plain') {
if (this.supports.blob && this.supports.URL) {
try {
const blob = new Blob([content], { type: mimeType });
const url = URL.createObjectURL(blob);
if (this.supports.download) {
const link = document.createElement('a');
link.href = url;
link.download = filename;
link.style.display = 'none';
document.body.appendChild(link);
link.click();
setTimeout(() => {
document.body.removeChild(link);
URL.revokeObjectURL(url);
}, 100);
return true;
} else {
window.open(url, '_blank');
setTimeout(() => {
URL.revokeObjectURL(url);
}, 100);
return true;
}
} catch (e) {
console.error('Error downloading file:', e);
}
}
try {
const iframeId = 'highlightit-download-iframe';
let iframe = document.getElementById(iframeId);
if (!iframe) {
iframe = document.createElement('iframe');
iframe.id = iframeId;
iframe.style.display = 'none';
document.body.appendChild(iframe);
}
const iframeDocument = iframe.contentWindow.document;
iframeDocument.open('text/html', 'replace');
iframeDocument.write(content);
iframeDocument.close();
iframeDocument.execCommand('SaveAs', true, filename);
return true;
} catch (e) {
console.error('Legacy download failed:', e);
return false;
}
},
animate: function (element, keyframes, options) {
if (!element) return null;
if (this.supports.animation) {
try {
return element.animate(keyframes, options);
} catch {
//
}
}
const animationId = Date.now().toString(36);
const duration = options.duration || 1000;
Object.keys(keyframes[0]).forEach((prop) => {
element.style[prop] = keyframes[0][prop];
});
setTimeout(() => {
element.style.transition = `all ${duration}ms ${options.easing || 'ease'}`;
const finalState = keyframes[keyframes.length - 1];
Object.keys(finalState).forEach((prop) => {
element.style[prop] = finalState[prop];
});
setTimeout(() => {
element.style.transition = '';
if (options.fill !== 'forwards') {
Object.keys(finalState).forEach((prop) => {
element.style[prop] = '';
});
}
}, duration);
}, 0);
return {
id: animationId,
cancel: function () {
element.style.transition = '';
},
finished: new Promise((resolve) => {
setTimeout(resolve, duration);
}),
};
},
/**
* Helper to check if a container has an element with a certain class
* Used as a fallback for browsers that don't support :has() selector
*
* @param {HTMLElement} container - The container element to check
* @param {string} selector - The selector to look for within the container
* @param {string} className - The class to add/remove from the container
* @param {boolean} shouldHaveClass - Whether to add or remove the class
*/
updateHasClass: function (container, selector, className, shouldHaveClass) {
if (!container || !selector || !className) return;
if (this.supports.cssHas) return;
const hasElement = container.querySelector(selector);
if (shouldHaveClass && hasElement) {
this.classList.add(container, className);
} else if (!shouldHaveClass && !hasElement) {
this.classList.remove(container, className);
}
},
};
// End bundled module: polyfills.js
// Begin bundled module: cache.js
/**
* Cache for commonly used data in the highlighting process
*/
const cache = {
popularLanguages: new Set([
'javascript',
'typescript',
'python',
'java',
'html',
'css',
'scss',
'php',
'ruby',
'go',
'rust',
'c',
'cpp',
'csharp',
'bash',
'json',
'markdown',
'yaml',
'xml',
]),
extensionMap: new Map([
['js', 'javascript'],
['ts', 'typescript'],
['jsx', 'javascript'],
['tsx', 'typescript'],
['html', 'html'],
['css', 'css'],
['scss', 'scss'],
['sass', 'scss'],
['py', 'python'],
['rb', 'ruby'],
['java', 'java'],
['c', 'c'],
['cpp', 'cpp'],
['hpp', 'cpp'],
['h', 'c'],
['cs', 'csharp'],
['php', 'php'],
['go', 'go'],
['rust', 'rust'],
['rs', 'rust'],
['swift', 'swift'],
['md', 'markdown'],
['json', 'json'],
['xml', 'xml'],
['yaml', 'yaml'],
['yml', 'yaml'],
['sh', 'bash'],
['bash', 'bash'],
['kt', 'kotlin'],
['kts', 'kotlin'],
['dart', 'dart'],
['pl', 'perl'],
['pm', 'perl'],
['lua', 'lua'],
['groovy', 'groovy'],
['r', 'r'],
['scala', 'scala'],
['sql', 'sql'],
['toml', 'toml'],
['ini', 'ini'],
['hcl', 'hcl'],
['tf', 'terraform'],
['dockerfile', 'dockerfile'],
['vue', 'vue'],
['elm', 'elm'],
['clj', 'clojure'],
['cljs', 'clojure'],
['hs', 'haskell'],
['lhs', 'haskell'],
['ex', 'elixir'],
['exs', 'elixir'],
['erl', 'erlang'],
['fs', 'fsharp'],
['fsx', 'fsharp'],
['ml', 'ocaml'],
['mli', 'ocaml'],
['graphql', 'graphql'],
['gql', 'graphql'],
['proto', 'protobuf'],
['sol', 'solidity'],
]),
htmlEscapes: new Map([
['&', '&'],
['<', '<'],
['>', '>'],
['"', '"'],
["'", '''],
['/', '/'],
['`', '`'],
['=', '='],
]),
svgIcons: {
copy: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="highlightit-copy-icon"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>`,
check: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="highlightit-check-icon"><polyline points="20 6 9 17 4 12"></polyline></svg>`,
share: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="highlightit-share-icon"><circle cx="18" cy="5" r="3"></circle><circle cx="6" cy="12" r="3"></circle><circle cx="18" cy="19" r="3"></circle><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"></line><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"></line></svg>`,
link: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="highlightit-link-icon"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>`,
download: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="highlightit-download-icon"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>`,
},
};
// End bundled module: cache.js
/**
* HighlightIt - A lightweight syntax highlighting library with themes, line numbers, and copy functionality.
* Uses highlight.js for code highlighting
*/
const BASE62_CHARS = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
/**
* HighlightIt class for syntax highlighting
*/
class HighlightIt {
/**
* Initialize HighlightIt by finding and highlighting all matching elements
* @param {Object} options - Configuration options
* @param {string} [options.selector='.highlight-it'] - CSS selector for elements to highlight
* @param {boolean} [options.autoDetect=true] - Whether to auto-detect language if not specified
* @param {boolean} [options.addCopyButton=true] - Whether to add a copy button to code blocks
* @param {boolean} [options.showLanguage=true] - Whether to show the language label
* @param {boolean} [options.addHeader=true] - Whether to add the header section to code blocks
* @param {boolean} [options.addLines=false] - Whether to add line numbers to code blocks
* @param {boolean} [options.addShare=false] - Whether to add share button to code blocks
* @param {boolean} [options.addDownload=false] - Whether to add download button to code blocks
* @param {string} [options.theme='auto'] - Theme to use ('light', 'dark', or 'auto')
* @param {number} [options.debounceTime=50] - Debounce time in ms for live updates (lower values = more responsive)
*/
static init(options = {}) {
const {
selector = '.highlight-it',
autoDetect = true,
addCopyButton = true,
showLanguage = true,
addHeader = true,
addLines = false,
addShare = false,
addDownload = false,
theme = 'auto',
debounceTime = 50,
} = options;
polyfills.init();
this.debounceTime = debounceTime;
this.applyGlobalTheme(theme);
this.isTouchDevice = polyfills.supports.touch;
if (this.isTouchDevice) {
polyfills.classList.add(document.documentElement, 'highlightit-touch-device');
}
const elements = document.querySelectorAll(`${selector}:not(.highlightit-original)`);
const chunkSize = 50;
const processChunk = (startIndex) => {
const endIndex = Math.min(startIndex + chunkSize, elements.length);
for (let i = startIndex; i < endIndex; i++) {
this.processElement(
elements[i],
autoDetect,
addCopyButton,
showLanguage,
addHeader,
addLines,
addShare,
addDownload
);
}
if (endIndex < elements.length) {
polyfills.requestAnimationFrame(() => processChunk(endIndex));
} else {
if (window.location.hash) {
this.scrollToAnchor();
}
}
};
processChunk(0);
this._initialized = true;
this.initSharing();
}
/**
* Generate a hash using SHA-256 and convert to a 12-character base62 string
* @param {string} input - The string to hash
* @returns {Promise<string>} - A 12-character base62 hash
*/
static async generateHash(input) {
try {
if (!window.crypto || !window.crypto.subtle) {
return polyfills.simpleHash(input);
}
const TextEncoderClass = window.TextEncoder || polyfills.TextEncoder;
if (!TextEncoderClass) {
return polyfills.simpleHash(input);
}
const encoder = new TextEncoderClass();
const data = encoder.encode(input);
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
if (typeof BigInt === 'undefined' && polyfills.BigIntPolyfill) {
return polyfills.simpleHash(input);
}
let base62Hash = '';
let value = 0n;
for (let i = 0; i < hashArray.length; i++) {
value = (value << 8n) | BigInt(hashArray[i]);
}
while (value > 0 || base62Hash.length < 12) {
const remainder = Number(value % 62n);
base62Hash = BASE62_CHARS[remainder] + base62Hash;
value = value / 62n;
if (value === 0n && base62Hash.length < 12) {
base62Hash = '0' + base62Hash;
}
}
return base62Hash.slice(-12).padStart(12, '0');
} catch (err) {
console.warn('Error generating hash:', err);
return polyfills.simpleHash(input);
}
}
/**
* Create a share button for code blocks
* @param {string} code - The code to generate hash from if no id exists
* @param {HTMLElement} container - The container element (to extract id)
* @returns {HTMLElement} - The share button element
*/
static async createShareButton(code, container) {
const shareButton = document.createElement('button');
shareButton.className = 'highlightit-button highlightit-share';
shareButton.setAttribute('aria-label', 'Copy link to this code');
shareButton.innerHTML = cache.svgIcons.share;
const originalId = container.getAttribute('data-original-id');
const containerId = container.id;
let blockId = originalId || containerId;
if (!blockId) {
blockId = await this.generateHash(code);
container.id = blockId;
}
const clickListener = async () => {
const currentId = container.getAttribute('data-original-id') || container.id;
const url = new URL(window.location.href);
url.hash = currentId;
const success = await polyfills.copyToClipboard(url.toString());
if (success) {
shareButton.classList.add('copied');
shareButton.innerHTML = cache.svgIcons.check;
setTimeout(() => {
shareButton.classList.remove('copied');
shareButton.innerHTML = cache.svgIcons.share;
}, 2000);
}
};
shareButton.addEventListener('click', clickListener);
shareButton.onclickBackup = clickListener;
shareButton._currentBlockId = blockId;
return shareButton;
}
/**
* Update block ID when code changes for blocks with live updates
* @param {HTMLElement} container - The container element
* @param {string} newCode - The new code content
* @returns {Promise<string>} - The updated block ID
*/
static async updateBlockId(container, newCode) {
const originalId = container.getAttribute('data-original-id');
if (originalId) {
const originalElement = document.querySelector(`.highlightit-original[id="${originalId}"]`);
if (originalElement) {
if (originalElement.id !== originalId) {
originalElement.id = originalId;
}
if (container.id === originalId) {
container.id = '';
}
container.setAttribute('data-original-id', originalId);
} else {
if (!container.id || container.id !== originalId) {
container.id = originalId;
}
}
return originalId;
} else if (container.id) {
return container.id;
} else {
try {
const newId = await this.generateHash(newCode);
container.id = newId;
return newId;
} catch (err) {
console.error('Error generating hash:', err);
const fallbackId = 'highlightit-' + Math.random().toString(36).substring(2, 15);
container.id = fallbackId;
return fallbackId;
}
}
}
/**
* Scroll to the element specified in the URL hash
* @param {number} [attempts=0] - Number of attempts made so far
*/
static scrollToAnchor(attempts = 0) {
const fullHash = window.location.hash.substring(1);