Skip to content

Add JS DOM utils to Scaladoc-js #14595

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

Merged
merged 2 commits into from
Mar 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion project/Build.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1253,7 +1253,7 @@ object Build {

lazy val `scaladoc-js-contributors` = project.in(file("scaladoc-js/contributors")).
enablePlugins(DottyJSPlugin).
dependsOn(`scala3-library-bootstrappedJS`).
dependsOn(`scaladoc-js-common`).
settings(
Test / fork := false,
scalaJSUseMainModuleInitializer := true,
Expand Down
119 changes: 45 additions & 74 deletions scaladoc-js/common/src/code-snippets/CodeSnippets.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import scala.scalajs.js
import org.scalajs.dom._
import org.scalajs.dom.ext._

import utils.HTML._
import scala.util.chaining._

import CodeSnippetsGlobals._

class CodeSnippets:
Expand Down Expand Up @@ -35,22 +38,13 @@ class CodeSnippets:
case _ =>
}
def createShowHideButton(toggleRoot: html.Element) = {
val div = document.createElement("div")
div.classList.add("snippet-showhide")
val p = document.createElement("p")
p.textContent = "Show collapsed lines"
val showHideButton = document.createElement("label")
showHideButton.classList.add("snippet-showhide-button")
val checkbox = document.createElement("input").asInstanceOf[html.Input]
checkbox.`type` = "checkbox"
val slider = document.createElement("span")
slider.classList.add("slider")
showHideButton.appendChild(checkbox)
showHideButton.appendChild(slider)
checkbox.addEventListener("change", _ => toggleHide(toggleRoot))
div.appendChild(showHideButton)
div.appendChild(p)
div
div(cls := "snippet-showhide")(
label(cls := "snippet-showhide-button")(
input("type" := "checkbox").tap(_.addEventListener("change", _ => toggleHide(toggleRoot))),
span(cls := "slider")
),
p("Show collapsed lines")
)
}

toggleHide(snippet)
Expand All @@ -65,8 +59,7 @@ class CodeSnippets:
private def snippetAnchor(snippet: html.Element): Unit = snippet.querySelector(".snippet-meta .snippet-label") match {
case e: html.Element =>
val name = e.textContent.trim
val anchor = document.createElement("a").asInstanceOf[html.Anchor]
anchor.id = s"snippet-$name"
val anchor = a(id := s"snippet-$name")
snippet.insertBefore(anchor, snippet.firstChild)
case _ =>
}
Expand All @@ -75,54 +68,42 @@ class CodeSnippets:
val included = snippet.querySelectorAll("code span.include")
val pre = snippet.querySelector("pre")
if included != null && included.nonEmpty && pre != null then {
val includesDiv = document.createElement("div")
includesDiv.classList.add("included-section")
includesDiv.classList.add("hideable")
included
val includes = included
.collect { case e: html.Element => e }
.toList
.filter(_.hasAttribute("name"))
.map(_.getAttribute("name"))
.distinct
.map { name =>
val a = document.createElement("a").asInstanceOf[html.Anchor]
a.classList.add("unselectable")
a.href = s"#snippet-$name"
a.innerHTML = s"included <b>$name</b>"
a
a(cls := "unselectable", href := s"#snippet-$name")(
"included",
b(name)
)
}
.foreach(a => includesDiv.appendChild(a))

val includesDiv = div(cls := "included-section hideable")(includes)

snippet.insertBefore(includesDiv, pre)
}
}

private def copyRunButtons(snippet: html.Element) = {
def copyButton = {
val div = document.createElement("div")
val button = document.createElement("button")
val icon = document.createElement("i")
icon.classList.add("far")
icon.classList.add("fa-clone")
button.appendChild(icon)
button.classList.add("copy-button")
button.addEventListener("click", _ => {
val code = snippet.querySelectorAll("code>span:not(.hidden)")
.map(_.textContent)
.mkString
window.navigator.clipboard.writeText(code)
})
div.appendChild(button)
div
div(
button(cls := "copy-button")(
i(cls := "far fa-clone")
).tap(_.addEventListener("click", _ => {
val code = snippet.querySelectorAll("code>span:not(.hidden)")
.map(_.textContent)
.mkString
window.navigator.clipboard.writeText(code)
}))
)
}
def runButton = {
val div = document.createElement("div").asInstanceOf[html.Div]
val runButton = document.createElement("button").asInstanceOf[html.Button]
val runIcon = document.createElement("i")
runIcon.classList.add("fas")
runIcon.classList.add("fa-play")
runButton.classList.add("run-button")
runButton.appendChild(runIcon)
val runButton = button(cls := "run-button")(
i(cls := "fas fa-play")
)

runButton.addEventListener("click", _ =>
if !runButton.hasAttribute("opened") then {
Expand All @@ -148,18 +129,14 @@ class CodeSnippets:
}
)

div.appendChild(runButton)
div
div(runButton)
}
def exitButton = {
val div = document.createElement("div").asInstanceOf[html.Div]
val exitButton = document.createElement("button").asInstanceOf[html.Element]
val exitIcon = document.createElement("i")
exitIcon.classList.toggle("fas")
exitIcon.classList.toggle("fa-times")
exitButton.classList.add("exit-button")
div.style = "display:none;"
exitButton.appendChild(exitIcon)
val exitButton = button(cls := "exit-button")(
i(cls := "fas fa-times")
)

val bdiv = div(style := "display:none;")(exitButton)

exitButton.addEventListener("click", _ =>
snippet.querySelector("pre") match {
Expand All @@ -178,22 +155,16 @@ class CodeSnippets:
case btn: html.Element => btn.parentElement.style = "display:none;"
case _ =>
}
div.style = "display:none;"
bdiv.style = "display:none;"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldnt be this event listerner block inlined in exitButton using tap?

)

div.appendChild(exitButton)
div
bdiv
}
def toScastieButton = {
val div = document.createElement("div").asInstanceOf[html.Div]
val toScastieButton = document.createElement("button").asInstanceOf[html.Element]
val toScastieIcon = document.createElement("i").asInstanceOf[html.Image]

toScastieIcon.classList.add("fas")
toScastieIcon.classList.add("fa-external-link-alt")
toScastieButton.classList.add("to-scastie-button")
div.style = "display:none;"
toScastieButton.appendChild(toScastieIcon)
def toScastieButton = {
val toScastieButton = button(cls := "to-scastie-button")(
i(cls := "fas fa-external-link-alt")
)

toScastieButton.addEventListener("click", _ =>
snippet.querySelector(".embedded-menu li.logo") match {
Expand All @@ -202,9 +173,9 @@ class CodeSnippets:
}
)

div.appendChild(toScastieButton)
div
div("style" := "display:none;")(toScastieButton)
}

val buttonsSection = getButtonsSection(snippet)
buttonsSection.foreach(s =>
s.appendChild(copyButton)
Expand Down
116 changes: 116 additions & 0 deletions scaladoc-js/common/src/utils/html.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package dotty.tools.scaladoc
package utils

import scala.scalajs.js
import org.scalajs.dom.{html => domhtml, _}

object HTML {
type TagArg = domhtml.Element | Seq[domhtml.Element | String] | String

type AttrArg = AppliedAttr | Seq[AppliedAttr]

case class Tag[T <: domhtml.Element](private val elemFactory: () => T):
private def textNode(s: String): Text = document.createTextNode(s)

def apply(tags: TagArg*): T = apply()(tags:_*)
def apply(first: AttrArg, rest: AttrArg*): T = apply((first +: rest):_*)()
def apply(attrs: AttrArg*)(tags: TagArg*): T =
val elem: T = elemFactory()
def unpackTags(tags: TagArg*): Unit = tags.foreach {
case e: domhtml.Element => elem.appendChild(e)
case s: String => elem.appendChild(textNode(s))
case elemSeq: (Seq[domhtml.Element | String] @unchecked) => unpackTags(elemSeq*)
}

def unpackAttributes(attrs: AttrArg*): Unit = attrs.foreach {
case ("id", id) => elem.id = id
case ("class", value) => value.split("\\s+").foreach(cls => elem.classList.add(cls))
case (attr, value) => elem.setAttribute(attr, value)
case s: Seq[AppliedAttr] => unpackAttributes(s*)
}

unpackTags(tags:_*)
unpackAttributes(attrs:_*)
elem

object Tag:
def apply[T <: domhtml.Element](s: String): Tag[T] =
Tag[T](() => document.createElement(s).asInstanceOf[T])

extension (s: String) def escapeReservedTokens: String =
s.replace("&", "&amp;")
.replace("<", "&lt;")
.replace(">", "&gt;")
.replace("\"", "&quot;")
.replace("'", "&apos;")

case class Attr(name: String):
def :=(value: String): AppliedAttr = (name, value)

extension (key: String) def :=(value: String): AppliedAttr =
(key, value)

opaque type AppliedAttr = (String, String)

val div = Tag[domhtml.Div]("div")
val span = Tag[domhtml.Span]("span")
val a = Tag[domhtml.Anchor]("a")
val p = Tag[domhtml.Paragraph]("p")
val h1 = Tag[domhtml.Heading]("h1")
val h2 = Tag[domhtml.Heading]("h2")
val h3 = Tag[domhtml.Heading]("h3")
val h4 = Tag[domhtml.Heading]("h4")
val h5 = Tag[domhtml.Heading]("h5")
val h6 = Tag[domhtml.Heading]("h6")
val dl = Tag[domhtml.DList]("dl")
val dd = Tag[domhtml.Element]("dd")
val dt = Tag[domhtml.Element]("dt")
val svg = Tag[domhtml.Element]("svg")
val button = Tag[domhtml.Button]("button")
val input = Tag[domhtml.Input]("input")
val label = Tag[domhtml.Label]("label")
val script = Tag[domhtml.Script]("script")
val link = Tag[domhtml.Link]("link")
val footer = Tag[domhtml.Element]("footer")
val htmlelem = Tag[domhtml.Html]("html")
val head = Tag[domhtml.Head]("head")
val meta = Tag[domhtml.Element]("meta")
val main = Tag[domhtml.Element]("main")
val title = Tag[domhtml.Title]("title")
val body = Tag[domhtml.Body]("body")
val nav = Tag[domhtml.Element]("nav")
val img = Tag[domhtml.Image]("img")
val ul = Tag[domhtml.UList]("ul")
val ol = Tag[domhtml.OList]("ol")
val li = Tag[domhtml.LI]("li")
val code = Tag[domhtml.Element]("code")
val pre = Tag[domhtml.Pre]("pre")
val table = Tag[domhtml.Table]("table")
val thead = Tag[domhtml.Element]("thead")
val tbody = Tag[domhtml.Element]("tbody")
val th = Tag[domhtml.TableCell]("th")
val tr = Tag[domhtml.TableRow]("tr")
val td = Tag[domhtml.TableCell]("td")
val b = Tag[domhtml.Element]("b")
val i = Tag[domhtml.Element]("i")

val cls = Attr("class")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

class, we do not code in C++ to count every character we type

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

class is a keyword and I didn't want to type:

`class`

in every tag

val href = Attr("href")
val style = Attr("style")
val id = Attr("id")
val `type` = Attr("type")
val placeholder = Attr("placeholder")
val defer = Attr("defer")
val src = Attr("src")
val rel = Attr("rel")
val charset = Attr("charset")
val name = Attr("name")
val content = Attr("content")
val testId = Attr("data-test-id")
val alt = Attr("alt")
val value = Attr("value")
val onclick=Attr("onclick")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

format

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also: i'd use onClick

val titleAttr =Attr("title")
val onkeyup = Attr("onkeyup")

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import scala.util.{Success,Failure}

import utils.HTML._

// Contributors widget
// see https://stackoverflow.com/a/19200303/4496364
// Copied from https://github.com/scala/docs.scala-lang/blob/main/resources/js/functions.js and rewritten to Scala.js
Expand Down Expand Up @@ -90,21 +92,17 @@ class ContentContributors:
getAuthorsForFilename(Globals.githubContributorsFilename.stripPrefix("/")).onComplete {
case Success(authors) =>
val maybeDiv = Option(document.getElementById("documentation-contributors"))
maybeDiv.foreach { div =>
authors.foreach { case FullAuthor(name, url, img) =>
val divN = document.createElement("div")
val imgN = document.createElement("img").asInstanceOf[html.Image]
imgN.src = img
val autN = document.createElement("a").asInstanceOf[html.Anchor]
autN.href = url
autN.text = name
divN.appendChild(imgN)
divN.appendChild(autN)
div.appendChild(divN)
maybeDiv.foreach { mdiv =>
authors.foreach { case FullAuthor(name, url, imgUrl) =>
val inner = div(
img(src := imgUrl)(),
a(href := url)(name)
)
mdiv.appendChild(inner)
}

if authors.nonEmpty then
div.asInstanceOf[html.Div].parentElement.classList.toggle("hidden")
mdiv.asInstanceOf[html.Div].parentElement.classList.toggle("hidden")
}
case Failure(err) =>
println(s"Couldn't fetch contributors. $err")
Expand Down
Loading