| ... | @@ -8,6 +8,8 @@ const NAV_MODES = { | ... | @@ -8,6 +8,8 @@ const NAV_MODES = { |
| 8 | }; | 8 | }; |
| 9 | | 9 | |
| 10 | (function () { | 10 | (function () { |
| | 11 | const domBanner = document.getElementById("banner"); |
| | 12 | const domMain = document.getElementById("main"); |
| 11 | const domStatus = document.getElementById("status"); | 13 | const domStatus = document.getElementById("status"); |
| 12 | const domSectNav = document.getElementById("sectNav"); | 14 | const domSectNav = document.getElementById("sectNav"); |
| 13 | const domListNav = document.getElementById("listNav"); | 15 | const domListNav = document.getElementById("listNav"); |
| ... | @@ -65,10 +67,18 @@ const NAV_MODES = { | ... | @@ -65,10 +67,18 @@ const NAV_MODES = { |
| 65 | const domTdZigVer = document.getElementById("tdZigVer"); | 67 | const domTdZigVer = document.getElementById("tdZigVer"); |
| 66 | const domHdrName = document.getElementById("hdrName"); | 68 | const domHdrName = document.getElementById("hdrName"); |
| 67 | const domHelpModal = document.getElementById("helpModal"); | 69 | const domHelpModal = document.getElementById("helpModal"); |
| | 70 | const domSearchKeys = document.getElementById("searchKeys"); |
| | 71 | const domPrefsModal = document.getElementById("prefsModal"); |
| 68 | const domSearchPlaceholder = document.getElementById("searchPlaceholder"); | 72 | const domSearchPlaceholder = document.getElementById("searchPlaceholder"); |
| 69 | const sourceFileUrlTemplate = "src/{{mod}}/{{file}}.html#L{{line}}" | 73 | const sourceFileUrlTemplate = "src/{{mod}}/{{file}}.html#L{{line}}" |
| 70 | const domLangRefLink = document.getElementById("langRefLink"); | 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 | let searchTimer = null; | 82 | let searchTimer = null; |
| 73 | let searchTrimResults = true; | 83 | let searchTrimResults = true; |
| 74 | | 84 | |
| ... | @@ -127,21 +137,21 @@ const NAV_MODES = { | ... | @@ -127,21 +137,21 @@ const NAV_MODES = { |
| 127 | window.guideSearch = guidesSearchIndex; | 137 | window.guideSearch = guidesSearchIndex; |
| 128 | parseGuides(); | 138 | parseGuides(); |
| 129 | | 139 | |
| 130 | // identifiers can contain '?' so we want to allow typing | 140 | // identifiers can contain modal trigger characters so we want to allow typing |
| 131 | // the question mark when the search is focused instead of toggling the help modal | 141 | // such characters when the search is focused instead of toggling the modal |
| 132 | let canToggleHelpModal = true; | 142 | let canToggleModal = true; |
| 133 | | 143 | |
| 134 | domSearch.disabled = false; | 144 | domSearch.disabled = false; |
| 135 | domSearch.addEventListener("keydown", onSearchKeyDown, false); | 145 | domSearch.addEventListener("keydown", onSearchKeyDown, false); |
| 136 | domSearch.addEventListener("input", onSearchInput, false); | 146 | domSearch.addEventListener("input", onSearchInput, false); |
| 137 | domSearch.addEventListener("focus", ev => { | 147 | domSearch.addEventListener("focus", ev => { |
| 138 | domSearchPlaceholder.classList.add("hidden"); | 148 | domSearchPlaceholder.classList.add("hidden"); |
| 139 | canToggleHelpModal = false; | 149 | canToggleModal = false; |
| 140 | }); | 150 | }); |
| 141 | domSearch.addEventListener("blur", ev => { | 151 | domSearch.addEventListener("blur", ev => { |
| 142 | if (domSearch.value.length == 0) | 152 | if (domSearch.value.length == 0) |
| 143 | domSearchPlaceholder.classList.remove("hidden"); | 153 | domSearchPlaceholder.classList.remove("hidden"); |
| 144 | canToggleHelpModal = true; | 154 | canToggleModal = true; |
| 145 | }); | 155 | }); |
| 146 | domSectSearchAllResultsLink.addEventListener('click', onClickSearchShowAllResults, false); | 156 | domSectSearchAllResultsLink.addEventListener('click', onClickSearchShowAllResults, false); |
| 147 | function onClickSearchShowAllResults(ev) { | 157 | function onClickSearchShowAllResults(ev) { |
| ... | @@ -156,10 +166,13 @@ const NAV_MODES = { | ... | @@ -156,10 +166,13 @@ const NAV_MODES = { |
| 156 | } | 166 | } |
| 157 | | 167 | |
| 158 | // make the modal disappear if you click outside it | 168 | // make the modal disappear if you click outside it |
| 159 | domHelpModal.addEventListener("click", ev => { | 169 | function handleModalClick(ev) { |
| 160 | if (ev.target.className == "help-modal") | 170 | if (ev.target.classList.contains("modal-container")) { |
| 161 | domHelpModal.classList.add("hidden"); | 171 | hideModal(this); |
| 162 | }); | 172 | } |
| | 173 | } |
| | 174 | domHelpModal.addEventListener("click", handleModalClick); |
| | 175 | domPrefsModal.addEventListener("click", handleModalClick); |
| 163 | | 176 | |
| 164 | window.addEventListener("hashchange", onHashChange, false); | 177 | window.addEventListener("hashchange", onHashChange, false); |
| 165 | window.addEventListener("keydown", onWindowKeyDown, false); | 178 | window.addEventListener("keydown", onWindowKeyDown, false); |
| ... | @@ -3996,8 +4009,12 @@ function addDeclToSearchResults(decl, declIndex, modNames, item, list, stack) { | ... | @@ -3996,8 +4009,12 @@ function addDeclToSearchResults(decl, declIndex, modNames, item, list, stack) { |
| 3996 | | 4009 | |
| 3997 | // hide the modal if it's visible or return to the previous result page and unfocus the search | 4010 | // hide the modal if it's visible or return to the previous result page and unfocus the search |
| 3998 | function onEscape(ev) { | 4011 | function onEscape(ev) { |
| 3999 | if (!domHelpModal.classList.contains("hidden")) { | 4012 | if (isModalVisible(domHelpModal)) { |
| 4000 | domHelpModal.classList.add("hidden"); | 4013 | hideModal(domHelpModal); |
| | 4014 | ev.preventDefault(); |
| | 4015 | ev.stopPropagation(); |
| | 4016 | } else if (isModalVisible(domPrefsModal)) { |
| | 4017 | hideModal(domPrefsModal); |
| 4001 | ev.preventDefault(); | 4018 | ev.preventDefault(); |
| 4002 | ev.stopPropagation(); | 4019 | ev.stopPropagation(); |
| 4003 | } else { | 4020 | } else { |
| ... | @@ -4110,8 +4127,10 @@ function addDeclToSearchResults(decl, declIndex, modNames, item, list, stack) { | ... | @@ -4110,8 +4127,10 @@ function addDeclToSearchResults(decl, declIndex, modNames, item, list, stack) { |
| 4110 | onEscape(ev); | 4127 | onEscape(ev); |
| 4111 | break; | 4128 | break; |
| 4112 | case "/": | 4129 | case "/": |
| | 4130 | if (!getPrefSlashSearch()) break; |
| | 4131 | // fallthrough |
| 4113 | case "s": | 4132 | case "s": |
| 4114 | if (domHelpModal.classList.contains("hidden")) { | 4133 | if (!isModalVisible(domHelpModal) && !isModalVisible(domPrefsModal)) { |
| 4115 | if (ev.target == domSearch) break; | 4134 | if (ev.target == domSearch) break; |
| 4116 | | 4135 | |
| 4117 | domSearch.focus(); | 4136 | domSearch.focus(); |
| ... | @@ -4123,28 +4142,65 @@ function addDeclToSearchResults(decl, declIndex, modNames, item, list, stack) { | ... | @@ -4123,28 +4142,65 @@ function addDeclToSearchResults(decl, declIndex, modNames, item, list, stack) { |
| 4123 | } | 4142 | } |
| 4124 | break; | 4143 | break; |
| 4125 | case "?": | 4144 | case "?": |
| 4126 | if (!canToggleHelpModal) break; | 4145 | if (!canToggleModal) break; |
| | 4146 | |
| | 4147 | if (isModalVisible(domPrefsModal)) { |
| | 4148 | hideModal(domPrefsModal); |
| | 4149 | } |
| 4127 | | 4150 | |
| 4128 | // toggle the help modal | 4151 | // toggle the help modal |
| 4129 | if (!domHelpModal.classList.contains("hidden")) { | 4152 | if (isModalVisible(domHelpModal)) { |
| 4130 | onEscape(ev); | 4153 | hideModal(domHelpModal); |
| 4131 | } else { | 4154 | } else { |
| 4132 | ev.preventDefault(); | 4155 | showModal(domHelpModal); |
| 4133 | ev.stopPropagation(); | | |
| 4134 | showHelpModal(); | | |
| 4135 | } | 4156 | } |
| | 4157 | ev.preventDefault(); |
| | 4158 | ev.stopPropagation(); |
| 4136 | break; | 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() { | 4178 | function isModalVisible(modal) { |
| 4141 | domHelpModal.classList.remove("hidden"); | 4179 | return !modal.classList.contains("hidden"); |
| 4142 | domHelpModal.style.left = | 4180 | } |
| 4143 | window.innerWidth / 2 - domHelpModal.clientWidth / 2 + "px"; | 4181 | |
| 4144 | domHelpModal.style.top = | 4182 | function showModal(modal) { |
| 4145 | window.innerHeight / 2 - domHelpModal.clientHeight / 2 + "px"; | 4183 | modal.classList.remove("hidden"); |
| 4146 | domHelpModal.focus(); | 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 | domSearch.blur(); | 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 | function clearAsyncSearch() { | 4206 | function clearAsyncSearch() { |
| ... | @@ -4678,6 +4734,47 @@ function addDeclToSearchResults(decl, declIndex, modNames, item, list, stack) { | ... | @@ -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 | function toggleExpand(event) { | 4780 | function toggleExpand(event) { |