From c04295aa7f0bdad9eb7b27e3dbc2b2c90094da86 Mon Sep 17 00:00:00 2001 From: Marcin Aman Date: Thu, 14 Jan 2021 16:51:25 +0100 Subject: [PATCH] Footer customisation (#1691) --- .../docs/user_guide/base-specific/frontend.md | 14 ++++ .../src/main/kotlin/DokkaBaseConfiguration.kt | 7 +- .../kotlin/renderers/html/HtmlRenderer.kt | 65 ++++++++++++------- .../kotlin/renderers/html/CustomFooterTest.kt | 44 +++++++++++++ .../renderers/html/FooterMessageTest.kt | 27 ++++++++ .../html/HtmlRenderingOnlyTestBase.kt | 9 ++- 6 files changed, 137 insertions(+), 29 deletions(-) create mode 100644 plugins/base/src/test/kotlin/renderers/html/CustomFooterTest.kt create mode 100644 plugins/base/src/test/kotlin/renderers/html/FooterMessageTest.kt diff --git a/docs/src/doc/docs/user_guide/base-specific/frontend.md b/docs/src/doc/docs/user_guide/base-specific/frontend.md index 11c0c78636..a9fd660fd7 100644 --- a/docs/src/doc/docs/user_guide/base-specific/frontend.md +++ b/docs/src/doc/docs/user_guide/base-specific/frontend.md @@ -1,5 +1,9 @@ # Configuration specific to HTML format +!!! important + Concepts specified below apply only to configuration of the Base Plugin (that contains Html format) + and needs to be applied via pluginsConfiguration and not on the root one. + ## Modifying assets It is possible to change static assets that are used to generate dokka's HTML. @@ -19,6 +23,16 @@ Dokka uses 3 stylesheets: User can choose to add or override those files. Resources will be overridden when in `pluginConfiguration` block there is a resource with the same name. +## Modifying footer + +Dokka supports custom messages in the footer via `footerMessage` string property on base plugin configuration. +Keep in mind that this value will be pased exactly to the output html, so it has to be valid and escaped correctly. + +## Separating inherited members + +By setting a boolean property `separateInheritedMembers` dokka will split inherited members (like functions, properties etc.) +from ones declared in viewed class. Separated members will have it's own tabs on the page. + ### Examples In order to override a logo and style it accordingly a simple css file named `logo-styles.css` is needed: ```css diff --git a/plugins/base/src/main/kotlin/DokkaBaseConfiguration.kt b/plugins/base/src/main/kotlin/DokkaBaseConfiguration.kt index 5b93c209c9..21757d702f 100644 --- a/plugins/base/src/main/kotlin/DokkaBaseConfiguration.kt +++ b/plugins/base/src/main/kotlin/DokkaBaseConfiguration.kt @@ -2,13 +2,16 @@ package org.jetbrains.dokka.base import org.jetbrains.dokka.plugability.ConfigurableBlock import java.io.File +import java.time.Year data class DokkaBaseConfiguration( var customStyleSheets: List = defaultCustomStyleSheets, var customAssets: List = defaultCustomAssets, - var separateInheritedMembers: Boolean = separateInheritedMembersDefault -): ConfigurableBlock { + var separateInheritedMembers: Boolean = separateInheritedMembersDefault, + var footerMessage: String = defaultFooterMessage +) : ConfigurableBlock { companion object { + val defaultFooterMessage = "© ${Year.now().value} Copyright" val defaultCustomStyleSheets: List = emptyList() val defaultCustomAssets: List = emptyList() const val separateInheritedMembersDefault: Boolean = false diff --git a/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt b/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt index 5c877f0315..6eb6828076 100644 --- a/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt +++ b/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt @@ -4,6 +4,8 @@ import kotlinx.html.* import kotlinx.html.stream.createHTML import org.jetbrains.dokka.DokkaSourceSetID import org.jetbrains.dokka.base.DokkaBase +import org.jetbrains.dokka.base.DokkaBaseConfiguration +import org.jetbrains.dokka.base.DokkaBaseConfiguration.Companion.defaultFooterMessage import org.jetbrains.dokka.base.renderers.DefaultRenderer import org.jetbrains.dokka.base.renderers.TabSortingStrategy import org.jetbrains.dokka.base.renderers.html.command.consumers.ImmediateResolutionTagConsumer @@ -20,16 +22,14 @@ import org.jetbrains.dokka.model.properties.PropertyContainer import org.jetbrains.dokka.model.sourceSetIDs import org.jetbrains.dokka.model.withDescendants import org.jetbrains.dokka.pages.* -import org.jetbrains.dokka.plugability.DokkaContext -import org.jetbrains.dokka.plugability.plugin -import org.jetbrains.dokka.plugability.query -import org.jetbrains.dokka.plugability.querySingle +import org.jetbrains.dokka.plugability.* import org.jetbrains.dokka.utilities.htmlEscape import java.net.URI open class HtmlRenderer( context: DokkaContext ) : DefaultRenderer(context) { + private val configuration = configuration(context) private val sourceSetDependencyMap: Map> = context.configuration.sourceSets.map { sourceSet -> @@ -107,8 +107,13 @@ open class HtmlRenderer( } node.hasStyle(TextStyle.Paragraph) -> p(additionalClasses) { childrenCallback() } node.hasStyle(TextStyle.Block) -> div(additionalClasses) { childrenCallback() } - node.isAnchorable -> buildAnchor(node.anchor!!, node.anchorLabel!!, node.sourceSetsFilters) { childrenCallback() } - node.extra[InsertTemplateExtra] != null -> node.extra[InsertTemplateExtra]?.let { templateCommand(it.command) } ?: Unit + node.isAnchorable -> buildAnchor( + node.anchor!!, + node.anchorLabel!!, + node.sourceSetsFilters + ) { childrenCallback() } + node.extra[InsertTemplateExtra] != null -> node.extra[InsertTemplateExtra]?.let { templateCommand(it.command) } + ?: Unit else -> childrenCallback() } } @@ -147,7 +152,7 @@ open class HtmlRenderer( } } - fun FlowContent.withHtml(content: String): Unit = when (this){ + fun FlowContent.withHtml(content: String): Unit = when (this) { is HTMLTag -> unsafe { +content } else -> div { unsafe { +content } } } @@ -225,13 +230,14 @@ open class HtmlRenderer( sourceSet.sourceSetIDs.all.flatMap { sourceSetDependencyMap[it].orEmpty() } .any { sourceSetId -> sourceSetId in sourceSets.sourceSetIDs } }.map { - it to createHTML(prettyPrint = false).prepareForTemplates().div(classes = "content sourceset-depenent-content") { - if (counter++ == 0) attributes["data-active"] = "" - attributes["data-togglable"] = it.sourceSetIDs.merged.toString() - unsafe { - +html + it to createHTML(prettyPrint = false).prepareForTemplates() + .div(classes = "content sourceset-depenent-content") { + if (counter++ == 0) attributes["data-active"] = "" + attributes["data-togglable"] = it.sourceSetIDs.merged.toString() + unsafe { + +html + } } - } } } } @@ -423,15 +429,15 @@ open class HtmlRenderer( div { toRender.filter { it !is ContentLink && !it.hasStyle(ContentStyle.RowTitle) } .takeIf { it.isNotEmpty() }?.let { - if (ContentKind.shouldBePlatformTagged(contextNode.dci.kind) && contextNode.sourceSets.size == 1) - createPlatformTags(contextNode) + if (ContentKind.shouldBePlatformTagged(contextNode.dci.kind) && contextNode.sourceSets.size == 1) + createPlatformTags(contextNode) - div("title") { - it.forEach { - it.build(this, pageContext, sourceSetRestriction) + div("title") { + it.forEach { + it.build(this, pageContext, sourceSetRestriction) + } } } - } } } } @@ -450,7 +456,9 @@ open class HtmlRenderer( .forEach { span("inline-flex") { it.build(this, pageContext, sourceSetRestriction) - if(it is ContentLink && !anchorDestination.isNullOrBlank()) buildAnchorCopyButton(anchorDestination) + if (it is ContentLink && !anchorDestination.isNullOrBlank()) buildAnchorCopyButton( + anchorDestination + ) } } } @@ -472,7 +480,7 @@ open class HtmlRenderer( toRender: List, pageContext: ContentPage, sourceSetRestriction: Set?, - ){ + ) { toRender.filter { it !is ContentLink }.takeIf { it.isNotEmpty() }?.let { it.forEach { span(classes = if (it.dci.kind == ContentKind.Comment) "brief-comment" else "") { @@ -573,7 +581,12 @@ open class HtmlRenderer( } } - private fun FlowContent.buildAnchor(anchor: String, anchorLabel: String, sourceSets: String, content: FlowContent.() -> Unit) { + private fun FlowContent.buildAnchor( + anchor: String, + anchorLabel: String, + sourceSets: String, + content: FlowContent.() -> Unit + ) { a { attributes["data-name"] = anchor attributes["anchor-label"] = anchorLabel @@ -772,9 +785,13 @@ open class HtmlRenderer( span("go-to-top-icon") { a(href = "#content") } - span { text("© 2020 Copyright") } + span { + configuration?.footerMessage?.takeIf { it.isNotEmpty() } + ?.let { unsafe { raw(it) } } + ?: text(defaultFooterMessage) + } span("pull-right") { - span { text("Sponsored and developed by ") } + span { text("Generated by ") } a(href = "https://github.com/Kotlin/dokka") { span { text("dokka") } span(classes = "padded-icon") diff --git a/plugins/base/src/test/kotlin/renderers/html/CustomFooterTest.kt b/plugins/base/src/test/kotlin/renderers/html/CustomFooterTest.kt new file mode 100644 index 0000000000..c1ef612159 --- /dev/null +++ b/plugins/base/src/test/kotlin/renderers/html/CustomFooterTest.kt @@ -0,0 +1,44 @@ +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.renderers.html.HtmlRenderer +import org.jetbrains.dokka.base.templating.toJsonString +import org.jsoup.Jsoup +import org.jsoup.nodes.Element +import org.junit.jupiter.api.Test +import renderers.testPage +import utils.A +import utils.Div +import utils.Span +import utils.match + +class CustomFooterTest : HtmlRenderingOnlyTestBase() { + @Test + fun `should include message from custom footer`() { + val page = testPage { } + HtmlRenderer(context).render(page) + renderedContent.match( + Span(A()), + Span(Div("Custom message")), + Span(Span("Generated by "), A(Span("dokka"), Span())) + ) + } + + override val configuration: DokkaConfigurationImpl + get() = super.configuration.copy( + pluginsConfiguration = listOf( + PluginConfigurationImpl( + DokkaBase::class.java.canonicalName, + DokkaConfiguration.SerializationFormat.JSON, + toJsonString(DokkaBaseConfiguration(footerMessage = """
Custom message
""")) + ) + ) + ) + + override val renderedContent: Element + get() = files.contents.getValue("test-page.html").let { Jsoup.parse(it) }.select(".footer").single() +} \ No newline at end of file diff --git a/plugins/base/src/test/kotlin/renderers/html/FooterMessageTest.kt b/plugins/base/src/test/kotlin/renderers/html/FooterMessageTest.kt new file mode 100644 index 0000000000..d91c402ef6 --- /dev/null +++ b/plugins/base/src/test/kotlin/renderers/html/FooterMessageTest.kt @@ -0,0 +1,27 @@ +package renderers.html + +import org.jetbrains.dokka.base.DokkaBaseConfiguration.Companion.defaultFooterMessage +import org.jetbrains.dokka.base.renderers.html.HtmlRenderer +import org.jsoup.Jsoup +import org.jsoup.nodes.Element +import org.junit.jupiter.api.Test +import renderers.testPage +import utils.A +import utils.Span +import utils.match + +class FooterMessageTest : HtmlRenderingOnlyTestBase() { + @Test + fun `should include defaultFooter`() { + val page = testPage { } + HtmlRenderer(context).render(page) + renderedContent.match( + Span(A()), + Span(defaultFooterMessage), + Span(Span("Generated by "), A(Span("dokka"), Span())) + ) + } + + override val renderedContent: Element + get() = files.contents.getValue("test-page.html").let { Jsoup.parse(it) }.select(".footer").single() +} \ No newline at end of file diff --git a/plugins/base/src/test/kotlin/renderers/html/HtmlRenderingOnlyTestBase.kt b/plugins/base/src/test/kotlin/renderers/html/HtmlRenderingOnlyTestBase.kt index 71d141e222..9b88a1707a 100644 --- a/plugins/base/src/test/kotlin/renderers/html/HtmlRenderingOnlyTestBase.kt +++ b/plugins/base/src/test/kotlin/renderers/html/HtmlRenderingOnlyTestBase.kt @@ -41,6 +41,11 @@ abstract class HtmlRenderingOnlyTestBase : RenderingOnlyTestBase() { ) val files = TestOutputWriter() + + open val configuration = DokkaConfigurationImpl( + sourceSets = listOf(js, jvm, native) + ) + override val context = MockContext( DokkaBase().outputWriter to { _ -> files }, DokkaBase().locationProviderFactory to ::DokkaLocationProviderFactory, @@ -48,9 +53,7 @@ abstract class HtmlRenderingOnlyTestBase : RenderingOnlyTestBase() { DokkaBase().externalLocationProviderFactory to { ::JavadocExternalLocationProviderFactory }, DokkaBase().externalLocationProviderFactory to { ::DefaultExternalLocationProviderFactory }, DokkaBase().tabSortingStrategy to { DefaultTabSortingStrategy() }, - testConfiguration = DokkaConfigurationImpl( - sourceSets = listOf(js, jvm, native) - ) + testConfiguration = configuration ) override val renderedContent: Element by lazy {