collabdocchat 1.2.13 → 2.0.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.
Files changed (62) hide show
  1. package/README.md +219 -218
  2. package/index.html +2 -0
  3. package/install-and-start.bat +5 -0
  4. package/install-and-start.sh +5 -0
  5. package/package.json +9 -2
  6. package/scripts/pre-publish-check.js +213 -0
  7. package/scripts/start-app.js +15 -15
  8. package/server/index.js +38 -6
  9. package/server/middleware/cache.js +115 -0
  10. package/server/middleware/errorHandler.js +209 -0
  11. package/server/models/Document.js +66 -59
  12. package/server/models/File.js +49 -43
  13. package/server/models/Group.js +6 -0
  14. package/server/models/KnowledgeBase.js +254 -0
  15. package/server/models/Message.js +43 -0
  16. package/server/models/Task.js +87 -55
  17. package/server/models/User.js +67 -60
  18. package/server/models/Workflow.js +249 -0
  19. package/server/routes/ai.js +327 -0
  20. package/server/routes/audit.js +245 -210
  21. package/server/routes/backup.js +108 -0
  22. package/server/routes/chunked-upload.js +343 -0
  23. package/server/routes/export.js +440 -0
  24. package/server/routes/files.js +294 -218
  25. package/server/routes/groups.js +182 -0
  26. package/server/routes/knowledge.js +509 -0
  27. package/server/routes/tasks.js +257 -110
  28. package/server/routes/workflows.js +380 -0
  29. package/server/utils/backup.js +439 -0
  30. package/server/utils/cache.js +223 -0
  31. package/server/utils/workflow-engine.js +479 -0
  32. package/server/websocket/enhanced.js +509 -0
  33. package/server/websocket/index.js +233 -1
  34. package/src/components/knowledge-modal.js +485 -0
  35. package/src/components/optimized-poll-detail.js +724 -0
  36. package/src/main.js +5 -0
  37. package/src/pages/admin-dashboard.js +2248 -44
  38. package/src/pages/optimized-backup-view.js +616 -0
  39. package/src/pages/optimized-knowledge-view.js +803 -0
  40. package/src/pages/optimized-task-detail.js +843 -0
  41. package/src/pages/optimized-workflow-view.js +806 -0
  42. package/src/pages/simplified-workflows.js +651 -0
  43. package/src/pages/user-dashboard.js +677 -58
  44. package/src/services/api.js +65 -1
  45. package/src/services/auth.js +1 -1
  46. package/src/services/websocket.js +124 -16
  47. package/src/styles/collaboration-modern.js +708 -0
  48. package/src/styles/enhancements.css +392 -0
  49. package/src/styles/main.css +620 -1420
  50. package/src/styles/responsive.css +1000 -0
  51. package/src/styles/sidebar-fix.css +60 -0
  52. package/src/utils/ai-assistant.js +1398 -0
  53. package/src/utils/chat-enhancements.js +509 -0
  54. package/src/utils/collaboration-enhancer.js +1151 -0
  55. package/src/utils/feature-integrator.js +1724 -0
  56. package/src/utils/onboarding-guide.js +734 -0
  57. package/src/utils/performance.js +394 -0
  58. package/src/utils/permission-manager.js +890 -0
  59. package/src/utils/responsive-handler.js +491 -0
  60. package/src/utils/theme-manager.js +811 -0
  61. package/src/utils/ui-enhancements-loader.js +329 -0
  62. package/USAGE.md +0 -298
@@ -0,0 +1,509 @@
1
+ /**
2
+ * 聊天增强功能模块
3
+ * 包含消息撤回、@提及、已读状态等功能
4
+ */
5
+
6
+ export class ChatEnhancements {
7
+ constructor(apiService, wsService, currentUserId) {
8
+ this.apiService = apiService;
9
+ this.wsService = wsService;
10
+ this.currentUserId = currentUserId;
11
+ this.messageTimestamps = new Map(); // 存储消息发送时间
12
+ this.mentionSuggestions = [];
13
+ }
14
+
15
+ /**
16
+ * 渲染消息(带撤回按钮和已读状态)
17
+ */
18
+ renderMessage(msg, messagesDiv, groupMembers) {
19
+ const messageEl = document.createElement('div');
20
+ messageEl.className = `message ${msg.sender === this.currentUserId ? 'own' : ''}`;
21
+ messageEl.dataset.messageId = msg._id;
22
+
23
+ const isOwn = msg.sender === this.currentUserId;
24
+ const canRecall = isOwn && !msg.isRecalled && this.canRecallMessage(msg.timestamp);
25
+
26
+ // 处理@提及高亮
27
+ let content = this.highlightMentions(msg.content, groupMembers);
28
+
29
+ // 检查是否@了当前用户
30
+ const mentionsMe = this.checkMentionsMe(msg.content, groupMembers);
31
+ if (mentionsMe && !isOwn) {
32
+ messageEl.classList.add('mentions-me');
33
+ }
34
+
35
+ messageEl.innerHTML = `
36
+ <div class="message-header">
37
+ <span class="message-user">${msg.username}</span>
38
+ <span class="message-time">${new Date(msg.timestamp).toLocaleTimeString()}</span>
39
+ ${canRecall ? `<button class="btn-recall" data-message-id="${msg._id}" title="撤回消息">撤回</button>` : ''}
40
+ </div>
41
+ <div class="message-content ${msg.isRecalled ? 'recalled' : ''}">${content}</div>
42
+ ${isOwn && !msg.isRecalled ? `<div class="message-status">${this.getReadStatus(msg)}</div>` : ''}
43
+ `;
44
+
45
+ messagesDiv.appendChild(messageEl);
46
+
47
+ // 添加撤回按钮事件
48
+ if (canRecall) {
49
+ const recallBtn = messageEl.querySelector('.btn-recall');
50
+ recallBtn.addEventListener('click', () => this.recallMessage(msg._id, messageEl));
51
+ }
52
+
53
+ // 添加图片点击查看功能
54
+ const images = messageEl.querySelectorAll('.chat-image');
55
+ images.forEach(img => {
56
+ img.style.cursor = 'pointer';
57
+ img.addEventListener('click', () => {
58
+ this.showImagePreview(img.src, img.alt);
59
+ });
60
+ });
61
+
62
+ // 存储消息时间戳
63
+ this.messageTimestamps.set(msg._id, new Date(msg.timestamp));
64
+
65
+ return messageEl;
66
+ }
67
+
68
+ /**
69
+ * 显示图片预览
70
+ */
71
+ showImagePreview(src, alt) {
72
+ // 创建预览模态框
73
+ const modal = document.createElement('div');
74
+ modal.className = 'image-preview-modal';
75
+ modal.innerHTML = `
76
+ <div class="image-preview-backdrop"></div>
77
+ <div class="image-preview-content">
78
+ <button class="image-preview-close">&times;</button>
79
+ <img src="${src}" alt="${alt}" class="image-preview-img">
80
+ <div class="image-preview-caption">${alt || '图片预览'}</div>
81
+ </div>
82
+ `;
83
+
84
+ document.body.appendChild(modal);
85
+
86
+ // 关闭按钮
87
+ const closeBtn = modal.querySelector('.image-preview-close');
88
+ const backdrop = modal.querySelector('.image-preview-backdrop');
89
+
90
+ const closeModal = () => {
91
+ modal.classList.add('closing');
92
+ setTimeout(() => modal.remove(), 300);
93
+ };
94
+
95
+ closeBtn.addEventListener('click', closeModal);
96
+ backdrop.addEventListener('click', closeModal);
97
+
98
+ // ESC键关闭
99
+ const handleEsc = (e) => {
100
+ if (e.key === 'Escape') {
101
+ closeModal();
102
+ document.removeEventListener('keydown', handleEsc);
103
+ }
104
+ };
105
+ document.addEventListener('keydown', handleEsc);
106
+
107
+ // 添加显示动画
108
+ setTimeout(() => modal.classList.add('show'), 10);
109
+ }
110
+
111
+ /**
112
+ * 检查消息是否可以撤回(2分钟内)
113
+ */
114
+ canRecallMessage(timestamp) {
115
+ const now = Date.now();
116
+ const messageTime = new Date(timestamp).getTime();
117
+ const twoMinutes = 2 * 60 * 1000;
118
+ return (now - messageTime) < twoMinutes;
119
+ }
120
+
121
+ /**
122
+ * 撤回消息
123
+ */
124
+ async recallMessage(messageId, messageEl) {
125
+ try {
126
+ await this.apiService.recallMessage(this.currentGroupId, messageId);
127
+
128
+ // 更新UI
129
+ const contentEl = messageEl.querySelector('.message-content');
130
+ contentEl.textContent = '[消息已撤回]';
131
+ contentEl.classList.add('recalled');
132
+
133
+ // 移除撤回按钮
134
+ const recallBtn = messageEl.querySelector('.btn-recall');
135
+ if (recallBtn) recallBtn.remove();
136
+
137
+ // 通过WebSocket通知其他用户
138
+ this.wsService.recallMessage(messageId);
139
+ } catch (error) {
140
+ alert('撤回失败: ' + error.message);
141
+ }
142
+ }
143
+
144
+ /**
145
+ * 处理@提及功能
146
+ */
147
+ setupMentionInput(inputEl, groupMembers) {
148
+ this.mentionSuggestions = groupMembers;
149
+ let suggestionBox = null;
150
+
151
+ inputEl.addEventListener('input', (e) => {
152
+ const value = e.target.value;
153
+ const cursorPos = e.target.selectionStart;
154
+
155
+ // 检查是否输入了@
156
+ const textBeforeCursor = value.substring(0, cursorPos);
157
+ const atMatch = textBeforeCursor.match(/@(\w*)$/);
158
+
159
+ if (atMatch) {
160
+ const query = atMatch[1].toLowerCase();
161
+ const matches = this.mentionSuggestions.filter(member =>
162
+ member.username.toLowerCase().includes(query) &&
163
+ member._id !== this.currentUserId
164
+ );
165
+
166
+ if (matches.length > 0) {
167
+ this.showMentionSuggestions(inputEl, matches, (selected) => {
168
+ // 替换@文本为完整的用户名
169
+ const beforeAt = textBeforeCursor.substring(0, atMatch.index);
170
+ const afterCursor = value.substring(cursorPos);
171
+ inputEl.value = `${beforeAt}@${selected.username} ${afterCursor}`;
172
+ inputEl.focus();
173
+
174
+ // 移动光标到用户名后
175
+ const newPos = beforeAt.length + selected.username.length + 2;
176
+ inputEl.setSelectionRange(newPos, newPos);
177
+
178
+ this.hideMentionSuggestions();
179
+ });
180
+ } else {
181
+ this.hideMentionSuggestions();
182
+ }
183
+ } else {
184
+ this.hideMentionSuggestions();
185
+ }
186
+ });
187
+
188
+ // 点击外部关闭建议框
189
+ document.addEventListener('click', (e) => {
190
+ if (!inputEl.contains(e.target) && suggestionBox && !suggestionBox.contains(e.target)) {
191
+ this.hideMentionSuggestions();
192
+ }
193
+ });
194
+ }
195
+
196
+ /**
197
+ * 显示@提及建议框
198
+ */
199
+ showMentionSuggestions(inputEl, matches, onSelect) {
200
+ this.hideMentionSuggestions();
201
+
202
+ const suggestionBox = document.createElement('div');
203
+ suggestionBox.className = 'mention-suggestions';
204
+ suggestionBox.id = 'mentionSuggestions';
205
+
206
+ matches.forEach(member => {
207
+ const item = document.createElement('div');
208
+ item.className = 'mention-item';
209
+ item.innerHTML = `
210
+ <div class="avatar" style="width: 30px; height: 30px; font-size: 14px;">
211
+ ${member.username[0].toUpperCase()}
212
+ </div>
213
+ <span>${member.username}</span>
214
+ `;
215
+ item.addEventListener('click', () => onSelect(member));
216
+ suggestionBox.appendChild(item);
217
+ });
218
+
219
+ // 定位建议框
220
+ const rect = inputEl.getBoundingClientRect();
221
+ suggestionBox.style.position = 'absolute';
222
+ suggestionBox.style.bottom = (window.innerHeight - rect.top + 5) + 'px';
223
+ suggestionBox.style.left = rect.left + 'px';
224
+ suggestionBox.style.width = rect.width + 'px';
225
+
226
+ document.body.appendChild(suggestionBox);
227
+ }
228
+
229
+ /**
230
+ * 隐藏@提及建议框
231
+ */
232
+ hideMentionSuggestions() {
233
+ const suggestionBox = document.getElementById('mentionSuggestions');
234
+ if (suggestionBox) {
235
+ suggestionBox.remove();
236
+ }
237
+ }
238
+
239
+ /**
240
+ * 高亮@提及
241
+ */
242
+ highlightMentions(content, groupMembers) {
243
+ let highlighted = content;
244
+
245
+ groupMembers.forEach(member => {
246
+ const regex = new RegExp(`@${member.username}\\b`, 'g');
247
+ highlighted = highlighted.replace(regex, `<span class="mention">@${member.username}</span>`);
248
+ });
249
+
250
+ return highlighted;
251
+ }
252
+
253
+ /**
254
+ * 检查消息是否@了当前用户
255
+ */
256
+ checkMentionsMe(content, groupMembers) {
257
+ const currentUser = groupMembers.find(m => m._id === this.currentUserId);
258
+ if (!currentUser) return false;
259
+
260
+ const regex = new RegExp(`@${currentUser.username}\\b`);
261
+ return regex.test(content);
262
+ }
263
+
264
+ /**
265
+ * 获取消息已读状态
266
+ */
267
+ getReadStatus(msg) {
268
+ if (!msg.readBy || msg.readBy.length === 0) {
269
+ return '<span class="read-status unread">未读</span>';
270
+ }
271
+
272
+ const readCount = msg.readBy.length;
273
+ return `<span class="read-status read">已读 ${readCount}</span>`;
274
+ }
275
+
276
+ /**
277
+ * 标记消息为已读
278
+ */
279
+ async markAsRead(messageId) {
280
+ try {
281
+ await this.apiService.markMessageAsRead(this.currentGroupId, messageId);
282
+ this.wsService.markMessageAsRead(messageId);
283
+ } catch (error) {
284
+ console.error('标记已读失败:', error);
285
+ }
286
+ }
287
+
288
+ /**
289
+ * 批量标记消息为已读
290
+ */
291
+ async markAllAsRead(messageIds) {
292
+ try {
293
+ await this.apiService.markMessagesAsRead(this.currentGroupId, messageIds);
294
+ } catch (error) {
295
+ console.error('批量标记失败:', error);
296
+ }
297
+ }
298
+
299
+ /**
300
+ * 获取未读消息数量
301
+ */
302
+ async getUnreadCount() {
303
+ try {
304
+ const result = await this.apiService.getUnreadCount(this.currentGroupId);
305
+ return result.unreadCount;
306
+ } catch (error) {
307
+ console.error('获取未读数量失败:', error);
308
+ return 0;
309
+ }
310
+ }
311
+
312
+ /**
313
+ * 监听消息撤回事件
314
+ */
315
+ onMessageRecalled(callback) {
316
+ this.wsService.on('message_recalled', (data) => {
317
+ callback(data);
318
+ });
319
+ }
320
+
321
+ /**
322
+ * 监听已读回执
323
+ */
324
+ onMessageRead(callback) {
325
+ this.wsService.on('message_read_receipt', (data) => {
326
+ callback(data);
327
+ });
328
+ }
329
+
330
+ /**
331
+ * 设置当前群组ID
332
+ */
333
+ setCurrentGroup(groupId) {
334
+ this.currentGroupId = groupId;
335
+ }
336
+
337
+ /**
338
+ * 显示未读消息徽章
339
+ */
340
+ async showUnreadBadge(badgeEl) {
341
+ const count = await this.getUnreadCount();
342
+ if (count > 0) {
343
+ badgeEl.textContent = count > 99 ? '99+' : count;
344
+ badgeEl.style.display = 'inline-block';
345
+ } else {
346
+ badgeEl.style.display = 'none';
347
+ }
348
+ }
349
+
350
+ /**
351
+ * 解析消息中的@提及用户ID
352
+ */
353
+ parseMentions(content, groupMembers) {
354
+ const mentions = [];
355
+
356
+ groupMembers.forEach(member => {
357
+ const regex = new RegExp(`@${member.username}\\b`, 'g');
358
+ if (regex.test(content)) {
359
+ mentions.push(member._id);
360
+ }
361
+ });
362
+
363
+ return mentions;
364
+ }
365
+
366
+ /**
367
+ * 清理资源
368
+ */
369
+ cleanup() {
370
+ this.messageTimestamps.clear();
371
+ this.hideMentionSuggestions();
372
+ }
373
+ }
374
+
375
+ /**
376
+ * 在线状态管理
377
+ */
378
+ export class OnlineStatusManager {
379
+ constructor(wsService) {
380
+ this.wsService = wsService;
381
+ this.onlineUsers = new Set();
382
+ }
383
+
384
+ /**
385
+ * 更新用户在线状态
386
+ */
387
+ updateUserStatus(userId, isOnline) {
388
+ if (isOnline) {
389
+ this.onlineUsers.add(userId);
390
+ } else {
391
+ this.onlineUsers.delete(userId);
392
+ }
393
+
394
+ // 更新UI中的在线状态指示器
395
+ this.updateStatusIndicator(userId, isOnline);
396
+ }
397
+
398
+ /**
399
+ * 更新状态指示器
400
+ */
401
+ updateStatusIndicator(userId, isOnline) {
402
+ const indicators = document.querySelectorAll(`[data-user-id="${userId}"] .online-indicator`);
403
+ indicators.forEach(indicator => {
404
+ indicator.className = `online-indicator ${isOnline ? 'online' : 'offline'}`;
405
+ indicator.title = isOnline ? '在线' : '离线';
406
+ });
407
+ }
408
+
409
+ /**
410
+ * 检查用户是否在线
411
+ */
412
+ isUserOnline(userId) {
413
+ return this.onlineUsers.has(userId);
414
+ }
415
+
416
+ /**
417
+ * 监听在线状态变化
418
+ */
419
+ setupListeners() {
420
+ this.wsService.on('user_online', (data) => {
421
+ this.updateUserStatus(data.userId, true);
422
+ });
423
+
424
+ this.wsService.on('user_offline', (data) => {
425
+ this.updateUserStatus(data.userId, false);
426
+ });
427
+ }
428
+
429
+ /**
430
+ * 渲染在线状态指示器
431
+ */
432
+ renderStatusIndicator(userId) {
433
+ const isOnline = this.isUserOnline(userId);
434
+ return `<span class="online-indicator ${isOnline ? 'online' : 'offline'}" title="${isOnline ? '在线' : '离线'}"></span>`;
435
+ }
436
+ }
437
+
438
+ /**
439
+ * 消息通知管理
440
+ */
441
+ export class MessageNotificationManager {
442
+ constructor() {
443
+ this.requestPermission();
444
+ }
445
+
446
+ /**
447
+ * 请求通知权限
448
+ */
449
+ async requestPermission() {
450
+ if ('Notification' in window && Notification.permission === 'default') {
451
+ await Notification.requestPermission();
452
+ }
453
+ }
454
+
455
+ /**
456
+ * 显示桌面通知
457
+ */
458
+ showNotification(title, body, options = {}) {
459
+ if ('Notification' in window && Notification.permission === 'granted') {
460
+ const notification = new Notification(title, {
461
+ body: body,
462
+ icon: options.icon || '/icon.png',
463
+ badge: '/icon.png',
464
+ tag: options.tag || 'chat-message',
465
+ requireInteraction: options.requireInteraction || false
466
+ });
467
+
468
+ notification.onclick = () => {
469
+ window.focus();
470
+ if (options.onClick) {
471
+ options.onClick();
472
+ }
473
+ notification.close();
474
+ };
475
+
476
+ // 自动关闭
477
+ setTimeout(() => notification.close(), options.duration || 5000);
478
+ }
479
+ }
480
+
481
+ /**
482
+ * 显示@提及通知
483
+ */
484
+ showMentionNotification(username, message) {
485
+ this.showNotification(
486
+ `${username} 提到了你`,
487
+ message,
488
+ {
489
+ tag: 'mention',
490
+ requireInteraction: true
491
+ }
492
+ );
493
+ }
494
+
495
+ /**
496
+ * 显示新消息通知
497
+ */
498
+ showNewMessageNotification(username, message) {
499
+ this.showNotification(
500
+ `${username} 发来新消息`,
501
+ message,
502
+ {
503
+ tag: 'new-message'
504
+ }
505
+ );
506
+ }
507
+ }
508
+
509
+