From 71428219389d5fe429c0bad0bd31b2cda55cdfce Mon Sep 17 00:00:00 2001 From: Błażej Kardyś Date: Mon, 20 Jan 2020 17:13:28 +0100 Subject: Adding changes to HTML UI render --- core/src/main/kotlin/renderers/DefaultRenderer.kt | 1 - core/src/main/kotlin/renderers/FileWriter.kt | 34 ++++- core/src/main/kotlin/renderers/HtmlRenderer.kt | 167 ++++++++++++++++++--- core/src/main/kotlin/renderers/OutputWriter.kt | 1 + .../kotlin/resolvers/DefaultLocationProvider.kt | 4 +- core/src/main/kotlin/resolvers/LocationProvider.kt | 1 + .../src/main/resources/dokka/images/arrow_down.svg | 3 + core/src/main/resources/dokka/images/logo-icon.svg | 3 + core/src/main/resources/dokka/images/logo-text.svg | 6 + core/src/main/resources/dokka/scripts/scripts.js | 11 ++ core/src/main/resources/dokka/scripts/search.js | 5 + core/src/main/resources/dokka/styles/style.css | 63 +++++++- 12 files changed, 273 insertions(+), 26 deletions(-) create mode 100755 core/src/main/resources/dokka/images/arrow_down.svg create mode 100755 core/src/main/resources/dokka/images/logo-icon.svg create mode 100755 core/src/main/resources/dokka/images/logo-text.svg create mode 100644 core/src/main/resources/dokka/scripts/scripts.js create mode 100644 core/src/main/resources/dokka/scripts/search.js (limited to 'core/src/main') diff --git a/core/src/main/kotlin/renderers/DefaultRenderer.kt b/core/src/main/kotlin/renderers/DefaultRenderer.kt index 1152bcc5..5e3eadbe 100644 --- a/core/src/main/kotlin/renderers/DefaultRenderer.kt +++ b/core/src/main/kotlin/renderers/DefaultRenderer.kt @@ -6,7 +6,6 @@ import org.jetbrains.dokka.plugability.DokkaContext import org.jetbrains.dokka.plugability.single import org.jetbrains.dokka.resolvers.LocationProvider - abstract class DefaultRenderer( protected val outputWriter: OutputWriter, protected val context: DokkaContext diff --git a/core/src/main/kotlin/renderers/FileWriter.kt b/core/src/main/kotlin/renderers/FileWriter.kt index 5439db17..22a2e8f9 100644 --- a/core/src/main/kotlin/renderers/FileWriter.kt +++ b/core/src/main/kotlin/renderers/FileWriter.kt @@ -1,8 +1,10 @@ package org.jetbrains.dokka.renderers +import com.intellij.util.io.isDirectory import java.io.File import java.io.IOException -import java.nio.file.Paths +import java.net.URI +import java.nio.file.* class FileWriter(val root: String, override val extension: String): OutputWriter { private val createdFiles: MutableSet = mutableSetOf() @@ -19,15 +21,43 @@ class FileWriter(val root: String, override val extension: String): OutputWriter val dir = Paths.get(root, path.dropLastWhile { it != '/' }).toFile() dir.mkdirsOrFail() Paths.get(root, "$path$ext").toFile().writeText(text) - } catch (e : Throwable) { + } catch (e: Throwable) { println("Failed to write $this. ${e.message}") e.printStackTrace() } } + override fun writeResources(pathFrom: String, pathTo: String) { + val rebase = fun(path: String) = + "$pathTo/${path.removePrefix(pathFrom)}" + val dest = Paths.get(root, pathTo).toFile() + dest.mkdirsOrFail() + val uri = javaClass.getResource(pathFrom).toURI() + val fs = getFileSystemForURI(uri) + val path = fs.getPath(pathFrom) + for (file in Files.walk(path).iterator()) { + if (file.isDirectory()) { + val dirPath = file.toAbsolutePath().toString() + Paths.get(root, rebase(dirPath)).toFile().mkdirsOrFail() + } else { + val filePath = file.toAbsolutePath().toString() + Paths.get(root, rebase(filePath)).toFile().writeBytes( + javaClass.getResourceAsStream(filePath).readBytes() + ) + } + } + } + private fun File.mkdirsOrFail() { if (!mkdirs() && !exists()) { throw IOException("Failed to create directory $this") } } + + private fun getFileSystemForURI(uri: URI): FileSystem = + try { + FileSystems.newFileSystem(uri, emptyMap()) + } catch (e: FileSystemAlreadyExistsException) { + FileSystems.getFileSystem(uri) + } } \ No newline at end of file diff --git a/core/src/main/kotlin/renderers/HtmlRenderer.kt b/core/src/main/kotlin/renderers/HtmlRenderer.kt index 8742f202..ae120795 100644 --- a/core/src/main/kotlin/renderers/HtmlRenderer.kt +++ b/core/src/main/kotlin/renderers/HtmlRenderer.kt @@ -1,9 +1,13 @@ package org.jetbrains.dokka.renderers import kotlinx.html.* +import kotlinx.html.dom.document import kotlinx.html.stream.appendHTML +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.model.Documentable import org.jetbrains.dokka.pages.* import org.jetbrains.dokka.plugability.DokkaContext +import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty import java.io.File import java.net.URL @@ -12,6 +16,29 @@ open class HtmlRenderer( context: DokkaContext ) : DefaultRenderer(outputWriter, context) { + private val pageList = mutableListOf() + + private var idCounter = 0 + get() = ++field + + private fun FlowContent.buildSideMenu(context: PageNode, node: PageNode) { + val children = node.children.filter { it !is MemberPageNode } + val className = children.ifNotEmpty { "nav$idCounter" } + div("sideMenuPart") { + className?.let { id = it } + div("overview") { + buildLink(node, context) + className?.let { + span("navButton") { + onClick = """document.getElementById("$it").classList.toggle("hidden");""" + span("navButtonContent") + } + } + } + children.forEach { buildSideMenu(context, it) } + } + } + override fun FlowContent.buildList(node: ContentList, pageContext: PageNode) = if (node.ordered) ol { buildListItems(node.children, pageContext) @@ -89,25 +116,28 @@ open class HtmlRenderer( } } - override fun FlowContent.buildNavigation(page: PageNode) { + override fun FlowContent.buildNavigation(page: PageNode) = locationProvider.ancestors(page).forEach { node -> text("/") - buildLink(locationProvider.resolve(node, page)) { - text(node.name) - } + buildLink(node, page) + } + + private fun FlowContent.buildLink(to: PageNode, from: PageNode) = + buildLink(locationProvider.resolve(to, from)) { + text(to.name) } - } override fun buildError(node: ContentNode) { context.logger.error("Unknown ContentNode type: $node") } - override fun FlowContent.buildNewLine() { br() } - - override fun FlowContent.buildLink(address: String, content: FlowContent.() -> Unit) { - a (href = address, block = content) + override fun FlowContent.buildNewLine() { + br() } + override fun FlowContent.buildLink(address: String, content: FlowContent.() -> Unit) = + a(href = address, block = content) + override fun FlowContent.buildCode(code: List, language: String, pageContext: PageNode) { buildNewLine() code.forEach { @@ -116,29 +146,126 @@ open class HtmlRenderer( } } + override fun renderPage(page: PageNode) { + super.renderPage(page) + pageList.add("""{ "name": "${page.name}", ${if (page is ClassPageNode) "\"class\": \"${page.name}\"," else ""} "location": "${locationProvider.resolve(page)}" }""") + } + override fun FlowContent.buildText(textNode: ContentText) { text(textNode.text) } + override fun render(root: PageNode) { + super.render(root) + outputWriter.write("scripts/pages", "var pages = [\n${pageList.joinToString(",\n")}\n]", ".js") + } override fun buildSupportFiles() { // TODO copy file instead of reading outputWriter.write( "style.css", javaClass.getResourceAsStream("/dokka/styles/style.css").reader().readText() ) + renderPage(searchPageNode) + outputWriter.writeResources("/dokka/styles", "styles") + outputWriter.writeResources("/dokka/scripts", "scripts") + outputWriter.writeResources("/dokka/images", "images") } - override fun buildPage(page: PageNode, content: (FlowContent, PageNode) -> Unit): String = StringBuilder().appendHTML().html { - head { - title(page.name) - link(rel = LinkRel.stylesheet, href = "${locationProvider.resolveRoot(page)}style.css") - page.embeddedResources.filter { URL(it).path.substringAfterLast('.') == "js" } - .forEach { script(type = ScriptType.textJavaScript, src = it) { async = true } } - } - body { - content(this, page) - } - }.toString() + private fun PageNode.root(path: String) = + "${if (this != searchPageNode) locationProvider.resolveRoot(this) else ""}$path" + + override fun buildPage(page: PageNode, content: (FlowContent, PageNode) -> Unit): String = + StringBuilder().appendHTML().html { + document { + + } + head { + title(page.name) + link(rel = LinkRel.stylesheet, href = page.root("styles/style.css")) + page.embeddedResources.filter { + URL(it).path.substringAfterLast('.') == "js" + }.forEach { + script(type = ScriptType.textJavaScript, src = it) { async = true } + } + if (page == searchPageNode) { + script( + type = ScriptType.textJavaScript, + src = page.root("scripts/pages.js") + ) { async = true } + } + } + body { + div { + id = "navigation" + div { + id = "searchBar" + form(action = page.root("-search.html"), method = FormMethod.get) { + id = "searchForm" + input(type = InputType.search, name = "query") + input(type = InputType.submit) { value = "Search" } + } + } + div { + id = "sideMenu" + img(src = page.root("images/logo-icon.svg")) + img(src = page.root("images/logo-text.svg")) + hr() + input(type = InputType.search) { + id = "navigationFilter" + } + script( + type = ScriptType.textJavaScript, + src = page.root("scripts/scripts.js") + ) { async = true } + buildSideMenu(page, locationProvider.top()) + } + } + div { + id = "content" + if (page != searchPageNode) content(this, page) + else { + h1 { + id = "searchTitle" + text("Search results for ") + } + table { + tbody { + id = "searchTable" + } + } + script( + type = ScriptType.textJavaScript, + src = page.root("scripts/search.js") + ) { async = true } + } + } + } + }.toString() protected open fun List.joinAttr() = this.joinToString(" ") { it.key + "=" + it.value } + + private val searchPageNode = + object: PageNode { + override val name: String + get() = "Search" + override val content = object: ContentNode{ + override val dci: DCI = DCI(DRI.topLevel, ContentKind.Main) + override val platforms: Set = emptySet() + override val style: Set