@@ -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 = Globals .pathToRoot + p.location
26
22
resultA.text = s " ${p.fullName}"
@@ -34,7 +30,6 @@ class SearchbarComponent(engine: SearchbarEngine, inkuireEngine: InkuireJSSearch
34
30
location.classList.add(" scaladoc-searchbar-location" )
35
31
location.textContent = p.description
36
32
37
- wrapper.appendChild(icon)
38
33
wrapper.appendChild(resultA)
39
34
resultA.appendChild(location)
40
35
wrapper.addEventListener(" mouseover" , {
@@ -45,24 +40,19 @@ class SearchbarComponent(engine: SearchbarEngine, inkuireEngine: InkuireJSSearch
45
40
extension (m : InkuireMatch )
46
41
def toHTML =
47
42
val wrapper = document.createElement(" div" ).asInstanceOf [html.Div ]
48
- wrapper.classList.add(" scaladoc-searchbar-result" )
43
+ wrapper.classList.add(" scaladoc-searchbar-row" )
44
+ wrapper.setAttribute(" result" , " " )
45
+ wrapper.setAttribute(" inkuire-result" , " " )
49
46
wrapper.classList.add(" monospace" )
50
47
wrapper.setAttribute(" mq" , m.mq.toString)
51
48
52
- val resultDiv = document.createElement(" div" ).asInstanceOf [html.Div ]
53
- resultDiv.classList.add(" scaladoc-searchbar-result-row" )
54
-
55
- val icon = document.createElement(" span" ).asInstanceOf [html.Span ]
56
- icon.classList.add(" micon" )
57
- icon.classList.add(m.entryType.take(2 ))
58
-
59
49
val resultA = document.createElement(" a" ).asInstanceOf [html.Anchor ]
60
- resultA.href =
50
+ resultA.href =
61
51
if (new URI (m.pageLocation).isAbsolute()) {
62
52
m.pageLocation
63
53
} else {
64
54
Globals .pathToRoot + m.pageLocation
65
- }
55
+ }
66
56
resultA.text = m.functionName
67
57
resultA.onclick = (event : Event ) =>
68
58
if (document.body.contains(rootDiv)) {
@@ -84,9 +74,7 @@ class SearchbarComponent(engine: SearchbarEngine, inkuireEngine: InkuireJSSearch
84
74
signature.classList.add(" scaladoc-searchbar-inkuire-signature" )
85
75
signature.textContent = m.prettifiedSignature
86
76
87
- wrapper.appendChild(resultDiv)
88
- resultDiv.appendChild(icon)
89
- resultDiv.appendChild(resultA)
77
+ wrapper.appendChild(resultA)
90
78
resultA.appendChild(signature)
91
79
wrapper.appendChild(packageDiv)
92
80
packageDiv.appendChild(packageIcon)
@@ -96,29 +84,78 @@ class SearchbarComponent(engine: SearchbarEngine, inkuireEngine: InkuireJSSearch
96
84
})
97
85
wrapper
98
86
87
+ def createKindSeparator (kind : String ) =
88
+ val kindSeparator = document.createElement(" div" ).asInstanceOf [html.Div ]
89
+ val icon = document.createElement(" span" ).asInstanceOf [html.Span ]
90
+ icon.classList.add(" micon" )
91
+ icon.classList.add(kind.take(2 ))
92
+ val name = document.createElement(" span" ).asInstanceOf [html.Span ]
93
+ name.textContent = kind
94
+ kindSeparator.classList.add(" scaladoc-searchbar-row" )
95
+ kindSeparator.setAttribute(" divider" , " " )
96
+ kindSeparator.classList.add(" monospace" )
97
+ kindSeparator.appendChild(icon)
98
+ kindSeparator.appendChild(name)
99
+ kindSeparator
100
+
99
101
def handleNewFluffQuery (matchers : List [Matchers ]) =
100
- val result = engine.query(matchers).map(_.toHTML)
101
- resultsDiv.scrollTop = 0
102
- while (resultsDiv.hasChildNodes()) resultsDiv.removeChild(resultsDiv.lastChild)
102
+ val result = engine.query(matchers)
103
103
val fragment = document.createDocumentFragment()
104
- result.take(resultsChunkSize).foreach(fragment.appendChild)
105
- resultsDiv.appendChild(fragment)
106
- def loadMoreResults (result : List [raw.HTMLElement ]): Unit = {
107
- resultsDiv.onscroll = (event : Event ) => {
108
- if (resultsDiv.scrollHeight - resultsDiv.scrollTop == resultsDiv.clientHeight) {
109
- val fragment = document.createDocumentFragment()
110
- result.take(resultsChunkSize).foreach(fragment.appendChild)
111
- resultsDiv.appendChild(fragment)
112
- loadMoreResults(result.drop(resultsChunkSize))
104
+ def createLoadMoreElement =
105
+ val loadMoreElement = document.createElement(" div" ).asInstanceOf [html.Div ]
106
+ loadMoreElement.classList.add(" scaladoc-searchbar-row" )
107
+ loadMoreElement.setAttribute(" loadmore" , " " )
108
+ loadMoreElement.classList.add(" monospace" )
109
+ val anchor = document.createElement(" a" ).asInstanceOf [html.Anchor ]
110
+ anchor.text = " Show more..."
111
+ loadMoreElement.appendChild(anchor)
112
+ loadMoreElement.addEventListener(" mouseover" , _ => handleHover(loadMoreElement))
113
+ loadMoreElement
114
+ result.groupBy(_.kind).map {
115
+ case (kind, entries) =>
116
+ val kindSeparator = createKindSeparator(kind)
117
+ val htmlEntries = entries.map(_.toHTML)
118
+ val loadMoreElement = createLoadMoreElement
119
+ def loadMoreResults (entries : List [raw.HTMLElement ]): Unit = {
120
+ loadMoreElement.onclick = (event : Event ) => {
121
+ entries.take(resultsChunkSize).foreach(_.classList.remove(" hidden" ))
122
+ val nextElems = entries.drop(resultsChunkSize)
123
+ if nextElems.nonEmpty then loadMoreResults(nextElems) else loadMoreElement.classList.add(" hidden" )
124
+ }
113
125
}
114
- }
126
+
127
+ fragment.appendChild(kindSeparator)
128
+ htmlEntries.foreach(fragment.appendChild)
129
+ fragment.appendChild(loadMoreElement)
130
+
131
+ val nextElems = htmlEntries.drop(resultsChunkSize)
132
+ if nextElems.nonEmpty then {
133
+ nextElems.foreach(_.classList.add(" hidden" ))
134
+ loadMoreResults(nextElems)
135
+ } else {
136
+ loadMoreElement.classList.add(" hidden" )
137
+ }
138
+
115
139
}
116
- loadMoreResults(result.drop(resultsChunkSize))
140
+
141
+ resultsDiv.scrollTop = 0
142
+ while (resultsDiv.hasChildNodes()) resultsDiv.removeChild(resultsDiv.lastChild)
143
+ resultsDiv.appendChild(fragment)
144
+
145
+ def createLoadingAnimation : raw.HTMLElement = {
146
+ val loading = document.createElement(" div" ).asInstanceOf [html.Div ]
147
+ loading.classList.add(" loading-wrapper" )
148
+ val animation = document.createElement(" div" ).asInstanceOf [html.Div ]
149
+ animation.classList.add(" loading" )
150
+ loading.appendChild(animation)
151
+ loading
152
+ }
117
153
118
154
extension (s : String )
119
155
def toHTMLError =
120
156
val wrapper = document.createElement(" div" ).asInstanceOf [html.Div ]
121
- wrapper.classList.add(" scaladoc-searchbar-result" )
157
+ wrapper.classList.add(" scaladoc-searchbar-row" )
158
+ wrapper.classList.add(" scaladoc-searchbar-error" )
122
159
wrapper.classList.add(" monospace" )
123
160
124
161
val errorSpan = document.createElement(" span" ).asInstanceOf [html.Span ]
@@ -133,35 +170,30 @@ class SearchbarComponent(engine: SearchbarEngine, inkuireEngine: InkuireJSSearch
133
170
clearTimeout(timeoutHandle)
134
171
resultsDiv.scrollTop = 0
135
172
resultsDiv.onscroll = (event : Event ) => { }
136
- while (resultsDiv.hasChildNodes()) resultsDiv.removeChild(resultsDiv.lastChild)
173
+ def clearResults () = while (resultsDiv.hasChildNodes()) resultsDiv.removeChild(resultsDiv.lastChild)
137
174
val fragment = document.createDocumentFragment()
138
175
parser.parse(query) match {
139
176
case EngineMatchersQuery (matchers) =>
177
+ clearResults()
140
178
handleNewFluffQuery(matchers)
141
179
case BySignature (signature) =>
142
180
timeoutHandle = setTimeout(1 .second) {
143
- val properResultsDiv = document.createElement(" div" ).asInstanceOf [html.Div ]
144
- resultsDiv.appendChild(properResultsDiv)
145
- val loading = document.createElement(" div" ).asInstanceOf [html.Div ]
146
- loading.classList.add(" loading-wrapper" )
147
- val animation = document.createElement(" div" ).asInstanceOf [html.Div ]
148
- animation.classList.add(" loading" )
149
- loading.appendChild(animation)
150
- properResultsDiv.appendChild(loading)
181
+ val loading = createLoadingAnimation
182
+ val kindSeparator = createKindSeparator(" inkuire" )
183
+ clearResults()
184
+ resultsDiv.appendChild(loading)
185
+ resultsDiv.appendChild(kindSeparator)
151
186
inkuireEngine.query(query) { (m : InkuireMatch ) =>
152
- val next = properResultsDiv.children.foldLeft[Option [Element ]](None ) {
153
- case (acc, child) if ! acc.isEmpty => acc
154
- case (_, child) =>
155
- Option .when(child.hasAttribute(" mq" ) && Integer .parseInt(child.getAttribute(" mq" )) > m.mq)(child)
156
- }
187
+ val next = resultsDiv.children
188
+ .find(child => child.hasAttribute(" mq" ) && Integer .parseInt(child.getAttribute(" mq" )) > m.mq)
157
189
next.fold {
158
- properResultsDiv .appendChild(m.toHTML)
190
+ resultsDiv .appendChild(m.toHTML)
159
191
} { next =>
160
- properResultsDiv .insertBefore(m.toHTML, next)
192
+ resultsDiv .insertBefore(m.toHTML, next)
161
193
}
162
194
} { (s : String ) =>
163
- animation.classList.remove( " loading" )
164
- properResultsDiv .appendChild(s.toHTMLError)
195
+ resultsDiv.removeChild( loading)
196
+ resultsDiv .appendChild(s.toHTMLError)
165
197
}
166
198
}
167
199
}
@@ -218,7 +250,7 @@ class SearchbarComponent(engine: SearchbarEngine, inkuireEngine: InkuireJSSearch
218
250
searchIcon.addEventListener(" mousedown" , (e : Event ) => e.stopPropagation())
219
251
document.body.addEventListener(" mousedown" , (e : Event ) =>
220
252
if (document.body.contains(element)) {
221
- document.body.removeChild(element )
253
+ handleEscape( )
222
254
}
223
255
)
224
256
element.addEventListener(" keydown" , {
@@ -237,32 +269,50 @@ class SearchbarComponent(engine: SearchbarEngine, inkuireEngine: InkuireJSSearch
237
269
val selectedElement = resultsDiv.querySelector(" [selected]" )
238
270
if selectedElement != null then {
239
271
selectedElement.removeAttribute(" selected" )
240
- val sibling = selectedElement.previousElementSibling
241
- if sibling != null && sibling.classList.contains(" scaladoc-searchbar-result" ) then {
272
+ def recur (elem : raw.Element ): raw.Element = {
273
+ val prev = elem.previousElementSibling
274
+ if prev == null then null
275
+ else {
276
+ if ! prev.classList.contains(" hidden" ) &&
277
+ prev.classList.contains(" scaladoc-searchbar-row" ) &&
278
+ (prev.hasAttribute(" result" ) || prev.hasAttribute(" loadmore" ))
279
+ then prev
280
+ else recur(prev)
281
+ }
282
+ }
283
+ val sibling = recur(selectedElement)
284
+ if sibling != null then {
242
285
sibling.setAttribute(" selected" , " " )
243
286
resultsDiv.scrollTop = sibling.asInstanceOf [html.Element ].offsetTop - (2 * sibling.asInstanceOf [html.Element ].clientHeight)
244
287
}
245
288
}
246
289
}
247
290
private def handleArrowDown () = {
248
291
val selectedElement = resultsDiv.querySelector(" [selected]" )
292
+ def recur (elem : raw.Element ): raw.Element = {
293
+ val next = elem.nextElementSibling
294
+ if next == null then null
295
+ else {
296
+ if ! next.classList.contains(" hidden" ) &&
297
+ next.classList.contains(" scaladoc-searchbar-row" ) &&
298
+ (next.hasAttribute(" result" ) || next.hasAttribute(" loadmore" ))
299
+ then next
300
+ else recur(next)
301
+ }
302
+ }
249
303
if selectedElement != null then {
250
- val sibling = selectedElement.nextElementSibling
304
+ val sibling = recur( selectedElement)
251
305
if sibling != null then {
252
306
selectedElement.removeAttribute(" selected" )
253
307
sibling.setAttribute(" selected" , " " )
254
308
resultsDiv.scrollTop = sibling.asInstanceOf [html.Element ].offsetTop - (2 * sibling.asInstanceOf [html.Element ].clientHeight)
255
309
}
256
310
} else {
257
311
val firstResult = resultsDiv.firstElementChild
258
- if firstResult != null && firstResult.classList.contains(" scaladoc-searchbar-result" ) then {
259
- firstResult.setAttribute(" selected" , " " )
260
- resultsDiv.scrollTop = firstResult.asInstanceOf [html.Element ].offsetTop - (2 * firstResult.asInstanceOf [html.Element ].clientHeight)
261
- } else if firstResult != null && firstResult.firstElementChild != null && firstResult.firstElementChild.nextElementSibling != null then {
262
- // 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
263
- val properFirstResult = firstResult.firstElementChild.nextElementSibling
264
- properFirstResult.setAttribute(" selected" , " " )
265
- resultsDiv.scrollTop = properFirstResult.asInstanceOf [html.Element ].offsetTop - (2 * properFirstResult.asInstanceOf [html.Element ].clientHeight)
312
+ if firstResult != null then {
313
+ val toSelect = if firstResult.classList.contains(" scaladoc-searchbar-row" ) && firstResult.hasAttribute(" result" ) then firstResult else recur(firstResult)
314
+ toSelect.setAttribute(" selected" , " " )
315
+ resultsDiv.scrollTop = toSelect.asInstanceOf [html.Element ].offsetTop - (2 * toSelect.asInstanceOf [html.Element ].clientHeight)
266
316
}
267
317
}
268
318
}
0 commit comments