Improve formatting of raw factbooks
authorLanius Trolling <lanius@laniustrolling.dev>
Fri, 8 Mar 2024 13:20:37 +0000 (08:20 -0500)
committerLanius Trolling <lanius@laniustrolling.dev>
Fri, 8 Mar 2024 13:20:37 +0000 (08:20 -0500)
src/jvmMain/kotlin/info/mechyrdia/lore/article_listing.kt
src/jvmMain/kotlin/info/mechyrdia/lore/http_utils.kt
src/jvmMain/kotlin/info/mechyrdia/lore/parser_raw.kt
src/jvmMain/kotlin/info/mechyrdia/lore/parser_tags.kt
src/jvmMain/kotlin/info/mechyrdia/lore/view_tpl.kt
src/jvmMain/kotlin/info/mechyrdia/lore/views_robots.kt
src/jvmMain/resources/static/raw.css [new file with mode: 0644]

index 14643cc7a169115f26ba894b25ba5e1c42ae0fc9..5ae6e26fc198dcbf9ea71e3df715c59945b724d0 100644 (file)
@@ -32,7 +32,7 @@ val File.isViewable: Boolean
        get() = name.isViewable
 
 fun List<ArticleNode>.renderInto(list: UL, base: String? = null, suffix: String = "") {
-       val prefix by lazy(LazyThreadSafetyMode.NONE) { base?.let { "$it/" } ?: "" }
+       val prefix by lazy(LazyThreadSafetyMode.NONE) { base?.let { "$it/" }.orEmpty() }
        for (node in this) {
                if (node.isViewable)
                        list.li {
index 923c971a0ff75f954057bdb23859372d9e470c56..11d59984f4eb5757a8f98bddb38d2bf5f95a8f11 100644 (file)
@@ -8,7 +8,7 @@ fun redirect(url: String, permanent: Boolean = false): Nothing = throw HttpRedir
 
 fun redirectWithError(url: String, error: String, hash: String? = null): Nothing {
        val parameters = parametersOf("error", error).formUrlEncode()
-       val markedHash = hash?.let { "#$it" } ?: ""
+       val markedHash = hash?.let { "#$it" }.orEmpty()
        val urlWithError = "$url?$parameters$markedHash"
        redirect(urlWithError, false)
 }
index 1cc340b3d7ff93a7c764ebfd0ef85b142610bc78..7f7bfde4ff5595bc919e0d8098ff9e3244de2f76 100644 (file)
@@ -7,24 +7,52 @@ import java.io.File
 fun String.toRawLink() = substringBeforeLast('#') + ".raw"
 
 enum class TextParserRawPageTag(val type: TextParserTagType<Unit>) {
+       B(
+               TextParserTagType.Direct(
+                       false,
+                       { _, _ -> "<span data-format=\"b\">" },
+                       { _ -> "</span>" },
+               )
+       ),
+       I(
+               TextParserTagType.Direct(
+                       false,
+                       { _, _ -> "<span data-format=\"i\">" },
+                       { _ -> "</span>" },
+               )
+       ),
+       U(
+               TextParserTagType.Direct(
+                       false,
+                       { _, _ -> "<span data-format=\"u\">" },
+                       { _ -> "</span>" },
+               )
+       ),
+       S(
+               TextParserTagType.Direct(
+                       false,
+                       { _, _ -> "<span data-format=\"s\">" },
+                       { _ -> "</span>" },
+               )
+       ),
        IPA(
                TextParserTagType.Direct(
                        false,
-                       { _, _ -> "" },
-                       { _ -> "" },
+                       { _, _ -> "<span data-format=\"ipa\">" },
+                       { _ -> "</span>" },
                )
        ),
        CODE(
                TextParserTagType.Direct(
                        false,
-                       { _, _ -> "<span style='font-family: monospace'>" },
+                       { _, _ -> "<span data-format=\"code\">" },
                        { _ -> "</span>" },
                )
        ),
        CODE_BLOCK(
                TextParserTagType.Direct(
                        false,
-                       { _, _ -> "<div style='font-family: monospace'><pre>" },
+                       { _, _ -> "<div data-format=\"code\"><pre>" },
                        { _ -> "</pre></div>" },
                )
        ),
@@ -70,13 +98,25 @@ enum class TextParserRawPageTag(val type: TextParserTagType<Unit>) {
                        { _ -> "</h6>" }
                )
        ),
+       ALIGN(
+               TextParserTagType.Direct(
+                       true,
+                       { tagParam, _ ->
+                               val alignments = setOf("left", "center", "right", "justify")
+                               val alignment = tagParam?.takeIf { it in alignments }
+                               val styleAttr = alignment?.let { " data-align=\"$it\"" }.orEmpty()
+                               "<div$styleAttr>"
+                       },
+                       { _ -> "</div>" },
+               )
+       ),
        ASIDE(
                TextParserTagType.Direct(
                        true,
                        { tagParam, _ ->
                                val floats = setOf("left", "right")
                                val float = tagParam?.takeIf { it in floats } ?: "right"
-                               "<div style='float: $float; max-width: 50vw'>"
+                               "<div data-aside=\"$float\">"
                        },
                        { _ -> "</div>" },
                )
@@ -91,9 +131,9 @@ enum class TextParserRawPageTag(val type: TextParserTagType<Unit>) {
                                File(Configuration.CurrentConfiguration.assetDir, "images")
                                        .combineSafe(imageUrl)
                                        .readText()
-                                       .replace("<svg", "<svg${getRawImageSizeAttributes(width, height)}")
+                                       .replaceFirst("<svg", "<svg${getRawImageSizeAttributes(width, height)}")
                        else
-                               "<img${getRawImageSizeAttributes(width, height)} src='/assets/images/$imageUrl'/>"
+                               "<img${getRawImageSizeAttributes(width, height)} src=\"/assets/images/$imageUrl\"/>"
                }
        ),
        MODEL(
@@ -109,7 +149,7 @@ enum class TextParserRawPageTag(val type: TextParserTagType<Unit>) {
        TABLE(
                TextParserTagType.Direct(
                        true,
-                       { _, _ -> "<table style='width: 100%; table-layout: fixed'>" },
+                       { _, _ -> "<table>" },
                        { _ -> "</table>" },
                )
        ),
@@ -120,7 +160,7 @@ enum class TextParserRawPageTag(val type: TextParserTagType<Unit>) {
                                val (width, height) = getSizeParam(tagParam)
                                val sizeAttrs = getTableSizeAttributes(width, height)
                                
-                               "<td$sizeAttrs style='border: 1px solid #555'>"
+                               "<td$sizeAttrs>"
                        },
                        { _ -> "</td>" },
                )
@@ -132,7 +172,7 @@ enum class TextParserRawPageTag(val type: TextParserTagType<Unit>) {
                                val (width, height) = getSizeParam(tagParam)
                                val sizeAttrs = getTableSizeAttributes(width, height)
                                
-                               "<th$sizeAttrs style='border: 1px solid #222'>"
+                               "<th$sizeAttrs>"
                        },
                        { _ -> "</th>" },
                )
@@ -143,7 +183,7 @@ enum class TextParserRawPageTag(val type: TextParserTagType<Unit>) {
                        { tagParam, _ ->
                                val param = tagParam?.let { TextParserState.censorText(it) }
                                val url = param?.let { if (it.startsWith('/')) "/lore$it" else "./$it" }
-                               val attr = url?.let { " href=\"${it.toRawLink()}\"" } ?: ""
+                               val attr = url?.let { " href=\"${it.toRawLink()}\"" }.orEmpty()
                                
                                "<a$attr>"
                        },
@@ -155,13 +195,16 @@ enum class TextParserRawPageTag(val type: TextParserTagType<Unit>) {
                        val target = TextParserState.censorText(content)
                        val url = if (target.startsWith('/')) "/lore$target" else "./$target"
                        
-                       "<a href='${url.toRawLink()}'>Click here for a manual redirect</a>"
+                       "<a href=\"${url.toRawLink()}\">Click here for a manual redirect</a>"
                }
        ),
        LANG(
                TextParserTagType.Direct(
                        false,
-                       { _, _ -> "<span style='font-style: italic'>" },
+                       { param, _ ->
+                               val lang = param?.let { sanitizeLang(it) } ?: "foreign"
+                               "<span style=\"lang-$lang\">"
+                       },
                        { _ -> "</span>" }
                )
        ),
@@ -184,5 +227,8 @@ enum class TextParserRawPageTag(val type: TextParserTagType<Unit>) {
        }
 }
 
-fun getRawImageSizeStyleValue(width: Int?, height: Int?) = (width?.let { "width: ${it * 0.25}px;" } ?: "") + (height?.let { "height: ${it * 0.25}px;" } ?: "")
-fun getRawImageSizeAttributes(width: Int?, height: Int?) = " style=\"${getRawImageSizeStyleValue(width, height)}\""
+fun getRawImageSizeStyleValue(width: Int?, height: Int?) = width?.let { "width:${it * 0.25}px;" }.orEmpty() + height?.let { "height:${it * 0.25}px;" }.orEmpty()
+fun getRawImageSizeAttributes(width: Int?, height: Int?) = width?.let { " data-width=\"$it\"" }.orEmpty() + height?.let { " data-height=\"$it\"" }.orEmpty() + " style=\"${getRawImageSizeStyleValue(width, height)}\""
+
+val NON_LANG_CHAR = Regex("[^a-z0-9\\-]")
+fun sanitizeLang(attr: String) = attr.replace(NON_LANG_CHAR, "")
index 7fa4183ee7fb7bff1992c93330ad46deaf8c949d..aec52ae870d9c9a9d8210c00558f08946e52081c 100644 (file)
@@ -52,28 +52,28 @@ enum class TextParserFormattingTag(val type: TextParserTagType<Unit>) {
        B(
                TextParserTagType.Direct(
                        false,
-                       { _, _ -> "<span style='font-weight: bold'>" },
+                       { _, _ -> "<span style=\"font-weight:bold\">" },
                        { _ -> "</span>" },
                )
        ),
        I(
                TextParserTagType.Direct(
                        false,
-                       { _, _ -> "<span style='font-style: italic'>" },
+                       { _, _ -> "<span style=\"font-style:italic\">" },
                        { _ -> "</span>" },
                )
        ),
        U(
                TextParserTagType.Direct(
                        false,
-                       { _, _ -> "<span style='text-decoration: underline'>" },
+                       { _, _ -> "<span style=\"text-decoration:underline\">" },
                        { _ -> "</span>" },
                )
        ),
        S(
                TextParserTagType.Direct(
                        false,
-                       { _, _ -> "<span style='text-decoration: line-through'>" },
+                       { _, _ -> "<span style=\"text-decoration:line-through\">" },
                        { _ -> "</span>" },
                )
        ),
@@ -96,7 +96,7 @@ enum class TextParserFormattingTag(val type: TextParserTagType<Unit>) {
                        false,
                        { tagParam, _ ->
                                val color = tagParam?.toIntOrNull(16)?.toString(16)?.padStart(6, '0')
-                               val style = color?.let { " style=\"color: #$it\"" } ?: ""
+                               val style = color?.let { " style=\"color:#$it\"" }.orEmpty()
                                "<span$style>"
                        },
                        { _ -> "</span>" },
@@ -105,57 +105,57 @@ enum class TextParserFormattingTag(val type: TextParserTagType<Unit>) {
        IPA(
                TextParserTagType.Direct(
                        false,
-                       { _, _ -> "<span style='font-family: DejaVu Sans'>" },
+                       { _, _ -> "<span style=\"font-family:DejaVu Sans\">" },
                        { _ -> "</span>" },
                )
        ),
        CODE(
                TextParserTagType.Direct(
                        false,
-                       { _, _ -> "<span style='font-family: JetBrains Mono'>" },
+                       { _, _ -> "<span style=\"font-family:JetBrains Mono\">" },
                        { _ -> "</span>" },
                )
        ),
        CODE_BLOCK(
                TextParserTagType.Direct(
                        true,
-                       { _, _ -> "<div style='font-family: JetBrains Mono'><pre>" },
+                       { _, _ -> "<div style=\"font-family:JetBrains Mono\"><pre>" },
                        { _ -> "</pre></div>" },
                )
        ),
        H1(
                TextParserTagType.Indirect(true) { _, content, _ ->
-                       "<h1>$content</h1><script>window.checkRedirectTarget('');</script>"
+                       "<h1>$content</h1><script>window.checkRedirectTarget(\"\");</script>"
                }
        ),
        H2(
                TextParserTagType.Indirect(true) { _, content, _ ->
                        val anchor = headerContentToAnchor(content)
-                       "</section><section><h2><a id='$anchor'></a>$content</h2><script>window.checkRedirectTarget('#$anchor');</script>"
+                       "</section><section><h2><a id=\"$anchor\"></a>$content</h2><script>window.checkRedirectTarget(\"#$anchor\");</script>"
                }
        ),
        H3(
                TextParserTagType.Indirect(true) { _, content, _ ->
                        val anchor = headerContentToAnchor(content)
-                       "<h3><a id='$anchor'></a>$content</h3><script>window.checkRedirectTarget('#$anchor');</script>"
+                       "<h3><a id=\"$anchor\"></a>$content</h3><script>window.checkRedirectTarget(\"#$anchor\");</script>"
                }
        ),
        H4(
                TextParserTagType.Indirect(true) { _, content, _ ->
                        val anchor = headerContentToAnchor(content)
-                       "<h4><a id='$anchor'></a>$content</h4><script>window.checkRedirectTarget('#$anchor');</script>"
+                       "<h4><a id=\"$anchor\"></a>$content</h4><script>window.checkRedirectTarget(\"#$anchor\");</script>"
                }
        ),
        H5(
                TextParserTagType.Indirect(true) { _, content, _ ->
                        val anchor = headerContentToAnchor(content)
-                       "<h5><a id='$anchor'></a>$content</h5><script>window.checkRedirectTarget('#$anchor');</script>"
+                       "<h5><a id=\"$anchor\"></a>$content</h5><script>window.checkRedirectTarget(\"#$anchor\");</script>"
                }
        ),
        H6(
                TextParserTagType.Indirect(true) { _, content, _ ->
                        val anchor = headerContentToAnchor(content)
-                       "<h6><a id='$anchor'></a>$content</h6><script>window.checkRedirectTarget('#$anchor');</script>"
+                       "<h6><a id=\"$anchor\"></a>$content</h6><script>window.checkRedirectTarget(\"#$anchor\");</script>"
                }
        ),
        ALIGN(
@@ -164,7 +164,7 @@ enum class TextParserFormattingTag(val type: TextParserTagType<Unit>) {
                        { tagParam, _ ->
                                val alignments = setOf("left", "center", "right", "justify")
                                val alignment = tagParam?.takeIf { it in alignments }
-                               val styleAttr = alignment?.let { " style=\"text-align: $it\"" } ?: ""
+                               val styleAttr = alignment?.let { " style=\"text-align:$it\"" }.orEmpty()
                                "<div$styleAttr>"
                        },
                        { _ -> "</div>" },
@@ -176,7 +176,7 @@ enum class TextParserFormattingTag(val type: TextParserTagType<Unit>) {
                        { tagParam, _ ->
                                val floats = setOf("left", "right")
                                val float = tagParam?.takeIf { it in floats } ?: "right"
-                               "<div style='float: $float; max-width: var(--aside-width)'>"
+                               "<div style=\"float:$float;max-width:var(--aside-width)\">"
                        },
                        { _ -> "</div>" },
                )
@@ -212,9 +212,9 @@ enum class TextParserFormattingTag(val type: TextParserTagType<Unit>) {
                                File(Configuration.CurrentConfiguration.assetDir, "images")
                                        .combineSafe(imageUrl)
                                        .readText()
-                                       .replace("<svg", "<svg${getImageSizeAttributes(width, height)}")
+                                       .replaceFirst("<svg", "<svg${getImageSizeAttributes(width, height)}")
                        else
-                               "<script>window.appendImageThumb('/assets/images/$imageUrl', '${getImageSizeStyleValue(width, height)}');</script>"
+                               "<script>window.appendImageThumb(\"/assets/images/$imageUrl\", \"${getImageSizeStyleValue(width, height)}\");</script>"
                }
        ),
        MODEL(
@@ -315,7 +315,7 @@ enum class TextParserFormattingTag(val type: TextParserTagType<Unit>) {
                        { tagParam, _ ->
                                val param = tagParam?.let { TextParserState.censorText(it) }
                                val url = param?.let { if (it.startsWith('/')) "/lore$it" else "./$it" }
-                               val attr = url?.let { " href=\"$it\"" } ?: ""
+                               val attr = url?.let { " href=\"$it\"" }.orEmpty()
                                
                                "<a$attr>"
                        },
@@ -327,7 +327,7 @@ enum class TextParserFormattingTag(val type: TextParserTagType<Unit>) {
                        false,
                        { tagParam, _ ->
                                val url = tagParam?.let { TextParserState.censorText(it) }
-                               val attr = url?.let { " href=\"$it\"" } ?: ""
+                               val attr = url?.let { " href=\"$it\"" }.orEmpty()
                                
                                "<a$attr>"
                        },
@@ -472,7 +472,7 @@ enum class TextParserCommentTags(val type: TextParserTagType<Unit>) {
                        false,
                        { tagParam, _ ->
                                val url = tagParam?.let { TextParserState.censorText(it) }
-                               val attr = url?.let { " href=\"$it\" rel=\"ugc nofollow\"" } ?: ""
+                               val attr = url?.let { " href=\"$it\" rel=\"ugc nofollow\"" }.orEmpty()
                                
                                "<a$attr>"
                        },
@@ -487,7 +487,7 @@ enum class TextParserCommentTags(val type: TextParserTagType<Unit>) {
                        val imageUrl = sanitizeExtLink(content)
                        val (width, height) = getSizeParam(tagParam)
                        
-                       "<img src='https://i.imgur.com/$imageUrl'${getImageSizeAttributes(width, height)}/>"
+                       "<img src=\"https://i.imgur.com/$imageUrl\"${getImageSizeAttributes(width, height)}/>"
                }
        ),
        IMGBB(
@@ -495,7 +495,7 @@ enum class TextParserCommentTags(val type: TextParserTagType<Unit>) {
                        val imageUrl = sanitizeExtLink(content)
                        val (width, height) = getSizeParam(tagParam)
                        
-                       "<img src='https://i.ibb.co/$imageUrl'${getImageSizeAttributes(width, height)}/>"
+                       "<img src=\"https://i.ibb.co/$imageUrl\"${getImageSizeAttributes(width, height)}/>"
                }
        ),
        
@@ -538,8 +538,8 @@ fun getSizeParam(tagParam: String?): Pair<Int?, Int?> = tagParam?.let { resoluti
        parts.getOrNull(0)?.toIntOrNull() to parts.getOrNull(1)?.toIntOrNull()
 } ?: (null to null)
 
-fun getTableSizeAttributes(width: Int?, height: Int?) = (width?.let { " colspan=\"$it\"" } ?: "") + (height?.let { " rowspan=\"$it\"" } ?: "")
-fun getImageSizeStyleValue(width: Int?, height: Int?) = (width?.let { "width: calc(var(--media-size-unit) * $it);" } ?: "") + (height?.let { "height: calc(var(--media-size-unit) * $it);" } ?: "")
+fun getTableSizeAttributes(width: Int?, height: Int?) = width?.let { " colspan=\"$it\"" }.orEmpty() + height?.let { " rowspan=\"$it\"" }.orEmpty()
+fun getImageSizeStyleValue(width: Int?, height: Int?) = width?.let { "width: calc(var(--media-size-unit) * $it);" }.orEmpty() + height?.let { "height: calc(var(--media-size-unit) * $it);" }.orEmpty()
 fun getImageSizeAttributes(width: Int?, height: Int?) = " style=\"${getImageSizeStyleValue(width, height)}\""
 
 val NON_ANCHOR_CHAR = Regex("[^a-zA-Z\\d\\-]")
index 64bb48744d49f564068eb34e4433410a1c8cbd74..6d718e3f77cf7d6b2fbe27dbf820aa78d7340c43 100644 (file)
@@ -186,6 +186,8 @@ fun ApplicationCall.rawPage(pageTitle: String, ogData: OpenGraphData? = null, co
                        
                        link(rel = "icon", type = "image/svg+xml", href = "/static/images/icon.png")
                        
+                       link(rel = "stylesheet", type = "text/css", href = "/static/raw.css")
+                       
                        title {
                                +pageTitle
                        }
index 60bac0165d69113c81d9122909d9ae3df31c0221..0d2e7e15b5f36757a97a95a7d7a870634c1a7e26 100644 (file)
@@ -26,7 +26,7 @@ private val File.lastContentModified: Instant
 
 context(Appendable)
 private fun List<ArticleNode>.renderIntoSitemap(base: String? = null) {
-       val prefix by lazy(LazyThreadSafetyMode.NONE) { base?.let { "$it/" } ?: "" }
+       val prefix by lazy(LazyThreadSafetyMode.NONE) { base?.let { "$it/" }.orEmpty() }
        for (node in this) {
                if (Configuration.CurrentConfiguration.isDevMode || !(node.name.endsWith(".wip") || node.name.endsWith(".old"))) {
                        val path = "$prefix${node.name}"
@@ -76,7 +76,7 @@ private fun Appendable.renderIntroSitemap() {
 }
 
 fun Appendable.generateSitemap() {
-       appendLine("<?xml version='1.0' encoding='UTF-8'?>")
+       appendLine("<?xml version=\"1.0\" encoding=\"UTF-8\"?>")
        appendLine("<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">")
        renderIntroSitemap()
        renderLoreSitemap()
diff --git a/src/jvmMain/resources/static/raw.css b/src/jvmMain/resources/static/raw.css
new file mode 100644 (file)
index 0000000..b769432
--- /dev/null
@@ -0,0 +1,71 @@
+img {
+       filter: drop-shadow(0 0 0.5rem rgba(0, 0, 0, 50%));
+       padding: 0.75rem;
+}
+
+table {
+       width: 100%;
+       table-layout: fixed;
+}
+
+td {
+       margin: 0.25rem;
+       background-color: #ddd;
+}
+
+th {
+       margin: 0.25rem;
+       background-color: #333;
+       color: #eee;
+}
+
+[data-format=b] {
+       font-weight: bold;
+}
+
+[data-format=i] {
+       font-style: italic;
+}
+
+[data-format=u] {
+       text-decoration: underline;
+}
+
+[data-format=s] {
+       text-decoration: line-through;
+}
+
+[data-format=code] {
+       font-family: monospace;
+}
+
+[data-format|=lang-] {
+       font-style: italic;
+}
+
+[data-align=left] {
+       text-align: left;
+}
+
+[data-align=center] {
+       text-align: center;
+}
+
+[data-align=right] {
+       text-align: right;
+}
+
+[data-align=justify] {
+       text-align: justify;
+       text-align-last: left;
+}
+
+[data-aside=left] {
+       float: left;
+       max-width: 50vw;
+}
+
+[data-aside=right] {
+       float: right;
+       max-width: 50vw;
+}