/*
* Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package org.jetbrains.dokka.base.renderers.html
import kotlinx.html.*
import kotlinx.html.stream.createHTML
import org.jetbrains.dokka.DokkaSourceSetID
import org.jetbrains.dokka.Platform
import org.jetbrains.dokka.base.DokkaBase
import org.jetbrains.dokka.base.renderers.*
import org.jetbrains.dokka.base.renderers.html.command.consumers.ImmediateResolutionTagConsumer
import org.jetbrains.dokka.base.renderers.html.innerTemplating.DefaultTemplateModelFactory
import org.jetbrains.dokka.base.renderers.html.innerTemplating.DefaultTemplateModelMerger
import org.jetbrains.dokka.base.renderers.html.innerTemplating.DokkaTemplateTypes
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.base.translators.documentables.shouldDocumentConstructors
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.*
import org.jetbrains.dokka.transformers.pages.PageTransformer
import org.jetbrains.dokka.utilities.htmlEscape
internal const val TEMPLATE_REPLACEMENT: String = "###"
internal const val TOGGLEABLE_CONTENT_TYPE_ATTR = "data-togglable"
public open class HtmlRenderer(
context: DokkaContext
) : DefaultRenderer<FlowContent>(context) {
private val sourceSetDependencyMap: Map<DokkaSourceSetID, List<DokkaSourceSetID>> =
context.configuration.sourceSets.associate { sourceSet ->
sourceSet.sourceSetID to context.configuration.sourceSets
.map { it.sourceSetID }
.filter { it in sourceSet.dependentSourceSets }
}
private val templateModelFactories = listOf(DefaultTemplateModelFactory(context)) // TODO: Make extension point
private val templateModelMerger = DefaultTemplateModelMerger()
private val templater = HtmlTemplater(context).apply {
setupSharedModel(templateModelMerger.invoke(templateModelFactories) { buildSharedModel() })
}
private var shouldRenderSourceSetTabs: Boolean = false
override val preprocessors: List<PageTransformer> = context.plugin<DokkaBase>().query { htmlPreprocessors }
/**
* 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
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.PROPERTY,
BasicTabbedContentType.FUNCTION
)
),
if (extensions.isEmpty()) null else ContentTab(
"Members & Extensions",
listOf(
BasicTabbedContentType.CONSTRUCTOR,
BasicTabbedContentType.TYPE,
BasicTabbedContentType.PROPERTY,
BasicTabbedContentType.FUNCTION,
BasicTabbedContentType.EXTENSION_PROPERTY,
BasicTabbedConten