Skip to content

Commit 8e0fdeb

Browse files
committed
js: Add read-only mode for comment threads
Implements a client-side "read-only" mode for comment threads, controlled via the new data-isso-read-only attribute on the embed script. When enabled, the postbox and reply/edit/delete UI elements are hidden, allowing threads to be displayed without permitting new comments or edits. - Adds "read-only" config option to client default config - Updates comment template and embed logic to respect read-only mode - Documents the new option in client-config docs - Includes unit tests for read-only mode behavior Closes: #1039
1 parent 194a18d commit 8e0fdeb

7 files changed

Lines changed: 150 additions & 11 deletions

File tree

CHANGES.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
11
Changelog for Isso
22
==================
33

4+
%(version)s (%(date)s)
5+
--------------------
6+
7+
New Features
8+
^^^^^^^^^^^^
9+
10+
- Client: Add ``data-isso-read-only`` client option to render threads in
11+
read-only mode (hides postbox and disables reply/edit/delete UI). (`#1104`_, pkvach)
12+
13+
.. _#1104: https://github.com/isso-comments/isso/pull/1104
14+
415
0.14.0 (2026-03-26)
516
--------------------
617

docs/docs/reference/client-config.rst

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,30 @@ data-isso-sorting
272272

273273
.. versionadded:: 0.13.1
274274

275+
.. _data-isso-read-only:
276+
277+
data-isso-read-only
278+
Set to ``true`` to display the comment thread in read-only mode. This will
279+
hide the main comment postbox and disable reply/edit/delete functionality,
280+
showing only existing comments.
281+
282+
This is useful for archived content, closed discussions, or when you want
283+
to display comments without allowing new submissions.
284+
285+
.. note::
286+
287+
This is a **client-side only** setting that affects the UI display but
288+
does not prevent direct API calls. It disables the postbox, reply,
289+
edit, and delete links while keeping voting and comment display active.
290+
291+
.. code-block:: html
292+
293+
<script src="..." data-isso-read-only="true"></script>
294+
295+
Default: ``false``
296+
297+
.. versionadded:: 0.14.1
298+
275299
Deprecated Client Settings
276300
--------------------------
277301

isso/js/app/default_config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ var default_config = {
2323
"vote-levels": null,
2424
"feed": false,
2525
"page-author-hashes": "",
26+
"read-only": false,
2627
};
2728
Object.freeze(default_config);
2829

isso/js/app/isso.js

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,17 @@ var insert = function({ comment, scrollIntoView, offset }) {
221221
text = $("#isso-" + comment.id + " > .isso-text-wrapper > .isso-text");
222222

223223
var form = null; // XXX: probably a good place for a closure
224-
$("a.isso-reply", footer).toggle("click",
224+
225+
// Helper function to attach toggle handlers to buttons with null checks
226+
var attachToggleHandler = function(selector, onActivate, onDeactivate) {
227+
var button = $(selector, footer);
228+
if (button) {
229+
button.toggle("click", onActivate, onDeactivate);
230+
}
231+
};
232+
233+
// Reply button handler
234+
attachToggleHandler("a.isso-reply",
225235
function(toggler) {
226236
form = footer.insertAfter(new Postbox(comment.parent === null ? comment.id : comment.parent));
227237
form.onsuccess = function() { toggler.next(); };
@@ -282,7 +292,8 @@ var insert = function({ comment, scrollIntoView, offset }) {
282292
votes(comment.likes - comment.dislikes);
283293
}
284294

285-
$("a.isso-edit", footer).toggle("click",
295+
// Edit button handler
296+
attachToggleHandler("a.isso-edit",
286297
function(toggler) {
287298
var edit = $("a.isso-edit", footer);
288299
var avatar = config["avatar"] || config["gravatar"] ? $(".isso-avatar", el, false)[0] : null;
@@ -345,7 +356,8 @@ var insert = function({ comment, scrollIntoView, offset }) {
345356
}
346357
);
347358

348-
$("a.isso-delete", footer).toggle("click",
359+
// Delete button handler
360+
attachToggleHandler("a.isso-delete",
349361
function(toggler) {
350362
var del = $("a.isso-delete", footer);
351363
var state = ! toggler.state;

isso/js/app/templates/comment.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,12 @@ var html = function (globals) {
4040
+ "<span class='isso-spacer'>|</span>"
4141
+ "<a class='isso-downvote' href='#'>" + svg['arrow-down'] + "</a>"
4242
: '')
43-
+ "<a class='isso-reply' href='#'>" + i18n('comment-reply') + "</a>"
44-
+ "<a class='isso-edit' href='#'>" + i18n('comment-edit') + "</a>"
45-
+ "<a class='isso-delete' href='#'>" + i18n('comment-delete') + "</a>"
43+
+ (conf["read-only"]
44+
? ''
45+
: "<a class='isso-reply' href='#'>" + i18n('comment-reply') + "</a>"
46+
+ "<a class='isso-edit' href='#'>" + i18n('comment-edit') + "</a>"
47+
+ "<a class='isso-delete' href='#'>" + i18n('comment-delete') + "</a>"
48+
)
4649
+ "</div>" // .isso-comment-footer
4750
+ "</div>" // .text-wrapper
4851
+ "<div class='isso-follow-up'></div>"

isso/js/embed.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,13 @@ function init() {
7878
if (!$('h4.isso-thread-heading')) {
7979
isso_thread.append(heading);
8080
}
81-
postbox = new isso.Postbox(null);
82-
if (!$('.isso-postbox')) {
83-
isso_thread.append(postbox);
84-
} else {
85-
$('.isso-postbox').value = postbox;
81+
if (!config["read-only"]) {
82+
postbox = new isso.Postbox(null);
83+
if (!$('.isso-postbox')) {
84+
isso_thread.append(postbox);
85+
} else {
86+
$('.isso-postbox').value = postbox;
87+
}
8688
}
8789
if (!$('#isso-root')) {
8890
isso_thread.append('<div id="isso-root"></div>');
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/**
2+
* @jest-environment jsdom
3+
*/
4+
5+
"use strict";
6+
7+
describe('Isso read-only mode', () => {
8+
beforeEach(() => {
9+
jest.resetModules();
10+
11+
// globals.offset.localTime() will be passed to i18n.ago()
12+
// localTime param will then be called as localTime.getTime()
13+
jest.mock('app/globals', () => ({
14+
offset: {
15+
localTime: jest.fn(() => ({
16+
getTime: jest.fn(() => 0),
17+
})),
18+
},
19+
}));
20+
21+
document.body.innerHTML =
22+
'<div id="isso-thread"></div>' +
23+
'<script src="http://isso.api/js/embed.min.js"' +
24+
' data-isso="/"' +
25+
' data-isso-read-only="true"></script>';
26+
});
27+
28+
test('should not render postbox in read-only mode', () => {
29+
const $ = require("app/dom");
30+
const config = require("app/config");
31+
const template = require("app/template");
32+
const i18n = require("app/i18n");
33+
const svg = require("app/svg");
34+
35+
template.set("conf", config);
36+
template.set("i18n", i18n.translate);
37+
template.set("pluralize", i18n.pluralize);
38+
template.set("svg", svg);
39+
40+
let isso_thread = $('#isso-thread');
41+
isso_thread.append('<div id="isso-root"></div>');
42+
43+
expect($('.isso-postbox')).toBeNull();
44+
});
45+
46+
test('should not render reply, edit, and delete buttons in read-only mode', () => {
47+
const isso = require("app/isso");
48+
const $ = require("app/dom");
49+
const config = require("app/config");
50+
const template = require("app/template");
51+
const i18n = require("app/i18n");
52+
const svg = require("app/svg");
53+
54+
template.set("conf", config);
55+
template.set("i18n", i18n.translate);
56+
template.set("pluralize", i18n.pluralize);
57+
template.set("svg", svg);
58+
59+
let isso_thread = $('#isso-thread');
60+
isso_thread.append('<div id="isso-root"></div>');
61+
62+
// Simulate a comment object
63+
let comment = {
64+
id: 1,
65+
hash: "abc123",
66+
author: "TestUser",
67+
website: null,
68+
created: 1651788192.4473603,
69+
mode: 1,
70+
text: "Test comment",
71+
likes: 0,
72+
dislikes: 0,
73+
replies: [],
74+
hidden_replies: 0,
75+
parent: null
76+
};
77+
78+
// Render comment
79+
isso.insert({comment, scrollIntoView: false, offset: 0});
80+
81+
// Verify that interactive buttons are not rendered in read-only mode
82+
expect($('a.isso-reply')).toBeNull();
83+
expect($('a.isso-edit')).toBeNull();
84+
expect($('a.isso-delete')).toBeNull();
85+
});
86+
});

0 commit comments

Comments
 (0)