const newLinkInput = document.getElementById('newLinkInput'); const addLinkBtn = document.getElementById('addLinkBtn'); const newLinksContainer = document.getElementById('newLinksContainer'); const watchedLinksContainer = document.getElementById('watchedLinksContainer'); const PER_PAGE = 8; const WATCHED = false; let currentPage = 1; let linksData = []; function extractReelId(url) { // Example: https://www.instagram.com/reel/ABC123xyz/ const match = url.match(/instagram\.com\/reel\/([a-zA-Z0-9_-]+)/); return match ? match[1] : null; } function loadLinks() { const cacheKeyWatched = `links_cache_page_${currentPage}_per_${PER_PAGE}&watched='true'`; const cachedWatched = localStorage.getItem(cacheKeyWatched); const cacheKey = `links_cache_page_${currentPage}_per_${PER_PAGE}`; const cached = localStorage.getItem(cacheKey); if (cachedWatched) { const data = JSON.parse(cachedWatched); linksData = data.results; renderPaginatedList( linksData,watchedLinksContainer,true,data.page,data.pages ); } if (cached) { const data = JSON.parse(cached); linksData = data.results; renderPaginatedList( linksData,watchedLinksContainer,true,data.page,data.pages ); } // Always fetch fresh data in the background fetch(`/api/links/?page=${currentPage}&per_page=${PER_PAGE}&watched='true'`) .then(response => response.json()) .then(data => { localStorage.setItem(cacheKeyWatched, JSON.stringify(data)); if (!cachedWatched) { linksData = data.results; renderPaginatedList( linksData,watchedLinksContainer,true,data.page,data.pages ); } }) .catch(err => console.error(err)); fetch(`/api/links/?page=${currentPage}&per_page=${PER_PAGE}`) .then(response => response.json()) .then(data => { localStorage.setItem(cacheKey, JSON.stringify(data)); if (!cached) { linksData = data.results; renderPaginatedList( linksData,newLinksContainer,false,data.page,data.pages ); } }) .catch(err => console.error(err)); } function renderPaginatedList(linkList, container, isWatched, page, totalPages) { container.innerHTML = ''; const row = document.createElement('div'); row.className = 'row gy-4'; linkList .filter(link => link.watched === isWatched) .forEach(link => { const column = document.createElement('div'); column.className = 'col-12 col-sm-6 col-md-4 col-lg-3'; column.appendChild(createLinkElement(link, isWatched)); row.appendChild(column); }); container.appendChild(row); // Pagination controls if (totalPages > 1) { const navigation = document.createElement('div'); navigation.className = 'd-flex justify-content-center mt-4 gap-2'; const previousBtn = document.createElement('button'); previousBtn.innerText = '← Previous'; previousBtn.className = 'btn btn-outline-primary'; previousBtn.disabled = page <= 1; previousBtn.onclick = () => { currentPage--; loadLinks(); }; const nextBtn = document.createElement('button'); nextBtn.innerText = 'Next →'; nextBtn.className = 'btn btn-outline-primary'; nextBtn.disabled = page >= totalPages; nextBtn.onclick = () => { currentPage++; loadLinks(); }; navigation.appendChild(previousBtn); navigation.appendChild(nextBtn); container.appendChild(navigation); } reinitializeInstagramEmbeds(); } // Create a DOM element for a single link function createLinkElement(link, isWatched) { const wrapper = document.createElement('div'); wrapper.className = 'mb-auto'; const reelId = extractReelId(link.url); if (!reelId) { wrapper.innerText = 'Invalid Instagram Reel URL'; return wrapper; } // Clean iframe embed const iframeWrapper = document.createElement('div'); iframeWrapper.style.width = '100%'; iframeWrapper.style.height = '600px'; iframeWrapper.style.overflow = 'hidden'; iframeWrapper.style.borderRadius = '10px'; const iframe = document.createElement('iframe'); iframe.src = `https://www.instagram.com/reel/${reelId}/embed`; iframe.width = '100%'; iframe.height = '700'; // taller to allow for cropping iframe.style.border = 'none'; iframe.allow="fullscreen"; iframe.loading = 'lazy'; iframeWrapper.appendChild(iframe); wrapper.appendChild(iframeWrapper); const btnGroup = document.createElement('div'); btnGroup.className = 'mt-2 d-flex gap-2'; // Button to mark watched if it's not watched if (!isWatched) { const watchButton = document.createElement('button'); watchButton.className = 'btn btn-sm btn-secondary'; watchButton.innerText = 'Mark Watched'; watchButton.onclick = () => markLinkWatched(link.id); btnGroup.appendChild(watchButton); } // Delete button const deleteButton = document.createElement('button'); deleteButton.innerText = 'Delete'; deleteButton.className = 'btn btn-sm btn-danger'; deleteButton.onclick = () => deleteLink(link.id); btnGroup.appendChild(deleteButton); wrapper.appendChild(btnGroup); return wrapper; } function deleteLink(linkId) { fetch(`/api/links/${linkId}/delete/`, { method: 'DELETE' }) .then(response => { if (response.ok) { linksData = linksData.filter(link => link.id !== linkId); currentPage = 1; loadLinks(); reinitializeInstagramEmbeds(); } }) .catch(err => console.error(err)); } // Re-run the Instagram embed script after we’ve injected new blockquotes function reinitializeInstagramEmbeds() { if (window.instgrm) { window.instgrm.Embeds.process(); } } // Mark a link as watched function markLinkWatched(linkId) { fetch(`/api/links/${linkId}/watched/`, { method: 'PATCH' }) .then(response => response.json()) .then(updatedLink => { // Update the local array const idx = linksData.findIndex(link => link.id === updatedLink.id); if (idx !== -1) { linksData[idx] = updatedLink; } loadLinks(); reinitializeInstagramEmbeds(); }) .catch(err => console.error(err)); } // Add new link addLinkBtn.addEventListener('click', () => { const newUrl = newLinkInput.value.trim(); if (!newUrl) return; fetch('/api/links/add/', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url: newUrl }) }) .then(response => response.json()) .then(addedLink => { // update local data linksData.push(addedLink); newLinkInput.value = ''; currentPage = 1; loadLinks(); reinitializeInstagramEmbeds(); }) .catch(err => console.error(err)); }); // On page load loadLinks();