diff --git a/scaladoc/resources/dotty_res/styles/theme/components/button/icon-button.css b/scaladoc/resources/dotty_res/styles/theme/components/button/icon-button.css index a6450984131e..d0957691fb1e 100644 --- a/scaladoc/resources/dotty_res/styles/theme/components/button/icon-button.css +++ b/scaladoc/resources/dotty_res/styles/theme/components/button/icon-button.css @@ -533,6 +533,50 @@ content: url("../../../../images/icon-buttons/gitter/dark/selected.svg"); } +/* custom button */ + +.icon-button.custom-dark{ + display: none; +} + +.icon-button.custom::after { + content: ""; + background-image: var(--bgimage); + background-repeat: no-repeat; + background-position: center; + background-size: contain; + display: block; + max-width: 100%; + max-height: 100%; +} + +.theme-dark .icon-button.custom-dark{ + display: unset; +} + +.theme-dark .icon-button.custom-dark::after{ + content: ""; + background-image: var(--bgimage-dark); + background-repeat: no-repeat; + background-position: center; + background-size: contain; + display: block; + max-width: 100%; + max-height: 100%; +} + +.theme-dark .icon-button.custom{ + display: none; +} + +.icon-button.custom:hover{ + opacity: 0.8; +} + +.icon-button.custom-dark:hover{ + opacity: 0.8; +} + /* copy button */ .icon-button.copy-button::after { @@ -830,4 +874,4 @@ .theme-dark .documentableElement .ar.icon-button.expanded.selected::after { content: url("../../../../images/icon-buttons/arrow-down/dark/selected.svg"); -} \ No newline at end of file +} diff --git a/scaladoc/resources/dotty_res/styles/theme/layout/footer.css b/scaladoc/resources/dotty_res/styles/theme/layout/footer.css index 7c169af00591..9178e4a01acc 100644 --- a/scaladoc/resources/dotty_res/styles/theme/layout/footer.css +++ b/scaladoc/resources/dotty_res/styles/theme/layout/footer.css @@ -64,7 +64,7 @@ display: none; } - #footer.mobile-footer .text-mobile { + #footer.mobile-footer .text-mobile { display: flex; width: 100%; justify-content: center; @@ -78,5 +78,5 @@ #footer.mobile-footer > .text-mobile { display: flex; } - + } \ No newline at end of file diff --git a/scaladoc/src/dotty/tools/scaladoc/ScaladocSettings.scala b/scaladoc/src/dotty/tools/scaladoc/ScaladocSettings.scala index 5f2faf6df4d5..57c2b2910d6a 100644 --- a/scaladoc/src/dotty/tools/scaladoc/ScaladocSettings.scala +++ b/scaladoc/src/dotty/tools/scaladoc/ScaladocSettings.scala @@ -43,7 +43,7 @@ class ScaladocSettings extends SettingGroup with AllScalaSettings: val socialLinks: Setting[List[String]] = MultiStringSetting("-social-links", "social-links", - "Links to social sites. '[github|twitter|gitter|discord]::link' syntax is used.") + "Links to social sites. '[github|twitter|gitter|discord]::link' or 'custom::link::light_icon_file_name[::dark_icon_file_name]' syntax is used. For custom links, the icons must be present in '_assets/images/'") val deprecatedSkipPackages: Setting[List[String]] = MultiStringSetting("-skip-packages", "packages", "Deprecated, please use `-skip-by-id` or `-skip-by-regex`") diff --git a/scaladoc/src/dotty/tools/scaladoc/SocialLinks.scala b/scaladoc/src/dotty/tools/scaladoc/SocialLinks.scala index a07029d06c50..545d9176675a 100644 --- a/scaladoc/src/dotty/tools/scaladoc/SocialLinks.scala +++ b/scaladoc/src/dotty/tools/scaladoc/SocialLinks.scala @@ -5,12 +5,16 @@ enum SocialLinks(val url: String, val className: String): case Twitter(tUrl: String) extends SocialLinks(tUrl, "twitter") case Gitter(gUrl: String) extends SocialLinks(gUrl, "gitter") case Discord(dUrl: String) extends SocialLinks(dUrl, "discord") + case Custom(cUrl: String, lightIcon: String, darkIcon: String) extends SocialLinks(cUrl, "custom") object SocialLinks: + val LowercaseNamePattern = "^[a-z]+$".r + def parse(s: String): Either[String, SocialLinks] = val errorPrefix = s"Social links arg $s is invalid: " val splitted = s.split("::") - splitted.head match { + + splitted.head.toLowerCase match { case "github" if splitted.size == 2 => Right(Github(splitted(1))) case "github" => Left(errorPrefix + "For 'github' arg expected one argument: url") case "twitter" if splitted.size == 2 => Right(Twitter(splitted(1))) @@ -19,5 +23,8 @@ object SocialLinks: case "gitter" => Left(errorPrefix + "For 'gitter' arg expected one argument: url") case "discord" if splitted.size == 2 => Right(Discord(splitted(1))) case "discord" => Left(errorPrefix + "For 'discord' arg expected one argument: url") + case LowercaseNamePattern() if splitted.size == 4 => Right(Custom(splitted(1), splitted(2), splitted(3))) + case LowercaseNamePattern() if splitted.size == 3 => Right(Custom(splitted(1), splitted(2), splitted(2))) + case LowercaseNamePattern() => Left(errorPrefix + "For the 'custom' link, a minimum of two arguments is expected: URL, light icon file name, [dark icon file name]") case _ => Left(errorPrefix) } diff --git a/scaladoc/src/dotty/tools/scaladoc/renderers/HtmlRenderer.scala b/scaladoc/src/dotty/tools/scaladoc/renderers/HtmlRenderer.scala index 93b86ce0bc51..872f8a4f09c9 100644 --- a/scaladoc/src/dotty/tools/scaladoc/renderers/HtmlRenderer.scala +++ b/scaladoc/src/dotty/tools/scaladoc/renderers/HtmlRenderer.scala @@ -166,7 +166,14 @@ class HtmlRenderer(rootPackage: Member, members: Map[DRI, Member])(using ctx: Do def icon(link: SocialLinks) = link.className args.socialLinks.map { link => a(href := link.url) ( - button(cls := s"icon-button ${icon(link)}") + link match + case SocialLinks.Custom(_, lightIcon, darkIcon) => + Seq( + button(cls := s"icon-button ${icon(link)}", style := s"--bgimage:url(../../../../images/$lightIcon)"), + button(cls := s"icon-button ${icon(link)}-dark", style := s"--bgimage-dark:url(../../../../images/$darkIcon)") + ) + case _ => + button(cls := s"icon-button ${icon(link)}") ) } @@ -308,18 +315,7 @@ class HtmlRenderer(rootPackage: Member, members: Map[DRI, Member])(using ctx: Do "Generated with" ), div(cls := "right-container")( - a(href := "https://github.com/lampepfl/dotty") ( - button(cls := "icon-button gh") - ), - a(href := "https://twitter.com/scala_lang") ( - button(cls := "icon-button twitter") - ), - a(href := "https://discord.com/invite/scala") ( - button(cls := "icon-button discord"), - ), - a(href := "https://gitter.im/scala/scala") ( - button(cls := "icon-button gitter"), - ), + socialLinks, div(cls := "text")(textFooter) ), div(cls := "text-mobile")(textFooter) diff --git a/scaladoc/test/dotty/tools/scaladoc/SocialLinksTest.scala b/scaladoc/test/dotty/tools/scaladoc/SocialLinksTest.scala new file mode 100644 index 000000000000..ede928ff2a08 --- /dev/null +++ b/scaladoc/test/dotty/tools/scaladoc/SocialLinksTest.scala @@ -0,0 +1,52 @@ +package dotty.tools.scaladoc + +import org.junit.Test +import org.junit.Assert._ +import dotty.tools.scaladoc.SocialLinks + +class SocialLinksTest: + + @Test def githubLink(): Unit = + val githubLink = "github::https://github.com/test" + val expected = SocialLinks.Github("https://github.com/test") + assertEquals(expected, SocialLinks.parse(githubLink).getOrElse(null)) + + @Test def twitterLink(): Unit = + val twitterLink = "twitter::https://twitter.com/test" + val expected = SocialLinks.Twitter("https://twitter.com/test") + assertEquals(expected, SocialLinks.parse(twitterLink).getOrElse(null)) + + @Test def gitterLink(): Unit = + val gitterLink = "gitter::https://gitter.im/test" + val expected = SocialLinks.Gitter("https://gitter.im/test") + assertEquals(expected, SocialLinks.parse(gitterLink).getOrElse(null)) + + @Test def discordLink(): Unit = + val discordLink = "discord::https://discord.gg/test" + val expected = SocialLinks.Discord("https://discord.gg/test") + assertEquals(expected, SocialLinks.parse(discordLink).getOrElse(null)) + + @Test def customLinkLight(): Unit = + val customLink = "namecustom::https://custom.com/test::custom" + val expected = SocialLinks.Custom("https://custom.com/test", "custom", "custom") + assertEquals(expected, SocialLinks.parse(customLink).getOrElse(null)) + + @Test def customLinkLightAndDark(): Unit = + val customLink = "namecustom::https://custom.com/test::custom::custom-dark" + val expected = SocialLinks.Custom("https://custom.com/test", "custom", "custom-dark") + assertEquals(expected, SocialLinks.parse(customLink).getOrElse(null)) + + @Test def customLinkUpper(): Unit = + val customLink = "Namecustom::https://custom.com/test::custom" + val expected = SocialLinks.Custom("https://custom.com/test", "custom", "custom") + assertEquals(expected, SocialLinks.parse(customLink).getOrElse(null)) + + @Test def parseRegexError(): Unit = + val regexErrorLink = "nameCustom3::https://custom.com/test::custom::custom-dark::custom" + val expected = s"Social links arg $regexErrorLink is invalid: " + assertEquals(expected, SocialLinks.parse(regexErrorLink).left.getOrElse(null)) + + @Test def parseLinkWithError(): Unit = + val errorLink = "namecustom::https://custom.com/test::custom::custom-dark::custom" + val expected = s"Social links arg $errorLink is invalid: For the 'custom' link, a minimum of two arguments is expected: URL, light icon file name, [dark icon file name]" + assertEquals(expected, SocialLinks.parse(errorLink).left.getOrElse(null))