diff --git a/docs/example/example-core/src/commonMain/kotlin/Example.kt b/docs/example/example-core/src/commonMain/kotlin/Example.kt index d02ffa0..6175ef8 100644 --- a/docs/example/example-core/src/commonMain/kotlin/Example.kt +++ b/docs/example/example-core/src/commonMain/kotlin/Example.kt @@ -58,6 +58,28 @@ class Example() : MyInterface { fun foo(number: Int): Boolean { TODO() } + + /** + * This is a method from [MyInterface] that has been overridden by the [Example] class. + */ + override fun fromInterface2(): String = + "overridden" + + /** + * The companion object. + * + * Second line of documentation. + */ + companion object { + const val DEFAULT = 4 + } + + class Nested { + + companion object { + const val DEFAULT = 5 + } + } } /** diff --git a/docs/example/example-core/src/commonMain/kotlin/MyInterface.kt b/docs/example/example-core/src/commonMain/kotlin/MyInterface.kt index 0063397..570b498 100644 --- a/docs/example/example-core/src/commonMain/kotlin/MyInterface.kt +++ b/docs/example/example-core/src/commonMain/kotlin/MyInterface.kt @@ -3,4 +3,21 @@ package opensavvy.dokka.material.mkdocs.example /** * Simple interface. */ -interface MyInterface +interface MyInterface { + + /** + * This is a method from the interface [MyInterface]. + * + * It is not overridden by the class [Example]. + */ + fun fromInterface1(): String { + return "initial" + } + + /** + * This is a method from the interface [MyInterface]. + * + * It is override by the class [Example]. + */ + fun fromInterface2(): String +} diff --git a/renderer/src/jvmMain/kotlin/MaterialForMkDocsPlugin.kt b/renderer/src/jvmMain/kotlin/MaterialForMkDocsPlugin.kt index 2b13991..301055d 100644 --- a/renderer/src/jvmMain/kotlin/MaterialForMkDocsPlugin.kt +++ b/renderer/src/jvmMain/kotlin/MaterialForMkDocsPlugin.kt @@ -53,6 +53,10 @@ class MaterialForMkDocsPlugin : DokkaPlugin() { val briefCommentPreprocessor: Extension by extending { mkdocsPreprocessor with BriefCommentPreprocessor() } + + val methodGroupingTransformer: Extension by extending { + mkdocsPreprocessor with MethodGroupingTransformer() + } val signatureBreaklineTransformer: Extension by extending { mkdocsPreprocessor with SignatureBreaklineTransformer() diff --git a/renderer/src/jvmMain/kotlin/MethodGroupingTransformer.kt b/renderer/src/jvmMain/kotlin/MethodGroupingTransformer.kt new file mode 100644 index 0000000..b0c79e6 --- /dev/null +++ b/renderer/src/jvmMain/kotlin/MethodGroupingTransformer.kt @@ -0,0 +1,82 @@ +package opensavvy.dokka.material.mkdocs + +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.pages.* +import org.jetbrains.dokka.transformers.pages.PageTransformer + +class MethodGroupingTransformer : PageTransformer { + override fun invoke(input: RootPageNode): RootPageNode { + val driToPage = mutableMapOf() + + // 1. Collect all member pages + input.transformContentPagesTree { page -> + if (page is MemberPageNode) { + page.documentables.forEach { doc -> + driToPage[doc.dri] = page + } + } + page + } + + return input.transformContentPagesTree { page -> + if (page is ClasslikePageNode) { + val alreadyMerged = mutableSetOf() + // 2. Replace summary rows with full member content + val newContent = page.content.recursiveMapTransform { table -> + if (table.dci.kind in listOf(ContentKind.Functions, ContentKind.Properties, ContentKind.Constructors)) { + table.copy( + children = table.children.map { row -> + if (row is ContentGroup) { + val dri = row.findDRI() + val memberPage = dri?.let { driToPage[it] } + if (memberPage != null) { + if (memberPage !in alreadyMerged) { + alreadyMerged.add(memberPage) + row.copy( + children = memberPage.content.children.map { it.demoteHeaders(2) } + ) + } else { + // Already merged as part of an overload group. Remove this row. + ContentGroup(emptyList(), row.dci, row.sourceSets, row.style, row.extra) + } + } else { + row + } + } else { + row + } + } + ) + } else { + table + } + } + + // 3. Remove member pages from this class's children + page.modified( + content = newContent as ContentGroup, + children = page.children.filterNot { it is MemberPageNode } + ) + } else { + page + } + } + } + + private fun ContentNode.findDRI(): DRI? { + if (this is ContentDRILink) return dci.dri.firstOrNull() + if (this is ContentComposite) { + for (child in this.children) { + val dri = child.findDRI() + if (dri != null) return dri + } + } + return dci.dri.firstOrNull() + } + + private fun ContentNode.demoteHeaders(by: Int): ContentNode { + return this.recursiveMapTransform { header -> + header.copy(level = header.level + by) + } + } +} -- 2.51.2 From 59fb4a68633ea4d63b0bb2415c0727e6eb027468 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?= Date: Thu, 12 Mar 2026 20:32:09 +0100 Subject: [PATCH 2/6] feat(renderer): Rework 'Parameters:', 'Return:', 'See also:', etc sections Before, they were proper sections in the Markdown page. That lead to a bloated table of contents. Instead, we make them simple bolded text, which makes the page much more lightweight. --- renderer/src/jvmMain/kotlin/renderer3/Header.kt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/renderer/src/jvmMain/kotlin/renderer3/Header.kt b/renderer/src/jvmMain/kotlin/renderer3/Header.kt index 1bb425a..94c3a75 100644 --- a/renderer/src/jvmMain/kotlin/renderer3/Header.kt +++ b/renderer/src/jvmMain/kotlin/renderer3/Header.kt @@ -23,13 +23,20 @@ import org.jetbrains.dokka.pages.ContentHeader internal fun RenderingContext.buildHeader( header: ContentHeader, ) { + val decorator = when (header.level) { 1 -> Decoration.ofPrefix("# ") 2 -> Decoration.ofPrefix("## ") 3 -> Decoration.ofPrefix("### ") 4 -> Decoration.ofPrefix("#### ") 5 -> Decoration.ofPrefix("##### ") - else -> Decoration.ofPrefix("###### ") + + else -> Decoration { content -> + append("**") + content() + append("**") + appendLine() + } } decorator.wrapIn(writer) { -- 2.51.2 From 4cf65fcbd29fa4b237a0c2e1fba1ca8fc7b9ddf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?= Date: Thu, 12 Mar 2026 20:36:31 +0100 Subject: [PATCH 3/6] feat(renderer): Parameters should be simple lists, not definition lists --- renderer/src/jvmMain/kotlin/renderer3/Tables.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/renderer/src/jvmMain/kotlin/renderer3/Tables.kt b/renderer/src/jvmMain/kotlin/renderer3/Tables.kt index 4ccf628..b16d839 100644 --- a/renderer/src/jvmMain/kotlin/renderer3/Tables.kt +++ b/renderer/src/jvmMain/kotlin/renderer3/Tables.kt @@ -30,11 +30,11 @@ internal fun RenderingContext.buildTable(node: ContentTable) { buildTableAsSections(node) } - ContentKind.Inheritors, ContentKind.Comment -> { + ContentKind.Inheritors, ContentKind.Comment, ContentKind.Parameters -> { buildTableAsList(node) } - ContentKind.Main, ContentKind.Parameters -> { + ContentKind.Main -> { buildTableAsDefinitions(node) } -- 2.51.2 From 48d5f317e413870aa59a8749990a93ac3635305c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?= Date: Thu, 12 Mar 2026 20:34:26 +0100 Subject: [PATCH 4/6] feat(renderer): Insert a ':' after each item name in a list --- renderer/src/jvmMain/kotlin/renderer3/Tables.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/renderer/src/jvmMain/kotlin/renderer3/Tables.kt b/renderer/src/jvmMain/kotlin/renderer3/Tables.kt index b16d839..d54f38d 100644 --- a/renderer/src/jvmMain/kotlin/renderer3/Tables.kt +++ b/renderer/src/jvmMain/kotlin/renderer3/Tables.kt @@ -69,9 +69,13 @@ private fun RenderingContext.buildTableAsList(node: ContentTable) { for (child in node.children) { append(" - ") - for (section in child.children) { + for ((i, section) in child.children.withIndex()) { + if (i == 1) + append(": ") // The section #0 is the title + else if (i > 1) + append(" ") + buildContent(section) - append(" ") } appendLine() } -- 2.51.2 From c6fa9fe5e2f5fb4ee39a939b75fc642432d629dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?= Date: Sat, 14 Mar 2026 19:49:05 +0100 Subject: [PATCH 5/6] fix(renderer): Fix links to methods --- .../src/commonMain/kotlin/Example.kt | 2 ++ .../kotlin/location/MarkdownLocationProvider.kt | 17 +++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/docs/example/example-core/src/commonMain/kotlin/Example.kt b/docs/example/example-core/src/commonMain/kotlin/Example.kt index 6175ef8..7fd34ad 100644 --- a/docs/example/example-core/src/commonMain/kotlin/Example.kt +++ b/docs/example/example-core/src/commonMain/kotlin/Example.kt @@ -98,6 +98,8 @@ fun topLevelFunction(): Int { /** * This is another top-level function with multiple parameters. + * + * This a link to a method: [Example.foo]. */ fun anotherTopLevelFunction( param1: String, diff --git a/renderer/src/jvmMain/kotlin/location/MarkdownLocationProvider.kt b/renderer/src/jvmMain/kotlin/location/MarkdownLocationProvider.kt index e07d2d0..d3644aa 100644 --- a/renderer/src/jvmMain/kotlin/location/MarkdownLocationProvider.kt +++ b/renderer/src/jvmMain/kotlin/location/MarkdownLocationProvider.kt @@ -7,6 +7,8 @@ package opensavvy.dokka.material.mkdocs.location import org.jetbrains.dokka.base.resolvers.local.DokkaLocationProvider import org.jetbrains.dokka.base.resolvers.local.LocationProvider import org.jetbrains.dokka.base.resolvers.local.LocationProviderFactory +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.model.DisplaySourceSet import org.jetbrains.dokka.pages.* import org.jetbrains.dokka.plugability.DokkaContext import java.util.* @@ -53,6 +55,21 @@ class MarkdownLocationProvider( pageGraphRoot.children.forEach { registerPath(it, emptyList()) } index } + + override fun resolve(dri: DRI, sourceSets: Set, context: PageNode?): String? { + val default = super.resolve(dri, sourceSets, context) + if (default != null && default.contains("#")) { + val anchor = if (dri.callable?.name == "") { + dri.classNames + } else { + dri.callable?.name ?: dri.classNames + } + if (anchor != null) { + return default.substringBefore("#") + "#" + anchor.lowercase() + } + } + return default + } class Factory(private val context: DokkaContext) : LocationProviderFactory { override fun getLocationProvider(pageNode: RootPageNode): LocationProvider = -- 2.51.2 From e779a72eaac6fd3215c21a5af7062a51dc428e68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?= Date: Thu, 12 Mar 2026 21:31:12 +0100 Subject: [PATCH 6/6] feat(renderer): Fix links with anchors in code blocks --- .../example-core/src/commonMain/kotlin/MyInterface.kt | 10 ++++++++++ renderer/src/jvmMain/kotlin/renderer3/Links.kt | 6 +++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/docs/example/example-core/src/commonMain/kotlin/MyInterface.kt b/docs/example/example-core/src/commonMain/kotlin/MyInterface.kt index 570b498..6428a68 100644 --- a/docs/example/example-core/src/commonMain/kotlin/MyInterface.kt +++ b/docs/example/example-core/src/commonMain/kotlin/MyInterface.kt @@ -21,3 +21,13 @@ interface MyInterface { */ fun fromInterface2(): String } + +/** + * A fake constructor that instantiates a [MyInterface] instance. + * + * ### Implementation details + * + * It uses the [Example] class. + */ +fun MyInterface(): MyInterface = + Example() diff --git a/renderer/src/jvmMain/kotlin/renderer3/Links.kt b/renderer/src/jvmMain/kotlin/renderer3/Links.kt index c7f62d5..0d9a748 100644 --- a/renderer/src/jvmMain/kotlin/renderer3/Links.kt +++ b/renderer/src/jvmMain/kotlin/renderer3/Links.kt @@ -47,7 +47,11 @@ internal fun Appendable.buildLink( } internal fun RenderingContext.buildDRILink(link: ContentDRILink) { - val resolvedAddress = locations.resolve(link.address, link.sourceSets, page) + var resolvedAddress = locations.resolve(link.address, link.sourceSets, page) + + if (resolvedAddress != null && isInCodeBlock && resolvedAddress.contains(".md")) { + resolvedAddress = resolvedAddress.replace(Regex("""\.md(#|$)"""), ".html$1") + } // Links to the module-level page shouldn't appear as code, since the module has a display name // Apparently, Dokka represents this as the package name '.ext'.