// Global variables let isLoading = false; let currentPostCount = 0; let allPosts = []; // Store all posts for search functionality let filteredPosts = []; // Store currently displayed posts const POSTS_CACHE_KEY = "cached_posts_100"; const CACHE_EXPIRY_TIME = 5 * 60 * 1000; // 5 minutes // Check for saved dark mode preference on page load function initializeDarkMode() { const savedMode = localStorage.getItem("darkMode"); const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches; if (savedMode === "enabled") { enableDarkMode(); } else if (savedMode === "disabled") { disableDarkMode(); } else if (prefersDark) { // If no preference saved and system prefers dark, enable it enableDarkMode(); } } function enableDarkMode() { document.body.classList.add("dark-mode"); document.querySelector(".dark-mode-toggle .dark-mode-icon").textContent = "☀️"; localStorage.setItem("darkMode", "enabled"); } function disableDarkMode() { document.body.classList.remove("dark-mode"); document.querySelector(".dark-mode-toggle .dark-mode-icon").textContent = "🌙"; localStorage.setItem("darkMode", "disabled"); } function toggleDarkMode() { if (document.body.classList.contains("dark-mode")) { disableDarkMode(); } else { enableDarkMode(); } } // Initialize dark mode on load document.addEventListener("DOMContentLoaded", initializeDarkMode); function createPostElement(post) { const postElement = document.createElement("div"); postElement.className = "post-card"; postElement.onclick = () => { window.location.href = post.url; }; postElement.style.cursor = "pointer"; // Create the content container const content = document.createElement("div"); content.className = "post-content"; const title = document.createElement("a"); title.href = post.url; title.className = "post-title"; // Check if this is a "Reddit - Prove your humanity" type title and extract real title from base_url if (post.title && post.title.includes("Reddit - Prove your humanity")) { const realTitle = extractRealTitleFromUrl(post.base_url); if (realTitle) { title.textContent = realTitle; } else { title.textContent = post.title; } } else { title.textContent = post.title; } // Create buttons container const buttonsContainer = document.createElement("div"); buttonsContainer.className = "post-buttons"; // Single button const singleButton = document.createElement("button"); singleButton.onclick = (e) => { e.stopPropagation(); window.location.href = post.url + "/singlefile"; }; singleButton.className = "post-button"; singleButton.textContent = "Single"; // HTML button const htmlButton = document.createElement("button"); htmlButton.onclick = (e) => { e.stopPropagation(); window.location.href = post.url + "/html"; }; htmlButton.className = "post-button"; htmlButton.textContent = "HTML"; // PDF button const pdfButton = document.createElement("button"); pdfButton.onclick = (e) => { e.stopPropagation(); window.location.href = post.url + "/pdf"; }; pdfButton.className = "post-button"; pdfButton.textContent = "PDF"; buttonsContainer.appendChild(singleButton); buttonsContainer.appendChild(htmlButton); buttonsContainer.appendChild(pdfButton); // Assemble the complete post content.appendChild(title); content.appendChild(buttonsContainer); postElement.appendChild(content); return postElement; } // Function to update search results counter function updateSearchResultsCount() { const searchResultsElement = document.getElementById("search-results"); const searchInput = document.getElementById("search-input"); if (searchResultsElement && searchInput) { const searchTerm = searchInput.value.trim().toLowerCase(); const count = searchTerm ? filteredPosts.length : allPosts.length; if (searchResultsElement) { searchResultsElement.textContent = searchTerm ? `${filteredPosts.length} post${filteredPosts.length !== 1 ? 's' : ''} found` : `${allPosts.length} post${allPosts.length !== 1 ? 's' : ''}`; } } } function renderPosts(posts) { const container = document.getElementById("posts-container"); // Clear container container.innerHTML = ""; posts.forEach((post) => { const postElement = createPostElement(post); container.appendChild(postElement); }); // Update search results counter updateSearchResultsCount(); } // Cache management functions function getCachedPosts() { try { const cached = localStorage.getItem(POSTS_CACHE_KEY); if (cached) { const { posts, timestamp } = JSON.parse(cached); // Check if cache is stale (more than 5 minutes old) if (Date.now() - timestamp < CACHE_EXPIRY_TIME) { return posts; } else { // Clear stale cache clearCachedPosts(); } } } catch (error) { console.error("Error retrieving cached posts:", error); } return null; } function cachePosts(posts) { try { const cacheData = { posts: posts, timestamp: Date.now(), }; localStorage.setItem(POSTS_CACHE_KEY, JSON.stringify(cacheData)); } catch (error) { console.error("Error caching posts:", error); } } function clearCachedPosts() { try { localStorage.removeItem(POSTS_CACHE_KEY); } catch (error) { console.error("Error clearing cached posts:", error); } } // Search functionality function performSearch() { const searchInput = document.getElementById("search-input"); const searchTerm = searchInput.value.trim().toLowerCase(); if (searchTerm === "") { // Show all posts if search is empty filteredPosts = [...allPosts]; renderPosts(filteredPosts); return; } // Filter posts by title (case-insensitive partial match) filteredPosts = allPosts.filter(post => post.title && post.title.toLowerCase().includes(searchTerm) ); renderPosts(filteredPosts); } // Initialize search functionality function initializeSearch() { const searchInput = document.getElementById("search-input"); if (searchInput) { // Add debouncing to search for better performance let searchTimeout; searchInput.addEventListener("input", () => { clearTimeout(searchTimeout); searchTimeout = setTimeout(performSearch, 300); // 300ms delay }); // Add search results counter const searchResults = document.createElement("div"); searchResults.id = "search-results"; searchResults.className = "search-results"; searchResults.textContent = "Loading posts..."; // Insert search results counter after the header const header = document.querySelector(".header"); if (header) { header.parentNode.insertBefore(searchResults, header.nextSibling); } } } // Extract real title from base_url function extractRealTitleFromUrl(baseUrl) { if (!baseUrl || typeof baseUrl !== "string") { return ""; } // Check if the URL contains the pattern we're looking for if (baseUrl.includes("reddit.com/")) { try { // Parse URL to extract path component const urlParts = baseUrl.split("/"); const lastPart = urlParts[urlParts.length - 1]; // Handle cases like "why_did_the_dday_beach_landing_soldiers_carry_all/#2025-10-14T23:33:16+00:00" let title = lastPart.split("#")[0]; // Convert URL-encoded characters back to readable text title = decodeURIComponent(title); // Replace underscores with spaces and capitalize first letter of each word title = title.replace(/_/g, " ").replace(/\b\w/g, (l) => l.toUpperCase()); return title; } catch (error) { console.error("Error extracting real title from URL:", error); return ""; } } return ""; } // Fetch the initial 100 posts from our API async function fetchRedditPosts() { try { const response = await fetch("/posts"); if (!response.ok) { throw new Error("Failed to fetch posts"); } const posts = await response.json(); return posts; } catch (error) { console.error("Error fetching initial posts:", error); // Return empty array on error return []; } } // Fetch more posts from our API async function fetchMorePosts(currentCount) { try { const response = await fetch(`/posts/more?count=${currentCount}`); if (!response.ok) { throw new Error("Failed to fetch more posts"); } const posts = await response.json(); return posts; } catch (error) { console.error("Error fetching more posts:", error); // Return empty array on error return []; } } async function loadInitialPosts() { // First try to load from cache const cachedPosts = getCachedPosts(); if (cachedPosts && cachedPosts.length > 0) { console.log("Loading posts from cache"); allPosts = cachedPosts; filteredPosts = [...allPosts]; renderPosts(filteredPosts); currentPostCount = cachedPosts.length; // Check if we have more posts or not const totalResponse = await fetch("/posts/total"); const totalData = await totalResponse.json(); if (totalData.total <= 100) { document.getElementById("no-more").classList.remove("hidden"); } return; } // If no cache, fetch from API try { console.log("Fetching posts from API"); const posts = await fetchRedditPosts(); allPosts = posts; filteredPosts = [...allPosts]; renderPosts(filteredPosts); currentPostCount = posts.length; // Cache the posts if (posts.length > 0) { cachePosts(posts); } // Check if we have more posts or not const totalResponse = await fetch("/posts/total"); const totalData = await totalResponse.json(); if (totalData.total <= 100) { document.getElementById("no-more").classList.remove("hidden"); } } catch (error) { console.error("Failed to load initial posts:", error); } } async function loadMorePosts() { if (isLoading) { return; } isLoading = true; document.getElementById("loading").classList.remove("hidden"); try { const morePosts = await fetchMorePosts(currentPostCount); if (morePosts.length > 0) { // Add new posts to allPosts and filteredPosts allPosts = [...allPosts, ...morePosts]; filteredPosts = [...filteredPosts, ...morePosts]; renderPosts(filteredPosts); currentPostCount += morePosts.length; } else { // No more posts to load document.getElementById("no-more").classList.remove("hidden"); document.getElementById("loading").classList.add("hidden"); } } catch (error) { console.error("Failed to load more posts:", error); } finally { isLoading = false; document.getElementById("loading").classList.add("hidden"); } } // Set up infinite scroll with Intersection Observer function setupInfiniteScroll() { const container = document.getElementById("posts-container"); // Use Intersection Observer API for efficient infinite scrolling const observer = new IntersectionObserver( (entries) => { if (entries[0].isIntersecting && !isLoading) { loadMorePosts(); } }, { root: null, rootMargin: "20px", threshold: 0.1, }, ); // Create a sentinel element to observe const sentinel = document.createElement("div"); sentinel.id = "sentinel"; container.appendChild(sentinel); observer.observe(sentinel); } // Initialize the app when the DOM is loaded document.addEventListener("DOMContentLoaded", () => { loadInitialPosts(); setupInfiniteScroll(); initializeSearch(); // Add event listener to dark mode toggle button const darkModeToggle = document.getElementById("dark-mode-toggle"); if (darkModeToggle) { darkModeToggle.addEventListener("click", toggleDarkMode); } // Also handle scroll manually for browsers that don't support Intersection Observer window.addEventListener("scroll", () => { if ( window.innerHeight + window.scrollY >= document.body.offsetHeight - 1000 && !isLoading ) { loadMorePosts(); } }); });