Welcome

Loading posts...

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