From 15b1640f89da31a50107c2cd1f5023e308eb715e Mon Sep 17 00:00:00 2001 From: Adhityaa Chandrasekar Date: Sat, 2 Mar 2019 15:14:42 -0500 Subject: [PATCH] count.js: add comment count display JS --- api/comment_count.go | 45 ++++++++++++++++----- api/errors.go | 1 + frontend/gulpfile.js | 1 + frontend/js/count.js | 96 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 132 insertions(+), 11 deletions(-) create mode 100644 frontend/js/count.js diff --git a/api/comment_count.go b/api/comment_count.go index 8533fae..c04f88e 100644 --- a/api/comment_count.go +++ b/api/comment_count.go @@ -1,27 +1,51 @@ package main import ( + "github.com/lib/pq" "net/http" ) -func commentCount(domain string, path string) (int, error) { - // path can be empty +func commentCount(domain string, paths []string) (map[string]int, error) { + commentCounts := map[string]int{} + if domain == "" { - return 0, errorMissingField + return nil, errorMissingField } - p, err := pageGet(domain, path) + if len(paths) == 0 { + return nil, errorEmptyPaths + } + + statement := ` + SELECT path, commentCount + FROM pages + WHERE domain = $1 AND path = ANY($2); + ` + rows, err := db.Query(statement, domain, pq.Array(paths)) if err != nil { - return 0, errorInternal + logger.Errorf("cannot get comments: %v", err) + return nil, errorInternal + } + defer rows.Close() + + for rows.Next() { + var path string + var commentCount int + if err = rows.Scan(&path, &commentCount); err != nil { + logger.Errorf("cannot scan path and commentCount: %v", err) + return nil, errorInternal + } + + commentCounts[path] = commentCount } - return p.CommentCount, nil + return commentCounts, nil } func commentCountHandler(w http.ResponseWriter, r *http.Request) { type request struct { - Domain *string `json:"domain"` - Path *string `json:"path"` + Domain *string `json:"domain"` + Paths *[]string `json:"paths"` } var x request @@ -31,13 +55,12 @@ func commentCountHandler(w http.ResponseWriter, r *http.Request) { } domain := domainStrip(*x.Domain) - path := *x.Path - count, err := commentCount(domain, path) + commentCounts, err := commentCount(domain, *x.Paths) if err != nil { bodyMarshal(w, response{"success": false, "message": err.Error()}) return } - bodyMarshal(w, response{"success": true, "count": count}) + bodyMarshal(w, response{"success": true, "commentCounts": commentCounts}) } diff --git a/api/errors.go b/api/errors.go index 7b74465..a53695a 100644 --- a/api/errors.go +++ b/api/errors.go @@ -44,3 +44,4 @@ var errorNewOwnerForbidden = errors.New("New user registrations are forbidden an var errorThreadLocked = errors.New("This thread is locked. You cannot add new comments.") var errorDatabaseMigration = errors.New("Encountered error applying database migration.") var errorNoSuchUnsubscribeSecretHex = errors.New("Invalid unsubscribe link.") +var errorEmptyPaths = errors.New("Empty paths field.") diff --git a/frontend/gulpfile.js b/frontend/gulpfile.js index 0273491..04cd203 100644 --- a/frontend/gulpfile.js +++ b/frontend/gulpfile.js @@ -76,6 +76,7 @@ const jsCompileMap = { "js/logout.js" ], "js/commento.js": ["js/commento.js"], + "js/count.js": ["js/count.js"], "js/unsubscribe.js": [ "js/constants.js", "js/utils.js", diff --git a/frontend/js/count.js b/frontend/js/count.js new file mode 100644 index 0000000..5fbbdc8 --- /dev/null +++ b/frontend/js/count.js @@ -0,0 +1,96 @@ +(function(global, document) { + "use strict"; + + var origin = "[[[.Origin]]]"; + + function post(url, data, callback) { + var xmlDoc = new XMLHttpRequest(); + + xmlDoc.open("POST", url, true); + xmlDoc.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + xmlDoc.onload = function() { + callback(JSON.parse(xmlDoc.response)); + }; + + xmlDoc.send(JSON.stringify(data)); + } + + function main() { + var paths = []; + var doms = []; + var as = document.getElementsByTagName("a"); + for (var i = 0; i < as.length; i++) { + var href = as[i].href; + if (href === undefined) { + return; + } + + href = href.replace(/^.*\/\/[^\/]+/, ""); + + if (href.endsWith("#commento")) { + var path = href.substr(0, href.indexOf("#commento")); + if (path.startsWith(parent.location.host)) { + path = path.substr(parent.location.host.length); + } + + paths.push(path); + doms.push(as[i]); + } + } + + var json = { + "domain": parent.location.host, + "paths": paths, + }; + + post(origin + "/api/comment/count", json, function(resp) { + if (!resp.success) { + console.log("[commento] error: " + resp.message); + return; + } + + for (var i = 0; i < paths.length; i++) { + var count = 0; + if (paths[i] in resp.commentCounts) { + count = resp.commentCounts[paths[i]]; + } + + doms[i].innerText = count + " " + (count === 1 ? "comment" : "comments"); + } + }); + } + + var initted = false; + + function init() { + if (initted) { + return; + } + initted = true; + + main(undefined); + } + + var readyLoad = function() { + var readyState = document.readyState; + + if (readyState === "loading") { + // The document is still loading. The div we need to fill might not have + // been parsed yet, so let's wait and retry when the readyState changes. + // If there is more than one state change, we aren't affected because we + // have a double-call protection in init(). + document.addEventListener("readystatechange", readyLoad); + } else if (readyState === "interactive") { + // The document has been parsed and DOM objects are now accessible. While + // JS, CSS, and images are still loading, we don't need to wait. + init(); + } else if (readyState === "complete") { + // The page has fully loaded (including JS, CSS, and images). From our + // point of view, this is practically no different from interactive. + init(); + } + }; + + readyLoad(); + +}(window, document));