From 1acaa8fbcf67ff35d628f1234169c43b89e1906c Mon Sep 17 00:00:00 2001 From: Ali Alammori Date: Sun, 12 Jul 2026 09:29:03 +0000 Subject: [PATCH] Fix: improve topic timeline filtering and window handling --- clefincode_chat/__init__.py | 2 +- clefincode_chat/desk/custom_load.py | 151 +++-- .../js/components/erpnext_chat_space.js | 526 +++++++++++++----- .../js/frappe/form/footer/form_timeline.js | 274 ++++++--- setup.py | 2 +- 5 files changed, 696 insertions(+), 259 deletions(-) diff --git a/clefincode_chat/__init__.py b/clefincode_chat/__init__.py index 9456cbf..1da7fb6 100644 --- a/clefincode_chat/__init__.py +++ b/clefincode_chat/__init__.py @@ -1 +1 @@ -__version__ = '1.3.912' \ No newline at end of file +__version__ = '1.3.913' \ No newline at end of file diff --git a/clefincode_chat/desk/custom_load.py b/clefincode_chat/desk/custom_load.py index bac9a94..b697711 100644 --- a/clefincode_chat/desk/custom_load.py +++ b/clefincode_chat/desk/custom_load.py @@ -192,67 +192,96 @@ def add_chat_topics(doc, docinfo): docinfo.chat_topics = [] chat_topics = frappe.db.sql( - f""" - SELECT - ref.parent AS topic_name, - topic.chat_channel, - topic.is_private, - topic.subject, - topic.owner, - topic.creation AS topic_creation, - topic.topic_status, - - {bucket_expr} AS period_bucket, - - COALESCE(MIN(msg.send_date), topic.creation) AS period_start, - CASE - WHEN MIN(msg.send_date) IS NOT NULL - AND MAX(msg.send_date) IS NOT NULL - AND MIN(msg.send_date) = MAX(msg.send_date) - THEN DATE_ADD(MAX(msg.send_date), INTERVAL 1 SECOND) - ELSE COALESCE(MAX(msg.send_date), topic.creation) - END AS period_end, - - MIN(msg.send_date) AS first_message_date, - MAX(msg.send_date) AS last_message_date, - SUBSTRING_INDEX( - GROUP_CONCAT(msg.name ORDER BY msg.send_date ASC), - ',', - 1 - ) AS first_message_name, - COUNT(msg.name) AS message_count - - FROM `tabClefinCode Chat Topic Reference` AS ref - - JOIN `tabClefinCode Chat Topic` AS topic - ON ref.parent = topic.name - - LEFT JOIN `tabClefinCode Chat Message` AS msg - ON msg.chat_topic = topic.name - AND msg.chat_channel = topic.chat_channel - AND IFNULL(msg.is_deleted, 0) = 0 - - WHERE - ref.docname = %s - AND ref.active = 1 - - GROUP BY - ref.parent, - topic.chat_channel, - topic.is_private, - topic.subject, - topic.owner, - topic.creation, - topic.topic_status, - {bucket_expr} - - ORDER BY - COALESCE(MIN(msg.send_date), topic.creation) DESC - """, - (doc.name,), - as_dict=True - ) - + f""" + SELECT + ref.parent AS topic_name, + topic.chat_channel, + topic.is_private, + topic.subject, + topic.owner, + topic.creation AS topic_creation, + topic.topic_status, + + {bucket_expr} AS period_bucket, + + COALESCE(MIN(msg.send_date), topic.creation) AS period_start, + + CASE + WHEN MAX(msg.send_date) IS NOT NULL + THEN DATE_ADD( + MAX(msg.send_date), + INTERVAL 1 SECOND + ) + ELSE topic.creation + END AS period_end, + + MIN(msg.send_date) AS first_message_date, + MAX(msg.send_date) AS last_message_date, + + SUBSTRING_INDEX( + GROUP_CONCAT( + msg.name + ORDER BY msg.send_date ASC, msg.creation ASC + ), + ',', + 1 + ) AS first_message_name, + + COUNT(msg.name) AS message_count + + FROM `tabClefinCode Chat Topic Reference` AS ref + + JOIN `tabClefinCode Chat Topic` AS topic + ON ref.parent = topic.name + + LEFT JOIN `tabClefinCode Chat Message` AS msg + ON msg.chat_topic = topic.name + AND msg.chat_channel = topic.chat_channel + AND IFNULL(msg.is_deleted, 0) = 0 + + AND LOWER( + TRIM(IFNULL(msg.message_type, '')) + ) != 'information' + + AND LOWER( + REPLACE( + REPLACE( + TRIM(IFNULL(msg.message_template_type, '')), + '-', + ' ' + ), + '_', + ' ' + ) + ) NOT IN ( + 'set topic', + 'remove topic', + 'close topic', + 'reopen topic' + ) + + WHERE + ref.docname = %s + AND ref.active = 1 + + GROUP BY + ref.parent, + topic.chat_channel, + topic.is_private, + topic.subject, + topic.owner, + topic.creation, + topic.topic_status, + {bucket_expr} + + HAVING COUNT(msg.name) > 0 + + ORDER BY + MIN(msg.send_date) DESC + """, + (doc.name,), + as_dict=True + ) for chat_topic_data in chat_topics: chat_channel = chat_topic_data.chat_channel is_private = chat_topic_data.is_private diff --git a/clefincode_chat/public/js/components/erpnext_chat_space.js b/clefincode_chat/public/js/components/erpnext_chat_space.js index b9a753a..1f7b497 100644 --- a/clefincode_chat/public/js/components/erpnext_chat_space.js +++ b/clefincode_chat/public/js/components/erpnext_chat_space.js @@ -38,10 +38,122 @@ import ChatContactList from "./erpnext_chat_contact_list"; import { add_group_member, create_group } from "./erpnext_chat_contact_list"; +function buildTopicWindowKey({ + source = "base", + chatChannel = "", + chatTopic = "", + fromDate = "", + toDate = "", + firstMessageName = "", +} = {}) { + const parts = [ + "topic-window", + source, + chatChannel, + chatTopic, + ]; + + if (source === "timeline") { + parts.push( + fromDate || "", + toDate || "", + firstMessageName || "" + ); + } + + return parts + .map((value) => + encodeURIComponent(String(value || "").trim()) + ) + .join("::"); +} + +function findTopicWindowInstance(windowKey) { + if (!windowKey) { + return null; + } + + const instances = window.CCChatSpaceInstances || []; + + return ( + instances.find((instance) => { + if (!instance) { + return false; + } + + const instanceKey = + instance.topic_window_key || + instance.profile?.topic_window_key || + instance.$wrapper?.attr?.( + "data-topic-window-key" + ) || + ""; + + return String(instanceKey) === String(windowKey); + }) || null + ); +} + +function showTopicWindowInstance(instance) { + if (!instance?.$wrapper?.length) { + return; + } + + const $window = instance.$wrapper.closest(".chat-window"); + + if (!$window.length) { + return; + } + + const $expandButton = $window.find( + ".expand-chat-window" + ); + + if ($expandButton.length) { + $expandButton.trigger("click"); + } + + $window.show(); +} + export default class ChatSpace { constructor(opts) { this.$wrapper = opts.$wrapper; this.profile = opts.profile; + + this.topic_window_key = + opts.topic_window_key || + this.profile?.topic_window_key || + null; + + this.topic_window_source = + opts.topic_window_source || + this.profile?.topic_window_source || + "base"; + + if (this.topic_window_key) { + this.profile.topic_window_key = + this.topic_window_key; + } + + this.profile.topic_window_source = + this.topic_window_source; + + if ( + this.topic_window_key && + this.$wrapper?.length + ) { + this.$wrapper + .attr( + "data-topic-window-key", + this.topic_window_key + ) + .attr( + "data-topic-source", + this.topic_window_source + ); + } + this.$chat_room = opts.$chat_room; this.new_group = opts.new_group; this.chat_list = opts.chat_list; @@ -356,16 +468,47 @@ isRealTopicTimelineMessage(message = {}) { return true; } -makeTopicStartSeparatorHtml(topicName, topicSubject = null, topicColor = null) { +makeTopicStartSeparatorHtml( + topicName, + topicSubject = null, + topicColor = null +) { if (!topicName) return ""; - if (this.isDedicatedTopicContext?.()) return ""; - const color = topicColor || this.getTopicColor(topicName); - const safeTopicName = frappe.utils.escape_html(String(topicName)); + + if (this.shouldHideTopicSeparators()) { + return ""; + } + + const color = + topicColor || + this.getTopicColor(topicName); + + const safeTopicName = + frappe.utils.escape_html(String(topicName)); + const displaySubject = this.formatTopicSeparatorSubject - ? this.formatTopicSeparatorSubject(topicSubject, topicName) - : String(topicSubject || topicName || __("Topic")).replace(/^topic\s*:\s*/i, "").trim(); - const safeSubject = frappe.utils.escape_html(displaySubject || topicName || __("Topic")); - const safeColor = frappe.utils.escape_html(color || ""); + ? this.formatTopicSeparatorSubject( + topicSubject, + topicName + ) + : String( + topicSubject || + topicName || + __("Topic") + ) + .replace(/^topic\s*:\s*/i, "") + .trim(); + + const safeSubject = + frappe.utils.escape_html( + displaySubject || + topicName || + __("Topic") + ); + + const safeColor = + frappe.utils.escape_html(color || ""); + return `
- ${safeSubject} + + ${safeSubject} +