Skip to content

Commit 50698b2

Browse files
committed
Refactor snippet loading and rendering logic
Fix infinite scroll and search functionality Add missing loadMoreIfNeeded() function to prevent ReferenceError Implement proper batch loading with viewport-aware initial loading (max 3 batches) Fix HTML rendering bug by encoding HTML entities for HTML snippets Ensure search loads all results at once for complete visibility Add ensureSnippetLoadedAndScroll() for sidebar navigation to unloaded snippets
1 parent 44c4dbd commit 50698b2

1 file changed

Lines changed: 93 additions & 13 deletions

File tree

public/js/app.js

Lines changed: 93 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,11 @@ document.addEventListener('DOMContentLoaded', () => {
165165
const initialBatch = currentSnippets.slice(0, batchSize);
166166
renderSnippets(initialBatch, true);
167167
loadedCount += initialBatch.length;
168+
169+
// Load more snippets if needed to fill viewport
170+
setTimeout(() => {
171+
loadMoreIfNeeded();
172+
}, 100);
168173
renderTree(data.folders, activeSnippets);
169174
})
170175
.catch(error => {
@@ -263,9 +268,23 @@ document.addEventListener('DOMContentLoaded', () => {
263268
}
264269

265270
loadedCount = 0;
266-
const initialBatch = currentSnippets.slice(0, batchSize);
267-
renderSnippets(initialBatch, true);
268-
loadedCount += initialBatch.length;
271+
272+
// For search results, load all at once since users expect to see all results
273+
// For no search, use batch loading
274+
if (query.trim() === '') {
275+
const initialBatch = currentSnippets.slice(0, batchSize);
276+
renderSnippets(initialBatch, true);
277+
loadedCount += initialBatch.length;
278+
279+
// Load more snippets if needed to fill viewport
280+
setTimeout(() => {
281+
loadMoreIfNeeded();
282+
}, 100);
283+
} else {
284+
// Load all search results at once
285+
renderSnippets(currentSnippets, true);
286+
loadedCount = currentSnippets.length;
287+
}
269288

270289
// Show/hide clear button
271290
clearSearchButton.style.display = e.target.value ? 'block' : 'none';
@@ -277,22 +296,75 @@ document.addEventListener('DOMContentLoaded', () => {
277296
e.target.classList.toggle('open');
278297
} else if (e.target.classList.contains('snippet-item')) {
279298
const snippetId = e.target.dataset.snippetId;
299+
ensureSnippetLoadedAndScroll(snippetId);
300+
}
301+
});
302+
303+
// Function to ensure a snippet is loaded and scroll to it
304+
function ensureSnippetLoadedAndScroll(snippetId) {
305+
const snippetIndex = currentSnippets.findIndex(s => s.id === snippetId);
306+
if (snippetIndex === -1) return; // Snippet not in current results
307+
308+
// If snippet is already loaded, just scroll to it
309+
if (snippetIndex < loadedCount) {
280310
const element = document.getElementById(`snippet-${snippetId}`);
281311
if (element) {
282312
element.scrollIntoView({ behavior: 'smooth' });
283313
}
314+
return;
284315
}
285-
});
316+
317+
// Load all snippets up to and including the target snippet
318+
const snippetsToLoad = currentSnippets.slice(loadedCount, snippetIndex + 1);
319+
renderSnippets(snippetsToLoad, false);
320+
loadedCount += snippetsToLoad.length;
321+
322+
// Now scroll to the snippet
323+
setTimeout(() => {
324+
const element = document.getElementById(`snippet-${snippetId}`);
325+
if (element) {
326+
element.scrollIntoView({ behavior: 'smooth' });
327+
}
328+
}, 100);
329+
}
330+
331+
// Function to load more snippets if needed to fill viewport
332+
function loadMoreIfNeeded() {
333+
const container = snippetsContainer;
334+
const scrollHeight = container.scrollHeight;
335+
const clientHeight = container.clientHeight;
336+
337+
// If content doesn't fill viewport and we haven't loaded too many initially, load one more batch
338+
if (scrollHeight <= clientHeight && loadedCount < currentSnippets.length && loadedCount < batchSize * 3) {
339+
const nextBatch = currentSnippets.slice(loadedCount, loadedCount + batchSize);
340+
renderSnippets(nextBatch, false);
341+
loadedCount += nextBatch.length;
342+
343+
// Check once more if still needed (but limit to prevent loading everything)
344+
setTimeout(() => {
345+
loadMoreIfNeeded();
346+
}, 50);
347+
}
348+
}
286349

287350
// Infinite scroll
351+
let scrollTimeout;
288352
snippetsContainer.addEventListener('scroll', () => {
289-
if (snippetsContainer.scrollTop + snippetsContainer.clientHeight >= snippetsContainer.scrollHeight - 100) {
290-
if (loadedCount < currentSnippets.length) {
291-
const nextBatch = currentSnippets.slice(loadedCount, loadedCount + batchSize);
292-
renderSnippets(nextBatch, false);
293-
loadedCount += nextBatch.length;
353+
clearTimeout(scrollTimeout);
354+
scrollTimeout = setTimeout(() => {
355+
const scrollTop = snippetsContainer.scrollTop;
356+
const clientHeight = snippetsContainer.clientHeight;
357+
const scrollHeight = snippetsContainer.scrollHeight;
358+
359+
// Load more when within 300px of bottom
360+
if (scrollTop + clientHeight >= scrollHeight - 300) {
361+
if (loadedCount < currentSnippets.length) {
362+
const nextBatch = currentSnippets.slice(loadedCount, loadedCount + batchSize);
363+
renderSnippets(nextBatch, false);
364+
loadedCount += nextBatch.length;
365+
}
294366
}
295-
}
367+
}, 100);
296368
});
297369

298370
// Function to check if text matches query (supports regex)
@@ -387,7 +459,11 @@ document.addEventListener('DOMContentLoaded', () => {
387459
code.className = `language-${getPrismLanguage(content.language)}`;
388460
// Use highlighted content if available, otherwise use original content
389461
const displayContent = highlightedContent.highlightedValue || content.value;
390-
code.innerHTML = displayContent;
462+
// For HTML content, encode HTML entities to prevent rendering
463+
const finalContent = getPrismLanguage(content.language) === 'html'
464+
? displayContent.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;')
465+
: displayContent;
466+
code.innerHTML = finalContent;
391467
pre.appendChild(code);
392468

393469
// Add copy button
@@ -452,7 +528,11 @@ document.addEventListener('DOMContentLoaded', () => {
452528
// Use highlighted content if available, otherwise use original content
453529
const highlightedContent = snippet.highlightedContent ? snippet.highlightedContent[index] : content;
454530
const displayContent = highlightedContent.highlightedValue || content.value;
455-
code.innerHTML = displayContent;
531+
// For HTML content, encode HTML entities to prevent rendering
532+
const finalContent = getPrismLanguage(content.language) === 'html'
533+
? displayContent.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;')
534+
: displayContent;
535+
code.innerHTML = finalContent;
456536
pre.appendChild(code);
457537

458538
// Add copy button
@@ -567,4 +647,4 @@ document.addEventListener('DOMContentLoaded', () => {
567647
const tree = buildTree(rootFolders);
568648
document.getElementById('sidebar-content').appendChild(tree);
569649
}
570-
});
650+
});

0 commit comments

Comments
 (0)