highlight_css.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. // Minimal CSS syntax highlighter for the docs (shared).
  2. // It converts plain-text CSS inside `.css-view` into highlighted HTML using
  3. // the existing demo.css token classes: .kwd .str .num .punc .com .attr .val .fun
  4. (function () {
  5. function escapeHtml(s) {
  6. return String(s)
  7. .replace(/&/g, '&')
  8. .replace(/</g, '&lt;')
  9. .replace(/>/g, '&gt;');
  10. }
  11. function highlightCSS(input) {
  12. let s = escapeHtml(input);
  13. // Comments
  14. s = s.replace(/\/\*[\s\S]*?\*\//g, (m) => `<span class="com">${m}</span>`);
  15. // Strings
  16. s = s.replace(/("(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*')/g, (m) => `<span class="str">${m}</span>`);
  17. // @rules
  18. s = s.replace(/(^|\s)(@[\w-]+)/g, (m, p1, p2) => `${p1}<span class="kwd">${p2}</span>`);
  19. // Hex colors
  20. 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>`);
  21. // Numbers with units (very rough, but good enough visually)
  22. s = s.replace(/(\b\d+(?:\.\d+)?)(%|px|rem|em|vh|vw|vmin|vmax|deg|turn|s|ms)?\b/g, (m, n, unit) => {
  23. return `<span class="num">${n}</span>${unit || ''}`;
  24. });
  25. // Property names: "prop: value"
  26. s = s.replace(/(^|\n)(\s*)([a-zA-Z_-][\w-]*)(\s*):/g, (m, p1, ws, prop, ws2) => {
  27. return `${p1}${ws}<span class="attr">${prop}</span>${ws2}<span class="punc">:</span>`;
  28. });
  29. // Highlight braces / punctuation
  30. s = s.replace(/([{}();,])/g, (m) => `<span class="punc">${m}</span>`);
  31. // Selectors (line before "{"), skip @rules
  32. s = s.replace(/(^|\n)([^\n{]+?)(\s*)(<span class="punc">\{<\/span>)/g, (m, p1, sel, ws, brace) => {
  33. const trimmed = sel.trimStart();
  34. if (trimmed.startsWith('@')) return `${p1}${sel}${ws}${brace}`;
  35. return `${p1}<span class="fun">${sel}</span>${ws}${brace}`;
  36. });
  37. // Values: after ":" until ";" or "}" (best-effort, runs after punctuation)
  38. s = s.replace(/(<span class="punc">:<\/span>)([^;\n}]+)(?=(<span class="punc">;|<span class="punc">\}))/g, (m, colon, val) => {
  39. // avoid re-highlighting spans
  40. if (val.includes('<span')) return `${colon}${val}`;
  41. return `${colon}<span class="val">${val}</span>`;
  42. });
  43. return s;
  44. }
  45. function enhance() {
  46. const blocks = document.querySelectorAll('.css-view');
  47. blocks.forEach((el) => {
  48. try {
  49. if (el.dataset.highlighted === '1') return;
  50. const text = el.textContent || '';
  51. // If already contains spans, assume it's already highlighted.
  52. if (el.innerHTML.includes('<span')) {
  53. el.dataset.highlighted = '1';
  54. return;
  55. }
  56. el.innerHTML = highlightCSS(text);
  57. el.dataset.highlighted = '1';
  58. } catch (_) {
  59. // Never break the page; just skip highlighting if anything goes wrong.
  60. }
  61. });
  62. }
  63. // Expose for pages that want to re-run manually
  64. window.__highlightCssViews = enhance;
  65. if (document.readyState === 'loading') {
  66. document.addEventListener('DOMContentLoaded', enhance, { once: true });
  67. } else {
  68. enhance();
  69. }
  70. // Re-run when switching to the CSS tab (covers cases where the page
  71. // injects/replaces code blocks after load or during navigation).
  72. document.addEventListener(
  73. 'click',
  74. (e) => {
  75. const tab = e.target && e.target.closest ? e.target.closest('.tab') : null;
  76. if (!tab) return;
  77. const label = (tab.textContent || '').trim().toLowerCase();
  78. if (label === 'css') {
  79. // Defer so the tab switch can toggle DOM/classes first
  80. setTimeout(enhance, 0);
  81. }
  82. },
  83. true
  84. );
  85. // Observe DOM changes to catch dynamically added `.css-view` blocks.
  86. try {
  87. const mo = new MutationObserver(() => {
  88. // Cheap debounce via microtask
  89. Promise.resolve().then(enhance);
  90. });
  91. mo.observe(document.documentElement, {
  92. subtree: true,
  93. childList: true,
  94. characterData: true
  95. });
  96. } catch (_) {}
  97. })();