aboutsummaryrefslogtreecommitdiff
path: root/plugins/base/src/main
diff options
context:
space:
mode:
authorVadim Mishenev <vad-mishenev@yandex.ru>2023-02-24 17:44:24 +0200
committerGitHub <noreply@github.com>2023-02-24 17:44:24 +0200
commit1040288ca76e070445f1400df2fcc5a56310be28 (patch)
tree52bed40e8a320f0835540b0cd38ea1899800e395 /plugins/base/src/main
parent8d23340d1c377b8f490cdee3c2c874453d321dd8 (diff)
downloaddokka-1040288ca76e070445f1400df2fcc5a56310be28.tar.gz
dokka-1040288ca76e070445f1400df2fcc5a56310be28.tar.bz2
dokka-1040288ca76e070445f1400df2fcc5a56310be28.zip
Reorganize tabs for Classlike (#2764)
Diffstat (limited to 'plugins/base/src/main')
-rw-r--r--plugins/base/src/main/kotlin/DokkaBase.kt5
-rw-r--r--plugins/base/src/main/kotlin/renderers/DefaultTabSortingStrategy.kt30
-rw-r--r--plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt160
-rw-r--r--plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt405
-rw-r--r--plugins/base/src/main/kotlin/translators/documentables/DescriptionSections.kt2
-rw-r--r--plugins/base/src/main/kotlin/translators/documentables/PageContentBuilder.kt52
-rw-r--r--plugins/base/src/main/resources/dokka/scripts/platform-content-handler.js52
-rw-r--r--plugins/base/src/main/resources/dokka/styles/style.css10
8 files changed, 462 insertions, 254 deletions
diff --git a/plugins/base/src/main/kotlin/DokkaBase.kt b/plugins/base/src/main/kotlin/DokkaBase.kt
index 456a587b..f76eef63 100644
--- a/plugins/base/src/main/kotlin/DokkaBase.kt
+++ b/plugins/base/src/main/kotlin/DokkaBase.kt
@@ -54,6 +54,7 @@ class DokkaBase : DokkaPlugin() {
val outputWriter by extensionPoint<OutputWriter>()
val htmlPreprocessors by extensionPoint<PageTransformer>()
val kotlinAnalysis by extensionPoint<KotlinAnalysis>()
+ @Deprecated("It is not used anymore")
val tabSortingStrategy by extensionPoint<TabSortingStrategy>()
val immediateHtmlCommandConsumer by extensionPoint<ImmediateHtmlCommandConsumer>()
val externalDocumentablesProvider by extensionPoint<ExternalDocumentablesProvider>()
@@ -182,10 +183,6 @@ class DokkaBase : DokkaPlugin() {
}
}
- val defaultTabSortingStrategy by extending {
- tabSortingStrategy with DefaultTabSortingStrategy()
- }
-
val htmlRenderer by extending {
CoreExtensions.renderer providing ::HtmlRenderer
}
diff --git a/plugins/base/src/main/kotlin/renderers/DefaultTabSortingStrategy.kt b/plugins/base/src/main/kotlin/renderers/DefaultTabSortingStrategy.kt
deleted file mode 100644
index 056e0f93..00000000
--- a/plugins/base/src/main/kotlin/renderers/DefaultTabSortingStrategy.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-package org.jetbrains.dokka.base.renderers
-
-import org.jetbrains.dokka.pages.ContentKind
-import org.jetbrains.dokka.pages.ContentNode
-import org.jetbrains.dokka.pages.Kind
-
-private val kindsOrder = listOf(
- ContentKind.Classlikes,
- ContentKind.Constructors,
- ContentKind.Functions,
- ContentKind.Properties,
- ContentKind.Extensions,
- ContentKind.Parameters,
- ContentKind.Inheritors,
- ContentKind.Source,
- ContentKind.Sample,
- ContentKind.Comment
-)
-
-class DefaultTabSortingStrategy : TabSortingStrategy {
- override fun <T: ContentNode> sort(tabs: Collection<T>): List<T> {
- val tabMap: Map<Kind, MutableList<T>> = kindsOrder.asSequence().map { it to mutableListOf<T>() }.toMap()
- val unrecognized: MutableList<T> = mutableListOf()
- tabs.forEach {
- tabMap[it.dci.kind]?.add(it) ?: unrecognized.add(it)
- }
- return tabMap.values.flatten() + unrecognized
- }
-
-}
diff --git a/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt b/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt
index b4d98b58..dfefbad8 100644
--- a/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt
+++ b/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt
@@ -14,9 +14,11 @@ import org.jetbrains.dokka.base.renderers.html.innerTemplating.HtmlTemplater
import org.jetbrains.dokka.base.resolvers.anchors.SymbolAnchorHint
import org.jetbrains.dokka.base.resolvers.local.DokkaBaseLocationProvider
import org.jetbrains.dokka.base.templating.*
+import org.jetbrains.dokka.base.transformers.documentables.CallableExtensions
import org.jetbrains.dokka.links.DRI
import org.jetbrains.dokka.model.*
import org.jetbrains.dokka.model.properties.PropertyContainer
+import org.jetbrains.dokka.model.properties.WithExtraProperties
import org.jetbrains.dokka.pages.*
import org.jetbrains.dokka.pages.HtmlContent
import org.jetbrains.dokka.plugability.*
@@ -24,6 +26,7 @@ import org.jetbrains.dokka.utilities.htmlEscape
import org.jetbrains.kotlin.utils.addIfNotNull
internal const val TEMPLATE_REPLACEMENT: String = "###"
+internal const val TOGGLEABLE_CONTENT_TYPE_ATTR = "data-togglable"
open class HtmlRenderer(
context: DokkaContext
@@ -45,19 +48,102 @@ open class HtmlRenderer(
override val preprocessors = context.plugin<DokkaBase>().query { htmlPreprocessors }
- private val tabSortingStrategy = context.plugin<DokkaBase>().querySingle { tabSortingStrategy }
+ /**
+ * Tabs themselves are created in HTML plugin since, currently, only HTML format supports them.
+ * [TabbedContentType] is used to mark content that should be inside tab content.
+ * A tab can display multiple [TabbedContentType].
+ * The content style [ContentStyle.TabbedContent] is used to determine where tabs will be generated.
+ *
+ * @see TabbedContentType
+ * @see ContentStyle.TabbedContent
+ */
+ private fun createTabs(pageContext: ContentPage): List<ContentTab> {
+ return when(pageContext) {
+ is ClasslikePage -> createTabsForClasslikes(pageContext)
+ is PackagePage -> createTabsForPackage(pageContext)
+ else -> throw IllegalArgumentException("Page ${pageContext.name} cannot have tabs")
+ }
+ }
+
+ private fun createTabsForClasslikes(page: ClasslikePage): List<ContentTab> {
+ val documentables = page.documentables
+ fun List<Documentable>.shouldDocumentConstructors() = !this.any { it is DAnnotation }
+ val csEnum = documentables.filterIsInstance<DEnum>()
+ val csWithConstructor = documentables.filterIsInstance<WithConstructors>()
+ val scopes = documentables.filterIsInstance<WithScope>()
+ val constructorsToDocumented = csWithConstructor.flatMap { it.constructors }
+
+ val containsRenderableConstructors = constructorsToDocumented.isNotEmpty() && documentables.shouldDocumentConstructors()
+ val containsRenderableMembers =
+ containsRenderableConstructors || scopes.any { it.classlikes.isNotEmpty() || it.functions.isNotEmpty() || it.properties.isNotEmpty() }
+
+ @Suppress("UNCHECKED_CAST")
+ val extensions = (documentables as List<WithExtraProperties<DClasslike>>).flatMap {
+ it.extra[CallableExtensions]?.extensions
+ ?.filterIsInstance<Documentable>().orEmpty()
+ }
+ .distinctBy { it.sourceSets to it.dri } // [Documentable] has expensive equals/hashCode at the moment, see #2620
+ return listOfNotNull(
+ if(!containsRenderableMembers) null else
+ ContentTab(
+ "Members",
+ listOf(
+ BasicTabbedContentType.CONSTRUCTOR,
+ BasicTabbedContentType.TYPE,
+ BasicTabbedContentType.FUNCTION,
+ BasicTabbedContentType.PROPERTY
+ )
+ ),
+ if (extensions.isEmpty()) null else ContentTab(
+ "Members & Extensions",
+ listOf(
+ BasicTabbedContentType.CONSTRUCTOR,
+ BasicTabbedContentType.TYPE,
+ BasicTabbedContentType.FUNCTION,
+ BasicTabbedContentType.PROPERTY,
+ BasicTabbedContentType.EXTENSION_PROPERTY,
+ BasicTabbedContentType.EXTENSION_FUNCTION
+ )
+ ),
+ if(csEnum.isEmpty()) null else ContentTab(
+ "Entries",
+ listOf(
+ BasicTabbedContentType.ENTRY
+ )
+ )
+ )
+ }
+
+ private fun createTabsForPackage(page: PackagePage): List<ContentTab> {
+ val p = page.documentables.single() as DPackage
+ return listOfNotNull(
+ ContentTab(
+ "Types",
+ listOf(
+ BasicTabbedContentType.TYPE,
+ )
+ ),
+ if (p.functions.isEmpty()) null else ContentTab(
+ "Functions",
+ listOf(
+ BasicTabbedContentType.FUNCTION,
+ BasicTabbedContentType.EXTENSION_FUNCTION,
+ )
+ ),
+ if (p.properties.isEmpty()) null else ContentTab(
+ "Properties",
+ listOf(
+ BasicTabbedContentType.PROPERTY,
+ BasicTabbedContentType.EXTENSION_PROPERTY,
+ )
+ )
+ )
+ }
private fun <R> TagConsumer<R>.prepareForTemplates() =
if (context.configuration.delayTemplateSubstitution || this is ImmediateResolutionTagConsumer) this
else ImmediateResolutionTagConsumer(this, context)
- private fun <T : ContentNode> sortTabs(strategy: TabSortingStrategy, tabs: Collection<T>): List<T> {
- val sorted = strategy.sort(tabs)
- if (sorted.size != tabs.size)
- context.logger.warn("Tab sorting strategy has changed number of tabs from ${tabs.size} to ${sorted.size}")
- return sorted
- }
-
override fun FlowContent.wrapGroup(
node: ContentGroup,
pageContext: ContentPage,
@@ -66,20 +152,16 @@ open class HtmlRenderer(
val additionalClasses = node.style.joinToString(" ") { it.toString().toLowerCase() }
return when {
node.hasStyle(ContentStyle.TabbedContent) -> div(additionalClasses) {
- val secondLevel = node.children.filterIsInstance<ContentComposite>().flatMap { it.children }
- .filterIsInstance<ContentHeader>().flatMap { it.children }.filterIsInstance<ContentText>()
- val firstLevel = node.children.filterIsInstance<ContentHeader>().flatMap { it.children }
- .filterIsInstance<ContentText>()
-
- val renderable = sortTabs(tabSortingStrategy, firstLevel.union(secondLevel))
+ val contentTabs = createTabs(pageContext)
div(classes = "tabs-section") {
attributes["tabs-section"] = "tabs-section"
- renderable.forEachIndexed { index, node ->
+ contentTabs.forEachIndexed { index, contentTab ->
button(classes = "section-tab") {
if (index == 0) attributes["data-active"] = ""
- attributes["data-togglable"] = node.text
- text(node.text)
+ attributes[TOGGLEABLE_CONTENT_TYPE_ATTR] =
+ contentTab.tabbedContentTypes.joinToString(",") { it.toHtmlAttribute() }
+ text(contentTab.text)
}
}
}
@@ -104,7 +186,9 @@ open class HtmlRenderer(
span("breakable-word") { childrenCallback() }
}
node.hasStyle(TextStyle.Span) -> span { childrenCallback() }
- node.dci.kind == ContentKind.Symbol -> div("symbol $additionalClasses") { childrenCallback() }
+ node.dci.kind == ContentKind.Symbol -> div("symbol $additionalClasses") {
+ childrenCallback()
+ }
node.dci.kind == SymbolContentKind.Parameters -> {
span("parameters $additionalClasses") {
childrenCallback()
@@ -122,7 +206,9 @@ open class HtmlRenderer(
}
node.dci.kind == ContentKind.Deprecation -> div("deprecation-content") { childrenCallback() }
node.hasStyle(TextStyle.Paragraph) -> p(additionalClasses) { childrenCallback() }
- node.hasStyle(TextStyle.Block) -> div(additionalClasses) { childrenCallback() }
+ node.hasStyle(TextStyle.Block) -> div(additionalClasses) {
+ childrenCallback()
+ }
node.hasStyle(TextStyle.Quotation) -> blockQuote(additionalClasses) { childrenCallback() }
node.hasStyle(TextStyle.FloatingRight) -> span("clearfix") { span("floating-right") { childrenCallback() } }
node.hasStyle(TextStyle.Strikethrough) -> strike { childrenCallback() }
@@ -139,6 +225,10 @@ open class HtmlRenderer(
node.hasStyle(ListStyle.DescriptionDetails) -> DD(emptyMap(), consumer).visit {
this@wrapGroup.childrenCallback()
}
+ node.extra.extraTabbedContentType() != null -> div() {
+ node.extra.extraTabbedContentType()?.let { attributes[TOGGLEABLE_CONTENT_TYPE_ATTR] = it.value.toHtmlAttribute() }
+ this@wrapGroup.childrenCallback()
+ }
else -> childrenCallback()
}
}
@@ -433,6 +523,7 @@ open class HtmlRenderer(
) {
buildAnchor(contextNode)
div(classes = "table-row") {
+ contextNode.extra.extraTabbedContentType()?.let { attributes[TOGGLEABLE_CONTENT_TYPE_ATTR] = it.value.toHtmlAttribute() }
addSourceSetFilteringAttributes(contextNode)
div("main-subrow keyValue " + contextNode.style.joinToString(separator = " ")) {
buildRowHeaderLink(toRender, pageContext, sourceSetRestriction, contextNode.anchor)
@@ -786,11 +877,19 @@ open class HtmlRenderer(
content(this, page)
}
+ private fun PageNode.getDocumentableType(): String? =
+ when(this) {
+ is PackagePage -> "package"
+ is ClasslikePage -> "classlike"
+ is MemberPage -> "member"
+ else -> null
+ }
open fun buildHtml(page: PageNode, resources: List<String>, content: FlowContent.() -> Unit): String =
templater.renderFromTemplate(DokkaTemplateTypes.BASE) {
val generatedContent =
createHTML().div("main-content") {
+ page.getDocumentableType()?.let { attributes["data-page-type"] = it }
id = "content"
(page as? ContentPage)?.let {
attributes["pageIds"] = "${context.configuration.moduleName}::${page.pageId}"
@@ -851,6 +950,28 @@ open class HtmlRenderer(
private val isPartial = context.configuration.delayTemplateSubstitution
}
+private fun TabbedContentType.toHtmlAttribute(): String =
+ when(this) {
+ is BasicTabbedContentType ->
+ when(this) {
+ BasicTabbedContentType.ENTRY -> "ENTRY"
+ BasicTabbedContentType.TYPE -> "TYPE"
+ BasicTabbedContentType.CONSTRUCTOR -> "CONSTRUCTOR"
+ BasicTabbedContentType.FUNCTION -> "FUNCTION"
+ BasicTabbedContentType.PROPERTY -> "PROPERTY"
+ BasicTabbedContentType.EXTENSION_PROPERTY -> "EXTENSION_PROPERTY"
+ BasicTabbedContentType.EXTENSION_FUNCTION -> "EXTENSION_FUNCTION"
+ }
+ else -> throw IllegalStateException("Unknown TabbedContentType $this")
+ }
+
+/**
+ * Tabs for a content with [ContentStyle.TabbedContent].
+ *
+ * @see ContentStyle.TabbedContent]
+ */
+private data class ContentTab(val text: String, val tabbedContentTypes: List<TabbedContentType>)
+
fun List<SimpleAttr>.joinAttr() = joinToString(" ") { it.extraKey + "=" + it.extraValue }
private fun String.stripDiv() = drop(5).dropLast(6) // TODO: Find a way to do it without arbitrary trims
@@ -859,6 +980,7 @@ private val PageNode.isNavigable: Boolean
get() = this !is RendererSpecificPage || strategy != RenderingStrategy.DoNothing
private fun PropertyContainer<ContentNode>.extraHtmlAttributes() = allOfType<SimpleAttr>()
+private fun PropertyContainer<ContentNode>.extraTabbedContentType() = this[TabbedContentTypeExtra]
private val ContentNode.sourceSetsFilters: String
get() = sourceSets.sourceSetIDs.joinToString(" ") { it.toString() }
diff --git a/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt b/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt
index 6006af5b..a9c6dcca 100644
--- a/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt
+++ b/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt
@@ -18,6 +18,7 @@ import org.jetbrains.dokka.model.properties.PropertyContainer
import org.jetbrains.dokka.model.properties.WithExtraProperties
import org.jetbrains.dokka.pages.*
import org.jetbrains.dokka.utilities.DokkaLogger
+import java.util.Comparator
import kotlin.reflect.KClass
internal typealias GroupedTags = Map<KClass<out TagWrapper>, List<Pair<DokkaSourceSet?, TagWrapper>>>
@@ -90,7 +91,7 @@ open class DefaultPageCreator(
}
val constructors =
- if (documentables.shouldRenderConstructors()) {
+ if (documentables.shouldDocumentConstructors()) {
documentables.flatMap { (it as? WithConstructors)?.constructors ?: emptyList() }
} else {
emptyList()
@@ -179,6 +180,22 @@ open class DefaultPageCreator(
private val WithScope.filteredProperties: List<DProperty>
get() = properties.filterNot { it.isInherited() }
+ private fun Collection<Documentable>.splitPropsAndFuns(): Pair<List<DProperty>, List<DFunction>> {
+ val first = ArrayList<DProperty>()
+ val second = ArrayList<DFunction>()
+ for (element in this) {
+ when (element) {
+ is DProperty -> first.add(element)
+ is DFunction -> second.add(element)
+ else -> throw IllegalStateException("Expected only properties or functions")
+ }
+ }
+ return Pair(first, second)
+ }
+
+ private fun <T> Collection<T>.splitInheritedExtension(dri: Set<DRI>): Pair<List<T>, List<T>> where T : org.jetbrains.dokka.model.Callable =
+ partition { it.receiver?.dri !in dri }
+
private fun <T> Collection<T>.splitInherited(): Pair<List<T>, List<T>> where T : Documentable, T : WithExtraProperties<T> =
partition { it.isInherited() }
@@ -235,14 +252,15 @@ open class DefaultPageCreator(
}
}
}
- group(styles = setOf(ContentStyle.TabbedContent)) {
+ group(styles = setOf(ContentStyle.TabbedContent), extra = mainExtra) {
+contentForScope(p, p.dri, p.sourceSets)
}
}
protected open fun contentForScopes(
scopes: List<WithScope>,
- sourceSets: Set<DokkaSourceSet>
+ sourceSets: Set<DokkaSourceSet>,
+ extensions: List<Documentable> = emptyList()
): ContentGroup {
val types = scopes.flatMap { it.classlikes } + scopes.filterIsInstance<DPackage>().flatMap { it.typealiases }
return contentForScope(
@@ -251,40 +269,59 @@ open class DefaultPageCreator(
sourceSets,
types,
scopes.flatMap { it.functions },
- scopes.flatMap { it.properties }
+ scopes.flatMap { it.properties },
+ extensions
)
}
protected open fun contentForScope(
s: WithScope,
dri: DRI,
- sourceSets: Set<DokkaSourceSet>
+ sourceSets: Set<DokkaSourceSet>,
+ extensions: List<Documentable> = emptyList()
): ContentGroup {
val types = listOf(
s.classlikes,
(s as? DPackage)?.typealiases ?: emptyList()
).flatten()
- return contentForScope(setOf(dri), sourceSets, types, s.functions, s.properties)
+ return contentForScope(setOf(dri), sourceSets, types, s.functions, s.properties, extensions)
}
- protected open fun contentForScope(
+ private fun contentForScope(
dri: Set<DRI>,
sourceSets: Set<DokkaSourceSet>,
types: List<Documentable>,
functions: List<DFunction>,
- properties: List<DProperty>
+ properties: List<DProperty>,
+ extensions: List<Documentable>
) = contentBuilder.contentFor(dri, sourceSets) {
- divergentBlock("Types", types, ContentKind.Classlikes, extra = mainExtra + SimpleAttr.header("Types"))
+ divergentBlock(
+ "Types",
+ types,
+ ContentKind.Classlikes
+ )
+ val (extensionProps, extensionFuns) = extensions.splitPropsAndFuns()
if (separateInheritedMembers) {
val (inheritedFunctions, memberFunctions) = functions.splitInherited()
val (inheritedProperties, memberProperties) = properties.splitInherited()
- propertiesBlock("Properties", memberProperties, sourceSets)
- propertiesBlock("Inherited properties", inheritedProperties, sourceSets)
- functionsBlock("Functions", memberFunctions)
- functionsBlock("Inherited functions", inheritedFunctions)
+
+ val (inheritedExtensionFunctions, extensionFunctions) = extensionFuns.splitInheritedExtension(dri)
+ val (inheritedExtensionProperties, extensionProperties) = extensionProps.splitInheritedExtension(dri)
+ propertiesBlock(
+ "Properties", memberProperties + extensionProperties
+ )
+ propertiesBlock(
+ "Inherited properties", inheritedProperties + inheritedExtensionProperties
+ )
+ functionsBlock("Functions", memberFunctions + extensionFunctions)
+ functionsBlock(
+ "Inherited functions", inheritedFunctions + inheritedExtensionFunctions
+ )
} else {
- functionsBlock("Functions", functions)
- propertiesBlock("Properties", properties, sourceSets)
+ functionsBlock("Functions", functions + extensionFuns)
+ propertiesBlock(
+ "Properties", properties + extensionProps
+ )
}
}
@@ -302,7 +339,8 @@ open class DefaultPageCreator(
val extensions = (classlikes as List<WithExtraProperties<DClasslike>>).flatMap {
it.extra[CallableExtensions]?.extensions
?.filterIsInstance<Documentable>().orEmpty()
- }.distinctBy{ it.sourceSets to it.dri} // [Documentable] has expensive equals/hashCode at the moment, see #2620
+ }
+ .distinctBy { it.sourceSets to it.dri } // [Documentable] has expensive equals/hashCode at the moment, see #2620
// Extensions are added to sourceSets since they can be placed outside the sourceSets from classlike
// Example would be an Interface in common and extension function in jvm
@@ -315,78 +353,91 @@ open class DefaultPageCreator(
}
}
}
- group(styles = setOf(ContentStyle.TabbedContent), sourceSets = mainSourcesetData + extensions.sourceSets) {
- val csWithConstructor = classlikes.filterIsInstance<WithConstructors>()
- if (csWithConstructor.isNotEmpty() && documentables.shouldRenderConstructors()) {
- val constructorsToDocumented = csWithConstructor.flatMap { it.constructors }
- multiBlock(
- "Constructors",
- 2,
- ContentKind.Constructors,
- constructorsToDocumented.groupBy { it.name }
- .map { (_, v) -> v.first().name to v },
- @Suppress("UNCHECKED_CAST")
- (csWithConstructor as List<Documentable>).sourceSets,
- needsAnchors = true,
- extra = PropertyContainer.empty<ContentNode>() + SimpleAttr.header("Constructors")
- ) { key, ds ->
- link(key, ds.first().dri, kind = ContentKind.Main, styles = setOf(ContentStyle.RowTitle))
- sourceSetDependentHint(
- ds.dri,
- ds.sourceSets,
- kind = ContentKind.SourceSetDependentHint,
- styles = emptySet(),
- extra = PropertyContainer.empty<ContentNode>()
- ) {
- ds.forEach {
- +buildSignature(it)
- contentForBrief(it)
- }
- }
- }
+ val csEnum = classlikes.filterIsInstance<DEnum>()
+ val csWithConstructor = classlikes.filterIsInstance<WithConstructors>()
+ val scopes = documentables.filterIsInstance<WithScope>()
+ val constructorsToDocumented = csWithConstructor.flatMap { it.constructors }
+
+ group(
+ styles = setOf(ContentStyle.TabbedContent),
+ sourceSets = mainSourcesetData + extensions.sourceSets,
+ extra = mainExtra
+ ) {
+ if (constructorsToDocumented.isNotEmpty() && documentables.shouldDocumentConstructors()) {
+ +contentForConstructors(constructorsToDocumented, classlikes.dri, classlikes.sourceSets)
}
- val csEnum = classlikes.filterIsInstance<DEnum>()
if (csEnum.isNotEmpty()) {
- multiBlock(
- "Entries",
- 2,
- ContentKind.Classlikes,
- csEnum.flatMap { it.entries }.groupBy { it.name }.toList(),
- csEnum.sourceSets,
- needsSorting = false,
- needsAnchors = true,
- extra = mainExtra + SimpleAttr.header("Entries"),
- styles = emptySet()
- ) { key, ds ->
- link(key, ds.first().dri)
- sourceSetDependentHint(
- ds.dri,
- ds.sourceSets,
- kind = ContentKind.SourceSetDependentHint,
- extra = PropertyContainer.empty<ContentNode>()
- ) {
- ds.forEach {
- +buildSignature(it)
- contentForBrief(it)
- }
- }
- }
+ +contentForEntries(csEnum.flatMap { it.entries }, csEnum.dri, csEnum.sourceSets)
}
- +contentForScopes(documentables.filterIsInstance<WithScope>(), documentables.sourceSets)
+ +contentForScopes(scopes, documentables.sourceSets, extensions)
+ }
+ }
+ protected open fun contentForConstructors(
+ constructorsToDocumented: List<DFunction>,
+ dri: Set<DRI>,
+ sourceSets: Set<DokkaSourceSet>
+ ) = contentBuilder.contentFor(dri, sourceSets) {
+ multiBlock(
+ "Constructors",
+ 2,
+ ContentKind.Constructors,
+ constructorsToDocumented.groupBy { it.name }
+ .map { (_, v) -> v.first().name to v },
+ @Suppress("UNCHECKED_CAST")
+ (constructorsToDocumented as List<Documentable>).sourceSets,
+ needsAnchors = true,
+ extra = PropertyContainer.empty<ContentNode>() + TabbedContentTypeExtra(
+ BasicTabbedContentType.CONSTRUCTOR
+ ),
+ ) { key, ds ->
+ link(key, ds.first().dri, kind = ContentKind.Main, styles = setOf(ContentStyle.RowTitle))
+ sourceSetDependentHint(
+ ds.dri,
+ ds.sourceSets,
+ kind = ContentKind.SourceSetDependentHint,
+ styles = emptySet(),
+ extra = PropertyContainer.empty<ContentNode>()
+ ) {
+ ds.forEach {
+ +buildSignature(it)
+ contentForBrief(it)
+ }
+ }
+ }
+ }
- divergentBlock(
- "Extensions",
- extensions,
- ContentKind.Extensions,
- extra = mainExtra + SimpleAttr.header("Extensions")
- )
+ protected open fun contentForEntries(
+ entries: List<DEnumEntry>,
+ dri: Set<DRI>,
+ sourceSets: Set<DokkaSourceSet>
+ ) = contentBuilder.contentFor(dri, sourceSets) {
+ multiBlock(
+ "Entries",
+ 2,
+ ContentKind.Classlikes,
+ entries.groupBy { it.name }.toList(),
+ entries.sourceSets,
+ needsSorting = false,
+ needsAnchors = true,
+ extra = mainExtra + TabbedContentTypeExtra(BasicTabbedContentType.ENTRY),
+ styles = emptySet()
+ ) { key, ds ->
+ link(key, ds.first().dri)
+ sourceSetDependentHint(
+ ds.dri,
+ ds.sourceSets,
+ kind = ContentKind.SourceSetDependentHint,
+ extra = PropertyContainer.empty<ContentNode>()
+ ) {
+ ds.forEach {
+ +buildSignature(it)
+ contentForBrief(it)
+ }
}
}
+ }
+
- // Annotations might have constructors to substitute reflection invocations
- // and for internal/compiler purposes, but they are not expected to be documented
- // and instantiated directly under normal circumstances, so constructors should not be rendered.
- private fun List<Documentable>.shouldRenderConstructors() = !this.any { it is DAnnotation }
protected open fun contentForDescription(
d: Documentable
@@ -410,7 +461,9 @@ open class DefaultPageCreator(
}.children
}
- protected open fun DocumentableContentBuilder.contentForBrief(documentable: Documentable) {
+ protected open fun DocumentableContentBuilder.contentForBrief(
+ documentable: Documentable
+ ) {
documentable.sourceSets.forEach { sourceSet ->
documentable.documentation[sourceSet]?.let {
/*
@@ -469,48 +522,50 @@ open class DefaultPageCreator(
}
}
- private fun DocumentableContentBuilder.functionsBlock(name: String, list: Collection<DFunction>) = divergentBlock(
- name,
- list.sorted(),
- ContentKind.Functions,
- extra = mainExtra + SimpleAttr.header(name)
- )
+ private fun DocumentableContentBuilder.functionsBlock(
+ name: String,
+ list: Collection<DFunction>
+ ) {
+ divergentBlock(
+ name,
+ list.sorted(),
+ ContentKind.Functions,
+ extra = mainExtra
+ )
+ }
private fun DocumentableContentBuilder.propertiesBlock(
name: String,
- list: Collection<DProperty>,
- sourceSets: Set<DokkaSourceSet>
+ list: Collection<DProperty>
) {
- multiBlock(
+ divergentBlock(
name,
- 2,
+ list,
ContentKind.Properties,
- list.groupBy { it.name }.toList(),
- sourceSets,
- needsAnchors = true,
- extra = mainExtra + SimpleAttr.header(name),
- headers = listOf(
- headers("Name", "Summary")
- )
- ) { key, props ->
- link(
- text = key,
- address = props.first().dri,
- kind = ContentKind.Main,
- styles = setOf(ContentStyle.RowTitle)
- )
- sourceSetDependentHint(props.dri, props.sourceSets, kind = ContentKind.SourceSetDependentHint) {
- props.forEach {
- +buildSignature(it)
- contentForBrief(it)
- contentForCustomTagsBrief(it)
- }
- }
- }
- }
+ extra = mainExtra
+ )
- private val groupKeyComparator: Comparator<Map.Entry<String?, *>> =
- compareBy(nullsFirst(canonicalAlphabeticalOrder)) { it.key }
+ }
+ private data class NameAndIsExtension(val name:String?, val isExtension: Boolean)
+
+ private fun groupAndSortDivergentCollection(collection: Collection<Documentable>): List<Map.Entry<NameAndIsExtension, List<Documentable>>> {
+ val groupKeyComparator: Comparator<Map.Entry<NameAndIsExtension, List<Documentable>>> =
+ compareBy<Map.Entry<NameAndIsExtension, List<Documentable>>, String?>(
+ nullsFirst(canonicalAlphabeticalOrder)
+ ) { it.key.name }
+ .thenBy { it.key.isExtension }
+
+ return collection
+ .groupBy {
+ NameAndIsExtension(
+ it.name,
+ it.isExtension()
+ )
+ } // This groupBy should probably use LocationProvider
+ // This hacks displaying actual typealias signatures along classlike ones
+ .mapValues { if (it.value.any { it is DClasslike }) it.value.filter { it !is DTypeAlias } else it.value }
+ .entries.sortedWith(groupKeyComparator)
+ }
protected open fun DocumentableContentBuilder.divergentBlock(
name: String,
@@ -519,60 +574,77 @@ open class DefaultPageCreator(
extra: PropertyContainer<ContentNode> = mainExtra
) {
if (collection.any()) {
- header(2, name, kind = kind)
- table(kind, extra = extra, styles = emptySet()) {
- header {
- group { text("Name") }
- group { text("Summary") }
- }
- collection
- .groupBy { it.name } // This groupBy should probably use LocationProvider
- // This hacks displaying actual typealias signatures along classlike ones
- .mapValues { if (it.value.any { it is DClasslike }) it.value.filter { it !is DTypeAlias } else it.value }
- .entries.sortedWith(groupKeyComparator)
- .forEach { (elementName, elements) -> // This groupBy should probably use LocationProvider
- val sortedElements = sortDivergentElementsDeterministically(elements)
- row(
- dri = sortedElements.map { it.dri }.toSet(),
- sourceSets = sortedElements.flatMap { it.sourceSets }.toSet(),
- kind = kind,
- styles = emptySet(),
- extra = elementName?.let { name -> extra + SymbolAnchorHint(name, kind) } ?: extra
- ) {
- link(
- text = elementName.orEmpty(),
- address = sortedElements.first().dri,
- kind = kind,
- styles = setOf(ContentStyle.RowTitle),
- sourceSets = sortedElements.sourceSets.toSet(),
- extra = extra
- )
- divergentGroup(
- ContentDivergentGroup.GroupID(name),
- sortedElements.map { it.dri }.toSet(),
- kind = kind,
- extra = extra
+ val onlyExtensions = collection.all { it.isExtension() }
+ val groupExtra = when(kind) {
+ ContentKind.Functions -> extra + TabbedContentTypeExtra(if (onlyExtensions) BasicTabbedContentType.EXTENSION_FUNCTION else BasicTabbedContentType.FUNCTION)
+ ContentKind.Properties -> extra + TabbedContentTypeExtra(if (onlyExtensions) BasicTabbedContentType.EXTENSION_PROPERTY else BasicTabbedContentType.PROPERTY)
+ ContentKind.Classlikes -> extra + TabbedContentTypeExtra(BasicTabbedContentType.TYPE)
+ else -> extra
+ }
+
+ group(extra = groupExtra) {
+ // be careful: groupExtra will be applied for children by default
+ header(2, name, kind = kind, extra = extra) { }
+ val isFunctions = collection.any { it is DFunction }
+ table(kind, extra = extra, styles = emptySet()) {
+ header {
+ group { text("Name") }
+ group { text("Summary") }
+ }
+ groupAndSortDivergentCollection(collection)
+ .forEach { (elementNameAndIsExtension, elements) -> // This groupBy should probably use LocationProvider
+ val elementName = elementNameAndIsExtension.name
+ val isExtension = elementNameAndIsExtension.isExtension
+ val rowExtra =
+ if (isExtension) extra + TabbedContentTypeExtra(if(isFunctions) BasicTabbedContentType.EXTENSION_FUNCTION else BasicTabbedContentType.EXTENSION_PROPERTY) else extra
+ val rowKind = if (isExtension) ContentKind.Extensions else kind
+ val sortedElements = sortDivergentElementsDeterministically(elements)
+ row(
+ dri = sortedElements.map { it.dri }.toSet(),
+ sourceSets = sortedElements.flatMap { it.sourceSets }.toSet(),
+ kind = rowKind,
+ styles = emptySet(),
+ extra = elementName?.let { name -> rowExtra + SymbolAnchorHint(name, kind) } ?: rowExtra
) {
- sortedElements.map {
- instance(
- setOf(it.dri),
- it.sourceSets.toSet(),
- extra = PropertyContainer.withAll(SymbolAnchorHint(it.name ?: "", kind))
- ) {
- divergent(extra = PropertyContainer.empty()) {
- group {
- +buildSignature(it)
+ link(
+ text = elementName.orEmpty(),
+ address = sortedElements.first().dri,
+ kind = rowKind,
+ styles = setOf(ContentStyle.RowTitle),
+ sourceSets = sortedElements.sourceSets.toSet(),
+ extra = extra
+ )
+ divergentGroup(
+ ContentDivergentGroup.GroupID(name),
+ sortedElements.map { it.dri }.toSet(),
+ kind = rowKind,
+ extra = extra
+ ) {
+ sortedElements.map { element ->
+ instance(
+ setOf(element.dri),
+ element.sourceSets.toSet(),
+ extra = PropertyContainer.withAll(
+ SymbolAnchorHint(element.name ?: "", rowKind)
+ )
+ ) {
+ divergent(extra = PropertyContainer.empty()) {
+ group {
+ +buildSignature(element)
+ }
+ }
+ after(
+ extra = PropertyContainer.empty()
+ ) {
+ contentForBrief(element)
+ contentForCustomTagsBrief(element)
}
- }
- after(extra = PropertyContainer.empty()) {
- contentForBrief(it)
- contentForCustomTagsBrief(it)
}
}
}
}
}
- }
+ }
}
}
}
@@ -664,3 +736,8 @@ internal inline fun <reified T : NamedTagWrapper> GroupedTags.withTypeNamed(): M
?.groupByTo(linkedMapOf()) { it.second.name }
?.mapValues { (_, v) -> v.toMap() }
.orEmpty()
+
+// Annotations might have constructors to substitute reflection invocations
+// and for internal/compiler purposes, but they are not expected to be documented
+// and instantiated directly under normal circumstances, so constructors should not be rendered.
+internal fun List<Documentable>.shouldDocumentConstructors() = !this.any { it is DAnnotation } \ No newline at end of file
diff --git a/plugins/base/src/main/kotlin/translators/documentables/DescriptionSections.kt b/plugins/base/src/main/kotlin/translators/documentables/DescriptionSections.kt
index 1720b1d0..8cd53525 100644
--- a/plugins/base/src/main/kotlin/translators/documentables/DescriptionSections.kt
+++ b/plugins/base/src/main/kotlin/translators/documentables/DescriptionSections.kt
@@ -229,7 +229,6 @@ internal fun PageContentBuilder.DocumentableContentBuilder.samplesSectionContent
sourceSets = setOf(sourceSet),
kind = ContentKind.Sample,
styles = setOf(TextStyle.Monospace, ContentStyle.RunnableSample),
- extra = mainExtra + SimpleAttr.header("Samples")
) {
samples.filter { it.value.isEmpty() || sourceSet in it.value }
.forEach { text(text = it.key, sourceSets = setOf(sourceSet)) }
@@ -333,7 +332,6 @@ private fun PageContentBuilder.DocumentableContentBuilder.tableSectionContentBlo
table(
kind = kind,
sourceSets = sourceSets,
- extra = mainExtra + SimpleAttr.header(blockName)
) {
body()
}
diff --git a/plugins/base/src/main/kotlin/translators/documentables/PageContentBuilder.kt b/plugins/base/src/main/kotlin/translators/documentables/PageContentBuilder.kt
index 000c955f..4eb1f06b 100644
--- a/plugins/base/src/main/kotlin/translators/documentables/PageContentBuilder.kt
+++ b/plugins/base/src/main/kotlin/translators/documentables/PageContentBuilder.kt
@@ -245,28 +245,40 @@ open class PageContentBuilder(
needsAnchors: Boolean = false,
operation: DocumentableContentBuilder.(String, List<Documentable>) -> Unit
) {
+
if (renderWhenEmpty || groupedElements.any()) {
- header(level, name, kind = kind) { }
- contents += ContentTable(
- header = headers,
- children = groupedElements
- .let {
- if (needsSorting)
- it.sortedWith(compareBy(nullsLast(String.CASE_INSENSITIVE_ORDER)) { it.first })
- else it
- }
- .map {
- val newExtra = if (needsAnchors) extra + SymbolAnchorHint(it.first, kind) else extra
- val documentables = it.second
- buildGroup(documentables.map { it.dri }.toSet(), documentables.flatMap { it.sourceSets }.toSet(), kind, styles, newExtra) {
- operation(it.first, documentables)
+ group(extra = extra) {
+ header(level, name, kind = kind) { }
+ contents += ContentTable(
+ header = headers,
+ children = groupedElements
+ .let {
+ if (needsSorting)
+ it.sortedWith(compareBy(nullsLast(String.CASE_INSENSITIVE_ORDER)) { it.first })
+ else it
}
- },
- dci = DCI(mainDRI, kind),
- sourceSets = sourceSets.toDisplaySourceSets(),
- style = styles,
- extra = extra
- )
+ .map {
+ val documentables = it.second
+ val newExtra = if (needsAnchors) extra + SymbolAnchorHint(
+ it.first,
+ kind
+ ) else extra
+ buildGroup(
+ documentables.map { it.dri }.toSet(),
+ documentables.flatMap { it.sourceSets }.toSet(),
+ kind,
+ styles,
+ newExtra
+ ) {
+ operation(it.first, documentables)
+ }
+ },
+ dci = DCI(mainDRI, kind),
+ sourceSets = sourceSets.toDisplaySourceSets(),
+ style = styles,
+ extra = extra
+ )
+ }
}
}
diff --git a/plugins/base/src/main/resources/dokka/scripts/platform-content-handler.js b/plugins/base/src/main/resources/dokka/scripts/platform-content-handler.js
index def9dae8..1f99ba1f 100644
--- a/plugins/base/src/main/resources/dokka/scripts/platform-content-handler.js
+++ b/plugins/base/src/main/resources/dokka/scripts/platform-content-handler.js
@@ -129,22 +129,39 @@ function handleAnchor() {
highlightedAnchor = null;
}
- let searchForTab = function (element) {
+ let searchForContentTarget = function (element) {
if (element && element.hasAttribute) {
- if (element.hasAttribute("data-togglable")) return element;
- else return searchForTab(element.parentNode)
+ if (element.hasAttribute("data-togglable")) return element.getAttribute("data-togglable");
+ else return searchForContentTarget(element.parentNode)
} else return null
}
+
+ let findAnyTab = function (target) {
+ let result = null
+ document.querySelectorAll('div[tabs-section] > button[data-togglable]')
+ .forEach(node => {
+ if(node.getAttribute("data-togglable").split(",").includes(target)) {
+ result = node
+ }
+ })
+ return result
+ }
+
let anchor = window.location.hash
if (anchor != "") {
anchor = anchor.substring(1)
let element = document.querySelector('a[data-name="' + anchor + '"]')
+
if (element) {
- let tab = searchForTab(element)
- if (tab) {
- toggleSections(tab)
- }
const content = element.nextElementSibling
+ const contentStyle = window.getComputedStyle(content)
+ if(contentStyle.display == 'none') {
+ let tab = findAnyTab(searchForContentTarget(content))
+ if (tab) {
+ toggleSections(tab)
+ }
+ }
+
if (content) {
content.classList.add('anchor-highlight')
highlightedAnchor = content
@@ -174,10 +191,7 @@ function initTabs() {
function showCorrespondingTabBody(element) {
const buttonWithKey = element.querySelector("button[data-active]")
if (buttonWithKey) {
- const key = buttonWithKey.getAttribute("data-togglable")
- document.querySelector(".tabs-section-body")
- .querySelector("div[data-togglable='" + key + "']")
- .setAttribute("data-active", "")
+ toggleSections(buttonWithKey)
}
}
@@ -249,7 +263,6 @@ function removeSourcesetFilterFromCache(sourceset) {
}
function toggleSections(target) {
- localStorage.setItem('active-tab', JSON.stringify(target.getAttribute("data-togglable")))
const activateTabs = (containerClass) => {
for (const element of document.getElementsByClassName(containerClass)) {
for (const child of element.children) {
@@ -261,13 +274,24 @@ function toggleSections(target) {
}
}
}
-
+ const toggleTargets = target.getAttribute("data-togglable").split(",")
+ const activateTabsBody = (containerClass) => {
+ document.querySelectorAll("." + containerClass + " *[data-togglable]")
+ .forEach(child => {
+ if (toggleTargets.includes(child.getAttribute("data-togglable"))) {
+ child.setAttribute("data-active", "")
+ } else if(!child.classList.contains("sourceset-dependent-content")) { // data-togglable is used to switch source set as well, ignore it
+ child.removeAttribute("data-active")
+ }
+ })
+ }
activateTabs("tabs-section")
- activateTabs("tabs-section-body")
+ activateTabsBody("tabs-section-body")
}
function toggleSectionsEventHandler(evt) {
if (!evt.target.getAttribute("data-togglable")) return
+ localStorage.setItem('active-tab', JSON.stringify(evt.target.getAttribute("data-togglable")))
toggleSections(evt.target)
}
diff --git a/plugins/base/src/main/resources/dokka/styles/style.css b/plugins/base/src/main/resources/dokka/styles/style.css
index 43c8dde6..e34b9fb4 100644
--- a/plugins/base/src/main/resources/dokka/styles/style.css
+++ b/plugins/base/src/main/resources/dokka/styles/style.css
@@ -947,7 +947,7 @@ td.content {
}
.platform-hinted > .content:not([data-active]),
-.tabs-section-body > *:not([data-active]) {
+.tabs-section-body *[data-togglable]:not([data-active]) {
display: none;
}
@@ -1297,3 +1297,11 @@ div.runnablesample {
.floating-right {
float: right;
}
+
+/*
+the hack to hide the headers inside tabs for a package page because each tab
+has only one header, and the header text is the same as the tab name, so no point in showing it
+*/
+.main-content[data-page-type="package"] .tabs-section-body h2 {
+ display: none;
+} \ No newline at end of file