diff --git a/scaladoc/src/dotty/tools/scaladoc/renderers/HtmlRenderer.scala b/scaladoc/src/dotty/tools/scaladoc/renderers/HtmlRenderer.scala index 22803ef924dd..e73d515c7de9 100644 --- a/scaladoc/src/dotty/tools/scaladoc/renderers/HtmlRenderer.scala +++ b/scaladoc/src/dotty/tools/scaladoc/renderers/HtmlRenderer.scala @@ -45,8 +45,8 @@ class HtmlRenderer(rootPackage: Member, val members: Map[DRI, Member])(using ctx val msg = s"ERROR: Multiple index pages for doc found ${indexes.map(_.file)}" report.error(msg) - val templatePages = - (templates ++ siteContext.indexTemplate()).map(templateToPage(_, siteContext)) + val templatePages = templates.map(templateToPage(_, siteContext)) + indexes.headOption match case None if templatePages.isEmpty=> rootPckPage.withTitle(args.name) @@ -63,10 +63,29 @@ class HtmlRenderer(rootPackage: Member, val members: Map[DRI, Member])(using ctx link = navigablePage.link.copy(dri = docsRootDRI), children = Nil )) - case Some(site) => - site.orphanedTemplates.map(templateToPage(_, site)) + case Some(siteContext) => + (siteContext.orphanedTemplates :+ siteContext.indexTemplate()).map(templateToPage(_, siteContext)) + + /** + * Here we have to retrive index pages from hidden pages and replace fake index pages in navigable page tree. + */ + private def getAllPages: Seq[Page] = + + def traversePages(page: Page): (Page, Seq[Page]) = + val (newChildren, newPagesToRemove): (Seq[Page], Seq[Page]) = page.children.map(traversePages(_)).foldLeft((Seq[Page](), Seq[Page]())) { + case ((pAcc, ptrAcc), (p, ptr)) => (pAcc :+ p, ptrAcc ++ ptr) + } + hiddenPages.find(_.link == page.link) match + case None => + (page.copy(children = newChildren), newPagesToRemove) + case Some(newPage) => + (newPage.copy(children = newChildren), newPagesToRemove :+ newPage) + + val (newNavigablePage, pagesToRemove) = traversePages(navigablePage) - val allPages = navigablePage +: hiddenPages + newNavigablePage +: hiddenPages.filterNot(pagesToRemove.contains) + + val allPages = getAllPages def renderContent(page: Page) = page.content match case m: Member => @@ -160,13 +179,16 @@ class HtmlRenderer(rootPackage: Member, val members: Map[DRI, Member])(using ctx def renderNested(nav: Page, toplevel: Boolean = false): (Boolean, AppliedTag) = val isSelected = nav.link.dri == pageLink.dri - def linkHtml(exapnded: Boolean = false) = - val attrs = if (isSelected || toplevel) Seq(cls := "selected expanded") else Nil + def linkHtml(expanded: Boolean = false) = + val attrs: Seq[String] = Seq( + Option.when(isSelected)("selected"), + Option.when(expanded)("expanded") + ).flatten val icon = nav.content match { case m: Member => navigationIcon(m) case _ => Nil } - Seq(a(href := pathToPage(pageLink.dri, nav.link.dri), attrs)(icon, span(nav.link.name))) + Seq(a(href := pathToPage(pageLink.dri, nav.link.dri), cls := attrs.mkString(" "))(icon, span(nav.link.name))) nav.children match case Nil => isSelected -> div(linkHtml()) @@ -177,9 +199,10 @@ class HtmlRenderer(rootPackage: Member, val members: Map[DRI, Member])(using ctx if expanded || isSelected || toplevel then Seq(cls := "expanded") else Nil (isSelected || expanded) -> div(attr)( linkHtml(expanded), - span(cls := "ar"), + if toplevel then Nil else span(cls := "ar"), nested.map(_._2) ) + renderNested(navigablePage, toplevel = true)._2 private def canonicalUrl(l: String): AppliedTag | String = diff --git a/scaladoc/src/dotty/tools/scaladoc/renderers/SiteRenderer.scala b/scaladoc/src/dotty/tools/scaladoc/renderers/SiteRenderer.scala index a2748850ba22..a1dd1e4e71fb 100644 --- a/scaladoc/src/dotty/tools/scaladoc/renderers/SiteRenderer.scala +++ b/scaladoc/src/dotty/tools/scaladoc/renderers/SiteRenderer.scala @@ -22,7 +22,7 @@ trait SiteRenderer(using DocContext) extends Locations: def templateToPage(t: LoadedTemplate, staticSiteCtx: StaticSiteContext): Page = val dri = staticSiteCtx.driFor(t.file.toPath) val content = ResolvedTemplate(t, staticSiteCtx) - Page(Link(t.templateFile.title, dri), content, t.children.map(templateToPage(_, staticSiteCtx))) + Page(Link(t.templateFile.title.name, dri), content, t.children.map(templateToPage(_, staticSiteCtx))) private val HashRegex = "([^#]+)(#.+)".r diff --git a/scaladoc/src/dotty/tools/scaladoc/site/LoadedTemplate.scala b/scaladoc/src/dotty/tools/scaladoc/site/LoadedTemplate.scala index 30555868704a..f7623d79c7c0 100644 --- a/scaladoc/src/dotty/tools/scaladoc/site/LoadedTemplate.scala +++ b/scaladoc/src/dotty/tools/scaladoc/site/LoadedTemplate.scala @@ -33,7 +33,7 @@ case class LoadedTemplate( val site = templateFile.settings.getOrElse("page", Map.empty).asInstanceOf[Map[String, Object]] site.asJava.entrySet() ++ JSet( LazyEntry("url", () => ctx.relativePath(LoadedTemplate.this).toString), - LazyEntry("title", () => templateFile.title), + LazyEntry("title", () => templateFile.title.name), LazyEntry("excerpt", () => brief(ctx)) ) @@ -49,6 +49,6 @@ case class LoadedTemplate( val updatedSettings = templateFile.settings ++ ctx.projectWideProperties + ("site" -> (getMap("site") + ("posts" -> posts))) + ("urls" -> sourceLinks.toMap) + - ("page" -> (getMap("page") + ("title" -> templateFile.title))) + ("page" -> (getMap("page") + ("title" -> templateFile.title.name))) templateFile.resolveInner(RenderingContext(updatedSettings, ctx.layouts))(using ctx) diff --git a/scaladoc/src/dotty/tools/scaladoc/site/SidebarParser.scala b/scaladoc/src/dotty/tools/scaladoc/site/SidebarParser.scala index 2cb8be059aeb..971c766b7fe2 100644 --- a/scaladoc/src/dotty/tools/scaladoc/site/SidebarParser.scala +++ b/scaladoc/src/dotty/tools/scaladoc/site/SidebarParser.scala @@ -8,12 +8,11 @@ import collection.JavaConverters._ import java.util.Optional enum Sidebar: - val title: String - case Category(title: String, nested: List[Sidebar]) - case Page(title: String, url: String) + case Category(title: String, url: Option[String], nested: List[Sidebar]) + case Page(title: Option[String], url: String) object Sidebar: - case class RawInput(var title: String,var url: String, var subsection: JList[RawInput]): + case class RawInput(var title: String, var url: String, var subsection: JList[RawInput]): def this() = this("", "", JList()) def setTitle(t: String) = this.title = t @@ -24,10 +23,10 @@ object Sidebar: private object RawTypeRef extends TypeReference[RawInnerTpe] private def toSidebar(r: RawInput): Sidebar = r match - case RawInput(title, url, list) if title.nonEmpty && url.nonEmpty && list.isEmpty() || title == "Blog" => - Sidebar.Page(title, url) - case RawInput(title, url, list) if title.nonEmpty && url.isEmpty && !list.isEmpty() => - Sidebar.Category(title, list.asScala.map(toSidebar).toList) + case RawInput(title, url, list) if url.nonEmpty && list.isEmpty() || title == "Blog" => + Sidebar.Page(Option.when(title.nonEmpty)(title), url) + case RawInput(title, url, list) if title.nonEmpty && !list.isEmpty() => + Sidebar.Category(title, Option.when(url.nonEmpty)(url), list.asScala.map(toSidebar).toList) def load(content: String): Seq[Sidebar] = val mapper = ObjectMapper(YAMLFactory()) diff --git a/scaladoc/src/dotty/tools/scaladoc/site/StaticSiteContext.scala b/scaladoc/src/dotty/tools/scaladoc/site/StaticSiteContext.scala index 0ccffd149876..00526bf623b1 100644 --- a/scaladoc/src/dotty/tools/scaladoc/site/StaticSiteContext.scala +++ b/scaladoc/src/dotty/tools/scaladoc/site/StaticSiteContext.scala @@ -19,14 +19,17 @@ class StaticSiteContext( var memberLinkResolver: String => Option[DRI] = _ => None - def indexTemplate(): Seq[LoadedTemplate] = + def indexTemplate(): LoadedTemplate = val files = List(new File(root, "index.html"), new File(root, "index.md")).filter { _.exists() } if files.size > 1 then val msg = s"ERROR: Multiple root index pages found: ${files.map(_.getAbsolutePath)}" report.error(msg) - files.flatMap(loadTemplate(_, isBlog = false)).take(1) + files.flatMap(loadTemplate(_, isBlog = false)).headOption.getOrElse { + val fakeFile = new File(root, "index.html") + LoadedTemplate(emptyTemplate(fakeFile, "index"), List.empty, fakeFile) + } lazy val layouts: Map[String, TemplateFile] = val layoutRoot = new File(root, "_layouts") @@ -39,7 +42,7 @@ class StaticSiteContext( else Some(Sidebar.load(Files.readAllLines(sidebarFile).asScala.mkString("\n"))) lazy val templates: Seq[LoadedTemplate] = - sideBarConfig.fold(loadAllFiles().sortBy(_.templateFile.title))(_.map(loadSidebarContent)) + sideBarConfig.fold(loadAllFiles().sortBy(_.templateFile.title.name))(_.map(loadSidebarContent)) lazy val orphanedTemplates: Seq[LoadedTemplate] = { def doFlatten(t: LoadedTemplate): Seq[Path] = @@ -93,7 +96,7 @@ class StaticSiteContext( val pageSettings = p.templateFile.settings.get("page").collect{ case m: Map[String @unchecked, _] => m } pageSettings.flatMap(_.get("date").collect{ case s: String => s}).getOrElse(default) // blogs without date are last - val processedChildren: Seq[LoadedTemplate] = if !isBlog then children.sortBy(_.templateFile.title) else + val processedChildren: Seq[LoadedTemplate] = if !isBlog then children.sortBy(_.templateFile.title.name) else children.sortBy(dateFrom(_)).reverse processedChildren.foreach { child => @@ -108,10 +111,10 @@ class StaticSiteContext( val processedTemplate = // Set provided name as arg in page for `docs` if from.getParentFile.toPath == docsPath && templateFile.isIndexPage() then - if templateFile.title != "index" then + if templateFile.title.name != "index" then report.warn("Property `title` will be overridden by project name", from) - templateFile.copy(title = args.name) + templateFile.copy(title = TemplateName.FilenameDefined(args.name)) else templateFile Some(LoadedTemplate(processedTemplate, processedChildren.toList, from)) @@ -122,20 +125,31 @@ class StaticSiteContext( None private def loadSidebarContent(entry: Sidebar): LoadedTemplate = entry match - case Sidebar.Page(title, url) => - val isBlog = title == "Blog" + case Sidebar.Page(optionTitle, url) => + val isBlog = optionTitle == Some("Blog") val path = if isBlog then "blog" else if Files.exists(root.toPath.resolve(url)) then url else url.stripSuffix(".html") + ".md" val file = root.toPath.resolve(path).toFile val LoadedTemplate(template, children, _) = loadTemplate(file, isBlog).get // Add proper logging if file does not exisits - LoadedTemplate(template.copy(settings = template.settings + ("title" -> title), file = file), children, file) - - case Sidebar.Category(title, nested) => - // Add support for index.html/index.md files! - val fakeFile = new File(new File(root, "docs"), title) - LoadedTemplate(emptyTemplate(fakeFile, title), nested.map(loadSidebarContent), fakeFile) + optionTitle match + case Some(title) => + val newTitle = template.title match + case t: TemplateName.YamlDefined => t + case _: TemplateName.FilenameDefined => TemplateName.SidebarDefined(title) + case t: TemplateName.SidebarDefined => t // should never reach this path + LoadedTemplate(template.copy(settings = template.settings + ("title" -> newTitle.name), file = file, title = newTitle), children, file) + case None => + LoadedTemplate(template.copy(settings = template.settings, file = file), children, file) + + case Sidebar.Category(title, optionUrl, nested) => + optionUrl match + case Some(url) => // There is an index page for section, let's load it + loadSidebarContent(Sidebar.Page(Some(title), url)).copy(children = nested.map(loadSidebarContent)) + case None => // No index page, let's create default fake file. + val fakeFile = new File(new File(root, "docs"), title) + LoadedTemplate(emptyTemplate(fakeFile, title), nested.map(loadSidebarContent), fakeFile) private def loadAllFiles() = def dir(name: String)= List(new File(root, name)).filter(_.isDirectory) diff --git a/scaladoc/src/dotty/tools/scaladoc/site/common.scala b/scaladoc/src/dotty/tools/scaladoc/site/common.scala index ffa7001303db..bb09cd92f452 100644 --- a/scaladoc/src/dotty/tools/scaladoc/site/common.scala +++ b/scaladoc/src/dotty/tools/scaladoc/site/common.scala @@ -17,8 +17,8 @@ import com.vladsch.flexmark.ext.wikilink.WikiLinkExtension import scala.collection.JavaConverters._ -val docsRootDRI: DRI = DRI(location = "docs", symbolUUID = staticFileSymbolUUID) -val apiPageDRI: DRI = DRI(location = "api") +val docsRootDRI: DRI = DRI(location = "docs/index", symbolUUID = staticFileSymbolUUID) +val apiPageDRI: DRI = DRI(location = "api/index") val defaultMarkdownOptions: DataHolder = new MutableDataSet() @@ -45,7 +45,7 @@ def emptyTemplate(file: File, title: String): TemplateFile = TemplateFile( rawCode = "", settings = Map.empty, name = file.getName.stripSuffix(".html"), - title = title, + title = TemplateName.FilenameDefined(title), hasFrame = true, resources = List.empty, layout = None, @@ -104,7 +104,7 @@ def loadTemplateFile(file: File): TemplateFile = { rawCode = content.mkString(LineSeparator), settings = settings, name = name, - title = stringSetting(allSettings, "title").getOrElse(name), + title = stringSetting(allSettings, "title").map(TemplateName.YamlDefined(_)).getOrElse(TemplateName.FilenameDefined(name)), hasFrame = !stringSetting(allSettings, "hasFrame").contains("false"), resources = (listSetting(allSettings, "extraCSS") ++ listSetting(allSettings, "extraJS")).flatten.toList, layout = stringSetting(allSettings, "layout"), diff --git a/scaladoc/src/dotty/tools/scaladoc/site/templates.scala b/scaladoc/src/dotty/tools/scaladoc/site/templates.scala index f4ce939cf44f..d14970423e43 100644 --- a/scaladoc/src/dotty/tools/scaladoc/site/templates.scala +++ b/scaladoc/src/dotty/tools/scaladoc/site/templates.scala @@ -35,6 +35,12 @@ case class RenderingContext( ) case class ResolvedPage(code: String, resources: List[String] = Nil) + +enum TemplateName(val name: String): + case YamlDefined(override val name: String) extends TemplateName(name) + case SidebarDefined(override val name: String) extends TemplateName(name) + case FilenameDefined(override val name: String) extends TemplateName(name) + /** * case class for the template files. * Template file is a file `.md` or `.html` handling settings. @@ -49,7 +55,7 @@ case class TemplateFile( rawCode: String, settings: Map[String, Object], name: String, - title: String, + title: TemplateName, hasFrame: Boolean, resources: List[String], layout: Option[String], diff --git a/scaladoc/test/dotty/tools/scaladoc/site/NavigationTest.scala b/scaladoc/test/dotty/tools/scaladoc/site/NavigationTest.scala index adfbb5113ff2..63b96d64db5b 100644 --- a/scaladoc/test/dotty/tools/scaladoc/site/NavigationTest.scala +++ b/scaladoc/test/dotty/tools/scaladoc/site/NavigationTest.scala @@ -29,7 +29,6 @@ class NavigationTest extends BaseHtmlTest: NavMenuTestEntry("Nested in a directory", "dir/nested.html", Nil) )), NavMenuTestEntry("Adoc", "Adoc.html", Seq()), - NavMenuTestEntry("Basic test", "../index.html", Seq()), NavMenuTestEntry("API", "../api/index.html", Seq( NavMenuTestEntry("tests.site", "../api/tests/site.html", Seq( NavMenuTestEntry("BrokenLink", "../api/tests/site/BrokenLink.html", Nil), @@ -47,4 +46,4 @@ class NavigationTest extends BaseHtmlTest: )) testNavMenu("docs/Adoc.html", topLevelNav) - } \ No newline at end of file + } diff --git a/scaladoc/test/dotty/tools/scaladoc/site/SiteGeneratationTest.scala b/scaladoc/test/dotty/tools/scaladoc/site/SiteGeneratationTest.scala index ebda93ff2a29..89baf7f90a4a 100644 --- a/scaladoc/test/dotty/tools/scaladoc/site/SiteGeneratationTest.scala +++ b/scaladoc/test/dotty/tools/scaladoc/site/SiteGeneratationTest.scala @@ -41,7 +41,7 @@ class SiteGeneratationTest extends BaseHtmlTest: checkFile("docs/index.html")(title = projectName, header = s"$projectName in header") def testMainIndexPage()(using ProjectContext) = - checkFile("index.html")(title = "Basic test", header = "Header", parents = Seq(projectName), indexLinks) + checkFile("index.html")(title = "Basic test", header = "Header", parents = Seq(), indexLinks) def testApiPages( mainTitle: String = "API", @@ -110,4 +110,4 @@ class SiteGeneratationTest extends BaseHtmlTest: "dir/nested.svg" ) } - } \ No newline at end of file + } diff --git a/scaladoc/test/dotty/tools/scaladoc/site/TemplateFileTests.scala b/scaladoc/test/dotty/tools/scaladoc/site/TemplateFileTests.scala index 06d0fd59d055..cbb942e36476 100644 --- a/scaladoc/test/dotty/tools/scaladoc/site/TemplateFileTests.scala +++ b/scaladoc/test/dotty/tools/scaladoc/site/TemplateFileTests.scala @@ -43,7 +43,7 @@ class TemplateFileTests: |code""".stripMargin ) { t => assertEquals(t.rawCode, "code") - assertEquals(t.title, "myTitle") + assertEquals(t.title.name, "myTitle") }