aboutsummaryrefslogtreecommitdiff
path: root/plugins/base/src/main/kotlin/renderers
diff options
context:
space:
mode:
authorIgnat Beresnev <ignat.beresnev@jetbrains.com>2022-07-29 14:32:24 +0200
committerGitHub <noreply@github.com>2022-07-29 14:32:24 +0200
commit7a875ee7d20b67725debd4c2c9e1f93e1889c302 (patch)
tree075210f83e5e5a7679194ba8c88dc426dead0777 /plugins/base/src/main/kotlin/renderers
parent26dde5b201b3c7e66212b07ddef333a3e340022a (diff)
downloaddokka-7a875ee7d20b67725debd4c2c9e1f93e1889c302.tar.gz
dokka-7a875ee7d20b67725debd4c2c9e1f93e1889c302.tar.bz2
dokka-7a875ee7d20b67725debd4c2c9e1f93e1889c302.zip
Add member icons to navigation menu (#2578)
Diffstat (limited to 'plugins/base/src/main/kotlin/renderers')
-rw-r--r--plugins/base/src/main/kotlin/renderers/html/NavigationDataProvider.kt90
-rw-r--r--plugins/base/src/main/kotlin/renderers/html/NavigationPage.kt63
-rw-r--r--plugins/base/src/main/kotlin/renderers/html/htmlPreprocessors.kt67
3 files changed, 166 insertions, 54 deletions
diff --git a/plugins/base/src/main/kotlin/renderers/html/NavigationDataProvider.kt b/plugins/base/src/main/kotlin/renderers/html/NavigationDataProvider.kt
new file mode 100644
index 00000000..647ba687
--- /dev/null
+++ b/plugins/base/src/main/kotlin/renderers/html/NavigationDataProvider.kt
@@ -0,0 +1,90 @@
+package org.jetbrains.dokka.base.renderers.html
+
+import org.jetbrains.dokka.base.renderers.sourceSets
+import org.jetbrains.dokka.base.transformers.documentables.isException
+import org.jetbrains.dokka.base.translators.documentables.DocumentableLanguage
+import org.jetbrains.dokka.base.translators.documentables.documentableLanguage
+import org.jetbrains.dokka.model.*
+import org.jetbrains.dokka.pages.*
+
+abstract class NavigationDataProvider {
+ open fun navigableChildren(input: RootPageNode): NavigationNode = input.withDescendants()
+ .first { it is ModulePage || it is MultimoduleRootPage }.let { visit(it as ContentPage) }
+
+ open fun visit(page: ContentPage): NavigationNode =
+ NavigationNode(
+ name = page.displayableName(),
+ dri = page.dri.first(),
+ sourceSets = page.sourceSets(),
+ icon = chooseNavigationIcon(page),
+ children = page.navigableChildren()
+ )
+
+ /**
+ * Parenthesis is applied in 1 case:
+ * - page only contains functions (therefore documentable from this page is [DFunction])
+ */
+ private fun ContentPage.displayableName(): String =
+ if (this is WithDocumentables && documentables.all { it is DFunction }) {
+ "$name()"
+ } else {
+ name
+ }
+
+ private fun chooseNavigationIcon(contentPage: ContentPage): NavigationNodeIcon? {
+ return if (contentPage is WithDocumentables) {
+ val documentable = contentPage.documentables.firstOrNull()
+ val isJava = documentable?.hasAnyJavaSources() ?: false
+
+ when (documentable) {
+ is DClass -> when {
+ documentable.isException -> NavigationNodeIcon.EXCEPTION
+ documentable.isAbstract() -> {
+ if (isJava) NavigationNodeIcon.ABSTRACT_CLASS else NavigationNodeIcon.ABSTRACT_CLASS_KT
+ }
+ else -> if (isJava) NavigationNodeIcon.CLASS else NavigationNodeIcon.CLASS_KT
+ }
+ is DFunction -> NavigationNodeIcon.FUNCTION
+ is DProperty -> {
+ val isVar = documentable.extra[IsVar] != null
+ if (isVar) NavigationNodeIcon.VAR else NavigationNodeIcon.VAL
+ }
+ is DInterface -> if (isJava) NavigationNodeIcon.INTERFACE else NavigationNodeIcon.INTERFACE_KT
+ is DEnum,
+ is DEnumEntry -> if (isJava) NavigationNodeIcon.ENUM_CLASS else NavigationNodeIcon.ENUM_CLASS_KT
+ is DAnnotation -> {
+ if (isJava) NavigationNodeIcon.ANNOTATION_CLASS else NavigationNodeIcon.ANNOTATION_CLASS_KT
+ }
+ is DObject -> NavigationNodeIcon.OBJECT
+ else -> null
+ }
+ } else {
+ null
+ }
+ }
+
+ private fun Documentable.hasAnyJavaSources(): Boolean {
+ val withSources = this as? WithSources ?: return false
+ return this.sourceSets.any { withSources.documentableLanguage(it) == DocumentableLanguage.JAVA }
+ }
+
+ private fun DClass.isAbstract(): Boolean {
+ return modifier.values.all { it is KotlinModifier.Abstract || it is JavaModifier.Abstract }
+ }
+
+ private fun ContentPage.navigableChildren(): List<NavigationNode> {
+ return if (this !is ClasslikePageNode) {
+ children
+ .filterIsInstance<ContentPage>()
+ .map { visit(it) }
+ .sortedBy { it.name.toLowerCase() }
+ } else if (documentables.any { it is DEnum }) {
+ // no sorting for enum entries, should be the same as in source code
+ children
+ .filter { child -> child is WithDocumentables && child.documentables.any { it is DEnumEntry } }
+ .map { visit(it as ContentPage) }
+ } else {
+ emptyList()
+ }
+ }
+}
diff --git a/plugins/base/src/main/kotlin/renderers/html/NavigationPage.kt b/plugins/base/src/main/kotlin/renderers/html/NavigationPage.kt
index e0b20f01..e5183699 100644
--- a/plugins/base/src/main/kotlin/renderers/html/NavigationPage.kt
+++ b/plugins/base/src/main/kotlin/renderers/html/NavigationPage.kt
@@ -7,13 +7,15 @@ import org.jetbrains.dokka.base.templating.AddToNavigationCommand
import org.jetbrains.dokka.links.DRI
import org.jetbrains.dokka.model.DisplaySourceSet
import org.jetbrains.dokka.model.WithChildren
-import org.jetbrains.dokka.pages.PageNode
-import org.jetbrains.dokka.pages.RendererSpecificPage
-import org.jetbrains.dokka.pages.RenderingStrategy
+import org.jetbrains.dokka.pages.*
import org.jetbrains.dokka.plugability.DokkaContext
-class NavigationPage(val root: NavigationNode, val moduleName: String, val context: DokkaContext) :
- RendererSpecificPage {
+class NavigationPage(
+ val root: NavigationNode,
+ val moduleName: String,
+ val context: DokkaContext
+) : RendererSpecificPage {
+
override val name = "navigation"
override val children = emptyList<PageNode>()
@@ -46,22 +48,69 @@ class NavigationPage(val root: NavigationNode, val moduleName: String, val conte
span("navButtonContent")
}
}
- buildLink(node.dri, node.sourceSets.toList()) { buildBreakableText(node.name) }
+ buildLink(node.dri, node.sourceSets.toList()) {
+ // special condition for Enums as it has children enum entries in navigation
+ val withIcon = node.icon != null && (node.children.isEmpty() || node.isEnum())
+ if (withIcon) {
+ // in case link text is so long that it needs to have word breaks,
+ // and it stretches to two or more lines, make sure the icon
+ // is always on the left in the grid and is not wrapped with text
+ span("nav-link-grid") {
+ span("nav-link-child ${node.icon?.style()}")
+ span("nav-link-child") {
+ buildBreakableText(node.name)
+ }
+ }
+ } else {
+ buildBreakableText(node.name)
+ }
+ }
}
node.children.withIndex().forEach { (n, p) -> visit(p, "$navId-$n", renderer) }
}
}
+
+ private fun NavigationNode.isEnum(): Boolean {
+ return icon == NavigationNodeIcon.ENUM_CLASS || icon == NavigationNodeIcon.ENUM_CLASS_KT
+ }
}
data class NavigationNode(
val name: String,
val dri: DRI,
val sourceSets: Set<DisplaySourceSet>,
+ val icon: NavigationNodeIcon?,
override val children: List<NavigationNode>
) : WithChildren<NavigationNode>
+/**
+ * [CLASS] represents a neutral (a.k.a Java-style) icon,
+ * whereas [CLASS_KT] should be Kotlin-styled
+ */
+enum class NavigationNodeIcon(
+ private val cssClass: String
+) {
+ CLASS("class"),
+ CLASS_KT("class-kt"),
+ ABSTRACT_CLASS("abstract-class"),
+ ABSTRACT_CLASS_KT("abstract-class-kt"),
+ ENUM_CLASS("enum-class"),
+ ENUM_CLASS_KT("enum-class-kt"),
+ ANNOTATION_CLASS("annotation-class"),
+ ANNOTATION_CLASS_KT("annotation-class-kt"),
+ INTERFACE("interface"),
+ INTERFACE_KT("interface-kt"),
+ FUNCTION("function"),
+ EXCEPTION("exception-class"),
+ OBJECT("object"),
+ VAL("val"),
+ VAR("var");
+
+ internal fun style(): String = "nav-icon $cssClass"
+}
+
fun NavigationPage.transform(block: (NavigationNode) -> NavigationNode) =
NavigationPage(root.transform(block), moduleName, context)
fun NavigationNode.transform(block: (NavigationNode) -> NavigationNode) =
- run(block).let { NavigationNode(it.name, it.dri, it.sourceSets, it.children.map(block)) }
+ run(block).let { NavigationNode(it.name, it.dri, it.sourceSets, it.icon, it.children.map(block)) }
diff --git a/plugins/base/src/main/kotlin/renderers/html/htmlPreprocessors.kt b/plugins/base/src/main/kotlin/renderers/html/htmlPreprocessors.kt
index 4527baa7..2de6f2b7 100644
--- a/plugins/base/src/main/kotlin/renderers/html/htmlPreprocessors.kt
+++ b/plugins/base/src/main/kotlin/renderers/html/htmlPreprocessors.kt
@@ -2,60 +2,16 @@ package org.jetbrains.dokka.base.renderers.html
import org.jetbrains.dokka.base.DokkaBase
import org.jetbrains.dokka.base.DokkaBaseConfiguration
-import org.jetbrains.dokka.base.renderers.sourceSets
import org.jetbrains.dokka.base.templating.AddToSourcesetDependencies
import org.jetbrains.dokka.base.templating.toJsonString
-import org.jetbrains.dokka.model.DEnum
-import org.jetbrains.dokka.model.DEnumEntry
-import org.jetbrains.dokka.model.DFunction
-import org.jetbrains.dokka.model.withDescendants
-import org.jetbrains.dokka.pages.*
+import org.jetbrains.dokka.pages.RendererSpecificResourcePage
+import org.jetbrains.dokka.pages.RenderingStrategy
+import org.jetbrains.dokka.pages.RootPageNode
import org.jetbrains.dokka.plugability.DokkaContext
import org.jetbrains.dokka.plugability.configuration
import org.jetbrains.dokka.transformers.pages.PageTransformer
-abstract class NavigationDataProvider {
- open fun navigableChildren(input: RootPageNode): NavigationNode = input.withDescendants()
- .first { it is ModulePage || it is MultimoduleRootPage }.let { visit(it as ContentPage) }
-
- open fun visit(page: ContentPage): NavigationNode =
- NavigationNode(
- name = page.displayableName,
- dri = page.dri.first(),
- sourceSets = page.sourceSets(),
- children = page.navigableChildren()
- )
-
- private fun ContentPage.navigableChildren(): List<NavigationNode> {
- return if (this !is ClasslikePageNode) {
- children
- .filterIsInstance<ContentPage>()
- .map { visit(it) }
- .sortedBy { it.name.toLowerCase() }
- } else if (documentables.any { it is DEnum }) {
- // no sorting for enum entries, should be the same as in source code
- children
- .filter { child -> child is WithDocumentables && child.documentables.any { it is DEnumEntry } }
- .map { visit(it as ContentPage) }
- } else {
- emptyList()
- }
- }
-
- /**
- * Parenthesis is applied in 1 case:
- * - page only contains functions (therefore documentable from this page is [DFunction])
- */
- private val ContentPage.displayableName: String
- get() = if (this is WithDocumentables && documentables.all { it is DFunction }) {
- "$name()"
- } else {
- name
- }
-}
-
open class NavigationPageInstaller(val context: DokkaContext) : NavigationDataProvider(), PageTransformer {
-
override fun invoke(input: RootPageNode): RootPageNode =
input.modified(
children = input.children + NavigationPage(
@@ -138,6 +94,23 @@ object AssetsInstaller : PageTransformer {
"images/copy-icon.svg",
"images/copy-successful-icon.svg",
"images/theme-toggle.svg",
+
+ // navigation icons
+ "images/nav-icons/abstract-class.svg",
+ "images/nav-icons/abstract-class-kotlin.svg",
+ "images/nav-icons/annotation.svg",
+ "images/nav-icons/annotation-kotlin.svg",
+ "images/nav-icons/class.svg",
+ "images/nav-icons/class-kotlin.svg",
+ "images/nav-icons/enum.svg",
+ "images/nav-icons/enum-kotlin.svg",
+ "images/nav-icons/exception-class.svg",
+ "images/nav-icons/field-value.svg",
+ "images/nav-icons/field-variable.svg",
+ "images/nav-icons/function.svg",
+ "images/nav-icons/interface.svg",
+ "images/nav-icons/interface-kotlin.svg",
+ "images/nav-icons/object.svg",
)
override fun invoke(input: RootPageNode) = input.modified(