diff --git a/scala3doc/resources/dotty_res/scripts/fast-navigation-loader.js b/scala3doc/resources/dotty_res/scripts/fast-navigation-loader.js deleted file mode 100644 index 3b9c5b9b9b57..000000000000 --- a/scala3doc/resources/dotty_res/scripts/fast-navigation-loader.js +++ /dev/null @@ -1,55 +0,0 @@ -navigationPageText = fetch(pathToRoot + "navigation.html").then(response => response.text()) - -window.addEventListener('DOMContentLoaded', () => { - navigationPageText.then(data => { - document.getElementById("sideMenu").innerHTML = data; - }).then(() => { - document.querySelectorAll(".overview > a").forEach(link => { - link.setAttribute("href", pathToRoot + link.getAttribute("href")); - }) - }).then(() => { - document.querySelectorAll(".sideMenuPart").forEach(nav => { - if (!nav.classList.contains("hidden")) nav.classList.add("hidden") - }) - }).then(() => { - revealNavigationForCurrentPage() - }) - - /* Smooth scrolling support for going to the top of the page */ - document.querySelectorAll('.footer a[href^="#"]').forEach(anchor => { - anchor.addEventListener('click', function (e) { - e.preventDefault(); - - document.querySelector(this.getAttribute('href')).scrollIntoView({ - behavior: 'smooth' - }); - }); - }); -}) - -revealNavigationForCurrentPage = () => { - let pageIdParts = document.getElementById("content").attributes["pageIds"].value.toString().split("/") - let parts = document.querySelectorAll(".sideMenuPart"); - let found = 0; - do { - let pageId = pageIdParts.join("/") - parts.forEach(part => { - if (part.attributes['pageId'].value.indexOf(pageId) !== -1 && found === 0) { - found = 1; - if (part.classList.contains("hidden")){ - part.classList.remove("hidden"); - part.setAttribute('data-active',""); - } - revealParents(part) - } - }); - pageIdParts.pop() - } while (pageIdParts.length > 0) -}; - -revealParents = (part) => { - if (part.classList.contains("sideMenuPart")) { - if (part.classList.contains("hidden")) part.classList.remove("hidden"); - revealParents(part.parentNode) - } -}; \ No newline at end of file diff --git a/scala3doc/resources/dotty_res/scripts/ux.js b/scala3doc/resources/dotty_res/scripts/ux.js index 185b2a718d44..2587b8629d54 100644 --- a/scala3doc/resources/dotty_res/scripts/ux.js +++ b/scala3doc/resources/dotty_res/scripts/ux.js @@ -15,6 +15,10 @@ window.addEventListener("DOMContentLoaded", () => { } } + $("#sideMenu2 span").on('click', function(){ + $(this).parent().toggleClass("expanded") + }); + if (location.hash) { var selected = document.getElementById(location.hash.substring(1)); diff --git a/scala3doc/resources/dotty_res/styles/scalastyle.css b/scala3doc/resources/dotty_res/styles/scalastyle.css index 6e17a18f45d2..6efd941b2a9d 100644 --- a/scala3doc/resources/dotty_res/styles/scalastyle.css +++ b/scala3doc/resources/dotty_res/styles/scalastyle.css @@ -123,12 +123,6 @@ th { } /* Left bar */ -#sideMenu { - overflow-y: auto; - scrollbar-width: thin; - height: 100%; - font-size: var(--leftbar-font-size); -} #paneSearch { display: none; } @@ -159,63 +153,88 @@ th { width: 116px; } -.sideMenuPart { - padding-left: 1em; +/* Navigation */ +#sideMenu2 { + overflow-y: auto; + scrollbar-width: thin; + height: 100%; + font-size: var(--leftbar-font-size); +} + +/* divs in sidebar represent entry and its children */ +#sideMenu2 div { + padding-left: 0.7em; + position: relative; + display: none; +} + +#sideMenu2 div.expanded { + display: block; +} + +/* hide children of hidden entries even if are expanded */ +#sideMenu2 div>div.expanded { + display: none; +} + +/* show direct children of currently exmanded node*/ +#sideMenu2 div.expanded>div { + display: block; +} + +/* always show top level entry*/ +#sideMenu2>div{ + display: block; } -.sideMenuPart a { + +/* 'a's in side menu represent text of entry with link */ +#sideMenu2 a { align-items: center; flex: 1; overflow-x: hidden; overflow-wrap: anywhere; color: var(--leftbar-fg); margin-right: .5rem; - padding: 7px 0; -} -.sideMenuPart > .overview { - display: flex; - align-items: center; - position: relative; - user-select: none; -} -.sideMenuPart > .overview::before { - width: var(--side-width); - box-sizing: border-box; - content: ''; - top: 0; - right: 0; - bottom: 0; - position: absolute; - z-index: 1; -} -.sideMenuPart > .overview:hover::before { - background: var(--leftbar-hover-bg); + padding-top: 3px; + padding-bottom: 3px; + margin-top: 1px; + margin-bottom: 1px; + width: 100%; + display: block; + /* This trick adds selected bachground stratching to the lef side of screen */ + margin-left: calc(0px - var(--side-width)); + padding-left: var(--side-width); + width: calc(2 * var(--side-width)); } -.sideMenuPart > .overview:hover > a { - color: var(--leftbar-hover-fg); + +#sideMenu2 a::before { + margin-left: -12em; + width: 12em; + background: red; } -.sideMenuPart[data-active] > .overview::before { + +#sideMenu2 a.selected { background: var(--leftbar-current-bg); -} -.sideMenuPart[data-active] > .overview > a { font-weight: bold; - color: var(--leftbar-current-fg); } -.sideMenuPart.hidden > .sideMenuPart { - height: 0; - visibility: hidden; -} -.overview a, .overview .navButton { - z-index: 3; + +#sideMenu2 a:hover { + color: var(--leftbar-hover-fg); + background: var(--leftbar-hover-bg); } -.sideMenuPart .navButton { + +/* spans represent a expand button */ +#sideMenu2 span { align-items: center; - display: flex; - justify-content: flex-end; - padding: 7px 10px; cursor: pointer; + position: absolute; + right: 1em; + top: 0px; + padding: 4px; } -.sideMenuPart .navButtonContent::before { - content: "\e905"; /* arrow up */ + +#sideMenu2 span::before { + content: "\e903"; /* arrow down */ font-family: "dotty-icons" !important; font-size: 20px; line-height: var(--leftbar-font-size); @@ -225,10 +244,11 @@ th { align-items: center; justify-content: center; } -.sideMenuPart.hidden .navButtonContent::before { - content: "\e903"; /* arrow down */ +#sideMenu2 .expanded>span::before { + content: "\e905"; /* arrow up */ } -.sideMenuPart .navButton:hover .navButtonContent::before { + +#sideMenu2 .div:hover>span::before { color: var(--leftbar-current-bg); } @@ -295,7 +315,7 @@ th { padding-top: 0; } -span[data-unresolved-link].strikethrough, a.strikethrough, div.strikethrough { +span[data-unresolved-link].deprecated, a.deprecated, div.deprecated { text-decoration: line-through; } .brief { diff --git a/scala3doc/src/dotty/dokka/DocContext.scala b/scala3doc/src/dotty/dokka/DocContext.scala index d6752cb4e889..7eb58039d1bd 100644 --- a/scala3doc/src/dotty/dokka/DocContext.scala +++ b/scala3doc/src/dotty/dokka/DocContext.scala @@ -69,6 +69,7 @@ extension (r: report.type) def warn(m: String, f: File)(using CompilerContext): Unit = r.warning(createMessage(m, f, null), sourcePostionFor(f)) +case class NavigationNode(name: String, dri: DRI, nested: Seq[NavigationNode]) case class DocContext(args: Scala3doc.Args, compilerContext: CompilerContext) extends DokkaConfiguration: @@ -86,6 +87,9 @@ case class DocContext(args: Scala3doc.Args, compilerContext: CompilerContext) lazy val displaySourceSets = getSourceSets.toDisplaySourceSet + // Nasty hack but will get rid of it once we migrate away from dokka renderer + var navigationNode: Option[NavigationNode] = None + val logger = new Scala3DocDokkaLogger(using compilerContext) lazy val staticSiteContext = args.docsRoot.map(path => StaticSiteContext( diff --git a/scala3doc/src/dotty/dokka/preprocessors/ScalaEmbeddedResourceApppender.scala b/scala3doc/src/dotty/dokka/preprocessors/ScalaEmbeddedResourceApppender.scala index 650b86856cd1..20e708ae9e02 100644 --- a/scala3doc/src/dotty/dokka/preprocessors/ScalaEmbeddedResourceApppender.scala +++ b/scala3doc/src/dotty/dokka/preprocessors/ScalaEmbeddedResourceApppender.scala @@ -37,8 +37,7 @@ class ScalaEmbeddedResourceAppender extends PageTransformer { "scripts/components/Input.js", "scripts/components/FilterGroup.js", "scripts/components/Filter.js", - "scripts/data.js", - "scripts/fast-navigation-loader.js" + "scripts/data.js" )).asJava, page.getChildren ) diff --git a/scala3doc/src/dotty/dokka/site/NavigationCreator.scala b/scala3doc/src/dotty/dokka/site/NavigationCreator.scala index 2207c59993bf..b3541a4b6ebe 100644 --- a/scala3doc/src/dotty/dokka/site/NavigationCreator.scala +++ b/scala3doc/src/dotty/dokka/site/NavigationCreator.scala @@ -1,7 +1,6 @@ package dotty.dokka package site -import org.jetbrains.dokka.base.renderers.html.{NavigationNode, NavigationPage} import org.jetbrains.dokka.model.DPackage import org.jetbrains.dokka.model.DModule import org.jetbrains.dokka.pages._ @@ -11,7 +10,7 @@ import scala.collection.JavaConverters._ class NavigationCreator(using ctx: DocContext) extends PageTransformer: - private def processApiPages(pages: List[PageNode]): JList[NavigationNode] = + private def processApiPages(pages: List[PageNode]): List[NavigationNode] = def flatMapPackages(pn: PageNode): List[NavigationNode] = def processChildren = pn.getChildren.asScala.flatMap(flatMapPackages).toList pn match @@ -22,8 +21,7 @@ class NavigationCreator(using ctx: DocContext) extends PageTransformer: case p: DPackage if p.getName == "" && p.getChildren.isEmpty => Nil case p: DPackage => - val ss = p.getSourceSets.asScala.toSet.toDisplay - List(new NavigationNode(p.getName, p.getDri, ss, JList())) ++ processChildren + List(new NavigationNode(p.getName, p.getDri, Nil)) ++ processChildren case _: DModule => processChildren case _ => @@ -31,15 +29,13 @@ class NavigationCreator(using ctx: DocContext) extends PageTransformer: case _ => Nil - pages.flatMap(flatMapPackages).sortBy(_.getName).asJava + pages.flatMap(flatMapPackages).sortBy(_.name) private def processStaticPages(input: PageNode)(staticSiteContext: StaticSiteContext) = def toNavigationNode(page: StaticPageNode): NavigationNode = NavigationNode( page.title(), page.getDri.asScala.head, - ctx.displaySourceSets, - page.getChildren.asScala - .collect { case p: StaticPageNode => toNavigationNode(p)}.asJava + page.getChildren.asScala.collect { case p: StaticPageNode => toNavigationNode(p) }.toList ) def singleContentPage(p: PageNode) = @@ -49,10 +45,9 @@ class NavigationCreator(using ctx: DocContext) extends PageTransformer: if !pageRoot.getDri.contains(topLevelDri) then pageRoot else singleContentPage(pageRoot) val apiPages = docsRoot.getChildren.asScala.filterNot(_.isInstanceOf[StaticPageNode]) - val staticPages = staticSiteContext.mainPages.map(toNavigationNode).toList.asJava + val staticPages = staticSiteContext.mainPages.map(toNavigationNode).toList val apiNodes = processApiPages(apiPages.toList) - staticPages ++ - JList(new NavigationNode("API", apiPageDRI, ctx.displaySourceSets, apiNodes)) + staticPages ++ List(NavigationNode("API", apiPageDRI, apiNodes)) private def emptyNavigationJson = val strategy = new RenderingStrategy.Write("[]") @@ -62,11 +57,11 @@ class NavigationCreator(using ctx: DocContext) extends PageTransformer: def defaultApiPages = processApiPages(input.getChildren.asScala.toList) val nodes = ctx.staticSiteContext.fold(defaultApiPages)(processStaticPages(input)) - val navigationPage = new NavigationPage(new NavigationNode( + summon[DocContext].navigationNode = Some(NavigationNode( ctx.args.name, ctx.staticSiteContext.fold(topLevelDri)(_ => docsRootDRI), - ctx.displaySourceSets, nodes )) - val newChildren = input.getChildren ++ JList(emptyNavigationJson, navigationPage) + + val newChildren = input.getChildren ++ JList(emptyNavigationJson) input.modified(input.getName, newChildren) \ No newline at end of file diff --git a/scala3doc/src/dotty/renderers/ScalaHtmlRenderer.scala b/scala3doc/src/dotty/renderers/ScalaHtmlRenderer.scala index 5f7180a38af7..f64911e69912 100644 --- a/scala3doc/src/dotty/renderers/ScalaHtmlRenderer.scala +++ b/scala3doc/src/dotty/renderers/ScalaHtmlRenderer.scala @@ -29,6 +29,8 @@ import org.jsoup.Jsoup import java.nio.file.Paths class SignatureRenderer(pageContext: ContentPage, sourceSetRestriciton: JSet[DisplaySourceSet], locationProvider: LocationProvider): + val currentDri = pageContext.getDri.asScala.head + def link(dri: DRI): Option[String] = Option(locationProvider.resolve(dri, sourceSetRestriciton, pageContext)) def renderLink(name: String, dri: DRI, modifiers: AppliedAttr*) = @@ -116,6 +118,29 @@ class ScalaHtmlRenderer(using ctx: DokkaContext) extends HtmlRenderer(ctx) { val renderer = SignatureRenderer(pageContext, sourceSets, getLocationProvider) (renderer, new MemberRenderer(renderer, buildWithKotlinx(_, pageContext, null))) + private def buildNavigation(r: SignatureRenderer)(rootNav: NavigationNode): AppliedTag = + val currentPageDri = r.currentDri + + def renderNested(nav: NavigationNode): (Boolean, AppliedTag) = + val isSelected = nav.dri == currentPageDri + def linkHtml(exapnded: Boolean = false) = + val attrs = if (isSelected) Seq(cls := "selected expanded") else Nil + a(href := r.link(nav.dri).getOrElse("#"), attrs)(nav.name) + + nav.nested match + case Nil => isSelected -> div(linkHtml()) + case children => + val nested = children.map(renderNested) + val expanded = nested.exists(_._1) | nav == rootNav + val attr = if expanded || isSelected then Seq(cls := "expanded") else Nil + (isSelected || expanded) -> div(attr)( + linkHtml(expanded), + span(), + nested.map(_._2) + ) + + renderNested(rootNav)._2 + private def buildDocumentableList(n: DocumentableList, pageContext: ContentPage, sourceSetRestriciton: JSet[DisplaySourceSet]) = def render(n: ContentNode) = raw(buildWithKotlinx(n, pageContext, null)) @@ -296,6 +321,8 @@ class ScalaHtmlRenderer(using ctx: DokkaContext) extends HtmlRenderer(ctx) { span(img(src := resolveRoot(page, s"project-logo/$fileName"))) }.toSeq + val renderer = SignatureRenderer(page.asInstanceOf[ContentPage], sourceSets, getLocationProvider) + html( head( meta(charset := "utf-8"), @@ -319,7 +346,9 @@ class ScalaHtmlRenderer(using ctx: DokkaContext) extends HtmlRenderer(ctx) { ) ), div(id := "paneSearch"), - nav(id := "sideMenu"), + nav(id := "sideMenu2")( + summon[DocContext ].navigationNode.fold("No Navigation")(buildNavigation(renderer)) + ), ), div(id := "main")( div (id := "leftToggler")(