From 2d084e719a6b896c5901da97bee1341eeb5317d7 Mon Sep 17 00:00:00 2001 From: Lanius Trolling Date: Mon, 27 Feb 2023 14:26:28 -0500 Subject: [PATCH] Forbid blank comments and rework error popup --- .../info/mechyrdia/data/views_comment.kt | 16 +++++++-- .../kotlin/info/mechyrdia/lore/http_utils.kt | 6 ++-- .../kotlin/info/mechyrdia/lore/view_tpl.kt | 21 ++++++++++++ src/main/resources/static/init.js | 34 +++++-------------- 4 files changed, 46 insertions(+), 31 deletions(-) diff --git a/src/main/kotlin/info/mechyrdia/data/views_comment.kt b/src/main/kotlin/info/mechyrdia/data/views_comment.kt index be3a69f..9fa6487 100644 --- a/src/main/kotlin/info/mechyrdia/data/views_comment.kt +++ b/src/main/kotlin/info/mechyrdia/data/views_comment.kt @@ -68,10 +68,13 @@ suspend fun ApplicationCall.newCommentRoute(): Nothing { val pagePath = pagePathParts.joinToString("/") val formParams = verifyCsrfToken() - val loggedInAs = currentNation() ?: redirectWithError("/auth/login", "You must be logged in to write comments") + val loggedInAs = currentNation() ?: redirectWithError("/auth/login", "You must be logged in to write comments", "comments") val contents = formParams.getOrFail("comment") + if (contents.isBlank()) + redirectWithError("/lore/$pagePath", "Comments may not be blank", "comments") + val comment = Comment( id = Id(), submittedBy = loggedInAs.id, @@ -99,9 +102,13 @@ suspend fun ApplicationCall.viewCommentRoute(): Nothing { val submitter = nationCache.getNation(comment.submittedBy) if (submitter.isBanned && currentNation?.id != comment.submittedBy && currentNation?.id != OwnerNationId) - redirectWithError("/lore/${comment.submittedIn}", "The user who posted that comment is banned from commenting") + redirectWithError("/lore/${comment.submittedIn}", "The user who posted that comment is banned from commenting", "comments") + + val queryParams = if (request.queryParameters.isEmpty()) + "" + else "?${request.queryParameters.formUrlEncode()}" - redirect("/lore/${comment.submittedIn}#comment-$commentId") + redirect("/lore/${comment.submittedIn}$queryParams#comment-$commentId") } suspend fun ApplicationCall.editCommentRoute(): Nothing { @@ -117,6 +124,9 @@ suspend fun ApplicationCall.editCommentRoute(): Nothing { val newContents = formParams.getOrFail("comment") + if (newContents.isBlank()) + redirectWithError("/comment/view/$commentId", "Comments may not be blank") + // Check for null edits, i.e. edits that don't change anything if (newContents == oldComment.contents) redirect("/comment/view/$commentId") diff --git a/src/main/kotlin/info/mechyrdia/lore/http_utils.kt b/src/main/kotlin/info/mechyrdia/lore/http_utils.kt index 6ded4da..923c971 100644 --- a/src/main/kotlin/info/mechyrdia/lore/http_utils.kt +++ b/src/main/kotlin/info/mechyrdia/lore/http_utils.kt @@ -6,7 +6,9 @@ data class HttpRedirectException(val url: String, val permanent: Boolean) : Runt fun redirect(url: String, permanent: Boolean = false): Nothing = throw HttpRedirectException(url, permanent) -fun redirectWithError(url: String, error: String): Nothing { - val urlWithError = url + "?" + parametersOf("error", error).formUrlEncode() +fun redirectWithError(url: String, error: String, hash: String? = null): Nothing { + val parameters = parametersOf("error", error).formUrlEncode() + val markedHash = hash?.let { "#$it" } ?: "" + val urlWithError = "$url?$parameters$markedHash" redirect(urlWithError, false) } diff --git a/src/main/kotlin/info/mechyrdia/lore/view_tpl.kt b/src/main/kotlin/info/mechyrdia/lore/view_tpl.kt index 7806670..6ea34ea 100644 --- a/src/main/kotlin/info/mechyrdia/lore/view_tpl.kt +++ b/src/main/kotlin/info/mechyrdia/lore/view_tpl.kt @@ -1,6 +1,9 @@ package info.mechyrdia.lore +import io.ktor.http.* import io.ktor.server.application.* +import io.ktor.server.request.* +import io.ktor.util.* import kotlinx.html.* fun ApplicationCall.page(pageTitle: String, navBar: List? = null, sidebar: Sidebar? = null, content: SECTIONS.() -> Unit): HTML.() -> Unit { @@ -74,6 +77,24 @@ fun ApplicationCall.page(pageTitle: String, navBar: List? = null, sideb } } + request.queryParameters["error"]?.let { errorMessage -> + div { + id = "error-popup" + + val paramsWithoutError = parametersOf(request.queryParameters.toMap() - "error") + val newQueryString = if (paramsWithoutError.isEmpty()) + "" + else "?${paramsWithoutError.formUrlEncode()}" + attributes["data-redirect-url"] = "${request.path()}$newQueryString" + + div(classes = "bg") + div(classes = "msg") { + p { +errorMessage } + p { +"Click to close this popup" } + } + } + } + script(src = "/static/init.js") {} } } diff --git a/src/main/resources/static/init.js b/src/main/resources/static/init.js index 5bca116..419447a 100644 --- a/src/main/resources/static/init.js +++ b/src/main/resources/static/init.js @@ -257,34 +257,16 @@ window.addEventListener("load", function () { // Error popup - const queryParams = new URLSearchParams(window.location.search); - if (queryParams.has("error")) { - const errorMessage = queryParams.get("error"); - - const errorPopup = document.createElement("div"); - errorPopup.id = "error-popup"; - - const errorBg = document.createElement("div"); - errorBg.classList.add("bg"); - errorPopup.append(errorBg); - - const errorMsg = document.createElement("div"); - errorMsg.classList.add("msg"); - - const errorP1 = document.createElement("p"); - errorP1.append(document.createTextNode(errorMessage)); - errorMsg.append(errorP1); - - const errorP2 = document.createElement("p"); - errorP2.append(document.createTextNode("Click to close this popup")); - errorMsg.append(errorP2); - - errorPopup.append(errorMsg); - - document.body.append(errorPopup); + const errorPopup = document.getElementById("error-popup"); + if (errorPopup != null) { errorPopup.addEventListener("click", e => { e.preventDefault(); - errorPopup.remove(); + + const thisElement = e.currentTarget; + const newUrl = window.location.origin + thisElement.getAttribute("data-redirect-url") + window.location.hash; + window.history.replaceState({}, '', newUrl); + + thisElement.remove(); }); } }); -- 2.25.1