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