diff options
author | Ignat Beresnev <ignat.beresnev@jetbrains.com> | 2022-07-29 14:32:24 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-07-29 14:32:24 +0200 |
commit | 7a875ee7d20b67725debd4c2c9e1f93e1889c302 (patch) | |
tree | 075210f83e5e5a7679194ba8c88dc426dead0777 /plugins/base/src/main/kotlin/renderers | |
parent | 26dde5b201b3c7e66212b07ddef333a3e340022a (diff) | |
download | dokka-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')
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( |