Add numbers to ToC header links
authorLanius Trolling <lanius@laniustrolling.dev>
Wed, 24 Aug 2022 13:45:05 +0000 (09:45 -0400)
committerLanius Trolling <lanius@laniustrolling.dev>
Wed, 24 Aug 2022 13:45:05 +0000 (09:45 -0400)
src/main/kotlin/info/mechyrdia/lore/parser.kt
src/main/kotlin/info/mechyrdia/lore/parser_tags.kt
src/main/kotlin/info/mechyrdia/lore/parser_toc.kt
src/main/kotlin/info/mechyrdia/lore/views_lore.kt
src/main/resources/static/init.js

index 1368bf6f55ca3c1e415b407ee954c169640d3534..8737a05f6e0e63bc582a9629f7036a2cd178787d 100644 (file)
@@ -31,9 +31,9 @@ sealed class TextParserState<TContext>(
        class Initial<TContext>(scope: TextParserScope<TContext>) : TextParserState<TContext>(scope, listOf(), listOf()) {
                override fun processCharacter(char: Char): TextParserState<TContext> {
                        return if (char == '[')
-                               OpenTag<TContext>(scope, "", insideTags, insideDirectTags)
+                               OpenTag(scope, "", insideTags, insideDirectTags)
                        else
-                               PlainText<TContext>(scope, "$char", insideTags, insideDirectTags)
+                               PlainText(scope, "$char", insideTags, insideDirectTags)
                }
                
                override fun processEndOfText() {
@@ -45,7 +45,7 @@ sealed class TextParserState<TContext>(
                override fun processCharacter(char: Char): TextParserState<TContext> {
                        return if (char == '[') {
                                appendText(text)
-                               OpenTag<TContext>(scope, "", insideTags, insideDirectTags)
+                               OpenTag(scope, "", insideTags, insideDirectTags)
                        } else if (char == '\n' && text.endsWith('\n')) {
                                appendText(text.removeSuffix("\n"))
                                
@@ -55,9 +55,9 @@ sealed class TextParserState<TContext>(
                                        "<br/>"
                                
                                appendTextRaw(newline)
-                               PlainText<TContext>(scope, "", insideTags, insideDirectTags)
+                               PlainText(scope, "", insideTags, insideDirectTags)
                        } else
-                               PlainText<TContext>(scope, text + char, insideTags, insideDirectTags)
+                               PlainText(scope, text + char, insideTags, insideDirectTags)
                }
                
                override fun processEndOfText() {
@@ -70,13 +70,13 @@ sealed class TextParserState<TContext>(
                        val newText = text + char
                        return if (newText.endsWith("[/$NO_FORMAT_TAG]")) {
                                appendText(newText.removeSuffix("[/$NO_FORMAT_TAG]"))
-                               PlainText<TContext>(scope, "", insideTags, insideDirectTags)
+                               PlainText(scope, "", insideTags, insideDirectTags)
                        } else if (newText.endsWith('\n')) {
                                appendText(newText.removeSuffix("\n"))
                                appendTextRaw("<br/>")
-                               NoFormatText<TContext>(scope, "", insideTags, insideDirectTags)
+                               NoFormatText(scope, "", insideTags, insideDirectTags)
                        } else
-                               NoFormatText<TContext>(scope, newText, insideTags, insideDirectTags)
+                               NoFormatText(scope, newText, insideTags, insideDirectTags)
                }
                
                override fun processEndOfText() {
@@ -98,11 +98,11 @@ sealed class TextParserState<TContext>(
                                } else
                                        PlainText(scope, "", insideTags + (tag to null), insideDirectTags)
                        } else if (char == '/' && tag == "")
-                               CloseTag<TContext>(scope, tag, insideTags, insideDirectTags)
+                               CloseTag(scope, tag, insideTags, insideDirectTags)
                        else if (char == '=' && tag != "")
-                               TagParam<TContext>(scope, tag, "", insideTags, insideDirectTags)
+                               TagParam(scope, tag, "", insideTags, insideDirectTags)
                        else
-                               OpenTag<TContext>(scope, tag + char, insideTags, insideDirectTags)
+                               OpenTag(scope, tag + char, insideTags, insideDirectTags)
                }
                
                override fun processEndOfText() {
@@ -166,15 +166,17 @@ sealed class TextParserState<TContext>(
                        .replace("&gt;", ">")
                        .replace("&amp;", "&")
                
-               fun <TContext> parseText(text: String, tags: TextParserTags<TContext>, context: TContext): String {
+               fun <TContext> parseText(text: String, tags: TextParserTags<TContext>, context: TContext): ParseOutcome {
                        val builder = StringBuilder()
                        try {
                                val fixedText = text.replace("\r\n", "\n").replace('\r', '\n')
                                fixedText.fold<TextParserState<TContext>>(Initial(TextParserScope(builder, tags, context))) { state, char -> state.processCharacter(char) }.processEndOfText()
                        } catch (ex: Exception) {
-                               return "<p>$builder</p><h1>INTERNAL ERROR</h1><pre>${ex.stackTraceToString()}</pre>"
+                               return ParseOutcome("<p>$builder</p><h1>Internal Error!</h1><pre>${ex.stackTraceToString()}</pre>", false)
                        }
-                       return "<p>$builder</p>"
+                       return ParseOutcome("<p>$builder</p>", true)
                }
        }
 }
+
+data class ParseOutcome(val html: String, val succeeded: Boolean)
index 6ada30891cd2d75f8afbca128eaf2ce403719d00..bf39cd018a04b28733e4afe0d925d40fb7bf15af 100644 (file)
@@ -304,43 +304,38 @@ enum class TextParserFormattingTag(val type: TextParserTagType<Unit>) {
 enum class TextParserToCBuilderTag(val type: TextParserTagType<TableOfContentsBuilder>) {
        H1(
                TextParserTagType.Indirect<TableOfContentsBuilder> { _, content, builder ->
-                       builder.add(headerContentToLabel(content), headerContentToAnchor(content))
-                       content
+                       builder.add(headerContentToLabel(content), 0, headerContentToAnchor(content))
+                       "[h1]$content[/h1]"
                }
        ),
        H2(
                TextParserTagType.Indirect<TableOfContentsBuilder> { _, content, builder ->
-                       val label = headerContentToLabel(content)
-                       builder.add("- $label", headerContentToAnchor(content))
-                       content
+                       builder.add(headerContentToLabel(content), 1, headerContentToAnchor(content))
+                       "[h2]$content[/h2]"
                }
        ),
        H3(
                TextParserTagType.Indirect<TableOfContentsBuilder> { _, content, builder ->
-                       val label = headerContentToLabel(content)
-                       builder.add("-- $label", headerContentToAnchor(content))
-                       content
+                       builder.add(headerContentToLabel(content), 2, headerContentToAnchor(content))
+                       "[h3]$content[/h3]"
                }
        ),
        H4(
                TextParserTagType.Indirect<TableOfContentsBuilder> { _, content, builder ->
-                       val label = headerContentToLabel(content)
-                       builder.add("--- $label", headerContentToAnchor(content))
-                       content
+                       builder.add(headerContentToLabel(content), 3, headerContentToAnchor(content))
+                       "[h4]$content[/h4]"
                }
        ),
        H5(
                TextParserTagType.Indirect<TableOfContentsBuilder> { _, content, builder ->
-                       val label = headerContentToLabel(content)
-                       builder.add("---- $label", headerContentToAnchor(content))
-                       content
+                       builder.add(headerContentToLabel(content), 4, headerContentToAnchor(content))
+                       "[h5]$content[/h5]"
                }
        ),
        H6(
                TextParserTagType.Indirect<TableOfContentsBuilder> { _, content, builder ->
-                       val label = headerContentToLabel(content)
-                       builder.add("----- $label", headerContentToAnchor(content))
-                       content
+                       builder.add(headerContentToLabel(content), 5, headerContentToAnchor(content))
+                       "[h6]$content[/h6]"
                }
        );
        
index 9ce1d5920fd4f259be021118390a09df9392745f..9e17a3819be86e42acbec2344ed958d2b5f56793 100644 (file)
@@ -1,10 +1,31 @@
 package info.mechyrdia.lore
 
-@JvmInline
-value class TableOfContentsBuilder private constructor(private val links: MutableList<NavLink>) {
-       constructor() : this(mutableListOf())
+class TableOfContentsBuilder {
+       private var title: String? = null
+       private val levels = mutableListOf<Int>()
+       private val links = mutableListOf<NavLink>()
        
-       fun add(text: String, toAnchor: String) = links.plusAssign(NavLink("#$toAnchor", text, aClasses = "left"))
+       fun add(text: String, level: Int, toAnchor: String) {
+               if (level == 0) {
+                       title = text
+                       return
+               }
+               
+               val number = if (level > levels.size) {
+                       if (level == levels.size + 1) {
+                               levels.add(1)
+                               levels.joinToString(separator = ".") { it.toString() }
+                       } else
+                               throw IllegalArgumentException("[h${level + 1}] cannot come after [h${levels.size + 1}]!")
+               } else {
+                       levels.addAll(levels.take(level).also { levels.clear() }.mapIndexed { i, n -> if (i == level - 1) n + 1 else n })
+                       levels.joinToString(separator = ".") { it.toString() }
+               }
+               
+               links.plusAssign(NavLink("#$toAnchor", "$number. $text", aClasses = "left"))
+       }
+       
+       fun toPageTitle() = title!!
        
        fun toNavBar(): List<NavItem> = links.toList()
 }
index 0a855a4fcb9ed0bd7b32618c0daaeab4d178a6fb..07366f8acdbcf5643589a75e72871570fd58a108 100644 (file)
@@ -27,7 +27,7 @@ fun ApplicationCall.loreArticlePage(): HTML.() -> Unit {
                }
        } else {
                val pageMarkup = pageFile.readText()
-               val pageHtml = TextParserState.parseText(pageMarkup, TextParserFormattingTag.asTags, Unit)
+               val pageHtml = TextParserState.parseText(pageMarkup, TextParserFormattingTag.asTags, Unit).html
                
                val pageToC = TableOfContentsBuilder()
                TextParserState.parseText(pageMarkup, TextParserToCBuilderTag.asTags, pageToC)
@@ -35,7 +35,7 @@ fun ApplicationCall.loreArticlePage(): HTML.() -> Unit {
                val navbar = standardNavBar(pagePathParts)
                val sidebar = PageNavSidebar(pageToC.toNavBar())
                
-               return page("~/${pagePath}", navbar, sidebar) {
+               return page(pageToC.toPageTitle(), navbar, sidebar) {
                        section {
                                a { id = "page-top" }
                                unsafe { raw(pageHtml) }
index a3b5a2819d5789d021cad1090d4d8cfb3cc4fd9d..b816e8dea635b462902c6ffe903e4a71a93a6c54 100644 (file)
@@ -27,7 +27,7 @@
        window.addEventListener("load", function () {
                // Preview themes
                const themeChoices = document.getElementsByName("theme");
-               for (let themeChoice of themeChoices) {
+               for (const themeChoice of themeChoices) {
                        const theme = themeChoice.value;
                        themeChoice.addEventListener("click", () => {
                                document.documentElement.setAttribute("data-theme", theme);