From cc1dfee0178cc61a8e5c8feb0e76014240b3a843 Mon Sep 17 00:00:00 2001 From: Adhityaa Chandrasekar Date: Wed, 15 May 2019 10:46:51 -0700 Subject: [PATCH] api, frontend: add comment editing --- api/comment_edit.go | 66 ++++++++++++++ api/router_api.go | 1 + frontend/js/commento.js | 132 ++++++++++++++++++++++++++-- frontend/sass/commento-options.scss | 8 ++ 4 files changed, 200 insertions(+), 7 deletions(-) create mode 100644 api/comment_edit.go 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;