aboutsummaryrefslogtreecommitdiff
path: root/plugins/base/src/main/kotlin/renderers/html
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/base/src/main/kotlin/renderers/html')
-rw-r--r--plugins/base/src/main/kotlin/renderers/html/HtmlContent.kt18
-rw-r--r--plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt1013
-rw-r--r--plugins/base/src/main/kotlin/renderers/html/NavigationDataProvider.kt134
-rw-r--r--plugins/base/src/main/kotlin/renderers/html/NavigationPage.kt129
-rw-r--r--plugins/base/src/main/kotlin/renderers/html/SearchbarDataInstaller.kt128
-rw-r--r--plugins/base/src/main/kotlin/renderers/html/Tags.kt82
-rw-r--r--plugins/base/src/main/kotlin/renderers/html/command/consumers/ImmediateResolutionTagConsumer.kt37
-rw-r--r--plugins/base/src/main/kotlin/renderers/html/command/consumers/PathToRootConsumer.kt26
-rw-r--r--plugins/base/src/main/kotlin/renderers/html/command/consumers/ReplaceVersionsConsumer.kt29
-rw-r--r--plugins/base/src/main/kotlin/renderers/html/command/consumers/ResolveLinkConsumer.kt34
-rw-r--r--plugins/base/src/main/kotlin/renderers/html/htmlFormatingUtils.kt67
-rw-r--r--plugins/base/src/main/kotlin/renderers/html/htmlPreprocessors.kt172
-rw-r--r--plugins/base/src/main/kotlin/renderers/html/innerTemplating/DefaultTemplateModelFactory.kt234
-rw-r--r--plugins/base/src/main/kotlin/renderers/html/innerTemplating/DefaultTemplateModelMerger.kt20
-rw-r--r--plugins/base/src/main/kotlin/renderers/html/innerTemplating/HtmlTemplater.kt82
-rw-r--r--plugins/base/src/main/kotlin/renderers/html/innerTemplating/TemplateModelFactory.kt19
-rw-r--r--plugins/base/src/main/kotlin/renderers/html/innerTemplating/TemplateModelMerger.kt9
-rw-r--r--plugins/base/src/main/kotlin/renderers/html/shouldRenderSourceSetBubbles.kt20
18 files changed, 0 insertions, 2253 deletions
diff --git a/plugins/base/src/main/kotlin/renderers/html/HtmlContent.kt b/plugins/base/src/main/kotlin/renderers/html/HtmlContent.kt
deleted file mode 100644
index 1ef6e04c..00000000
--- a/plugins/base/src/main/kotlin/renderers/html/HtmlContent.kt
+++ /dev/null
@@ -1,18 +0,0 @@
-/*
- * 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 org.jetbrains.dokka.pages.ContentBreakLine
-import org.jetbrains.dokka.pages.Style
-
-
-/**
- * Html-specific style that represents <hr> tag if used in conjunction with [ContentBreakLine]
- */
-internal object HorizontalBreakLineStyle : Style {
- // this exists as a simple internal solution to avoid introducing unnecessary public API on content level.
- // If you have the need to implement proper horizontal divider (i.e to support `---` markdown element),
- // consider removing this and providing proper API for all formats and levels
-}
diff --git a/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt b/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt
deleted file mode 100644
index 083876d5..00000000
--- a/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt
+++ /dev/null
@@ -1,1013 +0,0 @@
-/*
- * 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,
- 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(
- if (p.typealiases.isEmpty() && p.classlikes.isEmpty()) null else 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)
-
- 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 contentTabs = createTabs(pageContext)
-
- div(classes = "tabs-section") {
- attributes["tabs-section"] = "tabs-section"
- contentTabs.forEachIndexed { index, contentTab ->
- button(classes = "section-tab") {
- if (index == 0) attributes["data-active"] = ""
- attributes[TOGGLEABLE_CONTENT_TYPE_ATTR] =
- contentTab.tabbedContentTypes.joinToString(",") { it.toHtmlAttribute() }
- text(contentTab.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()
- }
- node.hasStyle(ContentStyle.KDocTag) -> span("kdoc-tag") { childrenCallback() }
- node.hasStyle(ContentStyle.Footnote) -> div("footnote") { childrenCallback() }
- 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 == SymbolContentKind.Parameters -> {
- span("parameters $additionalClasses") {
- childrenCallback()
- }
- }
- node.dci.kind == SymbolContentKind.Parameter -> {
- span("parameter $additionalClasses") {
- childrenCallback()
- }
- }
- node.hasStyle(TextStyle.InlineComment) -> div("inline-comment") { childrenCallback() }
- node.dci.kind == ContentKind.BriefComment -> div("brief $additionalClasses") { childrenCallback() }
- node.dci.kind == ContentKind.Cover -> div("cover $additionalClasses") { //TODO this can be removed
- childrenCallback()
- }
- 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.Quotation) -> blockQuote(additionalClasses) { childrenCallback() }
- node.hasStyle(TextStyle.FloatingRight) -> span("clearfix") { span("floating-right") { childrenCallback() } }
- node.hasStyle(TextStyle.Strikethrough) -> strike { childrenCallback() }
- node.isAnchorable -> buildAnchor(
- node.anchor!!,
- node.anchorLabel!!,
- node.buildSourceSetFilterValues()
- ) { childrenCallback() }
- node.extra[InsertTemplateExtra] != null -> node.extra[InsertTemplateExtra]?.let { templateCommand(it.command) }
- ?: Unit
- node.hasStyle(ListStyle.DescriptionTerm) -> DT(emptyMap(), consumer).visit {
- this@wrapGroup.childrenCallback()
- }
- 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()
- }
- }
-
- private fun FlowContent.copyButton() = span(classes = "top-right-position") {
- span("copy-icon")
- copiedPopup("Content copied to clipboard", "popup-to-left")
- }
-
- private fun FlowContent.copiedPopup(notificationContent: String, additionalClasses: String = "") =
- div("copy-popup-wrapper $additionalClasses") {
- span("copy-popup-icon")
- span {
- text(notificationContent)
- }
- }
-
- override fun FlowContent.buildPlatformDependent(
- content: PlatformHintedContent,
- pageContext: ContentPage,
- sourceSetRestriction: Set<DisplaySourceSet>?
- ) {
- buildPlatformDependent(
- content.sourceSets.filter {
- sourceSetRestriction == null || it in sourceSetRestriction
- }.associateWith { setOf(content.inner) },
- pageContext,
- content.extra,
- content.style
- )
- }
-
- private fun FlowContent.buildPlatformDependent(
- nodes: Map<DisplaySourceSet, Collection<ContentNode>>,
- pageContext: ContentPage,
- extra: PropertyContainer<ContentNode> = PropertyContainer.empty(),
- styles: Set<Style> = emptySet(),
- shouldHaveTabs: Boolean = shouldRenderSourceSetTabs
- ) {
- val contents = contentsForSourceSetDependent(nodes, pageContext)
- val isOnlyCommonContent = contents.singleOrNull()?.let { (sourceSet, _) ->
- sourceSet.platform == Platform.common
- && sourceSet.name.equals("common", ignoreCase = true)
- && sourceSet.sourceSetIDs.all.all { sourceSetDependencyMap[it]?.isEmpty() == true }
- } ?: false
-
- // little point in rendering a single "common" tab - it can be
- // assumed that code without any tabs is common by default
- val renderTabs = shouldHaveTabs && !isOnlyCommonContent
-
- val divStyles = "platform-hinted ${styles.joinToString()}" + if (renderTabs) " with-platform-tabs" else ""
- div(divStyles) {
- attributes["data-platform-hinted"] = "data-platform-hinted"
- extra.extraHtmlAttributes().forEach { attributes[it.extraKey] = it.extraValue }
- if (renderTabs) {
- div("platform-bookmarks-row") {
- attributes["data-toggle-list"] = "data-toggle-list"
- contents.forEachIndexed { index, pair ->
- button(classes = "platform-bookmark") {
- attributes["data-filterable-current"] = pair.first.sourceSetIDs.merged.toString()
- attributes["data-filterable-set"] = pair.first.sourceSetIDs.merged.toString()
- if (index == 0) attributes["data-active"] = ""
- attributes["data-toggle"] = pair.first.sourceSetIDs.merged.toString()
- text(pair.first.name)
- }
- }
- }
- }
- contents.forEach {
- consumer.onTagContentUnsafe { +it.second }
- }
- }
- }
-
- private fun contentsForSourceSetDependent(
- nodes: Map<DisplaySourceSet, Collection<ContentNode>>,
- pageContext: ContentPage,
- ): List<Pair<DisplaySourceSet, String>> {
- var counter = 0
- return nodes.toList().map { (sourceSet, elements) ->
- val htmlContent = createHTML(prettyPrint = false).prepareForTemplates().div {
- elements.forEach {
- buildContentNode(it, pageContext, sourceSet)
- }
- }.stripDiv()
- sourceSet to createHTML(prettyPrint = false).prepareForTemplates()
- .div(classes = "content sourceset-dependent-content") {
- if (counter++ == 0) attributes["data-active"] = ""
- attributes["data-togglable"] = sourceSet.sourceSetIDs.merged.toString()
- unsafe {
- +htmlContent
- }
- }
- }.sortedBy { it.first.comparableKey }
- }
-
- override fun FlowContent.buildDivergent(node: ContentDivergentGroup, pageContext: ContentPage) {
- if (node.implicitlySourceSetHinted) {
- val groupedInstancesBySourceSet = node.children.flatMap { instance ->
- instance.sourceSets.map { sourceSet -> instance to sourceSet }
- }.groupBy(
- Pair<ContentDivergentInstance, DisplaySourceSet>::second,
- Pair<ContentDivergentInstance, DisplaySourceSet>::first
- )
-
- val nodes = groupedInstancesBySourceSet.mapValues {
- val distinct =
- groupDivergentInstancesWithSourceSet(it.value, it.key, pageContext,
- beforeTransformer = { instance, _, sourceSet ->
- createHTML(prettyPrint = false).prepareForTemplates().div {
- instance.before?.let { before ->
- buildContentNode(before, pageContext, sourceSet)
- }
- }.stripDiv()
- },
- afterTransformer = { instance, _, sourceSet ->
- createHTML(prettyPrint = false).prepareForTemplates().div {
- instance.after?.let { after ->
- buildContentNode(after, pageContext, sourceSet)
- }
- }.stripDiv()
- })
-
- val isPageWithOverloadedMembers = pageContext is MemberPage && pageContext.documentables().size > 1
-
- val contentOfSourceSet = mutableListOf<ContentNode>()
- distinct.onEachIndexed{ index, (_, distinctInstances) ->
- distinctInstances.firstOrNull()?.before?.let { contentOfSourceSet.add(it) }
- contentOfSourceSet.addAll(distinctInstances.map { it.divergent })
- (distinctInstances.firstOrNull()?.after ?: if (index != distinct.size - 1) ContentBreakLine(setOf(it.key)) else null)
- ?.let { contentOfSourceSet.add(it) }
-
- // content kind main is important for declarations list to avoid double line breaks
- if (node.dci.kind == ContentKind.Main && index != distinct.size - 1) {
- if (isPageWithOverloadedMembers) {
- // add some spacing and distinction between function/property overloads.
- // not ideal, but there's no other place to modify overloads page atm
- contentOfSourceSet.add(ContentBreakLine(setOf(it.key), style = setOf(HorizontalBreakLineStyle)))
- } else {
- contentOfSourceSet.add(ContentBreakLine(setOf(it.key)))
- }
- }
- }
- contentOfSourceSet
- }
- buildPlatformDependent(nodes, pageContext)
- } else {
- node.children.forEach {
- buildContentNode(it.divergent, pageContext, it.sourceSets)
- }
- }
- }
-
- private fun groupDivergentInstancesWithSourceSet(
- instances: List<ContentDivergentInstance>,
- sourceSet: DisplaySourceSet,
- pageContext: ContentPage,
- beforeTransformer: (ContentDivergentInstance, ContentPage, DisplaySourceSet) -> String,
- afterTransformer: (ContentDivergentInstance, ContentPage, DisplaySourceSet) -> String
- ): Map<SerializedBeforeAndAfter, List<ContentDivergentInstance>> =
- instances.map { instance ->
- instance to Pair(
- beforeTransformer(instance, pageContext, sourceSet),
- afterTransformer(instance, pageContext, sourceSet)
- )
- }.groupBy(
- Pair<ContentDivergentInstance, SerializedBeforeAndAfter>::second,
- Pair<ContentDivergentInstance, SerializedBeforeAndAfter>::first
- )
-
- private fun ContentPage.documentables(): List<Documentable> {
- return (this as? WithDocumentables)?.documentables ?: emptyList()
- }
-
- override fun FlowContent.buildList(
- node: ContentList,
- pageContext: ContentPage,
- sourceSetRestriction: Set<DisplaySourceSet>?
- ) {
- return when {
- node.ordered -> {
- ol { buildListItems(node.children, pageContext, sourceSetRestriction) }
- }
- node.hasStyle(ListStyle.DescriptionList) -> {
- dl { node.children.forEach { it.build(this, pageContext, sourceSetRestriction) } }
- }
- else -> {
- ul { buildListItems(node.children, pageContext, sourceSetRestriction) }
- }
- }
- }
-
- public open fun OL.buildListItems(
- items: List<ContentNode>,
- pageContext: ContentPage,
- sourceSetRestriction: Set<DisplaySourceSet>? = null
- ) {
- items.forEach {
- if (it is ContentList)
- buildList(it, pageContext)
- else
- li { it.build(this, pageContext, sourceSetRestriction) }
- }
- }
-
- public open fun UL.buildListItems(
- items: List<ContentNode>,
- pageContext: ContentPage,
- sourceSetRestriction: Set<DisplaySourceSet>? = null
- ) {
- items.forEach {
- if (it is ContentList)
- buildList(it, pageContext)
- else
- li { it.build(this, pageContext) }
- }
- }
-
- override fun FlowContent.buildResource(
- node: ContentEmbeddedResource,
- pageContext: ContentPage
- ) { // TODO: extension point there
- if (node.isImage()) {
- img(src = node.address, alt = node.altText)
- } else {
- println("Unrecognized resource type: $node")
- }
- }
-
- private fun FlowContent.buildRow(
- node: ContentGroup,
- pageContext: ContentPage,
- sourceSetRestriction: Set<DisplaySourceSet>?
- ) {
- node.children
- .filter { sourceSetRestriction == null || it.sourceSets.any { s -> s in sourceSetRestriction } }
- .takeIf { it.isNotEmpty() }
- ?.let {
- when (pageContext) {
- is MultimoduleRootPage -> buildRowForMultiModule(node, it, pageContext, sourceSetRestriction)
- is ModulePage -> buildRowForModule(node, it, pageContext, sourceSetRestriction)
- else -> buildRowForContent(node, it, pageContext, sourceSetRestriction)
- }
- }
- }
-
- private fun FlowContent.buildRowForMultiModule(
- contextNode: ContentGroup,
- toRender: List<ContentNode>,
- pageContext: ContentPage,
- sourceSetRestriction: Set<DisplaySourceSet>?
- ) {
- buildAnchor(contextNode)
- div(classes = "table-row") {
- div("main-subrow " + contextNode.style.joinToString(separator = " ")) {
- buildRowHeaderLink(toRender, pageContext, sourceSetRestriction, contextNode.anchor, "w-100")
- div {
- buildRowBriefSectionForDocs(toRender, pageContext, sourceSetRestriction)
- }
- }
- }
- }
-
- private fun FlowContent.buildRowForModule(
- contextNode: ContentGroup,
- toRender: List<ContentNode>,
- pageContext: ContentPage,
- sourceSetRestriction: Set<DisplaySourceSet>?
- ) {
- buildAnchor(contextNode)
- div(classes = "table-row") {
- addSourceSetFilteringAttributes(contextNode)
- div {
- div("main-subrow " + contextNode.style.joinToString(separator = " ")) {
- buildRowHeaderLink(toRender, pageContext, sourceSetRestriction, contextNode.anchor)
- div("pull-right") {
- if (ContentKind.shouldBePlatformTagged(contextNode.dci.kind)) {
- createPlatformTags(contextNode, cssClasses = "no-gutters")
- }
- }
- }
- div {
- buildRowBriefSectionForDocs(toRender, pageContext, sourceSetRestriction)
- }
- }
- }
- }
-
- private fun FlowContent.buildRowForContent(
- contextNode: ContentGroup,
- toRender: List<ContentNode>,
- pageContext: ContentPage,
- sourceSetRestriction: Set<DisplaySourceSet>?
- ) {
- 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)
- div {
- toRender.filter { it !is ContentLink && !it.hasStyle(ContentStyle.RowTitle) }
- .takeIf { it.isNotEmpty() }?.let {
- div("title") {
- it.forEach {
- it.build(this, pageContext, sourceSetRestriction)
- }
- }
- }
- }
- }
- }
- }
-
- private fun FlowContent.buildRowHeaderLink(
- toRender: List<ContentNode>,
- pageContext: ContentPage,
- sourceSetRestriction: Set<DisplaySourceSet>?,
- anchorDestination: String?,
- classes: String = ""
- ) {
- toRender.filter { it is ContentLink || it.hasStyle(ContentStyle.RowTitle) }.takeIf { it.isNotEmpty() }?.let {
- div(classes) {
- it.filter { sourceSetRestriction == null || it.sourceSets.any { s -> s in sourceSetRestriction } }
- .forEach {
- span("inline-flex") {
- div {
- it.build(this, pageContext, sourceSetRestriction)
- }
- if (it is ContentLink && !anchorDestination.isNullOrBlank()) {
- buildAnchorCopyButton(anchorDestination)
- }
- }
- }
- }
- }
- }
-
- private fun FlowContent.addSourceSetFilteringAttributes(
- contextNode: ContentGroup,
- ) {
- attributes["data-filterable-current"] = contextNode.buildSourceSetFilterValues()
- attributes["data-filterable-set"] = contextNode.buildSourceSetFilterValues()
- }
-
- private fun ContentNode.buildSourceSetFilterValues(): String {
- // This value is used in HTML and JS for filtering out source set declarations,
- // it is expected that the separator is the same here and there.
- // See https://github.com/Kotlin/dokka/issues/3011#issuecomment-1568620493
- return this.sourceSets.joinToString(",") {
- it.sourceSetIDs.merged.toString()
- }
- }
-
- private fun FlowContent.buildRowBriefSectionForDocs(
- toRender: List<ContentNode>,
- pageContext: ContentPage,
- sourceSetRestriction: Set<DisplaySourceSet>?,
- ) {
- toRender.filter { it !is ContentLink }.takeIf { it.isNotEmpty() }?.let {
- it.forEach {
- span(classes = if (it.dci.kind == ContentKind.Comment) "brief-comment" else "") {
- it.build(this, pageContext, sourceSetRestriction)
- }
- }
- }
- }
-
- private fun FlowContent.createPlatformTagBubbles(sourceSets: List<DisplaySourceSet>, cssClasses: String = "") {
- div("platform-tags $cssClasses") {
- sourceSets.sortedBy { it.name }.forEach {
- div("platform-tag") {
- when (it.platform.key) {
- "common" -> classes = classes + "common-like"
- "native" -> classes = classes + "native-like"
- "jvm" -> classes = classes + "jvm-like"
- "js" -> classes = classes + "js-like"
- "wasm" -> classes = classes + "wasm-like"
- }
- text(it.name)
- }
- }
- }
- }
-
- private fun FlowContent.createPlatformTags(
- node: ContentNode,
- sourceSetRestriction: Set<DisplaySourceSet>? = null,
- cssClasses: String = ""
- ) {
- node.takeIf { sourceSetRestriction == null || it.sourceSets.any { s -> s in sourceSetRestriction } }?.let {
- createPlatformTagBubbles(node.sourceSets.filter {
- sourceSetRestriction == null || it in sourceSetRestriction
- }.sortedBy { it.name }, cssClasses)
- }
- }
-
- override fun FlowContent.buildTable(
- node: ContentTable,
- pageContext: ContentPage,
- sourceSetRestriction: Set<DisplaySourceSet>?
- ) {
- when {
- node.style.contains(CommentTable) -> buildDefaultTable(node, pageContext, sourceSetRestriction)
- else -> div(classes = "table") {
- node.extra.extraHtmlAttributes().forEach { attributes[it.extraKey] = it.extraValue }
- node.children.forEach {
- buildRow(it, pageContext, sourceSetRestriction)
- }
- }
- }
-
- }
-
- public fun FlowContent.buildDefaultTable(
- node: ContentTable,
- pageContext: ContentPage,
- sourceSetRestriction: Set<DisplaySourceSet>?
- ) {
- table {
- thead {
- node.header.forEach {
- tr {
- it.children.forEach {
- th {
- it.build(this@table, pageContext, sourceSetRestriction)
- }
- }
- }
- }
- }
- tbody {
- node.children.forEach {
- tr {
- it.children.forEach {
- td {
- it.build(this, pageContext, sourceSetRestriction)
- }
- }
- }
- }
- }
- }
- }
-
-
- override fun FlowContent.buildHeader(level: Int, node: ContentHeader, content: FlowContent.() -> Unit) {
- val classes = node.style.joinToString { it.toString() }.toLowerCase()
- when (level) {
- 1 -> h1(classes = classes, content)
- 2 -> h2(classes = classes, content)
- 3 -> h3(classes = classes, content)
- 4 -> h4(classes = classes, content)
- 5 -> h5(classes = classes, content)
- else -> h6(classes = classes, content)
- }
- }
-
- private fun FlowContent.buildAnchor(
- anchor: String,
- anchorLabel: String,
- sourceSets: String,
- content: FlowContent.() -> Unit
- ) {
- a {
- attributes["data-name"] = anchor
- attributes["anchor-label"] = anchorLabel
- attributes["id"] = anchor
- attributes["data-filterable-set"] = sourceSets
- }
- content()
- }
-
- private fun FlowContent.buildAnchor(anchor: String, anchorLabel: String, sourceSets: String) =
- buildAnchor(anchor, anchorLabel, sourceSets) {}
-
- private fun FlowContent.buildAnchor(node: ContentNode) {
- node.anchorLabel?.let { label -> buildAnchor(node.anchor!!, label, node.buildSourceSetFilterValues()) }
- }
-
-
- override fun FlowContent.buildNavigation(page: PageNode) {
- div(classes = "breadcrumbs") {
- val path = locationProvider.ancestors(page).filterNot { it is RendererSpecificPage }.asReversed()
- if (path.size > 1) {
- buildNavigationElement(path.first(), page)
- path.drop(1).forEach { node ->
- span(classes = "delimiter") {
- text("/")
- }
- buildNavigationElement(node, page)
- }
- }
- }
- }
-
- private fun FlowContent.buildNavigationElement(node: PageNode, page: PageNode) =
- if (node.isNavigable) {
- val isCurrentPage = (node == page)
- if (isCurrentPage) {
- span(classes = "current") {
- text(node.name)
- }
- } else {
- buildLink(node, page)
- }
- } else {
- text(node.name)
- }
-
- private fun FlowContent.buildLink(to: PageNode, from: PageNode) =
- locationProvider.resolve(to, from)?.let { path ->
- buildLink(path) {
- text(to.name)
- }
- } ?: span {
- attributes["data-unresolved-link"] = to.name.htmlEscape()
- text(to.name)
- }
-
- public fun FlowContent.buildAnchorCopyButton(pointingTo: String) {
- span(classes = "anchor-wrapper") {
- span(classes = "anchor-icon") {
- attributes["pointing-to"] = pointingTo
- }
- copiedPopup("Link copied to clipboard")
- }
- }
-
- public fun FlowContent.buildLink(
- to: DRI,
- platforms: List<DisplaySourceSet>,
- from: PageNode? = null,
- block: FlowContent.() -> Unit
- ) {
- locationProvider.resolve(to, platforms.toSet(), from)?.let { buildLink(it, block) }
- ?: run { context.logger.error("Cannot resolve path for `$to` from `$from`"); block() }
- }
-
- override fun buildError(node: ContentNode) {
- context.logger.error("Unknown ContentNode type: $node")
- }
-
- override fun FlowContent.buildLineBreak() {
- br()
- }
- override fun FlowContent.buildLineBreak(node: ContentBreakLine, pageContext: ContentPage) {
- if (node.style.contains(HorizontalBreakLineStyle)) {
- hr()
- } else {
- buildLineBreak()
- }
- }
-
- override fun FlowContent.buildLink(address: String, content: FlowContent.() -> Unit) {
- a(href = address, block = content)
- }
-
- override fun FlowContent.buildDRILink(
- node: ContentDRILink,
- pageContext: ContentPage,
- sourceSetRestriction: Set<DisplaySourceSet>?
- ) {
- locationProvider.resolve(node.address, node.sourceSets, pageContext)?.let { address ->
- buildLink(address) {
- buildText(node.children, pageContext, sourceSetRestriction)
- }
- } ?: if (isPartial) {
- templateCommand(ResolveLinkCommand(node.address)) {
- buildText(node.children, pageContext, sourceSetRestriction)
- }
- } else {
- span {
- attributes["data-unresolved-link"] = node.address.toString().htmlEscape()
- buildText(node.children, pageContext, sourceSetRestriction)
- }
- }
- }
-
- override fun FlowContent.buildCodeBlock(
- code: ContentCodeBlock,
- pageContext: ContentPage
- ) {
- div("sample-container") {
- val codeLang = "lang-" + code.language.ifEmpty { "kotlin" }
- val stylesWithBlock = code.style + TextStyle.Block + codeLang
- pre {
- code(stylesWithBlock.joinToString(" ") { it.toString().toLowerCase() }) {
- attributes["theme"] = "idea"
- code.children.forEach { buildContentNode(it, pageContext) }
- }
- }
- /*
- Disable copy button on samples as:
- - it is useless
- - it overflows with playground's run button
- */
- if (!code.style.contains(ContentStyle.RunnableSample)) copyButton()
- }
- }
-
- override fun FlowContent.buildCodeInline(
- code: ContentCodeInline,
- pageContext: ContentPage
- ) {
- val codeLang = "lang-" + code.language.ifEmpty { "kotlin" }
- val stylesWithBlock = code.style + codeLang
- code(stylesWithBlock.joinToString(" ") { it.toString().toLowerCase() }) {
- code.children.forEach { buildContentNode(it, pageContext) }
- }
- }
-
- override fun FlowContent.buildText(textNode: ContentText) {
- buildText(textNode, textNode.style)
- }
-
- private fun FlowContent.buildText(textNode: ContentText, unappliedStyles: Set<Style>) {
- when {
- textNode.extra[HtmlContent] != null -> {
- consumer.onTagContentUnsafe { raw(textNode.text) }
- }
- unappliedStyles.contains(TextStyle.Indented) -> {
- consumer.onTagContentEntity(Entities.nbsp)
- buildText(textNode, unappliedStyles - TextStyle.Indented)
- }
- unappliedStyles.isNotEmpty() -> {
- val styleToApply = unappliedStyles.first()
- applyStyle(styleToApply) {
- buildText(textNode, unappliedStyles - styleToApply)
- }
- }
- textNode.hasStyle(ContentStyle.RowTitle) || textNode.hasStyle(TextStyle.Cover) ->
- buildBreakableText(textNode.text)
- else -> text(textNode.text)
- }
- }
-
- private inline fun FlowContent.applyStyle(styleToApply: Style, crossinline body: FlowContent.() -> Unit) {
- when (styleToApply) {
- TextStyle.Bold -> b { body() }
- TextStyle.Italic -> i { body() }
- TextStyle.Strikethrough -> strike { body() }
- TextStyle.Strong -> strong { body() }
- TextStyle.Var -> htmlVar { body() }
- TextStyle.Underlined -> underline { body() }
- is TokenStyle -> span("token ${styleToApply.prismJsClass()}") { body() }
- else -> body()
- }
- }
-
- private fun TokenStyle.prismJsClass(): String = when(this) {
- // Prism.js parser adds Builtin token instead of Annotation
- // for some reason, so we also add it for consistency and correct coloring
- TokenStyle.Annotation -> "annotation builtin"
- else -> this.toString().toLowerCase()
- }
-
- override fun render(root: RootPageNode) {
- shouldRenderSourceSetTabs = shouldRenderSourceSetTabs(root)
- super.render(root)
- }
-
- override fun buildPage(page: ContentPage, content: (FlowContent, ContentPage) -> Unit): String =
- buildHtml(page, page.embeddedResources) {
- content(this, page)
- }
-
- private fun PageNode.getDocumentableType(): String? =
- when(this) {
- is PackagePage -> "package"
- is ClasslikePage -> "classlike"
- is MemberPage -> "member"
- else -> null
- }
-
- public open fun buildHtml(
- page: PageNode,
- resources: List<String>, content: FlowContent.() -> Unit
- ): String {
- return 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}"
- }
- content()
- }
-
- templateModelMerger.invoke(templateModelFactories) {
- buildModel(
- page,
- resources,
- locationProvider,
- generatedContent
- )
- }
- }
- }
-
- /**
- * This is deliberately left open for plugins that have some other pages above ours and would like to link to them
- * instead of ours when clicking the logo
- */
- public open fun FlowContent.clickableLogo(page: PageNode, pathToRoot: String) {
- if (context.configuration.delayTemplateSubstitution && page is ContentPage) {
- templateCommand(PathToRootSubstitutionCommand(pattern = "###", default = pathToRoot)) {
- a {
- href = "###index.html"
- templateCommand(
- ProjectNameSubstitutionCommand(
- pattern = "@@@",
- default = context.configuration.moduleName
- )
- ) {
- span {
- text("@@@")
- }
- }
- }
- }
- } else {
- a {
- href = pathToRoot + "index.html"
- text(context.configuration.moduleName)
- }
- }
- }
-
- private val ContentNode.isAnchorable: Boolean
- get() = anchorLabel != null
-
- private val ContentNode.anchorLabel: String?
- get() = extra[SymbolAnchorHint]?.anchorName
-
- private val ContentNode.anchor: String?
- get() = extra[SymbolAnchorHint]?.contentKind?.let { contentKind ->
- (locationProvider as DokkaBaseLocationProvider).anchorForDCI(DCI(dci.dri, contentKind), sourceSets)
- }
-
- 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>)
-
-public fun List<SimpleAttr>.joinAttr(): String = joinToString(" ") { it.extraKey + "=" + it.extraValue }
-
-private fun String.stripDiv() = drop(5).dropLast(6) // TODO: Find a way to do it without arbitrary trims
-
-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 DisplaySourceSet.comparableKey
- get() = sourceSetIDs.merged.let { it.scopeId + it.sourceSetName }
diff --git a/plugins/base/src/main/kotlin/renderers/html/NavigationDataProvider.kt b/plugins/base/src/main/kotlin/renderers/html/NavigationDataProvider.kt
deleted file mode 100644
index fccfd145..00000000
--- a/plugins/base/src/main/kotlin/renderers/html/NavigationDataProvider.kt
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * 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 org.jetbrains.dokka.base.renderers.sourceSets
-import org.jetbrains.dokka.base.signatures.KotlinSignatureUtils.annotations
-import org.jetbrains.dokka.base.transformers.documentables.isDeprecated
-import org.jetbrains.dokka.base.transformers.documentables.isException
-import org.jetbrains.dokka.base.utils.canonicalAlphabeticalOrder
-import org.jetbrains.dokka.model.*
-import org.jetbrains.dokka.pages.*
-import org.jetbrains.dokka.plugability.DokkaContext
-import org.jetbrains.dokka.plugability.plugin
-import org.jetbrains.dokka.plugability.querySingle
-import org.jetbrains.dokka.analysis.kotlin.internal.DocumentableLanguage
-import org.jetbrains.dokka.analysis.kotlin.internal.InternalKotlinAnalysisPlugin
-
-public abstract class NavigationDataProvider(
- dokkaContext: DokkaContext
-) {
- private val documentableSourceLanguageParser = dokkaContext.plugin<InternalKotlinAnalysisPlugin>().querySingle { documentableSourceLanguageParser }
-
- public open fun navigableChildren(input: RootPageNode): NavigationNode = input.withDescendants()
- .first { it is ModulePage || it is MultimoduleRootPage }.let { visit(it as ContentPage) }
-
- public open fun visit(page: ContentPage): NavigationNode =
- NavigationNode(
- name = page.displayableName(),
- dri = page.dri.first(),
- sourceSets = page.sourceSets(),
- icon = chooseNavigationIcon(page),
- styles = chooseStyles(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? =
- if (contentPage is WithDocumentables) {
- val documentable = contentPage.documentables.firstOrNull()
- val isJava = documentable?.hasAnyJavaSources() ?: false
-
- when (documentable) {
- is DTypeAlias -> NavigationNodeIcon.TYPEALIAS_KT
- 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 {
- return this.sourceSets.any { sourceSet ->
- documentableSourceLanguageParser.getLanguage(this, sourceSet) == DocumentableLanguage.JAVA
- }
- }
-
- private fun DClass.isAbstract() =
- modifier.values.all { it is KotlinModifier.Abstract || it is JavaModifier.Abstract }
-
- private fun chooseStyles(page: ContentPage): Set<Style> =
- if (page.containsOnlyDeprecatedDocumentables()) setOf(TextStyle.Strikethrough) else emptySet()
-
- private fun ContentPage.containsOnlyDeprecatedDocumentables(): Boolean {
- if (this !is WithDocumentables) {
- return false
- }
- return this.documentables.isNotEmpty() && this.documentables.all { it.isDeprecatedForAllSourceSets() }
- }
-
- private fun Documentable.isDeprecatedForAllSourceSets(): Boolean {
- val sourceSetAnnotations = this.annotations()
- return sourceSetAnnotations.isNotEmpty() && sourceSetAnnotations.all { (_, annotations) ->
- annotations.any { it.isDeprecated() }
- }
- }
-
- private val navigationNodeOrder: Comparator<NavigationNode> =
- compareBy(canonicalAlphabeticalOrder) { it.name }
-
- private fun ContentPage.navigableChildren() =
- if (this is ClasslikePage) {
- this.navigableChildren()
- } else {
- children
- .filterIsInstance<ContentPage>()
- .map { visit(it) }
- .sortedWith(navigationNodeOrder)
- }
-
- private fun ClasslikePage.navigableChildren(): List<NavigationNode> {
- // Classlikes should only have other classlikes as navigable children
- val navigableChildren = children
- .filterIsInstance<ClasslikePage>()
- .map { visit(it) }
-
- val isEnumPage = documentables.any { it is DEnum }
- return if (isEnumPage) {
- // no sorting for enum entries, should be the same order as in source code
- navigableChildren
- } else {
- navigableChildren.sortedWith(navigationNodeOrder)
- }
- }
-}
diff --git a/plugins/base/src/main/kotlin/renderers/html/NavigationPage.kt b/plugins/base/src/main/kotlin/renderers/html/NavigationPage.kt
deleted file mode 100644
index eae43daf..00000000
--- a/plugins/base/src/main/kotlin/renderers/html/NavigationPage.kt
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * 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.base.renderers.html.NavigationNodeIcon.CLASS
-import org.jetbrains.dokka.base.renderers.html.NavigationNodeIcon.CLASS_KT
-import org.jetbrains.dokka.base.renderers.pageId
-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.*
-import org.jetbrains.dokka.plugability.DokkaContext
-
-public class NavigationPage(
- public val root: NavigationNode,
- public val moduleName: String,
- public val context: DokkaContext
-) : RendererSpecificPage {
-
- override val name: String = "navigation"
-
- override val children: List<PageNode> = emptyList()
-
- override fun modified(name: String, children: List<PageNode>): NavigationPage = this
-
- override val strategy: RenderingStrategy = RenderingStrategy<HtmlRenderer> {
- createHTML().visit(root, this)
- }
-
- private fun <R> TagConsumer<R>.visit(node: NavigationNode, renderer: HtmlRenderer): R = with(renderer) {
- if (context.configuration.delayTemplateSubstitution) {
- templateCommand(AddToNavigationCommand(moduleName)) {
- visit(node, "${moduleName}-nav-submenu", renderer)
- }
- } else {
- visit(node, "${moduleName}-nav-submenu", renderer)
- }
- }
-
- private fun <R> TagConsumer<R>.visit(node: NavigationNode, navId: String, renderer: HtmlRenderer): R =
- with(renderer) {
- div("sideMenuPart") {
- id = navId
- attributes["pageId"] = "${moduleName}::${node.pageId}"
- div("overview") {
- if (node.children.isNotEmpty()) {
- span("navButton") {
- onClick = """document.getElementById("$navId").classList.toggle("hidden");"""
- span("navButtonContent")
- }
- }
- buildLink(node.dri, node.sourceSets.toList()) {
- val withIcon = node.icon != null
- 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") {
- nodeText(node)
- }
- }
- } else {
- nodeText(node)
- }
- }
- }
- node.children.withIndex().forEach { (n, p) -> visit(p, "$navId-$n", renderer) }
- }
- }
-
- private fun FlowContent.nodeText(node: NavigationNode) {
- if (node.styles.contains(TextStyle.Strikethrough)) {
- strike {
- buildBreakableText(node.name)
- }
- } else {
- buildBreakableText(node.name)
- }
- }
-}
-
-public data class NavigationNode(
- val name: String,
- val dri: DRI,
- val sourceSets: Set<DisplaySourceSet>,
- val icon: NavigationNodeIcon?,
- val styles: Set<Style> = emptySet(),
- override val children: List<NavigationNode>
-) : WithChildren<NavigationNode>
-
-/**
- * [CLASS] represents a neutral (a.k.a Java-style) icon,
- * whereas [CLASS_KT] should be Kotlin-styled
- */
-public 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"),
- TYPEALIAS_KT("typealias-kt"),
- VAL("val"),
- VAR("var");
-
- internal fun style(): String = "nav-icon $cssClass"
-}
-
-public fun NavigationPage.transform(block: (NavigationNode) -> NavigationNode): NavigationPage =
- NavigationPage(root.transform(block), moduleName, context)
-
-public fun NavigationNode.transform(block: (NavigationNode) -> NavigationNode): NavigationNode =
- run(block).let { NavigationNode(it.name, it.dri, it.sourceSets, it.icon, it.styles, it.children.map(block)) }
diff --git a/plugins/base/src/main/kotlin/renderers/html/SearchbarDataInstaller.kt b/plugins/base/src/main/kotlin/renderers/html/SearchbarDataInstaller.kt
deleted file mode 100644
index 83d4b24f..00000000
--- a/plugins/base/src/main/kotlin/renderers/html/SearchbarDataInstaller.kt
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * 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 com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
-import org.jetbrains.dokka.Platform
-import org.jetbrains.dokka.base.renderers.sourceSets
-import org.jetbrains.dokka.base.templating.AddToSearch
-import org.jetbrains.dokka.links.DRI
-import org.jetbrains.dokka.model.DisplaySourceSet
-import org.jetbrains.dokka.model.dfs
-import org.jetbrains.dokka.model.withDescendants
-import org.jetbrains.dokka.pages.*
-import org.jetbrains.dokka.plugability.DokkaContext
-import org.jetbrains.dokka.transformers.pages.PageTransformer
-
-public data class SearchRecord(
- val name: String,
- val description: String? = null,
- val location: String,
- val searchKeys: List<String> = listOf(name)
-) {
- public companion object
-}
-
-public open class SearchbarDataInstaller(
- public val context: DokkaContext
-) : PageTransformer {
-
- public data class DRIWithSourceSets(val dri: DRI, val sourceSet: Set<DisplaySourceSet>)
-
- public data class SignatureWithId(val driWithSourceSets: DRIWithSourceSets, val displayableSignature: String) {
- public constructor(dri: DRI, page: ContentPage) : this( DRIWithSourceSets(dri, page.sourceSets()),
- getSymbolSignature(page, dri)?.let { flattenToText(it) } ?: page.name)
-
- val id: String
- get() = with(driWithSourceSets.dri) {
- listOfNotNull(
- packageName?.takeIf { it.isNotBlank() },
- classNames,
- callable?.name
- ).joinToString(".")
- }
- }
-
- private val mapper = jacksonObjectMapper()
-
- public open fun generatePagesList(
- pages: List<SignatureWithId>,
- locationResolver: DriResolver
- ): List<SearchRecord> =
- pages.map { pageWithId ->
- createSearchRecord(
- name = pageWithId.displayableSignature,
- description = pageWithId.id,
- location = resolveLocation(locationResolver, pageWithId.driWithSourceSets).orEmpty(),
- searchKeys = listOf(
- pageWithId.id.substringAfterLast("."),
- pageWithId.displayableSignature,
- pageWithId.id,
- )
- )
- }.sortedWith(compareBy({ it.name }, { it.description }))
-
- public open fun createSearchRecord(
- name: String,
- description: String?,
- location: String,
- searchKeys: List<String>
- ): SearchRecord =
- SearchRecord(name, description, location, searchKeys)
-
- public open fun processPage(page: PageNode): List<SignatureWithId> =
- when (page) {
- is ContentPage -> page.takeIf { page !is ModulePageNode && page !is PackagePageNode }?.dri
- ?.map { dri -> SignatureWithId(dri, page) }.orEmpty()
- else -> emptyList()
- }
-
- private fun resolveLocation(locationResolver: DriResolver, driWithSourceSets: DRIWithSourceSets): String? =
- locationResolver(driWithSourceSets.dri, driWithSourceSets.sourceSet).also { location ->
- if (location.isNullOrBlank()) context.logger.warn("Cannot resolve path for ${driWithSourceSets.dri}")
- }
-
- override fun invoke(input: RootPageNode): RootPageNode {
- val signatureWithIds = input.withDescendants().fold(emptyList<SignatureWithId>()) { pageList, page ->
- pageList + processPage(page)
- }
- val page = RendererSpecificResourcePage(
- name = "scripts/pages.json",
- children = emptyList(),
- strategy = RenderingStrategy.DriLocationResolvableWrite { resolver ->
- val content = signatureWithIds.run {
- generatePagesList(this, resolver)
- }
-
- if (context.configuration.delayTemplateSubstitution) {
- mapper.writeValueAsString(AddToSearch(context.configuration.moduleName, content))
- } else {
- mapper.writeValueAsString(content)
- }
- })
-
- return input.modified(children = input.children + page)
- }
-}
-
-private fun getSymbolSignature(page: ContentPage, dri: DRI) =
- page.content.dfs { it.dci.kind == ContentKind.Symbol && it.dci.dri.contains(dri) }
-
-private fun flattenToText(node: ContentNode): String {
- fun getContentTextNodes(node: ContentNode, sourceSetRestriction: DisplaySourceSet): List<ContentText> =
- when (node) {
- is ContentText -> listOf(node)
- is ContentComposite -> node.children
- .filter { sourceSetRestriction in it.sourceSets }
- .flatMap { getContentTextNodes(it, sourceSetRestriction) }
- .takeIf { node.dci.kind != ContentKind.Annotations && node.dci.kind != ContentKind.Source }
- .orEmpty()
- else -> emptyList()
- }
-
- val sourceSetRestriction =
- node.sourceSets.find { it.platform == Platform.common } ?: node.sourceSets.first()
- return getContentTextNodes(node, sourceSetRestriction).joinToString("") { it.text }
-}
diff --git a/plugins/base/src/main/kotlin/renderers/html/Tags.kt b/plugins/base/src/main/kotlin/renderers/html/Tags.kt
deleted file mode 100644
index 7d6fc390..00000000
--- a/plugins/base/src/main/kotlin/renderers/html/Tags.kt
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * 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.base.renderers.html.command.consumers.ImmediateResolutionTagConsumer
-import org.jetbrains.dokka.base.templating.Command
-import org.jetbrains.dokka.base.templating.toJsonString
-
-public typealias TemplateBlock = TemplateCommand.() -> Unit
-
-@HtmlTagMarker
-public fun FlowOrPhrasingContent.wbr(classes: String? = null, block: WBR.() -> Unit = {}): Unit =
- WBR(attributesMapOf("class", classes), consumer).visit(block)
-
-@Suppress("unused")
-public open class WBR(initialAttributes: Map<String, String>, consumer: TagConsumer<*>) :
- HTMLTag("wbr", consumer, initialAttributes, namespace = null, inlineTag = true, emptyTag = false),
- HtmlBlockInlineTag
-
-/**
- * Work-around until next version of kotlinx.html doesn't come out
- */
-@HtmlTagMarker
-public inline fun FlowOrPhrasingContent.strike(classes : String? = null, crossinline block : STRIKE.() -> Unit = {}) : Unit = STRIKE(attributesMapOf("class", classes), consumer).visit(block)
-
-public open class STRIKE(initialAttributes: Map<String, String>, override val consumer: TagConsumer<*>) :
- HTMLTag("strike", consumer, initialAttributes, null, false, false), HtmlBlockInlineTag
-
-@HtmlTagMarker
-public inline fun FlowOrPhrasingContent.underline(classes : String? = null, crossinline block : UNDERLINE.() -> Unit = {}) : Unit = UNDERLINE(attributesMapOf("class", classes), consumer).visit(block)
-
-public open class UNDERLINE(initialAttributes: Map<String, String>, override val consumer: TagConsumer<*>) :
- HTMLTag("u", consumer, initialAttributes, null, false, false), HtmlBlockInlineTag
-
-public const val TEMPLATE_COMMAND_SEPARATOR: String = ":"
-public const val TEMPLATE_COMMAND_BEGIN_BORDER: String = "[+]cmd"
-public const val TEMPLATE_COMMAND_END_BORDER: String = "[-]cmd"
-
-public fun FlowOrMetaDataContent.templateCommandAsHtmlComment(data: Command, block: FlowOrMetaDataContent.() -> Unit = {}): Unit =
- (consumer as? ImmediateResolutionTagConsumer)?.processCommand(data, block)
- ?: let{
- comment( "$TEMPLATE_COMMAND_BEGIN_BORDER$TEMPLATE_COMMAND_SEPARATOR${toJsonString(data)}")
- block()
- comment(TEMPLATE_COMMAND_END_BORDER)
- }
-
-public fun <T: Appendable> T.templateCommandAsHtmlComment(command: Command, action: T.() -> Unit ) {
- append("<!--$TEMPLATE_COMMAND_BEGIN_BORDER$TEMPLATE_COMMAND_SEPARATOR${toJsonString(command)}-->")
- action()
- append("<!--$TEMPLATE_COMMAND_END_BORDER-->")
-}
-
-public fun FlowOrMetaDataContent.templateCommand(data: Command, block: TemplateBlock = {}): Unit =
- (consumer as? ImmediateResolutionTagConsumer)?.processCommand(data, block)
- ?: TemplateCommand(attributesMapOf("data", toJsonString(data)), consumer).visit(block)
-
-public fun <T> TagConsumer<T>.templateCommand(data: Command, block: TemplateBlock = {}): T =
- (this as? ImmediateResolutionTagConsumer)?.processCommandAndFinalize(data, block)
- ?: TemplateCommand(attributesMapOf("data", toJsonString(data)), this).visitAndFinalize(this, block)
-
-public fun templateCommandFor(data: Command, consumer: TagConsumer<*>): TemplateCommand =
- TemplateCommand(attributesMapOf("data", toJsonString(data)), consumer)
-
-public class TemplateCommand(initialAttributes: Map<String, String>, consumer: TagConsumer<*>) :
- HTMLTag(
- "dokka-template-command",
- consumer,
- initialAttributes,
- namespace = null,
- inlineTag = true,
- emptyTag = false
- ),
- CommonAttributeGroupFacadeFlowInteractivePhrasingContent
-
-// This hack is outrageous. I hate it but I cannot find any other way around `kotlinx.html` type system.
-public fun TemplateBlock.buildAsInnerHtml(): String = createHTML(prettyPrint = false).run {
- TemplateCommand(emptyMap, this).visitAndFinalize(this, this@buildAsInnerHtml).substringAfter(">").substringBeforeLast("<")
-}
diff --git a/plugins/base/src/main/kotlin/renderers/html/command/consumers/ImmediateResolutionTagConsumer.kt b/plugins/base/src/main/kotlin/renderers/html/command/consumers/ImmediateResolutionTagConsumer.kt
deleted file mode 100644
index 9cde1fca..00000000
--- a/plugins/base/src/main/kotlin/renderers/html/command/consumers/ImmediateResolutionTagConsumer.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * 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.command.consumers
-
-import kotlinx.html.TagConsumer
-import kotlinx.html.visit
-import org.jetbrains.dokka.base.DokkaBase
-import org.jetbrains.dokka.base.renderers.html.TemplateBlock
-import org.jetbrains.dokka.base.renderers.html.templateCommand
-import org.jetbrains.dokka.base.renderers.html.templateCommandFor
-import org.jetbrains.dokka.base.templating.Command
-import org.jetbrains.dokka.plugability.DokkaContext
-import org.jetbrains.dokka.plugability.plugin
-import org.jetbrains.dokka.plugability.query
-
-public class ImmediateResolutionTagConsumer<out R>(
- private val downstream: TagConsumer<R>,
- private val context: DokkaContext
-): TagConsumer<R> by downstream {
-
- public fun processCommand(command: Command, block: TemplateBlock) {
- context.plugin<DokkaBase>().query { immediateHtmlCommandConsumer }
- .find { it.canProcess(command) }
- ?.processCommand(command, block, this)
- ?: run { templateCommandFor(command, downstream).visit(block) }
- }
-
- public fun processCommandAndFinalize(command: Command, block: TemplateBlock): R {
- return context.plugin<DokkaBase>().query { immediateHtmlCommandConsumer }
- .find { it.canProcess(command) }
- ?.processCommandAndFinalize(command, block, this)
- ?: downstream.templateCommand(command, block)
- }
-}
-
diff --git a/plugins/base/src/main/kotlin/renderers/html/command/consumers/PathToRootConsumer.kt b/plugins/base/src/main/kotlin/renderers/html/command/consumers/PathToRootConsumer.kt
deleted file mode 100644
index 9ac6eb91..00000000
--- a/plugins/base/src/main/kotlin/renderers/html/command/consumers/PathToRootConsumer.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * 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.command.consumers
-
-import org.jetbrains.dokka.base.renderers.html.TemplateBlock
-import org.jetbrains.dokka.base.renderers.html.buildAsInnerHtml
-import org.jetbrains.dokka.base.templating.Command
-import org.jetbrains.dokka.base.templating.ImmediateHtmlCommandConsumer
-import org.jetbrains.dokka.base.templating.PathToRootSubstitutionCommand
-
-public object PathToRootConsumer: ImmediateHtmlCommandConsumer {
- override fun canProcess(command: Command): Boolean = command is PathToRootSubstitutionCommand
-
- override fun <R> processCommand(command: Command, block: TemplateBlock, tagConsumer: ImmediateResolutionTagConsumer<R>) {
- command as PathToRootSubstitutionCommand
- tagConsumer.onTagContentUnsafe { +block.buildAsInnerHtml().replace(command.pattern, command.default) }
- }
-
- override fun <R> processCommandAndFinalize(command: Command, block: TemplateBlock, tagConsumer: ImmediateResolutionTagConsumer<R>): R {
- processCommand(command, block, tagConsumer)
- return tagConsumer.finalize()
- }
-
-}
diff --git a/plugins/base/src/main/kotlin/renderers/html/command/consumers/ReplaceVersionsConsumer.kt b/plugins/base/src/main/kotlin/renderers/html/command/consumers/ReplaceVersionsConsumer.kt
deleted file mode 100644
index dd95c202..00000000
--- a/plugins/base/src/main/kotlin/renderers/html/command/consumers/ReplaceVersionsConsumer.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * 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.command.consumers
-
-import org.jetbrains.dokka.base.renderers.html.TemplateBlock
-import org.jetbrains.dokka.base.templating.Command
-import org.jetbrains.dokka.base.templating.ImmediateHtmlCommandConsumer
-import org.jetbrains.dokka.base.templating.ReplaceVersionsCommand
-import org.jetbrains.dokka.plugability.DokkaContext
-
-public class ReplaceVersionsConsumer(private val context: DokkaContext) : ImmediateHtmlCommandConsumer {
- override fun canProcess(command: Command): Boolean = command is ReplaceVersionsCommand
-
- override fun <R> processCommand(
- command: Command,
- block: TemplateBlock,
- tagConsumer: ImmediateResolutionTagConsumer<R>
- ) {
- command as ReplaceVersionsCommand
- tagConsumer.onTagContentUnsafe { +context.configuration.moduleVersion.orEmpty() }
- }
-
- override fun <R> processCommandAndFinalize(command: Command, block: TemplateBlock, tagConsumer: ImmediateResolutionTagConsumer<R>): R {
- processCommand(command, block, tagConsumer)
- return tagConsumer.finalize()
- }
-}
diff --git a/plugins/base/src/main/kotlin/renderers/html/command/consumers/ResolveLinkConsumer.kt b/plugins/base/src/main/kotlin/renderers/html/command/consumers/ResolveLinkConsumer.kt
deleted file mode 100644
index 292e88b0..00000000
--- a/plugins/base/src/main/kotlin/renderers/html/command/consumers/ResolveLinkConsumer.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * 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.command.consumers
-
-import kotlinx.html.SPAN
-import kotlinx.html.span
-import kotlinx.html.unsafe
-import kotlinx.html.visit
-import org.jetbrains.dokka.base.renderers.html.TemplateBlock
-import org.jetbrains.dokka.base.renderers.html.buildAsInnerHtml
-import org.jetbrains.dokka.base.templating.Command
-import org.jetbrains.dokka.base.templating.ImmediateHtmlCommandConsumer
-import org.jetbrains.dokka.base.templating.ResolveLinkCommand
-import org.jetbrains.dokka.utilities.htmlEscape
-
-public object ResolveLinkConsumer: ImmediateHtmlCommandConsumer {
- override fun canProcess(command: Command): Boolean = command is ResolveLinkCommand
-
- override fun <R> processCommand(command: Command, block: TemplateBlock, tagConsumer: ImmediateResolutionTagConsumer<R>) {
- command as ResolveLinkCommand
- SPAN(mapOf("data-unresolved-link" to command.dri.toString().htmlEscape()), tagConsumer).visit {
- unsafe { block.buildAsInnerHtml() }
- }
- }
-
- override fun <R> processCommandAndFinalize(command: Command, block: TemplateBlock, tagConsumer: ImmediateResolutionTagConsumer<R>): R {
- command as ResolveLinkCommand
- return tagConsumer.span {
- attributes["data-unresolved-link"] = command.dri.toString().htmlEscape()
- }
- }
-}
diff --git a/plugins/base/src/main/kotlin/renderers/html/htmlFormatingUtils.kt b/plugins/base/src/main/kotlin/renderers/html/htmlFormatingUtils.kt
deleted file mode 100644
index b6ce4147..00000000
--- a/plugins/base/src/main/kotlin/renderers/html/htmlFormatingUtils.kt
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * 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.FlowContent
-import kotlinx.html.span
-
-public fun FlowContent.buildTextBreakableAfterCapitalLetters(name: String, hasLastElement: Boolean = false) {
- if (name.contains(" ")) {
- val withOutSpaces = name.split(" ")
- withOutSpaces.dropLast(1).forEach {
- buildBreakableText(it)
- +" "
- }
- buildBreakableText(withOutSpaces.last())
- } else {
- val content = name.replace(Regex("(?<=[a-z])([A-Z])"), " $1").split(" ")
- joinToHtml(content, hasLastElement) {
- it
- }
- }
-}
-
-public fun FlowContent.buildBreakableDotSeparatedHtml(name: String) {
- val phrases = name.split(".")
- phrases.forEachIndexed { i, e ->
- val elementWithOptionalDot = e.takeIf { i == phrases.lastIndex } ?: "$e."
- if (e.length > 10) {
- buildTextBreakableAfterCapitalLetters(elementWithOptionalDot, hasLastElement = i == phrases.lastIndex)
- } else {
- buildBreakableHtmlElement(elementWithOptionalDot, i == phrases.lastIndex)
- }
- }
-}
-
-private fun FlowContent.joinToHtml(elements: List<String>, hasLastElement: Boolean = true, onEach: (String) -> String) {
- elements.dropLast(1).forEach {
- buildBreakableHtmlElement(onEach(it))
- }
- elements.takeIf { it.isNotEmpty() && it.last().isNotEmpty() }?.let {
- if (hasLastElement) {
- span {
- buildBreakableHtmlElement(it.last(), last = true)
- }
- } else {
- buildBreakableHtmlElement(it.last(), last = false)
- }
- }
-}
-
-private fun FlowContent.buildBreakableHtmlElement(element: String, last: Boolean = false) {
- element.takeIf { it.isNotBlank() }?.let {
- span {
- +it
- }
- }
- if (!last) {
- wbr { }
- }
-}
-
-public fun FlowContent.buildBreakableText(name: String) {
- if (name.contains(".")) buildBreakableDotSeparatedHtml(name)
- else buildTextBreakableAfterCapitalLetters(name, hasLastElement = true)
-}
diff --git a/plugins/base/src/main/kotlin/renderers/html/htmlPreprocessors.kt b/plugins/base/src/main/kotlin/renderers/html/htmlPreprocessors.kt
deleted file mode 100644
index dad013e2..00000000
--- a/plugins/base/src/main/kotlin/renderers/html/htmlPreprocessors.kt
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * 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 org.jetbrains.dokka.base.DokkaBase
-import org.jetbrains.dokka.base.DokkaBaseConfiguration
-import org.jetbrains.dokka.base.templating.AddToSourcesetDependencies
-import org.jetbrains.dokka.base.templating.toJsonString
-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
-
-public open class NavigationPageInstaller(
- public val context: DokkaContext
-) : NavigationDataProvider(context), PageTransformer {
- override fun invoke(input: RootPageNode): RootPageNode =
- input.modified(
- children = input.children + NavigationPage(
- root = navigableChildren(input),
- moduleName = context.configuration.moduleName,
- context = context
- )
- )
-}
-
-public class CustomResourceInstaller(
- public val dokkaContext: DokkaContext
-) : PageTransformer {
- private val configuration = configuration<DokkaBase, DokkaBaseConfiguration>(dokkaContext)
-
- private val customAssets = configuration?.customAssets?.map {
- RendererSpecificResourcePage("images/${it.name}", emptyList(), RenderingStrategy.Copy(it.absolutePath))
- }.orEmpty()
-
- private val customStylesheets = configuration?.customStyleSheets?.map {
- RendererSpecificResourcePage("styles/${it.name}", emptyList(), RenderingStrategy.Copy(it.absolutePath))
- }.orEmpty()
-
- override fun invoke(input: RootPageNode): RootPageNode {
- val customResourcesPaths = (customAssets + customStylesheets).map { it.name }.toSet()
- val withEmbeddedResources =
- input.transformContentPagesTree { it.modified(embeddedResources = it.embeddedResources + customResourcesPaths) }
- if(dokkaContext.configuration.delayTemplateSubstitution)
- return withEmbeddedResources
- val (currentResources, otherPages) = withEmbeddedResources.children.partition { it is RendererSpecificResourcePage }
- return input.modified(children = otherPages + currentResources.filterNot { it.name in customResourcesPaths } + customAssets + customStylesheets)
- }
-}
-
-public class ScriptsInstaller(private val dokkaContext: DokkaContext) : PageTransformer {
-
- // scripts ending with `_deferred.js` are loaded with `defer`, otherwise `async`
- private val scriptsPages = listOf(
- "scripts/clipboard.js",
- "scripts/navigation-loader.js",
- "scripts/platform-content-handler.js",
- "scripts/main.js",
- "scripts/prism.js",
-
- // It's important for this script to be deferred because it has logic that makes decisions based on
- // rendered elements (for instance taking their clientWidth), and if not all styles are loaded/applied
- // at the time of inspecting them, it will give incorrect results and might lead to visual bugs.
- // should be easy to test if you open any page in incognito or by reloading it (Ctrl+Shift+R)
- "scripts/symbol-parameters-wrapper_deferred.js",
- )
-
- override fun invoke(input: RootPageNode): RootPageNode =
- input.let { root ->
- if (dokkaContext.configuration.delayTemplateSubstitution) root
- else root.modified(children = input.children + scriptsPages.toRenderSpecificResourcePage())
- }.transformContentPagesTree {
- it.modified(
- embeddedResources = it.embeddedResources + scriptsPages
- )
- }
-}
-
-public class StylesInstaller(private val dokkaContext: DokkaContext) : PageTransformer {
- private val stylesPages = listOf(
- "styles/style.css",
- "styles/main.css",
- "styles/prism.css",
- "styles/logo-styles.css",
- "styles/font-jb-sans-auto.css"
- )
-
- override fun invoke(input: RootPageNode): RootPageNode =
- input.let { root ->
- if (dokkaContext.configuration.delayTemplateSubstitution) root
- else root.modified(children = input.children + stylesPages.toRenderSpecificResourcePage())
- }.transformContentPagesTree {
- it.modified(
- embeddedResources = it.embeddedResources + stylesPages
- )
- }
-}
-
-public object AssetsInstaller : PageTransformer {
- private val imagesPages = listOf(
- "images/arrow_down.svg",
- "images/logo-icon.svg",
- "images/go-to-top-icon.svg",
- "images/footer-go-to-link.svg",
- "images/anchor-copy-button.svg",
- "images/copy-icon.svg",
- "images/copy-successful-icon.svg",
- "images/theme-toggle.svg",
- "images/burger.svg",
- "images/homepage.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",
- "images/nav-icons/typealias-kotlin.svg",
- )
-
- override fun invoke(input: RootPageNode): RootPageNode = input.modified(
- children = input.children + imagesPages.toRenderSpecificResourcePage()
- )
-}
-
-private fun List<String>.toRenderSpecificResourcePage(): List<RendererSpecificResourcePage> =
- map { RendererSpecificResourcePage(it, emptyList(), RenderingStrategy.Copy("/dokka/$it")) }
-
-public class SourcesetDependencyAppender(
- public val context: DokkaContext
-) : PageTransformer {
- private val name = "scripts/sourceset_dependencies.js"
- override fun invoke(input: RootPageNode): RootPageNode {
- val dependenciesMap = context.configuration.sourceSets.associate {
- it.sourceSetID to it.dependentSourceSets
- }
-
- fun createDependenciesJson(): String =
- dependenciesMap.map { (key, values) -> key.toString() to values.map { it.toString() } }.toMap()
- .let { content ->
- if (context.configuration.delayTemplateSubstitution) {
- toJsonString(AddToSourcesetDependencies(context.configuration.moduleName, content))
- } else {
- "sourceset_dependencies='${toJsonString(content)}'"
- }
- }
-
- val deps = RendererSpecificResourcePage(
- name = name,
- children = emptyList(),
- strategy = RenderingStrategy.Write(createDependenciesJson())
- )
-
- return input.modified(
- children = input.children + deps
- ).transformContentPagesTree { it.modified(embeddedResources = it.embeddedResources + name) }
- }
-}
diff --git a/plugins/base/src/main/kotlin/renderers/html/innerTemplating/DefaultTemplateModelFactory.kt b/plugins/base/src/main/kotlin/renderers/html/innerTemplating/DefaultTemplateModelFactory.kt
deleted file mode 100644
index fe6f0089..00000000
--- a/plugins/base/src/main/kotlin/renderers/html/innerTemplating/DefaultTemplateModelFactory.kt
+++ /dev/null
@@ -1,234 +0,0 @@
-/*
- * 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.innerTemplating
-
-import freemarker.core.Environment
-import freemarker.template.*
-import kotlinx.html.*
-import kotlinx.html.stream.createHTML
-import org.jetbrains.dokka.DokkaConfiguration
-import org.jetbrains.dokka.base.DokkaBase
-import org.jetbrains.dokka.base.DokkaBaseConfiguration
-import org.jetbrains.dokka.base.renderers.URIExtension
-import org.jetbrains.dokka.base.renderers.html.TEMPLATE_REPLACEMENT
-import org.jetbrains.dokka.base.renderers.html.command.consumers.ImmediateResolutionTagConsumer
-import org.jetbrains.dokka.base.renderers.html.templateCommand
-import org.jetbrains.dokka.base.renderers.html.templateCommandAsHtmlComment
-import org.jetbrains.dokka.base.renderers.isImage
-import org.jetbrains.dokka.base.resolvers.local.LocationProvider
-import org.jetbrains.dokka.base.templating.PathToRootSubstitutionCommand
-import org.jetbrains.dokka.base.templating.ProjectNameSubstitutionCommand
-import org.jetbrains.dokka.base.templating.ReplaceVersionsCommand
-import org.jetbrains.dokka.base.templating.SubstitutionCommand
-import org.jetbrains.dokka.model.DisplaySourceSet
-import org.jetbrains.dokka.model.withDescendants
-import org.jetbrains.dokka.pages.ContentPage
-import org.jetbrains.dokka.pages.PageNode
-import org.jetbrains.dokka.plugability.DokkaContext
-import org.jetbrains.dokka.plugability.configuration
-import java.net.URI
-
-public class DefaultTemplateModelFactory(
- public val context: DokkaContext
-) : TemplateModelFactory {
- private val configuration = configuration<DokkaBase, DokkaBaseConfiguration>(context)
- private val isPartial = context.configuration.delayTemplateSubstitution
-
- private fun <R> TagConsumer<R>.prepareForTemplates() =
- if (context.configuration.delayTemplateSubstitution || this is ImmediateResolutionTagConsumer) this
- else ImmediateResolutionTagConsumer(this, context)
-
- public data class SourceSetModel(val name: String, val platform: String, val filter: String)
-
- override fun buildModel(
- page: PageNode,
- resources: List<String>,
- locationProvider: LocationProvider,
- content: String
- ): TemplateMap {
- val path = locationProvider.resolve(page)
- val pathToRoot = locationProvider.pathToRoot(page)
- val mapper = mutableMapOf<String, Any>()
- mapper["pageName"] = page.name
- mapper["resources"] = PrintDirective {
- val sb = StringBuilder()
- if (isPartial)
- sb.templateCommandAsHtmlComment(
- PathToRootSubstitutionCommand(
- TEMPLATE_REPLACEMENT,
- default = pathToRoot
- )
- ) { resourcesForPage(TEMPLATE_REPLACEMENT, resources) }
- else
- sb.resourcesForPage(pathToRoot, resources)
- sb.toString()
- }
- mapper["content"] = PrintDirective { content }
- mapper["version"] = PrintDirective {
- createHTML().prepareForTemplates().templateCommand(ReplaceVersionsCommand(path.orEmpty()))
- }
- mapper["template_cmd"] = TemplateDirective(context.configuration, pathToRoot)
-
- if (page is ContentPage) {
- val sourceSets = page.content.withDescendants()
- .flatMap { it.sourceSets }
- .distinct()
- .sortedBy { it.comparableKey }
- .map { SourceSetModel(it.name, it.platform.key, it.sourceSetIDs.merged.toString()) }
- .toList()
-
- if (sourceSets.isNotEmpty()) {
- mapper["sourceSets"] = sourceSets
- }
- }
- return mapper
- }
-
- override fun buildSharedModel(): TemplateMap {
- val mapper = mutableMapOf<String, Any>()
-
- mapper["footerMessage"] =
- (configuration?.footerMessage?.takeIf(String::isNotBlank) ?: DokkaBaseConfiguration.defaultFooterMessage)
-
- configuration?.homepageLink?.takeIf(String::isNotBlank)?.let { mapper["homepageLink"] = it }
-
- return mapper
- }
-
- private val DisplaySourceSet.comparableKey
- get() = sourceSetIDs.merged.let { it.scopeId + it.sourceSetName }
- private val String.isAbsolute: Boolean
- get() = URI(this).isAbsolute
-
- private fun Appendable.resourcesForPage(pathToRoot: String, resources: List<String>): Unit =
- resources.forEach { resource ->
-
- val resourceHtml = with(createHTML()) {
- when {
-
- resource.URIExtension == "css" ->
- link(
- rel = LinkRel.stylesheet,
- href = if (resource.isAbsolute) resource else "$pathToRoot$resource"
- )
-
- resource.URIExtension == "js" ->
- script(
- type = ScriptType.textJavaScript,
- src = if (resource.isAbsolute) resource else "$pathToRoot$resource"
- ) {
- if (resource == "scripts/main.js" || resource.endsWith("_deferred.js"))
- defer = true
- else
- async = true
- }
-
- resource.isImage() -> link(href = if (resource.isAbsolute) resource else "$pathToRoot$resource")
- else -> null
- }
- }
- if (resourceHtml != null) {
- append(resourceHtml)
- }
- }
-
-}
-
-private class PrintDirective(val generateData: () -> String) : TemplateDirectiveModel {
- override fun execute(
- env: Environment,
- params: MutableMap<Any?, Any?>?,
- loopVars: Array<TemplateModel>?,
- body: TemplateDirectiveBody?
- ) {
- if (params?.isNotEmpty() == true) throw TemplateModelException(
- "Parameters are not allowed"
- )
- if (loopVars?.isNotEmpty() == true) throw TemplateModelException(
- "Loop variables are not allowed"
- )
- env.out.write(generateData())
- }
-}
-
-private class TemplateDirective(
- val configuration: DokkaConfiguration,
- val pathToRoot: String
-) : TemplateDirectiveModel {
- override fun execute(
- env: Environment,
- params: MutableMap<Any?, Any?>?,
- loopVars: Array<TemplateModel>?,
- body: TemplateDirectiveBody?
- ) {
- val commandName = params?.get(PARAM_NAME) ?: throw TemplateModelException(
- "The required $PARAM_NAME parameter is missing."
- )
- val replacement = (params[PARAM_REPLACEMENT] as? SimpleScalar)?.asString ?: TEMPLATE_REPLACEMENT
-
- when ((commandName as? SimpleScalar)?.asString) {
- "pathToRoot" -> {
- body ?: throw TemplateModelException(
- "No directive body for $commandName command."
- )
- executeSubstituteCommand(
- PathToRootSubstitutionCommand(
- replacement, pathToRoot
- ),
- "pathToRoot",
- pathToRoot,
- Context(env, body)
- )
- }
-
- "projectName" -> {
- body ?: throw TemplateModelException(
- "No directive body $commandName command."
- )
- executeSubstituteCommand(
- ProjectNameSubstitutionCommand(
- replacement, configuration.moduleName
- ),
- "projectName",
- configuration.moduleName,
- Context(env, body)
- )
- }
-
- else -> throw TemplateModelException(
- "The parameter $PARAM_NAME $commandName is unknown"
- )
- }
- }
-
- private data class Context(val env: Environment, val body: TemplateDirectiveBody)
-
- private fun executeSubstituteCommand(
- command: SubstitutionCommand,
- name: String,
- value: String,
- ctx: Context
- ) {
- if (configuration.delayTemplateSubstitution)
- ctx.env.out.templateCommandAsHtmlComment(command) {
- renderWithLocalVar(name, command.pattern, ctx)
- }
- else {
- renderWithLocalVar(name, value, ctx)
- }
- }
-
- private fun renderWithLocalVar(name: String, value: String, ctx: Context) =
- with(ctx) {
- env.setVariable(name, SimpleScalar(value))
- body.render(env.out)
- env.setVariable(name, null)
- }
-
- companion object {
- const val PARAM_NAME = "name"
- const val PARAM_REPLACEMENT = "replacement"
- }
-}
diff --git a/plugins/base/src/main/kotlin/renderers/html/innerTemplating/DefaultTemplateModelMerger.kt b/plugins/base/src/main/kotlin/renderers/html/innerTemplating/DefaultTemplateModelMerger.kt
deleted file mode 100644
index 2f17183d..00000000
--- a/plugins/base/src/main/kotlin/renderers/html/innerTemplating/DefaultTemplateModelMerger.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * 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.innerTemplating
-
-public class DefaultTemplateModelMerger : TemplateModelMerger {
- override fun invoke(
- factories: List<TemplateModelFactory>,
- buildModel: TemplateModelFactory.() -> TemplateMap
- ): TemplateMap {
- val mapper = mutableMapOf<String, Any?>()
- factories.map(buildModel).forEach { partialModel ->
- partialModel.forEach { (k, v) ->
- mapper[k] = v
- }
- }
- return mapper
- }
-}
diff --git a/plugins/base/src/main/kotlin/renderers/html/innerTemplating/HtmlTemplater.kt b/plugins/base/src/main/kotlin/renderers/html/innerTemplating/HtmlTemplater.kt
deleted file mode 100644
index 1638c9c0..00000000
--- a/plugins/base/src/main/kotlin/renderers/html/innerTemplating/HtmlTemplater.kt
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * 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.innerTemplating
-
-import freemarker.cache.ClassTemplateLoader
-import freemarker.cache.FileTemplateLoader
-import freemarker.cache.MultiTemplateLoader
-import freemarker.log.Logger
-import freemarker.template.Configuration
-import freemarker.template.TemplateExceptionHandler
-import org.jetbrains.dokka.base.DokkaBase
-import org.jetbrains.dokka.base.DokkaBaseConfiguration
-import org.jetbrains.dokka.plugability.DokkaContext
-import org.jetbrains.dokka.plugability.configuration
-import java.io.StringWriter
-
-
-public enum class DokkaTemplateTypes(
- public val path: String
-) {
- BASE("base.ftl")
-}
-
-public typealias TemplateMap = Map<String, Any?>
-
-public class HtmlTemplater(
- context: DokkaContext
-) {
-
- init {
- // to disable logging, but it isn't reliable see [Logger.SYSTEM_PROPERTY_NAME_LOGGER_LIBRARY]
- // (use SLF4j further)
- System.setProperty(
- Logger.SYSTEM_PROPERTY_NAME_LOGGER_LIBRARY,
- System.getProperty(Logger.SYSTEM_PROPERTY_NAME_LOGGER_LIBRARY) ?: Logger.LIBRARY_NAME_NONE
- )
- }
-
- private val configuration = configuration<DokkaBase, DokkaBaseConfiguration>(context)
- private val templaterConfiguration =
- Configuration(Configuration.VERSION_2_3_31).apply { configureTemplateEngine() }
-
- private fun Configuration.configureTemplateEngine() {
- val loaderFromResources = ClassTemplateLoader(javaClass, "/dokka/templates")
- templateLoader = configuration?.templatesDir?.let {
- MultiTemplateLoader(
- arrayOf(
- FileTemplateLoader(it),
- loaderFromResources
- )
- )
- } ?: loaderFromResources
-
- unsetLocale()
- defaultEncoding = "UTF-8"
- templateExceptionHandler = TemplateExceptionHandler.RETHROW_HANDLER
- logTemplateExceptions = false
- wrapUncheckedExceptions = true
- fallbackOnNullLoopVariable = false
- templateUpdateDelayMilliseconds = Long.MAX_VALUE
- }
-
- public fun setupSharedModel(model: TemplateMap) {
- templaterConfiguration.setSharedVariables(model)
- }
-
- public fun renderFromTemplate(
- templateType: DokkaTemplateTypes,
- generateModel: () -> TemplateMap
- ): String {
- val out = StringWriter()
- // Freemarker has own thread-safe cache to keep templates
- val template = templaterConfiguration.getTemplate(templateType.path)
- val model = generateModel()
- template.process(model, out)
-
- return out.toString()
- }
-}
-
diff --git a/plugins/base/src/main/kotlin/renderers/html/innerTemplating/TemplateModelFactory.kt b/plugins/base/src/main/kotlin/renderers/html/innerTemplating/TemplateModelFactory.kt
deleted file mode 100644
index 3af11bf9..00000000
--- a/plugins/base/src/main/kotlin/renderers/html/innerTemplating/TemplateModelFactory.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * 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.innerTemplating
-
-import org.jetbrains.dokka.base.resolvers.local.LocationProvider
-import org.jetbrains.dokka.pages.PageNode
-
-public interface TemplateModelFactory {
- public fun buildModel(
- page: PageNode,
- resources: List<String>,
- locationProvider: LocationProvider,
- content: String
- ): TemplateMap
-
- public fun buildSharedModel(): TemplateMap
-}
diff --git a/plugins/base/src/main/kotlin/renderers/html/innerTemplating/TemplateModelMerger.kt b/plugins/base/src/main/kotlin/renderers/html/innerTemplating/TemplateModelMerger.kt
deleted file mode 100644
index ada0c6cd..00000000
--- a/plugins/base/src/main/kotlin/renderers/html/innerTemplating/TemplateModelMerger.kt
+++ /dev/null
@@ -1,9 +0,0 @@
-/*
- * 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.innerTemplating
-
-public fun interface TemplateModelMerger {
- public fun invoke(factories: List<TemplateModelFactory>, buildModel: TemplateModelFactory.() -> TemplateMap): TemplateMap
-}
diff --git a/plugins/base/src/main/kotlin/renderers/html/shouldRenderSourceSetBubbles.kt b/plugins/base/src/main/kotlin/renderers/html/shouldRenderSourceSetBubbles.kt
deleted file mode 100644
index a7bafadb..00000000
--- a/plugins/base/src/main/kotlin/renderers/html/shouldRenderSourceSetBubbles.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * 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 org.jetbrains.dokka.model.withDescendants
-import org.jetbrains.dokka.pages.ContentPage
-import org.jetbrains.dokka.pages.RootPageNode
-
-internal fun shouldRenderSourceSetTabs(page: RootPageNode): Boolean {
- return page.withDescendants()
- .flatMap { pageNode ->
- if (pageNode is ContentPage) pageNode.content.withDescendants()
- else emptySequence()
- }
- .flatMap { contentNode -> contentNode.sourceSets }
- .distinct()
- .count() > 1
-}