Skip to content

Commit 4975a66

Browse files
committed
minor bug fixes and add test csv
1 parent 10ffa2f commit 4975a66

File tree

5 files changed

+1252
-18
lines changed

5 files changed

+1252
-18
lines changed

media/main.js

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,15 @@ let editMode = null; // 'quick' | 'detail' | null
1919

2020
const table = document.querySelector('#csv-root table');
2121
const scrollContainer = document.querySelector('.table-container');
22+
const getFirstDataRow = () => {
23+
const cells = Array.from(table.querySelectorAll('tbody td[data-col]:not([data-col="-1"])'));
24+
let min = Infinity;
25+
for (const el of cells) {
26+
const v = parseInt(el.getAttribute('data-row') || 'NaN', 10);
27+
if (!Number.isNaN(v)) min = Math.min(min, v);
28+
}
29+
return Number.isFinite(min) ? min : 0;
30+
};
2231

2332
// Persist/restore view state (scroll + selection) across webview reloads
2433
const persistState = () => {
@@ -510,9 +519,8 @@ document.addEventListener('keydown', e => {
510519
const max = rowCells.reduce((acc, el) => Math.max(acc, parseInt(el.getAttribute('data-col'))), -1);
511520
target = rowCells.find(el => parseInt(el.getAttribute('data-col')) === max) || ref;
512521
} else if (['ArrowUp','Up','PageUp'].includes(e.key)) {
513-
target = hasHeader
514-
? (table.querySelector('th[data-row="0"][data-col="'+col+'"]') || ref)
515-
: (table.querySelector('td[data-row="0"][data-col="'+col+'"]') || ref);
522+
const topRow = getFirstDataRow();
523+
target = table.querySelector('td[data-row="'+topRow+'"][data-col="'+col+'"]') || ref;
516524
} else if (['ArrowDown','Down','PageDown'].includes(e.key)) {
517525
const colCells = Array.from(table.querySelectorAll('td[data-col="'+col+'"]'));
518526
target = (colCells.length ? colCells[colCells.length - 1] : null) || ref;
@@ -528,8 +536,8 @@ document.addEventListener('keydown', e => {
528536
persistState();
529537
target.focus({preventScroll:true});
530538
if (['ArrowUp','Up','PageUp'].includes(e.key)) {
531-
const belowRowIndex = hasHeader ? 1 : 0;
532-
const below = table.querySelector('td[data-row="'+belowRowIndex+'"][data-col="'+getCellCoords(target).col+'"]');
539+
const topRow = getFirstDataRow();
540+
const below = table.querySelector('td[data-row="'+topRow+'"][data-col="'+getCellCoords(target).col+'"]');
533541
if (below) {
534542
below.scrollIntoView({ block: 'center', inline: 'nearest', behavior: 'smooth' });
535543
} else {
@@ -635,14 +643,10 @@ document.addEventListener('keydown', e => {
635643
if (e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
636644
e.preventDefault();
637645
const cell = anchorCell;
638-
// Quick edit via direct typing
646+
// Quick edit via direct typing: start edit and inject the first char.
639647
editCell(cell, undefined, 'quick');
640-
cell.innerText = '';
641-
if (document.execCommand) {
642-
document.execCommand('insertText', false, e.key);
643-
} else {
644-
cell.innerText = e.key;
645-
}
648+
// Overwrite existing content with the typed character.
649+
cell.textContent = e.key;
646650
setCursorToEnd(cell);
647651
return;
648652
}

src/CsvEditorProvider.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -578,14 +578,15 @@ class CsvEditorController {
578578
const htmlChunk = allRows.slice(i, i + CHUNK_SIZE).map((row, localR) => {
579579
const startAbs = headerFlag ? offset + 1 : offset;
580580
const absRow = startAbs + i + localR;
581+
const displayIdx = i + localR + 1; // numbering relative to first visible data row
581582
let cells = '';
582583
for (let cIdx = 0; cIdx < numColumns; cIdx++) {
583584
const safe = this.escapeHtml(row[cIdx] || '');
584585
cells += `<td tabindex="0" style="min-width:${Math.min(columnWidths[cIdx]||0,100)}ch;max-width:100ch;border:1px solid ${isDark?'#555':'#ccc'};color:${columnColors[cIdx]};overflow:hidden;white-space:nowrap;text-overflow:ellipsis;" data-row="${absRow}" data-col="${cIdx}">${safe}</td>`;
585586
}
586587

587588
return `<tr>${
588-
addSerialIndex ? `<td tabindex="0" style="min-width:4ch;max-width:4ch;border:1px solid ${isDark?'#555':'#ccc'};color:#888;" data-row="${absRow}" data-col="-1">${absRow}</td>` : ''
589+
addSerialIndex ? `<td tabindex="0" style="min-width:4ch;max-width:4ch;border:1px solid ${isDark?'#555':'#ccc'};color:#888;" data-row="${absRow}" data-col="-1">${displayIdx}</td>` : ''
589590
}${cells}</tr>`;
590591
}).join('');
591592

@@ -615,7 +616,7 @@ class CsvEditorController {
615616
bodyData.forEach((row, r) => {
616617
tableHtml += `<tr>${
617618
addSerialIndex
618-
? `<td tabindex="0" style="min-width: 4ch; max-width: 4ch; border: 1px solid ${isDark ? '#555' : '#ccc'}; color: #888;" data-row="${offset + 1 + r}" data-col="-1">${offset + 1 + r}</td>`
619+
? `<td tabindex="0" style="min-width: 4ch; max-width: 4ch; border: 1px solid ${isDark ? '#555' : '#ccc'}; color: #888;" data-row="${offset + 1 + r}" data-col="-1">${r + 1}</td>`
619620
: ''
620621
}`;
621622
for (let i = 0; i < numColumns; i++) {
@@ -626,7 +627,7 @@ class CsvEditorController {
626627
});
627628
if (!chunked) {
628629
const virtualAbs = offset + 1 + bodyData.length;
629-
const idxCell = addSerialIndex ? `<td tabindex="0" style="min-width: 4ch; max-width: 4ch; border: 1px solid ${isDark ? '#555' : '#ccc'}; color: #888;" data-row="${virtualAbs}" data-col="-1">${virtualAbs}</td>` : '';
630+
const idxCell = addSerialIndex ? `<td tabindex="0" style="min-width: 4ch; max-width: 4ch; border: 1px solid ${isDark ? '#555' : '#ccc'}; color: #888;" data-row="${virtualAbs}" data-col="-1">${bodyData.length + 1}</td>` : '';
630631
const dataCells = Array.from({ length: numColumns }, (_, i) => `<td tabindex="0" style="min-width: ${Math.min(columnWidths[i] || 0, 100)}ch; max-width: 100ch; border: 1px solid ${isDark ? '#555' : '#ccc'}; color: ${columnColors[i]}; overflow: hidden; white-space: nowrap; text-overflow: ellipsis;" data-row="${virtualAbs}" data-col="${i}"></td>`).join('');
631632
tableHtml += `<tr>${idxCell}${dataCells}</tr>`;
632633
}
@@ -660,7 +661,7 @@ class CsvEditorController {
660661
if (chunked) {
661662
const startAbs = headerFlag ? offset + 1 : offset;
662663
const virtualAbs = startAbs + allRows.length;
663-
const displayIdx = headerFlag ? virtualAbs : (allRows.length + 1);
664+
const displayIdx = allRows.length + 1;
664665
const idxCell = addSerialIndex ? `<td tabindex="0" style="min-width: 4ch; max-width: 4ch; border: 1px solid ${isDark ? '#555' : '#ccc'}; color: #888;" data-row="${virtualAbs}" data-col="-1">${displayIdx}</td>` : '';
665666
const dataCells = Array.from({ length: numColumns }, (_, i) => `<td tabindex="0" style="min-width: ${Math.min(columnWidths[i] || 0, 100)}ch; max-width: 100ch; border: 1px solid ${isDark ? '#555' : '#ccc'}; color: ${columnColors[i]}; overflow: hidden; white-space: nowrap; text-overflow: ellipsis;" data-row="${virtualAbs}" data-col="${i}"></td>`).join('');
666667
const vrow = `<tr>${idxCell}${dataCells}</tr>`;
@@ -843,15 +844,26 @@ class CsvEditorController {
843844
return !isNaN(Date.parse(value));
844845
}
845846

847+
private isBooleanish(value: string): boolean {
848+
const v = (value ?? '').trim().toLowerCase();
849+
if (!v) return false;
850+
if (v === 'true' || v === 'false') return true;
851+
if (v === 't' || v === 'f') return true;
852+
if (v === 'yes' || v === 'no') return true;
853+
if (v === 'y' || v === 'n') return true;
854+
if (v === 'on' || v === 'off') return true;
855+
if (v === '1' || v === '0') return true;
856+
return false;
857+
}
858+
846859
private estimateColumnDataType(column: string[]): string {
847860
let allBoolean = true, allDate = true, allInteger = true, allFloat = true, allEmpty = true;
848861
for (const cell of column) {
849862
const items = cell.split(',').map(item => item.trim());
850863
for (const item of items){
851864
if (item === '') continue;
852865
allEmpty = false;
853-
const lower = item.toLowerCase();
854-
if (!(lower === 'true' || lower === 'false')) allBoolean = false;
866+
if (!this.isBooleanish(item)) allBoolean = false;
855867
if (!this.isDate(item)) allDate = false;
856868
const num = Number(item);
857869
if (!Number.isInteger(num)) allInteger = false;

src/test/provider-utils.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ describe('CsvEditorProvider utility methods', () => {
4242
it('estimateColumnDataType detects common types', () => {
4343
const estimate = getPrivate<(c: string[]) => string>(provider, 'estimateColumnDataType').bind(provider);
4444
assert.strictEqual(estimate(['true', 'FALSE']), 'boolean');
45+
assert.strictEqual(estimate(['1', '0', '0', '1']), 'boolean');
46+
assert.strictEqual(estimate(['t', 'F', 'T', 'f']), 'boolean');
47+
assert.strictEqual(estimate(['yes', 'No', 'Y', 'n']), 'boolean');
48+
assert.strictEqual(estimate(['on', 'OFF']), 'boolean');
4549
assert.strictEqual(estimate(['2020-01-01', '1999-12-31']), 'date');
4650
assert.strictEqual(estimate(['0x1', '0x2']), 'integer');
4751
assert.strictEqual(estimate(['1.2e0', '3.4e0']), 'float');

0 commit comments

Comments
 (0)