api, frontend, db: add comment sorting
Closes https://gitlab.com/commento/commento/issues/215
This commit is contained in:
parent
3101af8a5c
commit
3e1576d494
@ -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,
|
||||||
|
@ -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"`
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
10
db/20191204173000-sort-method.sql
Normal file
10
db/20191204173000-sort-method.sql
Normal 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';
|
@ -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>
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user