| ... | ... | @@ -8,6 +8,8 @@ const NAV_MODES = { |
| 8 | 8 | }; |
| 9 | 9 | |
| 10 | 10 | (function () { |
| 11 | const domBanner = document.getElementById("banner"); |
| 12 | const domMain = document.getElementById("main"); |
| 11 | 13 | const domStatus = document.getElementById("status"); |
| 12 | 14 | const domSectNav = document.getElementById("sectNav"); |
| 13 | 15 | const domListNav = document.getElementById("listNav"); |
| ... | ... | @@ -65,10 +67,18 @@ const NAV_MODES = { |
| 65 | 67 | const domTdZigVer = document.getElementById("tdZigVer"); |
| 66 | 68 | const domHdrName = document.getElementById("hdrName"); |
| 67 | 69 | const domHelpModal = document.getElementById("helpModal"); |
| 70 | const domSearchKeys = document.getElementById("searchKeys"); |
| 71 | const domPrefsModal = document.getElementById("prefsModal"); |
| 68 | 72 | const domSearchPlaceholder = document.getElementById("searchPlaceholder"); |
| 69 | 73 | const sourceFileUrlTemplate = "src/{{mod}}/{{file}}.html#L{{line}}" |
| 70 | 74 | const domLangRefLink = document.getElementById("langRefLink"); |
| 71 | 75 | |
| 76 | const domPrefSlashSearch = document.getElementById("prefSlashSearch"); |
| 77 | const prefs = getLocalStorage(); |
| 78 | loadPrefs(); |
| 79 | |
| 80 | domPrefSlashSearch.addEventListener("change", () => setPrefSlashSearch(domPrefSlashSearch.checked)); |
| 81 | |
| 72 | 82 | let searchTimer = null; |
| 73 | 83 | let searchTrimResults = true; |
| 74 | 84 | |
| ... | ... | @@ -127,21 +137,21 @@ const NAV_MODES = { |
| 127 | 137 | window.guideSearch = guidesSearchIndex; |
| 128 | 138 | parseGuides(); |
| 129 | 139 | |
| 130 | | // identifiers can contain '?' so we want to allow typing |
| 131 | | // the question mark when the search is focused instead of toggling the help modal |
| 132 | | let canToggleHelpModal = true; |
| 140 | // identifiers can contain modal trigger characters so we want to allow typing |
| 141 | // such characters when the search is focused instead of toggling the modal |
| 142 | let canToggleModal = true; |
| 133 | 143 | |
| 134 | 144 | domSearch.disabled = false; |
| 135 | 145 | domSearch.addEventListener("keydown", onSearchKeyDown, false); |
| 136 | 146 | domSearch.addEventListener("input", onSearchInput, false); |
| 137 | 147 | domSearch.addEventListener("focus", ev => { |
| 138 | 148 | domSearchPlaceholder.classList.add("hidden"); |
| 139 | | canToggleHelpModal = false; |
| 149 | canToggleModal = false; |
| 140 | 150 | }); |
| 141 | 151 | domSearch.addEventListener("blur", ev => { |
| 142 | 152 | if (domSearch.value.length == 0) |
| 143 | 153 | domSearchPlaceholder.classList.remove("hidden"); |
| 144 | | canToggleHelpModal = true; |
| 154 | canToggleModal = true; |
| 145 | 155 | }); |
| 146 | 156 | domSectSearchAllResultsLink.addEventListener('click', onClickSearchShowAllResults, false); |
| 147 | 157 | function onClickSearchShowAllResults(ev) { |
| ... | ... | @@ -156,10 +166,13 @@ const NAV_MODES = { |
| 156 | 166 | } |
| 157 | 167 | |
| 158 | 168 | // make the modal disappear if you click outside it |
| 159 | | domHelpModal.addEventListener("click", ev => { |
| 160 | | if (ev.target.className == "help-modal") |
| 161 | | domHelpModal.classList.add("hidden"); |
| 162 | | }); |
| 169 | function handleModalClick(ev) { |
| 170 | if (ev.target.classList.contains("modal-container")) { |
| 171 | hideModal(this); |
| 172 | } |
| 173 | } |
| 174 | domHelpModal.addEventListener("click", handleModalClick); |
| 175 | domPrefsModal.addEventListener("click", handleModalClick); |
| 163 | 176 | |
| 164 | 177 | window.addEventListener("hashchange", onHashChange, false); |
| 165 | 178 | window.addEventListener("keydown", onWindowKeyDown, false); |
| ... | ... | @@ -3996,8 +4009,12 @@ function addDeclToSearchResults(decl, declIndex, modNames, item, list, stack) { |
| 3996 | 4009 | |
| 3997 | 4010 | // hide the modal if it's visible or return to the previous result page and unfocus the search |
| 3998 | 4011 | function onEscape(ev) { |
| 3999 | | if (!domHelpModal.classList.contains("hidden")) { |
| 4000 | | domHelpModal.classList.add("hidden"); |
| 4012 | if (isModalVisible(domHelpModal)) { |
| 4013 | hideModal(domHelpModal); |
| 4014 | ev.preventDefault(); |
| 4015 | ev.stopPropagation(); |
| 4016 | } else if (isModalVisible(domPrefsModal)) { |
| 4017 | hideModal(domPrefsModal); |
| 4001 | 4018 | ev.preventDefault(); |
| 4002 | 4019 | ev.stopPropagation(); |
| 4003 | 4020 | } else { |
| ... | ... | @@ -4110,8 +4127,10 @@ function addDeclToSearchResults(decl, declIndex, modNames, item, list, stack) { |
| 4110 | 4127 | onEscape(ev); |
| 4111 | 4128 | break; |
| 4112 | 4129 | case "/": |
| 4130 | if (!getPrefSlashSearch()) break; |
| 4131 | // fallthrough |
| 4113 | 4132 | case "s": |
| 4114 | | if (domHelpModal.classList.contains("hidden")) { |
| 4133 | if (!isModalVisible(domHelpModal) && !isModalVisible(domPrefsModal)) { |
| 4115 | 4134 | if (ev.target == domSearch) break; |
| 4116 | 4135 | |
| 4117 | 4136 | domSearch.focus(); |
| ... | ... | @@ -4123,28 +4142,65 @@ function addDeclToSearchResults(decl, declIndex, modNames, item, list, stack) { |
| 4123 | 4142 | } |
| 4124 | 4143 | break; |
| 4125 | 4144 | case "?": |
| 4126 | | if (!canToggleHelpModal) break; |
| 4145 | if (!canToggleModal) break; |
| 4146 | |
| 4147 | if (isModalVisible(domPrefsModal)) { |
| 4148 | hideModal(domPrefsModal); |
| 4149 | } |
| 4127 | 4150 | |
| 4128 | 4151 | // toggle the help modal |
| 4129 | | if (!domHelpModal.classList.contains("hidden")) { |
| 4130 | | onEscape(ev); |
| 4152 | if (isModalVisible(domHelpModal)) { |
| 4153 | hideModal(domHelpModal); |
| 4131 | 4154 | } else { |
| 4132 | | ev.preventDefault(); |
| 4133 | | ev.stopPropagation(); |
| 4134 | | showHelpModal(); |
| 4155 | showModal(domHelpModal); |
| 4135 | 4156 | } |
| 4157 | ev.preventDefault(); |
| 4158 | ev.stopPropagation(); |
| 4136 | 4159 | break; |
| 4160 | case "p": |
| 4161 | if (!canToggleModal) break; |
| 4162 | |
| 4163 | if (isModalVisible(domHelpModal)) { |
| 4164 | hideModal(domHelpModal); |
| 4165 | } |
| 4166 | |
| 4167 | // toggle the preferences modal |
| 4168 | if (isModalVisible(domPrefsModal)) { |
| 4169 | hideModal(domPrefsModal); |
| 4170 | } else { |
| 4171 | showModal(domPrefsModal); |
| 4172 | } |
| 4173 | ev.preventDefault(); |
| 4174 | ev.stopPropagation(); |
| 4137 | 4175 | } |
| 4138 | 4176 | } |
| 4139 | 4177 | |
| 4140 | | function showHelpModal() { |
| 4141 | | domHelpModal.classList.remove("hidden"); |
| 4142 | | domHelpModal.style.left = |
| 4143 | | window.innerWidth / 2 - domHelpModal.clientWidth / 2 + "px"; |
| 4144 | | domHelpModal.style.top = |
| 4145 | | window.innerHeight / 2 - domHelpModal.clientHeight / 2 + "px"; |
| 4146 | | domHelpModal.focus(); |
| 4178 | function isModalVisible(modal) { |
| 4179 | return !modal.classList.contains("hidden"); |
| 4180 | } |
| 4181 | |
| 4182 | function showModal(modal) { |
| 4183 | modal.classList.remove("hidden"); |
| 4184 | modal.style.left = |
| 4185 | window.innerWidth / 2 - modal.clientWidth / 2 + "px"; |
| 4186 | modal.style.top = |
| 4187 | window.innerHeight / 2 - modal.clientHeight / 2 + "px"; |
| 4188 | const firstInput = modal.querySelector("input"); |
| 4189 | if (firstInput) { |
| 4190 | firstInput.focus(); |
| 4191 | } else { |
| 4192 | modal.focus(); |
| 4193 | } |
| 4147 | 4194 | domSearch.blur(); |
| 4195 | domBanner.inert = true; |
| 4196 | domMain.inert = true; |
| 4197 | } |
| 4198 | |
| 4199 | function hideModal(modal) { |
| 4200 | modal.classList.add("hidden"); |
| 4201 | domBanner.inert = false; |
| 4202 | domMain.inert = false; |
| 4203 | modal.blur(); |
| 4148 | 4204 | } |
| 4149 | 4205 | |
| 4150 | 4206 | function clearAsyncSearch() { |
| ... | ... | @@ -4678,6 +4734,47 @@ function addDeclToSearchResults(decl, declIndex, modNames, item, list, stack) { |
| 4678 | 4734 | } |
| 4679 | 4735 | } |
| 4680 | 4736 | |
| 4737 | function getLocalStorage() { |
| 4738 | if ("localStorage" in window) { |
| 4739 | try { |
| 4740 | return window.localStorage; |
| 4741 | } catch (ignored) { |
| 4742 | // localStorage may be disabled (SecurityError) |
| 4743 | } |
| 4744 | } |
| 4745 | // If localStorage isn't available, persist preferences only for the current session |
| 4746 | const sessionPrefs = {}; |
| 4747 | return { |
| 4748 | getItem(key) { |
| 4749 | return key in sessionPrefs ? sessionPrefs[key] : null; |
| 4750 | }, |
| 4751 | setItem(key, value) { |
| 4752 | sessionPrefs[key] = String(value); |
| 4753 | }, |
| 4754 | }; |
| 4755 | } |
| 4756 | |
| 4757 | function loadPrefs() { |
| 4758 | const storedPrefSlashSearch = prefs.getItem("slashSearch"); |
| 4759 | if (storedPrefSlashSearch === null) { |
| 4760 | // Slash search defaults to enabled for all browsers except Firefox |
| 4761 | setPrefSlashSearch(navigator.userAgent.indexOf("Firefox") === -1); |
| 4762 | } else { |
| 4763 | setPrefSlashSearch(storedPrefSlashSearch === "true"); |
| 4764 | } |
| 4765 | } |
| 4766 | |
| 4767 | function getPrefSlashSearch() { |
| 4768 | return prefs.getItem("slashSearch") === "true"; |
| 4769 | } |
| 4770 | |
| 4771 | function setPrefSlashSearch(enabled) { |
| 4772 | prefs.setItem("slashSearch", String(enabled)); |
| 4773 | domPrefSlashSearch.checked = enabled; |
| 4774 | const searchKeys = enabled ? "<kbd>/</kbd> or <kbd>s</kbd>" : "<kbd>s</kbd>"; |
| 4775 | domSearchKeys.innerHTML = searchKeys; |
| 4776 | domSearchPlaceholder.innerHTML = searchKeys + " to search, <kbd>?</kbd> for more options"; |
| 4777 | } |
| 4681 | 4778 | })(); |
| 4682 | 4779 | |
| 4683 | 4780 | function toggleExpand(event) { |