@@ -2,6 +2,7 @@ package dotty.tools.scaladoc
22
33import utils .HTML ._
44
5+ import scala .scalajs .js .Date
56import org .scalajs .dom ._
67import org .scalajs .dom .ext ._
78import org .scalajs .dom .html .Input
@@ -16,7 +17,7 @@ class SearchbarComponent(engine: SearchbarEngine, inkuireEngine: InkuireJSSearch
1617 val initialChunkSize = 5
1718 val resultsChunkSize = 20
1819 extension (p : PageEntry )
19- def toHTML =
20+ def toHTML ( boldChars : Set [ Int ]) =
2021 val location = if (p.isLocationExternal) {
2122 p.location
2223 } else {
@@ -25,7 +26,7 @@ class SearchbarComponent(engine: SearchbarEngine, inkuireEngine: InkuireJSSearch
2526
2627 div(cls := " scaladoc-searchbar-row monospace" , " result" := " " )(
2728 a(href := location)(
28- p.fullName,
29+ p.fullName.zipWithIndex.map((c, i) => if boldChars.contains(i) then b(c.toString) else c.toString) ,
2930 span(cls := " pull-right scaladoc-searchbar-location" )(p.description)
3031 ).tap { _.onclick = (event : Event ) =>
3132 if (document.body.contains(rootDiv)) {
@@ -63,14 +64,29 @@ class SearchbarComponent(engine: SearchbarEngine, inkuireEngine: InkuireJSSearch
6364 })
6465 }
6566
66- def createKindSeparator (kind : String ) =
67+ extension (rq : RecentQuery )
68+ def toHTML =
69+ div(cls := " scaladoc-searchbar-row monospace" , " result" := " " )(
70+ a(
71+ span(rq.query)
72+ )
73+ ).tap { _.addEventListener(" click" , _ => {
74+ inputElem.value = rq.query
75+ inputElem.dispatchEvent(new Event (" input" ))
76+ })
77+ }.tap { wrapper => wrapper.addEventListener(" mouseover" , {
78+ case e : MouseEvent => handleHover(wrapper)
79+ })
80+ }
81+
82+ def createKindSeparator (kind : String , customClass : String = " " ) =
6783 div(cls := " scaladoc-searchbar-row monospace" , " divider" := " " )(
68- span(cls := s " micon ${kind.take(2 )}" ),
84+ span(cls := s " micon ${kind.take(2 )} $customClass " ),
6985 span(kind)
7086 )
7187
7288 def handleNewFluffQuery (matchers : List [Matchers ]) =
73- val result = engine.query(matchers)
89+ val result : List [( PageEntry , Set [ Int ])] = engine.query(matchers)
7490 val fragment = document.createDocumentFragment()
7591 def createLoadMoreElement =
7692 div(cls := " scaladoc-searchbar-row monospace" , " loadmore" := " " )(
@@ -81,10 +97,10 @@ class SearchbarComponent(engine: SearchbarEngine, inkuireEngine: InkuireJSSearch
8197 .addEventListener(" mouseover" , _ => handleHover(loadMoreElement))
8298 }
8399
84- result.groupBy(_.kind).map {
100+ result.groupBy(_._1. kind).map {
85101 case (kind, entries) =>
86102 val kindSeparator = createKindSeparator(kind)
87- val htmlEntries = entries.map(_ .toHTML)
103+ val htmlEntries = entries.map((p, set) => p .toHTML(set) )
88104 val loadMoreElement = createLoadMoreElement
89105 def loadMoreResults (entries : List [raw.HTMLElement ]): Unit = {
90106 loadMoreElement.onclick = (event : Event ) => {
@@ -109,9 +125,26 @@ class SearchbarComponent(engine: SearchbarEngine, inkuireEngine: InkuireJSSearch
109125 }
110126
111127 resultsDiv.scrollTop = 0
112- while (resultsDiv.hasChildNodes()) resultsDiv.removeChild(resultsDiv.lastChild)
113128 resultsDiv.appendChild(fragment)
114129
130+ def handleRecentQueries (query : String ) = {
131+ val recentQueries = RecentQueryStorage .getData
132+ if query != " " then RecentQueryStorage .addEntry(RecentQuery (query, Date .now()))
133+ val matching = recentQueries
134+ .filterNot(rq => rq.query.equalsIgnoreCase(query)) // Don't show recent query that is equal to provided query
135+ .filter { rq => // Fuzzy search
136+ rq.query.foldLeft(query) { (pattern, nextChar) =>
137+ if ! pattern.isEmpty then {
138+ if pattern.head.toString.equalsIgnoreCase(nextChar.toString) then pattern.tail else pattern
139+ } else " "
140+ }.isEmpty
141+ }
142+ if matching.nonEmpty then {
143+ resultsDiv.appendChild(createKindSeparator(" Recently searched" , " fas fa-clock" ))
144+ matching.map(_.toHTML).foreach(resultsDiv.appendChild)
145+ }
146+ }
147+
115148 def createLoadingAnimation : raw.HTMLElement =
116149 div(cls := " loading-wrapper" )(
117150 div(cls := " loading" )
@@ -124,35 +157,33 @@ class SearchbarComponent(engine: SearchbarEngine, inkuireEngine: InkuireJSSearch
124157
125158 var timeoutHandle : SetTimeoutHandle = null
126159 def handleNewQuery (query : String ) =
127- clearTimeout(timeoutHandle)
128160 resultsDiv.scrollTop = 0
129161 resultsDiv.onscroll = (event : Event ) => { }
130- def clearResults () = while (resultsDiv.hasChildNodes()) resultsDiv.removeChild(resultsDiv.lastChild)
131162 val fragment = document.createDocumentFragment()
132- parser.parse(query) match {
133- case EngineMatchersQuery (matchers) =>
134- clearResults()
135- handleNewFluffQuery(matchers)
136- case BySignature (signature) =>
137- timeoutHandle = setTimeout(1 .second) {
138- val loading = createLoadingAnimation
139- val kindSeparator = createKindSeparator(" inkuire" )
140- clearResults()
141- resultsDiv.appendChild(loading)
142- resultsDiv.appendChild(kindSeparator)
143- inkuireEngine.query(query) { (m : InkuireMatch ) =>
144- val next = resultsDiv.children
145- .find(child => child.hasAttribute(" mq" ) && Integer .parseInt(child.getAttribute(" mq" )) > m.mq)
146- next.fold {
147- resultsDiv.appendChild(m.toHTML)
148- } { next =>
149- resultsDiv.insertBefore(m.toHTML, next)
163+ timeoutHandle = setTimeout(600 .millisecond) {
164+ clearResults()
165+ handleRecentQueries(query)
166+ parser.parse(query) match {
167+ case EngineMatchersQuery (matchers) =>
168+ handleNewFluffQuery(matchers)
169+ case BySignature (signature) =>
170+ val loading = createLoadingAnimation
171+ val kindSeparator = createKindSeparator(" inkuire" )
172+ resultsDiv.appendChild(loading)
173+ resultsDiv.appendChild(kindSeparator)
174+ inkuireEngine.query(query) { (m : InkuireMatch ) =>
175+ val next = resultsDiv.children
176+ .find(child => child.hasAttribute(" mq" ) && Integer .parseInt(child.getAttribute(" mq" )) > m.mq)
177+ next.fold {
178+ resultsDiv.appendChild(m.toHTML)
179+ } { next =>
180+ resultsDiv.insertBefore(m.toHTML, next)
181+ }
182+ } { (s : String ) =>
183+ resultsDiv.removeChild(loading)
184+ resultsDiv.appendChild(s.toHTMLError)
150185 }
151- } { (s : String ) =>
152- resultsDiv.removeChild(loading)
153- resultsDiv.appendChild(s.toHTMLError)
154- }
155- }
186+ }
156187 }
157188
158189 private val searchIcon : html.Button =
@@ -173,9 +204,12 @@ class SearchbarComponent(engine: SearchbarEngine, inkuireEngine: InkuireJSSearch
173204 private val inputElem : html.Input =
174205 input(id := " scaladoc-searchbar-input" , `type` := " search" , `placeholder`:= " Find anything" ).tap { element =>
175206 element.addEventListener(" input" , { e =>
207+ clearTimeout(timeoutHandle)
176208 val inputValue = e.target.asInstanceOf [html.Input ].value
177- if inputValue.isEmpty then showHints()
178- else handleNewQuery(inputValue)
209+ if inputValue.isEmpty then {
210+ clearResults()
211+ if RecentQueryStorage .isEmpty then showHints() else handleRecentQueries(" " )
212+ } else handleNewQuery(inputValue)
179213 })
180214
181215 element.autocomplete = " off"
@@ -184,6 +218,8 @@ class SearchbarComponent(engine: SearchbarEngine, inkuireEngine: InkuireJSSearch
184218 private val resultsDiv : html.Div =
185219 div(id := " scaladoc-searchbar-results" )
186220
221+ def clearResults () = while (resultsDiv.hasChildNodes()) resultsDiv.removeChild(resultsDiv.lastChild)
222+
187223 private val rootHiddenClasses = " hidden"
188224 private val rootShowClasses = " "
189225
@@ -291,7 +327,7 @@ class SearchbarComponent(engine: SearchbarEngine, inkuireEngine: InkuireJSSearch
291327 private def handleEscape () = {
292328 // clear the search input and close the search
293329 inputElem.value = " "
294- showHints( )
330+ inputElem.dispatchEvent( new Event ( " input " ) )
295331 document.body.removeChild(rootDiv)
296332 }
297333
@@ -321,7 +357,6 @@ class SearchbarComponent(engine: SearchbarEngine, inkuireEngine: InkuireJSSearch
321357 }
322358
323359 private def showHints () = {
324- def clearResults () = while (resultsDiv.hasChildNodes()) resultsDiv.removeChild(resultsDiv.lastChild)
325360 val hintsDiv = div(cls := " searchbar-hints" )(
326361 span(cls := " lightbulb" ),
327362 h1(cls := " body-medium" )(" A bunch of search hints to make your life easier" ),
@@ -339,8 +374,7 @@ class SearchbarComponent(engine: SearchbarEngine, inkuireEngine: InkuireJSSearch
339374 li(cls := " link body-small" )(" Availability of searching by inkuire depends on the configuration of Scaladoc. For more info, " , a(href := " https://docs.scala-lang.org/scala3/guides/scaladoc/search-engine.html" )(" the documentation" )),
340375 )
341376 )
342- clearResults()
343377 resultsDiv.appendChild(hintsDiv)
344378 }
345379
346- showHints( )
380+ inputElem.dispatchEvent( new Event ( " input " ) )
0 commit comments