api, frontend, db: add comment sorting

Closes https://gitlab.com/commento/commento/issues/215
This commit is contained in:
Adhityaa Chandrasekar 2019-12-04 18:50:50 -08:00
parent 3101af8a5c
commit 3e1576d494
9 changed files with 185 additions and 27 deletions

View File

@ -191,6 +191,7 @@ func commentListHandler(w http.ResponseWriter, r *http.Request) {
"requireIdentification": d.RequireIdentification, "requireIdentification": d.RequireIdentification,
"isFrozen": d.State == "frozen", "isFrozen": d.State == "frozen",
"isModerator": isModerator, "isModerator": isModerator,
"defaultSortPolicy": d.DefaultSortPolicy,
"attributes": p, "attributes": p,
"configuredOauths": map[string]bool{ "configuredOauths": map[string]bool{
"commento": d.CommentoProvider, "commento": d.CommentoProvider,

View File

@ -25,4 +25,5 @@ type domain struct {
SsoProvider bool `json:"ssoProvider"` SsoProvider bool `json:"ssoProvider"`
SsoSecret string `json:"ssoSecret"` SsoSecret string `json:"ssoSecret"`
SsoUrl string `json:"ssoUrl"` SsoUrl string `json:"ssoUrl"`
DefaultSortPolicy string `json:"defaultSortPolicy"`
} }

View File

@ -27,7 +27,8 @@ func domainGet(dmn string) (domain, error) {
gitlabProvider, gitlabProvider,
ssoProvider, ssoProvider,
ssoSecret, ssoSecret,
ssoUrl ssoUrl,
defaultSortPolicy
FROM domains FROM domains
WHERE domain = $1; WHERE domain = $1;
` `
@ -54,7 +55,8 @@ func domainGet(dmn string) (domain, error) {
&d.GitlabProvider, &d.GitlabProvider,
&d.SsoProvider, &d.SsoProvider,
&d.SsoSecret, &d.SsoSecret,
&d.SsoUrl); err != nil { &d.SsoUrl,
&d.DefaultSortPolicy); err != nil {
return d, errorNoSuchDomain return d, errorNoSuchDomain
} }

View File

@ -29,7 +29,8 @@ func domainList(ownerHex string) ([]domain, error) {
gitlabProvider, gitlabProvider,
ssoProvider, ssoProvider,
ssoSecret, ssoSecret,
ssoUrl ssoUrl,
defaultSortPolicy
FROM domains FROM domains
WHERE ownerHex=$1; WHERE ownerHex=$1;
` `
@ -62,7 +63,8 @@ func domainList(ownerHex string) ([]domain, error) {
&d.GitlabProvider, &d.GitlabProvider,
&d.SsoProvider, &d.SsoProvider,
&d.SsoSecret, &d.SsoSecret,
&d.SsoUrl); err != nil { &d.SsoUrl,
&d.DefaultSortPolicy); err != nil {
logger.Errorf("cannot Scan domain: %v", err) logger.Errorf("cannot Scan domain: %v", err)
return nil, errorInternal return nil, errorInternal
} }

View File

@ -25,7 +25,8 @@ func domainUpdate(d domain) error {
githubProvider=$12, githubProvider=$12,
gitlabProvider=$13, gitlabProvider=$13,
ssoProvider=$14, ssoProvider=$14,
ssoUrl=$15 ssoUrl=$15,
defaultSortPolicy=$16
WHERE domain=$1; WHERE domain=$1;
` `
@ -44,7 +45,8 @@ func domainUpdate(d domain) error {
d.GithubProvider, d.GithubProvider,
d.GitlabProvider, d.GitlabProvider,
d.SsoProvider, d.SsoProvider,
d.SsoUrl) d.SsoUrl,
d.DefaultSortPolicy)
if err != nil { if err != nil {
logger.Errorf("cannot update non-moderators: %v", err) logger.Errorf("cannot update non-moderators: %v", err)
return errorInternal return errorInternal

View File

@ -0,0 +1,10 @@
-- Default sort policy for each domain
CREATE TYPE sortPolicy AS ENUM (
'score-desc',
'creationdate-desc',
'creationdate-asc'
);
ALTER TABLE domains
ADD defaultSortPolicy sortPolicy NOT NULL DEFAULT 'score-desc';

View File

@ -204,6 +204,26 @@
</div> </div>
</div> </div>
<div class="question">
<div class="title">
Comment Sorting
</div>
<div class="answer">
<div class="row no-border commento-round-check">
<input type="radio" id="defaultSortPolicy-score-desc" value="score-desc" v-model="domains[cd].defaultSortPolicy">
<label for="defaultSortPolicy-score-desc">Most upvoted first</label>
</div>
<div class="row no-border commento-round-check">
<input type="radio" id="defaultSortPolicy-creationdate-desc" value="creationdate-desc" v-model="domains[cd].defaultSortPolicy">
<label for="defaultSortPolicy-creationdate-desc">Newest first</label>
</div>
<div class="row no-border commento-round-check">
<input type="radio" id="defaultSortPolicy-creationdate-asc" value="creationdate-asc" v-model="domains[cd].defaultSortPolicy">
<label for="defaultSortPolicy-creationdate-asc">Oldest first</label>
</div>
</div>
</div>
<div class="center"> <div class="center">
<button id="save-general-button" onclick="window.commento.generalSaveHandler()" class="button">Save Changes</button> <button id="save-general-button" onclick="window.commento.generalSaveHandler()" class="button">Save Changes</button>
</div> </div>
@ -320,6 +340,26 @@
</div> </div>
</div> </div>
<div class="question">
<div class="title">
Default Comment Sorting
</div>
<div class="answer">
<div class="row no-border commento-round-check">
<input type="radio" id="defaultSortPolicy-score-desc" value="score-desc" v-model="domains[cd].defaultSortPolicy">
<label for="defaultSortPolicy-score-desc">Most upvoted first</label>
</div>
<div class="row no-border commento-round-check">
<input type="radio" id="defaultSortPolicy-creationdate-desc" value="creationdate-desc" v-model="domains[cd].defaultSortPolicy">
<label for="defaultSortPolicy-creationdate-desc">Newest first</label>
</div>
<div class="row no-border commento-round-check">
<input type="radio" id="defaultSortPolicy-creationdate-asc" value="creationdate-asc" v-model="domains[cd].defaultSortPolicy">
<label for="defaultSortPolicy-creationdate-asc">Oldest first</label>
</div>
</div>
</div>
<div class="center"> <div class="center">
<button id="save-general-button" onclick="window.commento.generalSaveHandler()" class="button">Save Changes</button> <button id="save-general-button" onclick="window.commento.generalSaveHandler()" class="button">Save Changes</button>
</div> </div>

View File

@ -34,11 +34,13 @@
var ID_MOD_TOOLS_LOCK_BUTTON = "commento-mod-tools-lock-button"; var ID_MOD_TOOLS_LOCK_BUTTON = "commento-mod-tools-lock-button";
var ID_ERROR = "commento-error"; var ID_ERROR = "commento-error";
var ID_LOGGED_CONTAINER = "commento-logged-container"; var ID_LOGGED_CONTAINER = "commento-logged-container";
var ID_PRE_COMMENTS_AREA = "commento-pre-comments-area";
var ID_COMMENTS_AREA = "commento-comments-area"; var ID_COMMENTS_AREA = "commento-comments-area";
var ID_SUPER_CONTAINER = "commento-textarea-super-container-"; var ID_SUPER_CONTAINER = "commento-textarea-super-container-";
var ID_TEXTAREA_CONTAINER = "commento-textarea-container-"; var ID_TEXTAREA_CONTAINER = "commento-textarea-container-";
var ID_TEXTAREA = "commento-textarea-"; var ID_TEXTAREA = "commento-textarea-";
var ID_ANONYMOUS_CHECKBOX = "commento-anonymous-checkbox-"; var ID_ANONYMOUS_CHECKBOX = "commento-anonymous-checkbox-";
var ID_SORT_POLICY = "commento-sort-policy-";
var ID_CARD = "commento-comment-card-"; var ID_CARD = "commento-comment-card-";
var ID_BODY = "commento-comment-body-"; var ID_BODY = "commento-comment-body-";
var ID_TEXT = "commento-comment-text-"; var ID_TEXT = "commento-comment-text-";
@ -85,6 +87,7 @@
var configuredOauths = {}; var configuredOauths = {};
var popupBoxType = "login"; var popupBoxType = "login";
var oauthButtonsShown = false; var oauthButtonsShown = false;
var sortPolicy = "score-desc";
var selfHex = undefined; var selfHex = undefined;
var mobileView = null; var mobileView = null;
@ -388,6 +391,8 @@
commenters = Object.assign({}, commenters, resp.commenters) commenters = Object.assign({}, commenters, resp.commenters)
configuredOauths = resp.configuredOauths; configuredOauths = resp.configuredOauths;
sortPolicy = resp.defaultSortPolicy;
call(callback); call(callback);
}); });
} }
@ -564,13 +569,62 @@
} }
var sortPolicyNames = {
"score-desc": "Upvotes",
"creationdate-desc": "Newest",
"creationdate-asc": "Oldest",
};
function sortPolicyApply(policy) {
classRemove($(ID_SORT_POLICY + sortPolicy), "sort-policy-button-selected");
var commentsArea = $(ID_COMMENTS_AREA);
commentsArea.innerHTML = "";
sortPolicy = policy;
var cards = commentsRecurse(parentMap(comments), "root");
if (cards) {
append(commentsArea, cards);
}
classAdd($(ID_SORT_POLICY + policy), "sort-policy-button-selected");
}
function sortPolicyBox() {
var sortPolicyButtonsContainer = create("div");
var sortPolicyButtons = create("div");
classAdd(sortPolicyButtonsContainer, "sort-policy-buttons-container");
classAdd(sortPolicyButtons, "sort-policy-buttons");
for (var sp in sortPolicyNames) {
var sortPolicyButton = create("a");
sortPolicyButton.id = ID_SORT_POLICY + sp;
classAdd(sortPolicyButton, "sort-policy-button");
if (sp === sortPolicy) {
classAdd(sortPolicyButton, "sort-policy-button-selected");
}
sortPolicyButton.innerText = sortPolicyNames[sp];
onclick(sortPolicyButton, sortPolicyApply, sp);
append(sortPolicyButtons, sortPolicyButton)
}
append(sortPolicyButtonsContainer, sortPolicyButtons);
return sortPolicyButtonsContainer
}
function rootCreate(callback) { function rootCreate(callback) {
var login = create("div"); var login = create("div");
var loginText = create("div"); var loginText = create("div");
var mainArea = $(ID_MAIN_AREA); var mainArea = $(ID_MAIN_AREA);
var preCommentsArea = create("div");
var commentsArea = create("div"); var commentsArea = create("div");
login.id = ID_LOGIN; login.id = ID_LOGIN;
preCommentsArea.id = ID_PRE_COMMENTS_AREA;
commentsArea.id = ID_COMMENTS_AREA; commentsArea.id = ID_COMMENTS_AREA;
classAdd(login, "login"); classAdd(login, "login");
@ -601,6 +655,12 @@
append(mainArea, textareaCreate("root")); append(mainArea, textareaCreate("root"));
} }
if (comments.length > 0) {
append(mainArea, sortPolicyBox());
}
append(mainArea, preCommentsArea);
append(mainArea, commentsArea); append(mainArea, commentsArea);
append(root, mainArea); append(root, mainArea);
@ -696,7 +756,7 @@
onclick(replyButton, global.replyShow, id) onclick(replyButton, global.replyShow, id)
} else { } else {
textarea.value = ""; textarea.value = "";
insertAfter(textareaSuperContainer, newCard); insertAfter($(ID_PRE_COMMENTS_AREA), newCard);
} }
call(callback); call(callback);
@ -762,6 +822,27 @@
} }
var sortPolicyFunctions = {
"score-desc": function(a, b) {
return b.score - a.score;
},
"creationdate-desc": function(a, b) {
if (a.creationDate < b.creationDate) {
return 1;
} else {
return -1;
}
},
"creationdate-asc": function(a, b) {
if (a.creationDate < b.creationDate) {
return -1;
} else {
return 1;
}
},
};
function commentsRecurse(parentMap, parentHex) { function commentsRecurse(parentMap, parentHex) {
var cur = parentMap[parentHex]; var cur = parentMap[parentHex];
if (!cur || !cur.length) { if (!cur || !cur.length) {
@ -775,15 +856,7 @@
return Infinity; return Infinity;
} }
if (a.score !== b.score) { return sortPolicyFunctions[sortPolicy](a, b);
return b.score - a.score;
}
if (a.creationDate < b.creationDate) {
return -1;
} else {
return 1;
}
}); });
var curTime = (new Date()).getTime(); var curTime = (new Date()).getTime();
@ -1325,28 +1398,31 @@
} }
function commentsRender() { function parentMap(comments) {
var parentMap = {}; var m = {};
var parentHex;
var commentsArea = $(ID_COMMENTS_AREA);
comments.forEach(function(comment) { comments.forEach(function(comment) {
parentHex = comment.parentHex; var parentHex = comment.parentHex;
if (!(parentHex in parentMap)) { if (!(parentHex in m)) {
parentMap[parentHex] = []; m[parentHex] = [];
} }
comment.creationDate = new Date(comment.creationDate); comment.creationDate = new Date(comment.creationDate);
parentMap[parentHex].push(comment); console.log(m, parentHex);
m[parentHex].push(comment);
commentsMap[comment.commentHex] = { commentsMap[comment.commentHex] = {
"html": comment.html, "html": comment.html,
"markdown": comment.markdown, "markdown": comment.markdown,
}; };
}); });
var cards = commentsRecurse(parentMap, "root"); return m;
}
function commentsRender() {
var commentsArea = $(ID_COMMENTS_AREA);
var cards = commentsRecurse(parentMap(comments), "root");
if (cards) { if (cards) {
append(commentsArea, cards); append(commentsArea, cards);
} }

View File

@ -57,6 +57,30 @@
font-weight: 700; font-weight: 700;
margin-top: 16px; margin-top: 16px;
} }
.commento-sort-policy-buttons-container {
padding: 12px 0px;
font-weight: 400;
.commento-sort-policy-buttons {
float: right;
.commento-sort-policy-button {
color: $gray-6;
font-size: 13px;
padding: 0px 7px;
}
.commento-sort-policy-button:hover {
cursor: pointer;
}
.commento-sort-policy-button-selected {
color: $blue-8;
font-weight: bold;
}
}
}
} }
.commento-root-font { .commento-root-font {