count.js: add comment count display JS

This commit is contained in:
Adhityaa Chandrasekar 2019-03-02 15:14:42 -05:00
parent 216016a4be
commit 15b1640f89
4 changed files with 132 additions and 11 deletions

View File

@ -1,27 +1,51 @@
package main package main
import ( import (
"github.com/lib/pq"
"net/http" "net/http"
) )
func commentCount(domain string, path string) (int, error) { func commentCount(domain string, paths []string) (map[string]int, error) {
// path can be empty commentCounts := map[string]int{}
if domain == "" { 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 { 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
} }
return p.CommentCount, nil commentCounts[path] = commentCount
}
return commentCounts, nil
} }
func commentCountHandler(w http.ResponseWriter, r *http.Request) { func commentCountHandler(w http.ResponseWriter, r *http.Request) {
type request struct { type request struct {
Domain *string `json:"domain"` Domain *string `json:"domain"`
Path *string `json:"path"` Paths *[]string `json:"paths"`
} }
var x request var x request
@ -31,13 +55,12 @@ func commentCountHandler(w http.ResponseWriter, r *http.Request) {
} }
domain := domainStrip(*x.Domain) domain := domainStrip(*x.Domain)
path := *x.Path
count, err := commentCount(domain, path) commentCounts, err := commentCount(domain, *x.Paths)
if err != nil { if err != nil {
bodyMarshal(w, response{"success": false, "message": err.Error()}) bodyMarshal(w, response{"success": false, "message": err.Error()})
return return
} }
bodyMarshal(w, response{"success": true, "count": count}) bodyMarshal(w, response{"success": true, "commentCounts": commentCounts})
} }

View File

@ -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 errorThreadLocked = errors.New("This thread is locked. You cannot add new comments.")
var errorDatabaseMigration = errors.New("Encountered error applying database migration.") var errorDatabaseMigration = errors.New("Encountered error applying database migration.")
var errorNoSuchUnsubscribeSecretHex = errors.New("Invalid unsubscribe link.") var errorNoSuchUnsubscribeSecretHex = errors.New("Invalid unsubscribe link.")
var errorEmptyPaths = errors.New("Empty paths field.")

View File

@ -76,6 +76,7 @@ const jsCompileMap = {
"js/logout.js" "js/logout.js"
], ],
"js/commento.js": ["js/commento.js"], "js/commento.js": ["js/commento.js"],
"js/count.js": ["js/count.js"],
"js/unsubscribe.js": [ "js/unsubscribe.js": [
"js/constants.js", "js/constants.js",
"js/utils.js", "js/utils.js",

96
frontend/js/count.js Normal file
View File

@ -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));