From 78850b5786b7b2a767db1dbd7132a374b2f4f227 Mon Sep 17 00:00:00 2001 From: Marcin Aman Date: Wed, 25 Nov 2020 11:54:20 +0100 Subject: Make searchbar an extension point (#1615) * Make searchbar an extension point * Change SearchbarDataInstaller to be a preprocessor * Split navigation and navigation search --- .../src/main/kotlin/renderers/html/HtmlRenderer.kt | 15 --- .../renderers/html/SearchbarDataInstaller.kt | 125 +++++++++++++-------- .../kotlin/renderers/html/htmlPreprocessors.kt | 70 +++++++----- 3 files changed, 120 insertions(+), 90 deletions(-) (limited to 'plugins/base/src/main/kotlin/renderers/html') diff --git a/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt b/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt index 615bbfc3..d943fefb 100644 --- a/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt +++ b/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt @@ -41,8 +41,6 @@ open class HtmlRenderer( 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 { @@ -682,14 +680,6 @@ open class HtmlRenderer( } } - - override suspend fun renderPage(page: PageNode) { - super.renderPage(page) - if (page is ContentPage && page !is ModulePageNode && page !is PackagePageNode) - searchbarDataInstaller.processPage(page, locationProvider.resolve(page) - ?: run { context.logger.error("Cannot resolve path for ${page.dri}"); "" }) - } - override fun FlowContent.buildText(textNode: ContentText) = when { textNode.hasStyle(TextStyle.Indented) -> { @@ -703,11 +693,6 @@ open class HtmlRenderer( override fun render(root: RootPageNode) { shouldRenderSourceSetBubbles = shouldRenderSourceSetBubbles(root) super.render(root) - runBlocking(Dispatchers.Default) { - launch { - outputWriter.write("scripts/pages", "var pages = ${searchbarDataInstaller.generatePagesList()}", ".js") - } - } } private fun PageNode.root(path: String) = locationProvider.pathToRoot(this) + path diff --git a/plugins/base/src/main/kotlin/renderers/html/SearchbarDataInstaller.kt b/plugins/base/src/main/kotlin/renderers/html/SearchbarDataInstaller.kt index 3c562315..6ef6a6ec 100644 --- a/plugins/base/src/main/kotlin/renderers/html/SearchbarDataInstaller.kt +++ b/plugins/base/src/main/kotlin/renderers/html/SearchbarDataInstaller.kt @@ -4,74 +4,107 @@ import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import org.jetbrains.dokka.Platform 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 import java.util.concurrent.ConcurrentHashMap -data class SearchRecord(val name: String, val description: String? = null, val location: String, val searchKeys: List = listOf(name)) { - companion object { } +typealias PageId = String +typealias Json = String + +data class SearchRecord( + val name: String, + val description: String? = null, + val location: String, + val searchKeys: List = listOf(name) +) { + companion object {} } -open class SearchbarDataInstaller { - private val mapper = jacksonObjectMapper() +open class SearchbarDataInstaller(val context: DokkaContext) : PageTransformer { + data class PageWithId(val id: PageId, val page: ContentPage) { + val displayableSignature = getSymbolSignature(page)?.let { flattenToText(it) } ?: page.name + } - private val pageList = ConcurrentHashMap>() + private val mapper = jacksonObjectMapper() - open fun generatePagesList(): String { - val pages = pageList.entries + open fun generatePagesList(pages: Map, locationResolver: PageResolver): Json = + pages.entries .filter { it.key.isNotEmpty() } - .sortedWith(compareBy({ it.key }, { it.value.first }, { it.value.second })) + .sortedWith(compareBy({ it.key }, { it.value.displayableSignature })) .groupBy { it.key.substringAfterLast(".") } .entries .flatMap { entry -> entry.value.map { subentry -> - val name = subentry.value.first createSearchRecord( - name = name, + name = subentry.value.displayableSignature, description = subentry.key, - location = subentry.value.second, - searchKeys = listOf(entry.key, name) + location = resolveLocation(locationResolver, subentry.value.page).orEmpty(), + searchKeys = listOf(entry.key, subentry.value.displayableSignature) ) } - } - return mapper.writeValueAsString(pages) - } + }.run { mapper.writeValueAsString(this) } - open fun createSearchRecord(name: String, description: String?, location: String, searchKeys: List): SearchRecord = + open fun createSearchRecord( + name: String, + description: String?, + location: String, + searchKeys: List + ): SearchRecord = SearchRecord(name, description, location, searchKeys) + open fun processPage(page: PageNode): PageWithId? = + when (page) { + is ContentPage -> page.takeIf { page !is ModulePageNode && page !is PackagePageNode }?.documentable + ?.let { documentable -> + listOfNotNull( + documentable.dri.packageName, + documentable.dri.classNames, + documentable.dri.callable?.name + ).takeIf { it.isNotEmpty() }?.joinToString(".") + }?.let { id -> + PageWithId(id, page) + } + else -> null + } - private fun getSymbolSignature(page: ContentPage) = page.content.dfs { it.dci.kind == ContentKind.Symbol } + private fun resolveLocation(locationResolver: PageResolver, page: ContentPage): String? = + locationResolver(page, null).also { location -> + if (location.isNullOrBlank()) context.logger.warn("Cannot resolve path for ${page.dri}") + } - private fun flattenToText(node: ContentNode): String { - fun getContentTextNodes(node: ContentNode, sourceSetRestriction: DisplaySourceSet): List = - 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 } - .orEmpty() - else -> emptyList() - } + override fun invoke(input: RootPageNode): RootPageNode { + val page = RendererSpecificResourcePage( + name = "scripts/pages.js", + children = emptyList(), + strategy = RenderingStrategy.PageLocationResolvableWrite { resolver -> + input.withDescendants().fold(emptyMap()) { pageList, page -> + processPage(page)?.let { pageList + Pair(it.id, it) } ?: pageList + }.run { + """var pages = ${generatePagesList(this, resolver)}""" + } + }) - val sourceSetRestriction = - node.sourceSets.find { it.platform == Platform.common } ?: node.sourceSets.first() - return getContentTextNodes(node, sourceSetRestriction).joinToString("") { it.text } + return input.modified(children = input.children + page) } +} + +private fun getSymbolSignature(page: ContentPage) = page.content.dfs { it.dci.kind == ContentKind.Symbol } - open fun processPage(page: ContentPage, link: String) { - val signature = getSymbolSignature(page) - val textNodes = signature?.let { flattenToText(it) } - val documentable = page.documentable - if (documentable != null) { - listOf( - documentable.dri.packageName, - documentable.dri.classNames, - documentable.dri.callable?.name - ).filter { !it.isNullOrEmpty() } - .takeIf { it.isNotEmpty() } - ?.joinToString(".") - ?.let { id -> pageList.put(id, Pair(textNodes ?: page.name, link)) } +private fun flattenToText(node: ContentNode): String { + fun getContentTextNodes(node: ContentNode, sourceSetRestriction: DisplaySourceSet): List = + 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 } + .orEmpty() + else -> emptyList() } - } -} + + val sourceSetRestriction = + node.sourceSets.find { it.platform == Platform.common } ?: node.sourceSets.first() + return getContentTextNodes(node, sourceSetRestriction).joinToString("") { it.text } +} \ No newline at end of file diff --git a/plugins/base/src/main/kotlin/renderers/html/htmlPreprocessors.kt b/plugins/base/src/main/kotlin/renderers/html/htmlPreprocessors.kt index 8e31fbc6..45159fea 100644 --- a/plugins/base/src/main/kotlin/renderers/html/htmlPreprocessors.kt +++ b/plugins/base/src/main/kotlin/renderers/html/htmlPreprocessors.kt @@ -4,39 +4,18 @@ import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import org.jetbrains.dokka.base.DokkaBase import org.jetbrains.dokka.base.DokkaBaseConfiguration import org.jetbrains.dokka.base.renderers.sourceSets -import org.jetbrains.dokka.model.DEnum -import org.jetbrains.dokka.model.DEnumEntry -import org.jetbrains.dokka.model.DFunction -import org.jetbrains.dokka.model.withDescendants +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.model.* import org.jetbrains.dokka.pages.* import org.jetbrains.dokka.plugability.DokkaContext import org.jetbrains.dokka.plugability.configuration import org.jetbrains.dokka.transformers.pages.PageTransformer -object NavigationPageInstaller : PageTransformer { - private val mapper = jacksonObjectMapper() - - fun SearchRecord.Companion.from(node: NavigationNode, location: String): SearchRecord = - SearchRecord(name = node.name, location = location) - - override fun invoke(input: RootPageNode): RootPageNode { - val nodes = input.children.filterIsInstance().single() - .let(NavigationPageInstaller::visit) +abstract class NavigationDataProvider { + open fun navigableChildren(input: RootPageNode): NavigationNode = + input.children.filterIsInstance().single().let { visit(it) } - val page = RendererSpecificResourcePage( - name = "scripts/navigation-pane.json", - children = emptyList(), - strategy = RenderingStrategy.LocationResolvableWrite { resolver -> - mapper.writeValueAsString( - nodes.withDescendants().map { SearchRecord.from(it, resolver(it.dri, it.sourceSets)) }) - }) - - return input.modified( - children = input.children + page + NavigationPage(nodes) - ) - } - - private fun visit(page: ContentPage): NavigationNode = + open fun visit(page: ContentPage): NavigationNode = NavigationNode( name = page.displayableName, dri = page.dri.first(), @@ -61,6 +40,39 @@ object NavigationPageInstaller : PageTransformer { } } +open class NavigationSearchInstaller(val context: DokkaContext) : NavigationDataProvider(), PageTransformer { + private val mapper = jacksonObjectMapper() + + open fun createSearchRecordFromNode(node: NavigationNode, location: String): SearchRecord = + SearchRecord(name = node.name, location = location) + + override fun invoke(input: RootPageNode): RootPageNode { + val page = RendererSpecificResourcePage( + name = "scripts/navigation-pane.json", + children = emptyList(), + strategy = RenderingStrategy.DriLocationResolvableWrite { resolver -> + mapper.writeValueAsString( + navigableChildren(input).withDescendants().map { + createSearchRecordFromNode(it, resolveLocation(resolver, it.dri, it.sourceSets).orEmpty()) + }) + }) + + return input.modified(children = input.children + page) + } + + private fun resolveLocation(locationResolver: DriResolver, dri: DRI, sourceSets: Set): String? = + locationResolver(dri, sourceSets).also { location -> + if (location.isNullOrBlank()) context.logger.warn("Cannot resolve path for $dri and sourceSets: ${sourceSets.joinToString { it.name }}") + } + +} + +open class NavigationPageInstaller(val context: DokkaContext) : NavigationDataProvider(), PageTransformer { + + override fun invoke(input: RootPageNode): RootPageNode = + input.modified(children = input.children + NavigationPage(navigableChildren(input))) +} + class CustomResourceInstaller(val dokkaContext: DokkaContext) : PageTransformer { private val configuration = configuration(dokkaContext) @@ -74,7 +86,8 @@ class CustomResourceInstaller(val dokkaContext: DokkaContext) : PageTransformer override fun invoke(input: RootPageNode): RootPageNode { val customResourcesPaths = (customAssets + customStylesheets).map { it.name }.toSet() - val withEmbeddedResources = input.transformContentPagesTree { it.modified(embeddedResources = it.embeddedResources + customResourcesPaths) } + val withEmbeddedResources = + input.transformContentPagesTree { it.modified(embeddedResources = it.embeddedResources + customResourcesPaths) } val (currentResources, otherPages) = withEmbeddedResources.children.partition { it is RendererSpecificResourcePage } return input.modified(children = otherPages + currentResources.filterNot { it.name in customResourcesPaths } + customAssets + customStylesheets) } @@ -166,4 +179,3 @@ class SourcesetDependencyAppender(val context: DokkaContext) : PageTransformer { } } - -- cgit