diff options
author | Marcin Aman <marcin.aman@gmail.com> | 2020-12-17 10:17:45 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-12-17 10:17:45 +0100 |
commit | 2f7ee2b82cda39f6bd94c5200b83563418b68dd7 (patch) | |
tree | 0942f00012ee7a90208c5d80ed3dd5ec6a3d9f92 /plugins | |
parent | 9e344b2047f72051bed509fb4e7ac1ae53f8098e (diff) | |
download | dokka-2f7ee2b82cda39f6bd94c5200b83563418b68dd7.tar.gz dokka-2f7ee2b82cda39f6bd94c5200b83563418b68dd7.tar.bz2 dokka-2f7ee2b82cda39f6bd94c5200b83563418b68dd7.zip |
Navigate to root after logo click, add data to searchbars on multimodule (#1631)
Diffstat (limited to 'plugins')
13 files changed, 200 insertions, 46 deletions
diff --git a/plugins/all-modules-page/build.gradle.kts b/plugins/all-modules-page/build.gradle.kts index a0c5a5ed..ecf8a384 100644 --- a/plugins/all-modules-page/build.gradle.kts +++ b/plugins/all-modules-page/build.gradle.kts @@ -11,4 +11,5 @@ dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version") implementation("org.jsoup:jsoup:1.12.1") + implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.11.1") }
\ No newline at end of file diff --git a/plugins/all-modules-page/src/main/kotlin/AllModulesPagePlugin.kt b/plugins/all-modules-page/src/main/kotlin/AllModulesPagePlugin.kt index 95a94cf4..c99293ef 100644 --- a/plugins/all-modules-page/src/main/kotlin/AllModulesPagePlugin.kt +++ b/plugins/all-modules-page/src/main/kotlin/AllModulesPagePlugin.kt @@ -54,6 +54,18 @@ class AllModulesPagePlugin : DokkaPlugin() { templateProcessingStrategy providing ::FallbackTemplateProcessingStrategy } + val navigationSearchTemplateStrategy by extending { + templateProcessingStrategy providing ::NavigationSearchTemplateStrategy order { + before(fallbackProcessingStrategy) + } + } + + val pagesSearchTemplateStrategy by extending { + templateProcessingStrategy providing ::PagesSearchTemplateStrategy order { + before(fallbackProcessingStrategy) + } + } + val pathToRootSubstitutor by extending { substitutor providing ::PathToRootSubstitutor } diff --git a/plugins/all-modules-page/src/main/kotlin/templates/JsonElementBasedTemplateProcessingStrategy.kt b/plugins/all-modules-page/src/main/kotlin/templates/JsonElementBasedTemplateProcessingStrategy.kt new file mode 100644 index 00000000..c6c67752 --- /dev/null +++ b/plugins/all-modules-page/src/main/kotlin/templates/JsonElementBasedTemplateProcessingStrategy.kt @@ -0,0 +1,74 @@ +package org.jetbrains.dokka.allModulesPage.templates + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import java.io.File +import java.nio.file.Files +import com.fasterxml.jackson.module.kotlin.treeToValue +import org.jetbrains.dokka.base.renderers.html.SearchRecord +import org.jetbrains.dokka.base.templating.* +import org.jetbrains.dokka.plugability.DokkaContext +import java.util.concurrent.ConcurrentHashMap + +abstract class BaseJsonNavigationTemplateProcessingStrategy(val context: DokkaContext) : TemplateProcessingStrategy { + abstract val navigationFileNameWithoutExtension: String + abstract val path: String + + private val fragments = ConcurrentHashMap<String, List<SearchRecord>>() + + open fun canProcess(file: File): Boolean = + file.extension == "json" && file.nameWithoutExtension == navigationFileNameWithoutExtension + + override suspend fun process(input: File, output: File): Boolean = coroutineScope { + val canProcess = canProcess(input) + if (canProcess) { + launch { + withContext(Dispatchers.IO) { + runCatching { parseJson<AddToSearch>(input.readText()) }.getOrNull() + }?.let { command -> + fragments[command.moduleName] = command.elements + } ?: fallbackToCopy(input, output) + } + } + canProcess + } + + override suspend fun finish(output: File) { + if (fragments.isNotEmpty()) { + val content = toJsonString(fragments.entries.flatMap { (moduleName, navigation) -> + navigation.map { it.withResolvedLocation(moduleName) } + }) + withContext(Dispatchers.IO) { + output.resolve("$path/$navigationFileNameWithoutExtension.json").writeText(content) + + fragments.keys.forEach { + output.resolve(it).resolve("$path/$navigationFileNameWithoutExtension.json").writeText(content) + } + } + } + } + + private suspend fun fallbackToCopy(input: File, output: File) { + context.logger.warn("Falling back to just copying file for ${input.name} even thought it should process it") + withContext(Dispatchers.IO) { input.copyTo(output) } + } + + private fun SearchRecord.withResolvedLocation(moduleName: String): SearchRecord = + copy(location = "$moduleName/$location") + +} + +class NavigationSearchTemplateStrategy(val dokkaContext: DokkaContext) : + BaseJsonNavigationTemplateProcessingStrategy(dokkaContext) { + override val navigationFileNameWithoutExtension: String = "navigation-pane" + override val path: String = "scripts" +} + +class PagesSearchTemplateStrategy(val dokkaContext: DokkaContext) : + BaseJsonNavigationTemplateProcessingStrategy(dokkaContext) { + override val navigationFileNameWithoutExtension: String = "pages" + override val path: String = "scripts" +}
\ No newline at end of file diff --git a/plugins/base/frontend/src/main/components/navigationPaneSearch/navigationPaneSearch.tsx b/plugins/base/frontend/src/main/components/navigationPaneSearch/navigationPaneSearch.tsx index b11b36f6..152e7719 100644 --- a/plugins/base/frontend/src/main/components/navigationPaneSearch/navigationPaneSearch.tsx +++ b/plugins/base/frontend/src/main/components/navigationPaneSearch/navigationPaneSearch.tsx @@ -4,6 +4,7 @@ import { DokkaFuzzyFilterComponent } from '../search/dokkaFuzzyFilter'; import { IWindow, Option } from '../search/types'; import './navigationPaneSearch.scss'; import ClearIcon from 'react-svg-loader!./clear.svg'; +import { relativizeUrlForRequest } from '../utils/requests'; export const NavigationPaneSearch = () => { const [navigationList, setNavigationList] = useState<Option[]>([]); @@ -31,9 +32,7 @@ export const NavigationPaneSearch = () => { } useEffect(() => { - const pathToRoot = (window as IWindow).pathToRoot - const url = pathToRoot.endsWith('/') ? `${pathToRoot}scripts/navigation-pane.json` : `${pathToRoot}/scripts/navigation-pane.json` - fetch(url) + fetch(relativizeUrlForRequest('scripts/navigation-pane.json')) .then(response => response.json()) .then((result) => { setNavigationList(result.map((record: Option, idx: number) => { diff --git a/plugins/base/frontend/src/main/components/search/search.tsx b/plugins/base/frontend/src/main/components/search/search.tsx index 3616a396..f0527cc0 100644 --- a/plugins/base/frontend/src/main/components/search/search.tsx +++ b/plugins/base/frontend/src/main/components/search/search.tsx @@ -1,10 +1,11 @@ -import React, { useCallback, useState } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { Select, List } from '@jetbrains/ring-ui'; import '@jetbrains/ring-ui/components/input-size/input-size.scss'; import './search.scss'; import { IWindow, Option, Props } from "./types"; import { DokkaSearchAnchor } from "./dokkaSearchAnchor"; import { DokkaFuzzyFilterComponent } from "./dokkaFuzzyFilter"; +import { relativizeUrlForRequest } from '../utils/requests'; const WithFuzzySearchFilterComponent: React.FC<Props> = ({ data }: Props) => { const [selected, onSelected] = useState<Option>(data[0]); @@ -42,17 +43,27 @@ const WithFuzzySearchFilterComponent: React.FC<Props> = ({ data }: Props) => { } export const WithFuzzySearchFilter = () => { - let data: Option[] = []; - const pages = (window as IWindow).pages; - if (pages) { - data = pages.map((page, i) => ({ - ...page, - label: page.name, - key: i + 1, - type: page.kind, - rgItemType: List.ListProps.Type.CUSTOM - })); - } + const [navigationList, setNavigationList] = useState<Option[]>([]); - return <WithFuzzySearchFilterComponent data={data} />; + useEffect(() => { + fetch(relativizeUrlForRequest('scripts/pages.json')) + .then(response => response.json()) + .then((result) => { + setNavigationList(result.map((record: Option, idx: number) => { + return { + ...record, + label: record.name, + key: idx, + type: record.kind, + rgItemType: List.ListProps.Type.CUSTOM + } + })) + }, + (error) => { + console.error('failed to fetch pages data', error) + setNavigationList([]) + }) + }, []) + + return <WithFuzzySearchFilterComponent data={navigationList} />; }; diff --git a/plugins/base/frontend/src/main/components/utils/requests.tsx b/plugins/base/frontend/src/main/components/utils/requests.tsx new file mode 100644 index 00000000..4a14e6f6 --- /dev/null +++ b/plugins/base/frontend/src/main/components/utils/requests.tsx @@ -0,0 +1,7 @@ +import { IWindow } from "../search/types" + +export const relativizeUrlForRequest = (filePath: string) : string => { + const pathToRoot = (window as IWindow).pathToRoot + const relativePath = pathToRoot == "" ? "." : pathToRoot + return relativePath.endsWith('/') ? `${relativePath}${filePath}` : `${relativePath}/${filePath}` +}
\ No newline at end of file diff --git a/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt b/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt index 7287c08a..2a24a959 100644 --- a/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt +++ b/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt @@ -719,13 +719,14 @@ open class HtmlRenderer( private fun resolveLink(link: String, page: PageNode): String = if (URI(link).isAbsolute) link else page.root(link) - open fun buildHtml(page: PageNode, resources: List<String>, content: FlowContent.() -> Unit) = - createHTML().prepareForTemplates().html { + open fun buildHtml(page: PageNode, resources: List<String>, content: FlowContent.() -> Unit): String { + val pathToRoot = locationProvider.pathToRoot(page) + return createHTML().prepareForTemplates().html { head { meta(name = "viewport", content = "width=device-width, initial-scale=1", charset = "UTF-8") title(page.name) link(href = page.root("images/logo-icon.svg"), rel = "icon", type = "image/svg") - templateCommand(PathToRootSubstitutionCommand("###", default = locationProvider.pathToRoot(page))) { + templateCommand(PathToRootSubstitutionCommand("###", default = pathToRoot)) { script { unsafe { +"""var pathToRoot = "###";""" } } } resources.forEach { @@ -750,13 +751,9 @@ open class HtmlRenderer( id = "container" div { id = "leftColumn" + clickableLogo(page, pathToRoot) div { - id = "logo" - } - if (page !is MultimoduleRootPage) { - div { - id = "paneSearch" - } + id = "paneSearch" } div { id = "sideMenu" @@ -768,7 +765,6 @@ open class HtmlRenderer( id = "leftToggler" span("icon-toggler") } - script(type = ScriptType.textJavaScript, src = page.root("scripts/pages.js")) {} script(type = ScriptType.textJavaScript, src = page.root("scripts/main.js")) {} content() div(classes = "footer") { @@ -788,6 +784,33 @@ open class HtmlRenderer( } } } + } + + /** + * 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 + */ + open fun FlowContent.clickableLogo(page: PageNode, pathToRoot: String) { + if (context.configuration.delayTemplateSubstitution && page is ContentPage) { + templateCommand(PathToRootSubstitutionCommand(pattern = "###", default = pathToRoot)) { + a { + href = "###index.html" + div { + id = "logo" + } + } + } + } else a { + href = pathToRoot.split("/") + .filter { it.isNotBlank() } + .drop(1).takeIf { it.isNotEmpty() } + ?.joinToString(separator = "/", postfix = "/index.html") + ?: "index.html" + div { + id = "logo" + } + } + } private val ContentNode.isAnchorable: Boolean get() = anchorLabel != null diff --git a/plugins/base/src/main/kotlin/renderers/html/NavigationPage.kt b/plugins/base/src/main/kotlin/renderers/html/NavigationPage.kt index 15d2473f..e2953d46 100644 --- a/plugins/base/src/main/kotlin/renderers/html/NavigationPage.kt +++ b/plugins/base/src/main/kotlin/renderers/html/NavigationPage.kt @@ -10,8 +10,10 @@ import org.jetbrains.dokka.model.WithChildren import org.jetbrains.dokka.pages.PageNode import org.jetbrains.dokka.pages.RendererSpecificPage import org.jetbrains.dokka.pages.RenderingStrategy +import org.jetbrains.dokka.plugability.DokkaContext -class NavigationPage(val root: NavigationNode, val moduleName: String) : RendererSpecificPage { +class NavigationPage(val root: NavigationNode, val moduleName: String, val context: DokkaContext) : + RendererSpecificPage { override val name = "navigation" override val children = emptyList<PageNode>() @@ -23,9 +25,13 @@ class NavigationPage(val root: NavigationNode, val moduleName: String) : Rendere } private fun <R> TagConsumer<R>.visit(node: NavigationNode, renderer: HtmlRenderer): R = with(renderer) { - templateCommand(AddToNavigationCommand(moduleName)) { - visit(node,"${moduleName}-nav-submenu", 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 = @@ -52,9 +58,10 @@ data class NavigationNode( val dri: DRI, val sourceSets: Set<DisplaySourceSet>, override val children: List<NavigationNode> -): WithChildren<NavigationNode> +) : WithChildren<NavigationNode> -fun NavigationPage.transform(block: (NavigationNode) -> NavigationNode) = NavigationPage(root.transform(block), moduleName) +fun NavigationPage.transform(block: (NavigationNode) -> NavigationNode) = + NavigationPage(root.transform(block), moduleName, context) fun NavigationNode.transform(block: (NavigationNode) -> NavigationNode) = run(block).let { NavigationNode(it.name, it.dri, it.sourceSets, 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 index 6ef6a6ec..e2def59b 100644 --- a/plugins/base/src/main/kotlin/renderers/html/SearchbarDataInstaller.kt +++ b/plugins/base/src/main/kotlin/renderers/html/SearchbarDataInstaller.kt @@ -2,16 +2,15 @@ package org.jetbrains.dokka.base.renderers.html import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import org.jetbrains.dokka.Platform +import org.jetbrains.dokka.base.templating.AddToSearch 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 typealias PageId = String -typealias Json = String data class SearchRecord( val name: String, @@ -29,7 +28,7 @@ open class SearchbarDataInstaller(val context: DokkaContext) : PageTransformer { private val mapper = jacksonObjectMapper() - open fun generatePagesList(pages: Map<PageId, PageWithId>, locationResolver: PageResolver): Json = + open fun generatePagesList(pages: Map<PageId, PageWithId>, locationResolver: PageResolver): List<SearchRecord> = pages.entries .filter { it.key.isNotEmpty() } .sortedWith(compareBy({ it.key }, { it.value.displayableSignature })) @@ -44,7 +43,7 @@ open class SearchbarDataInstaller(val context: DokkaContext) : PageTransformer { searchKeys = listOf(entry.key, subentry.value.displayableSignature) ) } - }.run { mapper.writeValueAsString(this) } + } open fun createSearchRecord( name: String, @@ -76,13 +75,19 @@ open class SearchbarDataInstaller(val context: DokkaContext) : PageTransformer { override fun invoke(input: RootPageNode): RootPageNode { val page = RendererSpecificResourcePage( - name = "scripts/pages.js", + name = "scripts/pages.json", children = emptyList(), strategy = RenderingStrategy.PageLocationResolvableWrite { resolver -> - input.withDescendants().fold(emptyMap<PageId, PageWithId>()) { pageList, page -> + val content = input.withDescendants().fold(emptyMap<PageId, PageWithId>()) { pageList, page -> processPage(page)?.let { pageList + Pair(it.id, it) } ?: pageList }.run { - """var pages = ${generatePagesList(this, resolver)}""" + generatePagesList(this, resolver) + } + + if (context.configuration.delayTemplateSubstitution) { + mapper.writeValueAsString(AddToSearch(context.configuration.moduleName, content)) + } else { + mapper.writeValueAsString(content) } }) diff --git a/plugins/base/src/main/kotlin/renderers/html/htmlPreprocessors.kt b/plugins/base/src/main/kotlin/renderers/html/htmlPreprocessors.kt index cc97bb72..25d95a8f 100644 --- a/plugins/base/src/main/kotlin/renderers/html/htmlPreprocessors.kt +++ b/plugins/base/src/main/kotlin/renderers/html/htmlPreprocessors.kt @@ -4,6 +4,7 @@ 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.base.templating.AddToSearch import org.jetbrains.dokka.links.DRI import org.jetbrains.dokka.model.* import org.jetbrains.dokka.pages.* @@ -51,10 +52,14 @@ open class NavigationSearchInstaller(val context: DokkaContext) : NavigationData 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()) - }) + val content = navigableChildren(input).withDescendants().map { + createSearchRecordFromNode(it, resolveLocation(resolver, it.dri, it.sourceSets).orEmpty()) + } + if (context.configuration.delayTemplateSubstitution) { + mapper.writeValueAsString(AddToSearch(context.configuration.moduleName, content.toList())) + } else { + mapper.writeValueAsString(content) + } }) return input.modified(children = input.children + page) @@ -70,8 +75,13 @@ open class NavigationSearchInstaller(val context: DokkaContext) : NavigationData open class NavigationPageInstaller(val context: DokkaContext) : NavigationDataProvider(), PageTransformer { override fun invoke(input: RootPageNode): RootPageNode = - input.modified(children = input.children + NavigationPage(navigableChildren(input), - (listOf(input) + input.children).firstOrNull { it is ContentPage && it.name.isNotBlank() }?.name.orEmpty())) + input.modified( + children = input.children + NavigationPage( + root = navigableChildren(input), + moduleName = context.configuration.moduleName, + context = context + ) + ) } class CustomResourceInstaller(val dokkaContext: DokkaContext) : PageTransformer { diff --git a/plugins/base/src/main/kotlin/templating/AddToSearch.kt b/plugins/base/src/main/kotlin/templating/AddToSearch.kt new file mode 100644 index 00000000..fdec45c2 --- /dev/null +++ b/plugins/base/src/main/kotlin/templating/AddToSearch.kt @@ -0,0 +1,5 @@ +package org.jetbrains.dokka.base.templating + +import org.jetbrains.dokka.base.renderers.html.SearchRecord + +data class AddToSearch(val moduleName: String, val elements: List<SearchRecord>): Command
\ No newline at end of file diff --git a/plugins/base/src/main/kotlin/templating/jsonMapperForPlugins.kt b/plugins/base/src/main/kotlin/templating/jsonMapperForPlugins.kt index 9b656309..71245778 100644 --- a/plugins/base/src/main/kotlin/templating/jsonMapperForPlugins.kt +++ b/plugins/base/src/main/kotlin/templating/jsonMapperForPlugins.kt @@ -38,7 +38,6 @@ fun toJsonString(value: Any): String = objectMapper.writeValueAsString(value) inline fun <reified T : Any> parseJson(json: String): T = parseJson(json, TypeReference()) - @PublishedApi internal fun <T : Any> parseJson(json: String, typeReference: TypeReference<T>): T = objectMapper.readValue(json, typeReference.jackson) diff --git a/plugins/base/src/main/resources/dokka/styles/style.css b/plugins/base/src/main/resources/dokka/styles/style.css index d2658a9d..81e1012d 100644 --- a/plugins/base/src/main/resources/dokka/styles/style.css +++ b/plugins/base/src/main/resources/dokka/styles/style.css @@ -247,6 +247,7 @@ html ::-webkit-scrollbar-thumb { padding-left: 24px; padding-top: 24px; height: 48px; + cursor: pointer; } .monospace, |