Skip to content

Commit 1a10897

Browse files
committed
js: admin: update comment counts dynamically after moderation
Update the "Valid", "Pending", and "Staled" count badges in the admin interface immediately after a comment is activated or deleted. This eliminates the need for a manual page reload. Related #501
1 parent 1a6e28c commit 1a10897

4 files changed

Lines changed: 114 additions & 13 deletions

File tree

CHANGES.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ Changelog for Isso
77
New Features
88
^^^^^^^^^^^^
99

10-
- Add option to show/hide website field in comment form (`#1111`_, pkvach)
10+
- Update comment counts in the admin interface dynamically after moderation. (`#1113`_, pkvach)
1111

1212
.. _#1111: https://github.com/isso-comments/isso/pull/1111
13+
.. _#1113: https://github.com/isso-comments/isso/pull/1113
1314

1415
0.14.0 (2026-03-26)
1516
--------------------

isso/js/admin.js

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,37 @@ function fade(element) {
2424
op -= op * 0.1;
2525
}, 10);
2626
}
27+
const modeCountIds = {1: 'count-mode-1', 2: 'count-mode-2', 4: 'count-mode-4'};
28+
29+
function getCommentMode(com_id) {
30+
const commentEl = document.getElementById('isso-' + com_id);
31+
if (!commentEl) return null;
32+
const mode = parseInt(commentEl.dataset.mode, 10);
33+
return isNaN(mode) ? null : mode;
34+
}
35+
36+
function updateCountBadge(modeId, delta) {
37+
const el = document.getElementById(modeId);
38+
if (!el) return;
39+
const countEl = el.querySelector('.count');
40+
if (!countEl) return;
41+
const currentCount = parseInt(countEl.textContent, 10) || 0;
42+
countEl.textContent = Math.max(0, currentCount + delta);
43+
}
44+
2745
function moderate(com_id, hash, action, isso_host_script) {
28-
ajax({method: "POST",
29-
url: isso_host_script + "/id/" + com_id + "/" + action + "/" + hash,
30-
success: function(){
31-
fade(document.getElementById("isso-" + com_id));
32-
}});
46+
var fromMode = getCommentMode(com_id);
47+
ajax({method: "POST",
48+
url: isso_host_script + "/id/" + com_id + "/" + action + "/" + hash,
49+
success: function(){
50+
fade(document.getElementById("isso-" + com_id));
51+
if (fromMode && modeCountIds[fromMode]) {
52+
updateCountBadge(modeCountIds[fromMode], -1);
53+
}
54+
if (action === 'activate' && modeCountIds[1]) {
55+
updateCountBadge(modeCountIds[1], +1);
56+
}
57+
}});
3358
}
3459
function edit(com_id, hash, author, email, website, comment, isso_host_script) {
3560
ajax({method: "POST",
@@ -134,3 +159,7 @@ function toggleTooltip(tooltipContainer) {
134159
const tooltipText = tooltipContainer.querySelector(".search-tooltip-text");
135160
tooltipText.classList.toggle("show");
136161
}
162+
163+
if (typeof module !== 'undefined') {
164+
module.exports = { getCommentMode, updateCountBadge };
165+
}

isso/js/tests/unit/admin.test.js

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/**
2+
* @jest-environment jsdom
3+
*/
4+
5+
/* Keep the above exactly as-is!
6+
* https://jestjs.io/docs/configuration#testenvironment-string
7+
*/
8+
9+
const { getCommentMode, updateCountBadge } = require("admin");
10+
11+
describe("getCommentMode", () => {
12+
test("returns 1 for a valid comment", () => {
13+
document.body.innerHTML = `
14+
<div id="isso-1" data-mode="1">
15+
<span class="note"><span class="label label-valid">Valid</span></span>
16+
</div>`;
17+
expect(getCommentMode(1)).toBe(1);
18+
});
19+
20+
test("returns 2 for a pending comment", () => {
21+
document.body.innerHTML = `
22+
<div id="isso-2" data-mode="2">
23+
<span class="note"><span class="label label-pending">Pending</span></span>
24+
</div>`;
25+
expect(getCommentMode(2)).toBe(2);
26+
});
27+
28+
test("returns 4 for a staled comment", () => {
29+
document.body.innerHTML = `
30+
<div id="isso-3" data-mode="4">
31+
<span class="note"><span class="label label-staled">Staled</span></span>
32+
</div>`;
33+
expect(getCommentMode(3)).toBe(4);
34+
});
35+
36+
test("returns null when comment element does not exist", () => {
37+
document.body.innerHTML = "";
38+
expect(getCommentMode(999)).toBeNull();
39+
});
40+
});
41+
42+
describe("updateCountBadge", () => {
43+
test("decrements the count", () => {
44+
document.body.innerHTML = `<span id="count-mode-2">Pending (<span class="count">5</span>)</span>`;
45+
updateCountBadge("count-mode-2", -1);
46+
expect(document.getElementById("count-mode-2").querySelector('.count').textContent).toBe("4");
47+
});
48+
49+
test("increments the count", () => {
50+
document.body.innerHTML = `<span id="count-mode-1">Valid (<span class="count">1</span>)</span>`;
51+
updateCountBadge("count-mode-1", +1);
52+
expect(document.getElementById("count-mode-1").querySelector('.count').textContent).toBe("2");
53+
});
54+
55+
test("does not decrement below zero", () => {
56+
document.body.innerHTML = `<span id="count-mode-2">Pending (<span class="count">0</span>)</span>`;
57+
updateCountBadge("count-mode-2", -1);
58+
expect(document.getElementById("count-mode-2").querySelector('.count').textContent).toBe("0");
59+
});
60+
61+
test("does nothing when count span is absent", () => {
62+
document.body.innerHTML = `<span id="count-mode-2">Pending (3)</span>`;
63+
expect(() => updateCountBadge("count-mode-2", -1)).not.toThrow();
64+
expect(document.getElementById("count-mode-2").textContent).toBe("Pending (3)");
65+
});
66+
67+
test("does nothing for a nonexistent element", () => {
68+
document.body.innerHTML = "";
69+
expect(() => updateCountBadge("count-mode-99", -1)).not.toThrow();
70+
});
71+
});

isso/templates/admin.html

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,18 @@ <h2>Administration</h2>
2424
<div class="filters">
2525
<div class="mode">
2626
<a href="?mode=1&page={{page}}&order_by={{order_by}}">
27-
<span class="label label-valid {% if mode == 1 %}active{% endif %}">
28-
Valid ({{counts.get(1, 0)}})
27+
<span id="count-mode-1" class="label label-valid {% if mode == 1 %}active{% endif %}">
28+
Valid (<span class="count">{{counts.get(1, 0)}}</span>)
2929
</span>
3030
</a>
3131
<a href="?mode=2&page={{page}}&order_by={{order_by}}">
32-
<span class="label label-pending {% if mode == 2 %}active{% endif %}">
33-
Pending ({{counts.get(2, 0)}})
32+
<span id="count-mode-2" class="label label-pending {% if mode == 2 %}active{% endif %}">
33+
Pending (<span class="count">{{counts.get(2, 0)}}</span>)
3434
</span>
3535
</a>
3636
<a href="?mode=4&page={{page}}&order_by={{order_by}}">
37-
<span class="label label-staled {% if mode == 4 %}active{% endif %}">
38-
Staled ({{counts.get(4, 0)}})
37+
<span id="count-mode-4" class="label label-staled {% if mode == 4 %}active{% endif %}">
38+
Staled (<span class="count">{{counts.get(4, 0)}}</span>)
3939
</span>
4040
</a>
4141
</div>
@@ -100,7 +100,7 @@ <h2>Administration</h2>
100100
<h2 class="thread-title">{{comment.title}} (<a href="{{comment.uri}}">{{comment.uri}}</a>)</h2>
101101
{% endif %}
102102
{% endif %}
103-
<div class='isso-comment' id='isso-{{comment.id}}'>
103+
<div class='isso-comment' id='isso-{{comment.id}}' data-mode='{{comment.mode}}'>
104104
{% if conf.avatar %}
105105
<div class='avatar'>
106106
svg(data-hash='#{{comment.hash}}')

0 commit comments

Comments
 (0)