diff --git a/frappe/desk/form/load.py b/frappe/desk/form/load.py
index 565306f07b..81c030d93d 100644
--- a/frappe/desk/form/load.py
+++ b/frappe/desk/form/load.py
@@ -148,7 +148,7 @@ def add_comments(doc, docinfo):
comments = frappe.get_all(
"Comment",
- fields=["name", "creation", "content", "owner", "comment_type"],
+ fields=["name", "creation", "content", "owner", "comment_type", "published"],
filters={"reference_doctype": doc.doctype, "reference_name": doc.name},
)
diff --git a/frappe/desk/form/utils.py b/frappe/desk/form/utils.py
index 2d8406790d..2dbd07081d 100644
--- a/frappe/desk/form/utils.py
+++ b/frappe/desk/form/utils.py
@@ -68,6 +68,16 @@ def update_comment(name, content):
doc.save(ignore_permissions=True)
+@frappe.whitelist()
+def update_comment_publicity(name: str, publish: bool):
+ doc = frappe.get_doc("Comment", name)
+ if frappe.session.user != doc.owner and "System Manager" not in frappe.get_roles():
+ frappe.throw(_("Comment publicity can only be updated by the original author or a System Manager."))
+
+ doc.published = int(publish)
+ doc.save(ignore_permissions=True)
+
+
@frappe.whitelist()
def get_next(doctype, value, prev, filters=None, sort_order="desc", sort_field="creation"):
prev = int(prev)
diff --git a/frappe/public/js/frappe/form/footer/form_timeline.js b/frappe/public/js/frappe/form/footer/form_timeline.js
index bd88b6eabc..4cf29d1e85 100644
--- a/frappe/public/js/frappe/form/footer/form_timeline.js
+++ b/frappe/public/js/frappe/form/footer/form_timeline.js
@@ -601,18 +601,20 @@ class FormTimeline extends BaseTimeline {
let edit_box = this.make_editable(edit_wrapper);
let content_wrapper = comment_wrapper.find(".content");
let more_actions_wrapper = comment_wrapper.find(".more-actions");
- if (
- frappe.model.can_delete("Comment") &&
- (frappe.session.user == doc.owner || frappe.user.has_role("System Manager"))
- ) {
- const delete_option = $(`
-
-
- ${__("Delete")}
-
-
- `).click(() => this.delete_comment(doc.name));
- more_actions_wrapper.find(".dropdown-menu").append(delete_option);
+ const dropdown_menu = more_actions_wrapper.find(".dropdown-menu li");
+
+ if (frappe.session.user == doc.owner || frappe.user.has_role("System Manager")) {
+ if (frappe.model.can_delete("Comment")) {
+ const delete_option = $(`
+ ${__("Delete")}
+ `).click(() => this.delete_comment(doc.name));
+ dropdown_menu.append(delete_option);
+ }
+
+ const un_publish_button = $(`
+ ${doc.published ? __("Unpublish") : __("Publish")}
+ `).click(() => this.update_comment_publicity(doc.name, !doc.published));
+ dropdown_menu.append(un_publish_button);
}
let dismiss_button = $(`
@@ -723,6 +725,37 @@ class FormTimeline extends BaseTimeline {
});
}
+ update_comment_publicity(comment_name, publish) {
+ let message;
+ if (publish) {
+ message = __(
+ "Would you like to publish this comment? This means it will become visible to website/portal users."
+ );
+ } else {
+ message = __(
+ "Would you like to unpublish this comment? This means it will no longer be visible to website/portal users."
+ );
+ }
+
+ frappe.confirm(message, () => {
+ return frappe
+ .xcall("frappe.desk.form.utils.update_comment_publicity", {
+ name: comment_name,
+ publish,
+ })
+ .then(() => {
+ frappe.utils.play_sound("click");
+
+ // update the comment info that is stored in the frontend, then refresh the timeline
+ const comment = this.frm
+ .get_docinfo()
+ .comments.find((comment) => comment.name === comment_name);
+ comment.published = publish;
+ this.refresh();
+ });
+ });
+ }
+
copy_link(ev) {
let doc_link = frappe.urllib.get_full_url(
frappe.utils.get_form_link(this.frm.doctype, this.frm.docname)
diff --git a/frappe/public/js/frappe/form/templates/timeline_message_box.html b/frappe/public/js/frappe/form/templates/timeline_message_box.html
index 0242c00fdd..d7db828e64 100644
--- a/frappe/public/js/frappe/form/templates/timeline_message_box.html
+++ b/frappe/public/js/frappe/form/templates/timeline_message_box.html
@@ -35,6 +35,12 @@
+ {% if (doc.published) { %}
+ ยท
+
+ {% } %}
{% } else { %}