|
1 | 1 | /** |
2 | | - * Prefer light/dark diagram assets from Material's color scheme toggle. |
| 2 | + * Show the diagram asset that matches Material's active palette. |
3 | 3 | * |
4 | | - * Canonical docs keep <picture><source media="(prefers-color-scheme:…)"> |
5 | | - * for GitHub rendering. On the site, Material's palette can disagree with the |
6 | | - * OS preference, so we set the <img> src from the matching source whenever |
7 | | - * data-md-color-scheme changes. |
| 4 | + * After markdown fix-up, <source media="(prefers-color-scheme: …)"> points at |
| 5 | + * the asset meant for that *page* theme: |
| 6 | + * light page → dark-ink art (readable on white) |
| 7 | + * dark page → light-ink art (readable on near-black) |
| 8 | + * |
| 9 | + * Browsers re-resolve <source media> from the OS preference even when Material's |
| 10 | + * toggle disagrees, so we rewrite each <picture> into a dual-<img> pair driven |
| 11 | + * by data-md-color-scheme ("default" | "slate"). |
8 | 12 | */ |
9 | 13 | (function () { |
10 | | - function isDarkScheme() { |
11 | | - return document.body.getAttribute("data-md-color-scheme") === "slate"; |
| 14 | + function firstUrl(srcset) { |
| 15 | + if (!srcset) return null; |
| 16 | + return srcset.trim().split(/\s+/)[0] || null; |
12 | 17 | } |
13 | 18 |
|
14 | | - function pickSrc(picture, dark) { |
| 19 | + function pairFromPicture(picture) { |
15 | 20 | var sources = picture.querySelectorAll("source[srcset]"); |
16 | | - var light = null; |
17 | | - var darkSrc = null; |
| 21 | + var img = picture.querySelector("img"); |
| 22 | + var mediaDark = null; |
| 23 | + var mediaLight = null; |
| 24 | + var namedDarkInk = null; |
| 25 | + var namedLightInk = null; |
18 | 26 | var i; |
| 27 | + |
19 | 28 | for (i = 0; i < sources.length; i++) { |
20 | | - var media = sources[i].getAttribute("media") || ""; |
21 | | - var srcset = sources[i].getAttribute("srcset"); |
22 | | - if (!srcset) continue; |
23 | | - // first URL token in srcset |
24 | | - var src = srcset.trim().split(/\s+/)[0]; |
25 | | - if (media.indexOf("dark") !== -1) darkSrc = src; |
26 | | - else if (media.indexOf("light") !== -1) light = src; |
| 29 | + var media = (sources[i].getAttribute("media") || "").toLowerCase().replace(/\s+/g, ""); |
| 30 | + var src = firstUrl(sources[i].getAttribute("srcset")); |
| 31 | + if (!src) continue; |
| 32 | + var file = src.split("/").pop().toLowerCase(); |
| 33 | + |
| 34 | + if (media.indexOf("prefers-color-scheme:dark") !== -1) mediaDark = src; |
| 35 | + else if (media.indexOf("prefers-color-scheme:light") !== -1) mediaLight = src; |
| 36 | + |
| 37 | + // Filename heuristics (ink colour), skipping ambiguous setup-example-light.png |
| 38 | + if (file.indexOf("dark") !== -1) namedDarkInk = src; |
| 39 | + if (file.indexOf("light") !== -1 && file.indexOf("transparent") !== -1) { |
| 40 | + namedLightInk = src; |
| 41 | + } else if ( |
| 42 | + file.indexOf("light") !== -1 && |
| 43 | + file.indexOf("setup-example-light.png") === -1 |
| 44 | + ) { |
| 45 | + namedLightInk = src; |
| 46 | + } |
| 47 | + } |
| 48 | + |
| 49 | + // Media tags are authoritative once markdown is correct. |
| 50 | + var forLightPage = |
| 51 | + mediaLight || namedDarkInk || (img && img.getAttribute("src")); |
| 52 | + var forDarkPage = mediaDark || namedLightInk || forLightPage; |
| 53 | + |
| 54 | + return { forLightPage: forLightPage, forDarkPage: forDarkPage }; |
| 55 | + } |
| 56 | + |
| 57 | + function rewritePicture(picture) { |
| 58 | + if (picture.getAttribute("data-fmsg-scheme") === "done") return; |
| 59 | + // Already rewritten wrapper |
| 60 | + if ( |
| 61 | + picture.classList && |
| 62 | + picture.classList.contains("fmsg-scheme-image") |
| 63 | + ) { |
| 64 | + return; |
| 65 | + } |
| 66 | + |
| 67 | + var img = picture.querySelector("img"); |
| 68 | + if (!img) return; |
| 69 | + var alt = img.getAttribute("alt") || ""; |
| 70 | + var pair = pairFromPicture(picture); |
| 71 | + if (!pair.forLightPage) return; |
| 72 | + |
| 73 | + var wrap = document.createElement("span"); |
| 74 | + wrap.className = "fmsg-scheme-image"; |
| 75 | + wrap.setAttribute("data-fmsg-scheme", "done"); |
| 76 | + |
| 77 | + function make(cls, src) { |
| 78 | + var el = document.createElement("img"); |
| 79 | + el.className = cls; |
| 80 | + el.setAttribute("src", src); |
| 81 | + el.setAttribute("alt", alt); |
| 82 | + el.setAttribute("loading", img.getAttribute("loading") || "lazy"); |
| 83 | + return el; |
| 84 | + } |
| 85 | + |
| 86 | + wrap.appendChild(make("fmsg-for-light", pair.forLightPage)); |
| 87 | + wrap.appendChild( |
| 88 | + make("fmsg-for-dark", pair.forDarkPage || pair.forLightPage) |
| 89 | + ); |
| 90 | + |
| 91 | + if (picture.parentNode) { |
| 92 | + picture.parentNode.replaceChild(wrap, picture); |
27 | 93 | } |
28 | | - if (dark && darkSrc) return darkSrc; |
29 | | - if (!dark && light) return light; |
30 | | - if (darkSrc || light) return dark ? darkSrc || light : light || darkSrc; |
31 | | - return null; |
32 | 94 | } |
33 | 95 |
|
34 | | - function applySchemeImages() { |
35 | | - var dark = isDarkScheme(); |
| 96 | + function apply() { |
36 | 97 | var pictures = document.querySelectorAll(".md-typeset picture"); |
37 | 98 | for (var i = 0; i < pictures.length; i++) { |
38 | | - var picture = pictures[i]; |
39 | | - var img = picture.querySelector("img"); |
40 | | - if (!img) continue; |
41 | | - var next = pickSrc(picture, dark); |
42 | | - if (next && img.getAttribute("src") !== next) { |
43 | | - img.setAttribute("src", next); |
44 | | - } |
| 99 | + rewritePicture(pictures[i]); |
45 | 100 | } |
46 | 101 | } |
47 | 102 |
|
48 | | - function watchScheme() { |
49 | | - applySchemeImages(); |
| 103 | + function watch() { |
| 104 | + apply(); |
50 | 105 | if (typeof MutationObserver === "undefined") return; |
51 | | - var observer = new MutationObserver(applySchemeImages); |
52 | | - observer.observe(document.body, { |
| 106 | + var obs = new MutationObserver(function (mutations) { |
| 107 | + var i; |
| 108 | + for (i = 0; i < mutations.length; i++) { |
| 109 | + if ( |
| 110 | + mutations[i].type === "attributes" && |
| 111 | + mutations[i].attributeName === "data-md-color-scheme" |
| 112 | + ) { |
| 113 | + // CSS handles visibility; nothing else required. |
| 114 | + continue; |
| 115 | + } |
| 116 | + } |
| 117 | + apply(); |
| 118 | + }); |
| 119 | + obs.observe(document.body, { |
53 | 120 | attributes: true, |
54 | 121 | attributeFilter: ["data-md-color-scheme"], |
| 122 | + childList: true, |
| 123 | + subtree: true, |
55 | 124 | }); |
56 | 125 | } |
57 | 126 |
|
58 | | - // Material emits document$ on instant navigation; fall back to DOM ready. |
59 | 127 | if (typeof document$ !== "undefined" && document$.subscribe) { |
60 | | - document$.subscribe(watchScheme); |
| 128 | + document$.subscribe(watch); |
61 | 129 | } else if (document.readyState === "loading") { |
62 | | - document.addEventListener("DOMContentLoaded", watchScheme); |
| 130 | + document.addEventListener("DOMContentLoaded", watch); |
63 | 131 | } else { |
64 | | - watchScheme(); |
| 132 | + watch(); |
65 | 133 | } |
66 | 134 | })(); |
0 commit comments