UNPKG

highlight-it

Version:

A lightweight syntax highlighting library with themes, line numbers, and copy functionality

1,061 lines (918 loc) 124 kB
/*! * 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([ ['&', '&amp;'], ['<', '&lt;'], ['>', '&gt;'], ['"', '&quot;'], ["'", '&#39;'], ['/', '&#x2F;'], ['`', '&#x60;'], ['=', '&#x3D;'], ]), 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);