reviw 0.16.2 → 0.16.3
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/README.ja.md +2 -2
- package/README.md +2 -2
- package/cli.cjs +809 -11
- package/package.json +1 -1
package/README.ja.md
CHANGED
|
@@ -286,8 +286,8 @@ npx reviw .artifacts/<feature>/REPORT.md
|
|
|
286
286
|
Playwrightを使用したブラウザ自動化ツールキット。
|
|
287
287
|
|
|
288
288
|
**機能:**
|
|
289
|
-
-
|
|
290
|
-
-
|
|
289
|
+
- TypeScript Playwright Test (`@playwright/test`)
|
|
290
|
+
- webServerサポート付きPlaywright設定
|
|
291
291
|
- スクリーンショットと動画キャプチャ
|
|
292
292
|
- コンソールログとネットワークリクエスト監視
|
|
293
293
|
- 高度なデバッグ用CDP統合
|
package/README.md
CHANGED
|
@@ -318,8 +318,8 @@ npx reviw .artifacts/<feature>/REPORT.md
|
|
|
318
318
|
Browser automation toolkit using Playwright.
|
|
319
319
|
|
|
320
320
|
**Features:**
|
|
321
|
-
-
|
|
322
|
-
-
|
|
321
|
+
- TypeScript Playwright Test (`@playwright/test`)
|
|
322
|
+
- Playwright configuration with webServer support
|
|
323
323
|
- Screenshot and video capture
|
|
324
324
|
- Console log and network request monitoring
|
|
325
325
|
- CDP integration for advanced debugging
|
package/cli.cjs
CHANGED
|
@@ -825,9 +825,10 @@ function serializeForScript(value) {
|
|
|
825
825
|
.replace(/\$\{/g, "\\${");
|
|
826
826
|
}
|
|
827
827
|
|
|
828
|
-
function diffHtmlTemplate(diffData) {
|
|
828
|
+
function diffHtmlTemplate(diffData, history = []) {
|
|
829
829
|
const { rows, projectRoot, relativePath } = diffData;
|
|
830
830
|
const serialized = serializeForScript(rows);
|
|
831
|
+
const historyJson = serializeForScript(history);
|
|
831
832
|
const fileCount = rows.filter((r) => r.type === "file").length;
|
|
832
833
|
|
|
833
834
|
return `<!doctype html>
|
|
@@ -1235,6 +1236,221 @@ function diffHtmlTemplate(diffData) {
|
|
|
1235
1236
|
}
|
|
1236
1237
|
.no-diff h2 { font-size: 20px; margin: 0 0 8px; color: var(--text); }
|
|
1237
1238
|
.no-diff p { font-size: 14px; margin: 0; }
|
|
1239
|
+
|
|
1240
|
+
/* History Panel - Push layout */
|
|
1241
|
+
body { transition: margin-right 0.25s ease; }
|
|
1242
|
+
body.history-open { margin-right: 320px; }
|
|
1243
|
+
body.history-open header { right: 320px; }
|
|
1244
|
+
header { transition: right 0.25s ease; right: 0; }
|
|
1245
|
+
|
|
1246
|
+
.history-toggle {
|
|
1247
|
+
background: var(--selected-bg);
|
|
1248
|
+
color: var(--text);
|
|
1249
|
+
border: 1px solid var(--border);
|
|
1250
|
+
border-radius: 6px;
|
|
1251
|
+
padding: 6px 8px;
|
|
1252
|
+
font-size: 14px;
|
|
1253
|
+
cursor: pointer;
|
|
1254
|
+
width: 34px;
|
|
1255
|
+
height: 34px;
|
|
1256
|
+
display: flex;
|
|
1257
|
+
align-items: center;
|
|
1258
|
+
justify-content: center;
|
|
1259
|
+
}
|
|
1260
|
+
.history-toggle:hover { background: var(--border); }
|
|
1261
|
+
.history-panel {
|
|
1262
|
+
position: fixed;
|
|
1263
|
+
top: 0;
|
|
1264
|
+
right: 0;
|
|
1265
|
+
width: 320px;
|
|
1266
|
+
height: 100vh;
|
|
1267
|
+
background: var(--panel);
|
|
1268
|
+
border-left: 1px solid var(--border);
|
|
1269
|
+
z-index: 90;
|
|
1270
|
+
transform: translateX(100%);
|
|
1271
|
+
transition: transform 0.25s ease;
|
|
1272
|
+
display: flex;
|
|
1273
|
+
flex-direction: column;
|
|
1274
|
+
}
|
|
1275
|
+
.history-panel.open { transform: translateX(0); }
|
|
1276
|
+
.history-panel-header {
|
|
1277
|
+
padding: 16px;
|
|
1278
|
+
border-bottom: 1px solid var(--border);
|
|
1279
|
+
display: flex;
|
|
1280
|
+
justify-content: space-between;
|
|
1281
|
+
align-items: center;
|
|
1282
|
+
}
|
|
1283
|
+
.history-panel-header h3 { margin: 0; font-size: 14px; font-weight: 600; }
|
|
1284
|
+
.history-panel-close {
|
|
1285
|
+
background: transparent;
|
|
1286
|
+
border: none;
|
|
1287
|
+
color: var(--muted);
|
|
1288
|
+
cursor: pointer;
|
|
1289
|
+
font-size: 18px;
|
|
1290
|
+
padding: 4px;
|
|
1291
|
+
}
|
|
1292
|
+
.history-panel-close:hover { color: var(--text); }
|
|
1293
|
+
.history-panel-body {
|
|
1294
|
+
flex: 1;
|
|
1295
|
+
overflow-y: auto;
|
|
1296
|
+
padding: 12px;
|
|
1297
|
+
}
|
|
1298
|
+
.history-empty {
|
|
1299
|
+
color: var(--muted);
|
|
1300
|
+
font-size: 13px;
|
|
1301
|
+
text-align: center;
|
|
1302
|
+
padding: 40px 20px;
|
|
1303
|
+
}
|
|
1304
|
+
.history-date-group {
|
|
1305
|
+
margin-bottom: 16px;
|
|
1306
|
+
}
|
|
1307
|
+
.history-date {
|
|
1308
|
+
font-size: 11px;
|
|
1309
|
+
font-weight: 600;
|
|
1310
|
+
color: var(--muted);
|
|
1311
|
+
margin-bottom: 8px;
|
|
1312
|
+
text-transform: uppercase;
|
|
1313
|
+
}
|
|
1314
|
+
.history-item {
|
|
1315
|
+
background: var(--bg);
|
|
1316
|
+
border: 1px solid var(--border);
|
|
1317
|
+
border-radius: 6px;
|
|
1318
|
+
margin-bottom: 8px;
|
|
1319
|
+
overflow: hidden;
|
|
1320
|
+
}
|
|
1321
|
+
.history-item-header {
|
|
1322
|
+
display: flex;
|
|
1323
|
+
justify-content: space-between;
|
|
1324
|
+
align-items: center;
|
|
1325
|
+
padding: 8px 10px;
|
|
1326
|
+
background: var(--selected-bg);
|
|
1327
|
+
cursor: pointer;
|
|
1328
|
+
}
|
|
1329
|
+
.history-item-header:hover { background: var(--border); }
|
|
1330
|
+
.history-item-file {
|
|
1331
|
+
font-size: 12px;
|
|
1332
|
+
font-weight: 600;
|
|
1333
|
+
color: var(--text);
|
|
1334
|
+
white-space: nowrap;
|
|
1335
|
+
overflow: hidden;
|
|
1336
|
+
text-overflow: ellipsis;
|
|
1337
|
+
max-width: 180px;
|
|
1338
|
+
}
|
|
1339
|
+
.history-item-time {
|
|
1340
|
+
font-size: 10px;
|
|
1341
|
+
color: var(--muted);
|
|
1342
|
+
}
|
|
1343
|
+
.history-item-body {
|
|
1344
|
+
display: none;
|
|
1345
|
+
padding: 10px;
|
|
1346
|
+
font-size: 12px;
|
|
1347
|
+
border-top: 1px solid var(--border);
|
|
1348
|
+
}
|
|
1349
|
+
.history-item.expanded .history-item-body { display: block; }
|
|
1350
|
+
.history-summary {
|
|
1351
|
+
color: var(--text);
|
|
1352
|
+
margin-bottom: 8px;
|
|
1353
|
+
padding-bottom: 8px;
|
|
1354
|
+
border-bottom: 1px solid var(--border);
|
|
1355
|
+
}
|
|
1356
|
+
.history-summary-label {
|
|
1357
|
+
font-size: 10px;
|
|
1358
|
+
font-weight: 600;
|
|
1359
|
+
color: var(--muted);
|
|
1360
|
+
margin-bottom: 4px;
|
|
1361
|
+
}
|
|
1362
|
+
.history-summary-text {
|
|
1363
|
+
white-space: pre-wrap;
|
|
1364
|
+
line-height: 1.4;
|
|
1365
|
+
}
|
|
1366
|
+
.history-comments-label {
|
|
1367
|
+
font-size: 10px;
|
|
1368
|
+
font-weight: 600;
|
|
1369
|
+
color: var(--muted);
|
|
1370
|
+
margin-bottom: 6px;
|
|
1371
|
+
}
|
|
1372
|
+
.history-comment {
|
|
1373
|
+
padding: 6px 0;
|
|
1374
|
+
border-bottom: 1px solid var(--border);
|
|
1375
|
+
}
|
|
1376
|
+
.history-comment:last-child { border-bottom: none; }
|
|
1377
|
+
.history-comment-line {
|
|
1378
|
+
font-size: 10px;
|
|
1379
|
+
color: var(--accent);
|
|
1380
|
+
font-weight: 600;
|
|
1381
|
+
margin-bottom: 2px;
|
|
1382
|
+
}
|
|
1383
|
+
.history-comment-quote {
|
|
1384
|
+
background: rgba(0, 0, 0, 0.3);
|
|
1385
|
+
border-left: 2px solid var(--accent);
|
|
1386
|
+
padding: 4px 8px;
|
|
1387
|
+
margin: 4px 0;
|
|
1388
|
+
font-family: 'SF Mono', Monaco, Consolas, monospace;
|
|
1389
|
+
font-size: 11px;
|
|
1390
|
+
color: var(--muted);
|
|
1391
|
+
white-space: pre-wrap;
|
|
1392
|
+
word-break: break-all;
|
|
1393
|
+
max-height: 80px;
|
|
1394
|
+
overflow-y: auto;
|
|
1395
|
+
}
|
|
1396
|
+
.history-comment-text {
|
|
1397
|
+
color: var(--text);
|
|
1398
|
+
line-height: 1.4;
|
|
1399
|
+
white-space: pre-wrap;
|
|
1400
|
+
}
|
|
1401
|
+
.history-badge {
|
|
1402
|
+
display: inline-block;
|
|
1403
|
+
background: var(--accent);
|
|
1404
|
+
color: var(--text-inverse);
|
|
1405
|
+
font-size: 10px;
|
|
1406
|
+
padding: 2px 6px;
|
|
1407
|
+
border-radius: 10px;
|
|
1408
|
+
margin-left: 6px;
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
/* Past comment indicator on lines */
|
|
1412
|
+
.diff-line[data-has-history]::before {
|
|
1413
|
+
content: '💬';
|
|
1414
|
+
position: absolute;
|
|
1415
|
+
left: 4px;
|
|
1416
|
+
font-size: 10px;
|
|
1417
|
+
opacity: 0.6;
|
|
1418
|
+
}
|
|
1419
|
+
.diff-line { position: relative; }
|
|
1420
|
+
.past-comment-overlay {
|
|
1421
|
+
background: var(--selected-bg);
|
|
1422
|
+
border-left: 3px solid var(--muted);
|
|
1423
|
+
margin: 0;
|
|
1424
|
+
padding: 8px 12px;
|
|
1425
|
+
font-size: 11px;
|
|
1426
|
+
color: var(--muted);
|
|
1427
|
+
display: none;
|
|
1428
|
+
}
|
|
1429
|
+
.past-comment-overlay.visible { display: block; }
|
|
1430
|
+
.past-comment-header {
|
|
1431
|
+
display: flex;
|
|
1432
|
+
justify-content: space-between;
|
|
1433
|
+
align-items: center;
|
|
1434
|
+
margin-bottom: 4px;
|
|
1435
|
+
}
|
|
1436
|
+
.past-comment-date {
|
|
1437
|
+
font-size: 10px;
|
|
1438
|
+
color: var(--muted);
|
|
1439
|
+
}
|
|
1440
|
+
.past-comment-toggle {
|
|
1441
|
+
background: transparent;
|
|
1442
|
+
border: none;
|
|
1443
|
+
color: var(--muted);
|
|
1444
|
+
cursor: pointer;
|
|
1445
|
+
font-size: 10px;
|
|
1446
|
+
padding: 2px 4px;
|
|
1447
|
+
}
|
|
1448
|
+
.past-comment-toggle:hover { color: var(--text); }
|
|
1449
|
+
.past-comment-text {
|
|
1450
|
+
color: var(--text);
|
|
1451
|
+
white-space: pre-wrap;
|
|
1452
|
+
line-height: 1.4;
|
|
1453
|
+
}
|
|
1238
1454
|
</style>
|
|
1239
1455
|
</head>
|
|
1240
1456
|
<body>
|
|
@@ -1245,11 +1461,23 @@ function diffHtmlTemplate(diffData) {
|
|
|
1245
1461
|
<span class="pill">Comments <strong id="comment-count">0</strong></span>
|
|
1246
1462
|
</div>
|
|
1247
1463
|
<div class="actions">
|
|
1464
|
+
<button class="history-toggle" id="history-toggle" title="Review History">☰</button>
|
|
1248
1465
|
<button class="theme-toggle" id="theme-toggle" title="Toggle theme"><span id="theme-icon">🌙</span></button>
|
|
1249
1466
|
<button id="send-and-exit">Submit & Exit</button>
|
|
1250
1467
|
</div>
|
|
1251
1468
|
</header>
|
|
1252
1469
|
|
|
1470
|
+
<!-- History Panel -->
|
|
1471
|
+
<aside class="history-panel" id="history-panel">
|
|
1472
|
+
<div class="history-panel-header">
|
|
1473
|
+
<h3>📜 Review History</h3>
|
|
1474
|
+
<button class="history-panel-close" id="history-panel-close">✕</button>
|
|
1475
|
+
</div>
|
|
1476
|
+
<div class="history-panel-body" id="history-panel-body">
|
|
1477
|
+
<div class="history-empty">No review history yet.</div>
|
|
1478
|
+
</div>
|
|
1479
|
+
</aside>
|
|
1480
|
+
|
|
1253
1481
|
<div class="wrap">
|
|
1254
1482
|
${rows.length === 0 ? '<div class="no-diff"><h2>No changes</h2><p>Working tree is clean</p></div>' : '<div class="diff-container" id="diff-container"></div>'}
|
|
1255
1483
|
</div>
|
|
@@ -1298,8 +1526,9 @@ function diffHtmlTemplate(diffData) {
|
|
|
1298
1526
|
|
|
1299
1527
|
<script>
|
|
1300
1528
|
const DATA = ${serialized};
|
|
1301
|
-
const FILE_NAME = ${serializeForScript(
|
|
1529
|
+
const FILE_NAME = ${serializeForScript(relativePath)};
|
|
1302
1530
|
const MODE = 'diff';
|
|
1531
|
+
const HISTORY_DATA = ${historyJson};
|
|
1303
1532
|
|
|
1304
1533
|
// Theme
|
|
1305
1534
|
(function initTheme() {
|
|
@@ -1319,6 +1548,166 @@ function diffHtmlTemplate(diffData) {
|
|
|
1319
1548
|
});
|
|
1320
1549
|
})();
|
|
1321
1550
|
|
|
1551
|
+
// --- History Management ---
|
|
1552
|
+
// History is now server-side (file-based), HISTORY_DATA is provided by server
|
|
1553
|
+
|
|
1554
|
+
function loadHistory() {
|
|
1555
|
+
// Return server-provided history data
|
|
1556
|
+
return Array.isArray(HISTORY_DATA) ? HISTORY_DATA : [];
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
// saveToHistory is handled server-side via /exit endpoint
|
|
1560
|
+
function saveToHistory(payload) {
|
|
1561
|
+
// No-op on client - server saves history when receiving /exit
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
function formatDate(isoString) {
|
|
1565
|
+
const d = new Date(isoString);
|
|
1566
|
+
return d.toLocaleDateString('ja-JP', { year: 'numeric', month: '2-digit', day: '2-digit' });
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
function formatTime(isoString) {
|
|
1570
|
+
const d = new Date(isoString);
|
|
1571
|
+
return d.toLocaleTimeString('ja-JP', { hour: '2-digit', minute: '2-digit' });
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
function getBasename(filepath) {
|
|
1575
|
+
return filepath.split('/').pop() || filepath;
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
function renderHistoryPanel() {
|
|
1579
|
+
const body = document.getElementById('history-panel-body');
|
|
1580
|
+
const history = loadHistory();
|
|
1581
|
+
if (history.length === 0) {
|
|
1582
|
+
body.innerHTML = '<div class="history-empty">No review history yet.</div>';
|
|
1583
|
+
return;
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
const grouped = {};
|
|
1587
|
+
history.forEach((item, idx) => {
|
|
1588
|
+
const date = formatDate(item.submittedAt);
|
|
1589
|
+
if (!grouped[date]) grouped[date] = [];
|
|
1590
|
+
grouped[date].push({ ...item, _idx: idx });
|
|
1591
|
+
});
|
|
1592
|
+
|
|
1593
|
+
let html = '';
|
|
1594
|
+
for (const date of Object.keys(grouped)) {
|
|
1595
|
+
html += \`<div class="history-date-group">
|
|
1596
|
+
<div class="history-date">\${date}</div>\`;
|
|
1597
|
+
for (const item of grouped[date]) {
|
|
1598
|
+
const commentCount = item.comments?.length || 0;
|
|
1599
|
+
html += \`<div class="history-item" data-idx="\${item._idx}">
|
|
1600
|
+
<div class="history-item-header">
|
|
1601
|
+
<span class="history-item-file">\${getBasename(item.file)}</span>
|
|
1602
|
+
<span class="history-item-time">\${formatTime(item.submittedAt)}<span class="history-badge">\${commentCount}</span></span>
|
|
1603
|
+
</div>
|
|
1604
|
+
<div class="history-item-body">\`;
|
|
1605
|
+
if (item.summary) {
|
|
1606
|
+
html += \`<div class="history-summary">
|
|
1607
|
+
<div class="history-summary-label">Summary</div>
|
|
1608
|
+
<div class="history-summary-text">\${escapeHtmlForHistory(item.summary)}</div>
|
|
1609
|
+
</div>\`;
|
|
1610
|
+
}
|
|
1611
|
+
if (commentCount > 0) {
|
|
1612
|
+
html += \`<div class="history-comments-label">Line Comments (\${commentCount})</div>\`;
|
|
1613
|
+
for (const c of item.comments) {
|
|
1614
|
+
const lineLabel = c.line ? \`L\${c.line}\${c.lineEnd ? '-' + c.lineEnd : ''}\` : (c.row != null ? \`L\${c.row}\` : '');
|
|
1615
|
+
const text = c.comment || c.text || '';
|
|
1616
|
+
// Support both direct content and context.content structures
|
|
1617
|
+
const content = c.content || c.context?.content || '';
|
|
1618
|
+
html += \`<div class="history-comment">
|
|
1619
|
+
<div class="history-comment-line">\${lineLabel}</div>\`;
|
|
1620
|
+
if (content) {
|
|
1621
|
+
html += \`<div class="history-comment-quote">\${escapeHtmlForHistory(content)}</div>\`;
|
|
1622
|
+
}
|
|
1623
|
+
html += \`<div class="history-comment-text">\${escapeHtmlForHistory(text)}</div>
|
|
1624
|
+
</div>\`;
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
html += \`</div></div>\`;
|
|
1628
|
+
}
|
|
1629
|
+
html += \`</div>\`;
|
|
1630
|
+
}
|
|
1631
|
+
body.innerHTML = html;
|
|
1632
|
+
|
|
1633
|
+
body.querySelectorAll('.history-item-header').forEach(header => {
|
|
1634
|
+
header.addEventListener('click', () => {
|
|
1635
|
+
header.parentElement.classList.toggle('expanded');
|
|
1636
|
+
});
|
|
1637
|
+
});
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
function escapeHtmlForHistory(s) {
|
|
1641
|
+
if (!s) return '';
|
|
1642
|
+
return String(s).replace(/[&<>"]/g, c => ({'&':'&','<':'<','>':'>','"':'"'}[c] || c));
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
function getHistoryForFile(filename) {
|
|
1646
|
+
const history = loadHistory();
|
|
1647
|
+
return history.filter(h => h.file === filename || getBasename(h.file) === getBasename(filename));
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
function renderPastCommentsOnLines() {
|
|
1651
|
+
const fileHistory = getHistoryForFile(FILE_NAME);
|
|
1652
|
+
if (fileHistory.length === 0) return;
|
|
1653
|
+
|
|
1654
|
+
const lineComments = {};
|
|
1655
|
+
fileHistory.forEach(h => {
|
|
1656
|
+
if (!h.comments) return;
|
|
1657
|
+
h.comments.forEach(c => {
|
|
1658
|
+
const line = c.line || c.row;
|
|
1659
|
+
if (!line) return;
|
|
1660
|
+
if (!lineComments[line]) lineComments[line] = [];
|
|
1661
|
+
lineComments[line].push({
|
|
1662
|
+
date: formatDate(h.submittedAt),
|
|
1663
|
+
text: c.comment || c.text || ''
|
|
1664
|
+
});
|
|
1665
|
+
});
|
|
1666
|
+
});
|
|
1667
|
+
|
|
1668
|
+
Object.entries(lineComments).forEach(([line, comments]) => {
|
|
1669
|
+
const lineEl = document.querySelector('[data-row="' + line + '"]');
|
|
1670
|
+
if (lineEl && !lineEl.dataset.hasHistory) {
|
|
1671
|
+
lineEl.dataset.hasHistory = 'true';
|
|
1672
|
+
lineEl.title = comments.length + ' past comment(s) - click to view in History panel';
|
|
1673
|
+
}
|
|
1674
|
+
});
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
// History Panel Toggle
|
|
1678
|
+
(function initHistoryPanel() {
|
|
1679
|
+
const toggle = document.getElementById('history-toggle');
|
|
1680
|
+
const panel = document.getElementById('history-panel');
|
|
1681
|
+
const closeBtn = document.getElementById('history-panel-close');
|
|
1682
|
+
|
|
1683
|
+
function openPanel() {
|
|
1684
|
+
panel.classList.add('open');
|
|
1685
|
+
document.body.classList.add('history-open');
|
|
1686
|
+
renderHistoryPanel();
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1689
|
+
function closePanel() {
|
|
1690
|
+
panel.classList.remove('open');
|
|
1691
|
+
document.body.classList.remove('history-open');
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
toggle?.addEventListener('click', () => {
|
|
1695
|
+
if (panel.classList.contains('open')) {
|
|
1696
|
+
closePanel();
|
|
1697
|
+
} else {
|
|
1698
|
+
openPanel();
|
|
1699
|
+
}
|
|
1700
|
+
});
|
|
1701
|
+
|
|
1702
|
+
closeBtn?.addEventListener('click', closePanel);
|
|
1703
|
+
|
|
1704
|
+
document.addEventListener('keydown', (e) => {
|
|
1705
|
+
if (e.key === 'Escape' && panel.classList.contains('open')) {
|
|
1706
|
+
closePanel();
|
|
1707
|
+
}
|
|
1708
|
+
});
|
|
1709
|
+
})();
|
|
1710
|
+
|
|
1322
1711
|
const container = document.getElementById('diff-container');
|
|
1323
1712
|
const card = document.getElementById('comment-card');
|
|
1324
1713
|
const commentInput = document.getElementById('comment-input');
|
|
@@ -1628,7 +2017,18 @@ function diffHtmlTemplate(diffData) {
|
|
|
1628
2017
|
document.getElementById('clear-comment').addEventListener('click', clearCurrent);
|
|
1629
2018
|
document.getElementById('close-card').addEventListener('click', closeCard);
|
|
1630
2019
|
document.addEventListener('keydown', e => {
|
|
1631
|
-
if (e.key === 'Escape')
|
|
2020
|
+
if (e.key === 'Escape') {
|
|
2021
|
+
// Don't close card if any fullscreen overlay is open
|
|
2022
|
+
const imageOverlay = document.getElementById('image-fullscreen');
|
|
2023
|
+
const videoOverlay = document.getElementById('video-fullscreen');
|
|
2024
|
+
const mermaidOverlay = document.getElementById('mermaid-fullscreen');
|
|
2025
|
+
if (imageOverlay?.classList.contains('visible') ||
|
|
2026
|
+
videoOverlay?.classList.contains('visible') ||
|
|
2027
|
+
mermaidOverlay?.classList.contains('visible')) {
|
|
2028
|
+
return; // Let the fullscreen handlers handle ESC
|
|
2029
|
+
}
|
|
2030
|
+
closeCard();
|
|
2031
|
+
}
|
|
1632
2032
|
if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') saveCurrent();
|
|
1633
2033
|
});
|
|
1634
2034
|
|
|
@@ -1720,7 +2120,9 @@ function diffHtmlTemplate(diffData) {
|
|
|
1720
2120
|
if (sent) return;
|
|
1721
2121
|
sent = true;
|
|
1722
2122
|
clearStorage();
|
|
1723
|
-
|
|
2123
|
+
const p = payload(reason);
|
|
2124
|
+
saveToHistory(p);
|
|
2125
|
+
navigator.sendBeacon('/exit', new Blob([JSON.stringify(p)], { type: 'application/json' }));
|
|
1724
2126
|
}
|
|
1725
2127
|
function showSubmitModal() {
|
|
1726
2128
|
const count = Object.keys(comments).length;
|
|
@@ -1783,6 +2185,7 @@ function diffHtmlTemplate(diffData) {
|
|
|
1783
2185
|
|
|
1784
2186
|
renderDiff();
|
|
1785
2187
|
refreshList();
|
|
2188
|
+
renderPastCommentsOnLines();
|
|
1786
2189
|
|
|
1787
2190
|
// Recovery
|
|
1788
2191
|
(function checkRecovery() {
|
|
@@ -1802,11 +2205,12 @@ function diffHtmlTemplate(diffData) {
|
|
|
1802
2205
|
}
|
|
1803
2206
|
|
|
1804
2207
|
// --- HTML template ---------------------------------------------------------
|
|
1805
|
-
function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHtml, reviwQuestions = []) {
|
|
2208
|
+
function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHtml, reviwQuestions = [], history = []) {
|
|
1806
2209
|
const serialized = serializeForScript(dataRows);
|
|
1807
2210
|
const modeJson = serializeForScript(mode);
|
|
1808
2211
|
const titleJson = serializeForScript(relativePath); // Use relativePath as file identifier
|
|
1809
2212
|
const questionsJson = serializeForScript(reviwQuestions || []);
|
|
2213
|
+
const historyJson = serializeForScript(history);
|
|
1810
2214
|
const hasPreview = !!previewHtml;
|
|
1811
2215
|
return `<!doctype html>
|
|
1812
2216
|
<html lang="ja">
|
|
@@ -1979,7 +2383,7 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
1979
2383
|
position: sticky;
|
|
1980
2384
|
top: 0;
|
|
1981
2385
|
z-index: 3;
|
|
1982
|
-
background: var(--panel-solid);
|
|
2386
|
+
background: var(--panel-solid) !important;
|
|
1983
2387
|
color: var(--muted);
|
|
1984
2388
|
font-size: 12px;
|
|
1985
2389
|
text-align: center;
|
|
@@ -1989,6 +2393,9 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
1989
2393
|
white-space: nowrap;
|
|
1990
2394
|
transition: background 200ms ease;
|
|
1991
2395
|
}
|
|
2396
|
+
thead th:not(.selected) {
|
|
2397
|
+
background: var(--panel-solid) !important;
|
|
2398
|
+
}
|
|
1992
2399
|
thead th:first-child,
|
|
1993
2400
|
tbody th {
|
|
1994
2401
|
width: 28px;
|
|
@@ -2093,7 +2500,9 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
2093
2500
|
tr:nth-child(even) td:not(.selected):not(.has-comment) { background: var(--row-even); }
|
|
2094
2501
|
td:hover:not(.selected) { background: var(--hover-bg); box-shadow: inset 0 0 0 1px rgba(96,165,250,0.25); }
|
|
2095
2502
|
td.has-comment { background: rgba(34,197,94,0.12); box-shadow: inset 0 0 0 1px rgba(34,197,94,0.35); }
|
|
2096
|
-
td.selected,
|
|
2503
|
+
td.selected, tbody th.selected { background: rgba(99,102,241,0.22) !important; box-shadow: inset 0 0 0 1px rgba(99,102,241,0.45); }
|
|
2504
|
+
thead th.selected { background: #c7d2fe !important; box-shadow: inset 0 0 0 1px rgba(99,102,241,0.45); }
|
|
2505
|
+
[data-theme="dark"] thead th.selected { background: #3730a3 !important; }
|
|
2097
2506
|
body.dragging { user-select: none; cursor: crosshair; }
|
|
2098
2507
|
body.dragging td, body.dragging tbody th { cursor: crosshair; }
|
|
2099
2508
|
tbody th { cursor: pointer; }
|
|
@@ -2255,6 +2664,19 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
2255
2664
|
max-height: none;
|
|
2256
2665
|
overflow: visible;
|
|
2257
2666
|
}
|
|
2667
|
+
/* Ensure thead is opaque in md-right to prevent content showing through */
|
|
2668
|
+
.md-right thead th {
|
|
2669
|
+
background: var(--panel-solid) !important;
|
|
2670
|
+
}
|
|
2671
|
+
.md-right thead th.selected {
|
|
2672
|
+
background: #c7d2fe !important;
|
|
2673
|
+
}
|
|
2674
|
+
[data-theme="dark"] .md-right thead th {
|
|
2675
|
+
background: var(--panel-solid) !important;
|
|
2676
|
+
}
|
|
2677
|
+
[data-theme="dark"] .md-right thead th.selected {
|
|
2678
|
+
background: #3730a3 !important;
|
|
2679
|
+
}
|
|
2258
2680
|
.md-preview h1, .md-preview h2, .md-preview h3, .md-preview h4 {
|
|
2259
2681
|
margin: 0.4em 0 0.2em;
|
|
2260
2682
|
}
|
|
@@ -3090,6 +3512,169 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
3090
3512
|
font-family: monospace;
|
|
3091
3513
|
}
|
|
3092
3514
|
.mermaid-error-toast.visible { display: block; }
|
|
3515
|
+
|
|
3516
|
+
/* History Panel - Push layout */
|
|
3517
|
+
body { transition: margin-right 0.25s ease; }
|
|
3518
|
+
body.history-open { margin-right: 320px; }
|
|
3519
|
+
body.history-open header { right: 320px; }
|
|
3520
|
+
header { transition: right 0.25s ease; right: 0; }
|
|
3521
|
+
|
|
3522
|
+
.history-toggle {
|
|
3523
|
+
background: var(--selected-bg);
|
|
3524
|
+
color: var(--text);
|
|
3525
|
+
border: 1px solid var(--border);
|
|
3526
|
+
border-radius: 6px;
|
|
3527
|
+
padding: 6px 8px;
|
|
3528
|
+
font-size: 14px;
|
|
3529
|
+
cursor: pointer;
|
|
3530
|
+
width: 34px;
|
|
3531
|
+
height: 34px;
|
|
3532
|
+
display: flex;
|
|
3533
|
+
align-items: center;
|
|
3534
|
+
justify-content: center;
|
|
3535
|
+
}
|
|
3536
|
+
.history-toggle:hover { background: var(--border); }
|
|
3537
|
+
.history-panel {
|
|
3538
|
+
position: fixed;
|
|
3539
|
+
top: 0;
|
|
3540
|
+
right: 0;
|
|
3541
|
+
width: 320px;
|
|
3542
|
+
height: 100vh;
|
|
3543
|
+
background: var(--panel-solid);
|
|
3544
|
+
border-left: 1px solid var(--border);
|
|
3545
|
+
z-index: 90;
|
|
3546
|
+
transform: translateX(100%);
|
|
3547
|
+
transition: transform 0.25s ease;
|
|
3548
|
+
display: flex;
|
|
3549
|
+
flex-direction: column;
|
|
3550
|
+
}
|
|
3551
|
+
.history-panel.open { transform: translateX(0); }
|
|
3552
|
+
.history-panel-header {
|
|
3553
|
+
padding: 16px;
|
|
3554
|
+
border-bottom: 1px solid var(--border);
|
|
3555
|
+
display: flex;
|
|
3556
|
+
justify-content: space-between;
|
|
3557
|
+
align-items: center;
|
|
3558
|
+
}
|
|
3559
|
+
.history-panel-header h3 { margin: 0; font-size: 14px; font-weight: 600; }
|
|
3560
|
+
.history-panel-close {
|
|
3561
|
+
background: transparent;
|
|
3562
|
+
border: none;
|
|
3563
|
+
color: var(--muted);
|
|
3564
|
+
cursor: pointer;
|
|
3565
|
+
font-size: 18px;
|
|
3566
|
+
padding: 4px;
|
|
3567
|
+
}
|
|
3568
|
+
.history-panel-close:hover { color: var(--text); }
|
|
3569
|
+
.history-panel-body {
|
|
3570
|
+
flex: 1;
|
|
3571
|
+
overflow-y: auto;
|
|
3572
|
+
padding: 12px;
|
|
3573
|
+
}
|
|
3574
|
+
.history-empty {
|
|
3575
|
+
color: var(--muted);
|
|
3576
|
+
font-size: 13px;
|
|
3577
|
+
text-align: center;
|
|
3578
|
+
padding: 40px 20px;
|
|
3579
|
+
}
|
|
3580
|
+
.history-date-group { margin-bottom: 16px; }
|
|
3581
|
+
.history-date {
|
|
3582
|
+
font-size: 11px;
|
|
3583
|
+
font-weight: 600;
|
|
3584
|
+
color: var(--muted);
|
|
3585
|
+
margin-bottom: 8px;
|
|
3586
|
+
text-transform: uppercase;
|
|
3587
|
+
}
|
|
3588
|
+
.history-item {
|
|
3589
|
+
background: var(--bg);
|
|
3590
|
+
border: 1px solid var(--border);
|
|
3591
|
+
border-radius: 6px;
|
|
3592
|
+
margin-bottom: 8px;
|
|
3593
|
+
overflow: hidden;
|
|
3594
|
+
}
|
|
3595
|
+
.history-item-header {
|
|
3596
|
+
display: flex;
|
|
3597
|
+
justify-content: space-between;
|
|
3598
|
+
align-items: center;
|
|
3599
|
+
padding: 8px 10px;
|
|
3600
|
+
background: var(--selected-bg);
|
|
3601
|
+
cursor: pointer;
|
|
3602
|
+
}
|
|
3603
|
+
.history-item-header:hover { background: var(--hover-bg); }
|
|
3604
|
+
.history-item-file {
|
|
3605
|
+
font-size: 12px;
|
|
3606
|
+
font-weight: 600;
|
|
3607
|
+
color: var(--text);
|
|
3608
|
+
white-space: nowrap;
|
|
3609
|
+
overflow: hidden;
|
|
3610
|
+
text-overflow: ellipsis;
|
|
3611
|
+
max-width: 180px;
|
|
3612
|
+
}
|
|
3613
|
+
.history-item-time { font-size: 10px; color: var(--muted); }
|
|
3614
|
+
.history-item-body {
|
|
3615
|
+
display: none;
|
|
3616
|
+
padding: 10px;
|
|
3617
|
+
font-size: 12px;
|
|
3618
|
+
border-top: 1px solid var(--border);
|
|
3619
|
+
}
|
|
3620
|
+
.history-item.expanded .history-item-body { display: block; }
|
|
3621
|
+
.history-summary {
|
|
3622
|
+
color: var(--text);
|
|
3623
|
+
margin-bottom: 8px;
|
|
3624
|
+
padding-bottom: 8px;
|
|
3625
|
+
border-bottom: 1px solid var(--border);
|
|
3626
|
+
}
|
|
3627
|
+
.history-summary-label {
|
|
3628
|
+
font-size: 10px;
|
|
3629
|
+
font-weight: 600;
|
|
3630
|
+
color: var(--muted);
|
|
3631
|
+
margin-bottom: 4px;
|
|
3632
|
+
}
|
|
3633
|
+
.history-summary-text { white-space: pre-wrap; line-height: 1.4; }
|
|
3634
|
+
.history-comments-label {
|
|
3635
|
+
font-size: 10px;
|
|
3636
|
+
font-weight: 600;
|
|
3637
|
+
color: var(--muted);
|
|
3638
|
+
margin-bottom: 6px;
|
|
3639
|
+
}
|
|
3640
|
+
.history-comment {
|
|
3641
|
+
padding: 6px 0;
|
|
3642
|
+
border-bottom: 1px solid var(--border);
|
|
3643
|
+
}
|
|
3644
|
+
.history-comment:last-child { border-bottom: none; }
|
|
3645
|
+
.history-comment-line {
|
|
3646
|
+
font-size: 10px;
|
|
3647
|
+
color: var(--accent);
|
|
3648
|
+
font-weight: 600;
|
|
3649
|
+
margin-bottom: 2px;
|
|
3650
|
+
}
|
|
3651
|
+
.history-comment-quote {
|
|
3652
|
+
background: rgba(0, 0, 0, 0.3);
|
|
3653
|
+
border-left: 2px solid var(--accent);
|
|
3654
|
+
padding: 4px 8px;
|
|
3655
|
+
margin: 4px 0;
|
|
3656
|
+
font-family: 'SF Mono', Monaco, Consolas, monospace;
|
|
3657
|
+
font-size: 11px;
|
|
3658
|
+
color: var(--muted);
|
|
3659
|
+
white-space: pre-wrap;
|
|
3660
|
+
word-break: break-all;
|
|
3661
|
+
max-height: 80px;
|
|
3662
|
+
overflow-y: auto;
|
|
3663
|
+
}
|
|
3664
|
+
.history-comment-text {
|
|
3665
|
+
color: var(--text);
|
|
3666
|
+
line-height: 1.4;
|
|
3667
|
+
white-space: pre-wrap;
|
|
3668
|
+
}
|
|
3669
|
+
.history-badge {
|
|
3670
|
+
display: inline-block;
|
|
3671
|
+
background: var(--accent);
|
|
3672
|
+
color: var(--text-inverse);
|
|
3673
|
+
font-size: 10px;
|
|
3674
|
+
padding: 2px 6px;
|
|
3675
|
+
border-radius: 10px;
|
|
3676
|
+
margin-left: 6px;
|
|
3677
|
+
}
|
|
3093
3678
|
</style>
|
|
3094
3679
|
<script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
|
|
3095
3680
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11/build/styles/github-dark.min.css" id="hljs-theme-dark">
|
|
@@ -3104,6 +3689,7 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
3104
3689
|
<span class="pill">Comments <strong id="comment-count">0</strong></span>
|
|
3105
3690
|
</div>
|
|
3106
3691
|
<div class="actions">
|
|
3692
|
+
<button class="history-toggle" id="history-toggle" title="Review History">☰</button>
|
|
3107
3693
|
<button class="theme-toggle" id="theme-toggle" title="Toggle theme" aria-label="Toggle theme">
|
|
3108
3694
|
<span id="theme-icon">🌙</span>
|
|
3109
3695
|
</button>
|
|
@@ -3111,6 +3697,17 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
3111
3697
|
</div>
|
|
3112
3698
|
</header>
|
|
3113
3699
|
|
|
3700
|
+
<!-- History Panel -->
|
|
3701
|
+
<aside class="history-panel" id="history-panel">
|
|
3702
|
+
<div class="history-panel-header">
|
|
3703
|
+
<h3>📜 Review History</h3>
|
|
3704
|
+
<button class="history-panel-close" id="history-panel-close">✕</button>
|
|
3705
|
+
</div>
|
|
3706
|
+
<div class="history-panel-body" id="history-panel-body">
|
|
3707
|
+
<div class="history-empty">No review history yet.</div>
|
|
3708
|
+
</div>
|
|
3709
|
+
</aside>
|
|
3710
|
+
|
|
3114
3711
|
<div class="wrap">
|
|
3115
3712
|
${
|
|
3116
3713
|
hasPreview && mode === "markdown"
|
|
@@ -3285,6 +3882,7 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
3285
3882
|
const FILE_NAME = ${titleJson};
|
|
3286
3883
|
const MODE = ${modeJson};
|
|
3287
3884
|
const REVIW_QUESTIONS = ${questionsJson};
|
|
3885
|
+
const HISTORY_DATA = ${historyJson};
|
|
3288
3886
|
|
|
3289
3887
|
// --- Theme Management ---
|
|
3290
3888
|
(function initTheme() {
|
|
@@ -3337,6 +3935,134 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
3337
3935
|
themeToggle.addEventListener('click', toggleTheme);
|
|
3338
3936
|
})();
|
|
3339
3937
|
|
|
3938
|
+
// --- History Management ---
|
|
3939
|
+
// History is now server-side (file-based), HISTORY_DATA is provided by server
|
|
3940
|
+
|
|
3941
|
+
function loadHistory() {
|
|
3942
|
+
// Return server-provided history data
|
|
3943
|
+
return Array.isArray(HISTORY_DATA) ? HISTORY_DATA : [];
|
|
3944
|
+
}
|
|
3945
|
+
|
|
3946
|
+
// saveToHistory is handled server-side via /exit endpoint
|
|
3947
|
+
function saveToHistory(payload) {
|
|
3948
|
+
// No-op on client - server saves history when receiving /exit
|
|
3949
|
+
}
|
|
3950
|
+
|
|
3951
|
+
function formatDate(isoString) {
|
|
3952
|
+
const d = new Date(isoString);
|
|
3953
|
+
return d.toLocaleDateString('ja-JP', { year: 'numeric', month: '2-digit', day: '2-digit' });
|
|
3954
|
+
}
|
|
3955
|
+
|
|
3956
|
+
function formatTime(isoString) {
|
|
3957
|
+
const d = new Date(isoString);
|
|
3958
|
+
return d.toLocaleTimeString('ja-JP', { hour: '2-digit', minute: '2-digit' });
|
|
3959
|
+
}
|
|
3960
|
+
|
|
3961
|
+
function getBasename(filepath) {
|
|
3962
|
+
return filepath.split('/').pop() || filepath;
|
|
3963
|
+
}
|
|
3964
|
+
|
|
3965
|
+
function escapeHtmlForHistory(s) {
|
|
3966
|
+
if (!s) return '';
|
|
3967
|
+
return String(s).replace(/[&<>"]/g, c => ({'&':'&','<':'<','>':'>','"':'"'}[c] || c));
|
|
3968
|
+
}
|
|
3969
|
+
|
|
3970
|
+
function renderHistoryPanel() {
|
|
3971
|
+
const body = document.getElementById('history-panel-body');
|
|
3972
|
+
const history = loadHistory();
|
|
3973
|
+
if (history.length === 0) {
|
|
3974
|
+
body.innerHTML = '<div class="history-empty">No review history yet.</div>';
|
|
3975
|
+
return;
|
|
3976
|
+
}
|
|
3977
|
+
|
|
3978
|
+
const grouped = {};
|
|
3979
|
+
history.forEach((item, idx) => {
|
|
3980
|
+
const date = formatDate(item.submittedAt);
|
|
3981
|
+
if (!grouped[date]) grouped[date] = [];
|
|
3982
|
+
grouped[date].push({ ...item, _idx: idx });
|
|
3983
|
+
});
|
|
3984
|
+
|
|
3985
|
+
let html = '';
|
|
3986
|
+
for (const date of Object.keys(grouped)) {
|
|
3987
|
+
html += \`<div class="history-date-group">
|
|
3988
|
+
<div class="history-date">\${date}</div>\`;
|
|
3989
|
+
for (const item of grouped[date]) {
|
|
3990
|
+
const commentCount = item.comments?.length || 0;
|
|
3991
|
+
html += \`<div class="history-item" data-idx="\${item._idx}">
|
|
3992
|
+
<div class="history-item-header">
|
|
3993
|
+
<span class="history-item-file">\${escapeHtmlForHistory(getBasename(item.file))}</span>
|
|
3994
|
+
<span class="history-item-time">\${formatTime(item.submittedAt)}<span class="history-badge">\${commentCount}</span></span>
|
|
3995
|
+
</div>
|
|
3996
|
+
<div class="history-item-body">\`;
|
|
3997
|
+
if (item.summary) {
|
|
3998
|
+
html += \`<div class="history-summary">
|
|
3999
|
+
<div class="history-summary-label">Summary</div>
|
|
4000
|
+
<div class="history-summary-text">\${escapeHtmlForHistory(item.summary)}</div>
|
|
4001
|
+
</div>\`;
|
|
4002
|
+
}
|
|
4003
|
+
if (commentCount > 0) {
|
|
4004
|
+
html += \`<div class="history-comments-label">Line Comments (\${commentCount})</div>\`;
|
|
4005
|
+
for (const c of item.comments) {
|
|
4006
|
+
const lineLabel = c.line ? \`L\${c.line}\${c.lineEnd ? '-' + c.lineEnd : ''}\` : (c.row != null ? \`L\${c.row}\` : '');
|
|
4007
|
+
const text = c.comment || c.text || '';
|
|
4008
|
+
// Support both direct content and context.content structures
|
|
4009
|
+
const content = c.content || c.context?.content || c.value || '';
|
|
4010
|
+
html += \`<div class="history-comment">
|
|
4011
|
+
<div class="history-comment-line">\${lineLabel}</div>\`;
|
|
4012
|
+
if (content) {
|
|
4013
|
+
html += \`<div class="history-comment-quote">\${escapeHtmlForHistory(content)}</div>\`;
|
|
4014
|
+
}
|
|
4015
|
+
html += \`<div class="history-comment-text">\${escapeHtmlForHistory(text)}</div>
|
|
4016
|
+
</div>\`;
|
|
4017
|
+
}
|
|
4018
|
+
}
|
|
4019
|
+
html += \`</div></div>\`;
|
|
4020
|
+
}
|
|
4021
|
+
html += \`</div>\`;
|
|
4022
|
+
}
|
|
4023
|
+
body.innerHTML = html;
|
|
4024
|
+
|
|
4025
|
+
body.querySelectorAll('.history-item-header').forEach(header => {
|
|
4026
|
+
header.addEventListener('click', () => {
|
|
4027
|
+
header.parentElement.classList.toggle('expanded');
|
|
4028
|
+
});
|
|
4029
|
+
});
|
|
4030
|
+
}
|
|
4031
|
+
|
|
4032
|
+
// History Panel Toggle
|
|
4033
|
+
(function initHistoryPanel() {
|
|
4034
|
+
const toggle = document.getElementById('history-toggle');
|
|
4035
|
+
const panel = document.getElementById('history-panel');
|
|
4036
|
+
const closeBtn = document.getElementById('history-panel-close');
|
|
4037
|
+
|
|
4038
|
+
function openPanel() {
|
|
4039
|
+
panel.classList.add('open');
|
|
4040
|
+
document.body.classList.add('history-open');
|
|
4041
|
+
renderHistoryPanel();
|
|
4042
|
+
}
|
|
4043
|
+
|
|
4044
|
+
function closePanel() {
|
|
4045
|
+
panel.classList.remove('open');
|
|
4046
|
+
document.body.classList.remove('history-open');
|
|
4047
|
+
}
|
|
4048
|
+
|
|
4049
|
+
toggle?.addEventListener('click', () => {
|
|
4050
|
+
if (panel.classList.contains('open')) {
|
|
4051
|
+
closePanel();
|
|
4052
|
+
} else {
|
|
4053
|
+
openPanel();
|
|
4054
|
+
}
|
|
4055
|
+
});
|
|
4056
|
+
|
|
4057
|
+
closeBtn?.addEventListener('click', closePanel);
|
|
4058
|
+
|
|
4059
|
+
document.addEventListener('keydown', (e) => {
|
|
4060
|
+
if (e.key === 'Escape' && panel.classList.contains('open')) {
|
|
4061
|
+
closePanel();
|
|
4062
|
+
}
|
|
4063
|
+
});
|
|
4064
|
+
})();
|
|
4065
|
+
|
|
3340
4066
|
const tbody = document.getElementById('tbody');
|
|
3341
4067
|
const table = document.getElementById('csv-table');
|
|
3342
4068
|
const colgroup = document.getElementById('colgroup');
|
|
@@ -4083,7 +4809,18 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
4083
4809
|
document.getElementById('clear-comment').addEventListener('click', clearCurrent);
|
|
4084
4810
|
document.getElementById('close-card').addEventListener('click', closeCard);
|
|
4085
4811
|
document.addEventListener('keydown', (e) => {
|
|
4086
|
-
if (e.key === 'Escape')
|
|
4812
|
+
if (e.key === 'Escape') {
|
|
4813
|
+
// Don't close card if any fullscreen overlay is open
|
|
4814
|
+
const imageOverlay = document.getElementById('image-fullscreen');
|
|
4815
|
+
const videoOverlay = document.getElementById('video-fullscreen');
|
|
4816
|
+
const mermaidOverlay = document.getElementById('mermaid-fullscreen');
|
|
4817
|
+
if (imageOverlay?.classList.contains('visible') ||
|
|
4818
|
+
videoOverlay?.classList.contains('visible') ||
|
|
4819
|
+
mermaidOverlay?.classList.contains('visible')) {
|
|
4820
|
+
return; // Let the fullscreen handlers handle ESC
|
|
4821
|
+
}
|
|
4822
|
+
closeCard();
|
|
4823
|
+
}
|
|
4087
4824
|
if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') saveCurrent();
|
|
4088
4825
|
});
|
|
4089
4826
|
|
|
@@ -4366,7 +5103,9 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
4366
5103
|
if (sent) return;
|
|
4367
5104
|
sent = true;
|
|
4368
5105
|
clearCommentsFromStorage();
|
|
4369
|
-
const
|
|
5106
|
+
const p = payload(reason);
|
|
5107
|
+
saveToHistory(p);
|
|
5108
|
+
const blob = new Blob([JSON.stringify(p)], { type: 'application/json' });
|
|
4370
5109
|
navigator.sendBeacon('/exit', blob);
|
|
4371
5110
|
}
|
|
4372
5111
|
function showSubmitModal() {
|
|
@@ -5684,11 +6423,12 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
5684
6423
|
|
|
5685
6424
|
function buildHtml(filePath) {
|
|
5686
6425
|
const data = loadData(filePath);
|
|
6426
|
+
const history = loadHistoryFromFile(filePath);
|
|
5687
6427
|
if (data.mode === "diff") {
|
|
5688
|
-
return diffHtmlTemplate(data);
|
|
6428
|
+
return diffHtmlTemplate(data, history);
|
|
5689
6429
|
}
|
|
5690
6430
|
const { rows, cols, projectRoot, relativePath, mode, preview, reviwQuestions } = data;
|
|
5691
|
-
return htmlTemplate(rows, cols, projectRoot, relativePath, mode, preview, reviwQuestions);
|
|
6431
|
+
return htmlTemplate(rows, cols, projectRoot, relativePath, mode, preview, reviwQuestions, history);
|
|
5692
6432
|
}
|
|
5693
6433
|
|
|
5694
6434
|
// --- HTTP Server -----------------------------------------------------------
|
|
@@ -5808,6 +6548,54 @@ function checkExistingServer(filePath) {
|
|
|
5808
6548
|
}
|
|
5809
6549
|
}
|
|
5810
6550
|
|
|
6551
|
+
// --- History File Management ---
|
|
6552
|
+
const HISTORY_DIR = path.join(os.homedir(), '.reviw', 'history');
|
|
6553
|
+
const HISTORY_MAX = 50;
|
|
6554
|
+
|
|
6555
|
+
function getHistoryFilePath(filePath) {
|
|
6556
|
+
// Use SHA256 hash of absolute path (same as lock files)
|
|
6557
|
+
const hash = crypto.createHash('sha256').update(path.resolve(filePath)).digest('hex').slice(0, 16);
|
|
6558
|
+
return path.join(HISTORY_DIR, hash + '.json');
|
|
6559
|
+
}
|
|
6560
|
+
|
|
6561
|
+
function ensureHistoryDir() {
|
|
6562
|
+
try {
|
|
6563
|
+
if (!fs.existsSync(HISTORY_DIR)) {
|
|
6564
|
+
fs.mkdirSync(HISTORY_DIR, { recursive: true, mode: 0o700 });
|
|
6565
|
+
}
|
|
6566
|
+
} catch (err) {
|
|
6567
|
+
// Ignore errors
|
|
6568
|
+
}
|
|
6569
|
+
}
|
|
6570
|
+
|
|
6571
|
+
function loadHistoryFromFile(filePath) {
|
|
6572
|
+
try {
|
|
6573
|
+
const historyPath = getHistoryFilePath(filePath);
|
|
6574
|
+
if (!fs.existsSync(historyPath)) {
|
|
6575
|
+
return [];
|
|
6576
|
+
}
|
|
6577
|
+
const data = JSON.parse(fs.readFileSync(historyPath, 'utf8'));
|
|
6578
|
+
return Array.isArray(data) ? data : [];
|
|
6579
|
+
} catch (err) {
|
|
6580
|
+
return [];
|
|
6581
|
+
}
|
|
6582
|
+
}
|
|
6583
|
+
|
|
6584
|
+
function saveHistoryToFile(filePath, historyEntry) {
|
|
6585
|
+
try {
|
|
6586
|
+
ensureHistoryDir();
|
|
6587
|
+
const historyPath = getHistoryFilePath(filePath);
|
|
6588
|
+
let history = loadHistoryFromFile(filePath);
|
|
6589
|
+
history.unshift(historyEntry);
|
|
6590
|
+
if (history.length > HISTORY_MAX) {
|
|
6591
|
+
history = history.slice(0, HISTORY_MAX);
|
|
6592
|
+
}
|
|
6593
|
+
fs.writeFileSync(historyPath, JSON.stringify(history, null, 2), { mode: 0o600 });
|
|
6594
|
+
} catch (err) {
|
|
6595
|
+
// Ignore errors - history is optional
|
|
6596
|
+
}
|
|
6597
|
+
}
|
|
6598
|
+
|
|
5811
6599
|
// Try to activate an existing browser tab with the given URL (macOS only)
|
|
5812
6600
|
// Returns true if a tab was activated, false otherwise
|
|
5813
6601
|
function tryActivateExistingTab(url) {
|
|
@@ -6083,6 +6871,10 @@ function createFileServer(filePath, fileIndex = 0) {
|
|
|
6083
6871
|
if (raw && raw.trim()) {
|
|
6084
6872
|
payload = JSON.parse(raw);
|
|
6085
6873
|
}
|
|
6874
|
+
// Save to file-based history (only if there are comments)
|
|
6875
|
+
if (payload && (payload.comments?.length > 0 || payload.submitComment)) {
|
|
6876
|
+
saveHistoryToFile(ctx.filePath, payload);
|
|
6877
|
+
}
|
|
6086
6878
|
res.writeHead(200, { "Content-Type": "text/plain" });
|
|
6087
6879
|
res.end("bye");
|
|
6088
6880
|
// Notify all tabs to close before shutting down
|
|
@@ -6345,6 +7137,12 @@ function createDiffServer(diffContent) {
|
|
|
6345
7137
|
if (raw && raw.trim()) {
|
|
6346
7138
|
payload = JSON.parse(raw);
|
|
6347
7139
|
}
|
|
7140
|
+
// Save to file-based history (only if there are comments)
|
|
7141
|
+
// For diff mode, use relativePath as identifier
|
|
7142
|
+
if (payload && (payload.comments?.length > 0 || payload.submitComment)) {
|
|
7143
|
+
const filePath = ctx.diffData?.relativePath || 'stdin-diff';
|
|
7144
|
+
saveHistoryToFile(filePath, payload);
|
|
7145
|
+
}
|
|
6348
7146
|
res.writeHead(200, { "Content-Type": "text/plain" });
|
|
6349
7147
|
res.end("bye");
|
|
6350
7148
|
// Notify all tabs to close before shutting down
|