diff --git a/docs/docs/configuration/client.rst b/docs/docs/configuration/client.rst index 09e4e0f..fec0c36 100644 --- a/docs/docs/configuration/client.rst +++ b/docs/docs/configuration/client.rst @@ -10,6 +10,9 @@ preferably in the script tag which embeds the JS: data-isso-css="true" data-isso-lang="ru" data-isso-reply-to-self="false" + data-isso-max-comments-top="10" + data-isso-max-comments-nested="5" + data-isso-reveal-on-click="5" data-avatar-bg="#f0f0f0" data-avatar-fg="#9abf88 #5698c4 #e279a3 #9163b6 ..." src="/prefix/js/embed.js"> @@ -53,6 +56,19 @@ data-isso-reply-to-self Set to `true` when spam guard is configured with `reply-to-self = true`. +data-isso-max-comments-top and data-isso-max-comments-nested +------------------------------------------------------------ + +Number of top level (or nested) comments to show by default. If some +comments are not shown, an "X Hidden" link is shown. + +Set to `"inf"` to show all, or `"0"` to hide all. + +data-isso-reveal-on-click +------------------------- + +Number of comments to reveal on clicking the "X Hidden" link. + data-isso-avatar-bg ------------------- diff --git a/isso/db/comments.py b/isso/db/comments.py index 8c97121..350a3c9 100644 --- a/isso/db/comments.py +++ b/isso/db/comments.py @@ -97,15 +97,31 @@ class Comments: return None - def fetch(self, uri, mode=5): + def fetch(self, uri, mode=5, after=0, parent='any', order_by='id', limit=None): """ Return comments for :param:`uri` with :param:`mode`. """ - rv = self.db.execute([ - 'SELECT comments.* FROM comments INNER JOIN threads ON', - ' threads.uri=? AND comments.tid=threads.id AND (? | comments.mode) = ?' - 'ORDER BY id ASC;'], (uri, mode, mode)).fetchall() + sql = [ 'SELECT comments.* FROM comments INNER JOIN threads ON', + ' threads.uri=? AND comments.tid=threads.id AND (? | comments.mode) = ?', + ' AND comments.created>?'] + sql_args = [uri, mode, mode, after] + + if parent != 'any': + if parent is None: + sql.append('AND comments.parent IS NULL') + else: + sql.append('AND comments.parent=?') + sql_args.append(parent) + + sql.append('ORDER BY ? ASC') + sql_args.append(order_by) + + if limit: + sql.append('LIMIT ?') + sql_args.append(limit) + + rv = self.db.execute(sql, sql_args).fetchall() for item in rv: yield dict(zip(Comments.fields, item)) @@ -181,6 +197,17 @@ class Comments: return {'likes': likes + 1, 'dislikes': dislikes} return {'likes': likes, 'dislikes': dislikes + 1} + def reply_count(self, url, after=0): + """ + Return comment count for main thread and all reply threads for one url. + """ + + sql = [ 'SELECT comments.parent,count(*) FROM comments INNER JOIN threads ON', + ' threads.uri=? AND comments.tid=threads.id', + ' AND comments.mode = 1 AND comments.created>? GROUP BY comments.parent;'] + + return dict(self.db.execute(sql, [url, after]).fetchall()) + def count(self, *urls): """ Return comment count for one ore more urls.. diff --git a/isso/js/app/api.js b/isso/js/app/api.js index 73c2a88..1108c0a 100644 --- a/isso/js/app/api.js +++ b/isso/js/app/api.js @@ -78,7 +78,8 @@ define(["app/lib/promise", "app/globals"], function(Q, globals) { var qs = function(params) { var rv = ""; for (var key in params) { - if (params.hasOwnProperty(key) && params[key]) { + if (params.hasOwnProperty(key) && + params[key] !== null && typeof(params[key]) !== "undefined") { rv += key + "=" + encodeURIComponent(params[key]) + "&"; } } @@ -128,17 +129,31 @@ define(["app/lib/promise", "app/globals"], function(Q, globals) { return deferred.promise; }; - var fetch = function(tid) { + var fetch = function(tid, limit, nested_limit, parent, lastcreated) { + if (typeof(limit) === 'undefined') { limit = "inf"; } + if (typeof(nested_limit) === 'undefined') { nested_limit = "inf"; } + if (typeof(parent) === 'undefined') { parent = null; } + + var query_dict = {uri: tid || location, after: lastcreated, parent: parent}; + + if(limit !== "inf") { + query_dict['limit'] = limit; + } + if(nested_limit !== "inf"){ + query_dict['nested_limit'] = nested_limit; + } + var deferred = Q.defer(); - curl("GET", endpoint + "/?" + qs({uri: tid || location}), null, function(rv) { - if (rv.status === 200) { - deferred.resolve(JSON.parse(rv.body)); - } else if (rv.status === 404) { - deferred.resolve([]); - } else { - deferred.reject(rv.body); - } - }); + curl("GET", endpoint + "/?" + + qs(query_dict), null, function(rv) { + if (rv.status === 200) { + deferred.resolve(JSON.parse(rv.body)); + } else if (rv.status === 404) { + deferred.resolve({total_replies: 0}); + } else { + deferred.reject(rv.body); + } + }); return deferred.promise; }; diff --git a/isso/js/app/config.js b/isso/js/app/config.js index 3ebc5c7..aa092ed 100644 --- a/isso/js/app/config.js +++ b/isso/js/app/config.js @@ -5,6 +5,9 @@ define(function() { "css": true, "lang": (navigator.language || navigator.userLanguage).split("-")[0], "reply-to-self": false, + "max-comments-top": 10, + "max-comments-nested": 5, + "reveal-on-click": 5, "avatar-bg": "#f0f0f0", "avatar-fg": ["#9abf88", "#5698c4", "#e279a3", "#9163b6", "#be5168", "#f19670", "#e4bf80", "#447c69"].join(" ") diff --git a/isso/js/app/i18n/de.js b/isso/js/app/i18n/de.js index dcdbac3..98c4d61 100644 --- a/isso/js/app/i18n/de.js +++ b/isso/js/app/i18n/de.js @@ -15,6 +15,7 @@ define({ "comment-deleted": "Kommentar gelöscht.", "comment-queued": "Kommentar muss noch freigeschaltet werden.", "comment-anonymous": "Anonym", + "comment-hidden": "{{ hidden_replies }} versteckt", "date-now": "eben jetzt", "date-minute": "vor einer Minute\nvor {{ n }} Minuten", "date-hour": "vor einer Stunde\nvor {{ n }} Stunden", diff --git a/isso/js/app/i18n/en.js b/isso/js/app/i18n/en.js index 40b5bc9..6cc1d45 100644 --- a/isso/js/app/i18n/en.js +++ b/isso/js/app/i18n/en.js @@ -17,6 +17,7 @@ define({ "comment-deleted": "Comment deleted.", "comment-queued": "Comment in queue for moderation.", "comment-anonymous": "Anonymous", + "comment-hidden": "{{ hidden_replies }} Hidden", "date-now": "right now", "date-minute": "a minute ago\n{{ n }} minutes ago", diff --git a/isso/js/app/i18n/fr.js b/isso/js/app/i18n/fr.js index db72936..19d71a6 100644 --- a/isso/js/app/i18n/fr.js +++ b/isso/js/app/i18n/fr.js @@ -15,6 +15,7 @@ define({ "comment-deleted": "Commentaire supprimé.", "comment-queued": "Commentaire en attente de modération.", "comment-anonymous": "Anonyme", + "comment-hidden": "1 caché\n{{ hidden_replies }} cachés", "date-now": "À l'instant'", "date-minute": "Il y a une minute \n{{ n }} minutes", "date-hour": "Il y a une heure\n{{ n }} heures ", diff --git a/isso/js/app/isso.js b/isso/js/app/isso.js index e903b6a..da78ea0 100644 --- a/isso/js/app/isso.js +++ b/isso/js/app/isso.js @@ -84,8 +84,54 @@ define(["app/text/html", "app/dom", "app/utils", "app/config", "app/api", "app/m return el; }; - var insert = function(comment, scrollIntoView) { + var insert_loader = function(commentWrapper, lastcreated) { + var entrypoint; + if (commentWrapper.id === null) { + entrypoint = $("#isso-root"); + commentWrapper.name = 'null'; + } else { + entrypoint = $("#isso-" + commentWrapper.id + " > .text-wrapper > .isso-follow-up"); + commentWrapper.name = commentWrapper.id; + } + var el = $.htmlify(Mark.up(templates["comment_loader"], commentWrapper)); + entrypoint.append(el); + + $("a.load_hidden", el).toggle("click", + function() { + el.remove(); + api.fetch($("#isso-thread").getAttribute("data-isso-id"), + config["reveal-on-click"], config["max-comments-nested"], + commentWrapper.id, + lastcreated).then( + function(rv) { + if (rv.total_replies === 0) { + return; + } + + var lastcreated = 0; + rv.replies.forEach(function(commentObject) { + insert(commentObject, false); + if(commentObject.created > lastcreated) { + lastcreated = commentObject.created; + } + }); + + if(rv.hidden_replies > 0) { + insert_loader(rv, lastcreated); + } + + if (window.location.hash.length > 0) { + $(window.location.hash).scrollIntoView(); + } + }, + function(err) { + console.log(err); + }); + }); + }; + + var insert = function(comment, scrollIntoView) { var el = $.htmlify(Mark.up(templates["comment"], comment)); // update datetime every 60 seconds @@ -126,13 +172,13 @@ define(["app/text/html", "app/dom", "app/utils", "app/config", "app/api", "app/m $("a.reply", footer).textContent = msgs["comment-close"]; }, function() { - form.remove() + form.remove(); $("a.reply", footer).textContent = msgs["comment-reply"]; } ); // update vote counter, but hide if votes sum to 0 - var votes = function(value) { + var votes = function(value) { var span = $("span.votes", footer); if (span === null && value !== 0) { footer.prepend($.new("span.votes", value)); @@ -261,10 +307,27 @@ define(["app/text/html", "app/dom", "app/utils", "app/config", "app/api", "app/m if (! config["reply-to-self"] && utils.cookie("isso-" + comment.id)) { show($("a.reply", footer).detach()); } + + if(comment.hasOwnProperty('replies')) { + var lastcreated = 0; + comment.replies.forEach(function(replyObject) { + insert(replyObject, false); + if(replyObject.created > lastcreated) { + lastcreated = replyObject.created; + } + + }); + if(comment.hidden_replies > 0) { + insert_loader(comment, lastcreated); + } + + } + }; return { insert: insert, + insert_loader: insert_loader, Postbox: Postbox }; }); diff --git a/isso/js/app/text/comment-loader.html b/isso/js/app/text/comment-loader.html new file mode 100644 index 0000000..2e37e4b --- /dev/null +++ b/isso/js/app/text/comment-loader.html @@ -0,0 +1,3 @@ +