From 8fc1f15e2950af2bad23f71b10e908f5a08cfc63 Mon Sep 17 00:00:00 2001 From: sai-prasad-1 Date: Sat, 8 Jun 2024 21:12:34 +0530 Subject: [PATCH 01/25] Added AltDetails Block --- project/Dependencies.scala | 3 + .../src/dotty/tools/scaladoc/Scaladoc.scala | 19 ++++ .../scaladoc/site/StaticSiteContext.scala | 9 +- .../scaladoc/site/blocks/AltDetails.scala | 64 +++++++++++ .../scaladoc/site/helpers/DataLoader.scala | 106 ++++++++++++++++++ .../dotty/tools/scaladoc/site/templates.scala | 26 ++++- 6 files changed, 221 insertions(+), 6 deletions(-) create mode 100644 scaladoc/src/dotty/tools/scaladoc/site/blocks/AltDetails.scala create mode 100644 scaladoc/src/dotty/tools/scaladoc/site/helpers/DataLoader.scala diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 88a97721ee3c..d8e582de0afe 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -28,5 +28,8 @@ object Dependencies { "com.vladsch.flexmark" % "flexmark-ext-yaml-front-matter" % flexmarkVersion, ) + private val snakeYamlVersion = "2.2" + val `snakeyaml` = "org.yaml" % "snakeyaml" % snakeYamlVersion + val compilerInterface = "org.scala-sbt" % "compiler-interface" % "1.9.6" } diff --git a/scaladoc/src/dotty/tools/scaladoc/Scaladoc.scala b/scaladoc/src/dotty/tools/scaladoc/Scaladoc.scala index a2485085a927..c7fbe117899e 100644 --- a/scaladoc/src/dotty/tools/scaladoc/Scaladoc.scala +++ b/scaladoc/src/dotty/tools/scaladoc/Scaladoc.scala @@ -47,6 +47,7 @@ object Scaladoc: defaultTemplate: Option[String] = None, quickLinks: List[QuickLink] = List.empty, dynamicSideMenu: Boolean = false, + staticSiteOnly: Boolean = false, ) def run(args: Array[String], rootContext: CompilerContext): Reporter = @@ -67,6 +68,12 @@ object Scaladoc: if (parsedArgs.output.exists()) util.IO.delete(parsedArgs.output) + // TODO: Activate the new method to genarate only the static site + // if parsedArgs.staticSiteOnly then + // generateStaticSite(updatedArgs) // New method to generate only static site + // else + // run(updatedArgs) + run(updatedArgs) report.inform("Done") else report.error("Failure") @@ -197,6 +204,9 @@ object Scaladoc: if deprecatedSkipPackages.get.nonEmpty then report.warning(deprecatedSkipPackages.description) + val staticSiteOnly = args.contains("-staticSiteOnly") + + val docArgs = Args( projectName.withDefault("root"), dirs, @@ -231,10 +241,19 @@ object Scaladoc: defaultTemplate.nonDefault, quickLinksParsed, dynamicSideMenu.get, + staticSiteOnly ) (Some(docArgs), newContext) } + private def generateStaticSite(args: Args)(using ctx: CompilerContext): Unit = + given docContext: DocContext = new DocContext(args, ctx) + val module = ScalaModuleProvider.mkModule() + new dotty.tools.scaladoc.renderers.HtmlRenderer(module.rootPackage, module.members).render() + docContext.reportPathCompatIssues() + report.inform("Static site generation completed successfully") + docContext + private [scaladoc] def run(args: Args)(using ctx: CompilerContext): DocContext = given docContext: DocContext = new DocContext(args, ctx) val module = ScalaModuleProvider.mkModule() diff --git a/scaladoc/src/dotty/tools/scaladoc/site/StaticSiteContext.scala b/scaladoc/src/dotty/tools/scaladoc/site/StaticSiteContext.scala index a610e41f12f0..410a64855a8f 100644 --- a/scaladoc/src/dotty/tools/scaladoc/site/StaticSiteContext.scala +++ b/scaladoc/src/dotty/tools/scaladoc/site/StaticSiteContext.scala @@ -7,9 +7,9 @@ import java.nio.file.FileVisitOption import java.nio.file.Path import java.nio.file.Paths -import scala.jdk.CollectionConverters._ import scala.util.control.NonFatal + class StaticSiteContext( val root: File, val args: Scaladoc.Args, @@ -42,10 +42,15 @@ class StaticSiteContext( lazy val staticSiteRoot: StaticSiteRoot = StaticSiteLoader(root, args)(using this, outerCtx).load() + + lazy val allTemplates = def process(l: LoadedTemplate): List[LoadedTemplate] = l +: l.children.flatMap(process) process(staticSiteRoot.rootTemplate) + + + /** Handles redirecting from multiple locations to one page * * For each entry in redirectFrom setting, create a page which contains code that redirects you to the page where the redirectFrom is defined. @@ -116,6 +121,8 @@ class StaticSiteContext( def pathFromRoot(myTemplate: LoadedTemplate) = root.toPath.relativize(myTemplate.file.toPath) + val projectWideProperties = Seq("projectName" -> args.name) ++ args.projectVersion.map("projectVersion" -> _) + diff --git a/scaladoc/src/dotty/tools/scaladoc/site/blocks/AltDetails.scala b/scaladoc/src/dotty/tools/scaladoc/site/blocks/AltDetails.scala new file mode 100644 index 000000000000..918bf96324fc --- /dev/null +++ b/scaladoc/src/dotty/tools/scaladoc/site/blocks/AltDetails.scala @@ -0,0 +1,64 @@ +package dotty.tools.scaladoc.site.blocks + +import liqp.TemplateContext +import liqp.nodes.LNode +import liqp.TemplateContext +import liqp.nodes.LNode + +import java.time.LocalDate +import java.time.format.DateTimeFormatter +import java.{lang, util} +import java.util.Map as JMap +import java.util.HashMap + + +class AltDetails extends liqp.blocks.Block("altDetails") { + def render(context: TemplateContext, nodes: LNode*): Any = { + + // Ensure the block content is the last node + val block = nodes.last + val inputString = nodes.dropRight(1).map(_.render(context).toString).mkString + + val (id, title, cssClass) = extractInfo(inputString) + + + // Render the block content + val blockContent = super.asString(block.render(context), context) + + // Build the HTML string using StringBuilder + val builder = new StringBuilder + builder.append( + s""" + |
+ |
+ | + | + |
+ |
+ | $blockContent + |
+ |
+ |
+ |
+ """.stripMargin) + + // Return the final rendered string + builder.toString + } + + private def extractInfo(inputString: String): (String, String, String) = { + val pattern = """^([^']+)'(.*?)'(?:class=([\w\-]+))?$""".r + + val matchResult = pattern.findFirstMatchIn(inputString) + if (matchResult.isEmpty) { + return ("", "", "") // Or return default values + } + + val id = matchResult.get.group(1) + val title = matchResult.get.group(2) + val cssClass = matchResult.get.group(3) + + (id, title, cssClass) + } +} + diff --git a/scaladoc/src/dotty/tools/scaladoc/site/helpers/DataLoader.scala b/scaladoc/src/dotty/tools/scaladoc/site/helpers/DataLoader.scala new file mode 100644 index 000000000000..31804aad1f0a --- /dev/null +++ b/scaladoc/src/dotty/tools/scaladoc/site/helpers/DataLoader.scala @@ -0,0 +1,106 @@ +package dotty.tools.scaladoc.site.helpers + +import java.io.File +import org.yaml.snakeyaml.Yaml +import scala.io.Source +import scala.jdk.CollectionConverters._ + +/** + * Helper class to load data from YAML files in a directory. + */ +class DataLoader { + /** + * Loads data from YAML files in the specified directory. + * + * @param directoryPath The path to the directory containing YAML files. + * @return A map containing data loaded from YAML files, where the keys are file names (without extension) + * and the values are maps representing the contents of each YAML file. + */ + def loadDataDirectory(directoryPath: String): java.util.Map[String, Any] = { + val dataMap = new java.util.LinkedHashMap[String, Any]() // Change HashMap to LinkedHashMap + try { + loadDirectory(new File(directoryPath), dataMap) + } catch { + case e: Exception => + println(s"Error loading data directory: ${e.getMessage}") + } + dataMap + } + + + /** + * Recursively loads data from YAML files in the specified directory and its subdirectories. + * + * @param directory The directory to search for YAML files. + * @param dataMap The map to store the loaded data. + */ + private def loadDirectory(directory: File, dataMap: java.util.LinkedHashMap[String, Any]): Unit = { + if (directory.exists() && directory.isDirectory) { + for (file <- directory.listFiles()) { + if (file.isDirectory) { + // Recursive call for subdirectories + loadDirectory(file, dataMap) + } else { + // Read and parse YAML file + val yamlContents = readYamlFile(file) + if (yamlContents != null) { + val fileName = file.getName.stripSuffix(".yaml").stripSuffix(".yml") + dataMap.put(fileName, yamlContents) + } + } + } + } + } + + /** + * Reads and parses a YAML file. + * + * @param file The YAML file to read. + * @return A map representing the contents of the YAML file, or `null` if an error occurs. + */ + private def readYamlFile(file: File): java.util.Map[String, Any] = { + try { + val yaml = new Yaml() + val yamlContents = yaml.load(Source.fromFile(file).mkString).asInstanceOf[java.util.Map[String, Any]] + + // Convert to Scala mutable map before converting to Java map + val scalaMap = yamlContents.asScala + convertToJavaMap(scalaMap) + } catch { + case e: Exception => + println(s"Error reading YAML file '${file.getName}': ${e.getMessage}") + null // Handle any exceptions while reading or parsing YAML + } + } + + + /** + * Converts a Scala map to a Java map. + * + * @param scalaMap The Scala map to convert. + * @return The equivalent Java map. + */ + private def convertToJavaMap(scalaMap: scala.collection.mutable.Map[String, Any]): java.util.Map[String, Any] = { + val javaMap = new java.util.LinkedHashMap[String, Any]() + scalaMap.foreach { case (key, value) => + javaMap.put(key, convertValue(value)) + } + javaMap + } + + + /** + * Recursively converts values to appropriate types. + * + * @param value The value to convert. + * @return The converted value. + */ + private def convertValue(value: Any): Any = value match { + case map: java.util.Map[_, _] => + map.asScala.map { case (k, v) => k.toString -> convertValue(v) }.toMap // Convert to Scala map + case list: java.util.List[_] => + list.asScala.map(convertValue).asJava // Convert to Scala list recursively + case _ => value + } + +} diff --git a/scaladoc/src/dotty/tools/scaladoc/site/templates.scala b/scaladoc/src/dotty/tools/scaladoc/site/templates.scala index c37ff8fe0200..c78798c13ad3 100644 --- a/scaladoc/src/dotty/tools/scaladoc/site/templates.scala +++ b/scaladoc/src/dotty/tools/scaladoc/site/templates.scala @@ -3,7 +3,6 @@ package site import java.io.File import java.nio.file.{Files, Paths} - import com.vladsch.flexmark.ext.autolink.AutolinkExtension import com.vladsch.flexmark.ext.emoji.EmojiExtension import com.vladsch.flexmark.ext.gfm.strikethrough.StrikethroughExtension @@ -13,17 +12,19 @@ import com.vladsch.flexmark.ext.yaml.front.matter.{AbstractYamlFrontMatterVisito import com.vladsch.flexmark.parser.{Parser, ParserEmulationProfile} import com.vladsch.flexmark.html.HtmlRenderer import com.vladsch.flexmark.formatter.Formatter +import dotty.tools.scaladoc.site.helpers.DataLoader import liqp.Template import liqp.ParseSettings import liqp.parser.Flavor import liqp.TemplateContext import liqp.tags.Tag import liqp.nodes.LNode -import scala.jdk.CollectionConverters._ +import scala.jdk.CollectionConverters.* import scala.io.Source -import dotty.tools.scaladoc.snippets._ -import scala.util.chaining._ +import dotty.tools.scaladoc.snippets.* + +import scala.util.chaining.* /** RenderingContext stores information about defined properties, layouts and sites being resolved * @@ -79,6 +80,7 @@ case class TemplateFile( lazy val snippetCheckingFunc: SnippetChecker.SnippetCheckingFunc = val path = Some(Paths.get(file.getAbsolutePath)) + val pathBasedArg = ssctx.snippetCompilerArgs.get(path) val sourceFile = dotty.tools.dotc.util.SourceFile(dotty.tools.io.AbstractFile.getFile(path.get), scala.io.Codec.UTF8) (str: String, lineOffset: SnippetChecker.LineOffset, argOverride: Option[SCFlags]) => { @@ -98,6 +100,7 @@ case class TemplateFile( if (ctx.resolving.contains(file.getAbsolutePath)) throw new RuntimeException(s"Cycle in templates involving $file: ${ctx.resolving}") + val layoutTemplate = layout.map(name => ctx.layouts.getOrElse(name, throw new RuntimeException(s"No layouts named $name in ${ctx.layouts}")) ) @@ -110,7 +113,20 @@ case class TemplateFile( case other => other // Library requires mutable maps.. - val mutableProperties = new JHashMap(ctx.properties.transform((_, v) => asJavaElement(v)).asJava) + val mutableProperties = new JHashMap[String, Any]( + ctx.properties.transform((_, v) => asJavaElement(v)).asJava + ) + + val dataPath = ssctx.root.toPath.resolve("_data") + + + // Load the data from yaml file in _data folder + val dataMap = DataLoader().loadDataDirectory(dataPath.toString) + + + mutableProperties.put("site",dataMap) + + print(mutableProperties.get("site")) val parseSettings = ParseSettings.Builder().withFlavor(Flavor.JEKYLL).build() From feac0400b311452683928e854615811c2b649c62 Mon Sep 17 00:00:00 2001 From: sai-prasad-1 Date: Sat, 8 Jun 2024 22:59:34 +0530 Subject: [PATCH 02/25] Update : liqp version to 0.9 --- project/Build.scala | 2 +- .../src/dotty/tools/scaladoc/site/templates.scala | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index 921fbcd80b90..bdcd71330365 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1776,7 +1776,7 @@ object Build { bundleCSS.taskValue ), libraryDependencies ++= Dependencies.flexmarkDeps ++ Seq( - "nl.big-o" % "liqp" % "0.8.2", + "nl.big-o" % "liqp" % "0.9", "org.jsoup" % "jsoup" % "1.17.2", // Needed to process .html files for static site Dependencies.`jackson-dataformat-yaml`, diff --git a/scaladoc/src/dotty/tools/scaladoc/site/templates.scala b/scaladoc/src/dotty/tools/scaladoc/site/templates.scala index c78798c13ad3..142e14bf8d70 100644 --- a/scaladoc/src/dotty/tools/scaladoc/site/templates.scala +++ b/scaladoc/src/dotty/tools/scaladoc/site/templates.scala @@ -14,11 +14,12 @@ import com.vladsch.flexmark.html.HtmlRenderer import com.vladsch.flexmark.formatter.Formatter import dotty.tools.scaladoc.site.helpers.DataLoader import liqp.Template -import liqp.ParseSettings +import liqp.TemplateParser import liqp.parser.Flavor import liqp.TemplateContext import liqp.tags.Tag import liqp.nodes.LNode +import dotty.tools.scaladoc.site.blocks.AltDetails import scala.jdk.CollectionConverters.* import scala.io.Source @@ -126,11 +127,14 @@ case class TemplateFile( mutableProperties.put("site",dataMap) - print(mutableProperties.get("site")) + print(mutableProperties.get("site")) - val parseSettings = ParseSettings.Builder().withFlavor(Flavor.JEKYLL).build() +// val parseSettings = ParseSettings.Builder().withFlavor(Flavor.JEKYLL).build().withBlock(AltDetails()) + val liqpParser = TemplateParser.Builder() + .withFlavor(Flavor.JEKYLL) + .build() - val rendered = Template.parse(this.rawCode, parseSettings).render(mutableProperties) + val rendered = liqpParser.parse(this.rawCode).render(mutableProperties) // We want to render markdown only if next template is html val code = if (isHtml || layoutTemplate.exists(!_.isHtml)) rendered else From 89d11f9c73790ed7eca7b9b3926872da04b20977 Mon Sep 17 00:00:00 2001 From: sai-prasad-1 Date: Thu, 13 Jun 2024 00:54:11 +0530 Subject: [PATCH 03/25] Added Tabs Block --- .../scripts/staticsite/alt-details.js | 18 +++++ .../scripts/staticsite/tabs-block.js | 27 +++++++ .../styles/staticsite/alt-details.css | 79 +++++++++++++++++++ .../styles/staticsite/tabs-block.css | 38 +++++++++ .../dotty_res/styles/staticsitestyles.css | 79 +++++++++++++++++++ .../tools/scaladoc/renderers/Resources.scala | 9 ++- .../tools/scaladoc/site/blocks/Tabs.scala | 74 +++++++++++++++++ .../dotty/tools/scaladoc/site/templates.scala | 5 +- 8 files changed, 327 insertions(+), 2 deletions(-) create mode 100644 scaladoc/resources/dotty_res/scripts/staticsite/alt-details.js create mode 100644 scaladoc/resources/dotty_res/scripts/staticsite/tabs-block.js create mode 100644 scaladoc/resources/dotty_res/styles/staticsite/alt-details.css create mode 100644 scaladoc/resources/dotty_res/styles/staticsite/tabs-block.css create mode 100644 scaladoc/src/dotty/tools/scaladoc/site/blocks/Tabs.scala diff --git a/scaladoc/resources/dotty_res/scripts/staticsite/alt-details.js b/scaladoc/resources/dotty_res/scripts/staticsite/alt-details.js new file mode 100644 index 000000000000..43b8b93d1b12 --- /dev/null +++ b/scaladoc/resources/dotty_res/scripts/staticsite/alt-details.js @@ -0,0 +1,18 @@ +// Get all the alt-details-control elements +const altDetailsControls = document.querySelectorAll('.alt-details-control'); + +// Loop through each control element +altDetailsControls.forEach(control => { + // Add event listener for 'change' event + control.addEventListener('change', function() { + // Get the corresponding alt-details-detail element + const detailElement = this.nextElementSibling.nextElementSibling; + + // Toggle the display of the detail element based on checkbox state + if (this.checked) { + detailElement.style.display = 'block'; + } else { + detailElement.style.display = 'none'; + } + }); +}); diff --git a/scaladoc/resources/dotty_res/scripts/staticsite/tabs-block.js b/scaladoc/resources/dotty_res/scripts/staticsite/tabs-block.js new file mode 100644 index 000000000000..c6261f5ab0d3 --- /dev/null +++ b/scaladoc/resources/dotty_res/scripts/staticsite/tabs-block.js @@ -0,0 +1,27 @@ +// JavaScript for Tab Functionality +document.addEventListener("DOMContentLoaded", function() { + const tabLinks = document.querySelectorAll(".tab-link"); + const tabContents = document.querySelectorAll(".tab-content"); + + tabLinks.forEach(link => { + link.addEventListener("click", function() { + const targetId = this.getAttribute("data-tab"); + + tabLinks.forEach(l => l.classList.remove("active")); + this.classList.add("active"); + + tabContents.forEach(content => { + if (content.id === targetId) { + content.style.display = "block"; + } else { + content.style.display = "none"; + } + }); + }); + }); + + // Activate the first tab by default + if (tabLinks.length > 0) { + tabLinks[0].click(); + } +}); diff --git a/scaladoc/resources/dotty_res/styles/staticsite/alt-details.css b/scaladoc/resources/dotty_res/styles/staticsite/alt-details.css new file mode 100644 index 000000000000..79a8b8f1f288 --- /dev/null +++ b/scaladoc/resources/dotty_res/styles/staticsite/alt-details.css @@ -0,0 +1,79 @@ +.place-inline { + /* Assuming outer-container adds padding and a border */ + padding: 10px; /* Replace with actual values from outer-container */ + border: 1px solid #ccc; /* Replace with actual values from outer-container */ + + /* Adding vertical margin */ + margin: 20px 0; +} +/* ALT-DETAILS */ +.alt-details.help-info .alt-details-toggle { + background-color: #007bff; /* Replace $brand-primary with the actual color value */ + color: white; +} + +.alt-details.help-info .alt-details-toggle:hover { + background-color: #0056b3; /* Replace with darkened $brand-primary value */ +} + +.alt-details.help-info .alt-details-detail { + background: #fae6e6; +} + +.alt-details { + width: 100%; /* Assuming span-columns(12) means full width */ + + .alt-details-toggle { + width: 100%; /* Assuming span-columns(12) means full width */ + font-family: Arial, sans-serif; /* Replace $base-font-family with the actual font-family */ + line-height: normal; + text-align: center; + border: none; + background-color: #6c757d; /* Replace $brand-tertiary with the actual color value */ + padding: 5px 10px; + border-radius: 0.3rem; /* Replace $border-radius-large with the actual value */ + font-size: 1rem; /* Replace $font-size-medium with the actual value */ + cursor: pointer; + font-weight: 500; + color: #343a40; /* Replace $gray-dark with the actual color value */ + } + + .alt-details-toggle:hover { + background-color: #545b62; /* Replace with darkened $brand-tertiary value */ + } + + .alt-details-toggle:after { + content: "\f138"; /* */ + font-family: "Font Awesome 5 Free"; + font-weight: 900; + font-size: 15px; + float: right; + margin-top: 2px; + } + + .alt-details-control { + margin: 0; + } + + .alt-details-control + .alt-details-toggle + .alt-details-detail { + position: absolute; + top: -999em; + left: -999em; + } + + .alt-details-control:checked + .alt-details-toggle + .alt-details-detail { + position: static; + } + + .alt-details-control:checked + .alt-details-toggle:after { + content: "\f13a"; /* */ + } + + .alt-details-detail { + border-bottom: 1px solid #ddd; /* Replace $base-border-gray with the actual border value */ + border-left: 1px solid #ddd; /* Replace $base-border-gray with the actual border value */ + border-right: 1px solid #ddd; /* Replace $base-border-gray with the actual border value */ + padding-top: 15px; + margin-top: 15px; + } +} diff --git a/scaladoc/resources/dotty_res/styles/staticsite/tabs-block.css b/scaladoc/resources/dotty_res/styles/staticsite/tabs-block.css new file mode 100644 index 000000000000..be33349aa3ec --- /dev/null +++ b/scaladoc/resources/dotty_res/styles/staticsite/tabs-block.css @@ -0,0 +1,38 @@ +/* Style for Tabs */ +.tabs { + display: flex; + flex-direction: column; +} + +.tab-headers { + display: flex; + border-bottom: 1px solid #ccc; +} + +.tab-link { + padding: 10px 20px; + cursor: pointer; + border: none; + background: none; + outline: none; + transition: background-color 0.3s ease; +} + +.tab-link:hover { + background-color: red; +} + +.tab-link.active { + background-color: #ddd; + font-weight: bold; +} + +.tab-contents { + display: flex; + flex-direction: column; +} + +.tab-content { + display: none; + padding: 20px; +} diff --git a/scaladoc/resources/dotty_res/styles/staticsitestyles.css b/scaladoc/resources/dotty_res/styles/staticsitestyles.css index e69de29bb2d1..e28e5b938cf0 100644 --- a/scaladoc/resources/dotty_res/styles/staticsitestyles.css +++ b/scaladoc/resources/dotty_res/styles/staticsitestyles.css @@ -0,0 +1,79 @@ +/* ALT-DETAILS */ +.alt-details.help-info .alt-details-toggle { + background-color: #007bff; /* Replace $brand-primary with the actual color value */ + color: white; +} + +.alt-details.help-info .alt-details-toggle:hover { + background-color: #0056b3; /* Replace with darkened $brand-primary value */ +} + +.alt-details.help-info .alt-details-detail { + background: #fae6e6; +} + +.alt-details { + width: 100%; /* Assuming span-columns(12) means full width */ + + .alt-details-toggle { + width: 100%; /* Assuming span-columns(12) means full width */ + font-family: Arial, sans-serif; /* Replace $base-font-family with the actual font-family */ + line-height: normal; + text-align: center; + border: none; + background-color: #6c757d; /* Replace $brand-tertiary with the actual color value */ + padding: 5px 10px; + border-radius: 0.3rem; /* Replace $border-radius-large with the actual value */ + font-size: 1rem; /* Replace $font-size-medium with the actual value */ + cursor: pointer; + font-weight: 500; + color: #343a40; /* Replace $gray-dark with the actual color value */ + } + + .alt-details-toggle:hover { + background-color: #545b62; /* Replace with darkened $brand-tertiary value */ + } + + .alt-details-toggle:after { + content: "\f138"; /* */ + font-family: "Font Awesome 5 Free"; + font-weight: 900; + font-size: 15px; + float: right; + margin-top: 2px; + } + + .alt-details-control { + margin: 0; + } + + .alt-details-control + .alt-details-toggle + .alt-details-detail { + position: absolute; + top: -999em; + left: -999em; + } + + .alt-details-control:checked + .alt-details-toggle + .alt-details-detail { + position: static; + } + + .alt-details-control:checked + .alt-details-toggle:after { + content: "\f13a"; /* */ + } + + .alt-details-detail { + border-bottom: 1px solid #ddd; /* Replace $base-border-gray with the actual border value */ + border-left: 1px solid #ddd; /* Replace $base-border-gray with the actual border value */ + border-right: 1px solid #ddd; /* Replace $base-border-gray with the actual border value */ + padding-top: 15px; + margin-top: 15px; + } +} +.place-inline { + /* Assuming outer-container adds padding and a border */ + padding: 10px; /* Replace with actual values from outer-container */ + border: 1px solid #ccc; /* Replace with actual values from outer-container */ + + /* Adding vertical margin */ + margin: 20px 0; +} diff --git a/scaladoc/src/dotty/tools/scaladoc/renderers/Resources.scala b/scaladoc/src/dotty/tools/scaladoc/renderers/Resources.scala index 3e49af2e0576..c8c990c51de1 100644 --- a/scaladoc/src/dotty/tools/scaladoc/renderers/Resources.scala +++ b/scaladoc/src/dotty/tools/scaladoc/renderers/Resources.scala @@ -117,6 +117,7 @@ trait Resources(using ctx: DocContext) extends Locations, Writer: "scripts/components/Filter.js", "scripts/scaladoc-scalajs.js", "scripts/contributors.js", + ).map(dottyRes) val urls = List( @@ -135,7 +136,13 @@ trait Resources(using ctx: DocContext) extends Locations, Writer: ).map(dottyRes) val staticSiteOnlyResources = List( - "styles/staticsitestyles.css" + "styles/staticsitestyles.css", + "styles/staticsite/alt-details.css", + "styles/staticsite/tabs-block.css", + "scripts/staticsite/alt-details.js", + "scripts/staticsite/tabs-block.js" + + ).map(dottyRes) val searchDataPath = "scripts/searchData.js" diff --git a/scaladoc/src/dotty/tools/scaladoc/site/blocks/Tabs.scala b/scaladoc/src/dotty/tools/scaladoc/site/blocks/Tabs.scala new file mode 100644 index 000000000000..38305c7190fa --- /dev/null +++ b/scaladoc/src/dotty/tools/scaladoc/site/blocks/Tabs.scala @@ -0,0 +1,74 @@ +package dotty.tools.scaladoc.site.blocks + + +import liqp.TemplateContext +import liqp.nodes.LNode +import liqp.blocks.Block +import scala.collection.mutable.ListBuffer + +class TabsBlock extends Block("tabs") { + override def render(context: TemplateContext, nodes: LNode*): AnyRef = { + // Render the content of the tabs block + nodes.foreach(_.render(context)) + + // Extract tab headers and contents from the context + val tabHeaders = context.remove("tab-headers").asInstanceOf[ListBuffer[String]].mkString("\n") + val tabContents = context.remove("tab-contents").asInstanceOf[ListBuffer[String]].mkString("\n") + + // Build the HTML string using StringBuilder + val builder = new StringBuilder + builder.append( + s""" + |
+ |
+ | $tabHeaders + |
+ |
+ | $tabContents + |
+ |
+ |""".stripMargin) + + // Return the final rendered string + builder.toString + } +} + +class TabBlock extends Block("tab") { + override def render(context: TemplateContext, nodes: LNode*): AnyRef = { + val tabName = nodes.head.render(context).toString + val tabId = tabName.toLowerCase.replaceAll("\\s", "-") + val content = nodes.tail.map(_.render(context).toString).mkString + + val header = s"""""" + val tabContent = + s""" + |
+ | $content + |
+ |""".stripMargin + + // Add the header and content to the context + val headers = context.get("tab-headers") + if (headers.isInstanceOf[ListBuffer[?]]) { + val headersList = headers.asInstanceOf[ListBuffer[String]] + headersList += header + } else { + val newHeaders = ListBuffer[String]() + newHeaders += header + context.put("tab-headers", newHeaders) + } + + val contents = context.get("tab-contents") + if (contents.isInstanceOf[ListBuffer[?]]) { + val contentsList = contents.asInstanceOf[ListBuffer[String]] + contentsList += tabContent + } else { + val newContents = ListBuffer[String]() + newContents += tabContent + context.put("tab-contents", newContents) + } + + "" + } +} diff --git a/scaladoc/src/dotty/tools/scaladoc/site/templates.scala b/scaladoc/src/dotty/tools/scaladoc/site/templates.scala index 142e14bf8d70..d4da9ca8a95e 100644 --- a/scaladoc/src/dotty/tools/scaladoc/site/templates.scala +++ b/scaladoc/src/dotty/tools/scaladoc/site/templates.scala @@ -19,7 +19,7 @@ import liqp.parser.Flavor import liqp.TemplateContext import liqp.tags.Tag import liqp.nodes.LNode -import dotty.tools.scaladoc.site.blocks.AltDetails +import dotty.tools.scaladoc.site.blocks.{AltDetails,TabsBlock,TabBlock} import scala.jdk.CollectionConverters.* import scala.io.Source @@ -132,6 +132,9 @@ case class TemplateFile( // val parseSettings = ParseSettings.Builder().withFlavor(Flavor.JEKYLL).build().withBlock(AltDetails()) val liqpParser = TemplateParser.Builder() .withFlavor(Flavor.JEKYLL) + .withBlock(AltDetails()) + .withBlock(TabsBlock()) + .withBlock(TabBlock()) .build() val rendered = liqpParser.parse(this.rawCode).render(mutableProperties) From b2eab741d3ed8426fb0530dd191d1719ba6e641c Mon Sep 17 00:00:00 2001 From: sai-prasad-1 Date: Wed, 26 Jun 2024 15:07:36 +0530 Subject: [PATCH 04/25] Added Include Tag --- .../tools/scaladoc/renderers/Resources.scala | 1 - .../tools/scaladoc/site/blocks/Tabs.scala | 33 ++++++++++++++----- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/scaladoc/src/dotty/tools/scaladoc/renderers/Resources.scala b/scaladoc/src/dotty/tools/scaladoc/renderers/Resources.scala index c8c990c51de1..7a13cb78badd 100644 --- a/scaladoc/src/dotty/tools/scaladoc/renderers/Resources.scala +++ b/scaladoc/src/dotty/tools/scaladoc/renderers/Resources.scala @@ -142,7 +142,6 @@ trait Resources(using ctx: DocContext) extends Locations, Writer: "scripts/staticsite/alt-details.js", "scripts/staticsite/tabs-block.js" - ).map(dottyRes) val searchDataPath = "scripts/searchData.js" diff --git a/scaladoc/src/dotty/tools/scaladoc/site/blocks/Tabs.scala b/scaladoc/src/dotty/tools/scaladoc/site/blocks/Tabs.scala index 38305c7190fa..ecd3a6a7d30e 100644 --- a/scaladoc/src/dotty/tools/scaladoc/site/blocks/Tabs.scala +++ b/scaladoc/src/dotty/tools/scaladoc/site/blocks/Tabs.scala @@ -6,8 +6,19 @@ import liqp.nodes.LNode import liqp.blocks.Block import scala.collection.mutable.ListBuffer + + class TabsBlock extends Block("tabs") { override def render(context: TemplateContext, nodes: LNode*): AnyRef = { + val inputString = nodes.head.render(context).toString + + val pattern = """(.*?)(class=.*)""".r + + val (forValue, classValue) = inputString match { + case pattern(forPart, classPart) => (forPart, classPart.stripPrefix("class=")) + case _ => ("", "") + } + // Render the content of the tabs block nodes.foreach(_.render(context)) @@ -19,7 +30,7 @@ class TabsBlock extends Block("tabs") { val builder = new StringBuilder builder.append( s""" - |
+ |
|
| $tabHeaders |
@@ -36,14 +47,22 @@ class TabsBlock extends Block("tabs") { class TabBlock extends Block("tab") { override def render(context: TemplateContext, nodes: LNode*): AnyRef = { - val tabName = nodes.head.render(context).toString - val tabId = tabName.toLowerCase.replaceAll("\\s", "-") + val inputString = nodes.head.render(context).toString + + val pattern = """(.*)for=(.*)""".r + + val (tabName, forValue) = inputString match { + case pattern(tab, forPart) => (tab, forPart) + case _ => ("", "") + } + println(s"Rendering tab $tabName $forValue") + val content = nodes.tail.map(_.render(context).toString).mkString - val header = s"""""" + val header = s"""""" val tabContent = s""" - |
+ |
| $content |
|""".stripMargin @@ -58,7 +77,6 @@ class TabBlock extends Block("tab") { newHeaders += header context.put("tab-headers", newHeaders) } - val contents = context.get("tab-contents") if (contents.isInstanceOf[ListBuffer[?]]) { val contentsList = contents.asInstanceOf[ListBuffer[String]] @@ -68,7 +86,6 @@ class TabBlock extends Block("tab") { newContents += tabContent context.put("tab-contents", newContents) } - - "" + "" // Return empty string } } From 53512393ea1c6b2860be0efe33bb24059427ed43 Mon Sep 17 00:00:00 2001 From: sai-prasad-1 Date: Wed, 26 Jun 2024 15:12:29 +0530 Subject: [PATCH 05/25] Update Nameing --- .../scaladoc/site/customTags/IncludeTag.scala | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 scaladoc/src/dotty/tools/scaladoc/site/customTags/IncludeTag.scala diff --git a/scaladoc/src/dotty/tools/scaladoc/site/customTags/IncludeTag.scala b/scaladoc/src/dotty/tools/scaladoc/site/customTags/IncludeTag.scala new file mode 100644 index 000000000000..2add27ff7b38 --- /dev/null +++ b/scaladoc/src/dotty/tools/scaladoc/site/customTags/IncludeTag.scala @@ -0,0 +1,54 @@ +package dotty.tools.scaladoc.site.tags + + +class IncludeTag extends Tag("include") { + private val docsFolder = "_docs" + + override def render(context: TemplateContext, args: Array[? <: LNode]): Object = { + + + // Render arguments and validate filename + val filename = args(0).render(context) match { + case s: String if s.endsWith(".html") => s + case s: String => throw new IllegalArgumentException(s"{% include %} tag requires a string argument ending with .html, but got: $s") + case _ => throw new IllegalArgumentException("{% include %} tag requires a string argument ending with .html") + } + + val filePath = s"$docsFolder/$filename" + println(s"Attempting to include file: $filePath") + + val inputData = args(1).render(context) + context.put("include", inputData) + + + + try { + // Read and parse the file content + val fileContent = readFileContent(filePath) match { + case Success(content) => content + case Failure(exception) => throw new IllegalArgumentException(s"Error reading file '$filePath': ${exception.getMessage}") + } + + + val siteData = context.getVariables().asInstanceOf[java.util.Map[String, Any]] + // Parse the file content + context.getParser().parse(fileContent).render(siteData) + + } finally { + // Clean up: remove the input data from the context + context.remove("include") + } + } + + private def readFileContent(filePath: String): Try[String] = { + Try { + val file = new File(filePath) + println(s"Resolved file path: ${file.getAbsolutePath}") + if (!file.exists()) { + throw new IllegalArgumentException(s"File '$filePath' does not exist") + } + val source = Source.fromFile(file) + try source.mkString finally source.close() + } + } +} From 5fa510a1e236dffb22164f7c5086929a1426c55c Mon Sep 17 00:00:00 2001 From: sai-prasad-1 Date: Fri, 5 Jul 2024 11:39:00 +0530 Subject: [PATCH 06/25] Added Include Tag and optimise alt-details --- .../dotty_res/styles/staticsitestyles.css | 143 ++++++++++-------- .../tools/scaladoc/renderers/Resources.scala | 3 + .../scaladoc/site/blocks/AltDetails.scala | 18 +-- .../scaladoc/site/customTags/IncludeTag.scala | 52 +++++-- .../dotty/tools/scaladoc/site/templates.scala | 11 +- 5 files changed, 138 insertions(+), 89 deletions(-) diff --git a/scaladoc/resources/dotty_res/styles/staticsitestyles.css b/scaladoc/resources/dotty_res/styles/staticsitestyles.css index e28e5b938cf0..6acc1f41dc2f 100644 --- a/scaladoc/resources/dotty_res/styles/staticsitestyles.css +++ b/scaladoc/resources/dotty_res/styles/staticsitestyles.css @@ -1,11 +1,19 @@ -/* ALT-DETAILS */ +.place-inline { + padding: 10px; + margin: 20px 0; +} + + .alt-details.help-info .alt-details-toggle { - background-color: #007bff; /* Replace $brand-primary with the actual color value */ + width: 100%; + background-color: #007bff; + color: white; } .alt-details.help-info .alt-details-toggle:hover { - background-color: #0056b3; /* Replace with darkened $brand-primary value */ + background-color: #0056b3; + } .alt-details.help-info .alt-details-detail { @@ -13,67 +21,74 @@ } .alt-details { - width: 100%; /* Assuming span-columns(12) means full width */ - - .alt-details-toggle { - width: 100%; /* Assuming span-columns(12) means full width */ - font-family: Arial, sans-serif; /* Replace $base-font-family with the actual font-family */ - line-height: normal; - text-align: center; - border: none; - background-color: #6c757d; /* Replace $brand-tertiary with the actual color value */ - padding: 5px 10px; - border-radius: 0.3rem; /* Replace $border-radius-large with the actual value */ - font-size: 1rem; /* Replace $font-size-medium with the actual value */ - cursor: pointer; - font-weight: 500; - color: #343a40; /* Replace $gray-dark with the actual color value */ - } - - .alt-details-toggle:hover { - background-color: #545b62; /* Replace with darkened $brand-tertiary value */ - } - - .alt-details-toggle:after { - content: "\f138"; /* */ - font-family: "Font Awesome 5 Free"; - font-weight: 900; - font-size: 15px; - float: right; - margin-top: 2px; - } - - .alt-details-control { - margin: 0; - } - - .alt-details-control + .alt-details-toggle + .alt-details-detail { - position: absolute; - top: -999em; - left: -999em; - } - - .alt-details-control:checked + .alt-details-toggle + .alt-details-detail { - position: static; - } - - .alt-details-control:checked + .alt-details-toggle:after { - content: "\f13a"; /* */ - } - - .alt-details-detail { - border-bottom: 1px solid #ddd; /* Replace $base-border-gray with the actual border value */ - border-left: 1px solid #ddd; /* Replace $base-border-gray with the actual border value */ - border-right: 1px solid #ddd; /* Replace $base-border-gray with the actual border value */ - padding-top: 15px; - margin-top: 15px; - } + width: 100%; + display: flex; + justify-items: center; + align-items: center; + flex-direction: column; } -.place-inline { - /* Assuming outer-container adds padding and a border */ - padding: 10px; /* Replace with actual values from outer-container */ - border: 1px solid #ccc; /* Replace with actual values from outer-container */ - /* Adding vertical margin */ - margin: 20px 0; + +.alt-details-toggle { + width: 100%; + + font-family: Arial, sans-serif; + + line-height: normal; + text-align: center; + border: none; + background-color: #6c757d; + + padding: 5px 10px; + border-radius: 0.3rem; + + font-size: 1rem; + + cursor: pointer; + font-weight: 500; + color: #343a40; +} + +.alt-details-toggle:hover { + background-color: #545b62; + +} + +.alt-details-toggle:after { + content: "\f138"; + font-family: "Font Awesome 5 Free"; + font-weight: 900; + font-size: 15px; + float: right; + margin-top: 2px; +} + +.alt-details-control { + margin: 0; +} + +.alt-details-control+.alt-details-toggle+.alt-details-detail { + position: absolute; + top: -999em; + left: -999em; +} + +.alt-details-control:checked+.alt-details-toggle+.alt-details-detail { + position: static; +} + +.alt-details-control:checked+.alt-details-toggle:after { + content: "\f13a"; + +} + +.alt-details-detail { + border-bottom: 1px solid #ddd; + + border-left: 1px solid #ddd; + + border-right: 1px solid #ddd; + + padding-top: 15px; + margin-top: 15px; } diff --git a/scaladoc/src/dotty/tools/scaladoc/renderers/Resources.scala b/scaladoc/src/dotty/tools/scaladoc/renderers/Resources.scala index 7a13cb78badd..844e1bdbaff5 100644 --- a/scaladoc/src/dotty/tools/scaladoc/renderers/Resources.scala +++ b/scaladoc/src/dotty/tools/scaladoc/renderers/Resources.scala @@ -104,6 +104,9 @@ trait Resources(using ctx: DocContext) extends Locations, Writer: "styles/versions-dropdown.css", "styles/content-contributors.css", "styles/fontawesome.css", + "styles/staticsitestyles.css", + "scripts/staticsite/alt-details.js", + "scripts/staticsite/tabs-block.js", "hljs/highlight.pack.js", "hljs/LICENSE", "scripts/hljs-scala3.js", diff --git a/scaladoc/src/dotty/tools/scaladoc/site/blocks/AltDetails.scala b/scaladoc/src/dotty/tools/scaladoc/site/blocks/AltDetails.scala index 918bf96324fc..94e140b25c88 100644 --- a/scaladoc/src/dotty/tools/scaladoc/site/blocks/AltDetails.scala +++ b/scaladoc/src/dotty/tools/scaladoc/site/blocks/AltDetails.scala @@ -23,24 +23,13 @@ class AltDetails extends liqp.blocks.Block("altDetails") { // Render the block content - val blockContent = super.asString(block.render(context), context) + val blockContent = block.render(context).toString // Build the HTML string using StringBuilder val builder = new StringBuilder builder.append( - s""" - |
- |
- | - | - |
- |
- | $blockContent - |
- |
- |
- |
- """.stripMargin) + s"""
$blockContent
""" + ) // Return the final rendered string builder.toString @@ -61,4 +50,3 @@ class AltDetails extends liqp.blocks.Block("altDetails") { (id, title, cssClass) } } - diff --git a/scaladoc/src/dotty/tools/scaladoc/site/customTags/IncludeTag.scala b/scaladoc/src/dotty/tools/scaladoc/site/customTags/IncludeTag.scala index 2add27ff7b38..25f3d1ac74ce 100644 --- a/scaladoc/src/dotty/tools/scaladoc/site/customTags/IncludeTag.scala +++ b/scaladoc/src/dotty/tools/scaladoc/site/customTags/IncludeTag.scala @@ -1,12 +1,17 @@ package dotty.tools.scaladoc.site.tags +import liqp.TemplateContext +import liqp.nodes.LNode +import liqp.tags.Tag +import scala.io.Source +import java.io.File +import scala.util.{Try, Success, Failure} +import scala.jdk.CollectionConverters._ + class IncludeTag extends Tag("include") { - private val docsFolder = "_docs" override def render(context: TemplateContext, args: Array[? <: LNode]): Object = { - - // Render arguments and validate filename val filename = args(0).render(context) match { case s: String if s.endsWith(".html") => s @@ -14,14 +19,12 @@ class IncludeTag extends Tag("include") { case _ => throw new IllegalArgumentException("{% include %} tag requires a string argument ending with .html") } - val filePath = s"$docsFolder/$filename" + val filePath = s"${IncludeTag.docsFolder}/$filename" println(s"Attempting to include file: $filePath") val inputData = args(1).render(context) context.put("include", inputData) - - try { // Read and parse the file content val fileContent = readFileContent(filePath) match { @@ -29,11 +32,12 @@ class IncludeTag extends Tag("include") { case Failure(exception) => throw new IllegalArgumentException(s"Error reading file '$filePath': ${exception.getMessage}") } + // Resolve nested includes within the file content + val resolvedContent = resolveNestedIncludes(fileContent, context) val siteData = context.getVariables().asInstanceOf[java.util.Map[String, Any]] - // Parse the file content - context.getParser().parse(fileContent).render(siteData) - + // Parse the resolved content + context.getParser().parse(resolvedContent).render(siteData) } finally { // Clean up: remove the input data from the context context.remove("include") @@ -51,4 +55,34 @@ class IncludeTag extends Tag("include") { try source.mkString finally source.close() } } + + private def resolveNestedIncludes(content: String, context: TemplateContext): String = { + // Use a regex to find all {% include %} tags in the content + val includeTagPattern = "\\{%\\s*include\\s+\"([^\"]+)\"\\s*%\\}".r + + includeTagPattern.replaceAllIn(content, { matchData => + val includeFilename = matchData.group(1) + val nestedFilePath = s"${IncludeTag.docsFolder}/$includeFilename" + + // Read and parse the nested file content + val nestedFileContent = readFileContent(nestedFilePath) match { + case Success(content) => content + case Failure(exception) => throw new IllegalArgumentException(s"Error reading file '$nestedFilePath': ${exception.getMessage}") + } + + // Recursively resolve any further nested includes within the nested file content + val tempContext = new TemplateContext(context.getParser(), context.getVariables()) + resolveNestedIncludes(nestedFileContent, tempContext) + }) + } +} + +object IncludeTag { + @volatile private var docsFolder: String = "_docs" + + def setDocsFolder(path: String): Unit = { + docsFolder = path + } + + def getDocsFolder: String = docsFolder } diff --git a/scaladoc/src/dotty/tools/scaladoc/site/templates.scala b/scaladoc/src/dotty/tools/scaladoc/site/templates.scala index d4da9ca8a95e..7f4e3d9371c3 100644 --- a/scaladoc/src/dotty/tools/scaladoc/site/templates.scala +++ b/scaladoc/src/dotty/tools/scaladoc/site/templates.scala @@ -20,6 +20,7 @@ import liqp.TemplateContext import liqp.tags.Tag import liqp.nodes.LNode import dotty.tools.scaladoc.site.blocks.{AltDetails,TabsBlock,TabBlock} +import dotty.tools.scaladoc.site.tags.{IncludeTag} import scala.jdk.CollectionConverters.* import scala.io.Source @@ -127,7 +128,14 @@ case class TemplateFile( mutableProperties.put("site",dataMap) - print(mutableProperties.get("site")) + print(mutableProperties.get("site")) + + // assign the the path for Include Tag + val includePath = ssctx.root.toPath.resolve("_includes") + IncludeTag.setDocsFolder(includePath.toString) + + + // val parseSettings = ParseSettings.Builder().withFlavor(Flavor.JEKYLL).build().withBlock(AltDetails()) val liqpParser = TemplateParser.Builder() @@ -135,6 +143,7 @@ case class TemplateFile( .withBlock(AltDetails()) .withBlock(TabsBlock()) .withBlock(TabBlock()) + .withTag(IncludeTag()) .build() val rendered = liqpParser.parse(this.rawCode).render(mutableProperties) From 44e670e039d10e6988a50ad427c0059c6133e1d9 Mon Sep 17 00:00:00 2001 From: sai-prasad-1 Date: Fri, 5 Jul 2024 20:36:14 +0530 Subject: [PATCH 07/25] Update styling for alt-details and tabs --- .../dotty_res/scripts/staticsite/main.js | 47 +++++++++++++++++++ .../styles/staticsite/tabs-block.css | 37 --------------- .../dotty_res/styles/staticsitestyles.css | 44 +++++++++++++++++ .../tools/scaladoc/renderers/Resources.scala | 7 +-- .../scaladoc/site/blocks/AltDetails.scala | 15 ++++-- 5 files changed, 105 insertions(+), 45 deletions(-) create mode 100644 scaladoc/resources/dotty_res/scripts/staticsite/main.js diff --git a/scaladoc/resources/dotty_res/scripts/staticsite/main.js b/scaladoc/resources/dotty_res/scripts/staticsite/main.js new file mode 100644 index 000000000000..6b98eefdbc2a --- /dev/null +++ b/scaladoc/resources/dotty_res/scripts/staticsite/main.js @@ -0,0 +1,47 @@ +// Get all the alt-details-control elements +const altDetailsControls = document.querySelectorAll('.alt-details-control'); + +// Loop through each control element +altDetailsControls.forEach(control => { + // Add event listener for 'change' event + control.addEventListener('change', function () { + // Get the corresponding alt-details-detail element + const detailElement = this.nextElementSibling.nextElementSibling; + + // Toggle the display of the detail element based on checkbox state + if (this.checked) { + detailElement.style.display = 'block'; + } else { + detailElement.style.display = 'none'; + } + }); +}); + + +// JavaScript for Tab Functionality +document.addEventListener("DOMContentLoaded", function () { + const tabLinks = document.querySelectorAll(".tab-link"); + const tabContents = document.querySelectorAll(".tab-content"); + + tabLinks.forEach(link => { + link.addEventListener("click", function () { + const targetId = this.getAttribute("data-tab"); + + tabLinks.forEach(l => l.classList.remove("active")); + this.classList.add("active"); + + tabContents.forEach(content => { + if (content.id === targetId) { + content.style.display = "block"; + } else { + content.style.display = "none"; + } + }); + }); + }); + + // Activate the first tab by default + if (tabLinks.length > 0) { + tabLinks[0].click(); + } +}); diff --git a/scaladoc/resources/dotty_res/styles/staticsite/tabs-block.css b/scaladoc/resources/dotty_res/styles/staticsite/tabs-block.css index be33349aa3ec..f72419ee50f2 100644 --- a/scaladoc/resources/dotty_res/styles/staticsite/tabs-block.css +++ b/scaladoc/resources/dotty_res/styles/staticsite/tabs-block.css @@ -1,38 +1 @@ /* Style for Tabs */ -.tabs { - display: flex; - flex-direction: column; -} - -.tab-headers { - display: flex; - border-bottom: 1px solid #ccc; -} - -.tab-link { - padding: 10px 20px; - cursor: pointer; - border: none; - background: none; - outline: none; - transition: background-color 0.3s ease; -} - -.tab-link:hover { - background-color: red; -} - -.tab-link.active { - background-color: #ddd; - font-weight: bold; -} - -.tab-contents { - display: flex; - flex-direction: column; -} - -.tab-content { - display: none; - padding: 20px; -} diff --git a/scaladoc/resources/dotty_res/styles/staticsitestyles.css b/scaladoc/resources/dotty_res/styles/staticsitestyles.css index 6acc1f41dc2f..9b6072c7e14f 100644 --- a/scaladoc/resources/dotty_res/styles/staticsitestyles.css +++ b/scaladoc/resources/dotty_res/styles/staticsitestyles.css @@ -92,3 +92,47 @@ padding-top: 15px; margin-top: 15px; } + + + +.tabs { + display: flex; + flex-direction: column; + background: #e2e2e2; +} + +.tab-headers { + display: flex; + border: 1px solid #ccc; + border-top: none; + +} + +.tab-link { + padding: 10px 20px; + cursor: pointer; + border: none; + background: none; + outline: none; + transition: background-color 0.3s ease; +} + +.tab-link:hover { + background-color: rgb(132, 132, 132); +} + +.tab-link.active { + color: #1e2dff; + font-weight: bold; + border-bottom: #1e2dff 3px solid; +} + +.tab-contents { + display: flex; + flex-direction: column; +} + +.tab-content { + display: none; + padding: 20px; +} diff --git a/scaladoc/src/dotty/tools/scaladoc/renderers/Resources.scala b/scaladoc/src/dotty/tools/scaladoc/renderers/Resources.scala index 844e1bdbaff5..a192a4e15004 100644 --- a/scaladoc/src/dotty/tools/scaladoc/renderers/Resources.scala +++ b/scaladoc/src/dotty/tools/scaladoc/renderers/Resources.scala @@ -120,6 +120,7 @@ trait Resources(using ctx: DocContext) extends Locations, Writer: "scripts/components/Filter.js", "scripts/scaladoc-scalajs.js", "scripts/contributors.js", + "scripts/staticsite/main.js" ).map(dottyRes) @@ -139,11 +140,7 @@ trait Resources(using ctx: DocContext) extends Locations, Writer: ).map(dottyRes) val staticSiteOnlyResources = List( - "styles/staticsitestyles.css", - "styles/staticsite/alt-details.css", - "styles/staticsite/tabs-block.css", - "scripts/staticsite/alt-details.js", - "scripts/staticsite/tabs-block.js" + // "styles/staticsitestyles.css", ).map(dottyRes) diff --git a/scaladoc/src/dotty/tools/scaladoc/site/blocks/AltDetails.scala b/scaladoc/src/dotty/tools/scaladoc/site/blocks/AltDetails.scala index 94e140b25c88..30fece7ee0c0 100644 --- a/scaladoc/src/dotty/tools/scaladoc/site/blocks/AltDetails.scala +++ b/scaladoc/src/dotty/tools/scaladoc/site/blocks/AltDetails.scala @@ -28,8 +28,17 @@ class AltDetails extends liqp.blocks.Block("altDetails") { // Build the HTML string using StringBuilder val builder = new StringBuilder builder.append( - s"""
$blockContent
""" - ) + s""" + |
+ |
+ | + | + |
+ |
+ | $blockContent
+ |
+ |
+ """.stripMargin) // Return the final rendered string builder.toString @@ -40,7 +49,7 @@ class AltDetails extends liqp.blocks.Block("altDetails") { val matchResult = pattern.findFirstMatchIn(inputString) if (matchResult.isEmpty) { - return ("", "", "") // Or return default values + return ("", "", "") } val id = matchResult.get.group(1) From 907584b773af6c57ffa601ca2780d8d6379ca9aa Mon Sep 17 00:00:00 2001 From: sai-prasad-1 Date: Thu, 11 Jul 2024 02:35:20 +0530 Subject: [PATCH 08/25] Fix Front-matter parser --- old_code.txt | 77 +++++++++++++++++++ .../dotty/tools/scaladoc/site/common.scala | 26 ++++++- .../dotty/tools/scaladoc/site/templates.scala | 2 - 3 files changed, 101 insertions(+), 4 deletions(-) create mode 100644 old_code.txt diff --git a/old_code.txt b/old_code.txt new file mode 100644 index 000000000000..245f22957a33 --- /dev/null +++ b/old_code.txt @@ -0,0 +1,77 @@ + + + +package dotty.tools.scaladoc +package site + +import java.io.File +import java.nio.file.Files +import java.nio.file.Paths + +import org.jsoup.Jsoup +import scala.jdk.CollectionConverters._ + +case class LazyEntry(getKey: String, value: () => String) extends JMapEntry[String, Object]: + lazy val getValue: Object = value() + def setValue(x$0: Object): Object = ??? + +case class LoadedTemplate( + templateFile: TemplateFile, + children: List[LoadedTemplate], + file: File, + hidden: Boolean = false): + + private def brief(ctx: StaticSiteContext): String = + try + val code = Jsoup.parse(resolveToHtml(ctx).code) + Option(code.select("p").first()).fold("...")(_.outerHtml()) + catch + case e: Throwable => + val msg = s"[ERROR] Unable to process brief for ${templateFile.file}" + report.error(msg, templateFile.file, e)(using ctx.outerCtx) + "..." + + def lazyTemplateProperties(ctx: StaticSiteContext): JMap[String, Object] = new java.util.AbstractMap[String, Object](): + lazy val entrySet: JSet[JMapEntry[String, Object]] = + val site = templateFile.settings.getOrElse("page", Map.empty).asInstanceOf[Map[String, Object]] + site.asJava.entrySet() ++ JSet( + LazyEntry("url", () => "/" ++ ctx.pathFromRoot(LoadedTemplate.this).toString), + LazyEntry("title", () => templateFile.title.name), + LazyEntry("excerpt", () => brief(ctx)) + ) + + def resolveToHtml(ctx: StaticSiteContext): ResolvedPage = + val subpages = children.filterNot(_.hidden).map(_.lazyTemplateProperties(ctx)) + + def getMap(key: String): Map[String, Object] = + templateFile.settings.getOrElse(key, Map.empty).asInstanceOf[Map[String, Object]] + + // Handle nested structures and lists in frontmatter + def mergeMaps(map1: Map[String, Object], map2: Map[String, Object]): Map[String, Object] = + (map1.keySet ++ map2.keySet).map { key => + key -> ((map1.get(key), map2.get(key)) match { + case (Some(v1: Map[_, _]), Some(v2: Map[_, _])) => + mergeMaps(v1.asInstanceOf[Map[String, Object]], v2.asInstanceOf[Map[String, Object]]) + case (Some(v1: List[_]), Some(v2: List[_])) => + (v1 ++ v2).asInstanceOf[Object] + case (_, Some(v2)) => v2 + case (Some(v1), _) => v1 + case _ => throw new IllegalStateException("Unreachable code") + }) + }.toMap + + val sourceLinks = if !templateFile.file.exists() then Nil else + val actualPath = templateFile.file.toPath + ctx.sourceLinks.pathTo(actualPath).map("viewSource" -> _ ) ++ + ctx.sourceLinks.pathTo(actualPath, operation = "edit").map("editSource" -> _) + + val pageFrontmatter = getMap("page") + ("title" -> templateFile.title.name) + val mergedPageSettings = mergeMaps(pageFrontmatter, templateFile.settings.asInstanceOf[Map[String, Object]]) + + val siteSettings = getMap("site") + ("subpages" -> subpages) + val projectWidePropsMap = ctx.projectWideProperties.toMap.asInstanceOf[Map[String, Object]] // Convert to map + + val updatedSettings = mergeMaps(mergedPageSettings, projectWidePropsMap) + + ("site" -> siteSettings) + ("urls" -> sourceLinks.toMap.asInstanceOf[Map[String, Object]]) + + templateFile.resolveInner(RenderingContext(updatedSettings, ctx.layouts))(using ctx) diff --git a/scaladoc/src/dotty/tools/scaladoc/site/common.scala b/scaladoc/src/dotty/tools/scaladoc/site/common.scala index 9e58dbe3cd28..58288d804a5e 100644 --- a/scaladoc/src/dotty/tools/scaladoc/site/common.scala +++ b/scaladoc/src/dotty/tools/scaladoc/site/common.scala @@ -20,6 +20,9 @@ import scala.jdk.CollectionConverters._ import com.vladsch.flexmark.util.data.DataHolder import com.vladsch.flexmark.util.data.MutableDataSet +import org.yaml.snakeyaml.Yaml + + val docsRootDRI: DRI = DRI(location = "_docs/index", symbolUUID = staticFileSymbolUUID) val apiPageDRI: DRI = DRI(location = "api/index") @@ -75,17 +78,36 @@ def loadTemplateFile(file: File, defaultTitle: Option[TemplateName] = None)(usin (lines.tail, Nil) } else (Nil, lines) - val configParsed = yamlParser.parse(config.mkString(LineSeparator)) + val yamlString = config.mkString(LineSeparator) + val configParsed = yamlParser.parse(yamlString) val yamlCollector = new AbstractYamlFrontMatterVisitor() yamlCollector.visit(configParsed) + val cleanedYamlString = yamlString + .stripPrefix(ConfigSeparator) + .stripSuffix(ConfigSeparator) + + println(s"Cleaned YAML String: $cleanedYamlString") // Debug statement + val yaml = new Yaml() + val parsedYaml: java.util.LinkedHashMap[String, Object]= if (cleanedYamlString.trim.isEmpty) null else yaml.load(cleanedYamlString).asInstanceOf[java.util.LinkedHashMap[String, Object]] + + println(s"Parsed YAML: $parsedYaml") // Debug statement + + + + def getSettingValue(k: String, v: JList[String]): String | List[String] = if v.size == 1 then v.get(0) else v.asScala.toList val globalKeys = Set("extraJS", "extraCSS", "layout", "hasFrame", "name", "title") val allSettings = yamlCollector.getData.asScala.toMap.transform(getSettingValue) val (global, inner) = allSettings.partition((k,_) => globalKeys.contains(k)) - val settings = Map("page" -> inner) ++ global + val settings = if (parsedYaml == null) { + Map("page" -> inner) ++ global + } else { + Map("page" -> parsedYaml.asScala.toMap) ++ global + } + def stringSetting(settings: Map[String, Object], name: String): Option[String] = settings.get(name).map { case List(elem: String) => elem diff --git a/scaladoc/src/dotty/tools/scaladoc/site/templates.scala b/scaladoc/src/dotty/tools/scaladoc/site/templates.scala index 7f4e3d9371c3..8586d5eb3ed9 100644 --- a/scaladoc/src/dotty/tools/scaladoc/site/templates.scala +++ b/scaladoc/src/dotty/tools/scaladoc/site/templates.scala @@ -128,8 +128,6 @@ case class TemplateFile( mutableProperties.put("site",dataMap) - print(mutableProperties.get("site")) - // assign the the path for Include Tag val includePath = ssctx.root.toPath.resolve("_includes") IncludeTag.setDocsFolder(includePath.toString) From f25ab16702c93d7418989e9a40ea12b611887a1d Mon Sep 17 00:00:00 2001 From: sai-prasad-1 Date: Thu, 25 Jul 2024 11:47:46 +0530 Subject: [PATCH 09/25] Update Tabs Block to use Radio Buttons --- .../dotty_res/scripts/staticsite/main.js | 39 ++++---- .../dotty_res/styles/staticsitestyles.css | 52 +++++----- .../tools/scaladoc/site/blocks/Tabs.scala | 48 +++++----- .../scaladoc/site/helpers/DataLoader.scala | 94 +++++-------------- .../dotty/tools/scaladoc/site/templates.scala | 7 +- 5 files changed, 95 insertions(+), 145 deletions(-) diff --git a/scaladoc/resources/dotty_res/scripts/staticsite/main.js b/scaladoc/resources/dotty_res/scripts/staticsite/main.js index 6b98eefdbc2a..7ac99f629ae0 100644 --- a/scaladoc/resources/dotty_res/scripts/staticsite/main.js +++ b/scaladoc/resources/dotty_res/scripts/staticsite/main.js @@ -17,31 +17,30 @@ altDetailsControls.forEach(control => { }); }); - -// JavaScript for Tab Functionality document.addEventListener("DOMContentLoaded", function () { - const tabLinks = document.querySelectorAll(".tab-link"); - const tabContents = document.querySelectorAll(".tab-content"); + const tabContainers = document.querySelectorAll('.tabs'); + tabContainers.forEach(container => { + const radios = container.querySelectorAll('.tab-radio'); + const labels = container.querySelectorAll('.tab-label'); + const contents = container.querySelectorAll('.tab-content'); + + // Hide all tab contents except the first + contents.forEach((content, index) => { + if (index !== 0) content.style.display = 'none'; + }); - tabLinks.forEach(link => { - link.addEventListener("click", function () { - const targetId = this.getAttribute("data-tab"); + // Check the first radio button + if (radios.length > 0) radios[0].checked = true; + contents[0].style.display = 'block'; // Ensure the first tab content is displayed - tabLinks.forEach(l => l.classList.remove("active")); - this.classList.add("active"); + labels.forEach((label, index) => { + label.addEventListener('click', () => { + // Hide all tab contents + contents.forEach(content => content.style.display = 'none'); - tabContents.forEach(content => { - if (content.id === targetId) { - content.style.display = "block"; - } else { - content.style.display = "none"; - } + // Show the clicked tab's content + contents[index].style.display = 'block'; }); }); }); - - // Activate the first tab by default - if (tabLinks.length > 0) { - tabLinks[0].click(); - } }); diff --git a/scaladoc/resources/dotty_res/styles/staticsitestyles.css b/scaladoc/resources/dotty_res/styles/staticsitestyles.css index 9b6072c7e14f..8d2b29d69368 100644 --- a/scaladoc/resources/dotty_res/styles/staticsitestyles.css +++ b/scaladoc/resources/dotty_res/styles/staticsitestyles.css @@ -93,46 +93,42 @@ margin-top: 15px; } - - +/* General Tabs Styles */ .tabs { - display: flex; - flex-direction: column; - background: #e2e2e2; + width: 100%; + margin: 0 auto; } -.tab-headers { - display: flex; - border: 1px solid #ccc; - border-top: none; - +/* Tab Radio Buttons (Hidden) */ +.tab-radio { + display: none; } -.tab-link { +/* Tab Labels */ +.tab-label { + display: inline-block; padding: 10px 20px; + margin: 0; cursor: pointer; - border: none; - background: none; - outline: none; - transition: background-color 0.3s ease; -} - -.tab-link:hover { - background-color: rgb(132, 132, 132); -} - -.tab-link.active { - color: #1e2dff; - font-weight: bold; - border-bottom: #1e2dff 3px solid; + background-color: #f1f1f1; + border: 1px solid #ddd; + border-bottom: none; + transition: background-color 0.3s ease-in-out; } -.tab-contents { - display: flex; - flex-direction: column; +.tab-label:hover { + background-color: #ddd; } +/* Tab Contents */ .tab-content { display: none; padding: 20px; + border: 1px solid #ddd; + border-top: none; +} + +/* Show the content of the selected tab */ +.tab-radio:checked+.tab-label+.tab-content { + display: block; } diff --git a/scaladoc/src/dotty/tools/scaladoc/site/blocks/Tabs.scala b/scaladoc/src/dotty/tools/scaladoc/site/blocks/Tabs.scala index ecd3a6a7d30e..38ddd2449c16 100644 --- a/scaladoc/src/dotty/tools/scaladoc/site/blocks/Tabs.scala +++ b/scaladoc/src/dotty/tools/scaladoc/site/blocks/Tabs.scala @@ -5,38 +5,30 @@ import liqp.TemplateContext import liqp.nodes.LNode import liqp.blocks.Block import scala.collection.mutable.ListBuffer - - +import java.util.UUID class TabsBlock extends Block("tabs") { override def render(context: TemplateContext, nodes: LNode*): AnyRef = { - val inputString = nodes.head.render(context).toString + // Generate a unique ID for this set of tabs + val uniqueId = UUID.randomUUID().toString - val pattern = """(.*?)(class=.*)""".r - - val (forValue, classValue) = inputString match { - case pattern(forPart, classPart) => (forPart, classPart.stripPrefix("class=")) - case _ => ("", "") - } + // Add the unique ID to the context + context.put("tabs-unique-id", uniqueId) // Render the content of the tabs block nodes.foreach(_.render(context)) // Extract tab headers and contents from the context - val tabHeaders = context.remove("tab-headers").asInstanceOf[ListBuffer[String]].mkString("\n") - val tabContents = context.remove("tab-contents").asInstanceOf[ListBuffer[String]].mkString("\n") + val tabHeaders = context.remove(s"tab-headers-$uniqueId").asInstanceOf[ListBuffer[String]].mkString("\n") + val tabContents = context.remove(s"tab-contents-$uniqueId").asInstanceOf[ListBuffer[String]].mkString("\n") // Build the HTML string using StringBuilder val builder = new StringBuilder builder.append( s""" - |
- |
- | $tabHeaders - |
- |
- | $tabContents - |
+ |
+ | $tabHeaders + | $tabContents |
|""".stripMargin) @@ -55,36 +47,42 @@ class TabBlock extends Block("tab") { case pattern(tab, forPart) => (tab, forPart) case _ => ("", "") } - println(s"Rendering tab $tabName $forValue") val content = nodes.tail.map(_.render(context).toString).mkString - val header = s"""""" + // Retrieve the unique ID from the context + val uniqueId = context.get("tabs-unique-id").toString + + val header = + s""" + | + | + |""".stripMargin val tabContent = s""" - |
+ |
| $content |
|""".stripMargin // Add the header and content to the context - val headers = context.get("tab-headers") + val headers = context.get(s"tab-headers-$uniqueId") if (headers.isInstanceOf[ListBuffer[?]]) { val headersList = headers.asInstanceOf[ListBuffer[String]] headersList += header } else { val newHeaders = ListBuffer[String]() newHeaders += header - context.put("tab-headers", newHeaders) + context.put(s"tab-headers-$uniqueId", newHeaders) } - val contents = context.get("tab-contents") + val contents = context.get(s"tab-contents-$uniqueId") if (contents.isInstanceOf[ListBuffer[?]]) { val contentsList = contents.asInstanceOf[ListBuffer[String]] contentsList += tabContent } else { val newContents = ListBuffer[String]() newContents += tabContent - context.put("tab-contents", newContents) + context.put(s"tab-contents-$uniqueId", newContents) } "" // Return empty string } diff --git a/scaladoc/src/dotty/tools/scaladoc/site/helpers/DataLoader.scala b/scaladoc/src/dotty/tools/scaladoc/site/helpers/DataLoader.scala index 31804aad1f0a..abcf5f6ea7ee 100644 --- a/scaladoc/src/dotty/tools/scaladoc/site/helpers/DataLoader.scala +++ b/scaladoc/src/dotty/tools/scaladoc/site/helpers/DataLoader.scala @@ -4,20 +4,12 @@ import java.io.File import org.yaml.snakeyaml.Yaml import scala.io.Source import scala.jdk.CollectionConverters._ +import scala.collection.mutable -/** - * Helper class to load data from YAML files in a directory. - */ class DataLoader { - /** - * Loads data from YAML files in the specified directory. - * - * @param directoryPath The path to the directory containing YAML files. - * @return A map containing data loaded from YAML files, where the keys are file names (without extension) - * and the values are maps representing the contents of each YAML file. - */ - def loadDataDirectory(directoryPath: String): java.util.Map[String, Any] = { - val dataMap = new java.util.LinkedHashMap[String, Any]() // Change HashMap to LinkedHashMap + + def loadDataDirectory(directoryPath: String): mutable.LinkedHashMap[String, Any] = { + val dataMap = mutable.LinkedHashMap[String, Any]() try { loadDirectory(new File(directoryPath), dataMap) } catch { @@ -27,80 +19,42 @@ class DataLoader { dataMap } - - /** - * Recursively loads data from YAML files in the specified directory and its subdirectories. - * - * @param directory The directory to search for YAML files. - * @param dataMap The map to store the loaded data. - */ - private def loadDirectory(directory: File, dataMap: java.util.LinkedHashMap[String, Any]): Unit = { + private def loadDirectory(directory: File, dataMap: mutable.LinkedHashMap[String, Any]): Unit = { if (directory.exists() && directory.isDirectory) { for (file <- directory.listFiles()) { if (file.isDirectory) { - // Recursive call for subdirectories loadDirectory(file, dataMap) } else { - // Read and parse YAML file - val yamlContents = readYamlFile(file) - if (yamlContents != null) { - val fileName = file.getName.stripSuffix(".yaml").stripSuffix(".yml") - dataMap.put(fileName, yamlContents) - } + readYamlFile(file, dataMap) } } + } else { + println(s"Directory does not exist or is not a directory: ${directory.getPath}") } } - /** - * Reads and parses a YAML file. - * - * @param file The YAML file to read. - * @return A map representing the contents of the YAML file, or `null` if an error occurs. - */ - private def readYamlFile(file: File): java.util.Map[String, Any] = { + private def readYamlFile(file: File, dataMap: mutable.LinkedHashMap[String, Any]): Unit = { try { val yaml = new Yaml() - val yamlContents = yaml.load(Source.fromFile(file).mkString).asInstanceOf[java.util.Map[String, Any]] + val yamlContents: Any = yaml.load(Source.fromFile(file).mkString) + val fileName = file.getName.stripSuffix(".yaml").stripSuffix(".yml") + + val parsedContents = yamlContents match { + case map: java.util.Map[_, _] => + println(s"Parsed ${file.getName} as Map") + map.asScala.toMap + case list: java.util.List[_] => + println(s"Parsed ${file.getName} as List") + list.asScala.toList + case other => + println(s"Parsed ${file.getName} as ${other.getClass}") + other + } - // Convert to Scala mutable map before converting to Java map - val scalaMap = yamlContents.asScala - convertToJavaMap(scalaMap) + dataMap.put(fileName, parsedContents) } catch { case e: Exception => println(s"Error reading YAML file '${file.getName}': ${e.getMessage}") - null // Handle any exceptions while reading or parsing YAML } } - - - /** - * Converts a Scala map to a Java map. - * - * @param scalaMap The Scala map to convert. - * @return The equivalent Java map. - */ - private def convertToJavaMap(scalaMap: scala.collection.mutable.Map[String, Any]): java.util.Map[String, Any] = { - val javaMap = new java.util.LinkedHashMap[String, Any]() - scalaMap.foreach { case (key, value) => - javaMap.put(key, convertValue(value)) - } - javaMap - } - - - /** - * Recursively converts values to appropriate types. - * - * @param value The value to convert. - * @return The converted value. - */ - private def convertValue(value: Any): Any = value match { - case map: java.util.Map[_, _] => - map.asScala.map { case (k, v) => k.toString -> convertValue(v) }.toMap // Convert to Scala map - case list: java.util.List[_] => - list.asScala.map(convertValue).asJava // Convert to Scala list recursively - case _ => value - } - } diff --git a/scaladoc/src/dotty/tools/scaladoc/site/templates.scala b/scaladoc/src/dotty/tools/scaladoc/site/templates.scala index 8586d5eb3ed9..bccbc2f78df3 100644 --- a/scaladoc/src/dotty/tools/scaladoc/site/templates.scala +++ b/scaladoc/src/dotty/tools/scaladoc/site/templates.scala @@ -119,14 +119,17 @@ case class TemplateFile( ctx.properties.transform((_, v) => asJavaElement(v)).asJava ) - val dataPath = ssctx.root.toPath.resolve("_data") + val dataPath = ssctx.root.toPath.resolve("_data") // Load the data from yaml file in _data folder val dataMap = DataLoader().loadDataDirectory(dataPath.toString) - mutableProperties.put("site",dataMap) + val siteData = new JHashMap[String, Any]() + siteData.put("data",dataMap) + mutableProperties.put("site",siteData) + // assign the the path for Include Tag val includePath = ssctx.root.toPath.resolve("_includes") From c5b54500c749d6080eda7368f688d2121a236194 Mon Sep 17 00:00:00 2001 From: sai-prasad-1 Date: Wed, 7 Aug 2024 15:32:11 +0530 Subject: [PATCH 10/25] Update : Added Langauge Picker --- .../dotty_res/scripts/staticsite/main.js | 26 +++++++ .../site/customTags/LanguagePicker.scala | 60 ++++++++++++++++ .../scaladoc/site/helpers/ConfigLoader.scala | 68 +++++++++++++++++++ .../dotty/tools/scaladoc/site/templates.scala | 7 +- 4 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 scaladoc/src/dotty/tools/scaladoc/site/customTags/LanguagePicker.scala create mode 100644 scaladoc/src/dotty/tools/scaladoc/site/helpers/ConfigLoader.scala diff --git a/scaladoc/resources/dotty_res/scripts/staticsite/main.js b/scaladoc/resources/dotty_res/scripts/staticsite/main.js index 7ac99f629ae0..d9af20f20e2d 100644 --- a/scaladoc/resources/dotty_res/scripts/staticsite/main.js +++ b/scaladoc/resources/dotty_res/scripts/staticsite/main.js @@ -44,3 +44,29 @@ document.addEventListener("DOMContentLoaded", function () { }); }); }); + + +function handleLanguageChange(selectElement) { + var selectedLanguage = selectElement.value; + var currentUrl = window.location.href; + + // Remove any existing locale from the URL + var urlParts = currentUrl.split('/'); + var baseUrl = urlParts.slice(0, 3).join('/'); + var pathParts = urlParts.slice(3); + + // Check if the selected language is not the default + if (selectedLanguage) { + // Check if there's already a locale in the URL path + var updatedPath = []; + if (pathParts.length > 0 && !pathParts[0].match(/^(en|zh-cn|zh-tw|ja)$/)) { + updatedPath = [selectedLanguage].concat(pathParts); + } else { + updatedPath = [selectedLanguage].concat(pathParts); + } + window.location.href = baseUrl + '/' + updatedPath.join('/'); + } else { + // Redirect to the base URL (default language) + window.location.href = baseUrl + '/' + pathParts.join('/'); + } +} diff --git a/scaladoc/src/dotty/tools/scaladoc/site/customTags/LanguagePicker.scala b/scaladoc/src/dotty/tools/scaladoc/site/customTags/LanguagePicker.scala new file mode 100644 index 000000000000..94b0d2b27c5d --- /dev/null +++ b/scaladoc/src/dotty/tools/scaladoc/site/customTags/LanguagePicker.scala @@ -0,0 +1,60 @@ + +package dotty.tools.scaladoc.site.tags + +import java.nio.file.{Files, Paths, Path} +import java.io.{File, InputStream} +import scala.jdk.CollectionConverters._ +import scala.collection.mutable +import scala.language.dynamics +import scala.language.dynamics +import liqp.TemplateContext +import liqp.nodes.LNode +import liqp.tags.Tag +import scala.language.dynamics +import dotty.tools.scaladoc.site.helpers.{ConfigLoader} +import org.yaml.snakeyaml.Yaml + +class LanguagePickerTag extends Tag("language_picker") { + + override def render(context: TemplateContext, args: Array[? <: LNode]): Object = { + // Load the config using ConfigLoader + val configLoader = new ConfigLoader() + val basePath = LanguagePickerTag.getConfigFolder + val config = configLoader.loadConfig(basePath) + + // Accessing languages from the config + val languagesOpt = config.get[java.util.List[java.util.Map[String, String]]]("languages") + + val languages = languagesOpt match { + case Some(list) => list.asScala.toList.map(_.asScala.toMap) + case None => throw new IllegalArgumentException("No languages found in configuration") + } + + // Create the dropdown HTML + val dropdown = new StringBuilder + dropdown.append("") + dropdown.toString() + } + + +} + +object LanguagePickerTag { + @volatile private var configFolder: String = "_docs" + + def setConfigFolder(path: String): Unit = { + configFolder = path + } + + def getConfigFolder: String = configFolder +} diff --git a/scaladoc/src/dotty/tools/scaladoc/site/helpers/ConfigLoader.scala b/scaladoc/src/dotty/tools/scaladoc/site/helpers/ConfigLoader.scala new file mode 100644 index 000000000000..f802f47e98ca --- /dev/null +++ b/scaladoc/src/dotty/tools/scaladoc/site/helpers/ConfigLoader.scala @@ -0,0 +1,68 @@ +package dotty.tools.scaladoc.site.helpers + + +import java.io.{File, InputStream} +import java.nio.file.{Files, Paths, Path} +import scala.jdk.CollectionConverters._ +import scala.collection.mutable +import scala.language.dynamics +import scala.language.dynamics +import scala.language.dynamics +import liqp.TemplateContext +import liqp.nodes.LNode +import liqp.tags.Tag +import org.yaml.snakeyaml.Yaml + + +class Config(private val data: mutable.LinkedHashMap[String, Any]) extends Dynamic { + def selectDynamic(field: String): Any = { + data.get(field) match { + case Some(value: mutable.LinkedHashMap[_, _]) => + new Config(value.asInstanceOf[mutable.LinkedHashMap[String, Any]]) + case Some(value: java.util.ArrayList[_]) => + value.asScala.toSeq.map { + case map: java.util.Map[_, _] => + new Config(map.asInstanceOf[java.util.Map[String, Any]].asScala.to(mutable.LinkedHashMap)) + case other => other + } + case Some(value) => value + case None => throw new NoSuchElementException(s"No such element: $field") + } + } + + def get[T](key: String): Option[T] = { + data.get(key).map(_.asInstanceOf[T]) + } + +} + +class ConfigLoader { + def loadConfig(basePath: String): Config = { + val configMap = mutable.LinkedHashMap[String, Any]() + val yamlFileNames = Seq("_config.yml", "_config.yaml") + + try { + val yaml = new Yaml() + val baseDir = new File(basePath).getAbsolutePath + + val configFile = yamlFileNames + .map(baseDir + File.separator + _) + .map(Paths.get(_)) + .find(Files.exists(_)) + + configFile match { + case Some(path) => + val inputStream: InputStream = Files.newInputStream(path) + val data = yaml.load[java.util.Map[String, Any]](inputStream) + configMap ++= data.asScala.toMap + case None => + println(s"Warning: No config file found in path: $baseDir") + } + } catch { + case e: Exception => + println(s"Error loading config file: ${e.getMessage}") + } + + new Config(configMap) + } +} diff --git a/scaladoc/src/dotty/tools/scaladoc/site/templates.scala b/scaladoc/src/dotty/tools/scaladoc/site/templates.scala index bccbc2f78df3..e665709ecd35 100644 --- a/scaladoc/src/dotty/tools/scaladoc/site/templates.scala +++ b/scaladoc/src/dotty/tools/scaladoc/site/templates.scala @@ -20,7 +20,9 @@ import liqp.TemplateContext import liqp.tags.Tag import liqp.nodes.LNode import dotty.tools.scaladoc.site.blocks.{AltDetails,TabsBlock,TabBlock} -import dotty.tools.scaladoc.site.tags.{IncludeTag} +import dotty.tools.scaladoc.site.tags.{IncludeTag,LanguagePickerTag} + + import scala.jdk.CollectionConverters.* import scala.io.Source @@ -135,6 +137,8 @@ case class TemplateFile( val includePath = ssctx.root.toPath.resolve("_includes") IncludeTag.setDocsFolder(includePath.toString) + LanguagePickerTag.setConfigFolder(ssctx.root.toPath.resolve("/").toString) + @@ -145,6 +149,7 @@ case class TemplateFile( .withBlock(TabsBlock()) .withBlock(TabBlock()) .withTag(IncludeTag()) + .withTag(LanguagePickerTag()) .build() val rendered = liqpParser.parse(this.rawCode).render(mutableProperties) From 76247e92eb6b5675cc6057eb4a3e3fae600ba3e9 Mon Sep 17 00:00:00 2001 From: sai-prasad-1 Date: Wed, 7 Aug 2024 15:51:12 +0530 Subject: [PATCH 11/25] Fix Config Path --- scaladoc/src/dotty/tools/scaladoc/site/templates.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scaladoc/src/dotty/tools/scaladoc/site/templates.scala b/scaladoc/src/dotty/tools/scaladoc/site/templates.scala index e665709ecd35..d0e5047fe2c2 100644 --- a/scaladoc/src/dotty/tools/scaladoc/site/templates.scala +++ b/scaladoc/src/dotty/tools/scaladoc/site/templates.scala @@ -137,7 +137,7 @@ case class TemplateFile( val includePath = ssctx.root.toPath.resolve("_includes") IncludeTag.setDocsFolder(includePath.toString) - LanguagePickerTag.setConfigFolder(ssctx.root.toPath.resolve("/").toString) + LanguagePickerTag.setConfigFolder(ssctx.root.toPath.resolve().toString) From 44548bbc5f8c55ac821794f1f78718726b4c2753 Mon Sep 17 00:00:00 2001 From: sai-prasad-1 Date: Sun, 11 Aug 2024 23:48:52 +0530 Subject: [PATCH 12/25] Update Config Loader --- .../scaladoc/site/helpers/ConfigLoader.scala | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/scaladoc/src/dotty/tools/scaladoc/site/helpers/ConfigLoader.scala b/scaladoc/src/dotty/tools/scaladoc/site/helpers/ConfigLoader.scala index f802f47e98ca..a1878b714d53 100644 --- a/scaladoc/src/dotty/tools/scaladoc/site/helpers/ConfigLoader.scala +++ b/scaladoc/src/dotty/tools/scaladoc/site/helpers/ConfigLoader.scala @@ -36,6 +36,7 @@ class Config(private val data: mutable.LinkedHashMap[String, Any]) extends Dynam } + class ConfigLoader { def loadConfig(basePath: String): Config = { val configMap = mutable.LinkedHashMap[String, Any]() @@ -55,12 +56,32 @@ class ConfigLoader { val inputStream: InputStream = Files.newInputStream(path) val data = yaml.load[java.util.Map[String, Any]](inputStream) configMap ++= data.asScala.toMap + + // Check for language folders + val languagesOpt = configMap.get("languages").collect { + case list: java.util.List[java.util.Map[String, String]] => list.asScala.toList.map(_.asScala.toMap) + } + + languagesOpt match { + case Some(languages) => + for (language <- languages) { + val languageCode = language("code") + val languageFolderPath = Paths.get(baseDir, languageCode) + if (!Files.exists(languageFolderPath) || !Files.isDirectory(languageFolderPath)) { + throw new IllegalArgumentException(s"Language folder for '$languageCode' does not exist at path: $languageFolderPath") + } + } + case None => + println(s"Warning: No languages found in configuration.") + } + case None => println(s"Warning: No config file found in path: $baseDir") } } catch { case e: Exception => println(s"Error loading config file: ${e.getMessage}") + throw e } new Config(configMap) From b8b94ab83b703c25574d25447d782c4351465670 Mon Sep 17 00:00:00 2001 From: sai-prasad-1 Date: Sun, 11 Aug 2024 23:49:49 +0530 Subject: [PATCH 13/25] Update Langauge Picker --- .../site/customTags/LanguagePicker.scala | 48 ++++++++++++++++--- 1 file changed, 41 insertions(+), 7 deletions(-) diff --git a/scaladoc/src/dotty/tools/scaladoc/site/customTags/LanguagePicker.scala b/scaladoc/src/dotty/tools/scaladoc/site/customTags/LanguagePicker.scala index 94b0d2b27c5d..932766cc9f41 100644 --- a/scaladoc/src/dotty/tools/scaladoc/site/customTags/LanguagePicker.scala +++ b/scaladoc/src/dotty/tools/scaladoc/site/customTags/LanguagePicker.scala @@ -14,28 +14,62 @@ import scala.language.dynamics import dotty.tools.scaladoc.site.helpers.{ConfigLoader} import org.yaml.snakeyaml.Yaml + class LanguagePickerTag extends Tag("language_picker") { - override def render(context: TemplateContext, args: Array[? <: LNode]): Object = { + override def render(context: TemplateContext, args: Array[? <: LNode]): Object = { + // Check if the languages argument is provided + if (args.isEmpty || args(0) == null) { + throw new IllegalArgumentException("The 'languages' argument is required and must be provided in the {% language_picker %} tag.") + } + // Load the config using ConfigLoader val configLoader = new ConfigLoader() val basePath = LanguagePickerTag.getConfigFolder val config = configLoader.loadConfig(basePath) + // Extract the languages argument as a string + val languagesArgString = args(0).render(context).toString.trim + + // Ensure that the argument starts with "languages=" and parse it correctly + if (!languagesArgString.startsWith("languages=")) { + throw new IllegalArgumentException("The 'languages' argument is malformed. It should start with 'languages=[' and end with ']'") + } + + // Parse the string into a list of languages + val languagesArg = languagesArgString + .stripPrefix("languages=") + .stripPrefix("[") + .stripSuffix("]") + .split(",") + .map(_.trim.stripPrefix("'").stripSuffix("'")) + .toList + + // Debugging output + println(s"Parsed languages: ${languagesArg.mkString(", ")}") + // Accessing languages from the config val languagesOpt = config.get[java.util.List[java.util.Map[String, String]]]("languages") - - val languages = languagesOpt match { + val languagesFromConfig = languagesOpt match { case Some(list) => list.asScala.toList.map(_.asScala.toMap) case None => throw new IllegalArgumentException("No languages found in configuration") } - // Create the dropdown HTML - val dropdown = new StringBuilder - dropdown.append("") for (language <- languages) { val code = language("code") val name = language("name") @@ -45,7 +79,7 @@ class LanguagePickerTag extends Tag("language_picker") { dropdown.append("") dropdown.toString() } - +} } From 8ddd55f344c722516002adc687de4213a5dadd78 Mon Sep 17 00:00:00 2001 From: sai-prasad-1 Date: Mon, 12 Aug 2024 10:57:37 +0530 Subject: [PATCH 14/25] Update : check for langauge folder existance --- .../site/customTags/LanguagePicker.scala | 2 +- .../scaladoc/site/helpers/ConfigLoader.scala | 35 ++++++++++++------- .../dotty/tools/scaladoc/site/templates.scala | 2 +- 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/scaladoc/src/dotty/tools/scaladoc/site/customTags/LanguagePicker.scala b/scaladoc/src/dotty/tools/scaladoc/site/customTags/LanguagePicker.scala index 932766cc9f41..73c0f95d297c 100644 --- a/scaladoc/src/dotty/tools/scaladoc/site/customTags/LanguagePicker.scala +++ b/scaladoc/src/dotty/tools/scaladoc/site/customTags/LanguagePicker.scala @@ -81,7 +81,7 @@ class LanguagePickerTag extends Tag("language_picker") { } } -} + object LanguagePickerTag { @volatile private var configFolder: String = "_docs" diff --git a/scaladoc/src/dotty/tools/scaladoc/site/helpers/ConfigLoader.scala b/scaladoc/src/dotty/tools/scaladoc/site/helpers/ConfigLoader.scala index a1878b714d53..c8f7594cbdc5 100644 --- a/scaladoc/src/dotty/tools/scaladoc/site/helpers/ConfigLoader.scala +++ b/scaladoc/src/dotty/tools/scaladoc/site/helpers/ConfigLoader.scala @@ -59,22 +59,31 @@ class ConfigLoader { // Check for language folders val languagesOpt = configMap.get("languages").collect { - case list: java.util.List[java.util.Map[String, String]] => list.asScala.toList.map(_.asScala.toMap) - } - - languagesOpt match { - case Some(languages) => - for (language <- languages) { - val languageCode = language("code") - val languageFolderPath = Paths.get(baseDir, languageCode) - if (!Files.exists(languageFolderPath) || !Files.isDirectory(languageFolderPath)) { - throw new IllegalArgumentException(s"Language folder for '$languageCode' does not exist at path: $languageFolderPath") - } + case list: java.util.List[_] => + list.asScala.toList.collect { + case map: java.util.Map[_, _] => + map.asScala.toMap.collect { + case (key: String, value: String) => key -> value + } } - case None => - println(s"Warning: No languages found in configuration.") } + + // languagesOpt match { + // case Some(languages) => + // for (language <- languages) { + // // Cast the key to String to avoid type mismatch errors + // val languageCode = language("code".asInstanceOf[language.K]).asInstanceOf[String] + // val languageFolderPath = Paths.get(baseDir, languageCode) + // if (!Files.exists(languageFolderPath) || !Files.isDirectory(languageFolderPath)) { + // throw new IllegalArgumentException(s"Language folder for '$languageCode' does not exist at path: $languageFolderPath") + // } + // } + // case None => + // println(s"Warning: No languages found in configuration.") + // } + + case None => println(s"Warning: No config file found in path: $baseDir") } diff --git a/scaladoc/src/dotty/tools/scaladoc/site/templates.scala b/scaladoc/src/dotty/tools/scaladoc/site/templates.scala index d0e5047fe2c2..6d04345902a4 100644 --- a/scaladoc/src/dotty/tools/scaladoc/site/templates.scala +++ b/scaladoc/src/dotty/tools/scaladoc/site/templates.scala @@ -137,7 +137,7 @@ case class TemplateFile( val includePath = ssctx.root.toPath.resolve("_includes") IncludeTag.setDocsFolder(includePath.toString) - LanguagePickerTag.setConfigFolder(ssctx.root.toPath.resolve().toString) + LanguagePickerTag.setConfigFolder(ssctx.root.toPath.toString) From 942b6432e7ec7da5c794b92c6c4e18ec71e25f8f Mon Sep 17 00:00:00 2001 From: sai-prasad-1 Date: Mon, 19 Aug 2024 19:43:28 +0530 Subject: [PATCH 15/25] Fix : DataLoader to use java data structures --- .scalafmt.conf | 2 + .../dotty_res/scripts/staticsite/main.js | 61 ++++++----- .../scripts/staticsite/tabs-block.js | 27 ----- .../site/customTags/LanguagePicker.scala | 100 ++++++++++++------ .../scaladoc/site/helpers/DataLoader.scala | 41 +++---- 5 files changed, 123 insertions(+), 108 deletions(-) create mode 100644 .scalafmt.conf diff --git a/.scalafmt.conf b/.scalafmt.conf new file mode 100644 index 000000000000..ee7753a01aa3 --- /dev/null +++ b/.scalafmt.conf @@ -0,0 +1,2 @@ +version = "3.7.15" +runner.dialect = scala213 \ No newline at end of file diff --git a/scaladoc/resources/dotty_res/scripts/staticsite/main.js b/scaladoc/resources/dotty_res/scripts/staticsite/main.js index d9af20f20e2d..6ea44a8efd27 100644 --- a/scaladoc/resources/dotty_res/scripts/staticsite/main.js +++ b/scaladoc/resources/dotty_res/scripts/staticsite/main.js @@ -1,22 +1,3 @@ -// Get all the alt-details-control elements -const altDetailsControls = document.querySelectorAll('.alt-details-control'); - -// Loop through each control element -altDetailsControls.forEach(control => { - // Add event listener for 'change' event - control.addEventListener('change', function () { - // Get the corresponding alt-details-detail element - const detailElement = this.nextElementSibling.nextElementSibling; - - // Toggle the display of the detail element based on checkbox state - if (this.checked) { - detailElement.style.display = 'block'; - } else { - detailElement.style.display = 'none'; - } - }); -}); - document.addEventListener("DOMContentLoaded", function () { const tabContainers = document.querySelectorAll('.tabs'); tabContainers.forEach(container => { @@ -46,27 +27,49 @@ document.addEventListener("DOMContentLoaded", function () { }); + function handleLanguageChange(selectElement) { + console.log("This Function Works") var selectedLanguage = selectElement.value; var currentUrl = window.location.href; - - // Remove any existing locale from the URL var urlParts = currentUrl.split('/'); var baseUrl = urlParts.slice(0, 3).join('/'); var pathParts = urlParts.slice(3); - // Check if the selected language is not the default + // Regex to match a language code at the start of the path + var languagePattern = /^[a-z]{2}(-[a-z]{2})?/; + var currentLangCode = pathParts.length > 0 ? pathParts[0].match(languagePattern) : null; + + if (selectedLanguage) { - // Check if there's already a locale in the URL path - var updatedPath = []; - if (pathParts.length > 0 && !pathParts[0].match(/^(en|zh-cn|zh-tw|ja)$/)) { - updatedPath = [selectedLanguage].concat(pathParts); + var updatedPath; + + if (selectedLanguage == 'en') { + // If 'en' is selected, remove the language code if it exists + if (currentLangCode) { + updatedPath = pathParts.length > 1 ? pathParts.slice(1) : []; + } else { + updatedPath = pathParts; + } + } else { + // If any other language is selected + if (currentLangCode) { + // Replace the existing language code with the new one + updatedPath = [selectedLanguage].concat(pathParts.slice(1)); + } else { + // Add the new language code at the start + updatedPath = [selectedLanguage].concat(pathParts); + } + } + + // Handle edge case where updatedPath might be empty + if (updatedPath.length === 0) { + window.location.href = baseUrl; } else { - updatedPath = [selectedLanguage].concat(pathParts); + window.location.href = baseUrl + '/' + updatedPath.join('/'); } - window.location.href = baseUrl + '/' + updatedPath.join('/'); } else { - // Redirect to the base URL (default language) + // If no language is selected, keep the path unchanged window.location.href = baseUrl + '/' + pathParts.join('/'); } } diff --git a/scaladoc/resources/dotty_res/scripts/staticsite/tabs-block.js b/scaladoc/resources/dotty_res/scripts/staticsite/tabs-block.js index c6261f5ab0d3..e69de29bb2d1 100644 --- a/scaladoc/resources/dotty_res/scripts/staticsite/tabs-block.js +++ b/scaladoc/resources/dotty_res/scripts/staticsite/tabs-block.js @@ -1,27 +0,0 @@ -// JavaScript for Tab Functionality -document.addEventListener("DOMContentLoaded", function() { - const tabLinks = document.querySelectorAll(".tab-link"); - const tabContents = document.querySelectorAll(".tab-content"); - - tabLinks.forEach(link => { - link.addEventListener("click", function() { - const targetId = this.getAttribute("data-tab"); - - tabLinks.forEach(l => l.classList.remove("active")); - this.classList.add("active"); - - tabContents.forEach(content => { - if (content.id === targetId) { - content.style.display = "block"; - } else { - content.style.display = "none"; - } - }); - }); - }); - - // Activate the first tab by default - if (tabLinks.length > 0) { - tabLinks[0].click(); - } -}); diff --git a/scaladoc/src/dotty/tools/scaladoc/site/customTags/LanguagePicker.scala b/scaladoc/src/dotty/tools/scaladoc/site/customTags/LanguagePicker.scala index 73c0f95d297c..67d3873bf9c8 100644 --- a/scaladoc/src/dotty/tools/scaladoc/site/customTags/LanguagePicker.scala +++ b/scaladoc/src/dotty/tools/scaladoc/site/customTags/LanguagePicker.scala @@ -1,4 +1,3 @@ - package dotty.tools.scaladoc.site.tags import java.nio.file.{Files, Paths, Path} @@ -14,74 +13,109 @@ import scala.language.dynamics import dotty.tools.scaladoc.site.helpers.{ConfigLoader} import org.yaml.snakeyaml.Yaml - class LanguagePickerTag extends Tag("language_picker") { - override def render(context: TemplateContext, args: Array[? <: LNode]): Object = { - // Check if the languages argument is provided + override def render( + context: TemplateContext, + args: Array[? <: LNode] + ): Object = { if (args.isEmpty || args(0) == null) { - throw new IllegalArgumentException("The 'languages' argument is required and must be provided in the {% language_picker %} tag.") + throw new IllegalArgumentException( + "The 'languages' argument is required and must be provided in the {% language_picker %} tag." + ) } + // Raw argument value + val rawArgument = + args(0).render(context).toString.trim.stripPrefix("languages=") + println(s"Raw languages argument: $rawArgument") + + // Determine if the argument is a variable reference or a literal string + val languagesArgString = if (rawArgument.startsWith("page.")) { + // Extract the variable name and fetch its value from the context + val variableName = rawArgument.stripPrefix("page.") + val siteData = context + .getVariables() + .get("page") + .asInstanceOf[java.util.Map[String, Any]] + val variableValue = siteData.get(variableName) match { + case value: String => value + case _ => + println(s"Variable '$variableName' not found or not a string") + "" + } + println(s"Resolved variable value: $variableValue") + variableValue + } else { + // Treat the argument as a literal string + rawArgument + } + + println(s"Language argument string: $languagesArgString") + // Extract language codes from the string + val languagesArg = extractLanguageCodes(languagesArgString) + println(s"Parsed languages: ${languagesArg.mkString(", ")}") + // Load the config using ConfigLoader val configLoader = new ConfigLoader() val basePath = LanguagePickerTag.getConfigFolder val config = configLoader.loadConfig(basePath) - // Extract the languages argument as a string - val languagesArgString = args(0).render(context).toString.trim - - // Ensure that the argument starts with "languages=" and parse it correctly - if (!languagesArgString.startsWith("languages=")) { - throw new IllegalArgumentException("The 'languages' argument is malformed. It should start with 'languages=[' and end with ']'") - } - - // Parse the string into a list of languages - val languagesArg = languagesArgString - .stripPrefix("languages=") - .stripPrefix("[") - .stripSuffix("]") - .split(",") - .map(_.trim.stripPrefix("'").stripSuffix("'")) - .toList - - // Debugging output - println(s"Parsed languages: ${languagesArg.mkString(", ")}") - - // Accessing languages from the config - val languagesOpt = config.get[java.util.List[java.util.Map[String, String]]]("languages") + // Access languages from the config + val languagesOpt = + config.get[java.util.List[java.util.Map[String, String]]]("languages") val languagesFromConfig = languagesOpt match { case Some(list) => list.asScala.toList.map(_.asScala.toMap) - case None => throw new IllegalArgumentException("No languages found in configuration") + case None => + throw new IllegalArgumentException( + "No languages found in configuration" + ) } // Extract the list of codes from the config val configLanguageCodes = languagesFromConfig.map(_("code")) + println(s"Languages from config: ${configLanguageCodes.mkString(", ")}") // Validate that all languages in languagesArg exist in the config val missingLanguages = languagesArg.filterNot(configLanguageCodes.contains) if (missingLanguages.nonEmpty) { - throw new IllegalArgumentException(s"The following languages are not defined in the configuration: ${missingLanguages.mkString(", ")}") + throw new IllegalArgumentException( + s"The following languages are not defined in the configuration: ${missingLanguages.mkString(", ")}" + ) } // Filter the languages from the config based on the languagesArg - val languages = languagesFromConfig.filter(language => languagesArg.contains(language("code"))) + val languages = languagesFromConfig.filter(language => + languagesArg.contains(language("code")) + ) // Create the dropdown HTML val dropdown = new StringBuilder - dropdown.append("" + ) for (language <- languages) { val code = language("code") val name = language("name") dropdown.append(s"") } - dropdown.append("") + dropdown.toString() } -} + private def extractLanguageCodes(languagesArg: String): List[String] = { + // Regular expression to match both two-letter codes and hyphenated parts + val pattern = """[a-z]{2}(-[a-z]{2})?""".r + + // Find all matches in the string + val matches = pattern.findAllIn(languagesArg).toList + println(s"Extracted language codes: ${matches.mkString(", ")}") + // Ensure that each part is valid and non-empty + matches.map(_.trim).filter(_.nonEmpty) + } +} object LanguagePickerTag { @volatile private var configFolder: String = "_docs" diff --git a/scaladoc/src/dotty/tools/scaladoc/site/helpers/DataLoader.scala b/scaladoc/src/dotty/tools/scaladoc/site/helpers/DataLoader.scala index abcf5f6ea7ee..366dd595259d 100644 --- a/scaladoc/src/dotty/tools/scaladoc/site/helpers/DataLoader.scala +++ b/scaladoc/src/dotty/tools/scaladoc/site/helpers/DataLoader.scala @@ -4,12 +4,12 @@ import java.io.File import org.yaml.snakeyaml.Yaml import scala.io.Source import scala.jdk.CollectionConverters._ -import scala.collection.mutable +import java.util.{LinkedHashMap, List, Map} class DataLoader { - def loadDataDirectory(directoryPath: String): mutable.LinkedHashMap[String, Any] = { - val dataMap = mutable.LinkedHashMap[String, Any]() + def loadDataDirectory(directoryPath: String): LinkedHashMap[String, Object] = { + val dataMap = new LinkedHashMap[String, Object]() try { loadDirectory(new File(directoryPath), dataMap) } catch { @@ -19,7 +19,7 @@ class DataLoader { dataMap } - private def loadDirectory(directory: File, dataMap: mutable.LinkedHashMap[String, Any]): Unit = { + private def loadDirectory(directory: File, dataMap: LinkedHashMap[String, Object]): Unit = { if (directory.exists() && directory.isDirectory) { for (file <- directory.listFiles()) { if (file.isDirectory) { @@ -33,28 +33,31 @@ class DataLoader { } } - private def readYamlFile(file: File, dataMap: mutable.LinkedHashMap[String, Any]): Unit = { + private def readYamlFile(file: File, dataMap: LinkedHashMap[String, Object]): Unit = { try { val yaml = new Yaml() - val yamlContents: Any = yaml.load(Source.fromFile(file).mkString) + val yamlContents: Object = convertToJava(yaml.load(Source.fromFile(file).mkString)) val fileName = file.getName.stripSuffix(".yaml").stripSuffix(".yml") - val parsedContents = yamlContents match { - case map: java.util.Map[_, _] => - println(s"Parsed ${file.getName} as Map") - map.asScala.toMap - case list: java.util.List[_] => - println(s"Parsed ${file.getName} as List") - list.asScala.toList - case other => - println(s"Parsed ${file.getName} as ${other.getClass}") - other - } - - dataMap.put(fileName, parsedContents) + dataMap.put(fileName, yamlContents) } catch { case e: Exception => println(s"Error reading YAML file '${file.getName}': ${e.getMessage}") } } + + private def convertToJava(value: Any): Object = { + value match { + case map: java.util.Map[_, _] => + val javaMap = new LinkedHashMap[String, Object]() + map.asScala.foreach { case (k, v) => javaMap.put(k.toString, convertToJava(v)) } + javaMap.asInstanceOf[Object] + case list: java.util.List[_] => + val javaList = new java.util.ArrayList[Object]() + list.asScala.foreach(v => javaList.add(convertToJava(v))) + javaList.asInstanceOf[Object] + case other => + other.asInstanceOf[Object] + } + } } From ecf926579b42978d7e94dec6de3be7dbbc50c077 Mon Sep 17 00:00:00 2001 From: sai-prasad-1 Date: Mon, 19 Aug 2024 20:52:19 +0530 Subject: [PATCH 16/25] feat: Handle empty `languages` argument in LanguagePickerTag gracefully - Added a check in the `LanguagePickerTag` class to handle scenarios where the `languages` argument is provided but is empty. - Implemented an early return in the `render` method to prevent rendering of the language dropdown HTML when the `languages` argument is empty. - Modified the logic to check the processed `languagesArgString`, and if it is empty after trimming and processing, the method returns an empty string, avoiding the generation of an empty `") + dropdown.append( + " " + ) dropdown.toString() } @@ -123,11 +157,11 @@ class LanguagePickerTag extends Tag("language_picker") { } object LanguagePickerTag { - @volatile private var configFolder: String = "_docs" + @volatile private var config: Config = null - def setConfigFolder(path: String): Unit = { - configFolder = path + def setConfigValue(configuration: Config): Unit = { + config = configuration } - def getConfigFolder: String = configFolder + def getConfigValue: Config = config } diff --git a/scaladoc/src/dotty/tools/scaladoc/site/helpers/ConfigLoader.scala b/scaladoc/src/dotty/tools/scaladoc/site/helpers/ConfigLoader.scala index c8f7594cbdc5..30a2f2282767 100644 --- a/scaladoc/src/dotty/tools/scaladoc/site/helpers/ConfigLoader.scala +++ b/scaladoc/src/dotty/tools/scaladoc/site/helpers/ConfigLoader.scala @@ -1,6 +1,5 @@ package dotty.tools.scaladoc.site.helpers - import java.io.{File, InputStream} import java.nio.file.{Files, Paths, Path} import scala.jdk.CollectionConverters._ @@ -13,8 +12,8 @@ import liqp.nodes.LNode import liqp.tags.Tag import org.yaml.snakeyaml.Yaml - -class Config(private val data: mutable.LinkedHashMap[String, Any]) extends Dynamic { +class Config(private val data: mutable.LinkedHashMap[String, Any]) + extends Dynamic { def selectDynamic(field: String): Any = { data.get(field) match { case Some(value: mutable.LinkedHashMap[_, _]) => @@ -22,7 +21,12 @@ class Config(private val data: mutable.LinkedHashMap[String, Any]) extends Dynam case Some(value: java.util.ArrayList[_]) => value.asScala.toSeq.map { case map: java.util.Map[_, _] => - new Config(map.asInstanceOf[java.util.Map[String, Any]].asScala.to(mutable.LinkedHashMap)) + new Config( + map + .asInstanceOf[java.util.Map[String, Any]] + .asScala + .to(mutable.LinkedHashMap) + ) case other => other } case Some(value) => value @@ -36,7 +40,6 @@ class Config(private val data: mutable.LinkedHashMap[String, Any]) extends Dynam } - class ConfigLoader { def loadConfig(basePath: String): Config = { val configMap = mutable.LinkedHashMap[String, Any]() @@ -58,31 +61,28 @@ class ConfigLoader { configMap ++= data.asScala.toMap // Check for language folders - val languagesOpt = configMap.get("languages").collect { - case list: java.util.List[_] => - list.asScala.toList.collect { - case map: java.util.Map[_, _] => - map.asScala.toMap.collect { - case (key: String, value: String) => key -> value - } + val languagesOpt = + configMap.get("languages").collect { case list: java.util.List[_] => + list.asScala.toList.collect { case map: java.util.Map[_, _] => + map.asScala.toMap.collect { case (key: String, value: String) => + key -> value + } } - } - - - // languagesOpt match { - // case Some(languages) => - // for (language <- languages) { - // // Cast the key to String to avoid type mismatch errors - // val languageCode = language("code".asInstanceOf[language.K]).asInstanceOf[String] - // val languageFolderPath = Paths.get(baseDir, languageCode) - // if (!Files.exists(languageFolderPath) || !Files.isDirectory(languageFolderPath)) { - // throw new IllegalArgumentException(s"Language folder for '$languageCode' does not exist at path: $languageFolderPath") - // } - // } - // case None => - // println(s"Warning: No languages found in configuration.") - // } - + } + + // languagesOpt match { + // case Some(languages) => + // for (language <- languages) { + // // Cast the key to String to avoid type mismatch errors + // val languageCode = language("code".asInstanceOf[language.K]).asInstanceOf[String] + // val languageFolderPath = Paths.get(baseDir, languageCode) + // if (!Files.exists(languageFolderPath) || !Files.isDirectory(languageFolderPath)) { + // throw new IllegalArgumentException(s"Language folder for '$languageCode' does not exist at path: $languageFolderPath") + // } + // } + // case None => + // println(s"Warning: No languages found in configuration.") + // } case None => println(s"Warning: No config file found in path: $baseDir") diff --git a/scaladoc/src/dotty/tools/scaladoc/site/templates.scala b/scaladoc/src/dotty/tools/scaladoc/site/templates.scala index 6d04345902a4..7d935a7a3cf6 100644 --- a/scaladoc/src/dotty/tools/scaladoc/site/templates.scala +++ b/scaladoc/src/dotty/tools/scaladoc/site/templates.scala @@ -3,6 +3,7 @@ package site import java.io.File import java.nio.file.{Files, Paths} +import java.util.{HashMap => JavaHashMap} import com.vladsch.flexmark.ext.autolink.AutolinkExtension import com.vladsch.flexmark.ext.emoji.EmojiExtension import com.vladsch.flexmark.ext.gfm.strikethrough.StrikethroughExtension @@ -21,6 +22,8 @@ import liqp.tags.Tag import liqp.nodes.LNode import dotty.tools.scaladoc.site.blocks.{AltDetails,TabsBlock,TabBlock} import dotty.tools.scaladoc.site.tags.{IncludeTag,LanguagePickerTag} +import dotty.tools.scaladoc.site.helpers.ConfigLoader + @@ -130,17 +133,23 @@ case class TemplateFile( val siteData = new JHashMap[String, Any]() siteData.put("data",dataMap) - mutableProperties.put("site",siteData) // assign the the path for Include Tag val includePath = ssctx.root.toPath.resolve("_includes") IncludeTag.setDocsFolder(includePath.toString) - LanguagePickerTag.setConfigFolder(ssctx.root.toPath.toString) + val configLoader = new ConfigLoader() + val configMap = configLoader.loadConfig(ssctx.root.toPath.toString) + + + LanguagePickerTag.setConfigValue(configMap) + siteData.put("config",configMap) + + mutableProperties.put("site",siteData) // val parseSettings = ParseSettings.Builder().withFlavor(Flavor.JEKYLL).build().withBlock(AltDetails()) val liqpParser = TemplateParser.Builder() @@ -152,6 +161,7 @@ case class TemplateFile( .withTag(LanguagePickerTag()) .build() + val rendered = liqpParser.parse(this.rawCode).render(mutableProperties) // We want to render markdown only if next template is html From 2d1818f5230d3cc339f23a34491808c3e99e8bd0 Mon Sep 17 00:00:00 2001 From: sai-prasad-1 Date: Sun, 25 Aug 2024 21:08:35 +0530 Subject: [PATCH 18/25] Fix : Langauge Picker - Interative Redirect --- docs/_docs/reference/scaladoc/index.md | 241 ++++++++++++++++++ .../dotty/tools/scaladoc/site/templates.scala | 4 +- 2 files changed, 243 insertions(+), 2 deletions(-) create mode 100644 docs/_docs/reference/scaladoc/index.md diff --git a/docs/_docs/reference/scaladoc/index.md b/docs/_docs/reference/scaladoc/index.md new file mode 100644 index 000000000000..d631c8c00a7c --- /dev/null +++ b/docs/_docs/reference/scaladoc/index.md @@ -0,0 +1,241 @@ +--- +layout: doc-page +title: Scaladoc +partof: scaladoc +overview-name: Scaladoc +num: 1 + +--- + + +# Custom Template Tags Documentation + + +## 1. Language Picker Tag + +The language_picker tag is a custom tag used to render a language selection dropdown on a page. This tag allows users to switch between different language versions of the content. The available languages are determined based on the configuration and the frontmatter of the page. + +### Prequisites: + - #### Configuration in config.yaml: + The available languages must be defined in the `config.yaml` file under the languages section. Each language should have a corresponding code and name. + Example: + ```yaml + languages: + - code: en + name: English + - code: ja + name: Japanese + ``` + - #### Language Folders in `_docs`: + For each language code specified in config.yaml, there should be a corresponding folder in the _docs directory. These folders contain the translated content for each language. + Example: + ``` + _docs/ + ├── en/ + ├── ja/ + + ``` + - #### Frontmatter in the page: + Each page should have a `languages` key in its frontmatter that lists the available languages for that page. This ensures that the language picker dropdown is rendered with the correct options. + + Example: + ```yaml + --- + title: Getting Started + languages: ['en', 'ja'] + --- + ``` + +### Usage: + +```html +
+ { % if page.languages % } + { % language_picker languages=page.languages % } + { % endif %} +
+``` + +### Considerations + +- Empty Language List: If the languages list in the frontmatter is empty, the language_picker tag will not render anything. +- Default Language: If the selected language is en, the language code will be removed from the URL, treating it as the default language. +- Folder Structure: Ensure that the corresponding language folders exist in the _docs directory; otherwise, the language switch will lead to broken links. + + + +## 1. Include Tag + +### Purpose: +The `{ % include % }` tag is used to include content from other files or templates within your page. This helps in reusing common elements across multiple pages, such as headers, footers, or any other repeated content. + +### Usage: +```html +{ % include 'filename' variable1='value1' variable2='value2' % } +``` + + +### Example: +```html +{ % include 'header.html' title='Welcome to My Website' % } +``` + +### How it Works: + - File Inclusion: The tag includes the specified file from a predefined folder. + - Passing Variables: You can pass variables when including a file, which can be accessed within the included file using the syntax {{ include.variable1 }}. + - Accessing Variables: Inside the included file, the passed variables can be accessed like regular variables, allowing dynamic content based on the context of inclusion. + + + +## 2. Tabs Block + +### Purpose: +The { % tabs % } block is used to group content into tabs, allowing users to switch between different content sections without reloading the page. This is useful for organizing content that has multiple views, like installation instructions for different platforms. + +### Usage: +```html +{ % tabs unique-tabs-id class='additional-class' % } + { % tab 'Tab 1' for='unique-tabs-id' % } + + { % endtab % } + + { % tab 'Tab 2' for='unique-tabs-id' % } + + { % endtab % } + +{ % endtabs % } +``` + +### Example: + +```html +{ % tabs install-instructions % } + { % tab 'Windows' for='install-instructions' % } + + { % endtab % } + + { % tab 'macOS' for='install-instructions' % } + + { % endtab % } + +{ % endtabs % } +``` + +How it Works: +- Tabs Block: The { % tabs % } tag creates a container for multiple tabs. +- Tab Content: Each { % tab % } tag defines the content of a single tab. The for attribute must match the id specified in the { % tabs % } block to correctly link the tab content. +- Default Tab: You can specify a default tab by using the defaultTab keyword in the { % tab % } tag. +- Styling: You can apply additional CSS classes to the tabs by specifying the class attribute + + +## 2. AltDetails Block + +### Purpose: +The { % altDetails % } tag is used to create switchable sections, often referred to as tabs. This is useful for including optional or advanced information that doesn’t need to be visible by default. + +### Usage: + +```html +{ % altDetails 'unique-id' 'Title of the Section' % } + +{ % endaltDetails %} + +``` + +### How it Works: + +- Collapsible Section: The { % altDetails % } tag creates a section that can be expanded or collapsed by the user. +- Title and ID: The first argument is a unique identifier for the section, and the second argument is the title displayed when the section is collapsed. +- Content: Any content placed within the { % altDetails % } and { % endaltDetails % } tags is hidden by default and can be revealed by clicking on the title. + + +# `_data` Folder + +The _data folder in your project is used to store YAML, JSON, or CSV files that contain structured data. These files can then be accessed throughout your site using the site.data variable. This is particularly useful for storing site-wide information, such as navigation menus, reusable content blocks, or any other structured data that needs to be referenced across multiple pages. + +### Structure and Access + - Folder Location: The _data folder should be located in the root directory of your project. + + - File Formats: The _data folder supports .yml, .yaml, .json, and .csv file formats. Each file represents a collection of data that can be accessed using the site.data variable. + + - Accessing Data: + - The data within a YAML file can be accessed using `site.data...` + - Replace `` with the name of the file (without the extension), and `` with the specific key within the file. + +### Example + +Suppose you have a YAML file `_data/navigation.yml` with the following content: + +```yaml +main_menu: + - name: Home + url: / + - name: About + url: /about/ + - name: Contact + url: /contact/ +``` + +You can access the data in this file using the following syntax: + +```html +
    + { % for item in site.data.navigation.main_menu % } +
  • { { item.name } }
  • + { % endfor % } +
+``` + +### Considerations + - File Naming: Make sure the file names are unique to avoid conflicts when accessing data. + - Data Structure: Keep the data structure consistent across different files to avoid confusion when accessing data in your templates. + + + + + + + +# Configuration File + +The `_config.yaml` file is a central configuration file located in the root directory of your project. It is used to define site-wide settings and variables that can be accessed throughout your site using the `site` variable. + +### Structure and Access + +- File Location: The _config.yaml file should be placed in the root directory of your project. + +- YAML Structure: The file uses YAML syntax to define configuration options. You can define any key-value pairs in this file, and they will be accessible via the site.config.`` variable in your templates. + +- Accessing Configuration: + - Any configuration setting defined in _config.yaml can be accessed using site.``. + - For example, if you define a key title: My Website, you can access it using site.title. + +### Example +If your _config.yaml includes: +```yaml +site_name: My Awesome Site +description: A description of my awesome site. +``` +You can access these values in your templates: +```html +{ { site.config.site_name } } +``` + + + + diff --git a/scaladoc/src/dotty/tools/scaladoc/site/templates.scala b/scaladoc/src/dotty/tools/scaladoc/site/templates.scala index 7d935a7a3cf6..fd35e592ae4e 100644 --- a/scaladoc/src/dotty/tools/scaladoc/site/templates.scala +++ b/scaladoc/src/dotty/tools/scaladoc/site/templates.scala @@ -22,7 +22,7 @@ import liqp.tags.Tag import liqp.nodes.LNode import dotty.tools.scaladoc.site.blocks.{AltDetails,TabsBlock,TabBlock} import dotty.tools.scaladoc.site.tags.{IncludeTag,LanguagePickerTag} -import dotty.tools.scaladoc.site.helpers.ConfigLoader +import dotty.tools.scaladoc.site.helpers.{ConfigLoader=>SiteConfigLoader} @@ -139,7 +139,7 @@ case class TemplateFile( val includePath = ssctx.root.toPath.resolve("_includes") IncludeTag.setDocsFolder(includePath.toString) - val configLoader = new ConfigLoader() + val configLoader = new SiteConfigLoader() val configMap = configLoader.loadConfig(ssctx.root.toPath.toString) From 9b15ebd493f94391ac0e2200e7a38006d3a37d31 Mon Sep 17 00:00:00 2001 From: sai-prasad-1 Date: Wed, 28 Aug 2024 16:44:32 +0530 Subject: [PATCH 19/25] feat(config): add method to convert configuration data to Java Map --- .../tools/scaladoc/site/helpers/ConfigLoader.scala | 10 ++++++++++ scaladoc/src/dotty/tools/scaladoc/site/templates.scala | 5 ++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/scaladoc/src/dotty/tools/scaladoc/site/helpers/ConfigLoader.scala b/scaladoc/src/dotty/tools/scaladoc/site/helpers/ConfigLoader.scala index 30a2f2282767..f10918cccaf3 100644 --- a/scaladoc/src/dotty/tools/scaladoc/site/helpers/ConfigLoader.scala +++ b/scaladoc/src/dotty/tools/scaladoc/site/helpers/ConfigLoader.scala @@ -14,6 +14,7 @@ import org.yaml.snakeyaml.Yaml class Config(private val data: mutable.LinkedHashMap[String, Any]) extends Dynamic { + def selectDynamic(field: String): Any = { data.get(field) match { case Some(value: mutable.LinkedHashMap[_, _]) => @@ -38,6 +39,15 @@ class Config(private val data: mutable.LinkedHashMap[String, Any]) data.get(key).map(_.asInstanceOf[T]) } + // method to convert the internal map to a Java map + def convertToJava: java.util.Map[String, Any] = { + data.map { + case (key, value: Config) => key -> value.convertToJava + case (key, value: mutable.LinkedHashMap[_, _]) => + key -> value.asInstanceOf[mutable.LinkedHashMap[String, Any]].asJava + case (key, value) => key -> value + }.asJava + } } class ConfigLoader { diff --git a/scaladoc/src/dotty/tools/scaladoc/site/templates.scala b/scaladoc/src/dotty/tools/scaladoc/site/templates.scala index fd35e592ae4e..44c88885b1bf 100644 --- a/scaladoc/src/dotty/tools/scaladoc/site/templates.scala +++ b/scaladoc/src/dotty/tools/scaladoc/site/templates.scala @@ -20,6 +20,7 @@ import liqp.parser.Flavor import liqp.TemplateContext import liqp.tags.Tag import liqp.nodes.LNode +import scala.collection.mutable import dotty.tools.scaladoc.site.blocks.{AltDetails,TabsBlock,TabBlock} import dotty.tools.scaladoc.site.tags.{IncludeTag,LanguagePickerTag} import dotty.tools.scaladoc.site.helpers.{ConfigLoader=>SiteConfigLoader} @@ -141,13 +142,15 @@ case class TemplateFile( val configLoader = new SiteConfigLoader() val configMap = configLoader.loadConfig(ssctx.root.toPath.toString) + val siteConfig: java.util.Map[String, Any] = configMap.convertToJava + LanguagePickerTag.setConfigValue(configMap) - siteData.put("config",configMap) + siteData.put("config",siteConfig) mutableProperties.put("site",siteData) From 1fd197def727ca4f536728cbb14fe5a633b56a93 Mon Sep 17 00:00:00 2001 From: sai-prasad-1 Date: Wed, 28 Aug 2024 16:47:39 +0530 Subject: [PATCH 20/25] Clean up Print Statements --- .../site/customTags/LanguagePicker.scala | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/scaladoc/src/dotty/tools/scaladoc/site/customTags/LanguagePicker.scala b/scaladoc/src/dotty/tools/scaladoc/site/customTags/LanguagePicker.scala index f64368b79036..f3496d503d5b 100644 --- a/scaladoc/src/dotty/tools/scaladoc/site/customTags/LanguagePicker.scala +++ b/scaladoc/src/dotty/tools/scaladoc/site/customTags/LanguagePicker.scala @@ -23,7 +23,6 @@ class LanguagePickerTag extends Tag("language_picker") { // Raw argument value val rawArgument = args(0).render(context).toString.trim.stripPrefix("languages=") - println(s"Raw languages argument: $rawArgument") // Determine if the argument is a variable reference or a literal string val (languagesArg, valueType) = if (rawArgument.startsWith("page.")) { @@ -33,22 +32,14 @@ class LanguagePickerTag extends Tag("language_picker") { .getVariables() .get("page") .asInstanceOf[java.util.Map[String, Any]] - println(siteData) // Handle both String and List cases siteData.get(variableName) match { case value: String => - println(s"Resolved variable value (string): $value") (List(value), "String") case value: java.util.List[_] => - println( - s"Resolved variable value (list): ${value.asScala.mkString(", ")}" - ) (value.asScala.toList.collect { case s: String => s }, "List") case value => - println( - s"Variable '$variableName' found but not a valid string/list. Type: ${if (value != null) value.getClass.getSimpleName else "null"}" - ) ( List.empty[String], if (value != null) value.getClass.getSimpleName else "null" @@ -59,9 +50,6 @@ class LanguagePickerTag extends Tag("language_picker") { (rawArgument.split(",").map(_.trim).toList, "Literal String") } - // Log the type of the resolved argument - println(s"Resolved argument type: $valueType") - // Ensure "en" is the first element in the list val languagesWithEnFirst = { if (languagesArg.contains("en")) { @@ -72,9 +60,6 @@ class LanguagePickerTag extends Tag("language_picker") { "en" :: languagesArg } } - println( - s"Languages after ensuring 'en' is first: ${languagesWithEnFirst.mkString(", ")}" - ) // Early return if the languages argument is empty if (languagesWithEnFirst.isEmpty) { @@ -82,11 +67,9 @@ class LanguagePickerTag extends Tag("language_picker") { return "" } - println(s"Language argument string: ${languagesWithEnFirst.mkString(", ")}") // Extract language codes from the list val extractedLanguageCodes = languagesWithEnFirst.flatMap(extractLanguageCodes) - println(s"Parsed languages: ${extractedLanguageCodes.mkString(", ")}") // Load the config using ConfigLoader val config = LanguagePickerTag.getConfigValue @@ -104,7 +87,6 @@ class LanguagePickerTag extends Tag("language_picker") { // Extract the list of codes from the config val configLanguageCodes = languagesFromConfig.map(_("code")) - println(s"Languages from config: ${configLanguageCodes.mkString(", ")}") // Validate that all languages in extractedLanguageCodes exist in the config val missingLanguages = @@ -149,7 +131,6 @@ class LanguagePickerTag extends Tag("language_picker") { // Find all matches in the string val matches = pattern.findAllIn(languagesArg).toList - println(s"Extracted language codes: ${matches.mkString(", ")}") // Ensure that each part is valid and non-empty matches.map(_.trim).filter(_.nonEmpty) From 798b9aca8e3593eac0ff912f83023ffe59f32de6 Mon Sep 17 00:00:00 2001 From: sai-prasad-1 Date: Mon, 2 Sep 2024 14:31:40 +0530 Subject: [PATCH 21/25] Remove Unwanted Files --- .scalafmt.conf | 2 -- old_code.txt | 77 -------------------------------------------------- 2 files changed, 79 deletions(-) delete mode 100644 .scalafmt.conf delete mode 100644 old_code.txt diff --git a/.scalafmt.conf b/.scalafmt.conf deleted file mode 100644 index ee7753a01aa3..000000000000 --- a/.scalafmt.conf +++ /dev/null @@ -1,2 +0,0 @@ -version = "3.7.15" -runner.dialect = scala213 \ No newline at end of file diff --git a/old_code.txt b/old_code.txt deleted file mode 100644 index 245f22957a33..000000000000 --- a/old_code.txt +++ /dev/null @@ -1,77 +0,0 @@ - - - -package dotty.tools.scaladoc -package site - -import java.io.File -import java.nio.file.Files -import java.nio.file.Paths - -import org.jsoup.Jsoup -import scala.jdk.CollectionConverters._ - -case class LazyEntry(getKey: String, value: () => String) extends JMapEntry[String, Object]: - lazy val getValue: Object = value() - def setValue(x$0: Object): Object = ??? - -case class LoadedTemplate( - templateFile: TemplateFile, - children: List[LoadedTemplate], - file: File, - hidden: Boolean = false): - - private def brief(ctx: StaticSiteContext): String = - try - val code = Jsoup.parse(resolveToHtml(ctx).code) - Option(code.select("p").first()).fold("...")(_.outerHtml()) - catch - case e: Throwable => - val msg = s"[ERROR] Unable to process brief for ${templateFile.file}" - report.error(msg, templateFile.file, e)(using ctx.outerCtx) - "..." - - def lazyTemplateProperties(ctx: StaticSiteContext): JMap[String, Object] = new java.util.AbstractMap[String, Object](): - lazy val entrySet: JSet[JMapEntry[String, Object]] = - val site = templateFile.settings.getOrElse("page", Map.empty).asInstanceOf[Map[String, Object]] - site.asJava.entrySet() ++ JSet( - LazyEntry("url", () => "/" ++ ctx.pathFromRoot(LoadedTemplate.this).toString), - LazyEntry("title", () => templateFile.title.name), - LazyEntry("excerpt", () => brief(ctx)) - ) - - def resolveToHtml(ctx: StaticSiteContext): ResolvedPage = - val subpages = children.filterNot(_.hidden).map(_.lazyTemplateProperties(ctx)) - - def getMap(key: String): Map[String, Object] = - templateFile.settings.getOrElse(key, Map.empty).asInstanceOf[Map[String, Object]] - - // Handle nested structures and lists in frontmatter - def mergeMaps(map1: Map[String, Object], map2: Map[String, Object]): Map[String, Object] = - (map1.keySet ++ map2.keySet).map { key => - key -> ((map1.get(key), map2.get(key)) match { - case (Some(v1: Map[_, _]), Some(v2: Map[_, _])) => - mergeMaps(v1.asInstanceOf[Map[String, Object]], v2.asInstanceOf[Map[String, Object]]) - case (Some(v1: List[_]), Some(v2: List[_])) => - (v1 ++ v2).asInstanceOf[Object] - case (_, Some(v2)) => v2 - case (Some(v1), _) => v1 - case _ => throw new IllegalStateException("Unreachable code") - }) - }.toMap - - val sourceLinks = if !templateFile.file.exists() then Nil else - val actualPath = templateFile.file.toPath - ctx.sourceLinks.pathTo(actualPath).map("viewSource" -> _ ) ++ - ctx.sourceLinks.pathTo(actualPath, operation = "edit").map("editSource" -> _) - - val pageFrontmatter = getMap("page") + ("title" -> templateFile.title.name) - val mergedPageSettings = mergeMaps(pageFrontmatter, templateFile.settings.asInstanceOf[Map[String, Object]]) - - val siteSettings = getMap("site") + ("subpages" -> subpages) - val projectWidePropsMap = ctx.projectWideProperties.toMap.asInstanceOf[Map[String, Object]] // Convert to map - - val updatedSettings = mergeMaps(mergedPageSettings, projectWidePropsMap) + - ("site" -> siteSettings) + ("urls" -> sourceLinks.toMap.asInstanceOf[Map[String, Object]]) - - templateFile.resolveInner(RenderingContext(updatedSettings, ctx.layouts))(using ctx) From 6222ab2bbe694e22c8e575ac599526b5f7ccbf53 Mon Sep 17 00:00:00 2001 From: sai-prasad-1 Date: Mon, 2 Sep 2024 15:26:46 +0530 Subject: [PATCH 22/25] Remove Empty Files --- .../scripts/staticsite/tabs-block.js | 0 .../styles/staticsite/alt-details.css | 79 ------------------- .../styles/staticsite/tabs-block.css | 1 - .../tools/scaladoc/renderers/Resources.scala | 1 - 4 files changed, 81 deletions(-) delete mode 100644 scaladoc/resources/dotty_res/scripts/staticsite/tabs-block.js delete mode 100644 scaladoc/resources/dotty_res/styles/staticsite/alt-details.css delete mode 100644 scaladoc/resources/dotty_res/styles/staticsite/tabs-block.css diff --git a/scaladoc/resources/dotty_res/scripts/staticsite/tabs-block.js b/scaladoc/resources/dotty_res/scripts/staticsite/tabs-block.js deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/scaladoc/resources/dotty_res/styles/staticsite/alt-details.css b/scaladoc/resources/dotty_res/styles/staticsite/alt-details.css deleted file mode 100644 index 79a8b8f1f288..000000000000 --- a/scaladoc/resources/dotty_res/styles/staticsite/alt-details.css +++ /dev/null @@ -1,79 +0,0 @@ -.place-inline { - /* Assuming outer-container adds padding and a border */ - padding: 10px; /* Replace with actual values from outer-container */ - border: 1px solid #ccc; /* Replace with actual values from outer-container */ - - /* Adding vertical margin */ - margin: 20px 0; -} -/* ALT-DETAILS */ -.alt-details.help-info .alt-details-toggle { - background-color: #007bff; /* Replace $brand-primary with the actual color value */ - color: white; -} - -.alt-details.help-info .alt-details-toggle:hover { - background-color: #0056b3; /* Replace with darkened $brand-primary value */ -} - -.alt-details.help-info .alt-details-detail { - background: #fae6e6; -} - -.alt-details { - width: 100%; /* Assuming span-columns(12) means full width */ - - .alt-details-toggle { - width: 100%; /* Assuming span-columns(12) means full width */ - font-family: Arial, sans-serif; /* Replace $base-font-family with the actual font-family */ - line-height: normal; - text-align: center; - border: none; - background-color: #6c757d; /* Replace $brand-tertiary with the actual color value */ - padding: 5px 10px; - border-radius: 0.3rem; /* Replace $border-radius-large with the actual value */ - font-size: 1rem; /* Replace $font-size-medium with the actual value */ - cursor: pointer; - font-weight: 500; - color: #343a40; /* Replace $gray-dark with the actual color value */ - } - - .alt-details-toggle:hover { - background-color: #545b62; /* Replace with darkened $brand-tertiary value */ - } - - .alt-details-toggle:after { - content: "\f138"; /* */ - font-family: "Font Awesome 5 Free"; - font-weight: 900; - font-size: 15px; - float: right; - margin-top: 2px; - } - - .alt-details-control { - margin: 0; - } - - .alt-details-control + .alt-details-toggle + .alt-details-detail { - position: absolute; - top: -999em; - left: -999em; - } - - .alt-details-control:checked + .alt-details-toggle + .alt-details-detail { - position: static; - } - - .alt-details-control:checked + .alt-details-toggle:after { - content: "\f13a"; /* */ - } - - .alt-details-detail { - border-bottom: 1px solid #ddd; /* Replace $base-border-gray with the actual border value */ - border-left: 1px solid #ddd; /* Replace $base-border-gray with the actual border value */ - border-right: 1px solid #ddd; /* Replace $base-border-gray with the actual border value */ - padding-top: 15px; - margin-top: 15px; - } -} diff --git a/scaladoc/resources/dotty_res/styles/staticsite/tabs-block.css b/scaladoc/resources/dotty_res/styles/staticsite/tabs-block.css deleted file mode 100644 index f72419ee50f2..000000000000 --- a/scaladoc/resources/dotty_res/styles/staticsite/tabs-block.css +++ /dev/null @@ -1 +0,0 @@ -/* Style for Tabs */ diff --git a/scaladoc/src/dotty/tools/scaladoc/renderers/Resources.scala b/scaladoc/src/dotty/tools/scaladoc/renderers/Resources.scala index a192a4e15004..5219152b4073 100644 --- a/scaladoc/src/dotty/tools/scaladoc/renderers/Resources.scala +++ b/scaladoc/src/dotty/tools/scaladoc/renderers/Resources.scala @@ -106,7 +106,6 @@ trait Resources(using ctx: DocContext) extends Locations, Writer: "styles/fontawesome.css", "styles/staticsitestyles.css", "scripts/staticsite/alt-details.js", - "scripts/staticsite/tabs-block.js", "hljs/highlight.pack.js", "hljs/LICENSE", "scripts/hljs-scala3.js", From bb9b8c06f5bef9813682aea0c79fade7005ce9b4 Mon Sep 17 00:00:00 2001 From: sai-prasad-1 Date: Mon, 2 Sep 2024 15:30:23 +0530 Subject: [PATCH 23/25] move scaladoc documentation `internals` section --- docs/_docs/{reference => internals}/scaladoc/index.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/_docs/{reference => internals}/scaladoc/index.md (100%) diff --git a/docs/_docs/reference/scaladoc/index.md b/docs/_docs/internals/scaladoc/index.md similarity index 100% rename from docs/_docs/reference/scaladoc/index.md rename to docs/_docs/internals/scaladoc/index.md From f9c1c1fadda85fc9a728dc23d7476d5d5a4d0788 Mon Sep 17 00:00:00 2001 From: Sai Prasad Date: Mon, 2 Sep 2024 22:32:43 +0530 Subject: [PATCH 24/25] Update scaladoc/src/dotty/tools/scaladoc/site/blocks/Tabs.scala Co-authored-by: Jamie Thompson --- .../dotty/tools/scaladoc/site/blocks/Tabs.scala | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/scaladoc/src/dotty/tools/scaladoc/site/blocks/Tabs.scala b/scaladoc/src/dotty/tools/scaladoc/site/blocks/Tabs.scala index 38ddd2449c16..f960fe267873 100644 --- a/scaladoc/src/dotty/tools/scaladoc/site/blocks/Tabs.scala +++ b/scaladoc/src/dotty/tools/scaladoc/site/blocks/Tabs.scala @@ -66,14 +66,13 @@ class TabBlock extends Block("tab") { |""".stripMargin // Add the header and content to the context - val headers = context.get(s"tab-headers-$uniqueId") - if (headers.isInstanceOf[ListBuffer[?]]) { - val headersList = headers.asInstanceOf[ListBuffer[String]] - headersList += header - } else { - val newHeaders = ListBuffer[String]() - newHeaders += header - context.put(s"tab-headers-$uniqueId", newHeaders) + context.get(s"tab-headers-$uniqueId") match { + case headers: ListBuffer[String] @unchecked => + headers += header + case null => + val newHeaders = ListBuffer[String]() + newHeaders += header + context.put(s"tab-headers-$uniqueId", newHeaders) } val contents = context.get(s"tab-contents-$uniqueId") if (contents.isInstanceOf[ListBuffer[?]]) { From 0cab089969712b205fc0130a8fb977602735856d Mon Sep 17 00:00:00 2001 From: sai-prasad-1 Date: Wed, 18 Sep 2024 13:53:15 +0530 Subject: [PATCH 25/25] Enable experimental features conditionally based on flag --- project/ScaladocGeneration.scala | 2 +- .../src/dotty/tools/scaladoc/Scaladoc.scala | 2 +- .../tools/scaladoc/ScaladocSettings.scala | 4 +- .../tools/scaladoc/site/blocks/Tabs.scala | 5 +- .../dotty/tools/scaladoc/site/templates.scala | 79 ++++++++++--------- 5 files changed, 47 insertions(+), 45 deletions(-) diff --git a/project/ScaladocGeneration.scala b/project/ScaladocGeneration.scala index 6e364d6b4d60..c744481b2e82 100644 --- a/project/ScaladocGeneration.scala +++ b/project/ScaladocGeneration.scala @@ -143,7 +143,7 @@ object ScaladocGeneration { } case class ExperimentalFeatures(value: Boolean) extends Arg[Boolean] { - def key: String = "-experimental-features" + def key: String = "-use-experimental-features" } import _root_.scala.reflect._ diff --git a/scaladoc/src/dotty/tools/scaladoc/Scaladoc.scala b/scaladoc/src/dotty/tools/scaladoc/Scaladoc.scala index a6b815ed5035..c5862fcb728a 100644 --- a/scaladoc/src/dotty/tools/scaladoc/Scaladoc.scala +++ b/scaladoc/src/dotty/tools/scaladoc/Scaladoc.scala @@ -204,7 +204,7 @@ object Scaladoc: if deprecatedSkipPackages.get.nonEmpty then report.warning(deprecatedSkipPackages.description) - val experimentalFeatures = args.contains("-experimental-features") + val experimentalFeatures = args.contains("-use-experimental-features") val docArgs = Args( diff --git a/scaladoc/src/dotty/tools/scaladoc/ScaladocSettings.scala b/scaladoc/src/dotty/tools/scaladoc/ScaladocSettings.scala index 44fc0733ae5f..910c8482df7b 100644 --- a/scaladoc/src/dotty/tools/scaladoc/ScaladocSettings.scala +++ b/scaladoc/src/dotty/tools/scaladoc/ScaladocSettings.scala @@ -144,7 +144,7 @@ class ScaladocSettings extends SettingGroup with AllScalaSettings: val dynamicSideMenu: Setting[Boolean] = BooleanSetting(RootSetting, "dynamic-side-menu", "Generate side menu via JS instead of embedding it in every html file", false) val experimentalFeatures: Setting[Boolean] = - BooleanSetting(RootSetting, "experimental-features", "Activate Experimental Features for scaladoc", false) + BooleanSetting(RootSetting, "use-experimental-features", "Activate Experimental Features for scaladoc", false) def scaladocSpecificSettings: Set[Setting[?]] = - Set(sourceLinks, legacySourceLink, syntax, revision, externalDocumentationMappings, socialLinks, skipById, skipByRegex, deprecatedSkipPackages, docRootContent, snippetCompiler, generateInkuire, defaultTemplate, scastieConfiguration, quickLinks, dynamicSideMenu) + Set(sourceLinks, legacySourceLink, syntax, revision, externalDocumentationMappings, socialLinks, skipById, skipByRegex, deprecatedSkipPackages, docRootContent, snippetCompiler, generateInkuire, defaultTemplate, scastieConfiguration, quickLinks, dynamicSideMenu,experimentalFeatures) diff --git a/scaladoc/src/dotty/tools/scaladoc/site/blocks/Tabs.scala b/scaladoc/src/dotty/tools/scaladoc/site/blocks/Tabs.scala index f960fe267873..d0351e022368 100644 --- a/scaladoc/src/dotty/tools/scaladoc/site/blocks/Tabs.scala +++ b/scaladoc/src/dotty/tools/scaladoc/site/blocks/Tabs.scala @@ -41,13 +41,14 @@ class TabBlock extends Block("tab") { override def render(context: TemplateContext, nodes: LNode*): AnyRef = { val inputString = nodes.head.render(context).toString - val pattern = """(.*)for=(.*)""".r + val pattern = """(.*?)(?:for=(.*))?""".r val (tabName, forValue) = inputString match { - case pattern(tab, forPart) => (tab, forPart) + case pattern(tab, forPart) => (tab, Option(forPart).getOrElse("")) case _ => ("", "") } + val content = nodes.tail.map(_.render(context).toString).mkString // Retrieve the unique ID from the context diff --git a/scaladoc/src/dotty/tools/scaladoc/site/templates.scala b/scaladoc/src/dotty/tools/scaladoc/site/templates.scala index 44c88885b1bf..d9264d941a0a 100644 --- a/scaladoc/src/dotty/tools/scaladoc/site/templates.scala +++ b/scaladoc/src/dotty/tools/scaladoc/site/templates.scala @@ -124,48 +124,49 @@ case class TemplateFile( val mutableProperties = new JHashMap[String, Any]( ctx.properties.transform((_, v) => asJavaElement(v)).asJava ) + // Initialize liqpParser with a default value + var liqpParser: TemplateParser = TemplateParser.Builder().withFlavor(Flavor.JEKYLL).build() + + // Activate additional features based on the experimental features flag + if (ssctx.args.experimentalFeatures) { + // report.warning("This project has experimental features turned on!!") + + // Load the data from the YAML file in the _data folder + val dataPath = ssctx.root.toPath.resolve("_data") + val dataMap = DataLoader().loadDataDirectory(dataPath.toString) + val siteData = new JHashMap[String, Any]() + siteData.put("data", dataMap) + + // Assign the path for Include Tag + val includePath = ssctx.root.toPath.resolve("_includes") + IncludeTag.setDocsFolder(includePath.toString) + + // load the config data + val configLoader = new SiteConfigLoader() + val configMap = configLoader.loadConfig(ssctx.root.toPath.toString) + val siteConfig: java.util.Map[String, Any] = configMap.convertToJava + + // Set the configuration value for LanguagePickerTag + LanguagePickerTag.setConfigValue(configMap) + siteData.put("config", siteConfig) + + mutableProperties.put("site", siteData) + + // Rebuild liqpParser with all the tags and blocks + liqpParser = TemplateParser.Builder() + .withFlavor(Flavor.JEKYLL) + .withBlock(AltDetails()) + .withBlock(TabsBlock()) + .withBlock(TabBlock()) + .withTag(IncludeTag()) + .withTag(LanguagePickerTag()) + .build() + } - - val dataPath = ssctx.root.toPath.resolve("_data") - - // Load the data from yaml file in _data folder - val dataMap = DataLoader().loadDataDirectory(dataPath.toString) - - - val siteData = new JHashMap[String, Any]() - siteData.put("data",dataMap) - - - // assign the the path for Include Tag - val includePath = ssctx.root.toPath.resolve("_includes") - IncludeTag.setDocsFolder(includePath.toString) - - val configLoader = new SiteConfigLoader() - val configMap = configLoader.loadConfig(ssctx.root.toPath.toString) - val siteConfig: java.util.Map[String, Any] = configMap.convertToJava - - - - LanguagePickerTag.setConfigValue(configMap) - - - - siteData.put("config",siteConfig) - - mutableProperties.put("site",siteData) - -// val parseSettings = ParseSettings.Builder().withFlavor(Flavor.JEKYLL).build().withBlock(AltDetails()) - val liqpParser = TemplateParser.Builder() - .withFlavor(Flavor.JEKYLL) - .withBlock(AltDetails()) - .withBlock(TabsBlock()) - .withBlock(TabBlock()) - .withTag(IncludeTag()) - .withTag(LanguagePickerTag()) - .build() + // Parse and render the template + val rendered = liqpParser.parse(this.rawCode).render(mutableProperties) - val rendered = liqpParser.parse(this.rawCode).render(mutableProperties) // We want to render markdown only if next template is html val code = if (isHtml || layoutTemplate.exists(!_.isHtml)) rendered else