Skip to content

Commit 5fa2364

Browse files
committed
feat: Enhance DOM element safety checks and improve event handling for better stability
1 parent 73b84f9 commit 5fa2364

File tree

1 file changed

+126
-34
lines changed

1 file changed

+126
-34
lines changed

index.js

Lines changed: 126 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -362,21 +362,31 @@
362362
// ============================================================================
363363
const S = { files: [], tree: [], root: 'project', ctx: '', busy: false, rendered: 0, basePath: '', basePathSet: false, model: 'gpt' };
364364
const $ = id => document.getElementById(id);
365+
const $safe = id => {
366+
const el = document.getElementById(id);
367+
if (!el) console.warn(`Missing DOM element with id="${id}"`);
368+
return el;
369+
};
365370
const D = {
366-
side: $('sidebar'), tog: $('toggleSidebar'), tree: $('fileTree'),
367-
search: $('fileSearch'), sel: $('selectDirBtn'),
368-
exp: $('expandAll'), col: $('collapseAll'),
369-
all: $('selectAll'), none: $('deselectAll'),
370-
gen: $('generateContextBtn'), ed: $('codeEditor'),
371-
cnt: $('fileCount'), tok: $('tokenCount'), sz: $('totalSize'),
372-
pron: $('tokenPronunciation'), lang: $('languagesList'),
373-
copy: $('copyBtn'), txt: $('downloadTxtBtn'),
374-
load: $('loadingOverlay'), toast: $('toastContainer'),
375-
model: $('modelSelector'),
376-
loadingText: document.getElementById('loadingText')
371+
side: $safe('sidebar'), tog: $safe('toggleSidebar'), tree: $safe('fileTree'),
372+
search: $safe('fileSearch'), sel: $safe('selectDirBtn'),
373+
exp: $safe('expandAll'), col: $safe('collapseAll'),
374+
all: $safe('selectAll'), none: $safe('deselectAll'),
375+
gen: $safe('generateContextBtn'), ed: $safe('codeEditor'),
376+
cnt: $safe('fileCount'), tok: $safe('tokenCount'), sz: $safe('totalSize'),
377+
pron: $safe('tokenPronunciation'), lang: $safe('languagesList'),
378+
copy: $safe('copyBtn'), txt: $safe('downloadTxtBtn'),
379+
load: $safe('loadingOverlay'), toast: $safe('toastContainer'),
380+
model: $safe('modelSelector'),
381+
loadingText: $safe('loadingText')
377382
};
378383
const inp = document.createElement('input');
379-
inp.type = 'file'; inp.webkitdirectory = true; inp.multiple = true; inp.style.display = 'none';
384+
inp.type = 'file';
385+
inp.multiple = true;
386+
inp.setAttribute('webkitdirectory', '');
387+
inp.setAttribute('mozdirectory', '');
388+
inp.setAttribute('directory', '');
389+
inp.style.display = 'none';
380390
document.body.appendChild(inp);
381391

382392
// ============================================================================
@@ -1249,13 +1259,82 @@
12491259
for (let i = 0; i < textFiles.length; i += FAST_BATCH) {
12501260
const batch = textFiles.slice(i, i + FAST_BATCH);
12511261
const results = await Promise.allSettled(
1252-
batch.map(f => f.file.text()) // Only read files from the 'textFiles' list
1262+
batch.map(f => f.file.text())
12531263
);
12541264
results.forEach((result, idx) => {
12551265
if (result.status === 'fulfilled') {
12561266
const content = result.value;
12571267
const p = batch[idx].path;
1258-
const loadFiles = async list => {
1268+
const filename = batch[idx].name;
1269+
1270+
if (content.indexOf('\0') !== -1) {
1271+
console.warn('Download: Skipping file with null bytes:', filename);
1272+
return;
1273+
}
1274+
1275+
const lines = content.split('\n');
1276+
const longBase64Lines = lines.filter(line =>
1277+
line.length > 200 && /^[A-Za-z0-9+/=]+$/.test(line.trim())
1278+
).length;
1279+
1280+
if (longBase64Lines > 10) {
1281+
console.warn('Download: Skipping file with encoded data:', filename);
1282+
return;
1283+
}
1284+
1285+
const hasVeryLongLine = lines.some(line => line.length > 10000);
1286+
if (hasVeryLongLine) {
1287+
console.warn('Download: Skipping minified file:', filename);
1288+
return;
1289+
}
1290+
1291+
parts.push('=== FILE: ' + getFullPath(p) + ' ===\n');
1292+
parts.push('<document path="' + p + '">\n', content, '\n</document>\n');
1293+
1294+
const sFileIndex = S.files.findIndex(sf => sf.path === p);
1295+
if (sFileIndex !== -1) {
1296+
S.files[sFileIndex].file = null;
1297+
}
1298+
} else {
1299+
console.warn('Skip', batch[idx].name, result.reason);
1300+
}
1301+
});
1302+
done += batch.length;
1303+
const pct = Math.round(done / textFiles.length * 100);
1304+
const status = [];
1305+
status.push(`${done}/${textFiles.length}`);
1306+
if (binaryFiles && binaryFiles.length > 0) {
1307+
status.push(`${binaryFiles.length} binary`);
1308+
}
1309+
D.ed.innerHTML = `<div class="editor-placeholder"><span class="material-symbols-outlined">download</span><p>Preparing: ${pct}%</p><small>${status.join(' • ')}</small></div>`;
1310+
1311+
if (i % (FAST_BATCH * 5) === 0) {
1312+
await new Promise(r => setTimeout(r, 0));
1313+
}
1314+
}
1315+
1316+
await new Promise(r => setTimeout(r, 0));
1317+
1318+
const blob = new Blob(parts, { type: 'text/plain' });
1319+
const url = URL.createObjectURL(blob);
1320+
const a = document.createElement('a');
1321+
a.href = url;
1322+
a.download = `${S.root}-context.txt`;
1323+
a.click();
1324+
URL.revokeObjectURL(url);
1325+
1326+
const stats = [];
1327+
stats.push(`${textFiles.length} files`);
1328+
if (binaryFiles && binaryFiles.length > 0) {
1329+
stats.push(`${binaryFiles.length} binary`);
1330+
}
1331+
stats.push(bytes(blob.size));
1332+
D.ed.innerHTML = `<div class="editor-placeholder"><span class="material-symbols-outlined">check_circle</span><p>Downloaded!</p><small>File: ${S.root}-context.txt</small><small>${stats.join(' • ')}</small></div>`;
1333+
1334+
parts.length = 0;
1335+
};
1336+
1337+
const loadFiles = async list => {
12591338
try {
12601339
// Clear previous memory before loading new files
12611340
clearMemory();
@@ -1381,16 +1460,26 @@
13811460
// ============================================================================
13821461
const setup = () => {
13831462
console.log('Setting up event handlers...');
1384-
D.tog.onclick = () => D.side.classList.toggle('collapsed');
1385-
D.sel.onclick = () => {
1386-
console.log('Select folder clicked');
1387-
inp.click();
1388-
};
1463+
if (D.tog) D.tog.addEventListener('click', () => D.side && D.side.classList.toggle('collapsed'));
1464+
1465+
if (D.sel) {
1466+
D.sel.addEventListener('click', () => {
1467+
console.log('Select folder clicked');
1468+
try {
1469+
inp.click();
1470+
} catch (e) {
1471+
console.error('Failed to open directory picker:', e);
1472+
toast('Directory picker blocked', 'error');
1473+
}
1474+
});
1475+
} else {
1476+
console.warn('selectDirBtn not found — directory selection unavailable');
1477+
}
13891478
// Model selector event
13901479
if (D.model) {
1391-
D.model.onchange = () => {
1480+
D.model.addEventListener('change', () => {
13921481
S.model = D.model.value;
1393-
};
1482+
});
13941483
}
13951484
// Optimize file input change handler
13961485
inp.onchange = e => {
@@ -1423,13 +1512,13 @@
14231512
stats();
14241513
}
14251514
};
1426-
D.search.oninput = e => {
1515+
if (D.search) D.search.addEventListener('input', e => {
14271516
const q = e.target.value.toLowerCase();
14281517
document.querySelectorAll('.tree-item').forEach(item => {
14291518
const n = item.querySelector('.file-name').textContent.toLowerCase();
14301519
item.style.display = n.includes(q) ? '' : 'none';
14311520
});
1432-
};
1521+
});
14331522
const togFold = o => {
14341523
document.querySelectorAll('.expand-btn').forEach(btn => {
14351524
const item = btn.closest('.tree-item');
@@ -1445,21 +1534,24 @@
14451534
document.querySelectorAll('.file-checkbox:not([disabled])').forEach(cb => cb.checked = c);
14461535
stats();
14471536
};
1448-
D.exp.onclick = () => togFold(true);
1449-
D.col.onclick = () => togFold(false);
1450-
D.all.onclick = () => togCheck(true);
1451-
D.none.onclick = () => togCheck(false);
1452-
D.gen.onclick = gen;
1453-
D.copy.onclick = copyClip;
1454-
D.txt.onclick = () => dl('txt');
1537+
if (D.exp) D.exp.addEventListener('click', () => togFold(true));
1538+
if (D.col) D.col.addEventListener('click', () => togFold(false));
1539+
if (D.all) D.all.addEventListener('click', () => togCheck(true));
1540+
if (D.none) D.none.addEventListener('click', () => togCheck(false));
1541+
if (D.gen) D.gen.addEventListener('click', gen);
1542+
if (D.copy) D.copy.addEventListener('click', copyClip);
1543+
if (D.txt) D.txt.addEventListener('click', () => dl('txt'));
14551544
// Clear All button
14561545
const clearBtn = $('clearAll');
14571546
if (clearBtn) {
14581547
clearBtn.onclick = clearAll;
14591548
}
1460-
document.onkeydown = e => {
1461-
if ((e.ctrlKey || e.metaKey) && e.key === 'b') { e.preventDefault(); D.side.classList.toggle('collapsed'); }
1462-
};
1549+
document.addEventListener('keydown', e => {
1550+
if ((e.ctrlKey || e.metaKey) && e.key === 'b') {
1551+
e.preventDefault();
1552+
if (D.side) D.side.classList.toggle('collapsed');
1553+
}
1554+
});
14631555
}
14641556
// ============================================================================
14651557
// INIT
@@ -1471,4 +1563,4 @@
14711563
};
14721564
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
14731565
else init();
1474-
})();
1566+
}})();

0 commit comments

Comments
 (0)