claude-code-watch 0.0.24 → 0.0.25
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.
- package/package.json +1 -1
- package/public/index.html +204 -6
package/package.json
CHANGED
package/public/index.html
CHANGED
|
@@ -286,6 +286,73 @@ body {
|
|
|
286
286
|
|
|
287
287
|
/* Theme toggle button */
|
|
288
288
|
#btn-theme { font-size: 14px; }
|
|
289
|
+
|
|
290
|
+
/* ── Export modal ── */
|
|
291
|
+
.modal-overlay {
|
|
292
|
+
position: fixed; inset: 0;
|
|
293
|
+
background: rgba(0, 0, 0, 0.6);
|
|
294
|
+
z-index: 10000;
|
|
295
|
+
display: flex; align-items: center; justify-content: center;
|
|
296
|
+
}
|
|
297
|
+
:root[data-theme="light"] .modal-overlay { background: rgba(0, 0, 0, 0.3); }
|
|
298
|
+
:root[data-theme="light"] .modal-session-row.selected { background: rgba(124, 58, 237, 0.2); }
|
|
299
|
+
|
|
300
|
+
.modal-box {
|
|
301
|
+
background: var(--bg); border: 1px solid var(--border); border-radius: 8px;
|
|
302
|
+
width: 480px; max-width: 90vw; max-height: 80vh;
|
|
303
|
+
display: flex; flex-direction: column; overflow: hidden;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
.modal-header {
|
|
307
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
308
|
+
padding: 8px 12px; border-bottom: 1px solid var(--border); background: var(--bg2); flex-shrink: 0;
|
|
309
|
+
}
|
|
310
|
+
.modal-title { font-size: 13px; font-weight: 600; color: var(--white); }
|
|
311
|
+
|
|
312
|
+
.modal-toolbar {
|
|
313
|
+
display: flex; align-items: center; gap: 4px;
|
|
314
|
+
padding: 6px 12px; border-bottom: 1px solid var(--border); flex-shrink: 0;
|
|
315
|
+
}
|
|
316
|
+
.modal-count { margin-left: auto; font-size: 11px; color: var(--dim); }
|
|
317
|
+
|
|
318
|
+
.modal-body { flex: 1; overflow-y: auto; padding: 6px 0; }
|
|
319
|
+
|
|
320
|
+
.modal-session-row {
|
|
321
|
+
display: flex; align-items: center; gap: 8px;
|
|
322
|
+
padding: 6px 12px; cursor: pointer; transition: background 0.1s; user-select: none;
|
|
323
|
+
}
|
|
324
|
+
.modal-session-row:hover { background: var(--bg2); }
|
|
325
|
+
.modal-session-row.selected { background: rgba(124, 58, 237, 0.15); }
|
|
326
|
+
|
|
327
|
+
.modal-checkbox {
|
|
328
|
+
appearance: none; width: 16px; height: 16px;
|
|
329
|
+
border: 1px solid var(--border); border-radius: 3px; background: var(--bg2);
|
|
330
|
+
cursor: pointer; position: relative; flex-shrink: 0; transition: all 0.15s;
|
|
331
|
+
}
|
|
332
|
+
.modal-checkbox:checked { background: var(--purple); border-color: var(--purple); }
|
|
333
|
+
.modal-checkbox:checked::after {
|
|
334
|
+
content: '✓'; position: absolute; inset: 0;
|
|
335
|
+
display: flex; align-items: center; justify-content: center;
|
|
336
|
+
color: var(--white); font-size: 11px; font-weight: bold;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
.modal-session-prefix {
|
|
340
|
+
font-family: monospace; font-size: 12px; font-weight: 600; letter-spacing: 0.5px; flex-shrink: 0;
|
|
341
|
+
}
|
|
342
|
+
.modal-session-info {
|
|
343
|
+
flex: 1; min-width: 0; display: flex; align-items: baseline; gap: 4px; overflow: hidden;
|
|
344
|
+
}
|
|
345
|
+
.modal-session-project {
|
|
346
|
+
font-size: 12px; font-weight: 500; color: var(--text);
|
|
347
|
+
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
348
|
+
}
|
|
349
|
+
.modal-session-model { font-size: 10px; color: var(--dim); flex-shrink: 0; }
|
|
350
|
+
.modal-session-time { font-size: 10px; color: var(--dim); flex-shrink: 0; margin-left: auto; }
|
|
351
|
+
|
|
352
|
+
.modal-footer {
|
|
353
|
+
display: flex; align-items: center; justify-content: flex-end; gap: 6px;
|
|
354
|
+
padding: 8px 12px; border-top: 1px solid var(--border); background: var(--bg2); flex-shrink: 0;
|
|
355
|
+
}
|
|
289
356
|
</style>
|
|
290
357
|
</head>
|
|
291
358
|
<body>
|
|
@@ -302,7 +369,7 @@ body {
|
|
|
302
369
|
<span class="sep">│</span>
|
|
303
370
|
<span id="session-info">Connecting...</span>
|
|
304
371
|
<div class="auto">
|
|
305
|
-
<button class="btn btn-icon" id="btn-export" onclick="
|
|
372
|
+
<button class="btn btn-icon" id="btn-export" onclick="openExportModal()" data-tooltip="导出 HTML">💾</button>
|
|
306
373
|
<button class="btn btn-icon" id="btn-theme" onclick="toggleTheme()" data-tooltip="Toggle theme">🌙</button>
|
|
307
374
|
<button class="btn on" id="btn-autodisco" onclick="toggleAutoDiscovery()" data-tooltip="Auto-discover">🔍 Auto</button>
|
|
308
375
|
<span class="sep">│</span>
|
|
@@ -341,6 +408,25 @@ body {
|
|
|
341
408
|
<span id="footer-version" style="margin-left:auto;font-size:10px;color:var(--dim)"></span>
|
|
342
409
|
</div>
|
|
343
410
|
|
|
411
|
+
<div id="export-modal" class="modal-overlay" style="display:none">
|
|
412
|
+
<div class="modal-box">
|
|
413
|
+
<div class="modal-header">
|
|
414
|
+
<span class="modal-title">选择要导出的会话</span>
|
|
415
|
+
<button class="btn btn-icon" onclick="closeExportModal()" data-tooltip="关闭">✕</button>
|
|
416
|
+
</div>
|
|
417
|
+
<div class="modal-toolbar">
|
|
418
|
+
<button class="btn" onclick="exportModalToggleAll(true)">全选</button>
|
|
419
|
+
<button class="btn" onclick="exportModalToggleAll(false)">取消全选</button>
|
|
420
|
+
<span class="modal-count" id="modal-selected-count">已选 0 / 0</span>
|
|
421
|
+
</div>
|
|
422
|
+
<div class="modal-body" id="modal-session-list"></div>
|
|
423
|
+
<div class="modal-footer">
|
|
424
|
+
<button class="btn" onclick="closeExportModal()">取消</button>
|
|
425
|
+
<button class="btn on" id="modal-export-btn" onclick="confirmExport()" disabled>导出</button>
|
|
426
|
+
</div>
|
|
427
|
+
</div>
|
|
428
|
+
</div>
|
|
429
|
+
|
|
344
430
|
<script src="vendor/highlight.min.js"></script>
|
|
345
431
|
<script src="vendor/marked.min.js"></script>
|
|
346
432
|
<script src="vendor/purify.min.js"></script>
|
|
@@ -1633,17 +1719,122 @@ function scheduleRender() {
|
|
|
1633
1719
|
}
|
|
1634
1720
|
}
|
|
1635
1721
|
|
|
1722
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
1723
|
+
// Export modal — session selection
|
|
1724
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
1725
|
+
|
|
1726
|
+
let exportModalSelected = new Set();
|
|
1727
|
+
|
|
1728
|
+
function openExportModal() {
|
|
1729
|
+
if (sessions.length === 0) {
|
|
1730
|
+
const btn = document.getElementById('btn-export');
|
|
1731
|
+
const orig = btn.textContent;
|
|
1732
|
+
btn.textContent = '✕ 无会话';
|
|
1733
|
+
setTimeout(() => { btn.textContent = orig; }, 2000);
|
|
1734
|
+
return;
|
|
1735
|
+
}
|
|
1736
|
+
exportModalSelected = new Set(sessions.map(s => s.id));
|
|
1737
|
+
renderModalSessionList();
|
|
1738
|
+
updateModalCount();
|
|
1739
|
+
document.getElementById('export-modal').style.display = 'flex';
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
function renderModalSessionList() {
|
|
1743
|
+
const listEl = document.getElementById('modal-session-list');
|
|
1744
|
+
const sorted = [...sessions].sort((a, b) => (a.colorRank || 0) - (b.colorRank || 0));
|
|
1745
|
+
listEl.innerHTML = sorted.map(s => {
|
|
1746
|
+
const color = idColor(s.colorRank || 0);
|
|
1747
|
+
const project = folderName(s.projectPath) || s.projectPath || '';
|
|
1748
|
+
const prefix = s.id.split('-')[0].toUpperCase();
|
|
1749
|
+
const model = s.model || '';
|
|
1750
|
+
const time = formatTime(s.birthtimeMs);
|
|
1751
|
+
const checked = exportModalSelected.has(s.id) ? 'checked' : '';
|
|
1752
|
+
const selectedClass = exportModalSelected.has(s.id) ? ' selected' : '';
|
|
1753
|
+
return `<div class="modal-session-row${selectedClass}" data-sid="${esc(s.id)}" onclick="toggleModalSession('${esc(s.id)}', this)">
|
|
1754
|
+
<input type="checkbox" class="modal-checkbox" data-sid="${esc(s.id)}" ${checked} onclick="event.stopPropagation(); toggleModalSession('${esc(s.id)}', this.parentElement)">
|
|
1755
|
+
<span class="modal-session-prefix" style="color:${color}">${esc(prefix)}</span>
|
|
1756
|
+
<div class="modal-session-info">
|
|
1757
|
+
<span class="modal-session-project">${esc(project)}</span>
|
|
1758
|
+
${model ? `<span class="modal-session-model">${esc(model)}</span>` : ''}
|
|
1759
|
+
</div>
|
|
1760
|
+
${time ? `<span class="modal-session-time">${esc(time)}</span>` : ''}
|
|
1761
|
+
</div>`;
|
|
1762
|
+
}).join('\n');
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1765
|
+
function toggleModalSession(sid, rowEl) {
|
|
1766
|
+
if (exportModalSelected.has(sid)) {
|
|
1767
|
+
exportModalSelected.delete(sid);
|
|
1768
|
+
} else {
|
|
1769
|
+
exportModalSelected.add(sid);
|
|
1770
|
+
}
|
|
1771
|
+
const checkbox = rowEl.querySelector('.modal-checkbox');
|
|
1772
|
+
checkbox.checked = exportModalSelected.has(sid);
|
|
1773
|
+
rowEl.classList.toggle('selected', exportModalSelected.has(sid));
|
|
1774
|
+
updateModalCount();
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1777
|
+
function exportModalToggleAll(selectAll) {
|
|
1778
|
+
if (selectAll) {
|
|
1779
|
+
exportModalSelected = new Set(sessions.map(s => s.id));
|
|
1780
|
+
} else {
|
|
1781
|
+
exportModalSelected.clear();
|
|
1782
|
+
}
|
|
1783
|
+
document.querySelectorAll('#modal-session-list .modal-session-row').forEach(row => {
|
|
1784
|
+
const sid = row.dataset.sid;
|
|
1785
|
+
const checkbox = row.querySelector('.modal-checkbox');
|
|
1786
|
+
checkbox.checked = exportModalSelected.has(sid);
|
|
1787
|
+
row.classList.toggle('selected', exportModalSelected.has(sid));
|
|
1788
|
+
});
|
|
1789
|
+
updateModalCount();
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
function updateModalCount() {
|
|
1793
|
+
const total = sessions.length;
|
|
1794
|
+
const selected = exportModalSelected.size;
|
|
1795
|
+
document.getElementById('modal-selected-count').textContent = `已选 ${selected} / ${total}`;
|
|
1796
|
+
document.getElementById('modal-export-btn').disabled = selected === 0;
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
function closeExportModal() {
|
|
1800
|
+
document.getElementById('export-modal').style.display = 'none';
|
|
1801
|
+
exportModalSelected.clear();
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
// Esc key closes modal
|
|
1805
|
+
document.addEventListener('keydown', (e) => {
|
|
1806
|
+
if (e.key === 'Escape') {
|
|
1807
|
+
const modal = document.getElementById('export-modal');
|
|
1808
|
+
if (modal.style.display !== 'none') {
|
|
1809
|
+
closeExportModal();
|
|
1810
|
+
e.stopPropagation();
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
});
|
|
1814
|
+
|
|
1815
|
+
function confirmExport() {
|
|
1816
|
+
if (exportModalSelected.size === 0) return;
|
|
1817
|
+
const selectedIds = new Set(exportModalSelected);
|
|
1818
|
+
closeExportModal();
|
|
1819
|
+
exportHTML(selectedIds);
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1636
1822
|
// ══════════════════════════════════════════════════════════════════════════════
|
|
1637
1823
|
// Export HTML
|
|
1638
1824
|
// ══════════════════════════════════════════════════════════════════════════════
|
|
1639
1825
|
|
|
1640
|
-
function exportHTML() {
|
|
1826
|
+
function exportHTML(selectedIds = null) {
|
|
1641
1827
|
const theme = document.documentElement.getAttribute('data-theme') || 'dark';
|
|
1642
1828
|
|
|
1643
|
-
// Collect
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1829
|
+
// Collect sessions to export
|
|
1830
|
+
let sidsInExport;
|
|
1831
|
+
if (selectedIds) {
|
|
1832
|
+
sidsInExport = selectedIds;
|
|
1833
|
+
} else {
|
|
1834
|
+
sidsInExport = new Set();
|
|
1835
|
+
for (const item of visibleItems) {
|
|
1836
|
+
if (item.sessionID) sidsInExport.add(item.sessionID);
|
|
1837
|
+
}
|
|
1647
1838
|
}
|
|
1648
1839
|
const exportSessions = [];
|
|
1649
1840
|
for (const sid of sidsInExport) {
|
|
@@ -1697,6 +1888,13 @@ ${items}
|
|
|
1697
1888
|
clone.querySelectorAll('.copy-btn').forEach(el => el.remove());
|
|
1698
1889
|
clone.querySelectorAll('[onclick]').forEach(el => el.removeAttribute('onclick'));
|
|
1699
1890
|
|
|
1891
|
+
// Filter out stream lines from non-selected sessions
|
|
1892
|
+
if (selectedIds) {
|
|
1893
|
+
clone.querySelectorAll('[data-session-id]').forEach(el => {
|
|
1894
|
+
if (!selectedIds.has(el.dataset.sessionId)) el.remove();
|
|
1895
|
+
});
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1700
1898
|
// Get the cleaned innerHTML
|
|
1701
1899
|
const streamHTML = clone.innerHTML;
|
|
1702
1900
|
|