| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115 |
- // Minimal CSS syntax highlighter for the docs (shared).
- // It converts plain-text CSS inside `.css-view` into highlighted HTML using
- // the existing demo.css token classes: .kwd .str .num .punc .com .attr .val .fun
- (function () {
- function escapeHtml(s) {
- return String(s)
- .replace(/&/g, '&')
- .replace(/</g, '<')
- .replace(/>/g, '>');
- }
- function highlightCSS(input) {
- let s = escapeHtml(input);
- // Comments
- s = s.replace(/\/\*[\s\S]*?\*\//g, (m) => `<span class="com">${m}</span>`);
- // Strings
- s = s.replace(/("(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*')/g, (m) => `<span class="str">${m}</span>`);
- // @rules
- s = s.replace(/(^|\s)(@[\w-]+)/g, (m, p1, p2) => `${p1}<span class="kwd">${p2}</span>`);
- // Hex colors
- s = s.replace(/(#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8}))/g, (m) => `<span class="num">${m}</span>`);
- // Numbers with units (very rough, but good enough visually)
- s = s.replace(/(\b\d+(?:\.\d+)?)(%|px|rem|em|vh|vw|vmin|vmax|deg|turn|s|ms)?\b/g, (m, n, unit) => {
- return `<span class="num">${n}</span>${unit || ''}`;
- });
- // Property names: "prop: value"
- s = s.replace(/(^|\n)(\s*)([a-zA-Z_-][\w-]*)(\s*):/g, (m, p1, ws, prop, ws2) => {
- return `${p1}${ws}<span class="attr">${prop}</span>${ws2}<span class="punc">:</span>`;
- });
- // Highlight braces / punctuation
- s = s.replace(/([{}();,])/g, (m) => `<span class="punc">${m}</span>`);
- // Selectors (line before "{"), skip @rules
- s = s.replace(/(^|\n)([^\n{]+?)(\s*)(<span class="punc">\{<\/span>)/g, (m, p1, sel, ws, brace) => {
- const trimmed = sel.trimStart();
- if (trimmed.startsWith('@')) return `${p1}${sel}${ws}${brace}`;
- return `${p1}<span class="fun">${sel}</span>${ws}${brace}`;
- });
- // Values: after ":" until ";" or "}" (best-effort, runs after punctuation)
- s = s.replace(/(<span class="punc">:<\/span>)([^;\n}]+)(?=(<span class="punc">;|<span class="punc">\}))/g, (m, colon, val) => {
- // avoid re-highlighting spans
- if (val.includes('<span')) return `${colon}${val}`;
- return `${colon}<span class="val">${val}</span>`;
- });
- return s;
- }
- function enhance() {
- const blocks = document.querySelectorAll('.css-view');
- blocks.forEach((el) => {
- try {
- if (el.dataset.highlighted === '1') return;
- const text = el.textContent || '';
- // If already contains spans, assume it's already highlighted.
- if (el.innerHTML.includes('<span')) {
- el.dataset.highlighted = '1';
- return;
- }
- el.innerHTML = highlightCSS(text);
- el.dataset.highlighted = '1';
- } catch (_) {
- // Never break the page; just skip highlighting if anything goes wrong.
- }
- });
- }
- // Expose for pages that want to re-run manually
- window.__highlightCssViews = enhance;
- if (document.readyState === 'loading') {
- document.addEventListener('DOMContentLoaded', enhance, { once: true });
- } else {
- enhance();
- }
- // Re-run when switching to the CSS tab (covers cases where the page
- // injects/replaces code blocks after load or during navigation).
- document.addEventListener(
- 'click',
- (e) => {
- const tab = e.target && e.target.closest ? e.target.closest('.tab') : null;
- if (!tab) return;
- const label = (tab.textContent || '').trim().toLowerCase();
- if (label === 'css') {
- // Defer so the tab switch can toggle DOM/classes first
- setTimeout(enhance, 0);
- }
- },
- true
- );
- // Observe DOM changes to catch dynamically added `.css-view` blocks.
- try {
- const mo = new MutationObserver(() => {
- // Cheap debounce via microtask
- Promise.resolve().then(enhance);
- });
- mo.observe(document.documentElement, {
- subtree: true,
- childList: true,
- characterData: true
- });
- } catch (_) {}
- })();
|