reviw 0.22.0 → 0.23.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.
- package/cli.cjs +247 -36
- package/package.json +1 -1
package/cli.cjs
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Multiple files can be specified. Each file opens on a separate port.
|
|
9
9
|
* Click cells in the browser to add comments.
|
|
10
|
-
*
|
|
10
|
+
* Click "Submit & Exit" to send comments to the server.
|
|
11
11
|
* When all files are closed, outputs combined YAML to stdout and exits.
|
|
12
12
|
*/
|
|
13
13
|
|
|
@@ -1871,7 +1871,7 @@ function diffHtmlTemplate(diffData, history = []) {
|
|
|
1871
1871
|
<aside class="comment-list collapsed">
|
|
1872
1872
|
<h3>Comments</h3>
|
|
1873
1873
|
<ol id="comment-list"></ol>
|
|
1874
|
-
<p class="hint">Click "Submit & Exit" to finish review.</p>
|
|
1874
|
+
<p class="hint">Click "Submit & Exit" to finish review and output results.</p>
|
|
1875
1875
|
</aside>
|
|
1876
1876
|
|
|
1877
1877
|
<div class="modal-overlay" id="submit-modal">
|
|
@@ -2591,7 +2591,7 @@ function diffHtmlTemplate(diffData, history = []) {
|
|
|
2591
2591
|
document.getElementById('modal-cancel').addEventListener('click', hideSubmitModal);
|
|
2592
2592
|
async function doSubmit() {
|
|
2593
2593
|
globalComment = globalCommentInput.value;
|
|
2594
|
-
savePromptPrefs();
|
|
2594
|
+
if (typeof savePromptPrefs === 'function') savePromptPrefs();
|
|
2595
2595
|
hideSubmitModal();
|
|
2596
2596
|
await sendAndExit('button');
|
|
2597
2597
|
// Try to close window; if it fails (browser security), show completion message
|
|
@@ -2956,6 +2956,7 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
2956
2956
|
td:hover:not(.selected) { background: var(--hover-bg); box-shadow: inset 0 0 0 1px rgba(96,165,250,0.25); }
|
|
2957
2957
|
td.has-comment { background: rgba(34,197,94,0.12); box-shadow: inset 0 0 0 1px rgba(34,197,94,0.35); }
|
|
2958
2958
|
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); }
|
|
2959
|
+
.preview-highlight { background: rgba(167,139,250,0.18) !important; box-shadow: inset 0 0 0 2px rgba(139,92,246,0.35); border-radius: 4px; transition: background 150ms ease, box-shadow 150ms ease; padding-left: 8px; margin-left: -8px; }
|
|
2959
2960
|
thead th.selected { background: #c7d2fe !important; box-shadow: inset 0 0 0 1px rgba(99,102,241,0.45); }
|
|
2960
2961
|
[data-theme="dark"] thead th.selected { background: #3730a3 !important; }
|
|
2961
2962
|
body.dragging { user-select: none; cursor: crosshair; }
|
|
@@ -3525,6 +3526,18 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
3525
3526
|
border-radius: 8px;
|
|
3526
3527
|
line-height: 1.4;
|
|
3527
3528
|
}
|
|
3529
|
+
.view-toggle {
|
|
3530
|
+
background: var(--panel);
|
|
3531
|
+
border: 1px solid var(--border);
|
|
3532
|
+
border-radius: 8px;
|
|
3533
|
+
padding: 6px 10px;
|
|
3534
|
+
cursor: pointer;
|
|
3535
|
+
color: var(--text);
|
|
3536
|
+
font-size: 14px;
|
|
3537
|
+
transition: background 0.15s ease, border-color 0.15s ease;
|
|
3538
|
+
}
|
|
3539
|
+
.view-toggle:hover { background: rgba(96,165,250,0.2); }
|
|
3540
|
+
.view-toggle.active { background: var(--accent); color: #fff; }
|
|
3528
3541
|
@media (max-width: 1200px) {
|
|
3529
3542
|
.media-sidebar-viewer.open {
|
|
3530
3543
|
width: 40vw;
|
|
@@ -4431,10 +4444,13 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
4431
4444
|
}
|
|
4432
4445
|
@media (max-width: 960px) {
|
|
4433
4446
|
.md-layout { flex-direction: column; }
|
|
4434
|
-
.md-left { max-width: 100%; flex: 0 0
|
|
4447
|
+
.md-left { max-width: 100%; flex: 1 1 0; min-height: 0; }
|
|
4448
|
+
.md-right { display: none; }
|
|
4435
4449
|
.media-sidebar { display: none; }
|
|
4436
4450
|
.media-sidebar-toggle { display: none !important; }
|
|
4437
4451
|
}
|
|
4452
|
+
.md-layout.preview-only .md-right { display: none; }
|
|
4453
|
+
.md-layout.preview-only .md-left { flex: 1 1 0; min-height: 0; max-width: 100%; }
|
|
4438
4454
|
.filter-menu {
|
|
4439
4455
|
position: absolute;
|
|
4440
4456
|
background: var(--panel-solid);
|
|
@@ -4867,6 +4883,7 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
4867
4883
|
</div>
|
|
4868
4884
|
<div class="actions">
|
|
4869
4885
|
<button class="media-sidebar-toggle" id="media-sidebar-toggle" title="Media Gallery" aria-label="Toggle media gallery">🖼<span class="toggle-count" id="media-toggle-count"></span></button>
|
|
4886
|
+
<button class="view-toggle" id="view-toggle" title="Hide source panel" aria-label="Toggle source panel">📝</button>
|
|
4870
4887
|
<button class="history-toggle" id="history-toggle" title="Review History">☰</button>
|
|
4871
4888
|
<button class="theme-toggle" id="theme-toggle" title="Toggle theme" aria-label="Toggle theme">
|
|
4872
4889
|
<span id="theme-icon">🌙</span>
|
|
@@ -4967,7 +4984,7 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
4967
4984
|
<aside class="comment-list collapsed">
|
|
4968
4985
|
<h3>Comments</h3>
|
|
4969
4986
|
<ol id="comment-list"></ol>
|
|
4970
|
-
<p class="hint">
|
|
4987
|
+
<p class="hint">Click "Submit & Exit" to send comments and stop the server.</p>
|
|
4971
4988
|
</aside>
|
|
4972
4989
|
<div class="filter-menu" id="filter-menu">
|
|
4973
4990
|
<label class="menu-check"><input type="checkbox" id="freeze-col-check" /> Freeze up to this column</label>
|
|
@@ -6133,10 +6150,17 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
6133
6150
|
commentInput.value = existingComment?.text || '';
|
|
6134
6151
|
|
|
6135
6152
|
card.style.display = 'block';
|
|
6136
|
-
//
|
|
6137
|
-
|
|
6138
|
-
|
|
6139
|
-
|
|
6153
|
+
// Check if source panel is hidden (preview-only mode or narrow viewport)
|
|
6154
|
+
const mdRight = document.querySelector('.md-right');
|
|
6155
|
+
const isSourceHidden = mdRight && (mdRight.offsetParent === null || getComputedStyle(mdRight).display === 'none');
|
|
6156
|
+
|
|
6157
|
+
if (isSourceHidden && previewElement) {
|
|
6158
|
+
// In preview-only mode, position card below the clicked preview element
|
|
6159
|
+
positionCardBelowElement(previewElement);
|
|
6160
|
+
} else {
|
|
6161
|
+
// 常にソーステーブルの選択セル位置を基準にカードを配置
|
|
6162
|
+
positionCardForSelection(startRow, endRow, startCol, endCol);
|
|
6163
|
+
}
|
|
6140
6164
|
commentInput.focus();
|
|
6141
6165
|
}
|
|
6142
6166
|
|
|
@@ -6177,6 +6201,61 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
6177
6201
|
card.style.top = top + 'px';
|
|
6178
6202
|
}
|
|
6179
6203
|
|
|
6204
|
+
// Position card below a clicked element (used in preview-only / narrow mode)
|
|
6205
|
+
// Falls back to above the element if below doesn't fit
|
|
6206
|
+
function positionCardBelowElement(element) {
|
|
6207
|
+
const cardWidth = card.offsetWidth || 380;
|
|
6208
|
+
const cardHeight = card.offsetHeight || 220;
|
|
6209
|
+
const margin = 12;
|
|
6210
|
+
const vw = window.innerWidth;
|
|
6211
|
+
const vh = window.innerHeight;
|
|
6212
|
+
|
|
6213
|
+
const mdLeft = document.querySelector('.md-left');
|
|
6214
|
+
let rect = element.getBoundingClientRect();
|
|
6215
|
+
|
|
6216
|
+
// Try below first: scroll to make room if needed
|
|
6217
|
+
const spaceBelow = vh - rect.bottom - margin;
|
|
6218
|
+
if (spaceBelow < cardHeight && mdLeft) {
|
|
6219
|
+
const scrollNeeded = cardHeight - spaceBelow + margin;
|
|
6220
|
+
const scrollBefore = mdLeft.scrollTop;
|
|
6221
|
+
mdLeft.scrollBy({ top: scrollNeeded, behavior: 'instant' });
|
|
6222
|
+
// Recalculate after scroll
|
|
6223
|
+
rect = element.getBoundingClientRect();
|
|
6224
|
+
}
|
|
6225
|
+
|
|
6226
|
+
// Horizontal: ensure card fits
|
|
6227
|
+
let left = rect.left;
|
|
6228
|
+
if (left + cardWidth > vw - margin) {
|
|
6229
|
+
left = vw - cardWidth - margin;
|
|
6230
|
+
}
|
|
6231
|
+
left = Math.max(margin, left);
|
|
6232
|
+
|
|
6233
|
+
// Vertical: prefer below, fallback to above
|
|
6234
|
+
const spaceBelowAfterScroll = vh - rect.bottom - margin;
|
|
6235
|
+
const spaceAbove = rect.top - margin;
|
|
6236
|
+
let top;
|
|
6237
|
+
|
|
6238
|
+
if (spaceBelowAfterScroll >= cardHeight) {
|
|
6239
|
+
// Fits below
|
|
6240
|
+
top = rect.bottom + margin;
|
|
6241
|
+
} else if (spaceAbove >= cardHeight) {
|
|
6242
|
+
// Doesn't fit below, but fits above
|
|
6243
|
+
top = rect.top - cardHeight - margin;
|
|
6244
|
+
} else {
|
|
6245
|
+
// Doesn't fit either way: choose the side with more space
|
|
6246
|
+
if (spaceAbove > spaceBelowAfterScroll) {
|
|
6247
|
+
top = Math.max(margin, rect.top - cardHeight - margin);
|
|
6248
|
+
} else {
|
|
6249
|
+
top = rect.bottom + margin;
|
|
6250
|
+
top = Math.min(top, vh - cardHeight - margin);
|
|
6251
|
+
}
|
|
6252
|
+
}
|
|
6253
|
+
|
|
6254
|
+
top = Math.max(margin, top);
|
|
6255
|
+
card.style.left = left + 'px';
|
|
6256
|
+
card.style.top = top + 'px';
|
|
6257
|
+
}
|
|
6258
|
+
|
|
6180
6259
|
function positionCardForSelection(startRow, endRow, startCol, endCol) {
|
|
6181
6260
|
const cardWidth = card.offsetWidth || 380;
|
|
6182
6261
|
const cardHeight = card.offsetHeight || 220;
|
|
@@ -6255,6 +6334,8 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
6255
6334
|
card.style.display = 'none';
|
|
6256
6335
|
currentKey = null;
|
|
6257
6336
|
clearSelection();
|
|
6337
|
+
// Clear preview highlight
|
|
6338
|
+
document.querySelectorAll('.preview-highlight').forEach(el => el.classList.remove('preview-highlight'));
|
|
6258
6339
|
// Re-enable scroll sync when card is closed
|
|
6259
6340
|
window._disableScrollSync = false;
|
|
6260
6341
|
}
|
|
@@ -7145,6 +7226,52 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
7145
7226
|
}
|
|
7146
7227
|
}
|
|
7147
7228
|
|
|
7229
|
+
// --- Panel State (Preview-Only Toggle) ---
|
|
7230
|
+
const PANEL_STATE_KEY = 'reviw-panel-state';
|
|
7231
|
+
(function initPanelState() {
|
|
7232
|
+
const viewToggle = document.getElementById('view-toggle');
|
|
7233
|
+
const mdLayout = document.querySelector('.md-layout');
|
|
7234
|
+
if (!viewToggle || !mdLayout) return;
|
|
7235
|
+
|
|
7236
|
+
function applyPanelState(isPreviewOnly) {
|
|
7237
|
+
if (isPreviewOnly) {
|
|
7238
|
+
mdLayout.classList.add('preview-only');
|
|
7239
|
+
viewToggle.textContent = '\u{1F441}';
|
|
7240
|
+
viewToggle.title = 'Show source panel';
|
|
7241
|
+
viewToggle.classList.add('active');
|
|
7242
|
+
} else {
|
|
7243
|
+
mdLayout.classList.remove('preview-only');
|
|
7244
|
+
viewToggle.textContent = '\u{1F4DD}';
|
|
7245
|
+
viewToggle.title = 'Hide source panel';
|
|
7246
|
+
viewToggle.classList.remove('active');
|
|
7247
|
+
}
|
|
7248
|
+
}
|
|
7249
|
+
|
|
7250
|
+
// Load saved state
|
|
7251
|
+
const saved = localStorage.getItem(PANEL_STATE_KEY);
|
|
7252
|
+
if (saved === 'preview-only') {
|
|
7253
|
+
applyPanelState(true);
|
|
7254
|
+
}
|
|
7255
|
+
|
|
7256
|
+
// Also apply preview-only for narrow viewports (auto-detect)
|
|
7257
|
+
function checkNarrowMode() {
|
|
7258
|
+
if (window.innerWidth <= 960) {
|
|
7259
|
+
// In narrow mode, always act as preview-only (source is hidden by CSS)
|
|
7260
|
+
viewToggle.style.display = 'none';
|
|
7261
|
+
} else {
|
|
7262
|
+
viewToggle.style.display = '';
|
|
7263
|
+
}
|
|
7264
|
+
}
|
|
7265
|
+
checkNarrowMode();
|
|
7266
|
+
window.addEventListener('resize', checkNarrowMode);
|
|
7267
|
+
|
|
7268
|
+
viewToggle.addEventListener('click', () => {
|
|
7269
|
+
const isNowPreviewOnly = !mdLayout.classList.contains('preview-only');
|
|
7270
|
+
applyPanelState(isNowPreviewOnly);
|
|
7271
|
+
localStorage.setItem(PANEL_STATE_KEY, isNowPreviewOnly ? 'preview-only' : 'both');
|
|
7272
|
+
});
|
|
7273
|
+
})();
|
|
7274
|
+
|
|
7148
7275
|
// --- Mermaid Initialization ---
|
|
7149
7276
|
(function initMermaid() {
|
|
7150
7277
|
if (typeof mermaid === 'undefined') return;
|
|
@@ -8867,13 +8994,43 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
8867
8994
|
}
|
|
8868
8995
|
|
|
8869
8996
|
// Helper: find matching source line for table cell (prioritizes table rows)
|
|
8870
|
-
function findTableSourceLine(text, startFromLine) {
|
|
8997
|
+
function findTableSourceLine(text, startFromLine, element = null) {
|
|
8871
8998
|
if (!text) return -1;
|
|
8872
8999
|
startFromLine = startFromLine || 0;
|
|
8873
9000
|
// Remove toggle icon characters (▼, ▶) that may be included from heading toggles
|
|
8874
9001
|
const cleanText = text.replace(/[▼▶]/g, '').trim();
|
|
8875
9002
|
const normalized = cleanText.replace(/\\s+/g, ' ').slice(0, 100);
|
|
8876
9003
|
if (!normalized) return -1;
|
|
9004
|
+
const normalizedLower = normalized.toLowerCase();
|
|
9005
|
+
|
|
9006
|
+
function escapeRegExp(s) {
|
|
9007
|
+
return s.replace(/[\\^$.*+?()[\]{}|]/g, '\\$&');
|
|
9008
|
+
}
|
|
9009
|
+
|
|
9010
|
+
function normalizeCellText(cellText) {
|
|
9011
|
+
return stripMarkdown(cellText)
|
|
9012
|
+
.replace(/\\s+/g, ' ')
|
|
9013
|
+
.trim()
|
|
9014
|
+
.slice(0, 100);
|
|
9015
|
+
}
|
|
9016
|
+
|
|
9017
|
+
// If this click comes from a linked table cell, prefer matching by href for stable mapping.
|
|
9018
|
+
if (element) {
|
|
9019
|
+
const linkEl = element.querySelector('a[href]') || (element.matches?.('a[href]') ? element : null);
|
|
9020
|
+
const href = linkEl ? linkEl.getAttribute('href') : '';
|
|
9021
|
+
if (href) {
|
|
9022
|
+
var hrefSearchPasses = startFromLine > 0 ? [startFromLine, 0] : [0];
|
|
9023
|
+
for (var hrefPass = 0; hrefPass < hrefSearchPasses.length; hrefPass++) {
|
|
9024
|
+
var hrefSearchStart = hrefSearchPasses[hrefPass];
|
|
9025
|
+
var hrefSearchEnd = hrefPass === 0 && startFromLine > 0 ? DATA.length : (startFromLine > 0 ? startFromLine : DATA.length);
|
|
9026
|
+
for (let i = hrefSearchStart; i < hrefSearchEnd; i++) {
|
|
9027
|
+
const lineText = (DATA[i][0] || '').trim();
|
|
9028
|
+
if (!lineText || !lineText.startsWith('|')) continue;
|
|
9029
|
+
if (lineText.includes(href)) return i + 1;
|
|
9030
|
+
}
|
|
9031
|
+
}
|
|
9032
|
+
}
|
|
9033
|
+
}
|
|
8877
9034
|
|
|
8878
9035
|
// Two-pass strategy: search from startFromLine first, then fallback to 0
|
|
8879
9036
|
var searchPasses = startFromLine > 0 ? [startFromLine, 0] : [0];
|
|
@@ -8886,27 +9043,37 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
8886
9043
|
const lineText = (DATA[i][0] || '').trim();
|
|
8887
9044
|
if (!lineText || !lineText.startsWith('|')) continue;
|
|
8888
9045
|
|
|
8889
|
-
// Split into cells and check for exact match
|
|
9046
|
+
// Split into cells and check for exact match, including markdown link display text.
|
|
8890
9047
|
const cells = lineText.split('|').map(c => c.trim());
|
|
8891
9048
|
for (const cell of cells) {
|
|
8892
|
-
|
|
8893
|
-
|
|
9049
|
+
if (!cell) continue;
|
|
9050
|
+
const plainCell = normalizeCellText(cell);
|
|
8894
9051
|
|
|
8895
|
-
//
|
|
9052
|
+
// Exact raw-cell or rendered-cell text match.
|
|
8896
9053
|
if (cell === normalized) return i + 1;
|
|
9054
|
+
if (plainCell === normalized) return i + 1;
|
|
9055
|
+
if (plainCell.toLowerCase() === normalizedLower) return i + 1;
|
|
8897
9056
|
|
|
8898
|
-
// For short
|
|
8899
|
-
if (normalized.length <= 5 &&
|
|
9057
|
+
// For short labels (e.g. Go/OK), allow whole-word match in rendered cell.
|
|
9058
|
+
if (normalized.length <= 5 && plainCell) {
|
|
9059
|
+
const wordPattern = new RegExp('(^|\\s)' + escapeRegExp(normalizedLower) + '(\\s|$)', 'i');
|
|
9060
|
+
if (wordPattern.test(plainCell.toLowerCase())) return i + 1;
|
|
9061
|
+
}
|
|
8900
9062
|
}
|
|
8901
9063
|
}
|
|
8902
9064
|
|
|
8903
|
-
// Second pass:
|
|
9065
|
+
// Second pass: partial rendered-cell matching.
|
|
8904
9066
|
for (let i = searchStart; i < searchEnd; i++) {
|
|
8905
9067
|
const lineText = (DATA[i][0] || '').trim();
|
|
8906
9068
|
if (!lineText || !lineText.startsWith('|')) continue;
|
|
8907
9069
|
|
|
8908
|
-
const
|
|
8909
|
-
|
|
9070
|
+
const cells = lineText.split('|').map(c => normalizeCellText(c));
|
|
9071
|
+
for (const plainCell of cells) {
|
|
9072
|
+
if (!plainCell) continue;
|
|
9073
|
+
const plainLower = plainCell.toLowerCase();
|
|
9074
|
+
if (normalized.length > 5 && plainLower.includes(normalizedLower.slice(0, 30))) return i + 1;
|
|
9075
|
+
if (normalized.length > 5 && normalizedLower.includes(plainLower.slice(0, 30)) && plainLower.length > 5) return i + 1;
|
|
9076
|
+
}
|
|
8910
9077
|
}
|
|
8911
9078
|
}
|
|
8912
9079
|
|
|
@@ -9077,6 +9244,12 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
9077
9244
|
selection = { startRow, endRow: endRow || startRow, startCol: 1, endCol: 1 };
|
|
9078
9245
|
updateSelectionVisual();
|
|
9079
9246
|
|
|
9247
|
+
// Highlight the clicked preview element
|
|
9248
|
+
document.querySelectorAll('.preview-highlight').forEach(el => el.classList.remove('preview-highlight'));
|
|
9249
|
+
if (clickedPreviewElement) {
|
|
9250
|
+
clickedPreviewElement.classList.add('preview-highlight');
|
|
9251
|
+
}
|
|
9252
|
+
|
|
9080
9253
|
// Clear header selection
|
|
9081
9254
|
document.querySelectorAll('thead th.selected').forEach(el => el.classList.remove('selected'));
|
|
9082
9255
|
|
|
@@ -9097,7 +9270,7 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
9097
9270
|
}
|
|
9098
9271
|
|
|
9099
9272
|
// Open the card (synchronously) - now target cell should be visible for positioning
|
|
9100
|
-
openCardForSelection();
|
|
9273
|
+
openCardForSelection(clickedPreviewElement);
|
|
9101
9274
|
|
|
9102
9275
|
// Re-add scroll handlers after a delay to allow scroll to settle
|
|
9103
9276
|
setTimeout(() => {
|
|
@@ -9172,7 +9345,7 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
9172
9345
|
const isTableCell = parentBlock.tagName === 'TD' || parentBlock.tagName === 'TH';
|
|
9173
9346
|
const closestH = findClosestHeadingAbove(parentBlock);
|
|
9174
9347
|
const hLine = getHeadingSourceLine(closestH);
|
|
9175
|
-
const line = isTableCell ? findTableSourceLine(parentBlock.textContent, hLine) : findSourceLine(parentBlock.textContent, null, hLine);
|
|
9348
|
+
const line = isTableCell ? findTableSourceLine(parentBlock.textContent, hLine, parentBlock) : findSourceLine(parentBlock.textContent, null, hLine);
|
|
9176
9349
|
if (line > 0) {
|
|
9177
9350
|
selectSourceRange(line, null, parentBlock);
|
|
9178
9351
|
}
|
|
@@ -9233,7 +9406,7 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
9233
9406
|
|
|
9234
9407
|
// Use table-specific search for table cells, otherwise use element-aware search
|
|
9235
9408
|
const isTableCell = target.tagName === 'TD' || target.tagName === 'TH';
|
|
9236
|
-
const line = isTableCell ? findTableSourceLine(searchText, headingLine) : findSourceLine(searchText, target, headingLine);
|
|
9409
|
+
const line = isTableCell ? findTableSourceLine(searchText, headingLine, target) : findSourceLine(searchText, target, headingLine);
|
|
9237
9410
|
if (line <= 0) return;
|
|
9238
9411
|
|
|
9239
9412
|
// Don't prevent default for summary elements - let native <details> toggle work
|
|
@@ -9702,6 +9875,7 @@ function checkExistingServer(filePath) {
|
|
|
9702
9875
|
|
|
9703
9876
|
// --- History File Management ---
|
|
9704
9877
|
const HISTORY_DIR = path.join(os.homedir(), '.reviw', 'history');
|
|
9878
|
+
const OUTPUT_DIR = path.join(os.homedir(), '.reviw', 'outputs');
|
|
9705
9879
|
const HISTORY_MAX = 50;
|
|
9706
9880
|
|
|
9707
9881
|
function getHistoryFilePath(filePath) {
|
|
@@ -9720,6 +9894,16 @@ function ensureHistoryDir() {
|
|
|
9720
9894
|
}
|
|
9721
9895
|
}
|
|
9722
9896
|
|
|
9897
|
+
function ensureOutputDir() {
|
|
9898
|
+
try {
|
|
9899
|
+
if (!fs.existsSync(OUTPUT_DIR)) {
|
|
9900
|
+
fs.mkdirSync(OUTPUT_DIR, { recursive: true, mode: 0o700 });
|
|
9901
|
+
}
|
|
9902
|
+
} catch (err) {
|
|
9903
|
+
// Ignore errors
|
|
9904
|
+
}
|
|
9905
|
+
}
|
|
9906
|
+
|
|
9723
9907
|
function loadHistoryFromFile(filePath) {
|
|
9724
9908
|
try {
|
|
9725
9909
|
const historyPath = getHistoryFilePath(filePath);
|
|
@@ -9748,6 +9932,16 @@ function saveHistoryToFile(filePath, historyEntry) {
|
|
|
9748
9932
|
}
|
|
9749
9933
|
}
|
|
9750
9934
|
|
|
9935
|
+
function shouldSaveHistory(payload) {
|
|
9936
|
+
if (!payload || typeof payload !== "object") return false;
|
|
9937
|
+
if (Array.isArray(payload.comments) && payload.comments.length > 0) return true;
|
|
9938
|
+
if (typeof payload.summary === "string" && payload.summary.trim().length > 0) return true;
|
|
9939
|
+
if (Array.isArray(payload.reviwAnswers) && payload.reviwAnswers.length > 0) return true;
|
|
9940
|
+
if (Array.isArray(payload.summaryImages) && payload.summaryImages.length > 0) return true;
|
|
9941
|
+
if (Array.isArray(payload.prompts) && payload.prompts.length > 0) return true;
|
|
9942
|
+
return false;
|
|
9943
|
+
}
|
|
9944
|
+
|
|
9751
9945
|
// Try to activate an existing browser tab with the given URL (macOS only)
|
|
9752
9946
|
// Returns true if a tab was activated, false otherwise
|
|
9753
9947
|
function tryActivateExistingTab(url) {
|
|
@@ -9874,14 +10068,14 @@ function openBrowser(url, delay = 0) {
|
|
|
9874
10068
|
}
|
|
9875
10069
|
|
|
9876
10070
|
function outputAllResults() {
|
|
9877
|
-
|
|
10071
|
+
const outLines = ["/do"];
|
|
9878
10072
|
if (allResults.length === 1) {
|
|
9879
10073
|
const yamlOut = yaml.dump(allResults[0], { noRefs: true, lineWidth: 120 });
|
|
9880
|
-
|
|
10074
|
+
outLines.push(yamlOut.trim());
|
|
9881
10075
|
} else {
|
|
9882
10076
|
const combined = { files: allResults };
|
|
9883
10077
|
const yamlOut = yaml.dump(combined, { noRefs: true, lineWidth: 120 });
|
|
9884
|
-
|
|
10078
|
+
outLines.push(yamlOut.trim());
|
|
9885
10079
|
}
|
|
9886
10080
|
|
|
9887
10081
|
// Output answered questions if any
|
|
@@ -9892,10 +10086,27 @@ function outputAllResults() {
|
|
|
9892
10086
|
}
|
|
9893
10087
|
}
|
|
9894
10088
|
if (allAnswers.length > 0) {
|
|
9895
|
-
|
|
10089
|
+
outLines.push("");
|
|
10090
|
+
outLines.push("[REVIW_ANSWERS]");
|
|
9896
10091
|
const answersYaml = yaml.dump(allAnswers, { noRefs: true, lineWidth: 120 });
|
|
9897
|
-
|
|
9898
|
-
|
|
10092
|
+
outLines.push(answersYaml.trim());
|
|
10093
|
+
outLines.push("[/REVIW_ANSWERS]");
|
|
10094
|
+
}
|
|
10095
|
+
|
|
10096
|
+
const outputText = outLines.join("\n");
|
|
10097
|
+
console.log(outputText);
|
|
10098
|
+
|
|
10099
|
+
// Durable fallback: persist final output to file for recovery.
|
|
10100
|
+
try {
|
|
10101
|
+
ensureOutputDir();
|
|
10102
|
+
const latestPath = path.join(OUTPUT_DIR, "latest.yaml");
|
|
10103
|
+
fs.writeFileSync(latestPath, outputText + "\n", { mode: 0o600 });
|
|
10104
|
+
|
|
10105
|
+
const ts = new Date().toISOString().replace(/[:.]/g, "-");
|
|
10106
|
+
const archivePath = path.join(OUTPUT_DIR, `output-${ts}.yaml`);
|
|
10107
|
+
fs.writeFileSync(archivePath, outputText + "\n", { mode: 0o600 });
|
|
10108
|
+
} catch (err) {
|
|
10109
|
+
// Keep stdout behavior even if file backup fails.
|
|
9899
10110
|
}
|
|
9900
10111
|
}
|
|
9901
10112
|
|
|
@@ -10041,8 +10252,8 @@ function createFileServer(filePath, fileIndex = 0) {
|
|
|
10041
10252
|
if (payload) {
|
|
10042
10253
|
payload = processPayloadImages(payload, ctx.baseDir);
|
|
10043
10254
|
}
|
|
10044
|
-
// Save to file-based history
|
|
10045
|
-
if (
|
|
10255
|
+
// Save to file-based history when review includes any meaningful submission.
|
|
10256
|
+
if (shouldSaveHistory(payload)) {
|
|
10046
10257
|
saveHistoryToFile(ctx.filePath, payload);
|
|
10047
10258
|
}
|
|
10048
10259
|
res.writeHead(200, { "Content-Type": "text/plain" });
|
|
@@ -10397,9 +10608,9 @@ function createDiffServer(diffContent) {
|
|
|
10397
10608
|
if (payload) {
|
|
10398
10609
|
payload = processPayloadImages(payload, process.cwd());
|
|
10399
10610
|
}
|
|
10400
|
-
// Save to file-based history
|
|
10401
|
-
// For diff mode, use relativePath as identifier
|
|
10402
|
-
if (
|
|
10611
|
+
// Save to file-based history when review includes any meaningful submission.
|
|
10612
|
+
// For diff mode, use relativePath as identifier.
|
|
10613
|
+
if (shouldSaveHistory(payload)) {
|
|
10403
10614
|
const filePath = ctx.diffData?.relativePath || 'stdin-diff';
|
|
10404
10615
|
saveHistoryToFile(filePath, payload);
|
|
10405
10616
|
}
|
|
@@ -10516,7 +10727,7 @@ if (require.main === module) {
|
|
|
10516
10727
|
console.log("Starting diff viewer from stdin...");
|
|
10517
10728
|
serversRunning = 1;
|
|
10518
10729
|
await createDiffServer(stdinContent);
|
|
10519
|
-
console.log("
|
|
10730
|
+
console.log('Click "Submit & Exit" to finish.');
|
|
10520
10731
|
} else {
|
|
10521
10732
|
// Treat as plain text
|
|
10522
10733
|
console.log("Starting text viewer from stdin...");
|
|
@@ -10553,7 +10764,7 @@ if (require.main === module) {
|
|
|
10553
10764
|
for (let i = 0; i < filesToStart.length; i++) {
|
|
10554
10765
|
await createFileServer(filesToStart[i], i);
|
|
10555
10766
|
}
|
|
10556
|
-
console.log("
|
|
10767
|
+
console.log('Click "Submit & Exit" in each opened viewer to finish.');
|
|
10557
10768
|
} else {
|
|
10558
10769
|
// No files and no stdin: try auto git diff
|
|
10559
10770
|
console.log(`reviw v${VERSION}`);
|
|
@@ -10575,7 +10786,7 @@ if (require.main === module) {
|
|
|
10575
10786
|
console.log("Starting diff viewer...");
|
|
10576
10787
|
serversRunning = 1;
|
|
10577
10788
|
await createDiffServer(gitDiff);
|
|
10578
|
-
console.log("
|
|
10789
|
+
console.log('Click "Submit & Exit" to finish.');
|
|
10579
10790
|
} catch (err) {
|
|
10580
10791
|
console.error(err.message);
|
|
10581
10792
|
console.log("");
|