#22 adds possibility to edit own comments

Mon, 02 Aug 2021 17:04:17 +0200

author
Mike Becker <universe@uap-core.de>
date
Mon, 02 Aug 2021 17:04:17 +0200
changeset 207
479dd7993ef9
parent 206
fe4de34822a5
child 208
785820da6485

#22 adds possibility to edit own comments

src/main/kotlin/de/uapcore/lightpit/Constants.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/RequestMapping.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/dao/DataAccessObject.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/dao/PostgresDataAccessObject.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/entities/IssueComment.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/servlet/ProjectServlet.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/viewmodel/Issues.kt file | annotate | diff | comparison | revisions
src/main/resources/localization/strings.properties file | annotate | diff | comparison | revisions
src/main/resources/localization/strings_de.properties file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/changelogs/changelog-de.jspf file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/changelogs/changelog.jspf file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/jsp/issue-view.jsp file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/jsp/site.jsp file | annotate | diff | comparison | revisions
src/main/webapp/issue-editor.js file | annotate | diff | comparison | revisions
src/main/webapp/lightpit.css file | annotate | diff | comparison | revisions
src/main/webapp/projects.css file | annotate | diff | comparison | revisions
--- 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})">&#x270e;</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

mercurial