claude-code-templates 1.24.17 → 1.25.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.
@@ -704,31 +704,82 @@
704
704
  box-shadow: 0 0 8px rgba(255, 170, 0, 0.6);
705
705
  }
706
706
 
707
- /* Search toggle button in header */
708
- .search-toggle-btn {
709
- background: var(--bg-tertiary);
710
- border: 1px solid var(--border-primary);
711
- color: var(--terminal-orange);
712
- padding: 6px 12px;
713
- border-radius: 6px;
707
+ /* Action buttons group - unified design with orange accent */
708
+ .action-buttons-group {
709
+ display: inline-flex;
710
+ border-radius: 8px;
711
+ overflow: hidden;
712
+ border: 2px solid rgba(255, 170, 0, 0.4);
713
+ background: rgba(255, 170, 0, 0.08);
714
+ box-shadow: 0 2px 8px rgba(255, 170, 0, 0.15);
715
+ }
716
+
717
+ .action-btn {
718
+ background: transparent;
719
+ border: none;
720
+ border-right: 1px solid rgba(255, 170, 0, 0.2);
721
+ color: var(--text-primary);
722
+ padding: 10px 18px;
714
723
  cursor: pointer;
715
724
  font-size: 0.875rem;
725
+ font-weight: 600;
716
726
  transition: all 0.2s ease;
717
727
  display: flex;
718
728
  align-items: center;
719
- gap: 6px;
729
+ gap: 8px;
730
+ white-space: nowrap;
720
731
  }
721
732
 
722
- .search-toggle-btn:hover {
723
- background: var(--terminal-orange);
724
- color: white;
725
- border-color: var(--terminal-orange);
733
+ .action-btn:last-child {
734
+ border-right: none;
726
735
  }
727
736
 
728
- .search-toggle-btn.active {
729
- background: var(--terminal-orange);
730
- color: white;
731
- border-color: var(--terminal-orange);
737
+ .action-btn svg {
738
+ width: 15px;
739
+ height: 15px;
740
+ transition: all 0.2s ease;
741
+ stroke: var(--text-primary);
742
+ }
743
+
744
+ .action-btn:hover {
745
+ background: rgba(255, 170, 0, 0.12);
746
+ transform: translateY(-1px);
747
+ }
748
+
749
+ .action-btn.resume-btn:hover {
750
+ background: rgba(63, 185, 80, 0.15);
751
+ color: rgba(63, 185, 80, 1);
752
+ }
753
+
754
+ .action-btn.resume-btn:hover svg {
755
+ stroke: rgba(63, 185, 80, 1);
756
+ }
757
+
758
+ .action-btn.share-btn:hover {
759
+ background: rgba(59, 130, 246, 0.15);
760
+ color: rgba(59, 130, 246, 1);
761
+ }
762
+
763
+ .action-btn.share-btn:hover svg {
764
+ stroke: rgba(59, 130, 246, 1);
765
+ }
766
+
767
+ .action-btn.search-btn:hover {
768
+ background: rgba(251, 146, 60, 0.15);
769
+ color: rgba(251, 146, 60, 1);
770
+ }
771
+
772
+ .action-btn.search-btn:hover svg {
773
+ stroke: rgba(251, 146, 60, 1);
774
+ }
775
+
776
+ .action-btn.search-btn.active {
777
+ background: rgba(251, 146, 60, 0.2);
778
+ color: rgba(251, 146, 60, 1);
779
+ }
780
+
781
+ .action-btn.search-btn.active svg {
782
+ stroke: rgba(251, 146, 60, 1);
732
783
  }
733
784
 
734
785
  /* Conversations list */
@@ -816,6 +867,88 @@
816
867
  margin-top: 4px;
817
868
  }
818
869
 
870
+ /* Project grouping styles */
871
+ .project-group {
872
+ border-bottom: 1px solid var(--border-secondary);
873
+ }
874
+
875
+ .project-header {
876
+ display: flex;
877
+ align-items: center;
878
+ padding: 16px 20px;
879
+ background: var(--bg-secondary);
880
+ border-bottom: 1px solid var(--border-primary);
881
+ cursor: pointer;
882
+ transition: background-color 0.2s ease;
883
+ position: sticky;
884
+ top: 0;
885
+ z-index: 10;
886
+ }
887
+
888
+ .project-header:hover {
889
+ background: var(--bg-tertiary);
890
+ }
891
+
892
+ .project-avatar {
893
+ width: 44px;
894
+ height: 44px;
895
+ border-radius: 8px;
896
+ background: linear-gradient(135deg, var(--terminal-orange) 0%, #e67e22 100%);
897
+ display: flex;
898
+ align-items: center;
899
+ justify-content: center;
900
+ font-size: 1.2rem;
901
+ margin-right: 12px;
902
+ flex-shrink: 0;
903
+ color: var(--bg-primary);
904
+ font-weight: bold;
905
+ }
906
+
907
+ .project-info {
908
+ flex: 1;
909
+ min-width: 0;
910
+ }
911
+
912
+ .project-name {
913
+ font-weight: 600;
914
+ color: var(--text-primary);
915
+ font-size: 1rem;
916
+ margin-bottom: 2px;
917
+ }
918
+
919
+ .project-count {
920
+ color: var(--text-secondary);
921
+ font-size: 0.85rem;
922
+ }
923
+
924
+ .project-toggle {
925
+ margin-left: 8px;
926
+ color: var(--text-secondary);
927
+ transition: transform 0.2s ease;
928
+ }
929
+
930
+ .toggle-icon {
931
+ transition: transform 0.2s ease;
932
+ }
933
+
934
+ .toggle-icon.expanded {
935
+ transform: rotate(180deg);
936
+ }
937
+
938
+ /* Smaller avatar for conversations within groups */
939
+ .conversation-avatar-small {
940
+ width: 36px;
941
+ height: 36px;
942
+ font-size: 0.85rem;
943
+ background: var(--bg-tertiary);
944
+ color: var(--text-primary);
945
+ }
946
+
947
+ /* Indent conversations within project groups */
948
+ .project-group .conversation-item {
949
+ padding-left: 36px;
950
+ }
951
+
819
952
  .conversation-state {
820
953
  font-size: 0.7rem;
821
954
  padding: 3px 8px;
@@ -1353,13 +1486,16 @@
1353
1486
  align-items: center;
1354
1487
  justify-content: center;
1355
1488
  flex: 0 0 auto;
1489
+ gap: 8px;
1356
1490
  }
1357
1491
 
1492
+
1358
1493
  .header-right {
1359
1494
  display: flex;
1360
1495
  align-items: center;
1361
1496
  justify-content: flex-end;
1362
1497
  flex: 1;
1498
+ gap: 12px;
1363
1499
  }
1364
1500
 
1365
1501
  .chat-view-back {
@@ -1904,20 +2040,36 @@
1904
2040
 
1905
2041
  </button>
1906
2042
  <div class="chat-view-info">
1907
- <h2 class="chat-view-title" id="chatViewTitle">Select a conversation</h2>
2043
+ <h2 class="chat-view-title" id="chatViewTitle">Select a session</h2>
1908
2044
  <p class="chat-view-subtitle" id="chatViewSubtitle"></p>
1909
2045
  </div>
1910
2046
  </div>
1911
2047
  <div class="header-center">
1912
- <button class="header-btn resume-btn" id="resumeConversation" style="display: none;" onclick="resumeConversationWithClaude()">
1913
- ▶️ Resume
1914
- </button>
2048
+ <div class="action-buttons-group" style="display: none;" id="actionButtonsGroup">
2049
+ <button class="action-btn resume-btn" id="resumeConversation" onclick="resumeConversationWithClaude()">
2050
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2051
+ <polygon points="5 3 19 12 5 21 5 3"></polygon>
2052
+ </svg>
2053
+ <span>Resume</span>
2054
+ </button>
2055
+ <button class="action-btn share-btn" id="shareConversation" onclick="shareConversation()">
2056
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2057
+ <path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"></path>
2058
+ <polyline points="16 6 12 2 8 6"></polyline>
2059
+ <line x1="12" y1="2" x2="12" y2="15"></line>
2060
+ </svg>
2061
+ <span>Share</span>
2062
+ </button>
2063
+ <button class="action-btn search-btn" id="chatSearchToggle">
2064
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2065
+ <circle cx="11" cy="11" r="8"></circle>
2066
+ <path d="m21 21-4.35-4.35"></path>
2067
+ </svg>
2068
+ <span>Search</span>
2069
+ </button>
2070
+ </div>
1915
2071
  </div>
1916
2072
  <div class="header-right">
1917
- <button class="search-toggle-btn" id="chatSearchToggle">
1918
- <span>🔍</span>
1919
- <span>Search</span>
1920
- </button>
1921
2073
  <div class="tools-toggle" id="toolsToggle">
1922
2074
  <span class="tools-toggle-label" onclick="document.getElementById('showToolsSwitch').click()">Show Tools</span>
1923
2075
  <label class="toggle-switch">
@@ -1991,6 +2143,106 @@
1991
2143
  </div>
1992
2144
  </div>
1993
2145
 
2146
+ <!-- Confirm Share Modal (Security Warning) -->
2147
+ <div class="modal-overlay" id="confirmShareModal">
2148
+ <div class="modal">
2149
+ <div class="modal-header" style="background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);">
2150
+ <span class="modal-icon">📤</span>
2151
+ <h3 class="modal-title">Share Your Session</h3>
2152
+ </div>
2153
+ <div style="padding: 20px;">
2154
+ <p class="modal-description" style="margin-bottom: 16px; line-height: 1.5;">
2155
+ Your conversation will be uploaded to <strong>x0.at</strong>, a temporary file hosting service.
2156
+ The generated link can be shared with others to clone your session.
2157
+ </p>
2158
+
2159
+ <div style="background: rgba(59, 130, 246, 0.08); border-left: 3px solid #3b82f6; padding: 12px; border-radius: 6px; margin-bottom: 16px;">
2160
+ <h4 style="color: #3b82f6; margin: 0 0 8px 0; font-size: 14px;">📋 What you're sharing</h4>
2161
+ <p style="margin: 0; font-size: 13px; color: var(--text-secondary); line-height: 1.4;">
2162
+ Last <strong>100 messages</strong> from this conversation (or all messages if less than 100)
2163
+ </p>
2164
+ </div>
2165
+
2166
+ <div style="background: rgba(234, 179, 8, 0.08); border-left: 3px solid #eab308; padding: 12px; border-radius: 6px; margin-bottom: 16px;">
2167
+ <h4 style="color: #eab308; margin: 0 0 8px 0; font-size: 14px;">ℹ️ About x0.at</h4>
2168
+ <ul style="margin: 0; padding-left: 20px; font-size: 13px; color: var(--text-secondary); line-height: 1.5;">
2169
+ <li style="margin-bottom: 4px;">Simple temporary file hosting (open source)</li>
2170
+ <li style="margin-bottom: 4px;">Files available for 3-100 days depending on size</li>
2171
+ <li style="margin-bottom: 4px;">Anyone with the link can download your session</li>
2172
+ <li style="margin-bottom: 4px;">No encryption - avoid sharing sensitive data</li>
2173
+ </ul>
2174
+ </div>
2175
+
2176
+ <div style="background: rgba(100, 116, 139, 0.08); border-left: 3px solid #64748b; padding: 12px; border-radius: 6px;">
2177
+ <p style="margin: 0; font-size: 12px; color: var(--text-secondary); line-height: 1.4;">
2178
+ 💡 <strong>Tip:</strong> Only share links with people you trust. The session includes your conversation history and may contain project-specific information.
2179
+ </p>
2180
+ </div>
2181
+ </div>
2182
+
2183
+ <div class="modal-actions">
2184
+ <button class="modal-btn secondary" onclick="closeConfirmShareModal()">Cancel</button>
2185
+ <button class="modal-btn primary" onclick="proceedWithShare()" style="background: #3b82f6;">
2186
+ Continue & Upload
2187
+ </button>
2188
+ </div>
2189
+ </div>
2190
+ </div>
2191
+
2192
+ <!-- Share Modal -->
2193
+ <div class="modal-overlay" id="shareModal">
2194
+ <div class="modal">
2195
+ <div class="modal-header">
2196
+ <span class="modal-icon">📤</span>
2197
+ <h3 class="modal-title">Share Conversation</h3>
2198
+ </div>
2199
+ <div id="shareModalLoading" style="text-align: center; padding: 40px; display: none;">
2200
+ <div class="loading-spinner"></div>
2201
+ <p style="margin-top: 16px; color: var(--text-secondary);">Uploading session...</p>
2202
+ </div>
2203
+ <div id="shareModalContent" style="display: none;">
2204
+ <p class="modal-description">
2205
+ Your conversation has been uploaded to x0.at. Share the command or QR code below with others.
2206
+ </p>
2207
+ <div id="shareMessageInfo" style="display: none; padding: 12px; background: var(--bg-tertiary); border-radius: 6px; margin-bottom: 16px;">
2208
+ <p style="font-size: 13px; color: var(--text-secondary); margin: 0;">
2209
+ <span id="shareMessageCount"></span>
2210
+ </p>
2211
+ </div>
2212
+ <div style="text-align: center; margin: 24px 0;">
2213
+ <div style="margin-bottom: 16px; display: flex; flex-direction: column; align-items: center;">
2214
+ <h4 style="margin-bottom: 12px; color: var(--text-primary);">📱 Scan QR Code</h4>
2215
+ <img id="shareQRCode" src="" alt="QR Code" style="max-width: 300px; border-radius: 8px; border: 2px solid var(--border-color); display: block; margin: 0 auto;">
2216
+ <p style="font-size: 12px; color: var(--text-secondary); margin-top: 12px; margin-bottom: 0;">
2217
+ Scan this QR code to get the share command
2218
+ </p>
2219
+ </div>
2220
+ </div>
2221
+ <div>
2222
+ <h4 style="margin-bottom: 8px; color: var(--text-primary);">📋 Share Command</h4>
2223
+ <div class="modal-command" id="shareModalCommand">
2224
+ <!-- Command will be inserted here -->
2225
+ </div>
2226
+ </div>
2227
+ <div style="margin-top: 16px; padding: 12px; background: var(--bg-tertiary); border-radius: 6px;">
2228
+ <p style="font-size: 12px; color: var(--text-secondary); margin: 0;">
2229
+ 🔗 Direct URL: <a id="shareDirectUrl" href="#" target="_blank" style="color: var(--text-accent); word-break: break-all;"></a>
2230
+ </p>
2231
+ <p style="font-size: 12px; color: var(--text-warning); margin: 8px 0 0 0;">
2232
+ ⚠️ Files kept for 3-100 days (based on size)
2233
+ </p>
2234
+ <p style="font-size: 12px; color: var(--text-secondary); margin: 4px 0 0 0;">
2235
+ 🔓 Files are not encrypted by default
2236
+ </p>
2237
+ </div>
2238
+ </div>
2239
+ <div class="modal-actions">
2240
+ <button class="modal-btn secondary" onclick="closeShareModal()">Close</button>
2241
+ <button class="modal-btn primary" id="copyShareCommandBtn" onclick="copyShareCommand()" style="display: none;">Copy Command</button>
2242
+ </div>
2243
+ </div>
2244
+ </div>
2245
+
1994
2246
  <!-- Import WebSocket and Data Services -->
1995
2247
  <script src="services/WebSocketService.js"></script>
1996
2248
  <script src="services/DataService.js"></script>
@@ -2239,56 +2491,101 @@
2239
2491
 
2240
2492
  renderConversations(conversations, states = {}) {
2241
2493
  const conversationsList = document.getElementById('conversationsList');
2242
-
2494
+
2243
2495
  if (conversations.length === 0) {
2244
2496
  conversationsList.innerHTML = `
2245
2497
  <div class="no-conversations">
2246
2498
  <div class="no-conversations-icon">💬</div>
2247
- <h3>No conversations found</h3>
2248
- <p>Start a conversation with Claude Code to see it here</p>
2499
+ <h3>No sessions found</h3>
2500
+ <p>Start a session with Claude Code to see it here</p>
2249
2501
  </div>
2250
2502
  `;
2251
2503
  return;
2252
2504
  }
2253
2505
 
2254
- conversationsList.innerHTML = conversations.map(conv => {
2255
- const state = states[conv.id] || 'inactive';
2256
- const stateClass = this.getStateClass(state);
2257
- const stateLabel = this.getStateLabel(state);
2258
-
2259
- // Debug logging for first few conversations
2260
- console.log(`🔍 Conversation ${conv.id.slice(-8)}: State="${state}" -> Label="${stateLabel}" Class="${stateClass}"`);
2261
-
2262
- const lastActivity = this.formatRelativeTime(new Date(conv.lastModified));
2263
- const messageCount = conv.messageCount || 0;
2506
+ // Group conversations by project
2507
+ const groupedByProject = conversations.reduce((groups, conv) => {
2264
2508
  const projectName = conv.project || 'Unknown Project';
2265
- const conversationId = conv.id.slice(-8);
2266
-
2267
- // Get first letter of project name for avatar
2509
+ if (!groups[projectName]) {
2510
+ groups[projectName] = [];
2511
+ }
2512
+ groups[projectName].push(conv);
2513
+ return groups;
2514
+ }, {});
2515
+
2516
+ // Sort projects alphabetically
2517
+ const sortedProjects = Object.keys(groupedByProject).sort();
2518
+
2519
+ // Initialize expanded state if not exists
2520
+ if (!this.expandedProjects) {
2521
+ this.expandedProjects = new Set(); // All collapsed by default
2522
+ }
2523
+
2524
+ // Render grouped conversations
2525
+ conversationsList.innerHTML = sortedProjects.map(projectName => {
2526
+ const projectConversations = groupedByProject[projectName];
2527
+ const isExpanded = this.expandedProjects.has(projectName);
2268
2528
  const firstLetter = projectName.charAt(0).toUpperCase();
2269
-
2270
- return `
2271
- <div class="conversation-item" data-conversation-id="${conv.id}">
2272
- <div class="conversation-avatar">
2273
- ${firstLetter}
2529
+ const conversationCount = projectConversations.length;
2530
+
2531
+ // Render conversations for this project
2532
+ const conversationsHTML = projectConversations.map(conv => {
2533
+ const state = states[conv.id] || 'inactive';
2534
+ const stateClass = this.getStateClass(state);
2535
+ const stateLabel = this.getStateLabel(state);
2536
+
2537
+ const lastActivity = this.formatRelativeTime(new Date(conv.lastModified));
2538
+ const messageCount = conv.messageCount || 0;
2539
+ const conversationId = conv.id.slice(-8);
2540
+
2541
+ return `
2542
+ <div class="conversation-item" data-conversation-id="${conv.id}" style="display: ${isExpanded ? 'flex' : 'none'}">
2543
+ <div class="conversation-avatar conversation-avatar-small">
2544
+ ${conversationId.substring(0, 2).toUpperCase()}
2545
+ </div>
2546
+ <div class="conversation-content">
2547
+ <div class="conversation-header">
2548
+ <div class="conversation-name">Session ${conversationId}</div>
2549
+ <div class="conversation-time">${lastActivity}</div>
2550
+ </div>
2551
+ <div class="conversation-meta">
2552
+ <span class="conversation-state ${stateClass}">${stateLabel}</span>
2553
+ ${messageCount > 0 ? `<span class="message-count">${messageCount}</span>` : ''}
2554
+ </div>
2555
+ </div>
2274
2556
  </div>
2275
- <div class="conversation-content">
2276
- <div class="conversation-header">
2277
- <div class="conversation-name">${projectName}</div>
2278
- <div class="conversation-time">${lastActivity}</div>
2557
+ `;
2558
+ }).join('');
2559
+
2560
+ return `
2561
+ <div class="project-group" data-project="${projectName}">
2562
+ <div class="project-header" data-project="${projectName}">
2563
+ <div class="project-avatar">
2564
+ ${firstLetter}
2279
2565
  </div>
2280
- <div class="conversation-preview">
2281
- Conversation ${conversationId}
2566
+ <div class="project-info">
2567
+ <div class="project-name">${projectName}</div>
2568
+ <div class="project-count">${conversationCount} session${conversationCount !== 1 ? 's' : ''}</div>
2282
2569
  </div>
2283
- <div class="conversation-meta">
2284
- <span class="conversation-state ${stateClass}">${stateLabel}</span>
2285
- ${messageCount > 0 ? `<span class="message-count">${messageCount}</span>` : ''}
2570
+ <div class="project-toggle">
2571
+ <svg class="toggle-icon ${isExpanded ? 'expanded' : ''}" width="20" height="20" viewBox="0 0 20 20" fill="currentColor">
2572
+ <path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd"/>
2573
+ </svg>
2286
2574
  </div>
2287
2575
  </div>
2576
+ ${conversationsHTML}
2288
2577
  </div>
2289
2578
  `;
2290
2579
  }).join('');
2291
2580
 
2581
+ // Bind project toggle events
2582
+ conversationsList.querySelectorAll('.project-header').forEach(header => {
2583
+ header.addEventListener('click', (e) => {
2584
+ const projectName = header.dataset.project;
2585
+ this.toggleProject(projectName);
2586
+ });
2587
+ });
2588
+
2292
2589
  // Bind conversation click events
2293
2590
  conversationsList.querySelectorAll('.conversation-item').forEach(item => {
2294
2591
  item.addEventListener('click', () => {
@@ -2298,6 +2595,17 @@
2298
2595
  });
2299
2596
  }
2300
2597
 
2598
+ toggleProject(projectName) {
2599
+ if (this.expandedProjects.has(projectName)) {
2600
+ this.expandedProjects.delete(projectName);
2601
+ } else {
2602
+ this.expandedProjects.add(projectName);
2603
+ }
2604
+
2605
+ // Re-render conversations with current states
2606
+ this.renderConversations(this.conversations, this.conversationStates);
2607
+ }
2608
+
2301
2609
  selectConversation(conversationId) {
2302
2610
  this.selectedConversationId = conversationId;
2303
2611
 
@@ -2322,7 +2630,7 @@
2322
2630
  const projectName = conversation.project || 'Unknown Project';
2323
2631
  const convId = conversation.id.slice(-8);
2324
2632
  chatViewTitle.textContent = projectName;
2325
- chatViewSubtitle.textContent = `Conversation ${convId}`;
2633
+ chatViewSubtitle.textContent = `Session ${convId}`;
2326
2634
 
2327
2635
  // Show chat view with animation
2328
2636
  chatView.classList.add('active');
@@ -2334,10 +2642,14 @@
2334
2642
  chatView.classList.remove('show-tools');
2335
2643
  }
2336
2644
 
2337
- // Show resume button
2645
+ // Show action buttons group
2646
+ const actionButtonsGroup = document.getElementById('actionButtonsGroup');
2647
+ actionButtonsGroup.style.display = 'inline-flex';
2648
+
2338
2649
  const resumeBtn = document.getElementById('resumeConversation');
2339
- resumeBtn.style.display = 'block';
2650
+ const shareBtn = document.getElementById('shareConversation');
2340
2651
  resumeBtn.setAttribute('data-conversation-id', conversationId);
2652
+ shareBtn.setAttribute('data-conversation-id', conversationId);
2341
2653
 
2342
2654
  // Load messages (placeholder for now)
2343
2655
  this.loadChatMessages(conversationId);
@@ -2352,9 +2664,9 @@
2352
2664
  // Clean up scroll tracking when leaving conversation
2353
2665
  this.removeScrollTracking();
2354
2666
 
2355
- // Hide resume button
2356
- const resumeBtn = document.getElementById('resumeConversation');
2357
- resumeBtn.style.display = 'none';
2667
+ // Hide action buttons group
2668
+ const actionButtonsGroup = document.getElementById('actionButtonsGroup');
2669
+ actionButtonsGroup.style.display = 'none';
2358
2670
 
2359
2671
  // Remove active state from conversations
2360
2672
  document.querySelectorAll('.conversation-item').forEach(item => {
@@ -3024,14 +3336,68 @@
3024
3336
 
3025
3337
 
3026
3338
  filterConversations(searchTerm) {
3027
- const items = document.querySelectorAll('.conversation-item');
3028
- const term = searchTerm.toLowerCase();
3339
+ const term = searchTerm.toLowerCase().trim();
3340
+ const projectGroups = document.querySelectorAll('.project-group');
3341
+
3342
+ if (!term) {
3343
+ // If search is empty, collapse all projects and reset
3344
+ this.expandedProjects.clear(); // Close all projects
3029
3345
 
3030
- items.forEach(item => {
3031
- const name = item.querySelector('.conversation-name').textContent.toLowerCase();
3032
- const preview = item.querySelector('.conversation-preview').textContent.toLowerCase();
3033
- const matches = name.includes(term) || preview.includes(term);
3034
- item.style.display = matches ? 'flex' : 'none';
3346
+ projectGroups.forEach(group => {
3347
+ group.style.display = 'block';
3348
+
3349
+ // Hide all conversations
3350
+ const conversations = group.querySelectorAll('.conversation-item');
3351
+ conversations.forEach(conv => {
3352
+ conv.style.display = 'none';
3353
+ });
3354
+
3355
+ // Update toggle icon to collapsed state
3356
+ const toggleIcon = group.querySelector('.toggle-icon');
3357
+ if (toggleIcon) {
3358
+ toggleIcon.classList.remove('expanded');
3359
+ }
3360
+ });
3361
+ return;
3362
+ }
3363
+
3364
+ // Filter projects and conversations
3365
+ projectGroups.forEach(group => {
3366
+ const projectName = group.dataset.project;
3367
+ const projectNameLower = projectName.toLowerCase();
3368
+ const conversations = group.querySelectorAll('.conversation-item');
3369
+
3370
+ let hasMatchingConversation = false;
3371
+
3372
+ // Check each conversation in this project
3373
+ conversations.forEach(item => {
3374
+ const nameElement = item.querySelector('.conversation-name');
3375
+ const name = nameElement ? nameElement.textContent.toLowerCase() : '';
3376
+ const matches = name.includes(term) || projectNameLower.includes(term);
3377
+
3378
+ if (matches) {
3379
+ item.style.display = 'flex';
3380
+ hasMatchingConversation = true;
3381
+ } else {
3382
+ item.style.display = 'none';
3383
+ }
3384
+ });
3385
+
3386
+ // Show project group if it has matching conversations or if project name matches
3387
+ if (hasMatchingConversation || projectNameLower.includes(term)) {
3388
+ group.style.display = 'block';
3389
+ // Auto-expand project when searching
3390
+ if (!this.expandedProjects.has(projectName)) {
3391
+ this.expandedProjects.add(projectName);
3392
+ // Update toggle icon
3393
+ const toggleIcon = group.querySelector('.toggle-icon');
3394
+ if (toggleIcon) {
3395
+ toggleIcon.classList.add('expanded');
3396
+ }
3397
+ }
3398
+ } else {
3399
+ group.style.display = 'none';
3400
+ }
3035
3401
  });
3036
3402
  }
3037
3403
 
@@ -3647,6 +4013,11 @@
3647
4013
  // Clear advanced search filters
3648
4014
  this.resetAdvancedSearch();
3649
4015
 
4016
+ // Reset expanded projects state (close all projects)
4017
+ if (this.expandedProjects) {
4018
+ this.expandedProjects.clear();
4019
+ }
4020
+
3650
4021
  // Hide search results info
3651
4022
  searchResultsInfo.classList.remove('active');
3652
4023
 
@@ -4351,6 +4722,199 @@
4351
4722
  }
4352
4723
  }
4353
4724
 
4725
+ // Share conversation functions
4726
+ function shareConversation() {
4727
+ const shareBtn = document.getElementById('shareConversation');
4728
+ const conversationId = shareBtn.getAttribute('data-conversation-id');
4729
+
4730
+ if (!conversationId) {
4731
+ console.error('No conversation ID found');
4732
+ return;
4733
+ }
4734
+
4735
+ console.log('📤 Opening share confirmation for conversation:', conversationId);
4736
+
4737
+ // Show confirmation modal first
4738
+ const confirmModal = document.getElementById('confirmShareModal');
4739
+ confirmModal.classList.add('show');
4740
+
4741
+ // Close modal when clicking outside
4742
+ confirmModal.addEventListener('click', (e) => {
4743
+ if (e.target === confirmModal) {
4744
+ closeConfirmShareModal();
4745
+ }
4746
+ });
4747
+ }
4748
+
4749
+ function closeConfirmShareModal() {
4750
+ const confirmModal = document.getElementById('confirmShareModal');
4751
+ confirmModal.classList.remove('show');
4752
+ }
4753
+
4754
+ async function proceedWithShare() {
4755
+ // Close confirmation modal
4756
+ closeConfirmShareModal();
4757
+
4758
+ const shareBtn = document.getElementById('shareConversation');
4759
+ const conversationId = shareBtn.getAttribute('data-conversation-id');
4760
+
4761
+ if (!conversationId) {
4762
+ console.error('No conversation ID found');
4763
+ return;
4764
+ }
4765
+
4766
+ console.log('📤 User confirmed - Proceeding with share for conversation:', conversationId);
4767
+
4768
+ // Show share modal with loading state
4769
+ const modal = document.getElementById('shareModal');
4770
+ const loadingDiv = document.getElementById('shareModalLoading');
4771
+ const contentDiv = document.getElementById('shareModalContent');
4772
+ const copyBtn = document.getElementById('copyShareCommandBtn');
4773
+
4774
+ modal.classList.add('show');
4775
+ loadingDiv.style.display = 'block';
4776
+ contentDiv.style.display = 'none';
4777
+ copyBtn.style.display = 'none';
4778
+
4779
+ // Close modal when clicking outside
4780
+ modal.addEventListener('click', (e) => {
4781
+ if (e.target === modal) {
4782
+ closeShareModal();
4783
+ }
4784
+ });
4785
+
4786
+ try {
4787
+ // Call API to share the conversation
4788
+ const response = await fetch(`/api/conversations/${conversationId}/share`, {
4789
+ method: 'POST',
4790
+ headers: {
4791
+ 'Content-Type': 'application/json'
4792
+ }
4793
+ });
4794
+
4795
+ if (!response.ok) {
4796
+ throw new Error(`Failed to share session: ${response.statusText}`);
4797
+ }
4798
+
4799
+ const data = await response.json();
4800
+
4801
+ console.log('✅ Session shared successfully:', data);
4802
+
4803
+ // Update modal with share data
4804
+ const qrCodeImg = document.getElementById('shareQRCode');
4805
+ const commandDiv = document.getElementById('shareModalCommand');
4806
+ const directUrlLink = document.getElementById('shareDirectUrl');
4807
+
4808
+ // Set QR code
4809
+ if (data.qrCode && data.qrCode.dataUrl) {
4810
+ qrCodeImg.src = data.qrCode.dataUrl;
4811
+ qrCodeImg.style.display = 'block';
4812
+ } else {
4813
+ qrCodeImg.style.display = 'none';
4814
+ }
4815
+
4816
+ // Set command
4817
+ commandDiv.textContent = data.shareCommand;
4818
+ commandDiv.setAttribute('data-command', data.shareCommand);
4819
+
4820
+ // Set direct URL
4821
+ directUrlLink.textContent = data.uploadUrl;
4822
+ directUrlLink.href = data.uploadUrl;
4823
+
4824
+ // Show message count information
4825
+ const messageInfoDiv = document.getElementById('shareMessageInfo');
4826
+ const messageCountSpan = document.getElementById('shareMessageCount');
4827
+
4828
+ if (data.wasLimited) {
4829
+ messageCountSpan.innerHTML = `⚠️ This session has <strong>${data.totalMessageCount}</strong> messages. Sharing last <strong>${data.messageCount}</strong> messages to keep file size manageable.`;
4830
+ messageInfoDiv.style.display = 'block';
4831
+ } else {
4832
+ messageCountSpan.innerHTML = `✅ Sharing <strong>${data.messageCount}</strong> messages from this conversation.`;
4833
+ messageInfoDiv.style.display = 'block';
4834
+ }
4835
+
4836
+ // Show content and hide loading
4837
+ loadingDiv.style.display = 'none';
4838
+ contentDiv.style.display = 'block';
4839
+ copyBtn.style.display = 'block';
4840
+
4841
+ } catch (error) {
4842
+ console.error('❌ Failed to share session:', error);
4843
+ alert(`Failed to share session: ${error.message}`);
4844
+ closeShareModal();
4845
+ }
4846
+ }
4847
+
4848
+ function closeShareModal() {
4849
+ const modal = document.getElementById('shareModal');
4850
+ modal.classList.remove('show');
4851
+ }
4852
+
4853
+ function copyShareCommand() {
4854
+ const commandDiv = document.getElementById('shareModalCommand');
4855
+ const copyBtn = document.getElementById('copyShareCommandBtn');
4856
+ const command = commandDiv.getAttribute('data-command');
4857
+
4858
+ if (!command) {
4859
+ console.error('No command found to copy');
4860
+ return;
4861
+ }
4862
+
4863
+ if (navigator.clipboard && navigator.clipboard.writeText) {
4864
+ navigator.clipboard.writeText(command).then(() => {
4865
+ // Show success feedback
4866
+ const originalText = copyBtn.textContent;
4867
+ copyBtn.textContent = '✅ Copied!';
4868
+ copyBtn.style.backgroundColor = 'rgba(63, 185, 80, 0.8)';
4869
+ copyBtn.style.borderColor = 'rgba(63, 185, 80, 1)';
4870
+
4871
+ setTimeout(() => {
4872
+ copyBtn.textContent = originalText;
4873
+ copyBtn.style.backgroundColor = '';
4874
+ copyBtn.style.borderColor = '';
4875
+ }, 1500);
4876
+
4877
+ console.log('📋 Share command copied to clipboard:', command);
4878
+ }).catch(err => {
4879
+ console.error('Failed to copy to clipboard:', err);
4880
+ fallbackCopyShare(command);
4881
+ });
4882
+ } else {
4883
+ // Fallback for browsers without clipboard API
4884
+ fallbackCopyShare(command);
4885
+ }
4886
+ }
4887
+
4888
+ function fallbackCopyShare(command) {
4889
+ // Create a temporary text area to select and copy
4890
+ const tempTextArea = document.createElement('textarea');
4891
+ tempTextArea.value = command;
4892
+ tempTextArea.style.position = 'fixed';
4893
+ tempTextArea.style.left = '-9999px';
4894
+ document.body.appendChild(tempTextArea);
4895
+ tempTextArea.select();
4896
+
4897
+ try {
4898
+ document.execCommand('copy');
4899
+ const copyBtn = document.getElementById('copyShareCommandBtn');
4900
+ const originalText = copyBtn.textContent;
4901
+ copyBtn.textContent = '✅ Copied!';
4902
+ copyBtn.style.backgroundColor = 'rgba(63, 185, 80, 0.8)';
4903
+
4904
+ setTimeout(() => {
4905
+ copyBtn.textContent = originalText;
4906
+ copyBtn.style.backgroundColor = '';
4907
+ }, 1500);
4908
+
4909
+ console.log('📋 Share command copied using fallback method:', command);
4910
+ } catch (err) {
4911
+ console.error('Fallback copy failed:', err);
4912
+ alert(`Please copy this command manually:\n\n${command}`);
4913
+ } finally {
4914
+ document.body.removeChild(tempTextArea);
4915
+ }
4916
+ }
4917
+
4354
4918
  // Initialize the app
4355
4919
  document.addEventListener('DOMContentLoaded', () => {
4356
4920
  new ChatsMobileApp();