diff options
Diffstat (limited to 'plugins')
15 files changed, 486 insertions, 426 deletions
diff --git a/plugins/base/.gitignore b/plugins/base/.gitignore index a259cd26..d68571db 100644 --- a/plugins/base/.gitignore +++ b/plugins/base/.gitignore @@ -1,2 +1,3 @@ src/main/resources/dokka/scripts/main.js -src/main/resources/dokka/scripts/main.js.map
\ No newline at end of file +src/main/resources/dokka/scripts/main.js.map +search-component/dist/
\ No newline at end of file diff --git a/plugins/base/src/main/kotlin/renderers/DefaultRenderer.kt b/plugins/base/src/main/kotlin/renderers/DefaultRenderer.kt index 09d6cad1..4313f1e3 100644 --- a/plugins/base/src/main/kotlin/renderers/DefaultRenderer.kt +++ b/plugins/base/src/main/kotlin/renderers/DefaultRenderer.kt @@ -104,7 +104,7 @@ abstract class DefaultRenderer<T>( is ContentHeader -> buildHeader(node, pageContext, sourceSetRestriction) is ContentCode -> buildCode(node, pageContext) is ContentDRILink -> - buildLink(locationProvider.resolve(node.address, node.sourceSets.toList(), pageContext)) { + buildLink(locationProvider.resolve(node.address, node.sourceSets, pageContext)) { buildLinkText(node.children, pageContext, sourceSetRestriction) } is ContentResolvedLink -> buildLink(node.address) { diff --git a/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt b/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt index d56ab50c..de892bb1 100644 --- a/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt +++ b/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt @@ -526,7 +526,7 @@ open class HtmlRenderer( platforms: List<DokkaSourceSet>, from: PageNode? = null, block: FlowContent.() -> Unit - ) = buildLink(locationProvider.resolve(to, platforms, from), block) + ) = buildLink(locationProvider.resolve(to, platforms.toSet(), from), block) override fun buildError(node: ContentNode) { context.logger.error("Unknown ContentNode type: $node") diff --git a/plugins/base/src/main/kotlin/resolvers/local/DefaultLocationProvider.kt b/plugins/base/src/main/kotlin/resolvers/local/DefaultLocationProvider.kt index 4d44fdef..6c4869d8 100644 --- a/plugins/base/src/main/kotlin/resolvers/local/DefaultLocationProvider.kt +++ b/plugins/base/src/main/kotlin/resolvers/local/DefaultLocationProvider.kt @@ -46,7 +46,7 @@ open class DefaultLocationProvider( override fun resolve(node: PageNode, context: PageNode?, skipExtension: Boolean): String = pathTo(node, context) + if (!skipExtension) extension else "" - override fun resolve(dri: DRI, sourceSets: List<DokkaSourceSet>, context: PageNode?): String = + override fun resolve(dri: DRI, sourceSets: Set<DokkaSourceSet>, context: PageNode?): String = pagesIndex[dri]?.let { resolve(it, context) } ?: // Not found in PageGraph, that means it's an external link getLocation( diff --git a/plugins/base/src/main/kotlin/resolvers/local/LocationProvider.kt b/plugins/base/src/main/kotlin/resolvers/local/LocationProvider.kt index d6d616f3..745636d0 100644 --- a/plugins/base/src/main/kotlin/resolvers/local/LocationProvider.kt +++ b/plugins/base/src/main/kotlin/resolvers/local/LocationProvider.kt @@ -6,7 +6,7 @@ import org.jetbrains.dokka.pages.PageNode import org.jetbrains.dokka.pages.RootPageNode interface LocationProvider { - fun resolve(dri: DRI, sourceSets: List<DokkaSourceSet>, context: PageNode? = null): String + fun resolve(dri: DRI, sourceSets: Set<DokkaSourceSet>, context: PageNode? = null): String fun resolve(node: PageNode, context: PageNode? = null, skipExtension: Boolean = false): String fun resolveRoot(node: PageNode): String fun ancestors(node: PageNode): List<PageNode> diff --git a/plugins/base/src/main/kotlin/resolvers/local/MultimoduleLocationProvider.kt b/plugins/base/src/main/kotlin/resolvers/local/MultimoduleLocationProvider.kt index 39b005a1..1d41bce0 100644 --- a/plugins/base/src/main/kotlin/resolvers/local/MultimoduleLocationProvider.kt +++ b/plugins/base/src/main/kotlin/resolvers/local/MultimoduleLocationProvider.kt @@ -14,7 +14,7 @@ class MultimoduleLocationProvider(private val root: RootPageNode, context: Dokka it.name to it.path }.toMap() - override fun resolve(dri: DRI, sourceSets: List<DokkaSourceSet>, context: PageNode?): String = + override fun resolve(dri: DRI, sourceSets: Set<DokkaSourceSet>, context: PageNode?): String = dri.takeIf { it.packageName == MULTIMODULE_PACKAGE_PLACEHOLDER }?.classNames?.let { paths[it] }?.let { "$it/${dri.classNames}/index.html" } ?: defaultLocationProvider.resolve(dri, sourceSets, context) 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> |