From 89a6409cd01b7d123e752b42ee64597432bad63b Mon Sep 17 00:00:00 2001 From: Dylan Halperin Date: Sat, 22 May 2021 21:34:38 -0400 Subject: [PATCH 1/2] Improvements to search UX Pressing "S" while not focused on an input/textarea will now open the search. Pressing "F" while not focused on an input/textarea will focus the member filter input. Pressing Escape while focused on the search/filter will clear the input and close/unfocus it Search result links are now treated as block elements so you can click anywhere inside the rectangle rather than specifically on the result text. --- scaladoc-js/resources/scaladoc-searchbar.css | 8 ++++++ .../src/searchbar/SearchbarComponent.scala | 26 +++++++++++++++++-- .../dotty_res/scripts/components/Input.js | 12 +++++++++ scaladoc/resources/dotty_res/scripts/ux.js | 14 ++++++++++ 4 files changed, 58 insertions(+), 2 deletions(-) diff --git a/scaladoc-js/resources/scaladoc-searchbar.css b/scaladoc-js/resources/scaladoc-searchbar.css index 035adbee6555..1bd591248ef0 100644 --- a/scaladoc-js/resources/scaladoc-searchbar.css +++ b/scaladoc-js/resources/scaladoc-searchbar.css @@ -88,6 +88,14 @@ .scaladoc-searchbar-result a { color: #1f2326; + /* for some reason, with display:block if there's a wrap between the + * search result text and the location span, the dead space to the + * left of the location span doesn't get treated as part of the block, + * which defeats the purpose of making the a block element. + * But inline-block with width:100% works as desired. + */ + display: inline-block; + width: 100%; } .scaladoc-searchbar-result .scaladoc-searchbar-location { diff --git a/scaladoc-js/src/searchbar/SearchbarComponent.scala b/scaladoc-js/src/searchbar/SearchbarComponent.scala index 20f7842ad749..adbea3acc3bc 100644 --- a/scaladoc-js/src/searchbar/SearchbarComponent.scala +++ b/scaladoc-js/src/searchbar/SearchbarComponent.scala @@ -21,7 +21,7 @@ class SearchbarComponent(val callback: (String) => List[PageEntry]): location.textContent = p.description wrapper.appendChild(resultA) - wrapper.appendChild(location) + resultA.appendChild(location) wrapper.addEventListener("mouseover", { case e: MouseEvent => handleHover(wrapper) }) @@ -59,6 +59,8 @@ class SearchbarComponent(val callback: (String) => List[PageEntry]): document.body.appendChild(rootDiv) input.focus() } + // open the search if the user hits the `s` key when not focused on a text input + document.body.addEventListener("keydown", (e: KeyboardEvent) => handleGlobalKeyDown(e)) val element = createNestingDiv("search-content")( createNestingDiv("search-container")( @@ -70,7 +72,6 @@ class SearchbarComponent(val callback: (String) => List[PageEntry]): document.getElementById("scaladoc-searchBar").appendChild(element) element - private val input: html.Input = val element = document.createElement("input").asInstanceOf[html.Input] element.id = "scaladoc-searchbar-input" @@ -106,6 +107,7 @@ class SearchbarComponent(val callback: (String) => List[PageEntry]): if e.keyCode == 40 then handleArrowDown() else if e.keyCode == 38 then handleArrowUp() else if e.keyCode == 13 then handleEnter() + else if e.keyCode == 27 then handleEscape() }) element.id = "scaladoc-searchbar" element.appendChild(input) @@ -146,6 +148,12 @@ class SearchbarComponent(val callback: (String) => List[PageEntry]): selectedElement.click() } } + private def handleEscape() = { + // clear the search input and close the search + input.value = "" + handleNewQuery("") + document.body.removeChild(rootDiv) + } private def handleHover(elem: html.Element) = { val selectedElement = resultsDiv.querySelector("[selected]") @@ -155,4 +163,18 @@ class SearchbarComponent(val callback: (String) => List[PageEntry]): elem.setAttribute("selected","") } + private def handleGlobalKeyDown(e: KeyboardEvent) = { + // if the user presses the "S" key while not focused on an input, open the search + if (e.key == "s") { + val tag = e.target.asInstanceOf[html.Element].tagName + if (tag != "INPUT" && tag != "TEXTAREA") { + if (!document.body.contains(rootDiv)) { + document.body.appendChild(rootDiv) + // if we focus during the event handler, the `s` gets typed into the input + window.setTimeout(() => input.focus(), 1.0) + } + } + } + } + handleNewQuery("") diff --git a/scaladoc/resources/dotty_res/scripts/components/Input.js b/scaladoc/resources/dotty_res/scripts/components/Input.js index 0c7a449fa1e7..dbe6ad2d724a 100644 --- a/scaladoc/resources/dotty_res/scripts/components/Input.js +++ b/scaladoc/resources/dotty_res/scripts/components/Input.js @@ -4,15 +4,27 @@ class Input extends Component { this.inputRef = findRef(".filterableInput"); this.onChangeFn = withEvent(this.inputRef, "input", this.onInputChange); + this.onKeydownFn = withEvent(this.inputRef, "keydown", this.onKeydown); } onInputChange = ({ currentTarget: { value } }) => { this.props.onInputChange(value); }; + onKeydown = (e) => { + // if the user hits Escape while typing in the filter input, + // clear the filter and un-focus the input + if (e.keyCode == 27) { + this.inputRef.value = ''; + this.onInputChange(e); + setTimeout(() => this.inputRef.blur(), 1); + } + } + componentWillUnmount() { if (this.onChangeFn) { this.onChangeFn(); + this.onKeydownFn(); } } } diff --git a/scaladoc/resources/dotty_res/scripts/ux.js b/scaladoc/resources/dotty_res/scripts/ux.js index 710aa6ba3e05..63d284d2d93e 100644 --- a/scaladoc/resources/dotty_res/scripts/ux.js +++ b/scaladoc/resources/dotty_res/scripts/ux.js @@ -54,6 +54,20 @@ window.addEventListener("DOMContentLoaded", () => { hljs.registerLanguage("scala", highlightDotty); hljs.registerAliases(["dotty", "scala3"], "scala"); hljs.initHighlighting(); + + /* listen for the `F` key to be pressed, to focus on the member filter input (if it's present) */ + document.body.addEventListener('keydown', e => { + if (e.key == "f") { + const tag = e.target.tagName; + if (tag != "INPUT" && tag != "TEXTAREA") { + const filterInput = findRef('.documentableFilter input.filterableInput'); + if (filterInput != null) { + // if we focus during this event handler, the `f` key gets typed into the input + setTimeout(() => filterInput.focus(), 1); + } + } + } + }) }); var zoom; From 55b2a4079a17ccd85d9f0547a04c9f36962123b0 Mon Sep 17 00:00:00 2001 From: Dylan Halperin Date: Fri, 28 May 2021 12:06:14 -0400 Subject: [PATCH 2/2] also allow '/' to trigger the search --- scaladoc-js/src/searchbar/SearchbarComponent.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scaladoc-js/src/searchbar/SearchbarComponent.scala b/scaladoc-js/src/searchbar/SearchbarComponent.scala index adbea3acc3bc..1f8506f65ff5 100644 --- a/scaladoc-js/src/searchbar/SearchbarComponent.scala +++ b/scaladoc-js/src/searchbar/SearchbarComponent.scala @@ -165,10 +165,13 @@ class SearchbarComponent(val callback: (String) => List[PageEntry]): private def handleGlobalKeyDown(e: KeyboardEvent) = { // if the user presses the "S" key while not focused on an input, open the search - if (e.key == "s") { + if (e.key == "s" || e.key == "/") { val tag = e.target.asInstanceOf[html.Element].tagName if (tag != "INPUT" && tag != "TEXTAREA") { if (!document.body.contains(rootDiv)) { + // Firefox's "quick find" uses "/" as a trigger; prevent that. + e.preventDefault() + document.body.appendChild(rootDiv) // if we focus during the event handler, the `s` gets typed into the input window.setTimeout(() => input.focus(), 1.0)