Mon, 02 Aug 2021 17:04:17 +0200
#22 adds possibility to edit own comments
--- a/src/main/kotlin/de/uapcore/lightpit/Constants.kt Mon Aug 02 15:13:04 2021 +0200 +++ b/src/main/kotlin/de/uapcore/lightpit/Constants.kt Mon Aug 02 17:04:17 2021 +0200 @@ -92,6 +92,11 @@ const val REQ_ATTR_STYLESHEET = "extraCss" /** + * Key for the name of the optional java script file. + */ + const val REQ_ATTR_JAVASCRIPT = "javascriptFile" + + /** * Key for a location the page shall redirect to. * Will be used in a meta element. */
--- a/src/main/kotlin/de/uapcore/lightpit/RequestMapping.kt Mon Aug 02 15:13:04 2021 +0200 +++ b/src/main/kotlin/de/uapcore/lightpit/RequestMapping.kt Mon Aug 02 17:04:17 2021 +0200 @@ -60,7 +60,7 @@ /** * The name of the content page. * - * @see Constants#REQ_ATTR_CONTENT_PAGE + * @see Constants#REQ_ATTR_PAGE_TITLE */ var pageTitle = "" set(value) { @@ -82,6 +82,19 @@ } /** + * A list of additional style sheets. + * + * @see Constants#REQ_ATTR_JAVASCRIPT + */ + var javascript = "" + set(value) { + field = value + request.setAttribute(Constants.REQ_ATTR_JAVASCRIPT, + value.withExt(".js") + ) + } + + /** * The name of the navigation menu JSP. * * @see Constants#REQ_ATTR_NAVIGATION
--- a/src/main/kotlin/de/uapcore/lightpit/dao/DataAccessObject.kt Mon Aug 02 15:13:04 2021 +0200 +++ b/src/main/kotlin/de/uapcore/lightpit/dao/DataAccessObject.kt Mon Aug 02 17:04:17 2021 +0200 @@ -75,5 +75,7 @@ fun updateIssue(issue: Issue) fun listComments(issue: Issue): List<IssueComment> + fun findComment(id: Int): IssueComment? fun insertComment(issueComment: IssueComment) + fun updateComment(issueComment: IssueComment) } \ No newline at end of file
--- a/src/main/kotlin/de/uapcore/lightpit/dao/PostgresDataAccessObject.kt Mon Aug 02 15:13:04 2021 +0200 +++ b/src/main/kotlin/de/uapcore/lightpit/dao/PostgresDataAccessObject.kt Mon Aug 02 17:04:17 2021 +0200 @@ -658,6 +658,12 @@ queryAll { it.extractIssueComment() } } + override fun findComment(id: Int): IssueComment? = + withStatement("select * from lpit_issue_comment left join lpit_user using (userid) where commentid = ?") { + setInt(1, id) + querySingle { it.extractIssueComment() } + } + override fun insertComment(issueComment: IssueComment) { useStatement("update lpit_issue set updated = now() where issueid = ?") { updateIssueDate -> withStatement("insert into lpit_issue_comment (issueid, comment, userid) values (?, ? ,?)") { @@ -672,5 +678,19 @@ } } } + + override fun updateComment(issueComment: IssueComment) { + useStatement("update lpit_issue set updated = now() where issueid = ?") { updateIssueDate -> + withStatement("update lpit_issue_comment set comment = ?, updatecount = updatecount + 1, updated = now() where commentid = ?") { + with(issueComment) { + updateIssueDate.setInt(1, issueid) + setStringSafe(1, comment) + setInt(2, id) + } + executeUpdate() + updateIssueDate.executeUpdate() + } + } + } //</editor-fold> } \ No newline at end of file
--- a/src/main/kotlin/de/uapcore/lightpit/entities/IssueComment.kt Mon Aug 02 15:13:04 2021 +0200 +++ b/src/main/kotlin/de/uapcore/lightpit/entities/IssueComment.kt Mon Aug 02 17:04:17 2021 +0200 @@ -30,6 +30,7 @@ data class IssueComment(override val id: Int, val issueid: Int) : Entity { var author: User? = null + var commentFormatted: String = "" var comment: String = "" var created: Timestamp = Timestamp.from(Instant.now()) var updated: Timestamp = Timestamp.from(Instant.now())
--- a/src/main/kotlin/de/uapcore/lightpit/servlet/ProjectServlet.kt Mon Aug 02 15:13:04 2021 +0200 +++ b/src/main/kotlin/de/uapcore/lightpit/servlet/ProjectServlet.kt Mon Aug 02 17:04:17 2021 +0200 @@ -441,7 +441,6 @@ with(http) { pageTitle = "${projectInfo.project.name}: #${issue.id} ${issue.subject}" view = IssueDetailView(issue, comments, project, version, component) - // TODO: feed path for this particular issue feedPath = feedPath(projectInfo.project) navigationMenu = activeProjectNavMenu( dao.listProjects(), @@ -450,6 +449,7 @@ component ) styleSheets = listOf("projects") + javascript = "issue-editor" render("issue-view") } } @@ -492,6 +492,7 @@ component ) styleSheets = listOf("projects") + javascript = "issue-editor" render("issue-form") } } @@ -506,13 +507,26 @@ } // TODO: throw validator exception instead of using a default - val comment = IssueComment(-1, issue.id).apply { - author = http.remoteUser?.let { dao.findUserByName(it) } - comment = http.param("comment") ?: "" + + val commentId = http.param("commentid")?.toIntOrNull() ?: -1 + if (commentId > 0) { + val comment = dao.findComment(commentId) + val originalAuthor = comment?.author?.username + if (originalAuthor != null && originalAuthor == http.remoteUser) { + comment.comment = http.param("comment") ?: "" + dao.updateComment(comment) + } else { + http.response.sendError(403) + return + } + } else { + val comment = IssueComment(-1, issue.id).apply { + author = http.remoteUser?.let { dao.findUserByName(it) } + comment = http.param("comment") ?: "" + } + dao.insertComment(comment) } - dao.insertComment(comment) - http.renderCommit("${issuesHref}${issue.id}") } }
--- a/src/main/kotlin/de/uapcore/lightpit/viewmodel/Issues.kt Mon Aug 02 15:13:04 2021 +0200 +++ b/src/main/kotlin/de/uapcore/lightpit/viewmodel/Issues.kt Mon Aug 02 17:04:17 2021 +0200 @@ -78,7 +78,7 @@ issue.description = process(issue.description?:"") for (comment in comments) { - comment.comment = process(comment.comment) + comment.commentFormatted = process(comment.comment) } } }
--- a/src/main/resources/localization/strings.properties Mon Aug 02 15:13:04 2021 +0200 +++ b/src/main/resources/localization/strings.properties Mon Aug 02 17:04:17 2021 +0200 @@ -25,6 +25,7 @@ app.license.title=License app.name=Lightweight Project and Issue Tracking button.cancel=Cancel +button.comment.edit=Edit Comment button.comment=Comment button.component.create=New Component button.component.edit=Edit Component @@ -70,6 +71,8 @@ issue.category.Test=Test issue.category=Category issue.comments.anonauthor=Anonymous Author +issue.comments.lastupdate=Last edited: +issue.comments.updateCount=total edits issue.comments=Comments issue.created=Created issue.description=Description
--- a/src/main/resources/localization/strings_de.properties Mon Aug 02 15:13:04 2021 +0200 +++ b/src/main/resources/localization/strings_de.properties Mon Aug 02 17:04:17 2021 +0200 @@ -24,6 +24,7 @@ app.changelog=Versionshistorie app.license.title=Lizenz (Englisch) button.cancel=Abbrechen +button.comment.edit=Absenden button.comment=Kommentieren button.component.create=Neue Komponente button.component.edit=Komponente Bearbeiten @@ -55,11 +56,11 @@ error.message = Server Nachricht error.returnLink = Kehre zurück zu error.timestamp = Zeitstempel -feed=Feed feed.issues.created=Vorgang wurde erstellt. feed.issues.description=Feed \u00fcber k\u00fcrzlich aktualisierte Vorg\u00e4nge. feed.issues.title=LightPIT Vorg\u00e4nge feed.issues.updated=Vorgang wurde aktualisiert. +feed=Feed issue.affected-versions=Betroffene Versionen issue.assignee=Zugewiesen issue.category.Bug=Fehler @@ -69,6 +70,8 @@ issue.category.Test=Test issue.category=Kategorie issue.comments.anonauthor=Anonymer Autor +issue.comments.lastupdate=Zuletzt bearbeitet: +issue.comments.updateCount=mal bearbeitet issue.comments=Kommentare issue.created=Erstellt issue.description=Beschreibung
--- a/src/main/webapp/WEB-INF/changelogs/changelog-de.jspf Mon Aug 02 15:13:04 2021 +0200 +++ b/src/main/webapp/WEB-INF/changelogs/changelog-de.jspf Mon Aug 02 17:04:17 2021 +0200 @@ -28,6 +28,7 @@ <ul> <li>Infoseite hinzugefügt.</li> + <li>Eigene Kommentare können nun bearbeitet werden.</li> <li>Sortierreihenfolge der Versionen in der Übersicht an die Sortierreihenfolge im Seitenmenü angeglichen.</li> <li>Duplikate in Komponenten- und Versionslisten behoben.</li> <li>Fehler behoben, bei dem vorbereitete Datenbankabfragen nicht geschlossen wurden.</li>
--- a/src/main/webapp/WEB-INF/changelogs/changelog.jspf Mon Aug 02 15:13:04 2021 +0200 +++ b/src/main/webapp/WEB-INF/changelogs/changelog.jspf Mon Aug 02 17:04:17 2021 +0200 @@ -28,6 +28,7 @@ <ul> <li>Adds about page.</li> + <li>Adds possibility to edit own comments.</li> <li>Changes sort order of versions in the versions overview to be the same as in the left menu.</li> <li>Fixes duplicates in the components and versions lists.</li> <li>Fixes leaking prepared statements.</li>
--- a/src/main/webapp/WEB-INF/jsp/issue-view.jsp Mon Aug 02 15:13:04 2021 +0200 +++ b/src/main/webapp/WEB-INF/jsp/issue-view.jsp Mon Aug 02 17:04:17 2021 +0200 @@ -175,15 +175,18 @@ </form> <c:forEach var="comment" items="${viewmodel.comments}"> <div class="comment"> - <div class="caption"> + <div class="comment-author"> <c:if test="${not empty comment.author}"> <c:if test="${not empty comment.author.mail}"> - <a href="mailto:${comment.author.mail}"> + <a class="comment-author-name" href="mailto:${comment.author.mail}"> </c:if> <c:out value="${comment.author.displayname}"/> <c:if test="${not empty comment.author.mail}"> </a> </c:if> + <c:if test="${comment.author.username eq pageContext.request.remoteUser}"> + <a class="comment-edit-icon" onclick="showCommentEditor(${comment.id})">✎</a> + </c:if> </c:if> <c:if test="${empty comment.author}"> <fmt:message key="issue.comments.anonauthor"/> @@ -192,11 +195,34 @@ <div class="smalltext"> <fmt:formatDate type="BOTH" value="${comment.created}" /> <c:if test="${comment.updateCount gt 0}"> - <!-- TODO: update count --> + <span class="comment-edit-info"> + (<fmt:message key="issue.comments.lastupdate"/> <fmt:formatDate type="BOTH" value="${comment.updated}" />, ${comment.updateCount} <fmt:message key="issue.comments.updateCount"/>) + </span> </c:if> </div> - <div class="medskip markdown-styled"> - ${comment.comment} + <div id="comment-view-${comment.id}" class="medskip markdown-styled"> + ${comment.commentFormatted} + </div> + <div id="comment-editor-${comment.id}" style="display: none"> + <form id="comment-form-${comment.id}" action="${issuesHref}${issue.id}/comment" method="post"> + <input type="hidden" name="commentid" value="${comment.id}"> + <table class="formtable fullwidth"> + <tbody> + <tr> + <td> + <textarea rows="5" name="comment" required><c:out value="${comment.comment}"/></textarea> + </td> + </tr> + </tbody> + <tfoot> + <tr> + <td> + <button type="submit"><fmt:message key="button.comment.edit"/></button> + </td> + </tr> + </tfoot> + </table> + </form> </div> </div> </c:forEach>
--- a/src/main/webapp/WEB-INF/jsp/site.jsp Mon Aug 02 15:13:04 2021 +0200 +++ b/src/main/webapp/WEB-INF/jsp/site.jsp Mon Aug 02 17:04:17 2021 +0200 @@ -30,6 +30,9 @@ <%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> <%@taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> +<%-- Version suffix for forcing browsers to update the CSS / JS files --%> +<c:set scope="page" var="versionSuffix" value="20210802"/> + <%-- Make the base href easily available at request scope --%> <c:set scope="page" var="baseHref" value="${requestScope[Constants.REQ_ATTR_BASE_HREF]}"/> @@ -54,6 +57,9 @@ <%-- Define an alias for the additional stylesheet --%> <c:set scope="page" var="extraCss" value="${requestScope[Constants.REQ_ATTR_STYLESHEET]}"/> +<%-- Define an alias for the optional JS file --%> +<c:set scope="page" var="javascriptFile" value="${requestScope[Constants.REQ_ATTR_JAVASCRIPT]}"/> + <%-- Load resource bundle --%> <fmt:setLocale scope="request" value="${pageContext.response.locale}"/> <fmt:setBundle scope="request" basename="localization.strings"/> @@ -70,15 +76,18 @@ <c:if test="${not empty redirectLocation}"> <meta http-equiv="refresh" content="0; URL=${redirectLocation}"> </c:if> - <link rel="stylesheet" href="lightpit.css" type="text/css"> + <link rel="stylesheet" href="lightpit.css?v=${versionSuffix}" type="text/css"> <c:if test="${not empty feedHref}"> <link rel="alternate" type="application/rss+xml" title="RSS Feed" href="${feedHref}"/> </c:if> <c:if test="${not empty extraCss}"> <c:forEach items="${extraCss}" var="cssFile"> - <link rel="stylesheet" href="${cssFile}" type="text/css"> + <link rel="stylesheet" href="${cssFile}?v=${versionSuffix}" type="text/css"> </c:forEach> </c:if> + <c:if test="${not empty javascriptFile}"> + <script src="${javascriptFile}?v=${versionSuffix}" type="text/javascript"></script> + </c:if> </head> <body> <div id="mainMenu">
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/webapp/issue-editor.js Mon Aug 02 17:04:17 2021 +0200 @@ -0,0 +1,36 @@ +/* + * Copyright 2021 Mike Becker. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * Replaces the formatted comment text with an text area. + * + * @param {number} id the ID of the comment + */ +function showCommentEditor(id) { + const editor = document.getElementById(`comment-editor-${id}`) + const view = document.getElementById(`comment-view-${id}`) + editor.style.display='block' + view.style.display='none' +}
--- a/src/main/webapp/lightpit.css Mon Aug 02 15:13:04 2021 +0200 +++ b/src/main/webapp/lightpit.css Mon Aug 02 17:04:17 2021 +0200 @@ -43,6 +43,7 @@ } a { + cursor: pointer; color: #3060f8; text-decoration: none; }
--- a/src/main/webapp/projects.css Mon Aug 02 15:13:04 2021 +0200 +++ b/src/main/webapp/projects.css Mon Aug 02 17:04:17 2021 +0200 @@ -147,9 +147,30 @@ } div.comment { + padding-left: .25rem; margin-bottom: 1.25em; } +.comment-author { + color: #3060f8; + background: #e7e7ef; + margin-left: -.25rem; + padding: .25rem; +} + +.comment-author-name { + color: inherit; +} + +.comment-edit-icon { + margin-left: 1ex; +} + +span.comment-edit-info { + margin-left: 1ex; + color: #556080; +} + span.eta-overdue { color: red; } \ No newline at end of file