<p>Progress</p>
<div id="progress-container">
<div id="progress-bar" style="width: 0%;"></div>
<span id="progressPercentage">0%</span>
</div>
<div id="sort-controls">
<label for="sort-select">Sort by:</label>
<select id="sort-select" onchange="changeSortOrder(this.value)">
<option value="newest">Newest First</option>
<option value="oldest">Oldest First</option>
</select>
</div>
<div id="lit005-posts"></div>
<style>
#progress-container {
width: 100%;
height: 30px;
background-color: #f3f3f3;
margin-bottom: 20px;
border-radius: 5px;
overflow: hidden;
position: relative;
}
#progress-bar {
height: 100%;
background-color: #28a745;
text-align: center;
line-height: 30px;
color: white;
transition: width 0.5s ease-in-out;
}
#progressPercentage {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-weight: bold;
color: #333;
z-index: 2;
}
#sort-controls {
margin: 20px 0;
text-align: right;
}
#sort-select {
padding: 8px 12px;
border-radius: 5px;
border: 1px solid #ddd;
background-color: #f8f8f8;
cursor: pointer;
font-size: 14px;
transition: all 0.2s ease;
}
#sort-select:hover {
border-color: #bbb;
background-color: #f0f0f0;
}
#sort-select:focus {
outline: none;
border-color: #007bff;
box-shadow: 0 0 0 2px rgba(0,123,255,0.25);
}
#lit005-posts {
font-family: Arial, sans-serif;
line-height: 1.6;
margin: 20px;
}
#lit005-posts h3 {
color: #005c99;
border-bottom: 2px solid #005c99;
padding-bottom: 5px;
margin-top: 30px;
display: flex;
justify-content: space-between;
align-items: center;
}
#lit005-posts ul {
list-style: none;
padding-left: 0;
margin-left: 20px;
}
#lit005-posts li {
margin-bottom: 12px;
position: relative;
}
/* No visual indicator for completed posts as per request */
#lit005-posts a {
text-decoration: none;
color: #333;
font-size: 14px;
font-weight: bold;
display: inline-block;
margin-bottom: 5px;
padding: 10px 20px;
background-color: #007bff;
color: white;
border-radius: 5px;
text-align: center;
width: 80%;
transition: background-color 0.3s;
}
#lit005-posts a:hover {
background-color: #005c99;
}
/* New and improved pagination styles */
.pagination {
display: flex;
justify-content: center;
align-items: center;
flex-wrap: wrap;
margin: 30px 0 20px;
gap: 8px;
}
.pagination-item {
margin: 0 2px;
padding: 8px 16px;
background-color: #f0f0f0;
color: #333;
text-decoration: none;
border-radius: 5px;
min-width: 40px;
text-align: center;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
border: 1px solid #ddd;
user-select: none;
}
.pagination-item:hover:not(.active):not(.disabled) {
background-color: #e0e0e0;
border-color: #bbb;
}
.pagination-item.active {
background-color: #007bff;
color: white;
border-color: #0062cc;
font-weight: bold;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
cursor: default;
}
.pagination-item.disabled {
opacity: 0.6;
cursor: not-allowed;
background-color: #f8f8f8;
}
.pagination-ellipsis {
margin: 0 2px;
padding: 8px 12px;
color: #666;
}
.pagination-nav {
display: flex;
align-items: center;
padding: 8px 12px;
border-radius: 5px;
background-color: #f0f0f0;
border: 1px solid #ddd;
cursor: pointer;
transition: all 0.2s ease;
}
.pagination-nav:hover:not(.disabled) {
background-color: #e0e0e0;
border-color: #bbb;
}
.pagination-nav.disabled {
opacity: 0.6;
cursor: not-allowed;
}
.no-posts {
padding: 20px;
text-align: center;
font-size: 14px;
color: #666;
background-color: #f8f8f8;
border-radius: 5px;
margin-top: 20px;
}
/* Loading indicator */
.page-loading {
opacity: 0.7;
pointer-events: none;
transition: opacity 0.3s;
}
/* Add media query for mobile responsiveness */
@media (max-width: 576px) {
.pagination {
gap: 4px;
}
.pagination-item {
padding: 6px 10px;
min-width: 32px;
font-size: 14px;
}
.pagination-nav {
padding: 6px 8px;
}
.pagination-ellipsis {
padding: 6px 8px;
}
}
</style>
<script>
const blogURL = "https://jupebcourseindex.blogspot.com";
const label = "lit005"; // Changed label from lit004 to lit005
const maxPostsPerLabel = 10; // Maximum number of posts to show per page
let currentPage = 1;
let totalPosts = 0;
let totalPages = 0;
let completedPosts = 0;
let activeTimers = {}; // To track active timers for opened posts
let allPosts = []; // Store all posts for sorting
let currentSortOrder = "newest"; // Default sort order
let isPageLoading = false; // Track if a page transition is in progress
// Initialize when the page loads
document.addEventListener('DOMContentLoaded', function() {
// Restore current page from localStorage if available
const savedPage = localStorage.getItem('lit005-current-page');
if (savedPage) {
currentPage = parseInt(savedPage, 10);
}
// Restore sort preference if available
const savedSort = localStorage.getItem('lit005-sort-order');
if (savedSort) {
currentSortOrder = savedSort;
document.getElementById('sort-select').value = currentSortOrder;
}
fetchPostsByLabel();
});
// Run on load without waiting for DOMContentLoaded (for some environments)
if (document.readyState === 'complete' || document.readyState === 'interactive') {
setTimeout(() => {
// Restore current page from localStorage if available
const savedPage = localStorage.getItem('lit005-current-page');
if (savedPage) {
currentPage = parseInt(savedPage, 10);
}
// Restore sort preference if available
const savedSort = localStorage.getItem('lit005-sort-order');
if (savedSort) {
currentSortOrder = savedSort;
const sortSelect = document.getElementById('sort-select');
if (sortSelect) sortSelect.value = currentSortOrder;
}
fetchPostsByLabel();
}, 1);
}
function fetchPostsByLabel() {
setPageLoading(true);
const labelURL = `${blogURL}/feeds/posts/summary/-/${encodeURIComponent(label)}?alt=json&max-results=100`;
fetch(labelURL)
.then(response => response.json())
.then(data => {
const posts = data.feed.entry || [];
allPosts = posts.map(post => {
return {
title: post.title.$t,
link: post.link.find(l => l.rel === "alternate").href,
published: new Date(post.published.$t),
updated: new Date(post.updated.$t)
};
});
totalPosts = allPosts.length;
// Sort posts based on current preference
sortPosts();
// Save total posts count to localStorage
localStorage.setItem('lit002-total-posts', totalPosts);
displayCurrentPage();
setPageLoading(false);
})
.catch(error => {
console.error('Error fetching posts:', error);
document.getElementById("lit002-posts").innerHTML =
'<p class="no-posts">Error loading posts. Please try again later.</p>';
setPageLoading(false);
});
}
function setPageLoading(isLoading) {
isPageLoading = isLoading;
const postsContainer = document.getElementById("lit002-posts");
if (isLoading) {
postsContainer.classList.add("page-loading");
} else {
postsContainer.classList.remove("page-loading");
}
}
function sortPosts() {
if (currentSortOrder === "newest") {
allPosts.sort((a, b) => b.published - a.published);
} else {
allPosts.sort((a, b) => a.published - b.published);
}
}
function changeSortOrder(sortOrder) {
if (isPageLoading) return;
setPageLoading(true);
currentSortOrder = sortOrder;
localStorage.setItem('lit005-sort-order', sortOrder);
// Re-sort and display
sortPosts();
displayCurrentPage();
setTimeout(() => {
setPageLoading(false);
}, 300);
}
function displayCurrentPage() {
totalPages = Math.ceil(totalPosts / maxPostsPerLabel);
// Validate current page
if (currentPage > totalPages) {
currentPage = 1;
}
localStorage.setItem('lit002-total-pages', totalPages);
if (totalPosts === 0) {
document.getElementById("lit002-posts").innerHTML =
'<p class="no-posts">No posts available under lit005.</p>';
return;
}
// Save the current page to localStorage
localStorage.setItem('lit005-current-page', currentPage);
const startIndex = (currentPage - 1) * maxPostsPerLabel;
const endIndex = Math.min(startIndex + maxPostsPerLabel, totalPosts);
let html = `<h3>Literature 005 <span class="post-count">(${totalPosts} posts)</span></h3><ul>`;
for (let i = startIndex; i < endIndex; i++) {
const post = allPosts[i];
const title = post.title;
const link = post.link;
const postId = `post-${i}`;
// Save post info to localStorage
localStorage.setItem(`lit005-post-${i}-title`, title);
localStorage.setItem(`lit005-post-${i}-link`, link);
// No visual indication needed for completed posts
html += `<li data-post-id="${postId}"><a href="${link}" target="_blank" class="post-link" data-post-id="${postId}" data-index="${i}">${title}</a></li>`;
}
html += `</ul>`;
// Add pagination if there are multiple pages
if (totalPages > 1) {
html += generatePagination();
}
document.getElementById("lit005-posts").innerHTML = html;
// Setup event listeners
setupPostListeners();
// Update progress bar after posts are loaded
updateProgressBarFromStorage();
}
function generatePagination() {
let paginationHTML = '<div class="pagination" role="navigation" aria-label="Pagination Navigation">';
// Previous Page Button
if (currentPage > 1) {
paginationHTML += `<span class="pagination-nav" onclick="return changePage(${currentPage - 1})" aria-label="Go to previous page">« Prev</span>`;
} else {
paginationHTML += `<span class="pagination-nav disabled" aria-disabled="true">« Prev</span>`;
}
// Page Number Links
// Improved algorithm for page number display
const maxVisiblePages = 5; // Maximum page numbers to show
// Calculate range of pages to display
let startPage, endPage;
if (totalPages <= maxVisiblePages) {
// Show all pages if total pages is less than max visible
startPage = 1;
endPage = totalPages;
} else {
// Calculate start and end pages for large page counts
const halfVisible = Math.floor(maxVisiblePages / 2);
if (currentPage <= halfVisible + 1) {
// Near the start
startPage = 1;
endPage = maxVisiblePages;
} else if (currentPage >= totalPages - halfVisible) {
// Near the end
startPage = totalPages - maxVisiblePages + 1;
endPage = totalPages;
} else {
// In the middle
startPage = currentPage - halfVisible;
endPage = currentPage + halfVisible;
}
}
// First page link (if not already in range)
if (startPage > 1) {
paginationHTML += `<span class="pagination-item" onclick="return changePage(1)" aria-label="Go to page 1">1</span>`;
if (startPage > 2) {
paginationHTML += `<span class="pagination-ellipsis" aria-hidden="true">...</span>`;
}
}
// Page numbers
for (let i = startPage; i <= endPage; i++) {
if (i === currentPage) {
paginationHTML += `<span class="pagination-item active" aria-current="page" aria-label="Current page, page ${i}">${i}</span>`;
} else {
paginationHTML += `<span class="pagination-item" onclick="return changePage(${i})" aria-label="Go to page ${i}">${i}</span>`;
}
}
// Last page link (if not already in range)
if (endPage < totalPages) {
if (endPage < totalPages - 1) {
paginationHTML += `<span class="pagination-ellipsis" aria-hidden="true">...</span>`;
}
paginationHTML += `<span class="pagination-item" onclick="return changePage(${totalPages})" aria-label="Go to page ${totalPages}">${totalPages}</span>`;
}
// Next Page Button
if (currentPage < totalPages) {
paginationHTML += `<span class="pagination-nav" onclick="return changePage(${currentPage + 1})" aria-label="Go to next page">Next »</span>`;
} else {
paginationHTML += `<span class="pagination-nav disabled" aria-disabled="true">Next »</span>`;
}
paginationHTML += '</div>';
return paginationHTML;
}
function changePage(page) {
if (isPageLoading) return false;
if (page >= 1 && page <= totalPages && page !== currentPage) {
setPageLoading(true);
currentPage = page;
// Small delay to show loading state
setTimeout(() => {
displayCurrentPage();
setPageLoading(false);
// Scroll to top of posts container for better UX
const postsContainer = document.getElementById("lit005-posts");
if (postsContainer) {
postsContainer.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
}, 300);
}
return false; // Prevent default anchor behavior
}
function setupPostListeners() {
// Add click handlers to post links
const postLinks = document.querySelectorAll('.post-link');
postLinks.forEach(link => {
const postId = link.dataset.postId;
const postIndex = link.dataset.index;
link.addEventListener("click", function(e) {
// Start timer when user clicks
startPostTimer(postId, postIndex);
});
});
}
function startPostTimer(postId, postIndex) {
// Cancel any existing timer for this post
if (activeTimers[postId]) {
clearTimeout(activeTimers[postId]);
}
// Start a new 15-second timer
activeTimers[postId] = setTimeout(() => {
markPostAsCompleted(postId, postIndex);
}, 15000); // 15 seconds
}
function markPostAsCompleted(postId, postIndex) {
// Mark post as completed in local storage using consistent naming
localStorage.setItem(`lit005-completed-post-${postIndex}`, 'true');
// No need to update link appearance as we only track via progress bar
// Clear the timer reference
delete activeTimers[postId];
// Update progress bar
updateProgressBarFromStorage();
}
function updateProgressBarFromStorage() {
const totalPostsCount = parseInt(localStorage.getItem('lit002-total-posts') || '0', 10);
let completedCount = 0;
// Count completed posts from localStorage using consistent ID format
for (let i = 0; i < totalPostsCount; i++) {
if (localStorage.getItem(`lit002-completed-post-${i}`) === 'true') {
completedCount++;
}
}
// Update progress counter in localStorage
localStorage.setItem('lit005-completed-count', completedCount);
if (totalPostsCount > 0) {
const progress = (completedCount / totalPostsCount) * 100;
localStorage.setItem('lit005-progress-percentage', progress.toFixed(1));
const progressBar = document.getElementById("progress-bar");
const progressText = document.getElementById("progressPercentage");
if (progressBar && progressText) {
progressBar.style.width = `${progress}%`;
progressText.textContent = `${Math.round(progress)}%`;
}
}
}
</script>