diff --git a/clefincode_chat/__init__.py b/clefincode_chat/__init__.py
index 19626ee..6bb6ad1 100644
--- a/clefincode_chat/__init__.py
+++ b/clefincode_chat/__init__.py
@@ -1 +1 @@
-__version__ = '1.3.908'
\ No newline at end of file
+__version__ = '1.3.910'
\ No newline at end of file
diff --git a/clefincode_chat/public/js/components/erpnext_chat_info.js b/clefincode_chat/public/js/components/erpnext_chat_info.js
index fa380e5..bfa2eec 100644
--- a/clefincode_chat/public/js/components/erpnext_chat_info.js
+++ b/clefincode_chat/public/js/components/erpnext_chat_info.js
@@ -737,6 +737,16 @@ save_all_contacts(dialog) {
${frappe.utils.escape_html(topicStatus)}
+
+
+
+
`;
@@ -839,6 +849,106 @@ save_all_contacts(dialog) {
});
+ // Close topic
+ this.$chat_info.find(".close-topic").on("click", async () => {
+ const topicName =
+ this.chat_space.chat_topic_space ||
+ this.chat_space.chat_topic ||
+ this.chat_space.profile?.chat_topic;
+
+ if (!topicName) {
+ frappe.msgprint({
+ title: __("Error"),
+ message: __("No topic found."),
+ indicator: "red"
+ });
+ return;
+ }
+
+ try {
+ await frappe.call({
+ method: "clefincode_chat.api.api_1_3_4.api.update_chat_topic_info",
+ args: {
+ chat_topic: topicName,
+ topic_status: "Closed"
+ }
+ });
+
+ this.applyUpdatedTopicInfo(
+ topicName,
+ this.chat_space.chat_topic_subject ||
+ this.chat_space.chat_topic_space_subject ||
+ this.chat_space.alternative_subject ||
+ topicName,
+ this.chat_space.activeMessageTopicColor ||
+ this.chat_space.topicColorMap?.get(topicName) ||
+ this.chat_space.getTopicColor?.(topicName),
+ this.chat_space.is_private ??
+ this.chat_space.profile?.is_private ??
+ 0,
+ "Closed"
+ );
+
+ this.chat_space.topic_status = "Closed";
+ this.chat_space.chat_topic_status = "Closed";
+
+ if (this.chat_space.profile) {
+ this.chat_space.profile.topic_status = "Closed";
+ }
+
+ if (this.chat_space.topicDetailsCache?.has(topicName)) {
+ const cached = this.chat_space.topicDetailsCache.get(topicName);
+ if (cached) {
+ cached.topic_status = "Closed";
+ cached.status = "Closed";
+ }
+ }
+
+ if (this.chat_space.messageCache) {
+ this.chat_space.messageCache.forEach((cached, key) => {
+ if (
+ cached.chat_topic === topicName ||
+ cached.topic === topicName ||
+ cached.chat_topic_space === topicName
+ ) {
+ cached.topic_status = "Closed";
+ cached.chat_topic_status = "Closed";
+ cached.status = "Closed";
+ }
+ });
+ }
+
+ const $topicElements = this.chat_space.$chat_space_container
+ ? this.chat_space.$chat_space_container.find(
+ `[data-topic-name="${topicName}"]`
+ )
+ : this.chat_space.$chat_space
+ ? this.chat_space.$chat_space.find(
+ `[data-topic-name="${topicName}"]`
+ )
+ : $();
+
+ $topicElements.attr("data-topic-status", "Closed");
+
+ this.chat_space.refreshTopicNavBar?.();
+ this.chat_space.buildTopicMetaMap?.();
+ this.chat_space.applyTopicVisibility?.();
+
+ this.$chat_info.find(".close-topic").prop("disabled", true).text(__("Closed"));
+ $(".close-chat-window").click();
+ frappe.show_alert({
+ message: __("Topic closed"),
+ indicator: "green"
+ });
+ } catch (e) {
+ console.error("Failed to close topic", e);
+ frappe.msgprint({
+ title: __("Error"),
+ message: __("Failed to close topic."),
+ indicator: "red"
+ });
+ }
+ });
this.$chat_info.find(".back-to-chat-info").on("click", function () {
me.chat_space.$wrapper.find(".chat-info").show();
diff --git a/clefincode_chat/public/js/components/erpnext_chat_space.js b/clefincode_chat/public/js/components/erpnext_chat_space.js
index 1bad36c..d9fd1fc 100644
--- a/clefincode_chat/public/js/components/erpnext_chat_space.js
+++ b/clefincode_chat/public/js/components/erpnext_chat_space.js
@@ -90,6 +90,7 @@ export default class ChatSpace {
this.topic_read_only = Boolean(opts.topic_read_only);
this.chat_topic_status = opts.chat_topic_status || null;
this.reply_to_message_name = null;
+ this.pendingReplyAfterReopen = null;
this.topicInactiveNoticeDismissed = false;
this.$topicInactiveNotice = null;
this.pendingReplies = [];
@@ -182,7 +183,7 @@ export default class ChatSpace {
this.initial_message_scroll_done = false;
this.messageCache = new Map();
- this.setup();
+ this.ready = this.setup();
}
async fetchTopicColorFromDB(topicName) {
if (!topicName) return null;
@@ -554,7 +555,21 @@ async openTopicChatWindow(topicName, topicSubject = null, opts = {}) {
if (check_if_chat_window_open(topicKey, "topic")) {
$(`.expand-chat-window[data-id|='${topicKey}']`).click();
- return;
+
+ let existingInstance = null;
+ $(".chat-window").each(function () {
+ const inst = $(this).find(".chat-space").data("chat-space-instance");
+ if (inst && String(inst.chat_topic_space || inst.chat_topic || "") === topicKey) {
+ existingInstance = inst;
+ return false;
+ }
+ });
+
+ if (existingInstance?.ready) {
+ await existingInstance.ready;
+ }
+
+ return existingInstance;
}
const chat_window = new ChatWindow({
@@ -562,8 +577,11 @@ async openTopicChatWindow(topicName, topicSubject = null, opts = {}) {
topic: topicKey
}
});
+ chat_window.$chat_window
+ .attr("data-topic", topicKey)
+ .data("topic", topicKey);
- new ChatSpace({
+ const topicChatSpace = new ChatSpace({
$wrapper: chat_window.$chat_window,
profile: {
...this.profile,
@@ -596,6 +614,12 @@ async openTopicChatWindow(topicName, topicSubject = null, opts = {}) {
chat_topic_status: ctx.topic_status,
original_room_type: ctx.room_type || this.profile.room_type
});
+
+ if (topicChatSpace.ready) {
+ await topicChatSpace.ready;
+ }
+
+ return topicChatSpace;
}
toggleTopicMessages(topicName) {
if (!topicName) return;
@@ -999,18 +1023,33 @@ async openCreateTopicFromPlusDialog() {
latestDate: ""
});
- this.selectMessageTopic(topicName, topicSubject, { scroll: false, color, topic_color: color });
- await this.saveUserActiveChatTopic(topicName);
- setTimeout(() => {
- this.updatePlusTopicButton();
- }, 50);
+ // this.selectMessageTopic(topicName, topicSubject, { scroll: false, color, topic_color: color });
+ // await this.saveUserActiveChatTopic(topicName);
+ // setTimeout(() => {
+ // this.updatePlusTopicButton();
+ // }, 50);
- frappe.show_alert({
- message: __("Topic selected for new messages"),
- indicator: "green"
- });
+ // frappe.show_alert({
+ // message: __("Topic selected for new messages"),
+ // indicator: "green"
+ // });
+
- await this.loadChannelTopicsForSelect?.();
+ await this.loadChannelTopicsForSelect?.();
+ await this.openTopicChatWindow(topicName, topicSubject, {
+ topic_color: color
+ });
+ this.activeMessageTopic = null;
+ this.activeMessageTopicSubject = null;
+ this.activeMessageTopicColor = null;
+ this.chat_topic = null;
+
+ this.clearMessageTopic(false);
+ this.updateActiveTopicButton(null);
+ this.updatePlusTopicButton();
+
+ await this.clearUserActiveChatTopic(topicName);
+
}
}
});
@@ -2087,6 +2126,54 @@ if (original.is_deleted) {
text
};
}
+async prepareReplyToMessage(messageName) {
+ if (!messageName) return;
+
+ if (this.ready) {
+ await this.ready;
+ }
+
+ this.reply_to_message_name = messageName;
+
+ const snippet = await this.makeReplySnippet(messageName, 120);
+ const text = snippet?.text || "[Attachment]";
+
+ let $host = this.$chat_space.children(".reply-preview-host");
+
+ if (!$host.length) {
+ $host = $('');
+
+ if (this.$chat_actions && this.$chat_actions.length) {
+ this.$chat_actions.before($host);
+ } else {
+ this.$chat_space.append($host);
+ }
+ }
+
+ $host.html(`
+
+ ↩
+
+
+
+ `);
+
+ $host.find(".reply-preview__text").text(text);
+
+ setTimeout(() => {
+ if (this.type_message_input?.quill) {
+ this.type_message_input.quill.focus();
+ this.type_message_input.quill.setSelection(
+ this.type_message_input.quill.getLength(),
+ 0
+ );
+ return;
+ }
+
+ const $editor = this.$chat_actions?.find(".type-message .ql-editor");
+ if ($editor?.length) $editor.trigger("focus");
+ }, 0);
+}
async jumpToMessage(messageName, maxTries = 50) {
const limit = this.messages_limit || 10;
@@ -2124,6 +2211,8 @@ async jumpToMessage(messageName, maxTries = 50) {
this.applyTopicVisibility();
}
+ this.checkAndShowTopicInactiveNotice?.();
+
$msg = this.$chat_space.find(`#msg-${messageName}`);
@@ -2743,8 +2832,8 @@ async fetch_single_message(messageName) {
await this.setup_messages(res.results || []);
await this.setup_actions();
await this.applySavedActiveTopic();
- this.checkAndShowTopicInactiveNotice();
this.render();
+ this.checkAndShowTopicInactiveNotice?.();
} catch (error) {
console.log(error);
}
@@ -3556,6 +3645,15 @@ this.$chat_space.off("click.reopenTopic", ".reopen-topic-btn")
await me.setup_actions();
me.setup_events();
+ if (me.pendingReplyAfterReopen) {
+ const pendingMessageName = me.pendingReplyAfterReopen;
+ me.pendingReplyAfterReopen = null;
+
+ setTimeout(async () => {
+ await me.prepareReplyToMessage(pendingMessageName);
+ }, 150);
+ }
+
frappe.show_alert({
message: __("Topic reopened"),
indicator: "green"
@@ -3763,40 +3861,89 @@ this.$wrapper.off("click.chatMenuActions", ".reply-action")
me.closeMessageActionMenu();
const messageName = $(this).data("message-name");
- me.reply_to_message_name = messageName;
+ if (!messageName) return;
+
+ const isAlreadyInTopic =
+ me.is_topic_window ||
+ me.profile?.room_type === "Topic" ||
+ me.chat_topic_space;
- const snippet = await me.makeReplySnippet(messageName, 120);
- const text = snippet?.text || "[Attachment]";
+ let cached = me.messageCache.get(messageName) || {};
- let $host = me.$chat_space.children(".reply-preview-host");
- if (!$host.length) {
- $host = $('');
- me.$chat_actions.before($host);
+ if (!cached.chat_topic && !cached.topic && !cached.topic_name) {
+ try {
+ const msg = await me.fetch_single_message(messageName);
+
+ if (msg) {
+ cached = {
+ ...cached,
+ ...msg,
+ chat_topic: msg.chat_topic || msg.topic || msg.topic_name || cached.chat_topic || null,
+ chat_topic_subject:
+ msg.chat_topic_subject ||
+ msg.topic_subject ||
+ msg.chat_topic_title ||
+ msg.subject ||
+ cached.chat_topic_subject ||
+ null,
+ topic_color:
+ msg.topic_color ||
+ msg.chat_topic_color ||
+ cached.topic_color ||
+ null
+ };
+
+ me.messageCache.set(messageName, cached);
+ }
+ } catch (err) {
+ console.warn("Failed to fetch message before reply", err);
+ }
}
- $host.html(`
-
- ↩
-
-
-
- `);
+ const linkedTopic = await me.getLinkedTopicInfo(cached);
- $host.find(".reply-preview__text").text(text);
+ if (linkedTopic?.name && !isAlreadyInTopic) {
+ const topicSpace = await me.openTopicChatWindow(
+ linkedTopic.name,
+ linkedTopic.subject || linkedTopic.name,
+ {
+ topic_color:
+ linkedTopic.topic_color ||
+ linkedTopic.color ||
+ cached.topic_color ||
+ null
+ }
+ );
- setTimeout(() => {
- if (me.type_message_input?.quill) {
- me.type_message_input.quill.focus();
- me.type_message_input.quill.setSelection(
- me.type_message_input.quill.getLength(),
- 0
- );
+ if (topicSpace) {
+ const isClosed =
+ String(
+ topicSpace.chat_topic_status ||
+ topicSpace.profile?.chat_topic_status ||
+ ""
+ ).toLowerCase() === "closed";
+
+ const isReadOnly =
+ topicSpace.topic_read_only === true ||
+ topicSpace.topic_write_mode === false;
+
+ if (isClosed || isReadOnly) {
+ topicSpace.pendingReplyAfterReopen = messageName;
+
+ frappe.show_alert({
+ message: __("Reopen the topic to reply"),
+ indicator: "orange"
+ });
+
+ return;
+ }
+
+ await topicSpace.prepareReplyToMessage(messageName);
return;
}
+ }
- const $editor = me.$chat_actions?.find(".type-message .ql-editor");
- if ($editor?.length) $editor.trigger("focus");
- }, 0);
+ await me.prepareReplyToMessage(messageName);
});
this.$wrapper.off("click.chatMenuActions", ".react-action")
@@ -4366,6 +4513,7 @@ if (!me.chat_topic_space) {
e.stopPropagation();
me.closePlusMenu();
await me.openCreateTopicFromPlusDialog();
+
});
me.$chat_space.on("click", ".chat-read-more-btn", function (e) {
@@ -4395,7 +4543,7 @@ if (!me.chat_topic_space) {
me.renderTopicSelectList(query);
});
- me.$chat_space.on("click", ".topic-select-main", async function (e) {
+me.$chat_space.on("click", ".topic-select-main", async function (e) {
e.preventDefault();
e.stopPropagation();
@@ -4408,18 +4556,21 @@ if (!me.chat_topic_space) {
topicColor = await me.getTopicColorFromSource(topicName, null, { forceRefresh: true });
- me.selectMessageTopic(topicName, topicSubject, {
- scroll: false,
- color: topicColor,
- topic_color: topicColor
- });
+ // me.selectMessageTopic(topicName, topicSubject, {
+ // scroll: false,
+ // color: topicColor,
+ // topic_color: topicColor
+ // });
- await me.saveUserActiveChatTopic(topicName);
+ // await me.saveUserActiveChatTopic(topicName);
me.closeTopicSelectPopup?.();
me.closeAllTopicsView?.();
-});
+ await me.openTopicChatWindow(topicName, topicSubject, {
+ topic_color: topicColor
+ });
+});
me.$chat_space.on("click", ".topic-select-open", async function (e) {
e.preventDefault();
e.stopPropagation();
@@ -5142,32 +5293,96 @@ isDedicatedTopicContext() {
}
checkAndShowTopicInactiveNotice() {
- if (this.isDedicatedTopicContext()) return;
- if (this.topicInactiveNoticeDismissed) return;
+ if (
+ this.isDedicatedTopicContext?.() ||
+ this.is_topic_window ||
+ this.profile?.room_type === "Topic" ||
+ this.chat_topic_space
+ ) {
+ this.hideTopicInactiveNotice?.();
+ return;
+ }
+
+ if (!this.$chat_space_container || !this.$chat_space_container.length) {
+ return;
+ }
- const lastMsg = this.prevMessage;
- if (!lastMsg) return;
+ const $lastMsg = this.$chat_space_container
+ .find("[data-message-name]")
+ .filter((_, el) => {
+ const $el = $(el);
+ const messageName = $el.attr("data-message-name") || $el.data("message-name");
+ const cached = this.messageCache?.get(messageName) || {};
- const topicName = lastMsg.chat_topic || lastMsg.topic || null;
- if (!topicName) return;
+ if (Number(cached.is_deleted || 0) === 1) return false;
- if (this.activeMessageTopic && String(this.activeMessageTopic) === String(topicName)) return;
+ const messageType = String(cached.message_type || "").toLowerCase();
+ if (messageType === "information") return false;
+
+ const templateType = String(cached.message_template_type || "").toLowerCase();
+ if (
+ templateType === "set topic" ||
+ templateType === "set-topic" ||
+ templateType === "settopic" ||
+ templateType === "remove topic" ||
+ templateType === "remove-topic" ||
+ templateType === "removetopic" ||
+ templateType === "close topic" ||
+ templateType === "close-topic" ||
+ templateType === "closetopic"
+ ) {
+ return false;
+ }
+
+ return true;
+ })
+ .last();
+
+ if (!$lastMsg.length) {
+ this.hideTopicInactiveNotice?.();
+ return;
+ }
+
+ const messageName = $lastMsg.attr("data-message-name") || $lastMsg.data("message-name");
+ const cached = this.messageCache?.get(messageName) || {};
+
+ const topicName =
+ $lastMsg.attr("data-topic-name") ||
+ cached.chat_topic ||
+ cached.topic ||
+ cached.topic_name ||
+ null;
+
+ if (!topicName) {
+ this.hideTopicInactiveNotice?.();
+ return;
+ }
+
+ const topicSubject =
+ $lastMsg.attr("data-topic-subject") ||
+ cached.chat_topic_subject ||
+ cached.topic_subject ||
+ cached.chat_topic_title ||
+ topicName;
- const topicSubject = lastMsg.chat_topic_subject || lastMsg.topic_subject || topicName;
this.showTopicInactiveNotice(topicSubject);
}
showTopicInactiveNotice(topicSubject) {
+ this.hideTopicInactiveNotice?.();
+
const $notice = $(`
-
- ${__('No topic selected ')}
-
-`);
+
+ ${__("No topic selected")}
+
+ `);
+
if (this.$chat_actions && this.$chat_actions.length) {
this.$chat_actions.before($notice);
} else {
this.$chat_space.append($notice);
}
+
this.$topicInactiveNotice = $notice;
}
@@ -7765,16 +7980,26 @@ if (!is_deleted && type !== "info-message") {
await this.send_add_document_message(mention_doctypes, chat_room);
} else {
- let results = await create_chat_topic(
+ let results = await create_chat_topic(
mention_doctypes,
chat_room,
this.last_active_sub_channel
);
+ const createdTopicName = results?.[0]?.chat_topic || results?.[0]?.name;
+ const createdTopicSubject = mention_doctypes?.[0]?.docname || createdTopicName;
+ const createdTopicColor = results?.[0]?.topic_color || null;
+
await this.send_set_topic_message(
- mention_doctypes[0].docname,
+ createdTopicSubject,
chat_room
);
+
+ if (createdTopicName) {
+ await this.openTopicChatWindow(createdTopicName, createdTopicSubject, {
+ topic_color: createdTopicColor
+ });
+ }
}
return;
}
@@ -7959,6 +8184,11 @@ if (!is_deleted && type !== "info-message") {
mention_doctypes[0].docname,
chat_room
);
+ if (createdTopicName) {
+ await this.openTopicChatWindow(createdTopicName, this.activeMessageTopicSubject, {
+ topic_color: createdTopicColor
+ });
+ }
return;
}
@@ -8756,6 +8986,7 @@ async fetchTemplateSuggestions(textValue) {
this.fetchAndRenderReactions(res.message_name);
}
this.prevMessage = res;
+ this.checkAndShowTopicInactiveNotice?.();
}
openMessageActionMenu({ $trigger, messageName, isMyMessage, isTextOnly }) {
@@ -9644,6 +9875,7 @@ async rebuildMessage(messageName) {
me.buildTopicMetaMap();
me.applyTopicVisibility();
}
+ me.checkAndShowTopicInactiveNotice?.();
me.resolvePendingReplies();
me.hydrateReactionsForMessages(res.results);
if (res.results.length != 0) {
diff --git a/clefincode_chat/public/js/components/erpnext_chat_utils.js b/clefincode_chat/public/js/components/erpnext_chat_utils.js
index 75a8a3a..035d3a8 100644
--- a/clefincode_chat/public/js/components/erpnext_chat_utils.js
+++ b/clefincode_chat/public/js/components/erpnext_chat_utils.js
@@ -166,12 +166,14 @@ function set_notification_count(type) {
function check_if_chat_window_open(element, data) {
let open_chat_windows = $(".chat-window");
+ console.log(open_chat_windows);
let open_window_exist = false;
if (open_chat_windows.length > 0) {
open_chat_windows.each(function () {
let open_chat_window = $(this).data(data);
if (element == open_chat_window) {
open_window_exist = true;
+ return false;
}
});
}
diff --git a/setup.py b/setup.py
index 00298c1..f4738bb 100644
--- a/setup.py
+++ b/setup.py
@@ -6,7 +6,7 @@
setup(
name="clefincode_chat",
- version='1.3.908',
+ version='1.3.910',
description="ERPNext & Frappe Business Chat: A self-hosted communication solution.",
author="ClefinCode L.L.C-FZ",
author_email="info@clefincode.com",