package org.jetbrains.dokka.base.renderers.html
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.html.*
import kotlinx.html.stream.createHTML
import org.jetbrains.dokka.DokkaSourceSetID
import org.jetbrains.dokka.base.DokkaBase
import org.jetbrains.dokka.base.renderers.DefaultRenderer
import org.jetbrains.dokka.base.renderers.TabSortingStrategy
import org.jetbrains.dokka.base.resolvers.anchors.SymbolAnchorHint
import org.jetbrains.dokka.base.transformers.pages.sourcelinks.hasTabbedContent
import org.jetbrains.dokka.base.renderers.isImage
import org.jetbrains.dokka.base.renderers.pageId
import org.jetbrains.dokka.links.DRI
import org.jetbrains.dokka.model.CompositeSourceSetID
import org.jetbrains.dokka.model.DisplaySourceSet
import org.jetbrains.dokka.model.properties.PropertyContainer
import org.jetbrains.dokka.model.sourceSetIDs
import org.jetbrains.dokka.model.withDescendants
import org.jetbrains.dokka.pages.*
import org.jetbrains.dokka.plugability.DokkaContext
import org.jetbrains.dokka.plugability.plugin
import org.jetbrains.dokka.plugability.query
import org.jetbrains.dokka.plugability.querySingle
import org.jetbrains.dokka.utilities.htmlEscape
import org.jetbrains.dokka.utilities.urlEncoded
import java.net.URI
open class HtmlRenderer(
context: DokkaContext
) : DefaultRenderer(context) {
private val sourceSetDependencyMap: Map> =
context.configuration.sourceSets.map { sourceSet ->
sourceSet.sourceSetID to context.configuration.sourceSets
.map { it.sourceSetID }
.filter { it in sourceSet.dependentSourceSets }
}.toMap()
private var shouldRenderSourceSetBubbles: Boolean = false
override val preprocessors = context.plugin().query { htmlPreprocessors }
val searchbarDataInstaller = SearchbarDataInstaller()
private val tabSortingStrategy = context.plugin().querySingle { tabSortingStrategy }
private fun sortTabs(strategy: TabSortingStrategy, tabs: Collection): List {
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,
childrenCallback: FlowContent.() -> Unit
) {
val additionalClasses = node.style.joinToString(" ") { it.toString().toLowerCase() }
return when {
node.hasStyle(ContentStyle.TabbedContent) -> div(additionalClasses) {
val secondLevel = node.children.filterIsInstance().flatMap { it.children }
.filterIsInstance().flatMap { it.children }.filterIsInstance()
val firstLevel = node.children.filterIsInstance().flatMap { it.children }
.filterIsInstance()
val renderable = firstLevel.union(secondLevel).let { sortTabs(tabSortingStrategy, it) }
div(classes = "tabs-section") {
attributes["tabs-section"] = "tabs-section"
renderable.forEachIndexed { index, node ->
button(classes = "section-tab") {
if (index == 0) attributes["data-active"] = ""
attributes["data-togglable"] = node.text
text(node.text)
}
}
}
div(classes = "tabs-section-body") {
childrenCallback()
}
}
node.hasStyle(ContentStyle.WithExtraAttributes) -> div() {
node.extra.extraHtmlAttributes().forEach { attributes[it.extraKey] = it.extraValue }
childrenCallback()
}
node.dci.kind in setOf(ContentKind.Symbol) -> div("symbol $additionalClasses") {
childrenCallback()
if (node.hasStyle(TextStyle.Monospace)) copyButton()
}
node.hasStyle(TextStyle.BreakableAfter) -> {
span() { childrenCallback() }
wbr { }
}
node.hasStyle(TextStyle.Breakable) -> {
span("breakable-word") { childrenCallback() }
}
node.hasStyle(TextStyle.Span) -> span() { childrenCallback() }
node.dci.kind == ContentKind.Symbol -> div("symbol $additionalClasses") { childrenCallback() }
node.dci.kind == ContentKind.BriefComment -> div("brief $additionalClasses") { childrenCallback() }
node.dci.kind == ContentKind.Cover -> div("cover $additionalClasses") {
filterButtons(pageContext)
childrenCallback()
}
node.hasStyle(TextStyle.Paragraph) -> p(additionalClasses) { childrenCallback() }
node.hasStyle(TextStyle.Block) -> div(additionalClasses) { childrenCallback() }
node.isAnchorable -> buildAnchor(node.anchor!!, node.anchorLabel!!, node.sourceSetsFilters) { childrenCallback() }
else -> childrenCallback()
}
}
private fun FlowContent.filterButtons(page: ContentPage) {
if (shouldRenderSourceSetBubbles) {
div(classes = "filter-section") {
id = "filter-section"
page.content.withDescendants().flatMap { it.sourceSets }.distinct().forEach {
button(classes = "platform-tag platform-selector") {
attributes["data-active"] = ""
attributes["data-filter"] = it.sourceSetIDs.merged.toString()
when (it.platform.key) {
"common" -> classes = classes + "common-like"
"native" -> classes = classes + "native-like"
"jvm" -> classes = classes + "jvm-like"
"js" -> classes = classes + "js-like"
}
text(it.name)
}
}
}
}
}
private fun FlowContent.copyButton() = span(classes = "top-right-position") {
span("copy-icon") {
unsafe {
raw(
"""""".trimIndent()
)
}
}
copiedPopup("Content copied to clipboard", "popup-to-left")
}
private fun FlowContent.copiedPopup(notificationContent: String, additionalClasses: String = "") =
div("copy-popup-wrapper $additionalClasses") {
unsafe {
raw(
"""
""".trimIndent()
)
}
span {
text(notificationContent)
}
}
override fun FlowContent.buildPlatformDependent(
content: PlatformHintedContent,
pageContext: ContentPage,
sourceSetRestriction: Set?
) =
buildPlatformDependent(
content.sourceSets.filter {
sourceSetRestriction == null || it in sourceSetRestriction
}.map { it to setOf(content.inner) }.toMap(),
pageContext,
content.extra,
content.style
)
private fun FlowContent.buildPlatformDependent(
nodes: Map>,
pageContext: ContentPage,
extra: PropertyContainer = PropertyContainer.empty(),
styles: Set