Skip to content

Add browsable versions extension for scaladoc #12596

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 3 commits into from
Jun 8, 2021
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
3 changes: 2 additions & 1 deletion scaladoc-js/src/Globals.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ import scala.scalajs.js.annotation.JSGlobalScope
@JSGlobalScope
object Globals extends js.Object {
val pathToRoot: String = js.native
val versionsDictionaryUrl: String = js.native
}

object StringUtils {
def createCamelCaseTokens(s: String): List[String] =
if s.isEmpty then List.empty
else if s.tail.indexWhere(_.isUpper) == -1 then List(s)
else List(s.take(s.tail.indexWhere(_.isUpper) + 1)) ++ createCamelCaseTokens(s.drop(s.tail.indexWhere(_.isUpper) + 1))
}
}
1 change: 1 addition & 0 deletions scaladoc-js/src/Main.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ object Main extends App {
Searchbar()
SocialLinks()
CodeSnippets()
DropdownHandler()
}
94 changes: 94 additions & 0 deletions scaladoc-js/src/versions-dropdown/DropdownHandler.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package dotty.tools.scaladoc

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import scala.util.{Success,Failure}

import org.scalajs.dom._
import org.scalajs.dom.ext._
import scala.scalajs.js.annotation.JSExportTopLevel
import org.scalajs.dom.ext.Ajax
import scala.scalajs.js
import scala.scalajs.js.JSON

trait Versions extends js.Object:
def versions: js.Dictionary[String]

class DropdownHandler:

val KEY = "versions-json"
val UNDEFINED_VERSIONS = "undefined_versions"

private def addVersionsList(json: String) =
val ver = JSON.parse(json).asInstanceOf[Versions]
val ddc = document.getElementById("dropdown-content")
for (k, v) <- ver.versions do
var child = document.createElement("a").asInstanceOf[html.Anchor]
child.href = v
child.text = k
ddc.appendChild(child)
val arrow = document.createElement("span").asInstanceOf[html.Span]
arrow.classList.add("ar")
document.getElementById("dropdown-button").appendChild(arrow)

private def disableButton() =
val btn = document.getElementById("dropdown-button").asInstanceOf[html.Button]
btn.disabled = true
btn.classList.remove("dropdownbtnactive")

private def getURLContent(url: String): Future[String] = Ajax.get(url).map(_.responseText)

window.sessionStorage.getItem(KEY) match
case null => // If no key, returns null
js.typeOf(Globals.versionsDictionaryUrl) match
case "undefined" =>
window.sessionStorage.setItem(KEY, UNDEFINED_VERSIONS)
disableButton()
case _ =>
getURLContent(Globals.versionsDictionaryUrl).onComplete {
case Success(json: String) =>
window.sessionStorage.setItem(KEY, json)
addVersionsList(json)
case Failure(_) =>
window.sessionStorage.setItem(KEY, UNDEFINED_VERSIONS)
disableButton()
}
case value => value match
case UNDEFINED_VERSIONS =>
disableButton()
case json =>
addVersionsList(json)

document.addEventListener("click", (e: Event) => {
if e.target.asInstanceOf[html.Element].id != "dropdown-button" then
document.getElementById("dropdown-content").classList.remove("show")
document.getElementById("dropdown-button").classList.remove("expanded")
})

document.getElementById("version").asInstanceOf[html.Span].onclick = (e: Event) => {
e.stopPropagation
}
end DropdownHandler

@JSExportTopLevel("dropdownHandler")
def dropdownHandler() =
if document.getElementById("dropdown-content").getElementsByTagName("a").size > 0 &&
window.getSelection.toString.length == 0 then
Copy link
Contributor

Choose a reason for hiding this comment

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

why do you need to check for selection?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

To let people copy the version without clicking the button

Copy link
Contributor

Choose a reason for hiding this comment

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

that's a good point. It didn't occur to me.

document.getElementById("dropdown-content").classList.toggle("show")
document.getElementById("dropdown-button").classList.toggle("expanded")

@JSExportTopLevel("filterFunction")
def filterFunction() =
val input = document.getElementById("dropdown-input").asInstanceOf[html.Input]
val filter = input.value.toUpperCase
val div = document.getElementById("dropdown-content")
val as = div.getElementsByTagName("a")

as.foreach { a =>
val txtValue = a.innerText
val cl = a.asInstanceOf[html.Anchor].classList
if txtValue.toUpperCase.indexOf(filter) > -1 then
cl.remove("filtered")
else
cl.add("filtered")
}
77 changes: 73 additions & 4 deletions scaladoc/resources/dotty_res/styles/scalastyle.css
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,9 @@ th {
#logo .projectVersion {
color: var(--leftbar-fg);
font-size: 12px;
display: flex;
padding-left: calc(0.05 * var(--side-width));
padding-right: calc(0.08 * var(--side-width));
}

.scaladoc_logo {
Expand Down Expand Up @@ -277,7 +280,7 @@ th {
}

/* spans represent a expand button */
#sideMenu2 span.ar {
span.ar {
align-items: center;
cursor: pointer;
position: absolute;
Expand All @@ -286,7 +289,7 @@ th {
padding: 4px;
}

#sideMenu2 span.ar::before {
span.ar::before {
content: "\e903"; /* arrow down */
font-family: "dotty-icons" !important;
font-size: 20px;
Expand All @@ -297,11 +300,11 @@ th {
align-items: center;
justify-content: center;
}
#sideMenu2 .expanded>span.ar::before {
.expanded>span.ar::before {
content: "\e905"; /* arrow up */
}

#sideMenu2 .div:hover>span.ar::before {
.div:hover>span.ar::before {
color: var(--leftbar-current-bg);
}

Expand Down Expand Up @@ -861,3 +864,69 @@ footer .socials {
footer {
background-color: white;
}

/* The container <div> - needed to position the dropdown content */
.versions-dropdown {
position: relative;
}

/* Dropdown Button */
.dropdownbtn {
background-color: var(--leftbar-bg);
color: white;
padding: 4px 12px;
border: none;
}

/* Dropdown button on hover & focus */
.dropdownbtnactive:hover, .dropdownbtnactive:focus {
background-color: var(--leftbar-hover-bg);
cursor: pointer;
}

/* The search field */
#dropdown-input {
box-sizing: border-box;
background-image: url('searchicon.png');
background-position: 14px 12px;
background-repeat: no-repeat;
font-size: 16px;
padding: 14px 20px 12px 45px;
border: none;
border-bottom: 1px solid #ddd;
}

/* The search field when it gets focus/clicked on */
#dropdown-input:focus {outline: 3px solid #ddd;}


/* Dropdown Content (Hidden by Default) */
.dropdown-content {
display: none;
position: absolute;
background-color: #f6f6f6;
min-width: 230px;
border: 1px solid #ddd;
z-index: 1;
}

/* Links inside the dropdown */
.dropdown-content a {
color: black;
padding: 12px 16px;
text-decoration: none;
display: block;
}

/* Change color of dropdown links on hover */
.dropdown-content a:hover {background-color: #f1f1f1}

/* Show the dropdown menu (use JS to add this class to the .dropdown-content container when the user clicks on the dropdown button) */
.show {
display:block;
}

/* Filtered entries in dropdown menu */
.dropdown-content a.filtered {
display: none;
}
6 changes: 4 additions & 2 deletions scaladoc/src/dotty/tools/scaladoc/Scaladoc.scala
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ object Scaladoc:
docCanonicalBaseUrl: String = "",
documentSyntheticTypes: Boolean = false,
snippetCompiler: List[String] = Nil,
snippetCompilerDebug: Boolean = false
snippetCompilerDebug: Boolean = false,
versionsDictionaryUrl: Option[String] = None
)

def run(args: Array[String], rootContext: CompilerContext): Reporter =
Expand Down Expand Up @@ -195,7 +196,8 @@ object Scaladoc:
docCanonicalBaseUrl.get,
YdocumentSyntheticTypes.get,
snippetCompiler.get,
snippetCompilerDebug.get
snippetCompilerDebug.get,
versionsDictionaryUrl.nonDefault
)
(Some(docArgs), newContext)
}
Expand Down
7 changes: 7 additions & 0 deletions scaladoc/src/dotty/tools/scaladoc/ScaladocSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,13 @@ class ScaladocSettings extends SettingGroup with AllScalaSettings:
"./docs"
)

val versionsDictionaryUrl: Setting[String] = StringSetting(
"-versions-dictionary-url",
"versions dictionary url",
"A URL pointing to a JSON document containing a dictionary version -> documentation location. Useful for libraries that maintain different releases docs",
""
)

val YdocumentSyntheticTypes: Setting[Boolean] =
BooleanSetting("-Ydocument-synthetic-types", "Documents intrinsic types e. g. Any, Nothing. Setting is useful only for stdlib", false)

Expand Down
2 changes: 1 addition & 1 deletion scaladoc/src/dotty/tools/scaladoc/SourceLinks.scala
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ object SourceLinks:
| €{FILE_PATH}, and €{FILE_LINE} patterns
|
|
|Template can defined only by subset of sources defined by path prefix represented by `<sub-path>`.
|Template can be defined only by subset of sources defined by path prefix represented by `<sub-path>`.
|In such case paths used in templates will be relativized against `<sub-path>`""".stripMargin

def load(config: Seq[String], revision: Option[String], projectRoot: Path = Paths.get("").toAbsolutePath)(using CompilerContext): SourceLinks =
Expand Down
16 changes: 13 additions & 3 deletions scaladoc/src/dotty/tools/scaladoc/renderers/HtmlRenderer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,10 @@ class HtmlRenderer(rootPackage: Member, val members: Map[DRI, Member])(using ctx
href := resolveLink(page.link.dri, "favicon.ico")
),
linkResources(page.link.dri, resources).toList,
script(raw(s"""var pathToRoot = "${pathToRoot(page.link.dri)}";"""))
script(raw(s"""var pathToRoot = "${pathToRoot(page.link.dri)}";""")),
ctx.args.versionsDictionaryUrl match
case Some(url) => script(raw(s"""var versionsDictionaryUrl = "$url";"""))
case None => ""
)

private def buildNavigation(pageLink: Link): AppliedTag =
Expand Down Expand Up @@ -220,8 +223,15 @@ class HtmlRenderer(rootPackage: Member, val members: Map[DRI, Member])(using ctx
span(
div(cls:="projectName")(args.name)
),
span(
args.projectVersion.map(v => div(cls:="projectVersion")(v)).toList
div(id := "version")(
div(cls := "versions-dropdown")(
div(onclick := "dropdownHandler()", id := "dropdown-button", cls := "dropdownbtn dropdownbtnactive")(
args.projectVersion.map(v => div(cls:="projectVersion")(v)).getOrElse("")
),
div(id := "dropdown-content", cls := "dropdown-content")(
input(`type` := "text", placeholder := "Search...", id := "dropdown-input", onkeyup := "filterFunction()"),
),
)
),
div(cls := "socials")(
socialLinks()
Expand Down
1 change: 1 addition & 0 deletions scaladoc/src/dotty/tools/scaladoc/util/html.scala
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ object HTML:
val value = Attr("value")
val onclick=Attr("onclick")
val titleAttr =Attr("title")
val onkeyup = Attr("onkeyup")

def raw(content: String): AppliedTag = new AppliedTag(content)
def raw(content: StringBuilder): AppliedTag = content
Expand Down