@@ -9,18 +9,14 @@ import scala.concurrent.duration._
9
9
import java .net .URI
10
10
11
11
class SearchbarComponent (engine : SearchbarEngine , inkuireEngine : InkuireJSSearchEngine , parser : QueryParser ):
12
- val resultsChunkSize = 100
12
+ val resultsChunkSize = 5
13
13
extension (p : PageEntry )
14
14
def toHTML =
15
15
val wrapper = document.createElement(" div" ).asInstanceOf [html.Div ]
16
- wrapper.classList.add(" scaladoc-searchbar-result " )
17
- wrapper.classList.add( " scaladoc-searchbar- result-row " )
16
+ wrapper.classList.add(" scaladoc-searchbar-row " )
17
+ wrapper.setAttribute( " result" , " " )
18
18
wrapper.classList.add(" monospace" )
19
19
20
- val icon = document.createElement(" span" ).asInstanceOf [html.Span ]
21
- icon.classList.add(" micon" )
22
- icon.classList.add(p.kind.take(2 ))
23
-
24
20
val resultA = document.createElement(" a" ).asInstanceOf [html.Anchor ]
25
21
resultA.href =
26
22
if (p.isLocationExternal) {
@@ -39,7 +35,6 @@ class SearchbarComponent(engine: SearchbarEngine, inkuireEngine: InkuireJSSearch
39
35
location.classList.add(" scaladoc-searchbar-location" )
40
36
location.textContent = p.description
41
37
42
- wrapper.appendChild(icon)
43
38
wrapper.appendChild(resultA)
44
39
resultA.appendChild(location)
45
40
wrapper.addEventListener(" mouseover" , {
@@ -50,27 +45,22 @@ class SearchbarComponent(engine: SearchbarEngine, inkuireEngine: InkuireJSSearch
50
45
extension (m : InkuireMatch )
51
46
def toHTML =
52
47
val wrapper = document.createElement(" div" ).asInstanceOf [html.Div ]
53
- wrapper.classList.add(" scaladoc-searchbar-result" )
48
+ wrapper.classList.add(" scaladoc-searchbar-row" )
49
+ wrapper.setAttribute(" result" , " " )
50
+ wrapper.setAttribute(" inkuire-result" , " " )
54
51
wrapper.classList.add(" monospace" )
55
52
wrapper.setAttribute(" mq" , m.mq.toString)
56
53
57
- val resultDiv = document.createElement(" div" ).asInstanceOf [html.Div ]
58
- resultDiv.classList.add(" scaladoc-searchbar-result-row" )
59
-
60
- val icon = document.createElement(" span" ).asInstanceOf [html.Span ]
61
- icon.classList.add(" micon" )
62
- icon.classList.add(m.entryType.take(2 ))
63
-
64
54
val resultA = document.createElement(" a" ).asInstanceOf [html.Anchor ]
65
55
// Inkuire pageLocation should start with e (external)
66
56
// or i (internal). The rest of the string is an absolute
67
57
// or relative URL
68
- resultA.href =
58
+ resultA.href =
69
59
if (m.pageLocation(0 ) == 'e' ) {
70
60
m.pageLocation.substring(1 )
71
61
} else {
72
62
Globals .pathToRoot + m.pageLocation.substring(1 )
73
- }
63
+ }
74
64
resultA.text = m.functionName
75
65
resultA.onclick = (event : Event ) =>
76
66
if (document.body.contains(rootDiv)) {
@@ -92,9 +82,7 @@ class SearchbarComponent(engine: SearchbarEngine, inkuireEngine: InkuireJSSearch
92
82
signature.classList.add(" scaladoc-searchbar-inkuire-signature" )
93
83
signature.textContent = m.prettifiedSignature
94
84
95
- wrapper.appendChild(resultDiv)
96
- resultDiv.appendChild(icon)
97
- resultDiv.appendChild(resultA)
85
+ wrapper.appendChild(resultA)
98
86
resultA.appendChild(signature)
99
87
wrapper.appendChild(packageDiv)
100
88
packageDiv.appendChild(packageIcon)
@@ -104,29 +92,78 @@ class SearchbarComponent(engine: SearchbarEngine, inkuireEngine: InkuireJSSearch
104
92
})
105
93
wrapper
106
94
95
+ def createKindSeparator (kind : String ) =
96
+ val kindSeparator = document.createElement(" div" ).asInstanceOf [html.Div ]
97
+ val icon = document.createElement(" span" ).asInstanceOf [html.Span ]
98
+ icon.classList.add(" micon" )
99
+ icon.classList.add(kind.take(2 ))
100
+ val name = document.createElement(" span" ).asInstanceOf [html.Span ]
101
+ name.textContent = kind
102
+ kindSeparator.classList.add(" scaladoc-searchbar-row" )
103
+ kindSeparator.setAttribute(" divider" , " " )
104
+ kindSeparator.classList.add(" monospace" )
105
+ kindSeparator.appendChild(icon)
106
+ kindSeparator.appendChild(name)
107
+ kindSeparator
108
+
107
109
def handleNewFluffQuery (matchers : List [Matchers ]) =
108
- val result = engine.query(matchers).map(_.toHTML)
109
- resultsDiv.scrollTop = 0
110
- while (resultsDiv.hasChildNodes()) resultsDiv.removeChild(resultsDiv.lastChild)
110
+ val result = engine.query(matchers)
111
111
val fragment = document.createDocumentFragment()
112
- result.take(resultsChunkSize).foreach(fragment.appendChild)
113
- resultsDiv.appendChild(fragment)
114
- def loadMoreResults (result : List [raw.HTMLElement ]): Unit = {
115
- resultsDiv.onscroll = (event : Event ) => {
116
- if (resultsDiv.scrollHeight - resultsDiv.scrollTop == resultsDiv.clientHeight) {
117
- val fragment = document.createDocumentFragment()
118
- result.take(resultsChunkSize).foreach(fragment.appendChild)
119
- resultsDiv.appendChild(fragment)
120
- loadMoreResults(result.drop(resultsChunkSize))
112
+ def createLoadMoreElement =
113
+ val loadMoreElement = document.createElement(" div" ).asInstanceOf [html.Div ]
114
+ loadMoreElement.classList.add(" scaladoc-searchbar-row" )
115
+ loadMoreElement.setAttribute(" loadmore" , " " )
116
+ loadMoreElement.classList.add(" monospace" )
117
+ val anchor = document.createElement(" a" ).asInstanceOf [html.Anchor ]
118
+ anchor.text = " Show more..."
119
+ loadMoreElement.appendChild(anchor)
120
+ loadMoreElement.addEventListener(" mouseover" , _ => handleHover(loadMoreElement))
121
+ loadMoreElement
122
+ result.groupBy(_.kind).map {
123
+ case (kind, entries) =>
124
+ val kindSeparator = createKindSeparator(kind)
125
+ val htmlEntries = entries.map(_.toHTML)
126
+ val loadMoreElement = createLoadMoreElement
127
+ def loadMoreResults (entries : List [raw.HTMLElement ]): Unit = {
128
+ loadMoreElement.onclick = (event : Event ) => {
129
+ entries.take(resultsChunkSize).foreach(_.classList.remove(" hidden" ))
130
+ val nextElems = entries.drop(resultsChunkSize)
131
+ if nextElems.nonEmpty then loadMoreResults(nextElems) else loadMoreElement.classList.add(" hidden" )
132
+ }
121
133
}
122
- }
134
+
135
+ fragment.appendChild(kindSeparator)
136
+ htmlEntries.foreach(fragment.appendChild)
137
+ fragment.appendChild(loadMoreElement)
138
+
139
+ val nextElems = htmlEntries.drop(resultsChunkSize)
140
+ if nextElems.nonEmpty then {
141
+ nextElems.foreach(_.classList.add(" hidden" ))
142
+ loadMoreResults(nextElems)
143
+ } else {
144
+ loadMoreElement.classList.add(" hidden" )
145
+ }
146
+
123
147
}
124
- loadMoreResults(result.drop(resultsChunkSize))
148
+
149
+ resultsDiv.scrollTop = 0
150
+ while (resultsDiv.hasChildNodes()) resultsDiv.removeChild(resultsDiv.lastChild)
151
+ resultsDiv.appendChild(fragment)
152
+
153
+ def createLoadingAnimation : raw.HTMLElement = {
154
+ val loading = document.createElement(" div" ).asInstanceOf [html.Div ]
155
+ loading.classList.add(" loading-wrapper" )
156
+ val animation = document.createElement(" div" ).asInstanceOf [html.Div ]
157
+ animation.classList.add(" loading" )
158
+ loading.appendChild(animation)
159
+ loading
160
+ }
125
161
126
162
extension (s : String )
127
163
def toHTMLError =
128
164
val wrapper = document.createElement(" div" ).asInstanceOf [html.Div ]
129
- wrapper.classList.add(" scaladoc-searchbar-result" )
165
+ wrapper.classList.add(" scaladoc-searchbar-row" )
166
+ wrapper.classList.add(" scaladoc-searchbar-error" )
130
167
wrapper.classList.add(" monospace" )
131
168
132
169
val errorSpan = document.createElement(" span" ).asInstanceOf [html.Span ]
@@ -141,35 +178,30 @@ class SearchbarComponent(engine: SearchbarEngine, inkuireEngine: InkuireJSSearch
141
178
clearTimeout(timeoutHandle)
142
179
resultsDiv.scrollTop = 0
143
180
resultsDiv.onscroll = (event : Event ) => { }
144
- while (resultsDiv.hasChildNodes()) resultsDiv.removeChild(resultsDiv.lastChild)
181
+ def clearResults () = while (resultsDiv.hasChildNodes()) resultsDiv.removeChild(resultsDiv.lastChild)
145
182
val fragment = document.createDocumentFragment()
146
183
parser.parse(query) match {
147
184
case EngineMatchersQuery (matchers) =>
185
+ clearResults()
148
186
handleNewFluffQuery(matchers)
149
187
case BySignature (signature) =>
150
188
timeoutHandle = setTimeout(1 .second) {
151
- val properResultsDiv = document.createElement(" div" ).asInstanceOf [html.Div ]
152
- resultsDiv.appendChild(properResultsDiv)
153
- val loading = document.createElement(" div" ).asInstanceOf [html.Div ]
154
- loading.classList.add(" loading-wrapper" )
155
- val animation = document.createElement(" div" ).asInstanceOf [html.Div ]
156
- animation.classList.add(" loading" )
157
- loading.appendChild(animation)
158
- properResultsDiv.appendChild(loading)
189
+ val loading = createLoadingAnimation
190
+ val kindSeparator = createKindSeparator(" inkuire" )
191
+ clearResults()
192
+ resultsDiv.appendChild(loading)
193
+ resultsDiv.appendChild(kindSeparator)
159
194
inkuireEngine.query(query) { (m : InkuireMatch ) =>
160
- val next = properResultsDiv.children.foldLeft[Option [Element ]](None ) {
161
- case (acc, child) if ! acc.isEmpty => acc
162
- case (_, child) =>
163
- Option .when(child.hasAttribute(" mq" ) && Integer .parseInt(child.getAttribute(" mq" )) > m.mq)(child)
164
- }
195
+ val next = resultsDiv.children
196
+ .find(child => child.hasAttribute(" mq" ) && Integer .parseInt(child.getAttribute(" mq" )) > m.mq)
165
197
next.fold {
166
- properResultsDiv .appendChild(m.toHTML)
198
+ resultsDiv .appendChild(m.toHTML)
167
199
} { next =>
168
- properResultsDiv .insertBefore(m.toHTML, next)
200
+ resultsDiv .insertBefore(m.toHTML, next)
169
201
}
170
202
} { (s : String ) =>
171
- animation.classList.remove( " loading" )
172
- properResultsDiv .appendChild(s.toHTMLError)
203
+ resultsDiv.removeChild( loading)
204
+ resultsDiv .appendChild(s.toHTMLError)
173
205
}
174
206
}
175
207
}
@@ -226,7 +258,7 @@ class SearchbarComponent(engine: SearchbarEngine, inkuireEngine: InkuireJSSearch
226
258
searchIcon.addEventListener(" mousedown" , (e : Event ) => e.stopPropagation())
227
259
document.body.addEventListener(" mousedown" , (e : Event ) =>
228
260
if (document.body.contains(element)) {
229
- document.body.removeChild(element )
261
+ handleEscape( )
230
262
}
231
263
)
232
264
element.addEventListener(" keydown" , {
@@ -245,32 +277,50 @@ class SearchbarComponent(engine: SearchbarEngine, inkuireEngine: InkuireJSSearch
245
277
val selectedElement = resultsDiv.querySelector(" [selected]" )
246
278
if selectedElement != null then {
247
279
selectedElement.removeAttribute(" selected" )
248
- val sibling = selectedElement.previousElementSibling
249
- if sibling != null && sibling.classList.contains(" scaladoc-searchbar-result" ) then {
280
+ def recur (elem : raw.Element ): raw.Element = {
281
+ val prev = elem.previousElementSibling
282
+ if prev == null then null
283
+ else {
284
+ if ! prev.classList.contains(" hidden" ) &&
285
+ prev.classList.contains(" scaladoc-searchbar-row" ) &&
286
+ (prev.hasAttribute(" result" ) || prev.hasAttribute(" loadmore" ))
287
+ then prev
288
+ else recur(prev)
289
+ }
290
+ }
291
+ val sibling = recur(selectedElement)
292
+ if sibling != null then {
250
293
sibling.setAttribute(" selected" , " " )
251
294
resultsDiv.scrollTop = sibling.asInstanceOf [html.Element ].offsetTop - (2 * sibling.asInstanceOf [html.Element ].clientHeight)
252
295
}
253
296
}
254
297
}
255
298
private def handleArrowDown () = {
256
299
val selectedElement = resultsDiv.querySelector(" [selected]" )
300
+ def recur (elem : raw.Element ): raw.Element = {
301
+ val next = elem.nextElementSibling
302
+ if next == null then null
303
+ else {
304
+ if ! next.classList.contains(" hidden" ) &&
305
+ next.classList.contains(" scaladoc-searchbar-row" ) &&
306
+ (next.hasAttribute(" result" ) || next.hasAttribute(" loadmore" ))
307
+ then next
308
+ else recur(next)
309
+ }
310
+ }
257
311
if selectedElement != null then {
258
- val sibling = selectedElement.nextElementSibling
312
+ val sibling = recur( selectedElement)
259
313
if sibling != null then {
260
314
selectedElement.removeAttribute(" selected" )
261
315
sibling.setAttribute(" selected" , " " )
262
316
resultsDiv.scrollTop = sibling.asInstanceOf [html.Element ].offsetTop - (2 * sibling.asInstanceOf [html.Element ].clientHeight)
263
317
}
264
318
} else {
265
319
val firstResult = resultsDiv.firstElementChild
266
- if firstResult != null && firstResult.classList.contains(" scaladoc-searchbar-result" ) then {
267
- firstResult.setAttribute(" selected" , " " )
268
- resultsDiv.scrollTop = firstResult.asInstanceOf [html.Element ].offsetTop - (2 * firstResult.asInstanceOf [html.Element ].clientHeight)
269
- } else if firstResult != null && firstResult.firstElementChild != null && firstResult.firstElementChild.nextElementSibling != null then {
270
- // for Inkuire there is another wrapper to avoid displaying old results + the first (child) div is a loading animation wrapper | should be resolved in #12995
271
- val properFirstResult = firstResult.firstElementChild.nextElementSibling
272
- properFirstResult.setAttribute(" selected" , " " )
273
- resultsDiv.scrollTop = properFirstResult.asInstanceOf [html.Element ].offsetTop - (2 * properFirstResult.asInstanceOf [html.Element ].clientHeight)
320
+ if firstResult != null then {
321
+ val toSelect = if firstResult.classList.contains(" scaladoc-searchbar-row" ) && firstResult.hasAttribute(" result" ) then firstResult else recur(firstResult)
322
+ toSelect.setAttribute(" selected" , " " )
323
+ resultsDiv.scrollTop = toSelect.asInstanceOf [html.Element ].offsetTop - (2 * toSelect.asInstanceOf [html.Element ].clientHeight)
274
324
}
275
325
}
276
326
}
0 commit comments