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