Pudu programming language
Menu
Package

@chrismichaelps / pudu-lang-docgen

Documentation publishing for Pudu: articles, API references, navigation, search, and static websites

0.1.0Apache-2.01

InstallClose

Script.pudu

Pudu345 lines16.5 KB

GitHub ↗
1/** @Docgen.Theme.Script — the default site behavior: navigation, tabs, search, and theme */2module PuduLangDocgen.Theme.Script34/// Script lines, loaded deferred and working without any other library.5const 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]342343/// The script text published as `_assets/site.js`.344export fn script() -> Str { LINES.join("\n") + "\n" }345