Skip to content

Commit

Permalink
Add the link to GitHub repo to the header if there are source links d…
Browse files Browse the repository at this point in the history
…efined (#3235)

* Use URL from base plugin configuration
* Add integration test for the multi-module project that the homepage link exists everywhere
  • Loading branch information
whyoleg committed Nov 7, 2023
1 parent 84e48b5 commit 64cce58
Show file tree
Hide file tree
Showing 10 changed files with 195 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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/" }"""
)
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down
11 changes: 7 additions & 4 deletions plugins/base/api/base.api
Original file line number Diff line number Diff line change
Expand Up @@ -105,27 +105,30 @@ 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;
public fun hashCode ()I
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
Expand Down
3 changes: 2 additions & 1 deletion plugins/base/src/main/kotlin/DokkaBaseConfiguration.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand All @@ -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,
Expand All @@ -117,6 +124,7 @@ public class DefaultTemplateModelFactory(
else
async = true
}

resource.isImage() -> link(href = if (resource.isAbsolute) resource else "$pathToRoot$resource")
else -> null
}
Expand All @@ -125,6 +133,7 @@ public class DefaultTemplateModelFactory(
append(resourceHtml)
}
}

}

private class PrintDirective(val generateData: () -> String) : TemplateDirectiveModel {
Expand All @@ -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?>?,
Expand All @@ -170,6 +182,7 @@ private class TemplateDirective(val configuration: DokkaConfiguration, val pathT
Context(env, body)
)
}

"projectName" -> {
body ?: throw TemplateModelException(
"No directive body $commandName command."
Expand All @@ -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"
)
Expand Down
5 changes: 5 additions & 0 deletions plugins/base/src/main/resources/dokka/images/homepage.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
32 changes: 31 additions & 1 deletion plugins/base/src/main/resources/dokka/styles/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,7 @@ td:first-child {
/* --- Navigation controls --- */
.navigation-controls {
display: flex;
margin-left: 4px;
}

@media (min-width: 760px) {
Expand All @@ -365,7 +366,6 @@ td:first-child {
display: block;
border-radius: 50%;
background-color: inherit;
margin-left: 4px;
padding: 0;
border: none;
cursor: pointer;
Expand Down Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
102 changes: 102 additions & 0 deletions plugins/base/src/test/kotlin/renderers/html/HeaderTest.kt
Original file line number Diff line number Diff line change
@@ -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()

}

0 comments on commit 64cce58

Please sign in to comment.