From: Lanius Trolling Date: Thu, 16 Feb 2023 19:21:30 +0000 (-0500) Subject: Fix various anomalous behaviors X-Git-Url: https://gitweb.starshipfights.net/?a=commitdiff_plain;h=882e103875ce6e7373ccdf06f2e76b8ab8f2d62c;p=factbooks Fix various anomalous behaviors --- diff --git a/src/main/kotlin/info/mechyrdia/Factbooks.kt b/src/main/kotlin/info/mechyrdia/Factbooks.kt index de4ff2e..9bcdde7 100644 --- a/src/main/kotlin/info/mechyrdia/Factbooks.kt +++ b/src/main/kotlin/info/mechyrdia/Factbooks.kt @@ -203,13 +203,9 @@ fun Application.factbooks() { } post("/preview-comment") { - val result = TextParserState.parseText(call.receiveText(), TextParserCommentTags.asTags, Unit) call.respondText( - text = result.html, - contentType = ContentType.Text.Html, - status = if (result.succeeded) - HttpStatusCode.OK - else HttpStatusCode.BadRequest + text = TextParserState.parseText(call.receiveText(), TextParserCommentTags.asTags, Unit), + contentType = ContentType.Text.Html ) } } diff --git a/src/main/kotlin/info/mechyrdia/data/comments.kt b/src/main/kotlin/info/mechyrdia/data/comments.kt index 38397c9..2fa4feb 100644 --- a/src/main/kotlin/info/mechyrdia/data/comments.kt +++ b/src/main/kotlin/info/mechyrdia/data/comments.kt @@ -1,13 +1,14 @@ package info.mechyrdia.data import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.toSet +import kotlinx.coroutines.flow.toList import kotlinx.serialization.Contextual import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import org.litote.kmongo.and import org.litote.kmongo.descending import org.litote.kmongo.eq +import org.litote.kmongo.nin import java.time.Instant @Serializable @@ -48,7 +49,9 @@ data class CommentReplyLink( override val id: Id = Id(), val originalPost: Id, - val replyingPost: Id + val replyingPost: Id, + + val repliedAt: @Contextual Instant = Instant.now(), ) : DataDocument { companion object : TableHolder { override val Table: DocumentTable = DocumentTable() @@ -56,11 +59,16 @@ data class CommentReplyLink( override suspend fun initialize() { Table.index(CommentReplyLink::originalPost) Table.index(CommentReplyLink::replyingPost) - Table.unique(CommentReplyLink::originalPost, CommentReplyLink::replyingPost) + Table.unique(CommentReplyLink::replyingPost, CommentReplyLink::originalPost) } suspend fun updateComment(updatedReply: Id, repliesTo: Set>) { - Table.remove(CommentReplyLink::replyingPost eq updatedReply) + Table.remove( + and( + CommentReplyLink::originalPost nin repliesTo, + CommentReplyLink::replyingPost eq updatedReply + ) + ) Table.put( repliesTo.map { original -> CommentReplyLink( @@ -73,8 +81,11 @@ data class CommentReplyLink( suspend fun deleteComment(deletedReply: Id) = updateComment(deletedReply, emptySet()) - suspend fun getReplies(original: Id): Set> { - return Table.filter(CommentReplyLink::originalPost eq original).map { it.replyingPost }.toSet() + suspend fun getReplies(original: Id): List> { + return Table.filter(CommentReplyLink::originalPost eq original) + .toList() + .sortedBy { it.repliedAt } + .map { it.replyingPost } } } } diff --git a/src/main/kotlin/info/mechyrdia/data/view_comments.kt b/src/main/kotlin/info/mechyrdia/data/view_comments.kt index 17402c1..427835f 100644 --- a/src/main/kotlin/info/mechyrdia/data/view_comments.kt +++ b/src/main/kotlin/info/mechyrdia/data/view_comments.kt @@ -25,30 +25,28 @@ data class CommentRenderData( val contentsRaw: String, val contentsHtml: String, - val replyLinks: Set>, + val replyLinks: List>, ) { companion object { suspend operator fun invoke(comments: List, nations: MutableMap, NationData> = mutableMapOf()): List { return coroutineScope { - comments.mapNotNull { comment -> + comments.map { comment -> val nationData = nations.getNation(comment.submittedBy) val htmlResult = TextParserState.parseText(comment.contents, TextParserCommentTags.asTags, Unit) - if (htmlResult.succeeded) - async { - CommentRenderData( - id = comment.id, - submittedBy = nationData, - submittedIn = comment.submittedIn, - submittedAt = comment.submittedAt, - numEdits = comment.numEdits, - lastEdit = comment.lastEdit, - contentsRaw = comment.contents, - contentsHtml = htmlResult.html, - replyLinks = CommentReplyLink.getReplies(comment.id), - ) - } - else null + async { + CommentRenderData( + id = comment.id, + submittedBy = nationData, + submittedIn = comment.submittedIn, + submittedAt = comment.submittedAt, + numEdits = comment.numEdits, + lastEdit = comment.lastEdit, + contentsRaw = comment.contents, + contentsHtml = htmlResult, + replyLinks = CommentReplyLink.getReplies(comment.id), + ) + } }.map { it.await() } } } diff --git a/src/main/kotlin/info/mechyrdia/data/views_comment.kt b/src/main/kotlin/info/mechyrdia/data/views_comment.kt index 75edeff..28c590d 100644 --- a/src/main/kotlin/info/mechyrdia/data/views_comment.kt +++ b/src/main/kotlin/info/mechyrdia/data/views_comment.kt @@ -105,6 +105,10 @@ suspend fun ApplicationCall.editCommentRoute(): Nothing { val newContents = formParams.getOrFail("comment") + // Check for null edits, i.e. edits that don't change anything + if (newContents == oldComment.contents) + redirect("/comment/view/$commentId") + val newComment = oldComment.copy( numEdits = oldComment.numEdits + 1, lastEdit = Instant.now(), @@ -405,7 +409,7 @@ suspend fun ApplicationCall.commentHelpPage(): HTML.() -> Unit = page("Commentin |[/tr] |[/table] """.trimMargin() - val tableDemoHtml = TextParserState.parseText(tableDemoMarkup, TextParserCommentTags.asTags, Unit).html + val tableDemoHtml = TextParserState.parseText(tableDemoMarkup, TextParserCommentTags.asTags, Unit) p { +"Table cells in this custom BBCode markup also support row-spans and column-spans, even at the same time:" } diff --git a/src/main/kotlin/info/mechyrdia/data/views_user.kt b/src/main/kotlin/info/mechyrdia/data/views_user.kt index 6bdf218..d5838b1 100644 --- a/src/main/kotlin/info/mechyrdia/data/views_user.kt +++ b/src/main/kotlin/info/mechyrdia/data/views_user.kt @@ -15,7 +15,10 @@ suspend fun ApplicationCall.userPage(): HTML.() -> Unit { val currNation = currentNation() val viewingNation = NationData.get(Id(parameters["id"]!!)) - val comments = CommentRenderData(Comment.getCommentsBy(viewingNation.id).toList()) + val comments = CommentRenderData( + Comment.getCommentsBy(viewingNation.id).toList(), + mutableMapOf(viewingNation.id to viewingNation) + ) return page(viewingNation.name, standardNavBar()) { section { diff --git a/src/main/kotlin/info/mechyrdia/lore/parser.kt b/src/main/kotlin/info/mechyrdia/lore/parser.kt index 51f416e..531513f 100644 --- a/src/main/kotlin/info/mechyrdia/lore/parser.kt +++ b/src/main/kotlin/info/mechyrdia/lore/parser.kt @@ -89,14 +89,16 @@ sealed class TextParserState( return if (char == ']') { if (tag.equals(NO_FORMAT_TAG, ignoreCase = true)) NoFormatText(scope, "", insideTags, insideDirectTags) - else if (scope.tags[tag] is TextParserTagType.Direct) { - (scope.tags[tag] as? TextParserTagType.Direct)?.begin(null, scope.ctx)?.let { - appendTextRaw(it) + else when (val tagType = scope.tags[tag]) { + is TextParserTagType.Direct -> { + appendTextRaw(tagType.begin(null, scope.ctx)) + PlainText(scope, "", insideTags, insideDirectTags + tag) } - PlainText(scope, "", insideTags, insideDirectTags + tag) - } else - PlainText(scope, "", insideTags + (tag to null), insideDirectTags) + is TextParserTagType.Indirect -> PlainText(scope, "", insideTags + (tag to null), insideDirectTags) + + else -> PlainText(scope, "[$tag]", insideTags, insideDirectTags) + } } else if (char == '/' && tag == "") CloseTag(scope, tag, insideTags, insideDirectTags) else if (char == '=' && tag != "") @@ -112,15 +114,18 @@ sealed class TextParserState( class TagParam(scope: TextParserScope, val tag: String, val param: String, insideTags: List>, insideDirectTags: List) : TextParserState(scope, insideTags, insideDirectTags) { override fun processCharacter(char: Char): TextParserState { - return if (char == ']') { - val tagType = scope.tags[tag] - if (tagType is TextParserTagType.Direct) { - appendTextRaw(tagType.begin(param, scope.ctx)) + return if (char == ']') + when (val tagType = scope.tags[tag]) { + is TextParserTagType.Direct -> { + appendTextRaw(tagType.begin(param, scope.ctx)) + PlainText(scope, "", insideTags, insideDirectTags + tag) + } - PlainText(scope, "", insideTags, insideDirectTags + tag) - } else - PlainText(scope, "", insideTags + (tag to param), insideDirectTags) - } else + is TextParserTagType.Indirect -> PlainText(scope, "", insideTags + (tag to param), insideDirectTags) + + else -> PlainText(scope, "[$tag=$param]", insideTags, insideDirectTags) + } + else TagParam(scope, tag, param + char, insideTags, insideDirectTags) } @@ -166,17 +171,14 @@ sealed class TextParserState( .replace(">", ">") .replace("&", "&") - fun parseText(text: String, tags: TextParserTags, context: TContext): ParseOutcome { + fun parseText(text: String, tags: TextParserTags, context: TContext): String { val builder = StringBuilder() - try { - val fixedText = text.replace("\r\n", "\n").replace('\r', '\n') - fixedText.fold>(Initial(TextParserScope(builder, tags, context))) { state, char -> state.processCharacter(char) }.processEndOfText() - } catch (ex: Exception) { - return ParseOutcome("

$builder

Internal Error!

${ex.stackTraceToString()}
", false) - } - return ParseOutcome("

$builder

", true) + val fixedText = text.replace("\r\n", "\n").replace('\r', '\n') + fixedText + .fold>(Initial(TextParserScope(builder, tags, context))) { state, char -> + state.processCharacter(char) + }.processEndOfText() + return "

$builder

" } } } - -data class ParseOutcome(val html: String, val succeeded: Boolean) diff --git a/src/main/kotlin/info/mechyrdia/lore/parser_reply.kt b/src/main/kotlin/info/mechyrdia/lore/parser_reply.kt index 1e59569..16f2620 100644 --- a/src/main/kotlin/info/mechyrdia/lore/parser_reply.kt +++ b/src/main/kotlin/info/mechyrdia/lore/parser_reply.kt @@ -30,8 +30,7 @@ enum class TextParserReplyCounterTag(val type: TextParserTagType> { val builder = CommentRepliesBuilder() - if (!TextParserState.parseText(commentContents, TextParserReplyCounterTag.asTags, builder).succeeded) - return emptySet() + TextParserState.parseText(commentContents, TextParserReplyCounterTag.asTags, builder) return builder.toReplySet() } diff --git a/src/main/kotlin/info/mechyrdia/lore/parser_toc.kt b/src/main/kotlin/info/mechyrdia/lore/parser_toc.kt index b14ba19..e36d3e6 100644 --- a/src/main/kotlin/info/mechyrdia/lore/parser_toc.kt +++ b/src/main/kotlin/info/mechyrdia/lore/parser_toc.kt @@ -7,33 +7,31 @@ class TableOfContentsBuilder { fun add(text: String, level: Int, toAnchor: String) { if (level == 0) { - if (title != null) - throw IllegalArgumentException("[h1] cannot appear multiple times in an article!") + if (title == null) + title = text - 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 appear after [h${levels.size + 1}]!") - } else { + if (level > levels.size) + levels.add(1) + else { val newLevels = levels.take(level).mapIndexed { i, n -> if (i == level - 1) n + 1 else n } levels.clear() levels.addAll(newLevels) - - levels.joinToString(separator = ".") { it.toString() } } + val number = levels.joinToString(separator = ".") { it.toString() } links.plusAssign(NavLink("#$toAnchor", "$number. $text", aClasses = "left")) } - fun toPageTitle() = title!! + fun toPageTitle() = title ?: MISSING_TITLE + + fun toNavBar(): List = listOf(NavLink("#page-top", title ?: MISSING_TITLE, aClasses = "left")) + links.toList() - fun toNavBar(): List = listOf(NavLink("#page-top", title!!, aClasses = "left")) + links.toList() + companion object { + const val MISSING_TITLE = "Untitled" + } } enum class TextParserToCBuilderTag(val type: TextParserTagType) { diff --git a/src/main/kotlin/info/mechyrdia/lore/views_lore.kt b/src/main/kotlin/info/mechyrdia/lore/views_lore.kt index d4743c2..9b633cf 100644 --- a/src/main/kotlin/info/mechyrdia/lore/views_lore.kt +++ b/src/main/kotlin/info/mechyrdia/lore/views_lore.kt @@ -32,27 +32,10 @@ suspend fun ApplicationCall.loreArticlePage(): HTML.() -> Unit { } else { val pageTemplate = pageFile.readText() val pageMarkup = PreParser.preparse(pagePath, pageTemplate) - val pageResult = TextParserState.parseText(pageMarkup, TextParserFormattingTag.asTags, Unit) - val pageHtml = pageResult.html - if (!pageResult.succeeded) { - return page("Error rendering page", standardNavBar(pagePathParts), null) { - section { - a { id = "page-top" } - unsafe { raw(pageHtml) } - } - } - } + val pageHtml = TextParserState.parseText(pageMarkup, TextParserFormattingTag.asTags, Unit) val pageToC = TableOfContentsBuilder() - val pageToCResult = TextParserState.parseText(pageMarkup, TextParserToCBuilderTag.asTags, pageToC) - if (!pageToCResult.succeeded) { - return page("Error generating table of contents", standardNavBar(pagePathParts), null) { - section { - a { id = "page-top" } - unsafe { raw(pageToCResult.html) } - } - } - } + TextParserState.parseText(pageMarkup, TextParserToCBuilderTag.asTags, pageToC) val pageNav = pageToC.toNavBar() + NavLink("#comments", "Comments", aClasses = "left") diff --git a/src/main/resources/static/style.css b/src/main/resources/static/style.css index 0a7a83c..002e495 100644 --- a/src/main/resources/static/style.css +++ b/src/main/resources/static/style.css @@ -817,6 +817,12 @@ iframe { #error-popup { z-index: 998; + + position: fixed; + width: 100vw; + height: 100vh; + left: 0; + top: 0; } #error-popup > .bg {