diff --git a/api/comment_edit.go b/api/comment_edit.go
new file mode 100644
index 0000000..7ecbf7a
--- /dev/null
+++ b/api/comment_edit.go
@@ -0,0 +1,66 @@
+package main
+
+import (
+ "net/http"
+)
+
+func commentEdit(commentHex string, markdown string) (string, error) {
+ if commentHex == "" {
+ return "", errorMissingField
+ }
+
+ html := markdownToHtml(markdown)
+
+ statement := `
+ UPDATE comments
+ SET markdown = $2, html = $3
+ WHERE commentHex=$1;
+ `
+ _, err := db.Exec(statement, commentHex, markdown, html)
+
+ if err != nil {
+ // TODO: make sure this is the error is actually non-existant commentHex
+ return "", errorNoSuchComment
+ }
+
+ return html, nil
+}
+
+func commentEditHandler(w http.ResponseWriter, r *http.Request) {
+ type request struct {
+ CommenterToken *string `json:"commenterToken"`
+ CommentHex *string `json:"commentHex"`
+ Markdown *string `json:"markdown"`
+ }
+
+ var x request
+ if err := bodyUnmarshal(r, &x); err != nil {
+ bodyMarshal(w, response{"success": false, "message": err.Error()})
+ return
+ }
+
+ c, err := commenterGetByCommenterToken(*x.CommenterToken)
+ if err != nil {
+ bodyMarshal(w, response{"success": false, "message": err.Error()})
+ return
+ }
+
+ cm, err := commentGetByCommentHex(*x.CommentHex)
+ if err != nil {
+ bodyMarshal(w, response{"success": false, "message": err.Error()})
+ return
+ }
+
+ if cm.CommenterHex != c.CommenterHex {
+ bodyMarshal(w, response{"success": false, "message": errorNotAuthorised.Error()})
+ return
+ }
+
+ html, err := commentEdit(*x.CommentHex, *x.Markdown)
+ if err != nil {
+ bodyMarshal(w, response{"success": false, "message": err.Error()})
+ return
+ }
+
+ bodyMarshal(w, response{"success": true, "html": html})
+}
diff --git a/api/router_api.go b/api/router_api.go
index 61fd0cc..17626c4 100644
--- a/api/router_api.go
+++ b/api/router_api.go
@@ -51,6 +51,7 @@ func apiRouterInit(router *mux.Router) error {
router.HandleFunc("/api/oauth/sso/callback", ssoCallbackHandler).Methods("GET")
router.HandleFunc("/api/comment/new", commentNewHandler).Methods("POST")
+ router.HandleFunc("/api/comment/edit", commentEditHandler).Methods("POST")
router.HandleFunc("/api/comment/list", commentListHandler).Methods("POST")
router.HandleFunc("/api/comment/count", commentCountHandler).Methods("POST")
router.HandleFunc("/api/comment/vote", commentVoteHandler).Methods("POST")
diff --git a/frontend/js/commento.js b/frontend/js/commento.js
index 0586990..b17ed2c 100644
--- a/frontend/js/commento.js
+++ b/frontend/js/commento.js
@@ -68,6 +68,7 @@
var autoInit;
var isAuthenticated = false;
var comments = [];
+ var commentsMap = {};
var commenters = {};
var requireIdentification = true;
var isModerator = false;
@@ -76,6 +77,7 @@
var isLocked = false;
var stickyCommentHex = "none";
var shownReply = {};
+ var shownEdit = {};
var configuredOauths = {};
var popupBoxType = "login";
var oauthButtonsShown = false;
@@ -499,7 +501,7 @@
}
- function textareaCreate(id) {
+ function textareaCreate(id, edit) {
var textareaSuperContainer = create("div");
var textareaContainer = create("div");
var textarea = create("textarea");
@@ -529,11 +531,19 @@
attrSet(anonymousCheckboxLabel, "for", ID_ANONYMOUS_CHECKBOX + id);
anonymousCheckboxLabel.innerText = "Comment anonymously";
- submitButton.innerText = "Add Comment";
+ if (edit === true) {
+ submitButton.innerText = "Save Changes";
+ } else {
+ submitButton.innerText = "Add Comment";
+ }
markdownButton.innerHTML = "M ↓ Markdown";
textarea.oninput = autoExpander(textarea);
- onclick(submitButton, submitAccountDecide, id);
+ if (edit === true) {
+ onclick(submitButton, commentEdit, id);
+ } else {
+ onclick(submitButton, submitAccountDecide, id);
+ }
onclick(markdownButton, markdownHelpShow, id);
append(textareaContainer, textarea);
@@ -541,7 +551,7 @@
append(anonymousCheckboxContainer, anonymousCheckbox);
append(anonymousCheckboxContainer, anonymousCheckboxLabel);
append(textareaSuperContainer, submitButton);
- if (!requireIdentification) {
+ if (!requireIdentification && edit !== true) {
append(textareaSuperContainer, anonymousCheckboxContainer);
}
append(textareaSuperContainer, markdownButton);
@@ -911,6 +921,7 @@
}
}
+ onclick(edit, global.editShow, comment.commentHex);
onclick(collapse, global.commentCollapse, comment.commentHex);
onclick(approve, global.commentApprove, comment.commentHex);
onclick(remove, global.commentDelete, comment.commentHex);
@@ -933,11 +944,14 @@
append(options, collapse);
- // append(options, edit); // uncomment when implemented
append(options, downvote);
append(options, upvote);
- append(options, reply);
+ if (comment.commenterHex === selfHex) {
+ append(options, edit);
+ } else {
+ append(options, reply);
+ }
if (isModerator && parentHex === "root") {
append(options, sticky);
@@ -1109,6 +1123,106 @@
}
+ function commentEdit(id) {
+ var textarea = $(ID_TEXTAREA + id);
+
+ var markdown = textarea.value;
+
+ if (markdown === "") {
+ classAdd(textarea, "red-border");
+ return;
+ } else {
+ classRemove(textarea, "red-border");
+ }
+
+ var json = {
+ "commenterToken": commenterTokenGet(),
+ "commentHex": id,
+ "markdown": markdown,
+ };
+
+ post(origin + "/api/comment/edit", json, function(resp) {
+ if (!resp.success) {
+ errorShow(resp.message);
+ return;
+ } else {
+ errorHide();
+ }
+
+ commentsMap[id].markdown = markdown;
+ commentsMap[id].html = resp.html;
+
+ var editButton = $(ID_EDIT + id);
+ var textarea = $(ID_SUPER_CONTAINER + id);
+
+ textarea.innerHTML = commentsMap[id].html;
+ textarea.id = ID_TEXT + id;
+ delete shownEdit[id];
+
+ classAdd(editButton, "option-edit");
+ classRemove(editButton, "option-cancel");
+
+ editButton.title = "Edit comment";
+
+ editButton = removeAllEventListeners(editButton);
+ onclick(editButton, global.editShow, id)
+
+ var message = "";
+ if (resp.state === "unapproved") {
+ message = "Your comment is under moderation.";
+ } else if (resp.state === "flagged") {
+ message = "Your comment was flagged as spam and is under moderation.";
+ }
+
+ if (message !== "") {
+ prepend($(ID_SUPER_CONTAINER + id), messageCreate(message));
+ }
+ });
+ }
+
+
+ global.editShow = function(id) {
+ if (id in shownEdit && shownEdit[id]) {
+ return;
+ }
+
+ var text = $(ID_TEXT + id);
+ shownEdit[id] = true;
+ text.replaceWith(textareaCreate(id, true));
+
+ var textarea = $(ID_TEXTAREA + id);
+ textarea.innerText = commentsMap[id].markdown;
+
+ var editButton = $(ID_EDIT + id);
+
+ classRemove(editButton, "option-edit");
+ classAdd(editButton, "option-cancel");
+
+ editButton.title = "Cancel edit";
+
+ editButton = removeAllEventListeners(editButton);
+ onclick(editButton, global.editCollapse, id);
+ };
+
+
+ global.editCollapse = function(id) {
+ var editButton = $(ID_EDIT + id);
+ var textarea = $(ID_SUPER_CONTAINER + id);
+
+ textarea.innerHTML = commentsMap[id].html;
+ textarea.id = ID_TEXT + id;
+ delete shownEdit[id];
+
+ classAdd(editButton, "option-edit");
+ classRemove(editButton, "option-cancel");
+
+ editButton.title = "Edit comment";
+
+ editButton = removeAllEventListeners(editButton);
+ onclick(editButton, global.editShow, id)
+ }
+
+
global.replyShow = function(id) {
if (id in shownReply && shownReply[id]) {
return;
@@ -1135,7 +1249,7 @@
var el = $(ID_SUPER_CONTAINER + id);
el.remove();
- shownReply[id] = false;
+ delete shownReply[id];
classAdd(replyButton, "option-reply");
classRemove(replyButton, "option-cancel");
@@ -1198,6 +1312,10 @@
comment.creationDate = new Date(comment.creationDate);
parentMap[parentHex].push(comment);
+ commentsMap[comment.commentHex] = {
+ "html": comment.html,
+ "markdown": comment.markdown,
+ };
});
var cards = commentsRecurse(parentMap, "root");
diff --git a/frontend/sass/commento-options.scss b/frontend/sass/commento-options.scss
index 2c580cb..60bf264 100644
--- a/frontend/sass/commento-options.scss
+++ b/frontend/sass/commento-options.scss
@@ -67,6 +67,14 @@
background: $indigo-6;
}
+.commento-option-edit {
+ height: 14px;
+ width: 14px;
+ @include mask-image('data:image/svg+xml;utf8,');
+ margin: 12px 6px 12px 6px;
+ background: $gray-5;
+}
+
.commento-option-remove {
height: 14px;
width: 14px;