From 610b61831def84af451074d3e6066275d45414a2 Mon Sep 17 00:00:00 2001 From: Adhityaa Chandrasekar Date: Tue, 18 Dec 2018 18:57:32 -0500 Subject: [PATCH] frontend,api: open source comment sticky --- api/page.go | 1 + api/page_get.go | 5 +- api/page_update.go | 8 ++-- db/20181218183803-sticky-comments.sql | 2 + frontend/js/commento.js | 69 +++++++++++++++++++++++++-- frontend/sass/commento-buttons.scss | 14 ++++++ 6 files changed, 88 insertions(+), 11 deletions(-) create mode 100644 db/20181218183803-sticky-comments.sql diff --git a/api/page.go b/api/page.go index 4fda357..0a2fc92 100644 --- a/api/page.go +++ b/api/page.go @@ -7,4 +7,5 @@ type page struct { Path string `json:"path"` IsLocked bool `json:"isLocked"` CommentCount int `json:"commentCount"` + StickyCommentHex string `json:"stickyCommentHex"` } diff --git a/api/page_get.go b/api/page_get.go index c62d3d1..a7c2da4 100644 --- a/api/page_get.go +++ b/api/page_get.go @@ -11,20 +11,21 @@ func pageGet(domain string, path string) (page, error) { } statement := ` - SELECT isLocked, commentCount + SELECT isLocked, commentCount, stickyCommentHex FROM pages WHERE domain=$1 AND path=$2; ` row := db.QueryRow(statement, domain, path) p := page{Domain: domain, Path: path} - if err := row.Scan(&p.IsLocked, &p.CommentCount); err != nil { + if err := row.Scan(&p.IsLocked, &p.CommentCount, &p.StickyCommentHex); err != nil { if err == sql.ErrNoRows { // If there haven't been any comments, there won't be a record for this // page. The sane thing to do is return defaults. // TODO: the defaults are hard-coded in two places: here and the schema p.IsLocked = false p.CommentCount = 0 + p.StickyCommentHex = "none" } else { logger.Errorf("error scanning page: %v", err) return page{}, errorInternal diff --git a/api/page_update.go b/api/page_update.go index 1930271..13416ad 100644 --- a/api/page_update.go +++ b/api/page_update.go @@ -13,12 +13,12 @@ func pageUpdate(p page) error { // commentCount statement := ` INSERT INTO - pages (domain, path, isLocked) - VALUES ($1, $2, $3 ) + pages (domain, path, isLocked, stickyCommentHex) + VALUES ($1, $2, $3, $4 ) ON CONFLICT (domain, path) DO - UPDATE SET isLocked = $3; + UPDATE SET isLocked = $3, stickyCommentHex = $4; ` - _, err := db.Exec(statement, p.Domain, p.Path, p.IsLocked) + _, err := db.Exec(statement, p.Domain, p.Path, p.IsLocked, p.StickyCommentHex) if err != nil { logger.Errorf("error setting page attributes: %v", err) return errorInternal diff --git a/db/20181218183803-sticky-comments.sql b/db/20181218183803-sticky-comments.sql new file mode 100644 index 0000000..911d79d --- /dev/null +++ b/db/20181218183803-sticky-comments.sql @@ -0,0 +1,2 @@ +ALTER TABLE pages + ADD stickyCommentHex TEXT NOT NULL DEFAULT 'none'; diff --git a/frontend/js/commento.js b/frontend/js/commento.js index 5b9fbf3..1d5f474 100644 --- a/frontend/js/commento.js +++ b/frontend/js/commento.js @@ -47,6 +47,7 @@ var ID_DOWNVOTE = "commento-comment-downvote-"; var ID_APPROVE = "commento-comment-approve-"; var ID_REMOVE = "commento-comment-remove-"; + var ID_STICKY = "commento-comment-sticky-"; var ID_CONTENTS = "commento-comment-contents-"; var ID_SUBMIT_BUTTON = "commento-submit-button-"; var ID_FOOTER = "commento-footer"; @@ -67,6 +68,7 @@ var shownSubmitButton = {"root": false}; var chosenAnonymous = false; var isLocked = false; + var stickyCommentHex = "none"; var shownReply = {}; var configuredOauths = []; var loginBoxType = "signup"; @@ -341,6 +343,7 @@ isFrozen = resp.isFrozen; isLocked = resp.attributes.isLocked; + stickyCommentHex = resp.attributes.stickyCommentHex; comments = resp.comments; commenters = resp.commenters; @@ -449,10 +452,14 @@ commentsArea.innerHTML = ""; - if (!isLocked) - append(mainArea, textareaCreate("root")); + if (isLocked) { + if (isAuthenticated) + append(mainArea, messageCreate("This thread is locked. You cannot add new comments.")); + else + append(mainArea, textareaCreate("root")); + } else - append(mainArea, messageCreate("This thread is locked. You cannot create new comments.")); + append(mainArea, textareaCreate("root")); append(mainArea, commentsArea); append(root, mainArea); @@ -588,6 +595,10 @@ } cur.sort(function(a, b) { + if (a.commentHex == stickyCommentHex) + return -Infinity; + if (b.commentHex == stickyCommentHex) + return Infinity; return b.score - a.score; }); @@ -608,6 +619,7 @@ var downvote = create("button"); var approve = create("button"); var remove = create("button"); + var sticky = create("button"); var children = commentsRecurse(parentMap, comment.commentHex); var contents = create("div"); var color = colorGet(commenter.name); @@ -629,6 +641,7 @@ downvote.id = ID_DOWNVOTE + comment.commentHex; approve.id = ID_APPROVE + comment.commentHex; remove.id = ID_REMOVE + comment.commentHex; + sticky.id = ID_STICKY + comment.commentHex; contents.id = ID_CONTENTS + comment.commentHex; collapse.title = "Collapse"; @@ -638,6 +651,14 @@ reply.title = "Reply"; approve.title = "Approve"; remove.title = "Remove"; + if (stickyCommentHex == comment.commentHex) { + if (isModerator) + sticky.title = "Unsticky"; + else + sticky.title = "This comment has been stickied"; + } + else + sticky.title = "Sticky"; card.style["borderLeft"] = "2px solid " + color; name.innerText = commenter.name; @@ -684,6 +705,11 @@ classAdd(approve, "option-approve"); classAdd(remove, "option-button"); classAdd(remove, "option-remove"); + classAdd(sticky, "option-button"); + if (stickyCommentHex == comment.commentHex) + classAdd(sticky, "option-unsticky"); + else + classAdd(sticky, "option-sticky"); if (isAuthenticated) { if (comment.direction > 0) @@ -696,6 +722,7 @@ attrSet(collapse, "onclick", "commentCollapse('" + comment.commentHex + "')"); attrSet(approve, "onclick", "commentApprove('" + comment.commentHex + "')"); attrSet(remove, "onclick", "commentDelete('" + comment.commentHex + "')"); + attrSet(sticky, "onclick", "commentSticky('" + comment.commentHex + "')"); if (isAuthenticated) { if (comment.direction > 0) { @@ -730,14 +757,19 @@ append(options, downvote); append(options, upvote); - if (!isLocked) - append(options, reply); + append(options, reply); if (isModerator) { + if (parentHex == "root") + append(options, sticky); append(options, remove); if (comment.state == "unapproved") append(options, approve); } + else { + if (stickyCommentHex == comment.commentHex) + append(options, sticky); + } attrSet(options, "style", "width: " + ((options.childNodes.length+1)*32) + "px;"); for (var i = 0; i < options.childNodes.length; i++) @@ -1278,6 +1310,7 @@ function pageUpdate(callback) { var attributes = { "isLocked": isLocked, + "stickyCommentHex": stickyCommentHex, }; var json = { @@ -1314,6 +1347,32 @@ } + global.commentSticky = function(commentHex) { + if (stickyCommentHex != "none") { + var sticky = $(ID_STICKY + stickyCommentHex); + classRemove(sticky, "option-unsticky"); + classAdd(sticky, "option-sticky"); + } + + if (stickyCommentHex == commentHex) + stickyCommentHex = "none"; + else + stickyCommentHex = commentHex; + + pageUpdate(function(success) { + var sticky = $(ID_STICKY + commentHex); + if (stickyCommentHex == commentHex) { + classRemove(sticky, "option-sticky"); + classAdd(sticky, "option-unsticky"); + } + else { + classRemove(sticky, "option-unsticky"); + classAdd(sticky, "option-sticky"); + } + }); + } + + function mainAreaCreate() { var mainArea = create("div"); diff --git a/frontend/sass/commento-buttons.scss b/frontend/sass/commento-buttons.scss index 8831821..aaeaba7 100644 --- a/frontend/sass/commento-buttons.scss +++ b/frontend/sass/commento-buttons.scss @@ -83,6 +83,20 @@ background: $green-7; } +.commento-option-sticky, +.commento-option-unsticky { + height: 14px; + width: 14px; +@include mask-image('data:image/svg+xml;utf8,'); + margin: 12px 6px 12px 6px; + background: $gray-5; +} + +.commento-option-unsticky { + @include mask-image('data:image/svg+xml;utf8,backgroundLayer 1'); + background: $yellow-7; +} + .commento-option-button:focus { outline: none; }