diff --git a/aggregator/src/jvmMain/kotlin/DokkaMkDocsAggregationPlugin.kt b/aggregator/src/jvmMain/kotlin/DokkaMkDocsAggregationPlugin.kt index 61ae963..1e35ef1 100644 --- a/aggregator/src/jvmMain/kotlin/DokkaMkDocsAggregationPlugin.kt +++ b/aggregator/src/jvmMain/kotlin/DokkaMkDocsAggregationPlugin.kt @@ -31,6 +31,7 @@ import org.jetbrains.dokka.plugability.Extension import org.jetbrains.dokka.plugability.PluginApiPreviewAcknowledgement import org.jetbrains.dokka.templates.TemplateProcessingStrategy import org.jetbrains.dokka.templates.TemplatingPlugin +import org.jetbrains.dokka.transformers.pages.PageTransformer class DokkaMkDocsAggregationPlugin : DokkaPlugin() { @@ -68,6 +69,12 @@ class DokkaMkDocsAggregationPlugin : DokkaPlugin() { allModulesPagePlugin.partialLocationProviderFactory providing MarkdownLocationProvider::Factory override allModulesPagePlugin.baseLocationProviderFactory } + val referencePageTransformer: Extension by extending { + mkdocsPlugin.mkdocsPreprocessor providing { ReferencePageTransformer() } order { + before(mkdocsPlugin.rootCreator) + } + } + @OptIn(DokkaPluginApiPreview::class) override fun pluginApiPreviewAcknowledgement(): PluginApiPreviewAcknowledgement = PluginApiPreviewAcknowledgement diff --git a/aggregator/src/jvmMain/kotlin/ReferencePageTransformer.kt b/aggregator/src/jvmMain/kotlin/ReferencePageTransformer.kt new file mode 100644 index 0000000..c46f20b --- /dev/null +++ b/aggregator/src/jvmMain/kotlin/ReferencePageTransformer.kt @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2026, OpenSavvy and contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package opensavvy.dokka.material.mkdocs.aggregator + +import org.jetbrains.dokka.model.properties.PropertyContainer +import org.jetbrains.dokka.pages.* +import org.jetbrains.dokka.transformers.pages.PageTransformer + +/** + * Creates the 'reference' top-level page, which links to all modules. + */ +class ReferencePageTransformer : PageTransformer { + override fun invoke(input: RootPageNode): RootPageNode { + return input.transformContentPagesTree { page -> + val content = page.content + + if (content !is ContentGroup) + return@transformContentPagesTree page + + var found = false + val newContent = content.copy( + children = content.children.map { node -> + when (node) { + is ContentHeader if node.level == 2 && (node.children.firstOrNull() as? ContentText)?.text?.contains("modules", ignoreCase = true) == true -> { + found = true + ContentGroup( + children = listOf( + node.copy( + level = 1, + children = listOf(ContentText("Reference", node.dci, node.sourceSets)) + ), + ContentHeader( + children = node.children.map { child -> + if (child is ContentText) + child.copy(text = child.text.replace(":", "")) + else + child + }, + level = 2, + dci = node.dci, + sourceSets = node.sourceSets, + style = node.style, + extra = node.extra, + ) + ), + dci = node.dci, + sourceSets = node.sourceSets, + style = node.style, + extra = node.extra, + ) + } + + is ContentTable if node.dci.kind == ContentKind.Main -> { + // The multi-module page uses a table of kind Main to list modules. + // We want to transform each row into a H3 header and a body. + ContentGroup( + children = node.children.flatMap { row -> + val (header, body) = row.children + listOfNotNull( + (header as? ContentDRILink ?: header.children.filterIsInstance().firstOrNull())?.let { link -> + ContentHeader( + children = listOf(link), + level = 3, + dci = link.dci, + sourceSets = link.sourceSets, + style = emptySet(), + ) + } ?: (header as? ContentGroup)?.children?.filterIsInstance()?.firstOrNull { it.text.isNotBlank() }?.let { text -> + ContentHeader( + children = listOf(text), + level = 3, + dci = text.dci, + sourceSets = text.sourceSets, + style = emptySet(), + ) + } ?: ContentHeader( + children = listOf(header), + level = 3, + dci = header.dci, + sourceSets = header.sourceSets, + style = emptySet(), + ), + body.takeIf { (it as? ContentGroup)?.children?.isNotEmpty() == true || (it !is ContentGroup) } + ) + } + listOf( + ContentBreakLine(node.sourceSets, node.dci), + ContentText("
\n\nThis website is built with ", node.dci, node.sourceSets), + ContentResolvedLink( + children = listOf(ContentText("Dokka", node.dci, node.sourceSets)), + address = "https://github.com/kotlin/dokka", + extra = PropertyContainer.empty(), + dci = node.dci, + sourceSets = node.sourceSets + ), + ContentText(" and ", node.dci, node.sourceSets), + ContentResolvedLink( + children = listOf(ContentText("Dokka for MkDocs", node.dci, node.sourceSets)), + address = "https://dokka-mkdocs.opensavvy.dev/", + extra = PropertyContainer.empty(), + dci = node.dci, + sourceSets = node.sourceSets + ), + ContentText(". If you see any formatting issues, ", node.dci, node.sourceSets), + ContentResolvedLink( + children = listOf(ContentText("please report them", node.dci, node.sourceSets)), + address = "https://gitlab.com/opensavvy/automation/dokka-material-mkdocs/-/issues/new", + extra = PropertyContainer.empty(), + dci = node.dci, + sourceSets = node.sourceSets + ), + ContentText(".", node.dci, node.sourceSets), + ), + dci = node.dci, + sourceSets = node.sourceSets, + style = node.style, + extra = node.extra + ) + } + + else -> node + } + } + ) + + if (found) { + page.modified(content = newContent) + } else { + page + } + } + } +} diff --git a/renderer/src/jvmMain/kotlin/renderer3/Links.kt b/renderer/src/jvmMain/kotlin/renderer3/Links.kt index 2789baf..0100d1d 100644 --- a/renderer/src/jvmMain/kotlin/renderer3/Links.kt +++ b/renderer/src/jvmMain/kotlin/renderer3/Links.kt @@ -45,7 +45,12 @@ internal fun RenderingContext.buildDRILink(link: ContentDRILink) { val address = locations.resolve(link.address, link.sourceSets, page) if (address != null) { - buildLink(resolveInternalLink(address), isCode = true) { + buildLink( + resolveInternalLink(address), + // 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'. + isCode = link.address.packageName != ".ext", + ) { buildGroup(link) } } else { -- 2.51.2 From 45588c417cf64253afe01abe649891d03a6a0e16 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 15:25:22 +0100 Subject: [PATCH 2/6] fix(dokka-mkdocs): Fix the directory order Previously, clicking on a directory (e.g. a package) would return to the wrong page, because its index.md was listed after directories. --- .../src/main/kotlin/DokkaMkDocsPlugin.kt | 40 +++++++++++++------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/dokka-mkdocs/src/main/kotlin/DokkaMkDocsPlugin.kt b/dokka-mkdocs/src/main/kotlin/DokkaMkDocsPlugin.kt index d4c49ec..6efa534 100644 --- a/dokka-mkdocs/src/main/kotlin/DokkaMkDocsPlugin.kt +++ b/dokka-mkdocs/src/main/kotlin/DokkaMkDocsPlugin.kt @@ -137,10 +137,16 @@ abstract class DokkaMkDocsPlugin : DokkaFormatPlugin(formatName = "mkdocs") { a.isDirectory && b in a -> -1 b.isDirectory && a in b -> 1 else -> { - // \u00001 because it's the smallest invisible character, so directories will be listed first - val aPath = aR.path.split(File.separatorChar).joinToString("\u0001") { it.decodeAsDokkaUrl() } - val bPath = bR.path.split(File.separatorChar).joinToString("\u0001") { it.decodeAsDokkaUrl() } - aPath.compareTo(bPath) + // \u0000 for index.md + // \u0001 for directories + // \u0002 for files + val aParts = aR.path.split(File.separatorChar) + val bParts = bR.path.split(File.separatorChar) + + val aPath = buildComparisonName(aParts, a) + val bPath = buildComparisonName(bParts, b) + + aPath.compareTo(bPath, ignoreCase = true) } } } @@ -220,6 +226,14 @@ abstract class DokkaMkDocsPlugin : DokkaFormatPlugin(formatName = "mkdocs") { } } + private fun buildComparisonName(pathSegments: List, file: File): String = pathSegments.mapIndexed { index, s -> + when { + s == "index.md" -> "\u0000" + index < pathSegments.size - 1 || file.isDirectory -> "\u0001${s.decodeAsDokkaUrl()}" + else -> "\u0002${s.decodeAsDokkaUrl()}" + } + }.joinToString("\u0003") + @OptIn(InternalDokkaGradlePluginApi::class) private fun fixKmpCommonSourceSetClasspath(target: Project) { val kmpExtension = target.extensions.getByType(KotlinMultiplatformExtension::class.java) @@ -264,15 +278,17 @@ abstract class DokkaMkDocsPlugin : DokkaFormatPlugin(formatName = "mkdocs") { } fun String.decodeAsDokkaUrl(): String { - if (this.contains('-') && !this.startsWith("-")) { - return this.split('-').joinToString(" ") { it.replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() } } - } - var result = this - var index: Int - while (result.indexOf('-').also { index = it } >= 0) { - result = result.substring(0 until index) + result[index + 1].uppercase() + result.substring(index + 2) + val decoded = if (this.contains('-') && !this.startsWith("-")) { + this.split('-').joinToString(" ") { it.replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() } } + } else { + var result = this + var index: Int + while (result.indexOf('-').also { index = it } >= 0) { + result = result.substring(0 until index) + (result.getOrNull(index + 1)?.uppercase() ?: "") + result.substring((index + 2).coerceAtMost(result.length)) + } + result } - return result + return decoded.replaceFirstChar { it.uppercaseChar() } } operator fun File.contains(child: File): Boolean { -- 2.51.2 From 6aae5adedb5cc1a76418d19ec4e954fa5cdabc4f 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 16:26:12 +0100 Subject: [PATCH 3/6] fix(aggregator): Fix cross-module links --- .../Embedded_documentation.xml | 2 +- .../kotlin/DokkaMkDocsAggregationPlugin.kt | 2 +- .../kotlin/DokkaMkDocsTemplatingStrategy.kt | 63 ++++----------- .../src/commonMain/kotlin/Other.kt | 10 +++ .../src/commonMain/kotlin/Example.kt | 2 +- .../src/commonMain/kotlin/MyInterface.kt | 6 ++ renderer/src/jvmMain/kotlin/gfmTemplating.kt | 34 -------- .../jvmMain/kotlin/renderer3/DeferredLinks.kt | 81 +++++++++++++++++++ .../src/jvmMain/kotlin/renderer3/Links.kt | 49 +++++++---- 9 files changed, 148 insertions(+), 101 deletions(-) create mode 100644 docs/example/example-app/src/commonMain/kotlin/Other.kt create mode 100644 docs/example/example-core/src/commonMain/kotlin/MyInterface.kt delete mode 100644 renderer/src/jvmMain/kotlin/gfmTemplating.kt create mode 100644 renderer/src/jvmMain/kotlin/renderer3/DeferredLinks.kt diff --git a/.idea/runConfigurations/Embedded_documentation.xml b/.idea/runConfigurations/Embedded_documentation.xml index eb13c22..f0bc137 100644 --- a/.idea/runConfigurations/Embedded_documentation.xml +++ b/.idea/runConfigurations/Embedded_documentation.xml @@ -21,4 +21,4 @@ false - \ No newline at end of file + diff --git a/aggregator/src/jvmMain/kotlin/DokkaMkDocsAggregationPlugin.kt b/aggregator/src/jvmMain/kotlin/DokkaMkDocsAggregationPlugin.kt index 1e35ef1..f81ff37 100644 --- a/aggregator/src/jvmMain/kotlin/DokkaMkDocsAggregationPlugin.kt +++ b/aggregator/src/jvmMain/kotlin/DokkaMkDocsAggregationPlugin.kt @@ -41,7 +41,7 @@ class DokkaMkDocsAggregationPlugin : DokkaPlugin() { private val dokkaBase by lazy { plugin() } /** - * Processes GFM template commands (e.g., cross-module `ResolveLinkGfmCommand`) in generated `.md` files. + * Processes GFM template commands (e.g., cross-module link resolution) in generated `.md` files. * Must be registered before the fallback strategy. */ val mkdocsTemplatingStrategy: Extension by extending { diff --git a/aggregator/src/jvmMain/kotlin/DokkaMkDocsTemplatingStrategy.kt b/aggregator/src/jvmMain/kotlin/DokkaMkDocsTemplatingStrategy.kt index 539199a..86700f1 100644 --- a/aggregator/src/jvmMain/kotlin/DokkaMkDocsTemplatingStrategy.kt +++ b/aggregator/src/jvmMain/kotlin/DokkaMkDocsTemplatingStrategy.kt @@ -1,7 +1,3 @@ -/* - * Copyright 2014-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - /* * Copyright (c) 2025, OpenSavvy and contributors. * @@ -20,24 +16,21 @@ package opensavvy.dokka.material.mkdocs.aggregator -import opensavvy.dokka.material.mkdocs.GfmCommand -import opensavvy.dokka.material.mkdocs.GfmCommand.Companion.command -import opensavvy.dokka.material.mkdocs.GfmCommand.Companion.label -import opensavvy.dokka.material.mkdocs.GfmCommand.Companion.templateCommandRegex -import opensavvy.dokka.material.mkdocs.ResolveLinkGfmCommand +import opensavvy.dokka.material.mkdocs.renderer3.DeferredLinkCommand +import opensavvy.dokka.material.mkdocs.renderer3.DeferredLinkCommand.Companion.deferredCommandRegex +import opensavvy.dokka.material.mkdocs.renderer3.DeferredLinkCommand.Companion.deferredLinkCommand +import opensavvy.dokka.material.mkdocs.renderer3.DeferredLinkCommand.Companion.deferredLinkLabel import org.jetbrains.dokka.DokkaConfiguration import org.jetbrains.dokka.allModulesPage.AllModulesPagePlugin import org.jetbrains.dokka.base.templating.parseJson -import org.jetbrains.dokka.links.DRI import org.jetbrains.dokka.plugability.DokkaContext import org.jetbrains.dokka.plugability.plugin import org.jetbrains.dokka.plugability.querySingle import org.jetbrains.dokka.templates.TemplateProcessingStrategy -import java.io.BufferedWriter import java.io.File class DokkaMkDocsTemplatingStrategy( - val context: DokkaContext + context: DokkaContext ) : TemplateProcessingStrategy { private val externalModuleLinkResolver = @@ -45,43 +38,17 @@ class DokkaMkDocsTemplatingStrategy( override fun process(input: File, output: File, moduleContext: DokkaConfiguration.DokkaModuleDescription?): Boolean = if (input.isFile && input.extension == "md") { - input.bufferedReader().use { reader -> - //This should also work whenever we have a misconfigured dokka and output is pointing to the input - //the same way that html processing does - if (input.absolutePath == output.absolutePath) { - context.logger.info("Attempting to process MkDocs templates in place for directory $input, this suggests miss configuration.") - val lines = reader.readLines() - output.bufferedWriter().use { writer -> - lines.forEach { line -> - writer.processAndWrite(line, output) - } - } - } else { - output.bufferedWriter().use { writer -> - reader.lineSequence().forEach { line -> - writer.processAndWrite(line, output) - } - } - } + val processed = input.readText().replace(deferredCommandRegex) { match -> + val command = parseJson(match.deferredLinkCommand) + externalModuleLinkResolver.resolve(command.dri, output)?.let { address -> + command.format(address, match.deferredLinkLabel) + } ?: match.deferredLinkLabel } - true - } else false - private fun BufferedWriter.processAndWrite(line: String, output: File) = - processLine(line, output).run { - write(this) - newLine() - } - - private fun processLine(line: String, output: File): String = - line.replace(templateCommandRegex) { - when (val command = parseJson(it.command)) { - is ResolveLinkGfmCommand -> resolveLink(output, command.dri, it.label) - } + output.parentFile?.mkdirs() + output.writeText(processed) + true + } else { + false } - - private fun resolveLink(fileContext: File, dri: DRI, label: String): String = - externalModuleLinkResolver.resolve(dri, fileContext)?.let { address -> - "[$label]($address)" - } ?: label } diff --git a/docs/example/example-app/src/commonMain/kotlin/Other.kt b/docs/example/example-app/src/commonMain/kotlin/Other.kt new file mode 100644 index 0000000..28e8d91 --- /dev/null +++ b/docs/example/example-app/src/commonMain/kotlin/Other.kt @@ -0,0 +1,10 @@ +package opensavvy.dokka.material.mkdocs.example + +/** + * This method prints an instance of the class [Example], an implementation of the interface [MyInterface]. + * + * It also links to a random [internet address](https://google.com). + */ +fun println(example: Example) { + println("example: $example") +} diff --git a/docs/example/example-core/src/commonMain/kotlin/Example.kt b/docs/example/example-core/src/commonMain/kotlin/Example.kt index 660c918..d02ffa0 100644 --- a/docs/example/example-core/src/commonMain/kotlin/Example.kt +++ b/docs/example/example-core/src/commonMain/kotlin/Example.kt @@ -20,7 +20,7 @@ package opensavvy.dokka.material.mkdocs.example * * @constructor Public constructor for the [Example] class. */ -class Example() { +class Example() : MyInterface { /** * A value that increments by one each time it is accessed. diff --git a/docs/example/example-core/src/commonMain/kotlin/MyInterface.kt b/docs/example/example-core/src/commonMain/kotlin/MyInterface.kt new file mode 100644 index 0000000..0063397 --- /dev/null +++ b/docs/example/example-core/src/commonMain/kotlin/MyInterface.kt @@ -0,0 +1,6 @@ +package opensavvy.dokka.material.mkdocs.example + +/** + * Simple interface. + */ +interface MyInterface diff --git a/renderer/src/jvmMain/kotlin/gfmTemplating.kt b/renderer/src/jvmMain/kotlin/gfmTemplating.kt deleted file mode 100644 index bc27dcf..0000000 --- a/renderer/src/jvmMain/kotlin/gfmTemplating.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2014-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package opensavvy.dokka.material.mkdocs - -import org.jetbrains.dokka.base.templating.toJsonString -import org.jetbrains.dokka.links.DRI - -public sealed class GfmCommand { - - public companion object { - private const val delimiter = "\u1680" - - public val templateCommandRegex: Regex = - Regex("(.+?)(?=") - - public val MatchResult.command: String - get() = groupValues[1] - - public val MatchResult.label: String - get() = groupValues[2] - - public fun Appendable.templateCommand(command: GfmCommand, content: Appendable.() -> Unit) { - append("") - content() - append("") - } - } -} - -public class ResolveLinkGfmCommand( - public val dri: DRI -) : GfmCommand() diff --git a/renderer/src/jvmMain/kotlin/renderer3/DeferredLinks.kt b/renderer/src/jvmMain/kotlin/renderer3/DeferredLinks.kt new file mode 100644 index 0000000..bbd44cc --- /dev/null +++ b/renderer/src/jvmMain/kotlin/renderer3/DeferredLinks.kt @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2025, OpenSavvy and contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package opensavvy.dokka.material.mkdocs.renderer3 + +import org.jetbrains.dokka.base.templating.toJsonString +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.pages.ContentDRILink + +private const val deferredLinkCommandDelimiter = "\u1680" + +/** + * The current module doesn't know how to resolve this link. + * + * For example, because this is a link to a class defined in another module. + * + * The current module serializes a special marker in the documentation, + * and the aggregator plugin will re-attempt to resolve it later. + */ +class DeferredLinkCommand( + val dri: DRI, + /** + * The value of [RenderingContext.isInCodeBlock] at the place where the link is inserted. + */ + val isInCodeBlock: Boolean, + /** + * Whether the link should be displayed as Markdown inline code. + */ + val isCode: Boolean, +) { + + fun format(address: String, renderedLabel: String): String = buildString { + buildLink( + address = address, + isInCodeBlock = isInCodeBlock, + isCode = isCode, + ) { + append(renderedLabel) + } + } + + companion object { + val deferredCommandRegex: Regex = + Regex("(.+?)(?=") + + val MatchResult.deferredLinkCommand: String + get() = groupValues[1] + + val MatchResult.deferredLinkLabel: String + get() = groupValues[2] + } +} + +internal fun RenderingContext.buildDeferredLink( + link: ContentDRILink, + isCode: Boolean, + content: RenderingContext.() -> Unit, +) { + val command = DeferredLinkCommand( + dri = link.address, + isInCodeBlock = this.isInCodeBlock, + isCode = isCode, + ) + + append("") + content() + append("") +} diff --git a/renderer/src/jvmMain/kotlin/renderer3/Links.kt b/renderer/src/jvmMain/kotlin/renderer3/Links.kt index 0100d1d..c7f62d5 100644 --- a/renderer/src/jvmMain/kotlin/renderer3/Links.kt +++ b/renderer/src/jvmMain/kotlin/renderer3/Links.kt @@ -18,16 +18,21 @@ package opensavvy.dokka.material.mkdocs.renderer3 import org.jetbrains.dokka.pages.ContentDRILink import org.jetbrains.dokka.pages.ContentResolvedLink -import org.jetbrains.dokka.pages.PageNode -internal fun RenderingContext.buildLink(to: PageNode, from: PageNode) = - buildLink(resolveInternalLink(locations.resolve(to, from)!!)) { - append(to.name) - } - -internal fun RenderingContext.buildLink(address: String, isCode: Boolean = false, label: RenderingContext.() -> Unit) { +internal fun Appendable.buildLink( + address: String, + isInCodeBlock: Boolean, + isCode: Boolean, + label: Appendable.() -> Unit, +) { if (isInCodeBlock) { - append("") + // mkdocs cannot replace links within code blocks, so we do the .md -> .html substitution directly. + val actualAddress = if (address.endsWith(".md")) + address.removeSuffix(".md") + ".html" + else + address + + append("") label() append("") } else { @@ -42,24 +47,36 @@ internal fun RenderingContext.buildLink(address: String, isCode: Boolean = false } internal fun RenderingContext.buildDRILink(link: ContentDRILink) { - val address = locations.resolve(link.address, link.sourceSets, page) + val resolvedAddress = locations.resolve(link.address, link.sourceSets, page) + + // 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'. + val isCode = link.address.packageName != ".ext" - if (address != null) { + if (resolvedAddress != null) { buildLink( - resolveInternalLink(address), - // 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'. - isCode = link.address.packageName != ".ext", + resolveInternalLink(resolvedAddress), + isInCodeBlock = this.isInCodeBlock, + isCode = isCode, ) { buildGroup(link) } } else { - buildGroup(link) + buildDeferredLink( + link = link, + isCode = isCode, + ) { + buildGroup(link) + } } } internal fun RenderingContext.buildResolvedLink(link: ContentResolvedLink) { - buildLink(link.address) { + buildLink( + link.address, + isInCodeBlock = this.isInCodeBlock, + isCode = false, + ) { buildGroup(link) } } -- 2.51.2 From bc30a010e7285c11c04d5eb8dde64a41ea9b3e20 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 16:32:13 +0100 Subject: [PATCH 4/6] feat(dokka-mkdocs): Display the module's technical name in the navigation bar and breadcrumbs Closes #31 --- dokka-mkdocs/src/main/kotlin/DokkaMkDocsPlugin.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/dokka-mkdocs/src/main/kotlin/DokkaMkDocsPlugin.kt b/dokka-mkdocs/src/main/kotlin/DokkaMkDocsPlugin.kt index 6efa534..d4ae458 100644 --- a/dokka-mkdocs/src/main/kotlin/DokkaMkDocsPlugin.kt +++ b/dokka-mkdocs/src/main/kotlin/DokkaMkDocsPlugin.kt @@ -161,7 +161,12 @@ abstract class DokkaMkDocsPlugin : DokkaFormatPlugin(formatName = "mkdocs") { } file.isDirectory -> { - builder.appendLine("$indent- \"${relative.name.decodeAsDokkaUrl()}\":") + if ('/' !in relative.path) { + // Module name: keep the technical name + builder.appendLine("$indent- \"${relative.name}\":") + } else { + builder.appendLine("$indent- \"${relative.name.decodeAsDokkaUrl()}\":") + } } file.isFile && file.name.endsWith(".md") -> { -- 2.51.2 From ce5c323525a861c329e5f99004f10f670e88e1f8 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 16:42:25 +0100 Subject: [PATCH 5/6] feat(renderer): Rename the package pages by the package's technical name Closes #31 --- .../jvmMain/kotlin/MaterialForMkDocsPlugin.kt | 5 ++ .../renderer/PackageTitleTransformer.kt | 53 +++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 renderer/src/jvmMain/kotlin/renderer/PackageTitleTransformer.kt diff --git a/renderer/src/jvmMain/kotlin/MaterialForMkDocsPlugin.kt b/renderer/src/jvmMain/kotlin/MaterialForMkDocsPlugin.kt index 18815b1..2b13991 100644 --- a/renderer/src/jvmMain/kotlin/MaterialForMkDocsPlugin.kt +++ b/renderer/src/jvmMain/kotlin/MaterialForMkDocsPlugin.kt @@ -18,6 +18,7 @@ package opensavvy.dokka.material.mkdocs import opensavvy.dokka.material.mkdocs.location.MarkdownLocationProvider import opensavvy.dokka.material.mkdocs.renderer.BriefCommentPreprocessor +import opensavvy.dokka.material.mkdocs.renderer.PackageTitleTransformer import opensavvy.dokka.material.mkdocs.renderer.SignatureBreaklineTransformer import opensavvy.dokka.material.mkdocs.renderer3.MkDocsRenderer3 import org.jetbrains.dokka.CoreExtensions @@ -57,6 +58,10 @@ class MaterialForMkDocsPlugin : DokkaPlugin() { mkdocsPreprocessor with SignatureBreaklineTransformer() } + val packageTitleTransformer: Extension by extending { + mkdocsPreprocessor with PackageTitleTransformer() order { after(rootCreator) } + } + val packageListCreator: Extension by extending { (mkdocsPreprocessor providing { PackageListCreator(it, RecognizedLinkFormat.DokkaGFM) } diff --git a/renderer/src/jvmMain/kotlin/renderer/PackageTitleTransformer.kt b/renderer/src/jvmMain/kotlin/renderer/PackageTitleTransformer.kt new file mode 100644 index 0000000..f591e00 --- /dev/null +++ b/renderer/src/jvmMain/kotlin/renderer/PackageTitleTransformer.kt @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2025, OpenSavvy and contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package opensavvy.dokka.material.mkdocs.renderer + +import org.jetbrains.dokka.pages.* +import org.jetbrains.dokka.transformers.pages.PageTransformer + +/** + * Renames the package's page title from 'Package-level declarations' to the package's technical name. + */ +class PackageTitleTransformer : PageTransformer { + override fun invoke(input: RootPageNode): RootPageNode { + return input.transformContentPagesTree { page -> + if (page is PackagePageNode) { + page.modified( + content = page.content.recursiveMapTransform { header -> + if (header.level == 1) { + header.copy( + children = listOf( + ContentText( + text = page.name, + dci = header.dci, + sourceSets = header.sourceSets, + style = header.style, + extra = header.extra + ) + ) + ) + } else { + header + } + } + ) + } else { + page + } + } + } +} -- 2.51.2 From d74c94663d79e6f4d6ea1528b83411e5d31da92d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?= Date: Wed, 18 Mar 2026 00:06:08 +0100 Subject: [PATCH 6/6] ci(gitlab): OpenSavvy Containers 0.8.7 --- .gitlab-ci.main.kts | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/.gitlab-ci.main.kts b/.gitlab-ci.main.kts index 01dcade..f48ddeb 100755 --- a/.gitlab-ci.main.kts +++ b/.gitlab-ci.main.kts @@ -12,7 +12,7 @@ import opensavvy.gitlab.ci.script.shell /** * [OpenSavvy's CI container images](https://gitlab.com/opensavvy/automation/containers/-/releases) */ -val ciContainers = "0.8.5" +val ciContainers = "0.8.7" /** * The URL of the website built by /docs/website. @@ -197,11 +197,6 @@ gitlabCi { val documentKtMongo by job(stage = build) { opensavvyImage("mkdocs") - // TODO: remove after https://gitlab.com/opensavvy/automation/containers/-/merge_requests/33 - beforeScript { - shell("pacman -Syuu --noconfirm jdk-openjdk jdk17-openjdk") - } - script { shell("git clone --depth=1 https://gitlab.com/opensavvy/ktmongo.git /tmp/ktmongo") shell("cd /tmp/ktmongo") @@ -254,11 +249,6 @@ gitlabCi { opensavvyImage("mkdocs") variable("GIT_DEPTH", "0") - // TODO: remove after https://gitlab.com/opensavvy/automation/containers/-/merge_requests/33 - beforeScript { - shell("pacman -Syuu --noconfirm jdk-openjdk") - } - beforeScript { shell("./docs/website/verify-marker.sh") shell($$"./gradlew -p docs :website:embedDokkaIntoMkDocs -PappVersion=$project_version")