aboutsummaryrefslogtreecommitdiff
path: root/plugins/javadoc/src
diff options
context:
space:
mode:
authorBłażej Kardyś <bkardys@virtuslab.com>2020-06-23 22:25:18 +0200
committerPaweł Marks <pmarks@virtuslab.com>2020-06-26 00:40:47 +0200
commita1c316e829827ddb0e3e288e684ac287e8fd28ff (patch)
tree0a421ff350f1018b6c57a0674142ec3da66d9c25 /plugins/javadoc/src
parentfb9a5abfc24e93b918693f78a8f555da2c5a359f (diff)
downloaddokka-a1c316e829827ddb0e3e288e684ac287e8fd28ff.tar.gz
dokka-a1c316e829827ddb0e3e288e684ac287e8fd28ff.tar.bz2
dokka-a1c316e829827ddb0e3e288e684ac287e8fd28ff.zip
Briefs raw rendering and basic url handling
Diffstat (limited to 'plugins/javadoc/src')
-rw-r--r--plugins/javadoc/src/main/kotlin/javadoc/JavadocLocationProvider.kt2
-rw-r--r--plugins/javadoc/src/main/kotlin/javadoc/JavadocPageCreator.kt22
-rw-r--r--plugins/javadoc/src/main/kotlin/javadoc/JavadocPlugin.kt8
-rw-r--r--plugins/javadoc/src/main/kotlin/javadoc/KorteJavadocRenderer.kt402
-rw-r--r--plugins/javadoc/src/main/kotlin/javadoc/renderer/JavadocContentToHtmlTranslator.kt66
-rw-r--r--plugins/javadoc/src/main/kotlin/javadoc/renderer/JavadocContentToTemplateMapTranslator.kt203
-rw-r--r--plugins/javadoc/src/main/kotlin/javadoc/renderer/KorteJavadocRenderer.kt186
-rw-r--r--plugins/javadoc/src/main/resources/views/class.korte8
-rw-r--r--plugins/javadoc/src/main/resources/views/components/indexPage.korte2
9 files changed, 479 insertions, 420 deletions
diff --git a/plugins/javadoc/src/main/kotlin/javadoc/JavadocLocationProvider.kt b/plugins/javadoc/src/main/kotlin/javadoc/JavadocLocationProvider.kt
index f8ecf868..d731ec5f 100644
--- a/plugins/javadoc/src/main/kotlin/javadoc/JavadocLocationProvider.kt
+++ b/plugins/javadoc/src/main/kotlin/javadoc/JavadocLocationProvider.kt
@@ -65,7 +65,7 @@ class JavadocLocationProvider(pageRoot: RootPageNode, private val context: Dokka
private operator fun IdentityHashMap<PageNode, List<String>>.get(dri: DRI) = this[nodeIndex[dri]]
- override fun resolve(dri: DRI, sourceSets: List<DokkaSourceSet>, context: PageNode?): String =
+ override fun resolve(dri: DRI, sourceSets: Set<DokkaSourceSet>, context: PageNode?): String =
context?.let { resolve(it, skipExtension = false) } ?: nodeIndex[dri]?.let {
resolve(it, skipExtension = true)
} ?: with(externalLocationProvider!!) {
diff --git a/plugins/javadoc/src/main/kotlin/javadoc/JavadocPageCreator.kt b/plugins/javadoc/src/main/kotlin/javadoc/JavadocPageCreator.kt
index 3074a760..12c53ab7 100644
--- a/plugins/javadoc/src/main/kotlin/javadoc/JavadocPageCreator.kt
+++ b/plugins/javadoc/src/main/kotlin/javadoc/JavadocPageCreator.kt
@@ -39,19 +39,19 @@ open class JavadocPageCreator(
)
fun pageForClasslike(c: DClasslike): JavadocClasslikePageNode? =
- c.mostTopSourceSet?.let { jvm ->
+ c.highestJvmSourceSet?.let { jvm ->
JavadocClasslikePageNode(
name = c.name.orEmpty(),
content = contentForClasslike(c),
dri = setOf(c.dri),
modifiers = listOfNotNull(c.visibility[jvm]?.name),
- signature = signatureProvider.signature(c).jvmSignature(),
+ signature = signatureProvider.signature(c).nodeForJvm(jvm),
description = c.descriptionToContentNodes(),
constructors = c.safeAs<WithConstructors>()?.constructors?.map { it.toJavadocFunction(jvm) }.orEmpty(),
methods = c.functions.map { it.toJavadocFunction(jvm) },
- entries = c.safeAs<DEnum>()?.entries?.map { JavadocEntryNode(signatureProvider.signature(it).jvmSignature(), it.descriptionToContentNodes(jvm)) }.orEmpty(),
+ entries = c.safeAs<DEnum>()?.entries?.map { JavadocEntryNode(signatureProvider.signature(it).nodeForJvm(jvm), it.descriptionToContentNodes(jvm)) }.orEmpty(),
classlikes = c.classlikes.mapNotNull { pageForClasslike(it) },
- properties = c.properties.map { JavadocPropertyNode(signatureProvider.signature(it).jvmSignature(), it.descriptionToContentNodes(jvm)) },
+ properties = c.properties.map { JavadocPropertyNode(signatureProvider.signature(it).nodeForJvm(jvm), it.descriptionToContentNodes(jvm)) },
documentable = c,
extras = c.safeAs<WithExtraProperties<Documentable>>()?.extra ?: PropertyContainer.empty()
)
@@ -129,7 +129,7 @@ open class JavadocPageCreator(
private fun DFunction.toJavadocFunction(sourceSetData: DokkaSourceSet) = JavadocFunctionNode(
name = name,
- signature = signatureProvider.signature(this).jvmSignature(),
+ signature = signatureProvider.signature(this).nodeForJvm(sourceSetData),
brief = brief(sourceSetData),
parameters = parameters.map {
JavadocParameterNode(
@@ -141,21 +141,20 @@ open class JavadocPageCreator(
extras = extra
)
- // THIS MUST BE DISCUSSED
private val Documentable.jvmSource
get() = sourceSets.filter { it.analysisPlatform == Platform.jvm }
- private val Documentable.mostTopSourceSet
+ private val Documentable.highestJvmSourceSet
get() = jvmSource.let { sources ->
sources.firstOrNull { it != expectPresentInSet } ?: sources.firstOrNull()
}
private val firstSentenceRegex = Regex("^((?:[^.?!]|[.!?](?!\\s))*[.!?])")
- private inline fun <reified T: TagWrapper> Documentable.findNodeInDocumentation(sourceSetData: SourceSetData?): T? =
+ private inline fun <reified T: TagWrapper> Documentable.findNodeInDocumentation(sourceSetData: DokkaSourceSet?): T? =
documentation[sourceSetData]?.firstChildOfType<T>()
- private fun Documentable.descriptionToContentNodes(sourceSet: SourceSetData? = mostTopSourceSet) = findNodeInDocumentation<Description>(sourceSet)?.let {
+ private fun Documentable.descriptionToContentNodes(sourceSet: DokkaSourceSet? = highestJvmSourceSet) = findNodeInDocumentation<Description>(sourceSet)?.let {
DocTagToContentConverter.buildContent(
it.root,
DCI(setOf(dri), JavadocContentKind.OverviewSummary),
@@ -163,7 +162,10 @@ open class JavadocPageCreator(
)
}.orEmpty()
- private fun Documentable.brief(sourceSet: SourceSetData? = mostTopSourceSet): List<ContentNode> {
+ fun List<ContentNode>.nodeForJvm(jvm: DokkaSourceSet): ContentNode =
+ first { it.sourceSets.contains(jvm) }
+
+ private fun Documentable.brief(sourceSet: DokkaSourceSet? = highestJvmSourceSet): List<ContentNode> {
val description = descriptionToContentNodes(sourceSet)
val contents = mutableListOf<ContentNode>()
for (node in description) {
diff --git a/plugins/javadoc/src/main/kotlin/javadoc/JavadocPlugin.kt b/plugins/javadoc/src/main/kotlin/javadoc/JavadocPlugin.kt
index a90e46db..504eecfd 100644
--- a/plugins/javadoc/src/main/kotlin/javadoc/JavadocPlugin.kt
+++ b/plugins/javadoc/src/main/kotlin/javadoc/JavadocPlugin.kt
@@ -1,7 +1,7 @@
package org.jetbrains.dokka.javadoc
import javadoc.JavadocDocumentableToPageTranslator
-import javadoc.KorteJavadocRenderer
+import javadoc.renderer.KorteJavadocRenderer
import org.jetbrains.dokka.CoreExtensions
import org.jetbrains.dokka.base.DokkaBase
import org.jetbrains.dokka.plugability.DokkaPlugin
@@ -12,7 +12,11 @@ class JavadocPlugin : DokkaPlugin() {
val dokkaJavadocPlugin by extending {
val dokkaBasePlugin = plugin<DokkaBase>()
CoreExtensions.renderer providing { ctx ->
- KorteJavadocRenderer(dokkaBasePlugin.querySingle { outputWriter }, ctx, "views")
+ KorteJavadocRenderer(
+ dokkaBasePlugin.querySingle { outputWriter },
+ ctx,
+ "views"
+ )
} applyIf { format == "javadoc" }
}
diff --git a/plugins/javadoc/src/main/kotlin/javadoc/KorteJavadocRenderer.kt b/plugins/javadoc/src/main/kotlin/javadoc/KorteJavadocRenderer.kt
index cec5bbca..e69de29b 100644
--- a/plugins/javadoc/src/main/kotlin/javadoc/KorteJavadocRenderer.kt
+++ b/plugins/javadoc/src/main/kotlin/javadoc/KorteJavadocRenderer.kt
@@ -1,402 +0,0 @@
-package javadoc
-
-import com.soywiz.korte.*
-import javadoc.pages.*
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.runBlocking
-import org.jetbrains.dokka.Platform
-import org.jetbrains.dokka.base.renderers.OutputWriter
-import org.jetbrains.dokka.links.DRI
-import org.jetbrains.dokka.links.sureClassNames
-import org.jetbrains.dokka.model.ImplementedInterfaces
-import org.jetbrains.dokka.model.InheritedFunction
-import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet
-import org.jetbrains.dokka.pages.*
-import org.jetbrains.dokka.plugability.DokkaContext
-import org.jetbrains.dokka.renderers.Renderer
-import org.jetbrains.kotlin.utils.addToStdlib.safeAs
-import java.nio.file.Path
-import java.nio.file.Paths
-import java.time.LocalDate
-
-typealias TemplateMap = Map<String, Any?>
-
-class KorteJavadocRenderer(val outputWriter: OutputWriter, val context: DokkaContext, val resourceDir: String) :
- Renderer {
- private lateinit var locationProvider: JavadocLocationProvider
-
- override fun render(root: RootPageNode) = root.let { preprocessors.fold(root) { r, t -> t.invoke(r) } }.let { r ->
- locationProvider = JavadocLocationProvider(r, context)
- runBlocking(Dispatchers.IO) {
- renderModulePageNode(r as JavadocModulePageNode)
- }
- }
-
- private fun templateForNode(node: JavadocPageNode) = when (node) {
- is JavadocClasslikePageNode -> "class.korte"
- is JavadocPackagePageNode -> "tabPage.korte"
- is JavadocModulePageNode -> "tabPage.korte"
- is AllClassesPage -> "listPage.korte"
- is TreeViewPage -> "treePage.korte"
- else -> ""
- }
-
- private fun CoroutineScope.renderNode(node: PageNode, path: String = "") {
- if (node is JavadocPageNode) {
- renderJavadocNode(node)
- } else if (node is RendererSpecificPage) {
- renderSpecificPage(node, path)
- }
- }
-
- private fun CoroutineScope.renderModulePageNode(node: JavadocModulePageNode) {
- val link = "."
- val name = "index"
- val pathToRoot = ""
-
- val contentMap = mapOf<String, Any?>(
- "docName" to "docName", // todo docname
- "pathToRoot" to pathToRoot,
- "kind" to "main",
- ) + ContentNodesRenderer(pathToRoot).renderJavadocContentNode(node.content)
-
- writeFromTemplate(outputWriter, "$link/$name".toNormalized(), "tabPage.korte", contentMap.toList())
- node.children.forEach { renderNode(it, link) }
- }
-
- private fun CoroutineScope.renderJavadocNode(node: JavadocPageNode) {
- val link = locationProvider.resolve(node, skipExtension = true)
- val dir = Paths.get(link).parent?.let { it.toNormalized() }.orEmpty()
- val pathToRoot = dir.split("/").filter { it.isNotEmpty() }.joinToString("/") { ".." }.let {
- if (it.isNotEmpty()) "$it/" else it
- }
-
- val contentMap = mapOf(
- "docName" to "docName", // todo docname
- "pathToRoot" to pathToRoot,
- "dir" to dir
- ) + ContentNodesRenderer(dir).renderContentNodes(node)
- writeFromTemplate(outputWriter, link, templateForNode(node), contentMap.toList())
- node.children.forEach { renderNode(it, link.toNormalized()) }
- }
-
- fun CoroutineScope.renderSpecificPage(node: RendererSpecificPage, path: String) = launch {
- when (val strategy = node.strategy) {
- is RenderingStrategy.Copy -> outputWriter.writeResources(strategy.from, "")
- is RenderingStrategy.Write -> outputWriter.writeHtml(path, strategy.text)
- is RenderingStrategy.Callback -> outputWriter.writeResources(
- path,
- strategy.instructions(this@KorteJavadocRenderer, node)
- )
- RenderingStrategy.DoNothing -> Unit
- }
- }
-
- fun Pair<String, String>.pairToTag() =
- "\n<th class=\"colFirst\" scope=\"row\">${first}</th>\n<td class=\"colLast\">${second}</td>"
-
- fun DRI.toLink(context: PageNode? = null) = locationProvider.resolve(this, emptyList(), context)
-
- fun createLinkTag(address: String, name: String) =
- address.let { if (it.endsWith(".html")) it else "$it.html" }.let {
- """<a href="$it">$name</a>"""
- }
-
- private fun Path.toNormalized() = this.normalize().toFile().toString()
- private fun String.toNormalized() = Paths.get(this).toNormalized()
- private fun String.relativizePath(parent: String) = Paths.get(parent).relativize(Paths.get(this)).toNormalized()
-
- private suspend fun OutputWriter.writeHtml(path: String, text: String) = write(path, text, ".html")
- private fun CoroutineScope.writeFromTemplate(
- writer: OutputWriter,
- path: String,
- template: String,
- args: List<Pair<String, *>>
- ) = launch {
- val tmp = templateRenderer.render(template, *(args.toTypedArray()))
- writer.writeHtml(path, tmp)
- }
-
- private fun htmlForContentNodes(content: List<ContentNode>, render: (ContentNode) -> String): String =
- content.joinToString("") { render(it) }
-
- fun getTemplateConfig() = TemplateConfig().also { config ->
- listOf(
- TeFunction("curDate") { LocalDate.now() },
- TeFunction("jQueryVersion") { "3.1" },
- TeFunction("jQueryMigrateVersion") { "1.2.1" },
- TeFunction("rowColor") { args -> if ((args.first() as Int) % 2 == 0) "altColor" else "rowColor" },
- TeFunction("h1Title") { args -> if ((args.first() as? String) == "package") "title=\"Package\" " else "" },
- TeFunction("createTabRow") { args ->
- val (link, doc) = args.first() as RowJavadocListEntry
- val dir = args[1] as String?
- val renderer = ContentNodesRenderer(dir)
- (createLinkTag(
- locationProvider.resolve(link, dir.orEmpty()),
- link.name
- ) to htmlForContentNodes(doc, renderer::htmlForContentNode)).pairToTag().trim()
- },
- TeFunction("createListRow") { args ->
- val link = args.first() as LinkJavadocListEntry
- val dir = args[1] as String?
- createLinkTag(locationProvider.resolve(link, dir.orEmpty()), link.name)
- },
- TeFunction("createPackageHierarchy") { args ->
- val list = args.first() as List<JavadocPackagePageNode>
- list.mapIndexed { i, p ->
- val content = if (i + 1 == list.size) "" else ", "
- val name = p.name
- "<li><a href=\"$name/package-tree.html\">$name</a>$content</li>"
- }.joinToString("\n")
- },
- TeFunction("renderInheritanceGraph") { args ->
- val rootNodes = args.first() as List<TreeViewPage.InheritanceNode>
-
- fun drawRec(node: TreeViewPage.InheritanceNode): String {
- val returnValue = "<li class=\"circle\">" + node.dri.let { dri ->
- listOfNotNull(
- dri.packageName,
- dri.classNames
- ).joinToString(".") + node.interfaces.takeUnless { node.isInterface || it.isEmpty() }
- ?.let {
- " implements " + it.joinToString(", ") { n ->
- listOfNotNull(
- n.packageName,
- createLinkTag(n.toLink(), n.classNames.orEmpty())
- ).joinToString(".")
- }
- }.orEmpty()
- } + node.children.filterNot { it.isInterface }.takeUnless { it.isEmpty() }?.let {
- "<ul>" + it.joinToString("\n", transform = ::drawRec) + "</ul>"
- }.orEmpty() + "</li>"
- return returnValue
- }
- rootNodes.joinToString { drawRec(it) }
- },
- Filter("length") { subject.dynamicLength() },
- TeFunction("hasAnyDescription") { args ->
- args.first().safeAs<List<HashMap<String, String>>>()
- ?.any { it["description"]?.trim()?.isNotEmpty() ?: false }
- }
- ).forEach {
- when (it) {
- is TeFunction -> config.register(it)
- is Filter -> config.register(it)
- is Tag -> config.register(it)
- }
- }
- }
-
- val config = getTemplateConfig()
- val templateRenderer = Templates(ResourceTemplateProvider(resourceDir), config = config, cache = true)
-
- class ResourceTemplateProvider(val basePath: String) : TemplateProvider {
- override suspend fun get(template: String): String =
- javaClass.classLoader.getResourceAsStream("$basePath/$template")?.bufferedReader()?.lines()?.toArray()
- ?.joinToString("\n") ?: throw IllegalStateException("Template not found: $basePath/$template")
- }
-
- private inner class ContentNodesRenderer(private val currentLocation: String?) {
- fun renderContentNodes(node: JavadocPageNode): TemplateMap =
- when (node) {
- is JavadocClasslikePageNode -> renderClasslikeNode(node)
- is JavadocFunctionNode -> renderFunctionNode(node)
- is JavadocPackagePageNode -> renderPackagePageNode(node)
- is TreeViewPage -> renderTreeViewPage(node)
- is AllClassesPage -> renderAllClassesPage(node)
- else -> emptyMap()
- }
-
-
- private fun renderAllClassesPage(node: AllClassesPage): TemplateMap {
- return mapOf(
- "title" to "All Classes",
- "list" to node.classEntries
- )
- }
-
- private fun renderTreeViewPage(node: TreeViewPage): TemplateMap {
- return mapOf(
- "title" to node.title,
- "name" to node.name,
- "kind" to node.kind,
- "list" to node.packages.orEmpty() + node.classes.orEmpty(),
- "classGraph" to node.classGraph,
- "interfaceGraph" to node.interfaceGraph
- )
- }
-
- private fun renderPackagePageNode(node: JavadocPackagePageNode): TemplateMap {
- return mapOf(
- "kind" to "package"
- ) + renderJavadocContentNode(node.content)
- }
-
- private fun renderFunctionNode(node: JavadocFunctionNode): TemplateMap {
- val (modifiers, signature) = node.modifiersAndSignature
- return mapOf(
- "signature" to htmlForContentNode(node.signature),
- "brief" to htmlForContentNodes(node.brief),
- "parameters" to node.parameters.map { renderParameterNode(it) },
- "inlineParameters" to node.parameters.joinToString { "${it.type} ${it.name}" },
- "modifiers" to htmlForContentNode(modifiers),
- "signatureWithoutModifiers" to htmlForContentNode(signature),
- "name" to node.name
- )
- }
-
- private fun renderParameterNode(node: JavadocParameterNode): TemplateMap =
- mapOf(
- "description" to htmlForContentNodes(node.description),
- "name" to node.name,
- "type" to node.type
- )
-
- private fun renderClasslikeNode(node: JavadocClasslikePageNode): TemplateMap =
- mapOf(
- "constructors" to node.constructors.map { renderContentNodes(it) },
- "signature" to htmlForContentNode(node.signature),
- "methods" to renderClasslikeMethods(node.methods),
- "classlikeDocumentation" to renderContentNodes(node.description),
- "entries" to node.entries.map { renderEntryNode(it) },
- "properties" to node.properties.map { renderPropertyNode(it) },
- "classlikes" to node.classlikes.map { renderNestedClasslikeNode(it) },
- "implementedInterfaces" to renderImplementedInterfaces(node),
- "kind" to node.kind,
- "packageName" to node.packageName,
- "name" to node.name
- ) + renderJavadocContentNode(node.content)
-
- private fun renderImplementedInterfaces(node: JavadocClasslikePageNode) =
- node.extras[ImplementedInterfaces]?.interfaces?.entries?.firstOrNull { it.key.analysisPlatform == Platform.jvm }?.value?.map { it.displayable() } // TODO: REMOVE HARDCODED JVM DEPENDENCY
- .orEmpty()
-
- private fun renderClasslikeMethods(nodes: List<JavadocFunctionNode>): TemplateMap {
- val (inherited, own) = nodes.partition {
- val extra = it.extras[InheritedFunction]
- extra?.inheritedFrom?.keys?.first { it.analysisPlatform == Platform.jvm }?.let { jvm ->
- extra.isInherited(jvm)
- } ?: false
- }
- return mapOf(
- "own" to own.map { renderContentNodes(it) },
- "inherited" to inherited.map { renderInheritedMethod(it) }
- .groupBy { it["inheritedFrom"] as String }.entries.map {
- mapOf(
- "inheritedFrom" to it.key,
- "names" to it.value.map { it["name"] as String }.sorted().joinToString()
- )
- }
- )
- }
-
- private fun renderInheritedMethod(node: JavadocFunctionNode): TemplateMap {
- val inheritedFrom = node.extras[InheritedFunction]?.inheritedFrom
- return mapOf(
- "inheritedFrom" to inheritedFrom?.entries?.firstOrNull { it.key.analysisPlatform == Platform.jvm }?.value?.displayable() // TODO: REMOVE HARDCODED JVM DEPENDENCY
- .orEmpty(),
- "name" to node.name
- )
- }
-
- private fun renderNestedClasslikeNode(node: JavadocClasslikePageNode): TemplateMap {
- return mapOf(
- "modifiers" to (node.modifiers + "static" + node.kind).joinToString(separator = " "),
- "signature" to node.name,
- "description" to renderContentNodes(node.description)
- )
- }
-
- private fun renderPropertyNode(node: JavadocPropertyNode): TemplateMap {
- val (modifiers, signature) = node.modifiersAndSignature
- return mapOf(
- "modifiers" to htmlForContentNode(modifiers),
- "signature" to htmlForContentNode(signature),
- "description" to htmlForContentNodes(node.brief)
- )
- }
-
- private fun renderEntryNode(node: JavadocEntryNode): TemplateMap {
- return mapOf(
- "signature" to htmlForContentNode(node.signature),
- "brief" to node.brief
- )
- }
-
- private fun renderContentNodes(nodes: List<ContentNode>): String = nodes.joinToString(separator = "") { htmlForContentNode(it) }
-
- fun htmlForContentNode(node: ContentNode): String =
- when (node) {
- is ContentGroup -> node.children.joinToString(separator = "") { htmlForContentNode(it) }
- is ContentText -> node.text
- is TextNode -> node.text
- is ContentDRILink -> """<a href="${resolveLink(
- node.address,
- node.sourceSets
- )}">${node.children.joinToString { htmlForContentNode(it) }} </a>""".trimMargin()
- is ContentCode -> renderCode(node.children)
- else -> ""
- }
-
- private fun renderCode(code: List<ContentNode>) : String = code.map { element ->
- when (element) {
- is ContentText -> element.text
- is ContentBreakLine -> ""
- else -> run { context.logger.error("Cannot cast $element as ContentText!"); "" }
- }
- }.joinToString("<br>", "<span class=\"code\">", "</span>") { it }
-
- fun renderJavadocContentNode(node: JavadocContentNode): TemplateMap = when (node) {
- is TitleNode -> renderTitleNode(node)
- is JavadocContentGroup -> renderJavadocContentGroup(node)
- is TextNode -> renderTextNode(node)
- is ListNode -> renderListNode(node)
- else -> emptyMap()
- }
-
- private fun renderTitleNode(node: TitleNode): TemplateMap {
- return mapOf(
- "title" to node.title,
- "subtitle" to htmlForContentNodes(node.subtitle),
- "version" to node.version,
- "packageName" to node.parent
- )
- }
-
- private fun renderJavadocContentGroup(note: JavadocContentGroup): TemplateMap {
- return note.children.fold(emptyMap<String, Any?>()) { map, child ->
- map + renderJavadocContentNode(child)
- }
- }
-
- private fun renderTextNode(node: TextNode): TemplateMap {
- return mapOf("text" to node.text)
- }
-
- private fun renderListNode(node: ListNode): TemplateMap {
- return mapOf(
- "tabTitle" to node.tabTitle,
- "colTitle" to node.colTitle,
- "list" to node.children
- )
- }
-
- private fun resolveLink(address: DRI, sourceSets: Set<DokkaSourceSet>) =
- locationProvider.resolve(address, sourceSets.toList()).let {
- val afterFormattingToHtml = formatToEndWithHtml(it)
- if (currentLocation != null) afterFormattingToHtml.relativizePath(currentLocation)
- else afterFormattingToHtml
- }
-
- private fun formatToEndWithHtml(address: String) =
- if (address.endsWith(".html")) {
- address
- } else {
- "$address.html"
- }
-
- private fun DRI.displayable(): String = "${packageName}.${sureClassNames}"
- }
-} \ No newline at end of file
diff --git a/plugins/javadoc/src/main/kotlin/javadoc/renderer/JavadocContentToHtmlTranslator.kt b/plugins/javadoc/src/main/kotlin/javadoc/renderer/JavadocContentToHtmlTranslator.kt
new file mode 100644
index 00000000..5f628626
--- /dev/null
+++ b/plugins/javadoc/src/main/kotlin/javadoc/renderer/JavadocContentToHtmlTranslator.kt
@@ -0,0 +1,66 @@
+package javadoc.renderer
+
+import javadoc.pages.TextNode
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.base.resolvers.local.LocationProvider
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.pages.*
+import org.jetbrains.dokka.plugability.DokkaContext
+import java.nio.file.Path
+import java.nio.file.Paths
+
+internal class JavadocContentToHtmlTranslator(
+ private val locationProvider: LocationProvider,
+ private val context: DokkaContext
+) {
+
+ fun <T> htmlForContentNode(node: ContentNode, relative: T?, locate: ContentDRILink.(T?) -> String): String =
+ when (node) {
+ is ContentGroup -> htmlForContentNodes(node.children, relative, locate)
+ is ContentText -> node.text
+ is TextNode -> node.text
+ is ContentDRILink -> buildLink(
+ node.locate(relative),
+ htmlForContentNodes(node.children, relative, locate)
+ )
+ is ContentResolvedLink -> buildLink(node.address, htmlForContentNodes(node.children, relative, locate))
+ is ContentCode -> htmlForCode(node.children)
+ else -> ""
+ }
+
+ fun <T> htmlForContentNodes(list: List<ContentNode>, relative: T?, locate: ContentDRILink.(T?) -> String) =
+ list.joinToString(separator = "") { htmlForContentNode(it, relative, locate) }
+
+ private fun locate(link: ContentDRILink, relativePath: String?) =
+ resolveLink(link.address, link.sourceSets, relativePath)
+
+ fun htmlForContentNodes(list: List<ContentNode>, relative: String?) =
+ htmlForContentNodes(list, relative, ::locate)
+
+ private fun htmlForCode(code: List<ContentNode>): String = code.map { element ->
+ when (element) {
+ is ContentText -> element.text
+ is ContentBreakLine -> ""
+ else -> run { context.logger.error("Cannot cast $element as ContentText!"); "" }
+ }
+ }.joinToString("<br>", """<span class="code">""", "</span>") { it }
+
+ private fun resolveLink(address: DRI, sourceSets: Set<DokkaConfiguration.DokkaSourceSet>, relativePath: String?) =
+ locationProvider.resolve(address, sourceSets).let {
+ val afterFormattingToHtml = it.formatToEndWithHtml()
+ if (relativePath != null) afterFormattingToHtml.relativizePath(relativePath)
+ else afterFormattingToHtml
+ }
+
+ private fun String.relativizePath(parent: String) =
+ Paths.get(parent).relativize(Paths.get(this)).normalize().toFile().toString()
+
+ companion object {
+
+ fun buildLink(address: String, content: String) =
+ """<a href=${address.formatToEndWithHtml()}>$content</a>"""
+
+ private fun String.formatToEndWithHtml() =
+ if (endsWith(".html")) this else "$this.html"
+ }
+} \ No newline at end of file
diff --git a/plugins/javadoc/src/main/kotlin/javadoc/renderer/JavadocContentToTemplateMapTranslator.kt b/plugins/javadoc/src/main/kotlin/javadoc/renderer/JavadocContentToTemplateMapTranslator.kt
new file mode 100644
index 00000000..4d1ccca5
--- /dev/null
+++ b/plugins/javadoc/src/main/kotlin/javadoc/renderer/JavadocContentToTemplateMapTranslator.kt
@@ -0,0 +1,203 @@
+package javadoc.renderer
+
+import javadoc.pages.*
+import org.jetbrains.dokka.Platform
+import org.jetbrains.dokka.base.resolvers.local.LocationProvider
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.links.sureClassNames
+import org.jetbrains.dokka.model.ImplementedInterfaces
+import org.jetbrains.dokka.model.InheritedFunction
+import org.jetbrains.dokka.pages.*
+import org.jetbrains.dokka.plugability.DokkaContext
+
+internal class JavadocContentToTemplateMapTranslator(
+ private val locationProvider: LocationProvider,
+ private val context: DokkaContext,
+) {
+
+ fun templateMapForPageNode(node: JavadocPageNode, pathToRoot: String): TemplateMap =
+ mapOf<String, Any?>(
+ "docName" to "docName", // todo docname
+ "pathToRoot" to pathToRoot,
+ "kind" to "main",
+ ) + templateMapForNode(node)
+
+
+ fun templateMapForNode(node: JavadocPageNode): TemplateMap =
+ when (node) {
+ is JavadocModulePageNode -> InnerTranslator(node).templateMapForJavadocContentNode(node.content)
+ is JavadocClasslikePageNode -> InnerTranslator(node).templateMapForClasslikeNode(node)
+ is JavadocFunctionNode -> InnerTranslator(node).templateMapForFunctionNode(node)
+ is JavadocPackagePageNode -> InnerTranslator(node).templateMapForPackagePageNode(node)
+ is TreeViewPage -> InnerTranslator(node).templateMapForTreeViewPage(node)
+ is AllClassesPage -> InnerTranslator(node).templateMapForAllClassesPage(node)
+ else -> emptyMap()
+ }
+
+ private inner class InnerTranslator(val contextNode: PageNode) {
+
+ private val htmlTranslator = JavadocContentToHtmlTranslator(locationProvider, context)
+
+ internal fun templateMapForAllClassesPage(node: AllClassesPage): TemplateMap {
+ return mapOf(
+ "title" to "All Classes",
+ "list" to node.classEntries
+ )
+ }
+
+ internal fun templateMapForTreeViewPage(node: TreeViewPage): TemplateMap {
+ return mapOf(
+ "title" to node.title,
+ "name" to node.name,
+ "kind" to node.kind,
+ "list" to node.packages.orEmpty() + node.classes.orEmpty(),
+ "classGraph" to node.classGraph,
+ "interfaceGraph" to node.interfaceGraph
+ )
+ }
+
+ internal fun templateMapForPackagePageNode(node: JavadocPackagePageNode): TemplateMap {
+ return mapOf(
+ "kind" to "package"
+ ) + templateMapForJavadocContentNode(node.content)
+ }
+
+ internal fun templateMapForFunctionNode(node: JavadocFunctionNode): TemplateMap {
+ val (modifiers, signature) = node.modifiersAndSignature
+ return mapOf(
+ "signature" to htmlForContentNode(node.signature, node),
+ "brief" to htmlForContentNodes(node.brief, node),
+ "parameters" to node.parameters.map { templateMapForParameterNode(it) },
+ "inlineParameters" to node.parameters.joinToString { "${it.type} ${it.name}" },
+ "modifiers" to htmlForContentNode(modifiers, node),
+ "signatureWithoutModifiers" to htmlForContentNode(signature, node),
+ "name" to node.name
+ )
+ }
+
+ internal fun templateMapForClasslikeNode(node: JavadocClasslikePageNode): TemplateMap =
+ mapOf(
+ "constructors" to node.constructors.map { templateMapForNode(it) },
+ "signature" to htmlForContentNode(node.signature, node),
+ "methods" to templateMapForClasslikeMethods(node.methods),
+ "classlikeDocumentation" to htmlForContentNodes(node.description, node),
+ "entries" to node.entries.map { templateMapForEntryNode(it) },
+ "properties" to node.properties.map { templateMapForPropertyNode(it) },
+ "classlikes" to node.classlikes.map { templateMapForNestedClasslikeNode(it) },
+ "implementedInterfaces" to templateMapForImplementedInterfaces(node),
+ "kind" to node.kind,
+ "packageName" to node.packageName,
+ "name" to node.name
+ ) + templateMapForJavadocContentNode(node.content)
+
+ internal fun templateMapForJavadocContentNode(node: JavadocContentNode): TemplateMap =
+ when (node) {
+ is TitleNode -> templateMapForTitleNode(node)
+ is JavadocContentGroup -> templateMapForJavadocContentGroup(node)
+ is TextNode -> templateMapForTextNode(node)
+ is ListNode -> templateMapForListNode(node)
+ else -> emptyMap()
+ }
+
+ private fun templateMapForParameterNode(node: JavadocParameterNode): TemplateMap =
+ mapOf(
+ "description" to htmlForContentNodes(node.description, contextNode),
+ "name" to node.name,
+ "type" to node.type
+ )
+
+ private fun templateMapForImplementedInterfaces(node: JavadocClasslikePageNode) =
+ node.extras[ImplementedInterfaces]?.interfaces?.entries?.firstOrNull { it.key.analysisPlatform == Platform.jvm }?.value?.map { it.displayable() } // TODO: REMOVE HARDCODED JVM DEPENDENCY
+ .orEmpty()
+
+ private fun templateMapForClasslikeMethods(nodes: List<JavadocFunctionNode>): TemplateMap {
+ val (inherited, own) = nodes.partition {
+ val extra = it.extras[InheritedFunction]
+ extra?.inheritedFrom?.keys?.first { it.analysisPlatform == Platform.jvm }?.let { jvm ->
+ extra.isInherited(jvm)
+ } ?: false
+ }
+ return mapOf(
+ "own" to own.map { templateMapForNode(it) },
+ "inherited" to inherited.map { templateMapForInheritedMethod(it) }
+ .groupBy { it["inheritedFrom"] as String }.entries.map {
+ mapOf(
+ "inheritedFrom" to it.key,
+ "names" to it.value.map { it["name"] as String }.sorted().joinToString()
+ )
+ }
+ )
+ }
+
+ private fun templateMapForInheritedMethod(node: JavadocFunctionNode): TemplateMap {
+ val inheritedFrom = node.extras[InheritedFunction]?.inheritedFrom
+ return mapOf(
+ "inheritedFrom" to inheritedFrom?.entries?.firstOrNull { it.key.analysisPlatform == Platform.jvm }?.value?.displayable() // TODO: REMOVE HARDCODED JVM DEPENDENCY
+ .orEmpty(),
+ "name" to node.name
+ )
+ }
+
+ private fun templateMapForNestedClasslikeNode(node: JavadocClasslikePageNode): TemplateMap {
+ return mapOf(
+ "modifiers" to (node.modifiers + "static" + node.kind).joinToString(separator = " "),
+ "signature" to node.name,
+ "description" to htmlForContentNodes(node.description, node)
+ )
+ }
+
+ private fun templateMapForPropertyNode(node: JavadocPropertyNode): TemplateMap {
+ val (modifiers, signature) = node.modifiersAndSignature
+ return mapOf(
+ "modifiers" to htmlForContentNode(modifiers, contextNode),
+ "signature" to htmlForContentNode(signature, contextNode),
+ "description" to htmlForContentNodes(node.brief, contextNode)
+ )
+ }
+
+ private fun templateMapForEntryNode(node: JavadocEntryNode): TemplateMap {
+ return mapOf(
+ "signature" to htmlForContentNode(node.signature, contextNode),
+ "brief" to node.brief
+ )
+ }
+
+ private fun templateMapForTitleNode(node: TitleNode): TemplateMap {
+ return mapOf(
+ "title" to node.title,
+ "subtitle" to htmlForContentNodes(node.subtitle, contextNode),
+ "version" to node.version,
+ "packageName" to node.parent
+ )
+ }
+
+ private fun templateMapForJavadocContentGroup(note: JavadocContentGroup): TemplateMap {
+ return note.children.fold(emptyMap()) { map, child ->
+ map + templateMapForJavadocContentNode(child)
+ }
+ }
+
+ private fun templateMapForTextNode(node: TextNode): TemplateMap {
+ return mapOf("text" to node.text)
+ }
+
+ private fun templateMapForListNode(node: ListNode): TemplateMap {
+ return mapOf(
+ "tabTitle" to node.tabTitle,
+ "colTitle" to node.colTitle,
+ "list" to node.children
+ )
+ }
+ fun locate(link: ContentDRILink, relativeNode: PageNode?) =
+ locationProvider.resolve(link.address, link.sourceSets, relativeNode)
+
+ private fun htmlForContentNode(node: ContentNode, relativeNode: PageNode) =
+ htmlTranslator.htmlForContentNode(node, relativeNode, ::locate)
+
+ private fun htmlForContentNodes(nodes: List<ContentNode>, relativeNode: PageNode) =
+ htmlTranslator.htmlForContentNodes(nodes, relativeNode, ::locate)
+ }
+
+ private fun DRI.displayable(): String = "${packageName}.${sureClassNames}"
+}
+
diff --git a/plugins/javadoc/src/main/kotlin/javadoc/renderer/KorteJavadocRenderer.kt b/plugins/javadoc/src/main/kotlin/javadoc/renderer/KorteJavadocRenderer.kt
new file mode 100644
index 00000000..7b122b7d
--- /dev/null
+++ b/plugins/javadoc/src/main/kotlin/javadoc/renderer/KorteJavadocRenderer.kt
@@ -0,0 +1,186 @@
+package javadoc.renderer
+
+import com.soywiz.korte.*
+import javadoc.JavadocLocationProvider
+import javadoc.pages.*
+import javadoc.renderer.JavadocContentToHtmlTranslator.Companion.buildLink
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import org.jetbrains.dokka.base.renderers.OutputWriter
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.pages.*
+import org.jetbrains.dokka.plugability.DokkaContext
+import org.jetbrains.dokka.renderers.Renderer
+import org.jetbrains.kotlin.utils.addToStdlib.safeAs
+import java.nio.file.Path
+import java.nio.file.Paths
+import java.time.LocalDate
+
+typealias TemplateMap = Map<String, Any?>
+
+class KorteJavadocRenderer(val outputWriter: OutputWriter, val context: DokkaContext, val resourceDir: String) :
+ Renderer {
+ private lateinit var locationProvider: JavadocLocationProvider
+
+ override fun render(root: RootPageNode) = root.let { preprocessors.fold(root) { r, t -> t.invoke(r) } }.let { r ->
+ locationProvider = JavadocLocationProvider(r, context)
+ runBlocking(Dispatchers.IO) {
+ renderModulePageNode(r as JavadocModulePageNode)
+ }
+ }
+
+ private fun templateForNode(node: JavadocPageNode) = when (node) {
+ is JavadocModulePageNode,
+ is JavadocPackagePageNode -> "tabPage.korte"
+ is JavadocClasslikePageNode -> "class.korte"
+ is AllClassesPage -> "listPage.korte"
+ is TreeViewPage -> "treePage.korte"
+ else -> ""
+ }
+
+ private fun CoroutineScope.renderNode(node: PageNode, path: String = "") {
+ if (node is JavadocPageNode) {
+ renderJavadocPageNode(node)
+ } else if (node is RendererSpecificPage) {
+ renderSpecificPage(node, path)
+ }
+ }
+
+ private fun CoroutineScope.renderModulePageNode(node: JavadocModulePageNode) {
+ val link = "."
+ val name = "index"
+ val pathToRoot = ""
+
+ val contentMap = JavadocContentToTemplateMapTranslator(locationProvider, context).templateMapForPageNode(node, pathToRoot)
+
+ writeFromTemplate(outputWriter, "$link/$name".toNormalized(), "tabPage.korte", contentMap.toList())
+ node.children.forEach { renderNode(it, link) }
+ }
+
+ private fun CoroutineScope.renderJavadocPageNode(node: JavadocPageNode) {
+ val link = locationProvider.resolve(node, skipExtension = true)
+ val dir = Paths.get(link).parent?.let { it.toNormalized() }.orEmpty()
+ val pathToRoot = dir.split("/").filter { it.isNotEmpty() }.joinToString("/") { ".." }.let {
+ if (it.isNotEmpty()) "$it/" else it
+ }
+
+ val contentMap = JavadocContentToTemplateMapTranslator(locationProvider, context).templateMapForPageNode(node, pathToRoot)
+ writeFromTemplate(outputWriter, link, templateForNode(node), contentMap.toList())
+ node.children.forEach { renderNode(it, link.toNormalized()) }
+ }
+
+ fun CoroutineScope.renderSpecificPage(node: RendererSpecificPage, path: String) = launch {
+ when (val strategy = node.strategy) {
+ is RenderingStrategy.Copy -> outputWriter.writeResources(strategy.from, "")
+ is RenderingStrategy.Write -> outputWriter.writeHtml(path, strategy.text)
+ is RenderingStrategy.Callback -> outputWriter.writeResources(
+ path,
+ strategy.instructions(this@KorteJavadocRenderer, node)
+ )
+ RenderingStrategy.DoNothing -> Unit
+ }
+ }
+
+ fun Pair<String, String>.pairToTag() =
+ """<th class="colFirst" scope="row">${first}</th>\n<td class="colLast">${second}</td>"""
+
+ fun DRI.toLink(context: PageNode? = null) = locationProvider.resolve(this, emptySet(), context)
+
+ private fun Path.toNormalized() = this.normalize().toFile().toString()
+ private fun String.toNormalized() = Paths.get(this).toNormalized()
+
+ private suspend fun OutputWriter.writeHtml(path: String, text: String) = write(path, text, ".html")
+ private fun CoroutineScope.writeFromTemplate(
+ writer: OutputWriter,
+ path: String,
+ template: String,
+ args: List<Pair<String, *>>
+ ) = launch {
+ val tmp = templateRenderer.render(template, *(args.toTypedArray()))
+ writer.writeHtml(path, tmp)
+ }
+
+ fun getTemplateConfig() = TemplateConfig().also { config ->
+ listOf(
+ TeFunction("curDate") { LocalDate.now() },
+ TeFunction("jQueryVersion") { "3.1" },
+ TeFunction("jQueryMigrateVersion") { "1.2.1" },
+ TeFunction("rowColor") { args -> if ((args.first() as Int) % 2 == 0) "altColor" else "rowColor" },
+ TeFunction("h1Title") { args -> if ((args.first() as? String) == "package") "title=\"Package\" " else "" },
+ TeFunction("createTabRow") { args ->
+ val (link, doc) = args.first() as RowJavadocListEntry
+ val dir = args[1] as String?
+ val translator = JavadocContentToHtmlTranslator(locationProvider, context)
+ (buildLink(
+ locationProvider.resolve(link, dir.orEmpty()),
+ link.name
+ ) to translator.htmlForContentNodes(doc, dir)).pairToTag().trim()
+ },
+ TeFunction("createListRow") { args ->
+ val link = args.first() as LinkJavadocListEntry
+ val dir = args[1] as String?
+ buildLink(
+ locationProvider.resolve(link, dir.orEmpty()),
+ link.name
+ )
+ },
+ TeFunction("createPackageHierarchy") { args ->
+ val list = args.first() as List<JavadocPackagePageNode>
+ list.mapIndexed { i, p ->
+ val content = if (i + 1 == list.size) "" else ", "
+ val name = p.name
+ "<li><a href=\"$name/package-tree.html\">$name</a>$content</li>"
+ }.joinToString("\n")
+ },
+ TeFunction("renderInheritanceGraph") { args ->
+ val rootNodes = args.first() as List<TreeViewPage.InheritanceNode>
+
+ fun drawRec(node: TreeViewPage.InheritanceNode): String =
+ "<li class=\"circle\">" + node.dri.let { dri ->
+ listOfNotNull(
+ dri.packageName,
+ dri.classNames
+ ).joinToString(".") + node.interfaces.takeUnless { node.isInterface || it.isEmpty() }
+ ?.let {
+ " implements " + it.joinToString(", ") { n ->
+ listOfNotNull(
+ n.packageName,
+ buildLink(n.toLink(), n.classNames.orEmpty())
+ ).joinToString(".")
+ }
+ }.orEmpty()
+ } + node.children.filterNot { it.isInterface }.takeUnless { it.isEmpty() }?.let {
+ "<ul>" + it.joinToString("\n", transform = ::drawRec) + "</ul>"
+ }.orEmpty() + "</li>"
+
+ rootNodes.joinToString { drawRec(it) }
+ },
+ Filter("length") { subject.dynamicLength() },
+ TeFunction("hasAnyDescription") { args ->
+ args.first().safeAs<List<HashMap<String, String>>>()
+ ?.any { it["description"]?.trim()?.isNotEmpty() ?: false }
+ }
+ ).forEach {
+ when (it) {
+ is TeFunction -> config.register(it)
+ is Filter -> config.register(it)
+ is Tag -> config.register(it)
+ }
+ }
+ }
+
+ val config = getTemplateConfig()
+ val templateRenderer = Templates(
+ ResourceTemplateProvider(
+ resourceDir
+ ), config = config, cache = true)
+
+ class ResourceTemplateProvider(val basePath: String) : TemplateProvider {
+ override suspend fun get(template: String): String =
+ javaClass.classLoader.getResourceAsStream("$basePath/$template")?.bufferedReader()?.lines()?.toArray()
+ ?.joinToString("\n") ?: throw IllegalStateException("Template not found: $basePath/$template")
+ }
+
+} \ No newline at end of file
diff --git a/plugins/javadoc/src/main/resources/views/class.korte b/plugins/javadoc/src/main/resources/views/class.korte
index a9120fb2..6585e6b4 100644
--- a/plugins/javadoc/src/main/resources/views/class.korte
+++ b/plugins/javadoc/src/main/resources/views/class.korte
@@ -33,7 +33,7 @@
{% endif %}
<hr>
<pre>{{ signature|raw }}</pre>
- <div class="block">{{ classlikeDocumentation }}</div>
+ <div class="block">{{ classlikeDocumentation|raw }}</div>
</li>
</ul>
</div>
@@ -115,7 +115,7 @@
<tr class="{{ rowColor(loop.index0) }}">
<th class="colConstructorName" scope="row"><code><span class="memberNameLink"><a
href="#%3Cinit%3E({{ constructor.inlineParameters }})">{{ constructor.name }}</a></span>({{ constructor.inlineParameters }})</code></th>
- <td class="colLast">{{ constructor.brief }}</td>
+ <td class="colLast">{{ constructor.brief|raw }}</td>
</tr>
{% endfor %}
@@ -143,7 +143,7 @@
<tr class="{{ rowColor(loop.index0) }}">
<th class="colFirst" scope="row"><code><span class="memberNameLink"><a
href="TODO">{{ entry.signature|raw }}</a></span></code></th>
- <td class="colLast">{{ entry.brief }}</td>
+ <td class="colLast">{{ entry.brief|raw }}</td>
</tr>
{% endfor %}
</table>
@@ -223,7 +223,7 @@
<li class="blockList">
<h4>{{ constructor.name }}</h4>
<pre>{{ constructor.name }}({{ constructor.inlineParameters }})</pre>
- <div class="block">{{ constructor.brief }}</div>
+ <div class="block">{{ constructor.brief|raw}}</div>
{% if constructor.parameters.size != 0 && hasAnyDescription(constructor.parameters) %}
<dl>
<dt><span class="paramLabel">Parameters:</span></dt>
diff --git a/plugins/javadoc/src/main/resources/views/components/indexPage.korte b/plugins/javadoc/src/main/resources/views/components/indexPage.korte
index adba2457..02b94b75 100644
--- a/plugins/javadoc/src/main/resources/views/components/indexPage.korte
+++ b/plugins/javadoc/src/main/resources/views/components/indexPage.korte
@@ -4,7 +4,7 @@
</div>
<div class="header">
<div class="subtitle">
- <div class="block">{{ subtitle }}</div>
+ <div class="block">{{ subtitle|raw }}</div>
</div>
<p>See: <a href="#overview_description">Description</a></p>
</div>