
Script.pudu
Pudu345 lines16.5 KB
1/** @Docgen.Theme.Script — the default site behavior: navigation, tabs, search, and theme */2module PuduLangDocgen.Theme.Script345const LINES: Array[Str] = [6 "(function () \{",7 " \"use strict\";",8 " var root = document.documentElement.getAttribute(\"data-root\") || \"\";",9 " var store = \{",10 " get: function (key) \{ try \{ return window.localStorage.getItem(key); \} catch (e) \{ return null; \} \},",11 " set: function (key, value) \{ try \{ window.localStorage.setItem(key, value); \} catch (e) \{ return; \} \}",12 " \};",13 " var THEMES = [\"auto\", \"light\", \"dark\"];",14 "",15 " function each(selector, action, scope) \{",16 " Array.prototype.forEach.call((scope || document).querySelectorAll(selector), action);",17 " \}",18 "",19 " function setupTheme() \{",20 " var button = document.querySelector(\".theme-toggle\");",21 " if (!button) \{ return; \}",22 " var label = button.getAttribute(\"aria-label\");",23 " var system = window.matchMedia ? window.matchMedia(\"(prefers-color-scheme: dark)\") : null;",24 " function shown() \{",25 " var chosen = document.documentElement.getAttribute(\"data-theme\") || \"auto\";",26 " if (chosen !== \"auto\") \{ return chosen; \}",27 " return system && system.matches ? \"dark\" : \"light\";",28 " \}",29 " function apply(theme) \{",30 " document.documentElement.setAttribute(\"data-theme\", theme);",31 " button.setAttribute(\"aria-pressed\", shown() === \"dark\" ? \"true\" : \"false\");",32 " button.title = label + \": \" + shown();",33 " \}",34 " var saved = store.get(\"docgen-theme\");",35 " apply(THEMES.indexOf(saved) >= 0 ? saved : \"auto\");",36 " button.addEventListener(\"click\", function () \{",37 " var next = shown() === \"dark\" ? \"light\" : \"dark\";",38 " store.set(\"docgen-theme\", next);",39 " apply(next);",40 " \});",41 " if (system && system.addEventListener) \{",42 " system.addEventListener(\"change\", function () \{ apply(document.documentElement.getAttribute(\"data-theme\") || \"auto\"); \});",43 " \}",44 " \}",45 "",46 " function setupMenu() \{",47 " panel(document.querySelector(\".menu-toggle\"), document.getElementById(\"sidebar\"));",48 " panel(document.querySelector(\".nav-toggle\"), document.getElementById(\"navbar\"));",49 " \}",50 "",51 " function panel(toggle, sidebar) \{",52 " if (!toggle || !sidebar) \{ return; \}",53 " toggle.addEventListener(\"click\", function () \{",54 " var open = sidebar.classList.toggle(\"open\");",55 " toggle.setAttribute(\"aria-expanded\", open ? \"true\" : \"false\");",56 " \});",57 " document.addEventListener(\"keydown\", function (event) \{",58 " if (event.key === \"Escape\" && sidebar.classList.contains(\"open\")) \{",59 " sidebar.classList.remove(\"open\");",60 " toggle.setAttribute(\"aria-expanded\", \"false\");",61 " toggle.focus();",62 " \}",63 " \});",64 " \}",65 "",66 " function setupToc() \{",67 " var toc = document.querySelector(\".toc\");",68 " if (!toc) \{ return; \}",69 " var here = window.location.pathname.replace(/\\/index\\.html$/, \"/\");",70 " each(\"a\", function (link) \{",71 " var target = link.pathname.replace(/\\/index\\.html$/, \"/\");",72 " if (target === here && !link.hash) \{",73 " link.classList.add(\"active\");",74 " link.setAttribute(\"aria-current\", \"page\");",75 " var item = link.parentElement;",76 " while (item && item !== toc) \{",77 " if (item.tagName === \"LI\") \{ item.classList.add(\"expanded\"); \}",78 " item = item.parentElement;",79 " \}",80 " \}",81 " \}, toc);",82 " each(\".toc-group\", function (group) \{",83 " group.addEventListener(\"click\", function () \{",84 " var item = group.parentElement;",85 " var open = item.classList.toggle(\"expanded\");",86 " group.setAttribute(\"aria-expanded\", open ? \"true\" : \"false\");",87 " \});",88 " \}, toc);",89 " var active = toc.querySelector(\"a.active\");",90 " if (active && active.scrollIntoView) \{ active.scrollIntoView(\{ block: \"center\" \}); \}",91 " var filter = document.querySelector(\".toc-filter input\");",92 " if (!filter) \{ return; \}",93 " filter.addEventListener(\"input\", function () \{",94 " var words = filter.value.toLowerCase().split(/\\s+/).filter(Boolean);",95 " each(\"li\", function (item) \{",96 " var text = item.textContent.toLowerCase();",97 " var match = words.every(function (word) \{ return text.indexOf(word) >= 0; \});",98 " item.classList.toggle(\"filtered-out\", words.length > 0 && !match);",99 " if (words.length > 0 && match) \{ item.classList.add(\"expanded\"); \}",100 " \}, toc);",101 " \});",102 " \}",103 "",104 " function setupCopy() \{",105 " each(\".code-block .copy\", function (button) \{",106 " button.addEventListener(\"click\", function () \{",107 " var code = button.closest(\".code-block\").querySelector(\"code\");",108 " var text = code ? code.innerText : \"\";",109 " var done = function () \{",110 " var label = button.textContent;",111 " button.textContent = \"Copied\";",112 " window.setTimeout(function () \{ button.textContent = label; \}, 1500);",113 " \};",114 " if (navigator.clipboard) \{ navigator.clipboard.writeText(text).then(done, function () \{ return; \}); \}",115 " \});",116 " \});",117 " \}",118 "",119 " var chosenTabs = [];",120 "",121 " function selectTab(id) \{",122 " chosenTabs = [id].concat(chosenTabs.filter(function (other) \{ return other !== id; \}));",123 " each(\".tabs\", function (group) \{",124 " var buttons = group.querySelectorAll('[role=\"tab\"]');",125 " var owned = Array.prototype.map.call(buttons, function (button) \{ return button.getAttribute(\"data-tab\"); \});",126 " var active = chosenTabs.filter(function (candidate) \{ return owned.indexOf(candidate) >= 0; \})[0];",127 " if (!active) \{ return; \}",128 " Array.prototype.forEach.call(buttons, function (button) \{",129 " var chosen = button.getAttribute(\"data-tab\") === active;",130 " button.setAttribute(\"aria-selected\", chosen ? \"true\" : \"false\");",131 " button.tabIndex = chosen ? 0 : -1;",132 " \});",133 " var panels = Array.prototype.filter.call(group.querySelectorAll('[role=\"tabpanel\"]'), function (panel) \{ return panel.parentElement === group; \});",134 " var matching = panels.filter(function (panel) \{ return panel.getAttribute(\"data-tab\") === active; \});",135 " var shown = matching.filter(function (panel) \{",136 " var condition = panel.getAttribute(\"data-condition\");",137 " return condition && chosenTabs.indexOf(condition) >= 0;",138 " \})[0] || matching.filter(function (panel) \{ return !panel.getAttribute(\"data-condition\"); \})[0] || matching[0];",139 " panels.forEach(function (panel) \{ panel.hidden = panel !== shown; \});",140 " \});",141 " \}",142 "",143 " function setupTabs() \{",144 " each('.tabs [role=\"tab\"]', function (button) \{",145 " button.addEventListener(\"click\", function () \{",146 " var id = button.getAttribute(\"data-tab\");",147 " selectTab(id);",148 " store.set(\"docgen-tabs\", chosenTabs.slice(0, 8).join(\",\"));",149 " \});",150 " button.addEventListener(\"keydown\", function (event) \{",151 " if (event.key !== \"ArrowRight\" && event.key !== \"ArrowLeft\") \{ return; \}",152 " var list = Array.prototype.slice.call(button.parentElement.children);",153 " var step = event.key === \"ArrowRight\" ? 1 : -1;",154 " var next = list[(list.indexOf(button) + step + list.length) % list.length];",155 " next.focus();",156 " next.click();",157 " \});",158 " \});",159 " each(\".tabs\", function (group) \{",160 " var first = group.querySelector('[role=\"tab\"]');",161 " if (first && chosenTabs.indexOf(first.getAttribute(\"data-tab\")) < 0) \{ chosenTabs.push(first.getAttribute(\"data-tab\")); \}",162 " \});",163 " var remembered = (store.get(\"docgen-tabs\") || \"\").split(\",\").filter(Boolean);",164 " remembered.reverse().forEach(function (id) \{ selectTab(id); \});",165 " if (remembered.length === 0 && chosenTabs.length > 0) \{ selectTab(chosenTabs[0]); \}",166 " \}",167 "",168 " function setupAffix() \{",169 " var links = Array.prototype.slice.call(document.querySelectorAll(\".affix a\"));",170 " if (links.length === 0 || !(\"IntersectionObserver\" in window)) \{ return; \}",171 " var byId = \{\};",172 " links.forEach(function (link) \{ byId[decodeURIComponent(link.hash.slice(1))] = link; \});",173 " var visible = \{\};",174 " var observer = new IntersectionObserver(function (entries) \{",175 " entries.forEach(function (entry) \{ visible[entry.target.id] = entry.isIntersecting; \});",176 " var current = null;",177 " links.forEach(function (link) \{",178 " var id = decodeURIComponent(link.hash.slice(1));",179 " if (current === null && visible[id]) \{ current = link; \}",180 " \});",181 " if (current) \{",182 " links.forEach(function (link) \{ link.classList.toggle(\"active\", link === current); \});",183 " \}",184 " \}, \{ rootMargin: \"-60px 0px -60% 0px\" \});",185 " Object.keys(byId).forEach(function (id) \{",186 " var heading = document.getElementById(id);",187 " if (heading) \{ observer.observe(heading); \}",188 " \});",189 " \}",190 "",191 " function tokens(text) \{",192 " return text.toLowerCase().split(/[^\\p\{L\}\\p\{N\}_]+/u).filter(function (word) \{ return word.length > 0; \});",193 " \}",194 "",195 " function score(entry, words) \{",196 " var title = entry.title.toLowerCase();",197 " var text = entry.text.toLowerCase();",198 " var total = 0;",199 " for (var i = 0; i < words.length; i++) \{",200 " var word = words[i];",201 " var inTitle = title.indexOf(word);",202 " var inText = text.indexOf(word);",203 " if (inTitle < 0 && inText < 0) \{ return 0; \}",204 " if (inTitle === 0) \{ total += 20; \} else if (inTitle > 0) \{ total += 10; \}",205 " if (inText >= 0) \{ total += 1 + Math.min(5, text.split(word).length - 1); \}",206 " \}",207 " return total;",208 " \}",209 "",210 " function excerpt(text, words) \{",211 " var lower = text.toLowerCase();",212 " var at = lower.indexOf(words[0]);",213 " var start = Math.max(0, at - 60);",214 " var piece = text.slice(start, start + 180);",215 " return (start > 0 ? \"…\" : \"\") + piece + (start + 180 < text.length ? \"…\" : \"\");",216 " \}",217 "",218 " function highlighted(text, words) \{",219 " var fragment = document.createDocumentFragment();",220 " var lower = text.toLowerCase();",221 " var index = 0;",222 " while (index < text.length) \{",223 " var next = -1;",224 " var length = 0;",225 " words.forEach(function (word) \{",226 " var found = lower.indexOf(word, index);",227 " if (found >= 0 && (next < 0 || found < next)) \{ next = found; length = word.length; \}",228 " \});",229 " if (next < 0) \{ fragment.appendChild(document.createTextNode(text.slice(index))); break; \}",230 " if (next > index) \{ fragment.appendChild(document.createTextNode(text.slice(index, next))); \}",231 " var mark = document.createElement(\"mark\");",232 " mark.textContent = text.slice(next, next + length);",233 " fragment.appendChild(mark);",234 " index = next + length;",235 " \}",236 " return fragment;",237 " \}",238 "",239 " function setupSearch() \{",240 " var input = document.getElementById(\"search-input\");",241 " var panel = document.getElementById(\"search-results\");",242 " if (!input || !panel) \{ return; \}",243 " var index = null;",244 " var loading = null;",245 " var selected = -1;",246 " function load() \{",247 " if (loading) \{ return loading; \}",248 " loading = fetch(root + \"index.json\").then(function (response) \{ return response.json(); \}).then(function (data) \{",249 " index = data;",250 " return data;",251 " \}, function () \{ index = []; return index; \});",252 " return loading;",253 " \}",254 " function close() \{",255 " panel.hidden = true;",256 " input.setAttribute(\"aria-expanded\", \"false\");",257 " selected = -1;",258 " \}",259 " function choose(position) \{",260 " var items = panel.querySelectorAll(\".search-result\");",261 " if (items.length === 0) \{ return; \}",262 " selected = (position + items.length) % items.length;",263 " Array.prototype.forEach.call(items, function (item, at) \{ item.setAttribute(\"aria-selected\", at === selected ? \"true\" : \"false\"); \});",264 " items[selected].scrollIntoView(\{ block: \"nearest\" \});",265 " \}",266 " function show() \{",267 " var words = tokens(input.value);",268 " panel.textContent = \"\";",269 " if (words.length === 0) \{ close(); return; \}",270 " var ranked = index.map(function (entry) \{ return \{ entry: entry, score: score(entry, words) \}; \})",271 " .filter(function (hit) \{ return hit.score > 0; \})",272 " .sort(function (left, right) \{ return right.score - left.score; \})",273 " .slice(0, 12);",274 " if (ranked.length === 0) \{",275 " var empty = document.createElement(\"div\");",276 " empty.className = \"search-empty\";",277 " empty.textContent = \"No results for “\" + input.value.trim() + \"”\";",278 " panel.appendChild(empty);",279 " \}",280 " ranked.forEach(function (hit) \{",281 " var link = document.createElement(\"a\");",282 " link.className = \"search-result\";",283 " link.setAttribute(\"role\", \"option\");",284 " link.href = root + hit.entry.href;",285 " var title = document.createElement(\"strong\");",286 " title.appendChild(highlighted(hit.entry.title, words));",287 " var summary = document.createElement(\"span\");",288 " summary.appendChild(highlighted(excerpt(hit.entry.summary || hit.entry.text, words), words));",289 " link.appendChild(title);",290 " link.appendChild(summary);",291 " panel.appendChild(link);",292 " \});",293 " panel.hidden = false;",294 " input.setAttribute(\"aria-expanded\", \"true\");",295 " selected = -1;",296 " \}",297 " input.addEventListener(\"focus\", load);",298 " input.addEventListener(\"input\", function () \{ load().then(show); \});",299 " input.addEventListener(\"keydown\", function (event) \{",300 " if (event.key === \"ArrowDown\") \{ event.preventDefault(); choose(selected + 1); \}",301 " else if (event.key === \"ArrowUp\") \{ event.preventDefault(); choose(selected - 1); \}",302 " else if (event.key === \"Enter\") \{",303 " var items = panel.querySelectorAll(\".search-result\");",304 " var target = items[selected >= 0 ? selected : 0];",305 " if (target) \{ event.preventDefault(); window.location.href = target.href; \}",306 " \} else if (event.key === \"Escape\") \{ close(); input.blur(); \}",307 " \});",308 " document.addEventListener(\"click\", function (event) \{",309 " if (!panel.contains(event.target) && event.target !== input) \{ close(); \}",310 " \});",311 " document.addEventListener(\"keydown\", function (event) \{",312 " var typing = /INPUT|TEXTAREA|SELECT/.test(document.activeElement.tagName);",313 " if (event.key === \"/\" && !typing) \{ event.preventDefault(); input.focus(); \}",314 " \});",315 " \}",316 "",317 " function setupDiagrams() \{",318 " if (!document.querySelector(\"pre.mermaid\")) \{ return; \}",319 " window.addEventListener(\"load\", function () \{",320 " if (!window.mermaid) \{ return; \}",321 " var dark = document.documentElement.getAttribute(\"data-theme\") === \"dark\" ||",322 " (document.documentElement.getAttribute(\"data-theme\") === \"auto\" && window.matchMedia(\"(prefers-color-scheme: dark)\").matches);",323 " window.mermaid.initialize(\{ startOnLoad: false, theme: dark ? \"dark\" : \"default\", securityLevel: \"strict\" \});",324 " window.mermaid.run(\{ querySelector: \"pre.mermaid\" \});",325 " \});",326 " \}",327 "",328 " function start() \{",329 " setupTheme();",330 " setupMenu();",331 " setupToc();",332 " setupCopy();",333 " setupTabs();",334 " setupAffix();",335 " setupSearch();",336 " setupDiagrams();",337 " \}",338 "",339 " if (document.readyState === \"loading\") \{ document.addEventListener(\"DOMContentLoaded\", start); \} else \{ start(); \}",340 "\})();"341]342343344export fn script() -> Str { LINES.join("\n") + "\n" }345