tacel-chat 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,201 @@
1
+ /**
2
+ * Search Panel component for tacel-chat
3
+ * Allows searching within the current conversation/room messages by keyword.
4
+ * Shows matching results with click-to-scroll.
5
+ */
6
+
7
+ const { createElement } = require('../utils/dom');
8
+ const { formatTime } = require('../utils/format');
9
+
10
+ class SearchPanel {
11
+ constructor(options = {}) {
12
+ this.onScrollToMessage = options.onScrollToMessage || (() => {});
13
+ this.labels = options.labels || {};
14
+
15
+ this.containerEl = null;
16
+ this.inputEl = null;
17
+ this.resultsEl = null;
18
+ this.isOpen = false;
19
+ this.messages = []; // all messages in current context
20
+ this.query = '';
21
+ }
22
+
23
+ /**
24
+ * Create the toggle button for the header
25
+ * @returns {HTMLElement}
26
+ */
27
+ createToggleButton() {
28
+ const btn = createElement('button', {
29
+ className: 'tc-search-toggle-btn',
30
+ innerHTML: '<i class="fas fa-search"></i>',
31
+ attrs: { type: 'button', title: this.labels.searchMessages || 'Search Messages' },
32
+ events: { click: () => this.toggle() }
33
+ });
34
+ this.toggleBtnEl = btn;
35
+ return btn;
36
+ }
37
+
38
+ /**
39
+ * Render the panel into a parent (the tc-main container)
40
+ * @param {HTMLElement} parent
41
+ */
42
+ render(parent) {
43
+ this.containerEl = createElement('div', { className: 'tc-search-panel' });
44
+ this.containerEl.style.display = 'none';
45
+
46
+ // Header
47
+ const header = createElement('div', { className: 'tc-search-panel-header' });
48
+ header.innerHTML = `
49
+ <div class="tc-search-panel-title">
50
+ <i class="fas fa-search"></i>
51
+ <span>${this.labels.searchMessages || 'Search Messages'}</span>
52
+ </div>
53
+ `;
54
+ const closeBtn = createElement('button', {
55
+ className: 'tc-search-panel-close',
56
+ innerHTML: '<i class="fas fa-times"></i>',
57
+ attrs: { type: 'button' },
58
+ events: { click: () => this.close() }
59
+ });
60
+ header.appendChild(closeBtn);
61
+ this.containerEl.appendChild(header);
62
+
63
+ // Search input
64
+ this.inputEl = createElement('input', {
65
+ className: 'tc-search-panel-input',
66
+ attrs: { type: 'text', placeholder: this.labels.searchPlaceholder || 'Search messages...' }
67
+ });
68
+ this.inputEl.addEventListener('input', () => {
69
+ this.query = this.inputEl.value;
70
+ this._renderResults();
71
+ });
72
+ this.containerEl.appendChild(this.inputEl);
73
+
74
+ // Results
75
+ this.resultsEl = createElement('div', { className: 'tc-search-panel-results' });
76
+ this.containerEl.appendChild(this.resultsEl);
77
+
78
+ parent.appendChild(this.containerEl);
79
+ }
80
+
81
+ /**
82
+ * Update the messages list (called when messages change)
83
+ * @param {Array} messages
84
+ */
85
+ updateMessages(messages) {
86
+ this.messages = messages || [];
87
+ if (this.isOpen && this.query) this._renderResults();
88
+ }
89
+
90
+ toggle() {
91
+ if (this.isOpen) {
92
+ this.close();
93
+ } else {
94
+ this.open();
95
+ }
96
+ }
97
+
98
+ open() {
99
+ this.isOpen = true;
100
+ if (this.containerEl) this.containerEl.style.display = '';
101
+ if (this.toggleBtnEl) this.toggleBtnEl.classList.add('tc-search-toggle-active');
102
+ if (this.inputEl) {
103
+ this.inputEl.value = '';
104
+ this.query = '';
105
+ setTimeout(() => this.inputEl.focus(), 100);
106
+ }
107
+ this._renderResults();
108
+ }
109
+
110
+ close() {
111
+ this.isOpen = false;
112
+ if (this.containerEl) this.containerEl.style.display = 'none';
113
+ if (this.toggleBtnEl) this.toggleBtnEl.classList.remove('tc-search-toggle-active');
114
+ }
115
+
116
+ _renderResults() {
117
+ if (!this.resultsEl) return;
118
+ this.resultsEl.innerHTML = '';
119
+
120
+ if (!this.query || this.query.trim().length === 0) {
121
+ const hint = createElement('div', { className: 'tc-search-panel-empty' });
122
+ hint.innerHTML = `
123
+ <i class="fas fa-search" style="font-size:28px;opacity:0.2;margin-bottom:8px;"></i>
124
+ <div>${this.labels.searchHint || 'Type to search messages'}</div>
125
+ `;
126
+ this.resultsEl.appendChild(hint);
127
+ return;
128
+ }
129
+
130
+ const q = this.query.trim().toLowerCase();
131
+ const matches = this.messages.filter(m =>
132
+ m.content && m.content.toLowerCase().includes(q)
133
+ );
134
+
135
+ if (matches.length === 0) {
136
+ const empty = createElement('div', { className: 'tc-search-panel-empty' });
137
+ empty.innerHTML = `
138
+ <i class="fas fa-search" style="font-size:28px;opacity:0.2;margin-bottom:8px;"></i>
139
+ <div>${this.labels.noSearchResults || 'No messages found'}</div>
140
+ `;
141
+ this.resultsEl.appendChild(empty);
142
+ return;
143
+ }
144
+
145
+ // Count label
146
+ const countEl = createElement('div', { className: 'tc-search-panel-count' });
147
+ countEl.textContent = `${matches.length} result${matches.length !== 1 ? 's' : ''}`;
148
+ this.resultsEl.appendChild(countEl);
149
+
150
+ for (const msg of matches) {
151
+ const item = createElement('div', { className: 'tc-search-panel-item' });
152
+ item.dataset.msgId = msg.id;
153
+
154
+ const info = createElement('div', { className: 'tc-search-panel-item-info' });
155
+
156
+ const senderRow = createElement('div', { className: 'tc-search-panel-item-sender' });
157
+ senderRow.textContent = msg.senderName || 'Unknown';
158
+
159
+ const timeEl = createElement('span', { className: 'tc-search-panel-item-time' });
160
+ timeEl.textContent = formatTime(msg.timestamp);
161
+ senderRow.appendChild(timeEl);
162
+ info.appendChild(senderRow);
163
+
164
+ const textEl = createElement('div', { className: 'tc-search-panel-item-text' });
165
+ // Highlight the matching text
166
+ textEl.innerHTML = this._highlightMatch(msg.content, q);
167
+ info.appendChild(textEl);
168
+
169
+ item.appendChild(info);
170
+
171
+ // Click to scroll to message
172
+ item.addEventListener('click', () => {
173
+ this.onScrollToMessage(msg.id);
174
+ });
175
+
176
+ this.resultsEl.appendChild(item);
177
+ }
178
+ }
179
+
180
+ _highlightMatch(text, query) {
181
+ if (!text) return '';
182
+ const preview = text.substring(0, 150);
183
+ const escaped = preview.replace(/[&<>"']/g, c => ({
184
+ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;'
185
+ })[c]);
186
+ const regex = new RegExp(`(${query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
187
+ return escaped.replace(regex, '<mark class="tc-search-highlight">$1</mark>');
188
+ }
189
+
190
+ destroy() {
191
+ if (this.containerEl) {
192
+ this.containerEl.remove();
193
+ this.containerEl = null;
194
+ }
195
+ this.inputEl = null;
196
+ this.resultsEl = null;
197
+ this.toggleBtnEl = null;
198
+ }
199
+ }
200
+
201
+ module.exports = { SearchPanel };
@@ -0,0 +1,278 @@
1
+ /**
2
+ * Sidebar component for conversation mode in tacel-chat
3
+ */
4
+
5
+ const { getInitials } = require('../utils/dom');
6
+ const { formatChatListTime, truncate } = require('../utils/format');
7
+ const { checkOnline } = require('./presence');
8
+
9
+ class Sidebar {
10
+ constructor(options = {}) {
11
+ this.onSelectConversation = options.onSelectConversation || (() => {});
12
+ this.onNewChat = options.onNewChat || (() => {});
13
+ this.onSearch = options.onSearch || (() => {});
14
+ this.onContextMenu = options.onContextMenu || null;
15
+ this.labels = options.labels || {};
16
+ this.showPresence = options.showPresence !== false;
17
+ this.showSearch = options.showSearch !== false;
18
+ this.showNewChat = options.showNewChat !== false;
19
+ this.resizable = options.resizable !== false;
20
+ this.presenceStaleMs = options.presenceStaleMs || 10000;
21
+ this.containerEl = null;
22
+ this.listEl = null;
23
+ this.searchInput = null;
24
+ this.searchText = '';
25
+ this._resizeCleanup = null;
26
+ }
27
+
28
+ /**
29
+ * Render the sidebar into a container
30
+ * @param {HTMLElement} parent
31
+ */
32
+ render(parent) {
33
+ this.containerEl = document.createElement('div');
34
+ this.containerEl.className = 'tc-sidebar';
35
+
36
+ // Header
37
+ const header = document.createElement('div');
38
+ header.className = 'tc-sidebar-header';
39
+
40
+ const titleRow = document.createElement('div');
41
+ titleRow.className = 'tc-sidebar-title-row';
42
+ titleRow.innerHTML = `
43
+ <div class="tc-sidebar-title">
44
+ <i class="fas fa-comments"></i>
45
+ <h2>${this.labels.sidebarTitle || 'Messages'}</h2>
46
+ </div>
47
+ `;
48
+
49
+ if (this.showNewChat) {
50
+ const newBtn = document.createElement('button');
51
+ newBtn.className = 'tc-new-chat-btn';
52
+ newBtn.innerHTML = '<i class="fas fa-plus"></i>';
53
+ newBtn.title = 'New conversation';
54
+ newBtn.addEventListener('click', () => this.onNewChat());
55
+ titleRow.appendChild(newBtn);
56
+ }
57
+
58
+ header.appendChild(titleRow);
59
+
60
+ if (this.showSearch) {
61
+ const searchContainer = document.createElement('div');
62
+ searchContainer.className = 'tc-search-container';
63
+ this.searchInput = document.createElement('input');
64
+ this.searchInput.className = 'tc-search-input';
65
+ this.searchInput.type = 'text';
66
+ this.searchInput.placeholder = this.labels.searchPlaceholder || 'Search conversations...';
67
+ this.searchInput.addEventListener('input', (e) => {
68
+ this.searchText = e.target.value;
69
+ this.onSearch(this.searchText);
70
+ });
71
+ searchContainer.appendChild(this.searchInput);
72
+ header.appendChild(searchContainer);
73
+ }
74
+
75
+ this.containerEl.appendChild(header);
76
+
77
+ // Chat list
78
+ this.listEl = document.createElement('div');
79
+ this.listEl.className = 'tc-chat-list';
80
+ this.containerEl.appendChild(this.listEl);
81
+
82
+ parent.appendChild(this.containerEl);
83
+
84
+ // Resize handle
85
+ if (this.resizable) {
86
+ this._initResize();
87
+ }
88
+ }
89
+
90
+ _initResize() {
91
+ const handle = document.createElement('div');
92
+ handle.className = 'tc-sidebar-resize-handle';
93
+ this.containerEl.appendChild(handle);
94
+
95
+ let startX = 0;
96
+ let startWidth = 0;
97
+ let dragging = false;
98
+
99
+ const onMouseDown = (e) => {
100
+ e.preventDefault();
101
+ dragging = true;
102
+ startX = e.clientX;
103
+ startWidth = this.containerEl.getBoundingClientRect().width;
104
+ handle.classList.add('tc-sidebar-resize-active');
105
+ document.body.style.cursor = 'col-resize';
106
+ document.body.style.userSelect = 'none';
107
+ };
108
+
109
+ const onMouseMove = (e) => {
110
+ if (!dragging) return;
111
+ const delta = e.clientX - startX;
112
+ const newWidth = Math.min(Math.max(startWidth + delta, 200), 500);
113
+ const container = this.containerEl.closest('.tc-container');
114
+ if (container) {
115
+ container.style.gridTemplateColumns = newWidth + 'px 1fr';
116
+ }
117
+ };
118
+
119
+ const onMouseUp = () => {
120
+ if (!dragging) return;
121
+ dragging = false;
122
+ handle.classList.remove('tc-sidebar-resize-active');
123
+ document.body.style.cursor = '';
124
+ document.body.style.userSelect = '';
125
+ };
126
+
127
+ handle.addEventListener('mousedown', onMouseDown);
128
+ document.addEventListener('mousemove', onMouseMove);
129
+ document.addEventListener('mouseup', onMouseUp);
130
+
131
+ this._resizeCleanup = () => {
132
+ handle.removeEventListener('mousedown', onMouseDown);
133
+ document.removeEventListener('mousemove', onMouseMove);
134
+ document.removeEventListener('mouseup', onMouseUp);
135
+ };
136
+ }
137
+
138
+ /**
139
+ * Update the conversation list
140
+ * @param {Array} conversations - Universal conversation format
141
+ * @param {string} activeId - Currently active conversation ID
142
+ * @param {Array} users - All chat users (for presence)
143
+ */
144
+ update(conversations, activeId, users = []) {
145
+ if (!this.listEl) return;
146
+ this.listEl.innerHTML = '';
147
+
148
+ const search = this.searchText.toLowerCase();
149
+ const teams = [];
150
+ const directs = [];
151
+
152
+ for (const conv of conversations) {
153
+ if (search) {
154
+ const nameMatch = (conv.name || '').toLowerCase().includes(search);
155
+ const msgMatch = (conv.lastMessage || '').toLowerCase().includes(search);
156
+ if (!nameMatch && !msgMatch) continue;
157
+ }
158
+ if (conv.type === 'team') teams.push(conv);
159
+ else directs.push(conv);
160
+ }
161
+
162
+ if (teams.length > 0) {
163
+ const teamHeader = document.createElement('div');
164
+ teamHeader.className = 'tc-list-section-header';
165
+ teamHeader.textContent = this.labels.teamsHeader || 'Teams';
166
+ this.listEl.appendChild(teamHeader);
167
+ teams.forEach(c => this._renderItem(c, activeId, users));
168
+ }
169
+
170
+ if (directs.length > 0) {
171
+ const directHeader = document.createElement('div');
172
+ directHeader.className = 'tc-list-section-header';
173
+ directHeader.textContent = this.labels.directsHeader || 'Direct Messages';
174
+ this.listEl.appendChild(directHeader);
175
+ directs.forEach(c => this._renderItem(c, activeId, users));
176
+ }
177
+
178
+ if (teams.length === 0 && directs.length === 0) {
179
+ const empty = document.createElement('div');
180
+ empty.className = 'tc-list-empty';
181
+ empty.textContent = search ? (this.labels.noResults || 'No conversations found') : (this.labels.noConversations || 'No conversations yet');
182
+ this.listEl.appendChild(empty);
183
+ }
184
+ }
185
+
186
+ _renderItem(conv, activeId, users) {
187
+ const item = document.createElement('div');
188
+ item.className = `tc-chat-item ${String(conv.id) === String(activeId) ? 'tc-chat-item-active' : ''}`;
189
+ item.dataset.conversationId = conv.id;
190
+
191
+ const isTeam = conv.type === 'team';
192
+
193
+ // Avatar
194
+ const avatar = document.createElement('div');
195
+ avatar.className = `tc-chat-item-avatar ${isTeam ? 'tc-chat-item-avatar-team' : ''}`;
196
+ avatar.textContent = getInitials(conv.name);
197
+
198
+ // Info
199
+ const info = document.createElement('div');
200
+ info.className = 'tc-chat-item-info';
201
+
202
+ const nameRow = document.createElement('div');
203
+ nameRow.className = 'tc-chat-item-name-row';
204
+
205
+ const nameEl = document.createElement('span');
206
+ nameEl.className = 'tc-chat-item-name';
207
+
208
+ // Presence dot for direct messages
209
+ if (!isTeam && this.showPresence) {
210
+ const user = users.find(u => u.username.toLowerCase() === (conv.name || '').toLowerCase());
211
+ const online = user ? checkOnline(user, this.presenceStaleMs) : false;
212
+ const dot = document.createElement('span');
213
+ dot.className = `tc-presence-dot ${online ? 'tc-online' : 'tc-offline'}`;
214
+ nameEl.appendChild(dot);
215
+ }
216
+
217
+ const nameText = document.createTextNode(conv.name || 'Unknown');
218
+ nameEl.appendChild(nameText);
219
+
220
+ const timeEl = document.createElement('span');
221
+ timeEl.className = 'tc-chat-item-time';
222
+ timeEl.textContent = formatChatListTime(conv.lastMessageTime);
223
+
224
+ nameRow.appendChild(nameEl);
225
+ nameRow.appendChild(timeEl);
226
+
227
+ const previewRow = document.createElement('div');
228
+ previewRow.className = 'tc-chat-item-preview-row';
229
+
230
+ const preview = document.createElement('span');
231
+ preview.className = 'tc-chat-item-preview';
232
+ preview.textContent = truncate(conv.lastMessage || '', 30);
233
+
234
+ previewRow.appendChild(preview);
235
+
236
+ if (conv.mentionCount > 0) {
237
+ const mentionBadge = document.createElement('span');
238
+ mentionBadge.className = 'tc-mention-badge';
239
+ mentionBadge.innerHTML = `<i class="fas fa-at"></i>${conv.mentionCount > 99 ? '99+' : conv.mentionCount}`;
240
+ mentionBadge.title = `${conv.mentionCount} mention${conv.mentionCount > 1 ? 's' : ''}`;
241
+ previewRow.appendChild(mentionBadge);
242
+ }
243
+
244
+ if (conv.unreadCount > 0) {
245
+ const badge = document.createElement('span');
246
+ badge.className = 'tc-unread-badge';
247
+ badge.textContent = conv.unreadCount > 99 ? '99+' : conv.unreadCount;
248
+ previewRow.appendChild(badge);
249
+ }
250
+
251
+ info.appendChild(nameRow);
252
+ info.appendChild(previewRow);
253
+
254
+ item.appendChild(avatar);
255
+ item.appendChild(info);
256
+
257
+ item.addEventListener('click', () => this.onSelectConversation(conv.id));
258
+ if (this.onContextMenu) {
259
+ item.addEventListener('contextmenu', (e) => this.onContextMenu(conv, e));
260
+ }
261
+ this.listEl.appendChild(item);
262
+ }
263
+
264
+ destroy() {
265
+ if (this._resizeCleanup) {
266
+ this._resizeCleanup();
267
+ this._resizeCleanup = null;
268
+ }
269
+ if (this.containerEl) {
270
+ this.containerEl.remove();
271
+ this.containerEl = null;
272
+ }
273
+ this.listEl = null;
274
+ this.searchInput = null;
275
+ }
276
+ }
277
+
278
+ module.exports = { Sidebar };
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Tab bar component for room mode in tacel-chat
3
+ */
4
+
5
+ class TabBar {
6
+ constructor(options = {}) {
7
+ this.rooms = options.rooms || [];
8
+ this.onSelectRoom = options.onSelectRoom || (() => {});
9
+ this.onContextMenu = options.onContextMenu || null;
10
+ this.labels = options.labels || {};
11
+ this.containerEl = null;
12
+ this.activeRoomType = null;
13
+ this.badges = {}; // roomType -> count
14
+ this.mentionBadges = {}; // roomType -> mention count
15
+ }
16
+
17
+ /**
18
+ * Render the tab bar into a container
19
+ * @param {HTMLElement} parent
20
+ */
21
+ render(parent) {
22
+ this.containerEl = document.createElement('div');
23
+ this.containerEl.className = 'tc-topbar';
24
+
25
+ const topContent = document.createElement('div');
26
+ topContent.className = 'tc-topbar-content';
27
+ topContent.innerHTML = `<h1 class="tc-topbar-title">${this.labels.roomTitle || 'Chat Center'}</h1>`;
28
+
29
+ const tabsRow = document.createElement('div');
30
+ tabsRow.className = 'tc-tabs';
31
+
32
+ this.rooms.forEach(room => {
33
+ const tab = document.createElement('div');
34
+ tab.className = 'tc-tab';
35
+ tab.dataset.roomType = room.type;
36
+ tab.innerHTML = `
37
+ <i class="${room.icon || 'fas fa-comments'}"></i>
38
+ <span>${room.name}</span>
39
+ <div class="tc-tab-mention-badge" style="display:none;"><i class="fas fa-at"></i><span>0</span></div>
40
+ <div class="tc-tab-badge" style="display:none;">0</div>
41
+ `;
42
+ tab.addEventListener('click', () => this.onSelectRoom(room.type));
43
+ if (this.onContextMenu) {
44
+ tab.addEventListener('contextmenu', (e) => this.onContextMenu(room, e));
45
+ }
46
+ tabsRow.appendChild(tab);
47
+ });
48
+
49
+ // Actions area (search, pinned, files buttons will be placed here)
50
+ this.actionsEl = document.createElement('div');
51
+ this.actionsEl.className = 'tc-tabs-actions';
52
+ tabsRow.appendChild(this.actionsEl);
53
+
54
+ this.containerEl.appendChild(topContent);
55
+ this.containerEl.appendChild(tabsRow);
56
+ parent.appendChild(this.containerEl);
57
+ }
58
+
59
+ /**
60
+ * Set the active tab
61
+ * @param {string} roomType
62
+ */
63
+ setActive(roomType) {
64
+ this.activeRoomType = roomType;
65
+ if (!this.containerEl) return;
66
+ const tabs = this.containerEl.querySelectorAll('.tc-tab');
67
+ tabs.forEach(tab => {
68
+ const isActive = tab.dataset.roomType === roomType;
69
+ tab.classList.toggle('tc-tab-active', isActive);
70
+ // Hide badges on active tab
71
+ if (isActive) {
72
+ const badge = tab.querySelector('.tc-tab-badge');
73
+ if (badge) badge.style.display = 'none';
74
+ const mBadge = tab.querySelector('.tc-tab-mention-badge');
75
+ if (mBadge) mBadge.style.display = 'none';
76
+ }
77
+ });
78
+ }
79
+
80
+ /**
81
+ * Set badge count for a room
82
+ * @param {string} roomType
83
+ * @param {number} count
84
+ */
85
+ setBadge(roomType, count) {
86
+ this.badges[roomType] = count;
87
+ if (!this.containerEl) return;
88
+ if (roomType === this.activeRoomType) return; // Don't show badge on active tab
89
+ const tab = this.containerEl.querySelector(`.tc-tab[data-room-type="${roomType}"]`);
90
+ if (!tab) return;
91
+ const badge = tab.querySelector('.tc-tab-badge');
92
+ if (!badge) return;
93
+ if (count > 0) {
94
+ badge.textContent = count > 99 ? '99+' : count;
95
+ badge.style.display = '';
96
+ } else {
97
+ badge.style.display = 'none';
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Set mention badge count for a room
103
+ * @param {string} roomType
104
+ * @param {number} count
105
+ */
106
+ setMentionBadge(roomType, count) {
107
+ this.mentionBadges[roomType] = count;
108
+ if (!this.containerEl) return;
109
+ if (roomType === this.activeRoomType) return;
110
+ const tab = this.containerEl.querySelector(`.tc-tab[data-room-type="${roomType}"]`);
111
+ if (!tab) return;
112
+ const badge = tab.querySelector('.tc-tab-mention-badge');
113
+ if (!badge) return;
114
+ if (count > 0) {
115
+ badge.querySelector('span').textContent = count > 99 ? '99+' : count;
116
+ badge.style.display = '';
117
+ } else {
118
+ badge.style.display = 'none';
119
+ }
120
+ }
121
+
122
+ destroy() {
123
+ if (this.containerEl) {
124
+ this.containerEl.remove();
125
+ this.containerEl = null;
126
+ }
127
+ }
128
+ }
129
+
130
+ module.exports = { TabBar };
package/index.js ADDED
@@ -0,0 +1,12 @@
1
+ /**
2
+ * tacel-chat — Entry point
3
+ * Universal chat module for Tacel Electron applications.
4
+ */
5
+
6
+ const { TacelChat } = require('./chat');
7
+ const { initChatAPI } = require('./chat-api');
8
+ const { syncUser, normalizeUsername } = require('./chat-sync');
9
+ const { THEMES } = require('./themes');
10
+ const { ConfirmDialog } = require('./components/confirm');
11
+
12
+ module.exports = { TacelChat, initChatAPI, syncUser, normalizeUsername, themes: THEMES, ConfirmDialog };
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "tacel-chat",
3
+ "version": "1.2.0",
4
+ "description": "Universal chat module for Tacel Electron apps -- conversation and room modes with presence, read receipts, file attachments, reply system, pinned messages, in-chat search, confirm dialogs, resizable sidebar, responsive design, context menus, @mentions, 30 built-in themes, and full CSS variable theming.",
5
+ "main": "index.js",
6
+ "files": [
7
+ "index.js",
8
+ "chat.js",
9
+ "chat.css",
10
+ "chat-api.js",
11
+ "chat-sync.js",
12
+ "themes.js",
13
+ "components/",
14
+ "utils/",
15
+ "README.md"
16
+ ],
17
+ "keywords": [
18
+ "tacel",
19
+ "chat",
20
+ "electron",
21
+ "messaging",
22
+ "real-time",
23
+ "attachments",
24
+ "themes",
25
+ "pinned-messages",
26
+ "search",
27
+ "responsive",
28
+ "css-variables"
29
+ ],
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "https://github.com/Tacel-ltd/Modules.git",
33
+ "directory": "chat-module"
34
+ },
35
+ "homepage": "https://github.com/Tacel-ltd/Modules/tree/main/chat-module#readme",
36
+ "bugs": {
37
+ "url": "https://github.com/Tacel-ltd/Modules/issues"
38
+ },
39
+ "author": "Tacel",
40
+ "license": "ISC"
41
+ }