Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
Changelog for Isso
==================

%(version)s (%(date)s)
--------------------

New Features
^^^^^^^^^^^^

- Client: Add ``data-isso-read-only`` client option to render threads in
read-only mode (hides postbox and disables reply/edit/delete UI). (`#1104`_, pkvach)

.. _#1104: https://github.com/isso-comments/isso/pull/1104

0.14.0 (2026-03-26)
--------------------

Expand Down
24 changes: 24 additions & 0 deletions docs/docs/reference/client-config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,30 @@ data-isso-sorting

.. versionadded:: 0.13.1

.. _data-isso-read-only:

data-isso-read-only
Set to ``true`` to display the comment thread in read-only mode. This will
hide the main comment postbox and disable reply/edit/delete functionality,
showing only existing comments.

This is useful for archived content, closed discussions, or when you want
to display comments without allowing new submissions.

.. note::

This is a **client-side only** setting that affects the UI display but
does not prevent direct API calls. It disables the postbox, reply,
edit, and delete links while keeping voting and comment display active.

.. code-block:: html

<script src="..." data-isso-read-only="true"></script>

Default: ``false``

.. versionadded:: 0.14.1

Deprecated Client Settings
--------------------------

Expand Down
1 change: 1 addition & 0 deletions isso/js/app/default_config.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ var default_config = {
"vote-levels": null,
"feed": false,
"page-author-hashes": "",
"read-only": false,
};
Object.freeze(default_config);

Expand Down
18 changes: 15 additions & 3 deletions isso/js/app/isso.js
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,17 @@ var insert = function({ comment, scrollIntoView, offset }) {
text = $("#isso-" + comment.id + " > .isso-text-wrapper > .isso-text");

var form = null; // XXX: probably a good place for a closure
$("a.isso-reply", footer).toggle("click",

// Helper function to attach toggle handlers to buttons with null checks
var attachToggleHandler = function(selector, onActivate, onDeactivate) {
var button = $(selector, footer);
if (button) {
button.toggle("click", onActivate, onDeactivate);
}
};

// Reply button handler
attachToggleHandler("a.isso-reply",
function(toggler) {
form = footer.insertAfter(new Postbox(comment.parent === null ? comment.id : comment.parent));
form.onsuccess = function() { toggler.next(); };
Expand Down Expand Up @@ -282,7 +292,8 @@ var insert = function({ comment, scrollIntoView, offset }) {
votes(comment.likes - comment.dislikes);
}

$("a.isso-edit", footer).toggle("click",
// Edit button handler
attachToggleHandler("a.isso-edit",
function(toggler) {
var edit = $("a.isso-edit", footer);
var avatar = config["avatar"] || config["gravatar"] ? $(".isso-avatar", el, false)[0] : null;
Expand Down Expand Up @@ -345,7 +356,8 @@ var insert = function({ comment, scrollIntoView, offset }) {
}
);

$("a.isso-delete", footer).toggle("click",
// Delete button handler
attachToggleHandler("a.isso-delete",
function(toggler) {
var del = $("a.isso-delete", footer);
var state = ! toggler.state;
Expand Down
9 changes: 6 additions & 3 deletions isso/js/app/templates/comment.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,12 @@ var html = function (globals) {
+ "<span class='isso-spacer'>|</span>"
+ "<a class='isso-downvote' href='#'>" + svg['arrow-down'] + "</a>"
: '')
+ "<a class='isso-reply' href='#'>" + i18n('comment-reply') + "</a>"
+ "<a class='isso-edit' href='#'>" + i18n('comment-edit') + "</a>"
+ "<a class='isso-delete' href='#'>" + i18n('comment-delete') + "</a>"
+ (conf["read-only"]
? ''
: "<a class='isso-reply' href='#'>" + i18n('comment-reply') + "</a>"
+ "<a class='isso-edit' href='#'>" + i18n('comment-edit') + "</a>"
+ "<a class='isso-delete' href='#'>" + i18n('comment-delete') + "</a>"
)
+ "</div>" // .isso-comment-footer
+ "</div>" // .text-wrapper
+ "<div class='isso-follow-up'></div>"
Expand Down
12 changes: 7 additions & 5 deletions isso/js/embed.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,13 @@ function init() {
if (!$('h4.isso-thread-heading')) {
isso_thread.append(heading);
}
postbox = new isso.Postbox(null);
if (!$('.isso-postbox')) {
isso_thread.append(postbox);
} else {
$('.isso-postbox').value = postbox;
if (!config["read-only"]) {
postbox = new isso.Postbox(null);
if (!$('.isso-postbox')) {
isso_thread.append(postbox);
} else {
$('.isso-postbox').value = postbox;
}
}
if (!$('#isso-root')) {
isso_thread.append('<div id="isso-root"></div>');
Expand Down
86 changes: 86 additions & 0 deletions isso/js/tests/unit/read-only-mode.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/**
* @jest-environment jsdom
*/

"use strict";

describe('Isso read-only mode', () => {
beforeEach(() => {
jest.resetModules();

// globals.offset.localTime() will be passed to i18n.ago()
// localTime param will then be called as localTime.getTime()
jest.mock('app/globals', () => ({
offset: {
localTime: jest.fn(() => ({
getTime: jest.fn(() => 0),
})),
},
}));

document.body.innerHTML =
'<div id="isso-thread"></div>' +
'<script src="http://isso.api/js/embed.min.js"' +
' data-isso="/"' +
' data-isso-read-only="true"></script>';
});

test('should not render postbox in read-only mode', () => {
const $ = require("app/dom");
const config = require("app/config");
const template = require("app/template");
const i18n = require("app/i18n");
const svg = require("app/svg");

template.set("conf", config);
template.set("i18n", i18n.translate);
template.set("pluralize", i18n.pluralize);
template.set("svg", svg);

let isso_thread = $('#isso-thread');
isso_thread.append('<div id="isso-root"></div>');

expect($('.isso-postbox')).toBeNull();
});

test('should not render reply, edit, and delete buttons in read-only mode', () => {
const isso = require("app/isso");
const $ = require("app/dom");
const config = require("app/config");
const template = require("app/template");
const i18n = require("app/i18n");
const svg = require("app/svg");

template.set("conf", config);
template.set("i18n", i18n.translate);
template.set("pluralize", i18n.pluralize);
template.set("svg", svg);

let isso_thread = $('#isso-thread');
isso_thread.append('<div id="isso-root"></div>');

// Simulate a comment object
let comment = {
id: 1,
hash: "abc123",
author: "TestUser",
website: null,
created: 1651788192.4473603,
mode: 1,
text: "Test comment",
likes: 0,
dislikes: 0,
replies: [],
hidden_replies: 0,
parent: null
};

// Render comment
isso.insert({comment, scrollIntoView: false, offset: 0});

// Verify that interactive buttons are not rendered in read-only mode
expect($('a.isso-reply')).toBeNull();
expect($('a.isso-edit')).toBeNull();
expect($('a.isso-delete')).toBeNull();
});
});
Loading