reviw 0.10.0 → 0.10.1
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 +265 -2
- package/package.json +1 -1
package/cli.cjs
CHANGED
|
@@ -849,7 +849,7 @@ function diffHtmlTemplate(diffData) {
|
|
|
849
849
|
#submit-modal.visible { display: flex; }
|
|
850
850
|
#submit-modal .modal-dialog {
|
|
851
851
|
pointer-events: auto;
|
|
852
|
-
margin: 20px;
|
|
852
|
+
margin: 60px 20px 20px 20px; /* top margin avoids header button overlap */
|
|
853
853
|
}
|
|
854
854
|
.modal-dialog {
|
|
855
855
|
background: var(--panel);
|
|
@@ -2122,7 +2122,7 @@ function htmlTemplate(dataRows, cols, title, mode, previewHtml) {
|
|
|
2122
2122
|
#submit-modal.visible { display: flex; }
|
|
2123
2123
|
#submit-modal .modal-dialog {
|
|
2124
2124
|
pointer-events: auto;
|
|
2125
|
-
margin: 20px;
|
|
2125
|
+
margin: 60px 20px 20px 20px; /* top margin avoids header button overlap */
|
|
2126
2126
|
}
|
|
2127
2127
|
.modal-dialog {
|
|
2128
2128
|
background: var(--panel-solid);
|
|
@@ -3801,6 +3801,249 @@ function htmlTemplate(dataRows, cols, title, mode, previewHtml) {
|
|
|
3801
3801
|
}
|
|
3802
3802
|
});
|
|
3803
3803
|
})();
|
|
3804
|
+
|
|
3805
|
+
// --- Preview Commenting ---
|
|
3806
|
+
(function initPreviewCommenting() {
|
|
3807
|
+
if (MODE !== 'markdown') return;
|
|
3808
|
+
|
|
3809
|
+
const preview = document.querySelector('.md-preview');
|
|
3810
|
+
if (!preview) return;
|
|
3811
|
+
|
|
3812
|
+
// Add visual hint for clickable elements
|
|
3813
|
+
const style = document.createElement('style');
|
|
3814
|
+
style.textContent = \`
|
|
3815
|
+
.md-preview > p:hover, .md-preview > h1:hover, .md-preview > h2:hover,
|
|
3816
|
+
.md-preview > h3:hover, .md-preview > h4:hover, .md-preview > h5:hover,
|
|
3817
|
+
.md-preview > h6:hover, .md-preview > ul > li:hover, .md-preview > ol > li:hover,
|
|
3818
|
+
.md-preview > pre:hover, .md-preview > blockquote:hover {
|
|
3819
|
+
background: rgba(99, 102, 241, 0.08);
|
|
3820
|
+
cursor: pointer;
|
|
3821
|
+
border-radius: 4px;
|
|
3822
|
+
}
|
|
3823
|
+
.md-preview img:hover {
|
|
3824
|
+
outline: 2px solid var(--accent);
|
|
3825
|
+
cursor: pointer;
|
|
3826
|
+
}
|
|
3827
|
+
\`;
|
|
3828
|
+
document.head.appendChild(style);
|
|
3829
|
+
|
|
3830
|
+
// Helper: find matching source line for text
|
|
3831
|
+
function findSourceLine(text) {
|
|
3832
|
+
if (!text) return -1;
|
|
3833
|
+
const normalized = text.trim().replace(/\\s+/g, ' ').slice(0, 100);
|
|
3834
|
+
if (!normalized) return -1;
|
|
3835
|
+
|
|
3836
|
+
for (let i = 0; i < DATA.length; i++) {
|
|
3837
|
+
const lineText = (DATA[i][0] || '').trim();
|
|
3838
|
+
if (!lineText) continue;
|
|
3839
|
+
|
|
3840
|
+
const lineNorm = lineText.replace(/\\s+/g, ' ').slice(0, 100);
|
|
3841
|
+
if (lineNorm === normalized) return i + 1;
|
|
3842
|
+
if (lineNorm.includes(normalized.slice(0, 30)) && normalized.length > 5) return i + 1;
|
|
3843
|
+
if (normalized.includes(lineNorm.slice(0, 30)) && lineNorm.length > 5) return i + 1;
|
|
3844
|
+
|
|
3845
|
+
// Check for markdown headings: strip # from source and compare
|
|
3846
|
+
if (lineText.match(/^#+\\s/)) {
|
|
3847
|
+
const headingText = lineText.replace(/^#+\\s*/, '').trim();
|
|
3848
|
+
if (headingText === normalized || headingText.toLowerCase() === normalized.toLowerCase()) {
|
|
3849
|
+
return i + 1;
|
|
3850
|
+
}
|
|
3851
|
+
}
|
|
3852
|
+
}
|
|
3853
|
+
return -1;
|
|
3854
|
+
}
|
|
3855
|
+
|
|
3856
|
+
// Helper: find matching source line for table cell (prioritizes table rows)
|
|
3857
|
+
function findTableSourceLine(text) {
|
|
3858
|
+
if (!text) return -1;
|
|
3859
|
+
const normalized = text.trim().replace(/\\s+/g, ' ').slice(0, 100);
|
|
3860
|
+
if (!normalized) return -1;
|
|
3861
|
+
|
|
3862
|
+
// First pass: look for table rows (lines starting with |) containing the text
|
|
3863
|
+
for (let i = 0; i < DATA.length; i++) {
|
|
3864
|
+
const lineText = (DATA[i][0] || '').trim();
|
|
3865
|
+
if (!lineText || !lineText.startsWith('|')) continue;
|
|
3866
|
+
|
|
3867
|
+
const lineNorm = lineText.replace(/\\s+/g, ' ').slice(0, 100);
|
|
3868
|
+
if (lineNorm.includes(normalized.slice(0, 30)) && normalized.length > 5) return i + 1;
|
|
3869
|
+
}
|
|
3870
|
+
|
|
3871
|
+
// Fallback to normal search
|
|
3872
|
+
return findSourceLine(text);
|
|
3873
|
+
}
|
|
3874
|
+
|
|
3875
|
+
// Helper: find code block range in source (fenced code blocks)
|
|
3876
|
+
function findCodeBlockRange(codeText) {
|
|
3877
|
+
const clickedLines = codeText.split('\\n').map(l => l.trim()).filter(l => l);
|
|
3878
|
+
const clickedContent = clickedLines.join('\\n');
|
|
3879
|
+
|
|
3880
|
+
// Extract all code blocks from DATA
|
|
3881
|
+
const codeBlocks = [];
|
|
3882
|
+
let currentBlock = null;
|
|
3883
|
+
|
|
3884
|
+
for (let i = 0; i < DATA.length; i++) {
|
|
3885
|
+
const lineText = (DATA[i][0] || '').trim();
|
|
3886
|
+
|
|
3887
|
+
if (lineText.startsWith('\`\`\`') && !currentBlock) {
|
|
3888
|
+
// Start of a code block
|
|
3889
|
+
currentBlock = { startLine: i + 1, lines: [] };
|
|
3890
|
+
} else if (lineText === '\`\`\`' && currentBlock) {
|
|
3891
|
+
// End of a code block
|
|
3892
|
+
currentBlock.endLine = i + 1;
|
|
3893
|
+
currentBlock.content = currentBlock.lines.map(l => l.trim()).filter(l => l).join('\\n');
|
|
3894
|
+
codeBlocks.push(currentBlock);
|
|
3895
|
+
currentBlock = null;
|
|
3896
|
+
} else if (currentBlock) {
|
|
3897
|
+
// Inside a code block
|
|
3898
|
+
currentBlock.lines.push(DATA[i][0] || '');
|
|
3899
|
+
}
|
|
3900
|
+
}
|
|
3901
|
+
|
|
3902
|
+
// Find the best matching code block by content similarity
|
|
3903
|
+
let bestMatch = null;
|
|
3904
|
+
let bestScore = 0;
|
|
3905
|
+
|
|
3906
|
+
for (const block of codeBlocks) {
|
|
3907
|
+
// Calculate similarity score
|
|
3908
|
+
let score = 0;
|
|
3909
|
+
|
|
3910
|
+
// Exact match
|
|
3911
|
+
if (block.content === clickedContent) {
|
|
3912
|
+
score = 1000;
|
|
3913
|
+
} else {
|
|
3914
|
+
// Check line-by-line matches
|
|
3915
|
+
const blockLines = block.content.split('\\n');
|
|
3916
|
+
for (const clickedLine of clickedLines) {
|
|
3917
|
+
if (clickedLine.length > 3) {
|
|
3918
|
+
for (const blockLine of blockLines) {
|
|
3919
|
+
if (blockLine.includes(clickedLine) || clickedLine.includes(blockLine)) {
|
|
3920
|
+
score += clickedLine.length;
|
|
3921
|
+
}
|
|
3922
|
+
}
|
|
3923
|
+
}
|
|
3924
|
+
}
|
|
3925
|
+
}
|
|
3926
|
+
|
|
3927
|
+
if (score > bestScore) {
|
|
3928
|
+
bestScore = score;
|
|
3929
|
+
bestMatch = block;
|
|
3930
|
+
}
|
|
3931
|
+
}
|
|
3932
|
+
|
|
3933
|
+
if (bestMatch) {
|
|
3934
|
+
return { startLine: bestMatch.startLine, endLine: bestMatch.endLine };
|
|
3935
|
+
}
|
|
3936
|
+
|
|
3937
|
+
// Fallback: find by first line content matching
|
|
3938
|
+
const firstCodeLine = clickedLines[0];
|
|
3939
|
+
if (firstCodeLine && firstCodeLine.length > 3) {
|
|
3940
|
+
for (let i = 0; i < DATA.length; i++) {
|
|
3941
|
+
const lineText = (DATA[i][0] || '').trim();
|
|
3942
|
+
if (lineText.includes(firstCodeLine.slice(0, 30))) {
|
|
3943
|
+
return { startLine: i + 1, endLine: i + 1 };
|
|
3944
|
+
}
|
|
3945
|
+
}
|
|
3946
|
+
}
|
|
3947
|
+
|
|
3948
|
+
return { startLine: -1, endLine: -1 };
|
|
3949
|
+
}
|
|
3950
|
+
|
|
3951
|
+
// Helper: find source line for image by src
|
|
3952
|
+
function findImageSourceLine(src) {
|
|
3953
|
+
if (!src) return -1;
|
|
3954
|
+
const filename = src.split('/').pop().split('?')[0];
|
|
3955
|
+
for (let i = 0; i < DATA.length; i++) {
|
|
3956
|
+
const lineText = DATA[i][0] || '';
|
|
3957
|
+
if (lineText.includes(filename) || lineText.includes(src)) {
|
|
3958
|
+
return i + 1;
|
|
3959
|
+
}
|
|
3960
|
+
}
|
|
3961
|
+
return -1;
|
|
3962
|
+
}
|
|
3963
|
+
|
|
3964
|
+
// Trigger source cell selection (reuse existing comment flow)
|
|
3965
|
+
function selectSourceRange(startRow, endRow) {
|
|
3966
|
+
selection = { startRow, endRow: endRow || startRow, startCol: 1, endCol: 1 };
|
|
3967
|
+
updateSelectionVisual();
|
|
3968
|
+
|
|
3969
|
+
// Clear header selection
|
|
3970
|
+
document.querySelectorAll('thead th.selected').forEach(el => el.classList.remove('selected'));
|
|
3971
|
+
|
|
3972
|
+
// Scroll source table to show the selected row, then open card
|
|
3973
|
+
const sourceTd = tbody.querySelector('td[data-row="' + startRow + '"][data-col="1"]');
|
|
3974
|
+
if (sourceTd) {
|
|
3975
|
+
sourceTd.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
3976
|
+
// Wait for scroll to complete before positioning card
|
|
3977
|
+
setTimeout(() => openCardForSelection(), 350);
|
|
3978
|
+
} else {
|
|
3979
|
+
openCardForSelection();
|
|
3980
|
+
}
|
|
3981
|
+
}
|
|
3982
|
+
|
|
3983
|
+
// Click on block elements
|
|
3984
|
+
preview.addEventListener('click', (e) => {
|
|
3985
|
+
// Handle image clicks
|
|
3986
|
+
if (e.target.tagName === 'IMG') {
|
|
3987
|
+
if (!e.defaultPrevented) {
|
|
3988
|
+
const line = findImageSourceLine(e.target.src);
|
|
3989
|
+
if (line > 0) {
|
|
3990
|
+
e.preventDefault();
|
|
3991
|
+
e.stopPropagation();
|
|
3992
|
+
selectSourceRange(line);
|
|
3993
|
+
}
|
|
3994
|
+
}
|
|
3995
|
+
return;
|
|
3996
|
+
}
|
|
3997
|
+
|
|
3998
|
+
// Ignore clicks on links, mermaid, video overlay
|
|
3999
|
+
if (e.target.closest('a, .mermaid-container, .video-fullscreen-overlay')) return;
|
|
4000
|
+
|
|
4001
|
+
// Handle code blocks - select entire block
|
|
4002
|
+
const pre = e.target.closest('pre');
|
|
4003
|
+
if (pre) {
|
|
4004
|
+
const code = pre.querySelector('code') || pre;
|
|
4005
|
+
const { startLine, endLine } = findCodeBlockRange(code.textContent);
|
|
4006
|
+
if (startLine > 0) {
|
|
4007
|
+
e.preventDefault();
|
|
4008
|
+
selectSourceRange(startLine, endLine);
|
|
4009
|
+
}
|
|
4010
|
+
return;
|
|
4011
|
+
}
|
|
4012
|
+
|
|
4013
|
+
const target = e.target.closest('p, h1, h2, h3, h4, h5, h6, li, blockquote, td, th');
|
|
4014
|
+
if (!target) return;
|
|
4015
|
+
|
|
4016
|
+
// Use table-specific search for table cells
|
|
4017
|
+
const isTableCell = target.tagName === 'TD' || target.tagName === 'TH';
|
|
4018
|
+
const line = isTableCell ? findTableSourceLine(target.textContent) : findSourceLine(target.textContent);
|
|
4019
|
+
if (line <= 0) return;
|
|
4020
|
+
|
|
4021
|
+
e.preventDefault();
|
|
4022
|
+
selectSourceRange(line);
|
|
4023
|
+
});
|
|
4024
|
+
|
|
4025
|
+
// Text selection to open comment for range
|
|
4026
|
+
preview.addEventListener('mouseup', (e) => {
|
|
4027
|
+
setTimeout(() => {
|
|
4028
|
+
const sel = window.getSelection();
|
|
4029
|
+
if (!sel || sel.isCollapsed) return;
|
|
4030
|
+
|
|
4031
|
+
const text = sel.toString().trim();
|
|
4032
|
+
if (!text || text.length < 5) return;
|
|
4033
|
+
|
|
4034
|
+
const lines = text.split('\\n').filter(l => l.trim());
|
|
4035
|
+
if (lines.length === 0) return;
|
|
4036
|
+
|
|
4037
|
+
const startLine = findSourceLine(lines[0]);
|
|
4038
|
+
const endLine = lines.length > 1 ? findSourceLine(lines[lines.length - 1]) : startLine;
|
|
4039
|
+
|
|
4040
|
+
if (startLine <= 0) return;
|
|
4041
|
+
|
|
4042
|
+
sel.removeAllRanges();
|
|
4043
|
+
selectSourceRange(startLine, endLine > 0 ? endLine : startLine);
|
|
4044
|
+
}, 10);
|
|
4045
|
+
});
|
|
4046
|
+
})();
|
|
3804
4047
|
</script>
|
|
3805
4048
|
</body>
|
|
3806
4049
|
</html>`;
|
|
@@ -4039,7 +4282,10 @@ function createFileServer(filePath) {
|
|
|
4039
4282
|
res.end("not found");
|
|
4040
4283
|
});
|
|
4041
4284
|
|
|
4285
|
+
let serverStarted = false;
|
|
4286
|
+
|
|
4042
4287
|
function tryListen(attemptPort, attempts = 0) {
|
|
4288
|
+
if (serverStarted) return; // Prevent double-start race condition
|
|
4043
4289
|
if (attempts >= MAX_PORT_ATTEMPTS) {
|
|
4044
4290
|
console.error(
|
|
4045
4291
|
`Could not find an available port for ${baseName} after ${MAX_PORT_ATTEMPTS} attempts.`,
|
|
@@ -4050,6 +4296,7 @@ function createFileServer(filePath) {
|
|
|
4050
4296
|
}
|
|
4051
4297
|
|
|
4052
4298
|
ctx.server.once("error", (err) => {
|
|
4299
|
+
if (serverStarted) return; // Already started on another port
|
|
4053
4300
|
if (err.code === "EADDRINUSE") {
|
|
4054
4301
|
tryListen(attemptPort + 1, attempts + 1);
|
|
4055
4302
|
} else {
|
|
@@ -4060,6 +4307,12 @@ function createFileServer(filePath) {
|
|
|
4060
4307
|
});
|
|
4061
4308
|
|
|
4062
4309
|
ctx.server.listen(attemptPort, () => {
|
|
4310
|
+
if (serverStarted) {
|
|
4311
|
+
// Race condition: server started on multiple ports, close this one
|
|
4312
|
+
try { ctx.server.close(); } catch (_) {}
|
|
4313
|
+
return;
|
|
4314
|
+
}
|
|
4315
|
+
serverStarted = true;
|
|
4063
4316
|
ctx.port = attemptPort;
|
|
4064
4317
|
nextPort = attemptPort + 1;
|
|
4065
4318
|
activeServers.set(filePath, ctx);
|
|
@@ -4193,7 +4446,10 @@ function createDiffServer(diffContent) {
|
|
|
4193
4446
|
res.end("not found");
|
|
4194
4447
|
});
|
|
4195
4448
|
|
|
4449
|
+
let serverStarted = false;
|
|
4450
|
+
|
|
4196
4451
|
function tryListen(attemptPort, attempts = 0) {
|
|
4452
|
+
if (serverStarted) return; // Prevent double-start race condition
|
|
4197
4453
|
if (attempts >= MAX_PORT_ATTEMPTS) {
|
|
4198
4454
|
console.error(
|
|
4199
4455
|
`Could not find an available port for diff viewer after ${MAX_PORT_ATTEMPTS} attempts.`,
|
|
@@ -4204,6 +4460,7 @@ function createDiffServer(diffContent) {
|
|
|
4204
4460
|
}
|
|
4205
4461
|
|
|
4206
4462
|
ctx.server.once("error", (err) => {
|
|
4463
|
+
if (serverStarted) return; // Already started on another port
|
|
4207
4464
|
if (err.code === "EADDRINUSE") {
|
|
4208
4465
|
tryListen(attemptPort + 1, attempts + 1);
|
|
4209
4466
|
} else {
|
|
@@ -4214,6 +4471,12 @@ function createDiffServer(diffContent) {
|
|
|
4214
4471
|
});
|
|
4215
4472
|
|
|
4216
4473
|
ctx.server.listen(attemptPort, () => {
|
|
4474
|
+
if (serverStarted) {
|
|
4475
|
+
// Race condition: server started on multiple ports, close this one
|
|
4476
|
+
try { ctx.server.close(); } catch (_) {}
|
|
4477
|
+
return;
|
|
4478
|
+
}
|
|
4479
|
+
serverStarted = true;
|
|
4217
4480
|
ctx.port = attemptPort;
|
|
4218
4481
|
ctx.heartbeat = setInterval(() => broadcast("ping"), 25000);
|
|
4219
4482
|
console.log(`Diff viewer started: http://localhost:${attemptPort}`);
|