aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOleg Yukhnevich <whyoleg@gmail.com>2023-11-07 15:27:34 +0200
committerGitHub <noreply@github.com>2023-11-07 15:27:34 +0200
commit64cce58f73f908909ba60da6005d7abb2572b2f0 (patch)
treeb66897df0bb86e159d6d040c65e04552efd1ee68
parent84e48b516500433332e9279237cd139f7836ca94 (diff)
downloaddokka-64cce58f73f908909ba60da6005d7abb2572b2f0.tar.gz
dokka-64cce58f73f908909ba60da6005d7abb2572b2f0.tar.bz2
dokka-64cce58f73f908909ba60da6005d7abb2572b2f0.zip
Add the link to GitHub repo to the header if there are source links defined (#3235)
* Use URL from base plugin configuration * Add integration test for the multi-module project that the homepage link exists everywhere
-rw-r--r--integration-tests/gradle/projects/it-multimodule-0/moduleA/build.gradle.kts10
-rw-r--r--integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/MultiModule0IntegrationTest.kt15
-rw-r--r--plugins/base/api/base.api11
-rw-r--r--plugins/base/src/main/kotlin/DokkaBaseConfiguration.kt3
-rw-r--r--plugins/base/src/main/kotlin/renderers/html/htmlPreprocessors.kt1
-rw-r--r--plugins/base/src/main/kotlin/renderers/html/innerTemplating/DefaultTemplateModelFactory.kt24
-rw-r--r--plugins/base/src/main/resources/dokka/images/homepage.svg5
-rw-r--r--plugins/base/src/main/resources/dokka/styles/style.css32
-rw-r--r--plugins/base/src/main/resources/dokka/templates/includes/header.ftl3
-rw-r--r--plugins/base/src/test/kotlin/renderers/html/HeaderTest.kt102
10 files changed, 195 insertions, 11 deletions
diff --git a/integration-tests/gradle/projects/it-multimodule-0/moduleA/build.gradle.kts b/integration-tests/gradle/projects/it-multimodule-0/moduleA/build.gradle.kts
index 1e61f8b2..d24b90c5 100644
--- a/integration-tests/gradle/projects/it-multimodule-0/moduleA/build.gradle.kts
+++ b/integration-tests/gradle/projects/it-multimodule-0/moduleA/build.gradle.kts
@@ -8,3 +8,13 @@ plugins {
kotlin("jvm")
id("org.jetbrains.dokka")
}
+
+allprojects {
+ tasks.withType<org.jetbrains.dokka.gradle.AbstractDokkaTask> {
+ pluginsMapConfiguration.set(
+ mapOf(
+ "org.jetbrains.dokka.base.DokkaBase" to """{ "homepageLink" : "https://github.com/Kotlin/dokka/tree/master/integration-tests/gradle/projects/it-multimodule-0/" }"""
+ )
+ )
+ }
+}
diff --git a/integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/MultiModule0IntegrationTest.kt b/integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/MultiModule0IntegrationTest.kt
index 54ac3ff8..f4061345 100644
--- a/integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/MultiModule0IntegrationTest.kt
+++ b/integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/MultiModule0IntegrationTest.kt
@@ -73,6 +73,21 @@ class MultiModule0IntegrationTest : AbstractGradleIntegrationTest() {
"Expected moduleC being mentioned in -modules.html"
)
+ val htmlsWithHomepageLink = outputDir.walkTopDown().filter {
+ it.isFile && it.extension == "html" && it.name != "navigation.html"
+ }.toList()
+
+ assertEquals(16, htmlsWithHomepageLink.size)
+
+ htmlsWithHomepageLink.forEach {
+ assertTrue(
+ it.readText().contains(
+ """https://github.com/Kotlin/dokka/tree/master/integration-tests/gradle/projects/it-multimodule-0/"""
+ ),
+ "File ${it.absolutePath} doesn't contain link to homepage"
+ )
+ }
+
val gfmOutputDir = File(projectDir, "moduleA/build/dokka/gfmMultiModule")
assertTrue(gfmOutputDir.isDirectory, "Missing dokka GFM output directory")
diff --git a/plugins/base/api/base.api b/plugins/base/api/base.api
index ae872558..13f877e3 100644
--- a/plugins/base/api/base.api
+++ b/plugins/base/api/base.api
@@ -105,20 +105,22 @@ public final class org/jetbrains/dokka/base/DokkaBaseConfiguration : org/jetbrai
public static final field mergeImplicitExpectActualDeclarationsDefault Z
public static final field separateInheritedMembersDefault Z
public fun <init> ()V
- public fun <init> (Ljava/util/List;Ljava/util/List;ZLjava/lang/String;ZLjava/io/File;)V
- public synthetic fun <init> (Ljava/util/List;Ljava/util/List;ZLjava/lang/String;ZLjava/io/File;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public fun <init> (Ljava/util/List;Ljava/util/List;ZLjava/lang/String;ZLjava/io/File;Ljava/lang/String;)V
+ public synthetic fun <init> (Ljava/util/List;Ljava/util/List;ZLjava/lang/String;ZLjava/io/File;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Ljava/util/List;
public final fun component2 ()Ljava/util/List;
public final fun component3 ()Z
public final fun component4 ()Ljava/lang/String;
public final fun component5 ()Z
public final fun component6 ()Ljava/io/File;
- public final fun copy (Ljava/util/List;Ljava/util/List;ZLjava/lang/String;ZLjava/io/File;)Lorg/jetbrains/dokka/base/DokkaBaseConfiguration;
- public static synthetic fun copy$default (Lorg/jetbrains/dokka/base/DokkaBaseConfiguration;Ljava/util/List;Ljava/util/List;ZLjava/lang/String;ZLjava/io/File;ILjava/lang/Object;)Lorg/jetbrains/dokka/base/DokkaBaseConfiguration;
+ public final fun component7 ()Ljava/lang/String;
+ public final fun copy (Ljava/util/List;Ljava/util/List;ZLjava/lang/String;ZLjava/io/File;Ljava/lang/String;)Lorg/jetbrains/dokka/base/DokkaBaseConfiguration;
+ public static synthetic fun copy$default (Lorg/jetbrains/dokka/base/DokkaBaseConfiguration;Ljava/util/List;Ljava/util/List;ZLjava/lang/String;ZLjava/io/File;Ljava/lang/String;ILjava/lang/Object;)Lorg/jetbrains/dokka/base/DokkaBaseConfiguration;
public fun equals (Ljava/lang/Object;)Z
public final fun getCustomAssets ()Ljava/util/List;
public final fun getCustomStyleSheets ()Ljava/util/List;
public final fun getFooterMessage ()Ljava/lang/String;
+ public final fun getHomepageLink ()Ljava/lang/String;
public final fun getMergeImplicitExpectActualDeclarations ()Z
public final fun getSeparateInheritedMembers ()Z
public final fun getTemplatesDir ()Ljava/io/File;
@@ -126,6 +128,7 @@ public final class org/jetbrains/dokka/base/DokkaBaseConfiguration : org/jetbrai
public final fun setCustomAssets (Ljava/util/List;)V
public final fun setCustomStyleSheets (Ljava/util/List;)V
public final fun setFooterMessage (Ljava/lang/String;)V
+ public final fun setHomepageLink (Ljava/lang/String;)V
public final fun setMergeImplicitExpectActualDeclarations (Z)V
public final fun setSeparateInheritedMembers (Z)V
public final fun setTemplatesDir (Ljava/io/File;)V
diff --git a/plugins/base/src/main/kotlin/DokkaBaseConfiguration.kt b/plugins/base/src/main/kotlin/DokkaBaseConfiguration.kt
index 11184126..34195f65 100644
--- a/plugins/base/src/main/kotlin/DokkaBaseConfiguration.kt
+++ b/plugins/base/src/main/kotlin/DokkaBaseConfiguration.kt
@@ -14,7 +14,8 @@ public data class DokkaBaseConfiguration(
var separateInheritedMembers: Boolean = separateInheritedMembersDefault,
var footerMessage: String = defaultFooterMessage,
var mergeImplicitExpectActualDeclarations: Boolean = mergeImplicitExpectActualDeclarationsDefault,
- var templatesDir: File? = defaultTemplatesDir
+ var templatesDir: File? = defaultTemplatesDir,
+ var homepageLink: String? = null,
) : ConfigurableBlock {
public companion object {
public val defaultFooterMessage: String = "© ${Year.now().value} Copyright"
diff --git a/plugins/base/src/main/kotlin/renderers/html/htmlPreprocessors.kt b/plugins/base/src/main/kotlin/renderers/html/htmlPreprocessors.kt
index dc877605..dad013e2 100644
--- a/plugins/base/src/main/kotlin/renderers/html/htmlPreprocessors.kt
+++ b/plugins/base/src/main/kotlin/renderers/html/htmlPreprocessors.kt
@@ -111,6 +111,7 @@ public object AssetsInstaller : PageTransformer {
"images/copy-successful-icon.svg",
"images/theme-toggle.svg",
"images/burger.svg",
+ "images/homepage.svg",
// navigation icons
"images/nav-icons/abstract-class.svg",
diff --git a/plugins/base/src/main/kotlin/renderers/html/innerTemplating/DefaultTemplateModelFactory.kt b/plugins/base/src/main/kotlin/renderers/html/innerTemplating/DefaultTemplateModelFactory.kt
index 3883bc4a..fe6f0089 100644
--- a/plugins/base/src/main/kotlin/renderers/html/innerTemplating/DefaultTemplateModelFactory.kt
+++ b/plugins/base/src/main/kotlin/renderers/html/innerTemplating/DefaultTemplateModelFactory.kt
@@ -86,10 +86,16 @@ public class DefaultTemplateModelFactory(
return mapper
}
- override fun buildSharedModel(): TemplateMap = mapOf<String, Any>(
- "footerMessage" to (configuration?.footerMessage?.takeIf { it.isNotEmpty() }
- ?: DokkaBaseConfiguration.defaultFooterMessage)
- )
+ override fun buildSharedModel(): TemplateMap {
+ val mapper = mutableMapOf<String, Any>()
+
+ mapper["footerMessage"] =
+ (configuration?.footerMessage?.takeIf(String::isNotBlank) ?: DokkaBaseConfiguration.defaultFooterMessage)
+
+ configuration?.homepageLink?.takeIf(String::isNotBlank)?.let { mapper["homepageLink"] = it }
+
+ return mapper
+ }
private val DisplaySourceSet.comparableKey
get() = sourceSetIDs.merged.let { it.scopeId + it.sourceSetName }
@@ -107,6 +113,7 @@ public class DefaultTemplateModelFactory(
rel = LinkRel.stylesheet,
href = if (resource.isAbsolute) resource else "$pathToRoot$resource"
)
+
resource.URIExtension == "js" ->
script(
type = ScriptType.textJavaScript,
@@ -117,6 +124,7 @@ public class DefaultTemplateModelFactory(
else
async = true
}
+
resource.isImage() -> link(href = if (resource.isAbsolute) resource else "$pathToRoot$resource")
else -> null
}
@@ -125,6 +133,7 @@ public class DefaultTemplateModelFactory(
append(resourceHtml)
}
}
+
}
private class PrintDirective(val generateData: () -> String) : TemplateDirectiveModel {
@@ -144,7 +153,10 @@ private class PrintDirective(val generateData: () -> String) : TemplateDirective
}
}
-private class TemplateDirective(val configuration: DokkaConfiguration, val pathToRoot: String) : TemplateDirectiveModel {
+private class TemplateDirective(
+ val configuration: DokkaConfiguration,
+ val pathToRoot: String
+) : TemplateDirectiveModel {
override fun execute(
env: Environment,
params: MutableMap<Any?, Any?>?,
@@ -170,6 +182,7 @@ private class TemplateDirective(val configuration: DokkaConfiguration, val pathT
Context(env, body)
)
}
+
"projectName" -> {
body ?: throw TemplateModelException(
"No directive body $commandName command."
@@ -183,6 +196,7 @@ private class TemplateDirective(val configuration: DokkaConfiguration, val pathT
Context(env, body)
)
}
+
else -> throw TemplateModelException(
"The parameter $PARAM_NAME $commandName is unknown"
)
diff --git a/plugins/base/src/main/resources/dokka/images/homepage.svg b/plugins/base/src/main/resources/dokka/images/homepage.svg
new file mode 100644
index 00000000..a3d7602b
--- /dev/null
+++ b/plugins/base/src/main/resources/dokka/images/homepage.svg
@@ -0,0 +1,5 @@
+<!-- SOURCE: https://www.svgrepo.com/svg/416627/home-house-ui -->
+<svg fill="#ffffff" width="64px" height="64px" viewBox="0 0 512.00 512.00" xmlns="http://www.w3.org/2000/svg">
+ <path d="M256,0C114.615,0,0,114.615,0,256s114.615,256,256,256s256-114.615,256-256S397.385,0,256,0z M404.861,263.236 L404.861,263.236c-7.297,7.297-18.066,8.993-26.986,5.104v97.098c0,20.193-16.37,36.562-36.562,36.562H170.688 c-20.193,0-36.562-16.37-36.562-36.562v-97.098c-8.919,3.89-19.689,2.193-26.986-5.104c-9.519-9.519-9.519-24.952,0-34.471 L238.764,97.139h0c9.519-9.519,24.952-9.519,34.471,0l131.625,131.625C414.38,238.283,414.38,253.717,404.861,263.236z"/>
+ <path d="M286.469,267.938h-60.938c-6.731,0-12.188,5.457-12.188,12.188v73.125c0,6.731,5.457,12.188,12.188,12.188h60.938 c6.731,0,12.188-5.457,12.188-12.188v-73.125C298.656,273.394,293.2,267.938,286.469,267.938z"/>
+</svg>
diff --git a/plugins/base/src/main/resources/dokka/styles/style.css b/plugins/base/src/main/resources/dokka/styles/style.css
index 67a899a5..62b0ddbd 100644
--- a/plugins/base/src/main/resources/dokka/styles/style.css
+++ b/plugins/base/src/main/resources/dokka/styles/style.css
@@ -342,6 +342,7 @@ td:first-child {
/* --- Navigation controls --- */
.navigation-controls {
display: flex;
+ margin-left: 4px;
}
@media (min-width: 760px) {
@@ -365,7 +366,6 @@ td:first-child {
display: block;
border-radius: 50%;
background-color: inherit;
- margin-left: 4px;
padding: 0;
border: none;
cursor: pointer;
@@ -394,6 +394,36 @@ td:first-child {
}
/* /--- Navigation THEME --- */
+/* --- Navigation HOMEPAGE --- */
+.navigation-controls--homepage {
+ height: 40px;
+ width: 40px;
+ display: block;
+ border-radius: 50%;
+ cursor: pointer;
+}
+
+.navigation-controls--homepage a::before {
+ height: 100%;
+ width: 20px;
+ margin-left: 10px;
+ display: block;
+ content: "";
+ background: url("../images/homepage.svg");
+ background-size: 100% 100%;
+}
+
+.navigation-controls--homepage:hover {
+ background: var(--white-10);
+}
+
+@media (max-width: 759px) {
+ .navigation-controls--homepage {
+ display: none;
+ }
+}
+/* /--- Navigation HOMEPAGE --- */
+
.navigation .platform-selector:not([data-active]) {
color: #fff;
}
diff --git a/plugins/base/src/main/resources/dokka/templates/includes/header.ftl b/plugins/base/src/main/resources/dokka/templates/includes/header.ftl
index d5c7a613..d399e633 100644
--- a/plugins/base/src/main/resources/dokka/templates/includes/header.ftl
+++ b/plugins/base/src/main/resources/dokka/templates/includes/header.ftl
@@ -21,6 +21,9 @@
<@source_set_selector.display/>
</div>
<div class="navigation-controls">
+ <#if homepageLink?has_content>
+ <div class="navigation-controls--btn navigation-controls--homepage" id="homepage-link" role="button"><a href="${homepageLink}"></a></div>
+ </#if>
<button class="navigation-controls--btn navigation-controls--theme" id="theme-toggle-button" type="button">switch theme</button>
<div class="navigation-controls--btn navigation-controls--search" id="searchBar" role="button">search in API</div>
</div>
diff --git a/plugins/base/src/test/kotlin/renderers/html/HeaderTest.kt b/plugins/base/src/test/kotlin/renderers/html/HeaderTest.kt
new file mode 100644
index 00000000..c19f965f
--- /dev/null
+++ b/plugins/base/src/test/kotlin/renderers/html/HeaderTest.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package renderers.html
+
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.DokkaConfigurationImpl
+import org.jetbrains.dokka.PluginConfigurationImpl
+import org.jetbrains.dokka.base.DokkaBase
+import org.jetbrains.dokka.base.DokkaBaseConfiguration
+import org.jetbrains.dokka.base.templating.toJsonString
+import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest
+import org.jetbrains.dokka.pages.RootPageNode
+import org.jetbrains.dokka.plugability.DokkaContext
+import org.jsoup.Jsoup
+import utils.TestOutputWriter
+import utils.TestOutputWriterPlugin
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertNull
+
+class HeaderTest : BaseAbstractTest() {
+ private val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ name = "jvm"
+ sourceRoots = listOf("src/jvm")
+ }
+ sourceSet {
+ name = "js"
+ sourceRoots = listOf("src/js")
+ }
+ }
+ }
+
+ @Test
+ fun `should include homepage link if homepageLink is provided`() {
+ testRendering(
+ DokkaBaseConfiguration(homepageLink = "https://github.com/Kotlin/dokka/")
+ ) { _, _, writer ->
+ val renderedContent = navigationElement(writer)
+
+ val sourceLinkElement =
+ assertNotNull(renderedContent.getElementById("homepage-link"), "Source link element not found")
+ val aElement = assertNotNull(sourceLinkElement.selectFirst("a"))
+ assertEquals("https://github.com/Kotlin/dokka/", aElement.attr("href"))
+ }
+ }
+
+ @Test
+ fun `should not include homepage link by default`() {
+ testRendering(null) { _, _, writer ->
+ val renderedContent = navigationElement(writer)
+ assertNull(renderedContent.getElementById("homepage-link"), "Source link element found")
+ }
+ }
+
+ private fun testRendering(
+ baseConfiguration: DokkaBaseConfiguration?,
+ block: (RootPageNode, DokkaContext, writer: TestOutputWriter) -> Unit
+ ) {
+ fun configuration(): DokkaConfigurationImpl {
+ baseConfiguration ?: return configuration
+ return configuration.copy(
+ pluginsConfiguration = listOf(
+ PluginConfigurationImpl(
+ DokkaBase::class.java.canonicalName,
+ DokkaConfiguration.SerializationFormat.JSON,
+ toJsonString(baseConfiguration)
+ )
+ )
+ )
+ }
+
+ val writerPlugin = TestOutputWriterPlugin()
+ testInline(
+ """
+ |/src/jvm/Test.kt
+ |fun test() {}
+ |/src/js/Test.kt
+ |fun test() {}
+ """,
+ configuration(),
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { node, context ->
+ block(node, context, writerPlugin.writer)
+ }
+ }
+ }
+
+ private fun navigationElement(writer: TestOutputWriter) =
+ writer
+ .contents
+ .getValue("index.html")
+ .let(Jsoup::parse)
+ .select(".navigation")
+ .single()
+
+}