Web-Blog post Box theme
Web-Blog post Box theme with Page Button
Use this code as a gadget (V-1.0)
<div id="custom-post-grid">
<div class="cpg-grid" id="cpg-grid">
<p class="cpg-message">Loading posts...</p>
</div>
<div class="cpg-pagination" id="cpg-pagination"></div>
</div>
<style>
#custom-post-grid {
width: 100%;
margin: 0 auto;
}
#custom-post-grid .cpg-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 22px;
}
#custom-post-grid .cpg-card {
overflow: hidden;
background: #fff;
border: 1px solid #e8e8e8;
border-radius: 10px;
box-shadow: 0 3px 12px rgba(0,0,0,.06);
cursor: pointer;
transition: .2s ease;
}
#custom-post-grid .cpg-card:hover {
transform: translateY(-4px);
box-shadow: 0 7px 20px rgba(0,0,0,.11);
}
#custom-post-grid .cpg-image-link {
display: block;
aspect-ratio: 16 / 10;
overflow: hidden;
background: #f1f1f1;
}
#custom-post-grid .cpg-image {
width: 100%;
height: 100%;
display: block;
object-fit: cover;
}
#custom-post-grid .cpg-content {
padding: 15px;
}
#custom-post-grid .cpg-title {
margin: 0 0 9px;
font-size: 18px;
line-height: 1.4;
}
#custom-post-grid .cpg-title a {
color: #222;
text-decoration: none;
}
#custom-post-grid .cpg-title a:hover {
color: #0879d1;
}
#custom-post-grid .cpg-date {
display: block;
margin-bottom: 10px;
color: #888;
font-size: 12px;
}
#custom-post-grid .cpg-summary {
margin: 0;
color: #666;
font-size: 14px;
line-height: 1.6;
}
#custom-post-grid .cpg-message {
grid-column: 1 / -1;
text-align: center;
padding: 25px 10px;
}
#custom-post-grid .cpg-pagination {
display: flex;
justify-content: center;
align-items: center;
flex-wrap: wrap;
gap: 7px;
margin: 30px 0 20px;
}
#custom-post-grid .cpg-page {
min-width: 38px;
height: 38px;
padding: 0 11px;
border: 1px solid #ddd;
border-radius: 5px;
background: #fff;
color: #333;
font-size: 14px;
cursor: pointer;
}
#custom-post-grid .cpg-page:hover {
border-color: #0879d1;
color: #0879d1;
}
#custom-post-grid .cpg-page.active {
background: #0879d1;
border-color: #0879d1;
color: #fff;
}
#custom-post-grid .cpg-page:disabled {
opacity: .4;
cursor: not-allowed;
}
#custom-post-grid .cpg-dots {
padding: 0 4px;
color: #777;
}
/* Hide previously opened Blogger post content */
.cpg-previous-post-hidden {
display: none !important;
}
@media(max-width:900px) {
#custom-post-grid .cpg-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 16px;
}
}
@media(max-width:550px) {
#custom-post-grid .cpg-grid {
grid-template-columns: 1fr;
}
}
</style>
<script>
(function () {
"use strict";
const root = document.getElementById("custom-post-grid");
if (!root || root.dataset.initialized === "true") return;
root.dataset.initialized = "true";
const grid = root.querySelector("#cpg-grid");
const pagination = root.querySelector("#cpg-pagination");
const postsPerPage = 9;
let currentPage = 1;
let totalPages = 1;
let pageCache = {};
let requestNumber = 0;
const feedUrl = window.location.origin +
"/feeds/posts/default?alt=json";
// ==========================================
// CLOSE PREVIOUSLY OPENED BLOGGER POSTS
// ==========================================
function closePreviousPost() {
const selectors = [
".post-outer",
".post",
".blog-post",
"article.hentry",
".post-body",
".post-content"
];
const elements = document.querySelectorAll(
selectors.join(",")
);
elements.forEach(function(element) {
// Never hide the custom post grid or its parent.
if (
element === root ||
element.contains(root) ||
root.contains(element)
) {
return;
}
// Only hide visible post elements.
const style = window.getComputedStyle(element);
if (
style.display !== "none" &&
style.visibility !== "hidden"
) {
element.classList.add(
"cpg-previous-post-hidden"
);
}
});
}
// ==========================================
// GET POST INFORMATION
// ==========================================
function getLink(entry) {
const links = entry.link || [];
const link = links.find(function(item) {
return item.rel === "alternate";
});
return link ? link.href : "#";
}
function getImage(entry) {
if (
entry.media$thumbnail &&
entry.media$thumbnail.url
) {
return entry.media$thumbnail.url.replace(
/\/s\d+(-c)?\//,
"/s800/"
);
}
const html =
(entry.content && entry.content.$t) ||
(entry.summary && entry.summary.$t) ||
"";
const match = html.match(
/<img[^>]+src=["']([^"']+)["']/i
);
return match ? match[1] : "";
}
function getSummary(entry) {
const html =
(entry.summary && entry.summary.$t) ||
(entry.content && entry.content.$t) ||
"";
const doc = new DOMParser().parseFromString(
html,
"text/html"
);
const text = (doc.body.textContent || "").trim();
return text.length > 140
? text.substring(0, 140) + "..."
: text;
}
function getDate(entry) {
if (!entry.published) return "";
return new Date(
entry.published.$t
).toLocaleDateString(
undefined,
{
year: "numeric",
month: "short",
day: "numeric"
}
);
}
// ==========================================
// CREATE POST CARD
// ==========================================
function createCard(entry) {
const url = getLink(entry);
const title = entry.title
? entry.title.$t
: "Untitled";
const image = getImage(entry);
const card = document.createElement("article");
card.className = "cpg-card";
card.dataset.url = url;
if (image) {
const imageLink = document.createElement("a");
imageLink.className = "cpg-image-link";
imageLink.href = url;
const img = document.createElement("img");
img.className = "cpg-image";
img.src = image;
img.alt = title;
img.loading = "lazy";
imageLink.appendChild(img);
card.appendChild(imageLink);
}
const content = document.createElement("div");
content.className = "cpg-content";
const heading = document.createElement("h3");
heading.className = "cpg-title";
const titleLink = document.createElement("a");
titleLink.href = url;
titleLink.textContent = title;
heading.appendChild(titleLink);
content.appendChild(heading);
const date = getDate(entry);
if (date) {
const dateElement = document.createElement("span");
dateElement.className = "cpg-date";
dateElement.textContent = date;
content.appendChild(dateElement);
}
const summary = document.createElement("p");
summary.className = "cpg-summary";
summary.textContent = getSummary(entry);
content.appendChild(summary);
card.appendChild(content);
return card;
}
// ==========================================
// OPEN POST WHEN CARD IS CLICKED
// ==========================================
grid.addEventListener("click", function(event) {
if (event.target.closest("a")) return;
const card = event.target.closest(".cpg-card");
if (!card || !grid.contains(card)) return;
const url = card.dataset.url;
if (url && url !== "#") {
window.location.href = url;
}
});
// ==========================================
// PAGINATION BUTTONS
// ==========================================
function addButton(label, page, active, disabled) {
const button = document.createElement("button");
button.type = "button";
button.className =
"cpg-page" + (active ? " active" : "");
button.textContent = label;
button.dataset.page = page;
button.disabled = !!disabled;
pagination.appendChild(button);
}
function renderPagination() {
pagination.replaceChildren();
if (totalPages <= 1) return;
addButton(
"\u00AB",
currentPage - 1,
false,
currentPage === 1
);
let pages = [];
if (totalPages <= 7) {
for (let i = 1; i <= totalPages; i++) {
pages.push(i);
}
} else {
pages.push(1);
const start = Math.max(2, currentPage - 1);
const end = Math.min(
totalPages - 1,
currentPage + 1
);
if (start > 2) pages.push("...");
for (let i = start; i <= end; i++) {
pages.push(i);
}
if (end < totalPages - 1) {
pages.push("...");
}
pages.push(totalPages);
}
pages.forEach(function(page) {
if (page === "...") {
const dots = document.createElement("span");
dots.className = "cpg-dots";
dots.textContent = "...";
pagination.appendChild(dots);
} else {
addButton(
String(page),
page,
page === currentPage,
false
);
}
});
addButton(
"\u00BB",
currentPage + 1,
false,
currentPage === totalPages
);
}
// ==========================================
// SCROLL TO GRID
// ==========================================
function scrollToGrid() {
const top =
root.getBoundingClientRect().top +
window.pageYOffset - 20;
window.scrollTo({
top: Math.max(0, top),
behavior: "smooth"
});
}
// ==========================================
// LOAD SELECTED PAGE
// ==========================================
async function loadPage(page, shouldScroll) {
page = Math.max(
1,
Math.min(page, totalPages)
);
currentPage = page;
const thisRequest = ++requestNumber;
grid.innerHTML =
'<p class="cpg-message">Loading posts...</p>';
pagination.replaceChildren();
try {
let entries = pageCache[page];
if (!entries) {
const start =
((page - 1) * postsPerPage) + 1;
const url = feedUrl +
"&start-index=" + start +
"&max-results=" + postsPerPage;
const response = await fetch(url);
if (!response.ok) {
throw new Error("Feed request failed");
}
const data = await response.json();
const feed = data.feed || {};
const total = Number(
feed.openSearch$totalResults &&
feed.openSearch$totalResults.$t
) || 0;
totalPages = Math.max(
1,
Math.ceil(total / postsPerPage)
);
entries = feed.entry || [];
pageCache[page] = entries;
}
// Ignore outdated requests.
if (thisRequest !== requestNumber) return;
grid.replaceChildren();
if (!entries.length) {
grid.innerHTML =
'<p class="cpg-message">No posts found.</p>';
} else {
entries.forEach(function(entry) {
grid.appendChild(createCard(entry));
});
}
renderPagination();
if (shouldScroll) {
scrollToGrid();
}
} catch (error) {
if (thisRequest !== requestNumber) return;
grid.innerHTML =
'<p class="cpg-message">Sorry, posts could not be loaded. Please refresh.</p>';
console.error("Post grid error:", error);
}
}
// ==========================================
// FIX: PAGINATION CLICK HANDLER
// ==========================================
document.addEventListener("click", function(event) {
const button = event.target.closest(
"#custom-post-grid .cpg-pagination button[data-page]"
);
if (!button) return;
event.preventDefault();
event.stopPropagation();
event.stopImmediatePropagation();
if (button.disabled) return;
const page = Number(button.dataset.page);
if (
!Number.isInteger(page) ||
page < 1 ||
page > totalPages ||
page === currentPage
) {
return;
}
// First close the previously opened post.
closePreviousPost();
// Then load the newly selected page.
loadPage(page, true);
}, true);
// ==========================================
// INITIAL LOAD
// ==========================================
loadPage(1, false);
})();
</script>
Comments
Post a Comment
Thanks for your Comments.