@@ -9,18 +9,14 @@ import scala.concurrent.duration._
99import java .net .URI
1010
1111class SearchbarComponent (engine : SearchbarEngine , inkuireEngine : InkuireJSSearchEngine , parser : QueryParser ):
12- val resultsChunkSize = 100
12+ val resultsChunkSize = 5
1313 extension (p : PageEntry )
1414 def toHTML =
1515 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" , " " )
1818 wrapper.classList.add(" monospace" )
1919
20- val icon = document.createElement(" span" ).asInstanceOf [html.Span ]
21- icon.classList.add(" micon" )
22- icon.classList.add(p.kind.take(2 ))
23-
2420 val resultA = document.createElement(" a" ).asInstanceOf [html.Anchor ]
2521 resultA.href =
2622 if (p.isLocationExternal) {
@@ -39,7 +35,6 @@ class SearchbarComponent(engine: SearchbarEngine, inkuireEngine: InkuireJSSearch
3935 location.classList.add(" scaladoc-searchbar-location" )
4036 location.textContent = p.description
4137
42- wrapper.appendChild(icon)
4338 wrapper.appendChild(resultA)
4439 resultA.appendChild(location)
4540 wrapper.addEventListener(" mouseover" , {
@@ -50,27 +45,22 @@ class SearchbarComponent(engine: SearchbarEngine, inkuireEngine: InkuireJSSearch
5045 extension (m : InkuireMatch )
5146 def toHTML =
5247 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" , " " )
5451 wrapper.classList.add(" monospace" )
5552 wrapper.setAttribute(" mq" , m.mq.toString)
5653
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-
6454 val resultA = document.createElement(" a" ).asInstanceOf [html.Anchor ]
6555 // Inkuire pageLocation should start with e (external)
6656 // or i (internal). The rest of the string is an absolute
6757 // or relative URL
68- resultA.href =
58+ resultA.href =
6959 if (m.pageLocation(0 ) == 'e' ) {
7060 m.pageLocation.substring(1 )
7161 } else {
7262 Globals .pathToRoot + m.pageLocation.substring(1 )
73- }
63+ }
7464 resultA.text = m.functionName
7565 resultA.onclick = (event : Event ) =>
7666 if (document.body.contains(rootDiv)) {
@@ -92,9 +82,7 @@ class SearchbarComponent(engine: SearchbarEngine, inkuireEngine: InkuireJSSearch
9282 signature.classList.add(" scaladoc-searchbar-inkuire-signature" )
9383 signature.textContent = m.prettifiedSignature
9484
95- wrapper.appendChild(resultDiv)
96- resultDiv.appendChild(icon)
97- resultDiv.appendChild(resultA)
85+ wrapper.appendChild(resultA)
9886 resultA.appendChild(signature)
9987 wrapper.appendChild(packageDiv)
10088 packageDiv.appendChild(packageIcon)
@@ -104,29 +92,78 @@ class SearchbarComponent(engine: SearchbarEngine, inkuireEngine: InkuireJSSearch
10492 })
10593 wrapper
10694
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+
107109 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)
111111 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+ }
121133 }
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+
123147 }
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+ }
125161
126162 extension (s : String )
127163 def toHTMLError =
128164 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" )
130167 wrapper.classList.add(" monospace" )
131168
132169 val errorSpan = document.createElement(" span" ).asInstanceOf [html.Span ]
@@ -141,35 +178,30 @@ class SearchbarComponent(engine: SearchbarEngine, inkuireEngine: InkuireJSSearch
141178 clearTimeout(timeoutHandle)
142179 resultsDiv.scrollTop = 0
143180 resultsDiv.onscroll = (event : Event ) => { }
144- while (resultsDiv.hasChildNodes()) resultsDiv.removeChild(resultsDiv.lastChild)
181+ def clearResults () = while (resultsDiv.hasChildNodes()) resultsDiv.removeChild(resultsDiv.lastChild)
145182 val fragment = document.createDocumentFragment()
146183 parser.parse(query) match {
147184 case EngineMatchersQuery (matchers) =>
185+ clearResults()
148186 handleNewFluffQuery(matchers)
149187 case BySignature (signature) =>
150188 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)
159194 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)
165197 next.fold {
166- properResultsDiv .appendChild(m.toHTML)
198+ resultsDiv .appendChild(m.toHTML)
167199 } { next =>
168- properResultsDiv .insertBefore(m.toHTML, next)
200+ resultsDiv .insertBefore(m.toHTML, next)
169201 }
170202 } { (s : String ) =>
171- animation.classList.remove( " loading" )
172- properResultsDiv .appendChild(s.toHTMLError)
203+ resultsDiv.removeChild( loading)
204+ resultsDiv .appendChild(s.toHTMLError)
173205 }
174206 }
175207 }
@@ -226,7 +258,7 @@ class SearchbarComponent(engine: SearchbarEngine, inkuireEngine: InkuireJSSearch
226258 searchIcon.addEventListener(" mousedown" , (e : Event ) => e.stopPropagation())
227259 document.body.addEventListener(" mousedown" , (e : Event ) =>
228260 if (document.body.contains(element)) {
229- document.body.removeChild(element )
261+ handleEscape( )
230262 }
231263 )
232264 element.addEventListener(" keydown" , {
@@ -245,32 +277,50 @@ class SearchbarComponent(engine: SearchbarEngine, inkuireEngine: InkuireJSSearch
245277 val selectedElement = resultsDiv.querySelector(" [selected]" )
246278 if selectedElement != null then {
247279 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 {
250293 sibling.setAttribute(" selected" , " " )
251294 resultsDiv.scrollTop = sibling.asInstanceOf [html.Element ].offsetTop - (2 * sibling.asInstanceOf [html.Element ].clientHeight)
252295 }
253296 }
254297 }
255298 private def handleArrowDown () = {
256299 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+ }
257311 if selectedElement != null then {
258- val sibling = selectedElement.nextElementSibling
312+ val sibling = recur( selectedElement)
259313 if sibling != null then {
260314 selectedElement.removeAttribute(" selected" )
261315 sibling.setAttribute(" selected" , " " )
262316 resultsDiv.scrollTop = sibling.asInstanceOf [html.Element ].offsetTop - (2 * sibling.asInstanceOf [html.Element ].clientHeight)
263317 }
264318 } else {
265319 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)
274324 }
275325 }
276326 }
0 commit comments