diff options
author | Błażej Kardyś <bkardys@virtuslab.com> | 2020-01-20 17:13:28 +0100 |
---|---|---|
committer | Paweł Marks <Kordyjan@users.noreply.github.com> | 2020-01-31 15:07:06 +0100 |
commit | 71428219389d5fe429c0bad0bd31b2cda55cdfce (patch) | |
tree | 12a337a7ff55177cc2b4373b063c746310be2793 | |
parent | c3b911f286186a790be607e9b803e3ed63c77289 (diff) | |
download | dokka-71428219389d5fe429c0bad0bd31b2cda55cdfce.tar.gz dokka-71428219389d5fe429c0bad0bd31b2cda55cdfce.tar.bz2 dokka-71428219389d5fe429c0bad0bd31b2cda55cdfce.zip |
Adding changes to HTML UI render
-rw-r--r-- | core/src/main/kotlin/renderers/DefaultRenderer.kt | 1 | ||||
-rw-r--r-- | core/src/main/kotlin/renderers/FileWriter.kt | 34 | ||||
-rw-r--r-- | core/src/main/kotlin/renderers/HtmlRenderer.kt | 167 | ||||
-rw-r--r-- | core/src/main/kotlin/renderers/OutputWriter.kt | 1 | ||||
-rw-r--r-- | core/src/main/kotlin/resolvers/DefaultLocationProvider.kt | 4 | ||||
-rw-r--r-- | core/src/main/kotlin/resolvers/LocationProvider.kt | 1 | ||||
-rwxr-xr-x | core/src/main/resources/dokka/images/arrow_down.svg | 3 | ||||
-rwxr-xr-x | core/src/main/resources/dokka/images/logo-icon.svg | 3 | ||||
-rwxr-xr-x | core/src/main/resources/dokka/images/logo-text.svg | 6 | ||||
-rw-r--r-- | core/src/main/resources/dokka/scripts/scripts.js | 11 | ||||
-rw-r--r-- | core/src/main/resources/dokka/scripts/search.js | 5 | ||||
-rw-r--r-- | core/src/main/resources/dokka/styles/style.css | 63 |
12 files changed, 273 insertions, 26 deletions
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<T>( 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<String> = 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<String, Any>()) + } 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<FlowContent>(outputWriter, context) { + private val pageList = mutableListOf<String>() + + 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<ContentNode>, 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<HTMLMetadata>.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<PlatformData> = emptySet() + override val style: Set<Style> = emptySet() + override val extras: Set<Extra> = emptySet() + + } + override val dri: DRI = DRI.topLevel + override val documentable: Documentable? = null + override val embeddedResources: List<String> = emptyList() + override val children: List<PageNode> = emptyList() + + override fun modified( + name: String, + content: ContentNode, + embeddedResources: List<String>, + children: List<PageNode> + ): PageNode = this + + } }
\ No newline at end of file diff --git a/core/src/main/kotlin/renderers/OutputWriter.kt b/core/src/main/kotlin/renderers/OutputWriter.kt index 84cc124d..8ef7e8b2 100644 --- a/core/src/main/kotlin/renderers/OutputWriter.kt +++ b/core/src/main/kotlin/renderers/OutputWriter.kt @@ -3,4 +3,5 @@ package org.jetbrains.dokka.renderers interface OutputWriter{ val extension: String fun write(path: String, text: String, ext: String = extension) + fun writeResources(pathFrom: String, pathTo: String) }
\ No newline at end of file diff --git a/core/src/main/kotlin/resolvers/DefaultLocationProvider.kt b/core/src/main/kotlin/resolvers/DefaultLocationProvider.kt index d7089f96..3412d975 100644 --- a/core/src/main/kotlin/resolvers/DefaultLocationProvider.kt +++ b/core/src/main/kotlin/resolvers/DefaultLocationProvider.kt @@ -38,6 +38,8 @@ open class DefaultLocationProvider( else -> ancestors(node.parent()) + node } + override fun top(): PageNode = pageGraphRoot + protected open fun findInPageGraph(dri: DRI, platforms: List<PlatformData>): PageNode? = pageGraphRoot.dfs { it.dri == dri } @@ -52,7 +54,7 @@ open class DefaultLocationProvider( else -> getPath(pathNode.parent(), path + pathNode.pathName().ifEmpty { "root" }) } - val contextNode = if (context?.children?.isEmpty() == true) context.parent() else context + val contextNode = if (context?.children?.isEmpty() == true && context.parent() != null) context.parent() else context val nodePath = getPath(node).reversed() val contextPath = getPath(contextNode).reversed() diff --git a/core/src/main/kotlin/resolvers/LocationProvider.kt b/core/src/main/kotlin/resolvers/LocationProvider.kt index 2da2310d..7d77ccb8 100644 --- a/core/src/main/kotlin/resolvers/LocationProvider.kt +++ b/core/src/main/kotlin/resolvers/LocationProvider.kt @@ -9,4 +9,5 @@ interface LocationProvider { fun resolve(node: PageNode, context: PageNode? = null): String fun resolveRoot(node: PageNode): String fun ancestors(node: PageNode?): List<PageNode> + fun top(): PageNode } diff --git a/core/src/main/resources/dokka/images/arrow_down.svg b/core/src/main/resources/dokka/images/arrow_down.svg new file mode 100755 index 00000000..89e7df47 --- /dev/null +++ b/core/src/main/resources/dokka/images/arrow_down.svg @@ -0,0 +1,3 @@ +<svg width="10" height="7" viewBox="0 0 10 7" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M9.71824 1.66658L9.01113 0.959473L5.00497 4.96447L1.00008 0.959473L0.292969 1.66658L5.01113 6.38474L9.71824 1.66658Z" fill="#A1AAB4"/> +</svg> diff --git a/core/src/main/resources/dokka/images/logo-icon.svg b/core/src/main/resources/dokka/images/logo-icon.svg new file mode 100755 index 00000000..1b3b3670 --- /dev/null +++ b/core/src/main/resources/dokka/images/logo-icon.svg @@ -0,0 +1,3 @@ +<svg width="26" height="26" viewBox="0 0 26 26" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M26 26H0V0H26L12.9243 12.9747L26 26Z" fill="#F8873C"/> +</svg> diff --git a/core/src/main/resources/dokka/images/logo-text.svg b/core/src/main/resources/dokka/images/logo-text.svg new file mode 100755 index 00000000..7bf3e6c5 --- /dev/null +++ b/core/src/main/resources/dokka/images/logo-text.svg @@ -0,0 +1,6 @@ +<svg width="83" height="27" viewBox="0 0 83 27" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M47.1611 7.6297V25.6345V25.6867H61.8428V21.8039H51.3589V10.3852H61.8428V6.50244H47.1611V7.6297Z" fill="#27282C"/> +<path d="M82.9891 21.8039L72.778 10.3852H82.9051V6.50244H67.0586V10.3852L77.4585 21.8039H67.0586V25.6867H82.9996V21.8039H82.9891Z" fill="#27282C"/> +<path d="M16.2978 7.76556C14.5872 6.46086 12.4463 5.67804 10.1271 5.67804C4.53357 5.67804 0 10.1871 0 15.7503C0 21.3135 4.53357 25.8226 10.1271 25.8226C12.4463 25.8226 14.5872 25.0502 16.2978 23.735V25.7182H20.4955V0H16.2978V7.76556ZM10.1271 21.8041C6.75838 21.8041 4.02984 19.0903 4.02984 15.7399C4.02984 12.3894 6.75838 9.67563 10.1271 9.67563C13.4958 9.67563 16.2243 12.3894 16.2243 15.7399C16.2138 19.0903 13.4853 21.8041 10.1271 21.8041Z" fill="#27282C"/> +<path d="M33.9703 5.86566C28.3768 5.86566 23.8433 10.3747 23.8433 15.9379C23.8433 21.5011 28.3768 26.0102 33.9703 26.0102C39.5638 26.0102 44.0974 21.5011 44.0974 15.9379C44.0974 10.3747 39.5638 5.86566 33.9703 5.86566ZM33.9703 21.9917C30.6016 21.9917 27.8731 19.2779 27.8731 15.9275C27.8731 12.577 30.6016 9.86325 33.9703 9.86325C37.339 9.86325 40.0676 12.577 40.0676 15.9275C40.0676 19.2779 37.339 21.9917 33.9703 21.9917Z" fill="#27282C"/> +</svg> diff --git a/core/src/main/resources/dokka/scripts/scripts.js b/core/src/main/resources/dokka/scripts/scripts.js new file mode 100644 index 00000000..c2e29b9f --- /dev/null +++ b/core/src/main/resources/dokka/scripts/scripts.js @@ -0,0 +1,11 @@ +document.getElementById("navigationFilter").oninput = function (e) { + var input = e.target.value; + var menuParts = document.getElementsByClassName("sideMenuPart") + for (let part of menuParts) { + if(part.querySelector("a").textContent.startsWith(input)) { + part.classList.remove("filtered"); + } else { + part.classList.add("filtered"); + } + } +}
\ No newline at end of file diff --git a/core/src/main/resources/dokka/scripts/search.js b/core/src/main/resources/dokka/scripts/search.js new file mode 100644 index 00000000..63112ac5 --- /dev/null +++ b/core/src/main/resources/dokka/scripts/search.js @@ -0,0 +1,5 @@ +var query = new URLSearchParams(window.location.search).get("query"); + document.getElementById("searchTitle").innerHTML += '"' + query + '":'; + document.getElementById("searchTable").innerHTML = pages.filter(el => el.name.startsWith(query)).reduce((acc, element) => { return acc + + '<tr><td><a href="' + element.location + '">' + element.name + '</a></td></tr>' + }, "");
\ No newline at end of file diff --git a/core/src/main/resources/dokka/styles/style.css b/core/src/main/resources/dokka/styles/style.css index 60ea133f..035b4fcd 100644 --- a/core/src/main/resources/dokka/styles/style.css +++ b/core/src/main/resources/dokka/styles/style.css @@ -1,9 +1,68 @@ @import url(https://fonts.googleapis.com/css?family=Open+Sans:300i,400,700); + +#content { + margin-top: 3em; + margin-left: 15em; +} + +#navigation { + position: relative +} + +#sideMenu, #searchBar { + position: absolute; +} + +#sideMenu { + width: 14em; + padding-left: 0.5em; +} + +#sideMenu .sideMenuPart { + margin-left: 0.25em; +} + +#sideMenu img { + margin: 1em 0.25em; +} + +#sideMenu hr { + background: #DADFE6; +} + +#searchBar { + width: 100%; +} + +#searchForm { + float: right; +} + +.sideMenuPart > .navButton { + margin-left:0.25em +} + +.sideMenuPart > .overview .navButtonContent::after { + float: right; + content: url("../images/arrow_down.svg"); +} + +.sideMenuPart.hidden > .navButton .navButtonContent::after { + content: '\02192'; +} + +.sideMenuPart.hidden > .sideMenuPart { + display: none; +} + +.filtered > a, .filtered > .navButton { + display: none; +} + body, table{ - padding:50px; font:14px/1.5 'Open Sans', "Helvetica Neue", Helvetica, Arial, sans-serif; - color:#555; + background: #F4F4F4; font-weight:300; margin-left: auto; margin-right: auto; |