@@ -2,6 +2,7 @@ package dotty.tools.scaladoc
2
2
3
3
import utils .HTML ._
4
4
5
+ import scala .scalajs .js .Date
5
6
import org .scalajs .dom ._
6
7
import org .scalajs .dom .ext ._
7
8
import org .scalajs .dom .html .Input
@@ -16,7 +17,7 @@ class SearchbarComponent(engine: SearchbarEngine, inkuireEngine: InkuireJSSearch
16
17
val initialChunkSize = 5
17
18
val resultsChunkSize = 20
18
19
extension (p : PageEntry )
19
- def toHTML =
20
+ def toHTML ( boldChars : Set [ Int ]) =
20
21
val location = if (p.isLocationExternal) {
21
22
p.location
22
23
} else {
@@ -25,7 +26,7 @@ class SearchbarComponent(engine: SearchbarEngine, inkuireEngine: InkuireJSSearch
25
26
26
27
div(cls := " scaladoc-searchbar-row monospace" , " result" := " " )(
27
28
a(href := location)(
28
- p.fullName,
29
+ p.fullName.zipWithIndex.map((c, i) => if boldChars.contains(i) then b(c.toString) else c.toString) ,
29
30
span(cls := " pull-right scaladoc-searchbar-location" )(p.description)
30
31
).tap { _.onclick = (event : Event ) =>
31
32
if (document.body.contains(rootDiv)) {
@@ -63,14 +64,29 @@ class SearchbarComponent(engine: SearchbarEngine, inkuireEngine: InkuireJSSearch
63
64
})
64
65
}
65
66
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 = " " ) =
67
83
div(cls := " scaladoc-searchbar-row monospace" , " divider" := " " )(
68
- span(cls := s " micon ${kind.take(2 )}" ),
84
+ span(cls := s " micon ${kind.take(2 )} $customClass " ),
69
85
span(kind)
70
86
)
71
87
72
88
def handleNewFluffQuery (matchers : List [Matchers ]) =
73
- val result = engine.query(matchers)
89
+ val result : List [( PageEntry , Set [ Int ])] = engine.query(matchers)
74
90
val fragment = document.createDocumentFragment()
75
91
def createLoadMoreElement =
76
92
div(cls := " scaladoc-searchbar-row monospace" , " loadmore" := " " )(
@@ -81,10 +97,10 @@ class SearchbarComponent(engine: SearchbarEngine, inkuireEngine: InkuireJSSearch
81
97
.addEventListener(" mouseover" , _ => handleHover(loadMoreElement))
82
98
}
83
99
84
- result.groupBy(_.kind).map {
100
+ result.groupBy(_._1. kind).map {
85
101
case (kind, entries) =>
86
102
val kindSeparator = createKindSeparator(kind)
87
- val htmlEntries = entries.map(_ .toHTML)
103
+ val htmlEntries = entries.map((p, set) => p .toHTML(set) )
88
104
val loadMoreElement = createLoadMoreElement
89
105
def loadMoreResults (entries : List [raw.HTMLElement ]): Unit = {
90
106
loadMoreElement.onclick = (event : Event ) => {
@@ -109,9 +125,26 @@ class SearchbarComponent(engine: SearchbarEngine, inkuireEngine: InkuireJSSearch
109
125
}
110
126
111
127
resultsDiv.scrollTop = 0
112
- while (resultsDiv.hasChildNodes()) resultsDiv.removeChild(resultsDiv.lastChild)
113
128
resultsDiv.appendChild(fragment)
114
129
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
+
115
148
def createLoadingAnimation : raw.HTMLElement =
116
149
div(cls := " loading-wrapper" )(
117
150
div(cls := " loading" )
@@ -124,35 +157,33 @@ class SearchbarComponent(engine: SearchbarEngine, inkuireEngine: InkuireJSSearch
124
157
125
158
var timeoutHandle : SetTimeoutHandle = null
126
159
def handleNewQuery (query : String ) =
127
- clearTimeout(timeoutHandle)
128
160
resultsDiv.scrollTop = 0
129
161
resultsDiv.onscroll = (event : Event ) => { }
130
- def clearResults () = while (resultsDiv.hasChildNodes()) resultsDiv.removeChild(resultsDiv.lastChild)
131
162
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)
150
185
}
151
- } { (s : String ) =>
152
- resultsDiv.removeChild(loading)
153
- resultsDiv.appendChild(s.toHTMLError)
154
- }
155
- }
186
+ }
156
187
}
157
188
158
189
private val searchIcon : html.Button =
@@ -173,9 +204,12 @@ class SearchbarComponent(engine: SearchbarEngine, inkuireEngine: InkuireJSSearch
173
204
private val inputElem : html.Input =
174
205
input(id := " scaladoc-searchbar-input" , `type` := " search" , `placeholder`:= " Find anything" ).tap { element =>
175
206
element.addEventListener(" input" , { e =>
207
+ clearTimeout(timeoutHandle)
176
208
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)
179
213
})
180
214
181
215
element.autocomplete = " off"
@@ -184,6 +218,8 @@ class SearchbarComponent(engine: SearchbarEngine, inkuireEngine: InkuireJSSearch
184
218
private val resultsDiv : html.Div =
185
219
div(id := " scaladoc-searchbar-results" )
186
220
221
+ def clearResults () = while (resultsDiv.hasChildNodes()) resultsDiv.removeChild(resultsDiv.lastChild)
222
+
187
223
private val rootHiddenClasses = " hidden"
188
224
private val rootShowClasses = " "
189
225
@@ -291,7 +327,7 @@ class SearchbarComponent(engine: SearchbarEngine, inkuireEngine: InkuireJSSearch
291
327
private def handleEscape () = {
292
328
// clear the search input and close the search
293
329
inputElem.value = " "
294
- showHints( )
330
+ inputElem.dispatchEvent( new Event ( " input " ) )
295
331
document.body.removeChild(rootDiv)
296
332
}
297
333
@@ -321,7 +357,6 @@ class SearchbarComponent(engine: SearchbarEngine, inkuireEngine: InkuireJSSearch
321
357
}
322
358
323
359
private def showHints () = {
324
- def clearResults () = while (resultsDiv.hasChildNodes()) resultsDiv.removeChild(resultsDiv.lastChild)
325
360
val hintsDiv = div(cls := " searchbar-hints" )(
326
361
span(cls := " lightbulb" ),
327
362
h1(cls := " body-medium" )(" A bunch of search hints to make your life easier" ),
@@ -339,8 +374,7 @@ class SearchbarComponent(engine: SearchbarEngine, inkuireEngine: InkuireJSSearch
339
374
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" )),
340
375
)
341
376
)
342
- clearResults()
343
377
resultsDiv.appendChild(hintsDiv)
344
378
}
345
379
346
- showHints( )
380
+ inputElem.dispatchEvent( new Event ( " input " ) )
0 commit comments