Commit 4dacaa1e12

Ian Johnson <ian@ianjohnson.dev>
2023-06-27 06:19:40
Autodoc: add preference for `/` search
Closes #16081
1 parent 7166407
Changed files (2)
lib/docs/index.html
@@ -25,8 +25,8 @@
         --search-bg-color-focus: #ffffff;
         --search-sh-color: rgba(0, 0, 0, 0.18);
         --search-other-results-color: rgb(100, 100, 100);
-        --help-sh-color: rgba(0, 0, 0, 0.75);
-        --help-bg-color: #aaa;
+        --modal-sh-color: rgba(0, 0, 0, 0.75);
+        --modal-bg-color: #aaa;
       }
 
       html, body { margin: 0; padding: 0; height: 100%; }
@@ -99,7 +99,7 @@
         flex-shrink: 1;
       }
 
-      .help-modal {
+      .modal-container {
         z-index: 400;
       }
 
@@ -412,8 +412,8 @@
         padding: 1px 1em;
       }
 
-      /* help modal */
-      .help-modal {
+      /* modals */
+      .modal-container {
         display: flex;
         width: 100%;
         height: 100%;
@@ -426,29 +426,36 @@
         backdrop-filter: blur(0.3em);
       }
 
-      .help-modal > .modal {
+      .modal-container > .modal {
         max-width: 97vw;
         max-height: 97vh;
         overflow: auto;
         font-size: 1rem;
         color: #fff;
-        background-color: var(--help-bg-color);
+        background-color: var(--modal-bg-color);
         border: 0.125rem solid #000;
-        box-shadow: 0 0.5rem 2.5rem 0.3rem var(--help-sh-color);
+        box-shadow: 0 0.5rem 2.5rem 0.3rem var(--modal-sh-color);
       }
 
-      .help-modal h1 {
+      .modal-container h1 {
         margin: 0.75em 2.5em 1em 2.5em;
         font-size: 1.5em;
         text-align: center;
       }
 
-      .help-modal dt, .help-modal dd {
+      .modal-container dt, .modal-container dd {
         display: inline;
         margin: 0 0.2em;
       }
 
-      .help-modal dl {
+      .modal-container dl {
+        margin-left: 0.5em;
+        margin-right: 0.5em;
+      }
+
+      .prefs-list {
+        list-style: none;
+        padding: 0;
         margin-left: 0.5em;
         margin-right: 0.5em;
       }
@@ -539,8 +546,8 @@
           --search-bg-color-focus: #000;
           --search-sh-color: rgba(255, 255, 255, 0.28);
           --search-other-results-color: rgba(255, 255, 255, 0.28);
-          --help-sh-color: rgba(142, 142, 142, 0.5);
-          --help-bg-color: #333;
+          --modal-sh-color: rgba(142, 142, 142, 0.5);
+          --modal-bg-color: #333;
         }
 
         .docs pre {
@@ -677,13 +684,13 @@
     </style>
   </head>
   <body class="canvas">
-    <div class="banner">
+    <div id="banner" class="banner">
       This is a beta autodoc build; expect bugs and missing information.
       <a href="https://github.com/ziglang/zig/wiki/How-to-contribute-to-Autodoc">Report an Issue</a>,
       <a href="https://github.com/ziglang/zig/wiki/How-to-contribute-to-Autodoc">Contribute</a>,
       <a href="https://github.com/ziglang/zig/wiki/How-to-read-the-standard-library-source-code">Learn more about stdlib source code</a>.
     </div>
-    <div class="flex-main">
+    <div id="main" class="flex-main">
       <div class="flex-filler"></div>
       <div class="flex-left sidebar">
         <nav>
@@ -758,7 +765,7 @@
         <div class="wrap">
           <section class="docs" style="padding-top: 1.5rem; padding-bottom:0;">
           <div style="position: relative">
-            <span id="searchPlaceholder"><kbd>/</kbd> or <kbd>s</kbd> to search, <kbd>?</kbd> for more options</span>
+            <span id="searchPlaceholder"><!-- populated by setPrefSlashSearch --></span>
             <input type="search" class="search" id="search" autocomplete="off" spellcheck="false" disabled>
           </div>
           </section>
@@ -872,20 +879,31 @@
       </div>
     </div>
     <div id="helpModal" class="hidden">
-      <div class="help-modal">
+      <div class="modal-container">
         <div class="modal">
           <h1>Keyboard Shortcuts</h1>
           <dl><dt><kbd>?</kbd></dt><dd>Toggle this help modal</dd></dl>
-          <dl><dt><kbd>s</kbd> or <kbd>/</kbd></dt><dd>Focus the search field</dd></dl>
+          <dl><dt id="searchKeys"><!-- populated by setPrefSlashSearch --></dt><dd>Focus the search field</dd></dl>
           <div style="margin-left: 1em">
             <dl><dt><kbd>โ†‘</kbd></dt><dd>Move up in search results</dd></dl>
             <dl><dt><kbd>โ†“</kbd></dt><dd>Move down in search results</dd></dl>
             <dl><dt><kbd>โŽ</kbd></dt><dd>Go to active search result</dd></dl>
           </div>
+          <dl><dt><kbd>p</kbd></dt><dd>Open preferences</dd></dl>
           <dl><dt><kbd>Esc</kbd></dt><dd>Clear focus; close this modal</dd></dl>
         </div>
       </div>
     </div>
+    <div id="prefsModal" class="hidden">
+      <div class="modal-container">
+        <div class="modal">
+          <h1>Preferences</h1>
+          <ul class="prefs-list">
+            <li><input id="prefSlashSearch" type="checkbox"><label for="prefSlashSearch">Enable <kbd>/</kbd> for search</label></li>
+          </ul>
+        </div>
+      </div>
+    </div>
     <script src="data.js"></script>
     <script src="commonmark.js"></script>
     <script src="main.js"></script>
lib/docs/main.js
@@ -8,6 +8,8 @@ const NAV_MODES = {
 };
 
 (function () {
+  const domBanner = document.getElementById("banner");
+  const domMain = document.getElementById("main");
   const domStatus = document.getElementById("status");
   const domSectNav = document.getElementById("sectNav");
   const domListNav = document.getElementById("listNav");
@@ -65,10 +67,18 @@ const NAV_MODES = {
   const domTdZigVer = document.getElementById("tdZigVer");
   const domHdrName = document.getElementById("hdrName");
   const domHelpModal = document.getElementById("helpModal");
+  const domSearchKeys = document.getElementById("searchKeys");
+  const domPrefsModal = document.getElementById("prefsModal");
   const domSearchPlaceholder = document.getElementById("searchPlaceholder");
   const sourceFileUrlTemplate = "src/{{mod}}/{{file}}.html#L{{line}}"
   const domLangRefLink = document.getElementById("langRefLink");
 
+  const domPrefSlashSearch = document.getElementById("prefSlashSearch");
+  const prefs = getLocalStorage();
+  loadPrefs();
+
+  domPrefSlashSearch.addEventListener("change", () => setPrefSlashSearch(domPrefSlashSearch.checked));
+
   let searchTimer = null;
   let searchTrimResults = true;
 
@@ -127,21 +137,21 @@ const NAV_MODES = {
   window.guideSearch = guidesSearchIndex;
   parseGuides();
 
-  // identifiers can contain '?' so we want to allow typing
-  // the question mark when the search is focused instead of toggling the help modal
-  let canToggleHelpModal = true;
+  // identifiers can contain modal trigger characters so we want to allow typing
+  // such characters when the search is focused instead of toggling the modal
+  let canToggleModal = true;
 
   domSearch.disabled = false;
   domSearch.addEventListener("keydown", onSearchKeyDown, false);
   domSearch.addEventListener("input", onSearchInput, false);
   domSearch.addEventListener("focus", ev => {
     domSearchPlaceholder.classList.add("hidden");
-    canToggleHelpModal = false;
+    canToggleModal = false;
   });
   domSearch.addEventListener("blur", ev => {
     if (domSearch.value.length == 0)
       domSearchPlaceholder.classList.remove("hidden");
-    canToggleHelpModal = true;
+    canToggleModal = true;
   });
   domSectSearchAllResultsLink.addEventListener('click', onClickSearchShowAllResults, false);
   function onClickSearchShowAllResults(ev) {
@@ -156,10 +166,13 @@ const NAV_MODES = {
   }
 
   // make the modal disappear if you click outside it
-  domHelpModal.addEventListener("click", ev => {
-    if (ev.target.className == "help-modal")
-      domHelpModal.classList.add("hidden");
-  });
+  function handleModalClick(ev) {
+    if (ev.target.classList.contains("modal-container")) {
+      hideModal(this);
+    }
+  }
+  domHelpModal.addEventListener("click", handleModalClick);
+  domPrefsModal.addEventListener("click", handleModalClick);
 
   window.addEventListener("hashchange", onHashChange, false);
   window.addEventListener("keydown", onWindowKeyDown, false);
@@ -3996,8 +4009,12 @@ function addDeclToSearchResults(decl, declIndex, modNames, item, list, stack) {
 
   // hide the modal if it's visible or return to the previous result page and unfocus the search
   function onEscape(ev) {
-    if (!domHelpModal.classList.contains("hidden")) {
-      domHelpModal.classList.add("hidden");
+    if (isModalVisible(domHelpModal)) {
+      hideModal(domHelpModal);
+      ev.preventDefault();
+      ev.stopPropagation();
+    } else if (isModalVisible(domPrefsModal)) {
+      hideModal(domPrefsModal);
       ev.preventDefault();
       ev.stopPropagation();
     } else {
@@ -4110,8 +4127,10 @@ function addDeclToSearchResults(decl, declIndex, modNames, item, list, stack) {
         onEscape(ev);
         break;
       case "/":
+        if (!getPrefSlashSearch()) break;
+        // fallthrough
       case "s":
-        if (domHelpModal.classList.contains("hidden")) {
+        if (!isModalVisible(domHelpModal) && !isModalVisible(domPrefsModal)) {
           if (ev.target == domSearch) break;
 
           domSearch.focus();
@@ -4123,28 +4142,65 @@ function addDeclToSearchResults(decl, declIndex, modNames, item, list, stack) {
         }
         break;
       case "?":
-        if (!canToggleHelpModal) break;
+        if (!canToggleModal) break;
+
+        if (isModalVisible(domPrefsModal)) {
+          hideModal(domPrefsModal);
+        }
 
         // toggle the help modal
-        if (!domHelpModal.classList.contains("hidden")) {
-            onEscape(ev);
+        if (isModalVisible(domHelpModal)) {
+            hideModal(domHelpModal);
         } else {
-            ev.preventDefault();
-            ev.stopPropagation();
-            showHelpModal();
+            showModal(domHelpModal);
         }
+        ev.preventDefault();
+        ev.stopPropagation();
         break;
+      case "p":
+        if (!canToggleModal) break;
+
+        if (isModalVisible(domHelpModal)) {
+          hideModal(domHelpModal);
+        }
+
+        // toggle the preferences modal
+        if (isModalVisible(domPrefsModal)) {
+          hideModal(domPrefsModal);
+        } else {
+          showModal(domPrefsModal);
+        }
+        ev.preventDefault();
+        ev.stopPropagation();
     }
   }
 
-  function showHelpModal() {
-    domHelpModal.classList.remove("hidden");
-    domHelpModal.style.left =
-      window.innerWidth / 2 - domHelpModal.clientWidth / 2 + "px";
-    domHelpModal.style.top =
-      window.innerHeight / 2 - domHelpModal.clientHeight / 2 + "px";
-    domHelpModal.focus();
+  function isModalVisible(modal) {
+    return !modal.classList.contains("hidden");
+  }
+
+  function showModal(modal) {
+    modal.classList.remove("hidden");
+    modal.style.left =
+      window.innerWidth / 2 - modal.clientWidth / 2 + "px";
+    modal.style.top =
+      window.innerHeight / 2 - modal.clientHeight / 2 + "px";
+    const firstInput = modal.querySelector("input");
+    if (firstInput) {
+      firstInput.focus();
+    } else {
+      modal.focus();
+    }
     domSearch.blur();
+    domBanner.inert = true;
+    domMain.inert = true;
+  }
+
+  function hideModal(modal) {
+    modal.classList.add("hidden");
+    domBanner.inert = false;
+    domMain.inert = false;
+    modal.blur();
   }
 
   function clearAsyncSearch() {
@@ -4678,6 +4734,47 @@ function addDeclToSearchResults(decl, declIndex, modNames, item, list, stack) {
     }
   }
 
+  function getLocalStorage() {
+    if ("localStorage" in window) {
+      try {
+        return window.localStorage;
+      } catch (ignored) {
+        // localStorage may be disabled (SecurityError)
+      }
+    }
+    // If localStorage isn't available, persist preferences only for the current session
+    const sessionPrefs = {};
+    return {
+      getItem(key) {
+        return key in sessionPrefs ? sessionPrefs[key] : null;
+      },
+      setItem(key, value) {
+        sessionPrefs[key] = String(value);
+      },
+    };
+  }
+
+  function loadPrefs() {
+    const storedPrefSlashSearch = prefs.getItem("slashSearch");
+    if (storedPrefSlashSearch === null) {
+      // Slash search defaults to enabled for all browsers except Firefox
+      setPrefSlashSearch(navigator.userAgent.indexOf("Firefox") === -1);
+    } else {
+      setPrefSlashSearch(storedPrefSlashSearch === "true");
+    }
+  }
+
+  function getPrefSlashSearch() {
+    return prefs.getItem("slashSearch") === "true";
+  }
+
+  function setPrefSlashSearch(enabled) {
+    prefs.setItem("slashSearch", String(enabled));
+    domPrefSlashSearch.checked = enabled;
+    const searchKeys = enabled ? "<kbd>/</kbd> or <kbd>s</kbd>" : "<kbd>s</kbd>";
+    domSearchKeys.innerHTML = searchKeys;
+    domSearchPlaceholder.innerHTML = searchKeys + " to search, <kbd>?</kbd> for more options";
+  }
 })();
 
 function toggleExpand(event) {