-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Replace default dokka searchbar with new implemented in Scala.js #11021
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 7 commits
aafd1a1
113e934
7f6e7f6
20b4115
454a8e1
de5491d
625538d
d0a8992
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
/* button */ | ||
.search span { | ||
background: #ED3522; | ||
fill: #fff; | ||
cursor: pointer; | ||
border: none; | ||
padding: 9px; | ||
border-radius: 24px; | ||
box-shadow: 0 0 16px #F27264; | ||
} | ||
.search span:hover { | ||
fill: #F27264; | ||
} | ||
|
||
@media(max-width: 576px) { | ||
.search span { | ||
background: none; | ||
fill: var(--icon-color); | ||
cursor: pointer; | ||
border: none; | ||
padding: 0; | ||
box-shadow: none; | ||
margin-top: 2px; | ||
} | ||
.search span:hover { | ||
fill: var(--link-hover-fg); | ||
} | ||
} | ||
|
||
#scala3doc-search { | ||
margin-top: 10px; | ||
cursor: pointer; | ||
position: fixed; | ||
top: 0; | ||
right: 20px; | ||
z-index: 5; | ||
} | ||
|
||
#scala3doc-searchbar.hidden { | ||
display: none; | ||
} | ||
|
||
#scala3doc-searchbar { | ||
position: absolute; | ||
top: 50px; | ||
right: 40px; | ||
width: calc(100% - 360px); | ||
box-shadow: 0 2px 16px 0 rgba(0, 42, 76, 0.15); | ||
font-size: 13px; | ||
font-family: system-ui, -apple-system, Segoe UI, Roboto, Noto Sans, Ubuntu, Cantarell, Helvetica Neue, Arial, sans-serif; | ||
} | ||
|
||
#scala3doc-searchbar-input { | ||
width: 100%; | ||
min-height: 32px; | ||
border: none; | ||
border-bottom: 1px solid #bbb; | ||
padding: 10px; | ||
} | ||
|
||
#scala3doc-searchbar-input:focus { | ||
outline: none; | ||
} | ||
|
||
#scala3doc-searchbar-results { | ||
background: white; | ||
display: flex; | ||
flex-direction: column; | ||
max-height: 500px; | ||
overflow: auto; | ||
} | ||
|
||
.scala3doc-searchbar-result { | ||
line-height: 32px; | ||
padding-left: 10px; | ||
padding-right: 10px; | ||
} | ||
|
||
.scala3doc-searchbar-result:first-of-type { | ||
margin-top: 10px; | ||
} | ||
|
||
.scala3doc-searchbar-result:hover { | ||
background-color: #d4edff; | ||
} | ||
|
||
.scala3doc-searchbar-result a { | ||
color: #1f2326; | ||
} | ||
|
||
.scala3doc-searchbar-result .scala3doc-searchbar-location { | ||
color: gray; | ||
} | ||
|
||
#searchBar { | ||
display: inline-flex; | ||
} | ||
|
||
.pull-right { | ||
float: right; | ||
margin-left: auto | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package dotty.dokka | ||
|
||
import scala.scalajs.js | ||
import scala.scalajs.js.annotation.JSGlobalScope | ||
|
||
@js.native | ||
@JSGlobalScope | ||
object Globals extends js.Object { | ||
val pathToRoot: String = js.native | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package dotty.dokka | ||
|
||
object Main extends App { | ||
def initializeSearchbar(): Unit = Searchbar() | ||
|
||
initializeSearchbar() | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package dotty.dokka | ||
|
||
import scala.scalajs.js | ||
|
||
@js.native | ||
trait PageEntryJS extends js.Object { | ||
val name: String = js.native | ||
val description: String = js.native | ||
val location: String = js.native | ||
val searchKeys: js.Array[String] = js.native | ||
} | ||
|
||
case class PageEntry( | ||
fullName: String, | ||
description: String, | ||
location: String, | ||
shortName: String, | ||
acronym: Option[String], | ||
) | ||
|
||
object PageEntry { | ||
def apply(jsObj: PageEntryJS): PageEntry = PageEntry( | ||
jsObj.name, | ||
jsObj.description, | ||
jsObj.location, | ||
jsObj.searchKeys.head.toLowerCase, | ||
Option.when(jsObj.searchKeys.size > 1)(jsObj.searchKeys.last) | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package dotty.dokka | ||
|
||
class Searchbar { | ||
val pages = SearchbarGlobals.pages.toList.map(PageEntry.apply) | ||
val engine = SearchbarEngine(pages) | ||
val parser = QueryParser() | ||
val component = SearchbarComponent(q => engine.query(parser.parse(q))) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
package dotty.dokka | ||
|
||
import org.scalajs.dom._ | ||
import org.scalajs.dom.html.Input | ||
|
||
class SearchbarComponent(val callback: (String) => List[PageEntry]): | ||
extension (p: PageEntry) | ||
def toHTML = | ||
val wrapper = document.createElement("div").asInstanceOf[html.Div] | ||
wrapper.classList.add("scala3doc-searchbar-result") | ||
wrapper.classList.add("monospace") | ||
|
||
val resultA = document.createElement("a").asInstanceOf[html.Anchor] | ||
resultA.href = Globals.pathToRoot + p.location | ||
resultA.text = s"${p.fullName}" | ||
|
||
val location = document.createElement("span") | ||
location.classList.add("pull-right") | ||
location.classList.add("scala3doc-searchbar-location") | ||
location.textContent = p.description | ||
|
||
wrapper.appendChild(resultA) | ||
wrapper.appendChild(location) | ||
wrapper | ||
|
||
def handleNewQuery(query: String) = | ||
val result = callback(query).map(_.toHTML) | ||
resultsDiv.scrollTop = 0 | ||
while (resultsDiv.hasChildNodes()) resultsDiv.removeChild(resultsDiv.lastChild) | ||
pikinier20 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
val fragment = document.createDocumentFragment() | ||
result.take(100).foreach(fragment.appendChild) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. magic number There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. refactored |
||
resultsDiv.appendChild(fragment) | ||
def loadMoreResults(result: List[raw.HTMLElement]): Unit = { | ||
resultsDiv.onscroll = (event: Event) => { | ||
if (resultsDiv.scrollHeight - resultsDiv.scrollTop == resultsDiv.clientHeight) | ||
{ | ||
val fragment = document.createDocumentFragment() | ||
result.take(100).foreach(fragment.appendChild) | ||
resultsDiv.appendChild(fragment) | ||
loadMoreResults(result.drop(100)) | ||
} | ||
} | ||
} | ||
loadMoreResults(result.drop(100)) | ||
|
||
private val logoClick: html.Div = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. changed |
||
val span = document.createElement("span").asInstanceOf[html.Span] | ||
span.innerHTML = """<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20"><path d="M19.64 18.36l-6.24-6.24a7.52 7.52 0 10-1.28 1.28l6.24 6.24zM7.5 13.4a5.9 5.9 0 115.9-5.9 5.91 5.91 0 01-5.9 5.9z"></path></svg>""" | ||
span.id = "scala3doc-search" | ||
span.onclick = (event: Event) => | ||
if (document.body.contains(rootDiv)) { | ||
document.body.removeChild(rootDiv) | ||
} | ||
else document.body.appendChild(rootDiv) | ||
|
||
val element = createNestingDiv("search-content")( | ||
createNestingDiv("search-container")( | ||
createNestingDiv("search")( | ||
span | ||
) | ||
) | ||
) | ||
document.getElementById("scala3doc-searchBar").appendChild(element) | ||
element | ||
|
||
|
||
private val input: html.Input = | ||
val element = document.createElement("input").asInstanceOf[html.Input] | ||
element.id = "scala3doc-searchbar-input" | ||
element.addEventListener("input", (e) => handleNewQuery(e.target.asInstanceOf[html.Input].value)) | ||
element | ||
|
||
private val resultsDiv: html.Div = | ||
val element = document.createElement("div").asInstanceOf[html.Div] | ||
element.id = "scala3doc-searchbar-results" | ||
element | ||
|
||
private val rootHiddenClasses = "hidden" | ||
private val rootShowClasses = "" | ||
|
||
private def createNestingDiv(className: String)(innerElement: html.Element): html.Div = | ||
val element = document.createElement("div").asInstanceOf[html.Div] | ||
element.className = className | ||
element.appendChild(innerElement) | ||
element | ||
|
||
private val rootDiv: html.Div = | ||
val element = document.createElement("div").asInstanceOf[html.Div] | ||
element.addEventListener("mousedown", (e: Event) => e.stopPropagation()) | ||
logoClick.addEventListener("mousedown", (e: Event) => e.stopPropagation()) | ||
document.body.addEventListener("mousedown", (e: Event) => | ||
if (document.body.contains(element)) { | ||
document.body.removeChild(element) | ||
} | ||
) | ||
element.id = "scala3doc-searchbar" | ||
element.appendChild(input) | ||
element.appendChild(resultsDiv) | ||
element | ||
|
||
handleNewQuery("") |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package dotty.dokka | ||
|
||
import scala.scalajs.js | ||
import scala.scalajs.js.annotation.JSGlobalScope | ||
|
||
@js.native | ||
@JSGlobalScope | ||
object SearchbarGlobals extends js.Object { | ||
val pages: js.Array[PageEntryJS] = js.native | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package dotty.dokka | ||
|
||
enum Matchers(func: (PageEntry) => Int) extends Function1[PageEntry, Int]: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it will be more readable if func is implemented in this way:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wanted to try here some Scala3 tricks, but apparently it didn't make things clearer |
||
export func.apply | ||
case ByName(query: String) extends Matchers( (p) => { | ||
val nameOption = Option(p.shortName) | ||
val acronym = p.acronym | ||
//Edge case for empty query string | ||
if query == "" then 1 | ||
else { | ||
val results = List( | ||
nameOption.filter(_.contains(query.toLowerCase)).fold(-1)(_.size - query.size), | ||
acronym.filter(_.contains(query)).fold(-1)(_.size - query.size + 1) | ||
) | ||
if results.forall(_ == -1) then -1 else results.filter(_ != -1).min | ||
} | ||
}) | ||
case ByKind(kind: String) extends Matchers((p) => p.fullName.split(" ").headOption.filter(_.equalsIgnoreCase(kind)).fold(-1)(_ => 1)) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package dotty.dokka | ||
|
||
import scala.util.matching.Regex._ | ||
import scala.util.matching._ | ||
|
||
class QueryParser: | ||
val kinds = Seq( | ||
"class", | ||
"trait", | ||
"enum", | ||
"object", | ||
"def", | ||
"val", | ||
"var", | ||
"package", | ||
"given", | ||
"type" | ||
) | ||
val kindRegex = ("(?i)" + kinds.mkString("(","|",")") + " (.*)").r | ||
val restRegex = raw"(.*)".r | ||
val escapedRegex = raw"`(.*)`".r | ||
|
||
def parse(query: String): List[Matchers] = query match { | ||
case escapedRegex(rest) => List(Matchers.ByName(rest)) | ||
case kindRegex(kind, rest) => List(Matchers.ByKind(kind)) ++ parse(rest) | ||
case restRegex(name) => List(Matchers.ByName(name)) | ||
case _ => List() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't we simply call
Searchbar()
instead of defining method and calling it?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
changed