430 lines
12 KiB
JavaScript
430 lines
12 KiB
JavaScript
// 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();
|
|
}
|
|
});
|
|
});
|