prime8dev

prime8dev
git clone https://git.prime8.dev/prime8dev.git
Log | Files | Refs | README | LICENSE

blog.js (5377B)


      1 import { isEditableTarget } from "./dom.js";
      2 import { clearAppTitleDetail, setAppTitleDetail, updateAppTitle } from "./header.js";
      3 
      4 const blogHandlers = {
      5   keydown: null,
      6   popstate: null,
      7 };
      8 
      9 export function initBlog() {
     10   const section = document.getElementById("blog");
     11   if (!section) return;
     12 
     13   const listView = section.querySelector("#blog-list-view");
     14   const postView = section.querySelector("#blog-post-view");
     15   const backBtn = section.querySelector(".blog-back-btn");
     16   const listItems = Array.from(section.querySelectorAll(".blog-list-item"));
     17   if (!listView || !postView || listItems.length === 0) return;
     18 
     19   let selectedIndex = 0;
     20   const hasPrefilledPost = Boolean(document.getElementById("blog-post-content")?.children.length);
     21   let postOpen = !postView.classList.contains("hidden") || window.location.pathname.startsWith("/blog/");
     22   window.isBlogPostOpen = () => postOpen;
     23 
     24   if (postOpen) {
     25     listView.classList.add("hidden");
     26     postView.classList.remove("hidden");
     27   } else {
     28     listView.classList.remove("hidden");
     29     postView.classList.add("hidden");
     30   }
     31 
     32   function pathBlogID() {
     33     return decodeURIComponent(window.location.pathname.match(/^\/blog\/([^/]+)$/)?.[1] || "");
     34   }
     35 
     36   function itemByBlogID(blogID) {
     37     if (!blogID) return null;
     38     return listItems.find((item) => item.dataset.id === blogID) || null;
     39   }
     40 
     41   function select(index) {
     42     selectedIndex = index;
     43     listItems.forEach((item, i) => item.classList.toggle("selected", i === index));
     44     listItems[index]?.scrollIntoView({ behavior: "smooth", block: "nearest" });
     45 
     46     const detail = listItems[index]?.querySelector(".blog-list-title")?.textContent?.trim() || "";
     47     setAppTitleDetail("blog", detail);
     48     updateAppTitle("blog");
     49 
     50     const selectedItem = listItems[index];
     51     if (selectedItem) {
     52       selectedItem.dispatchEvent(new Event("blog:selected", { bubbles: true }));
     53     }
     54   }
     55 
     56   function openPost(blogID, updateHistory = true) {
     57     postOpen = true;
     58     listView.classList.add("hidden");
     59     postView.classList.remove("hidden");
     60     postView.scrollTop = 0;
     61 
     62     if (blogID && updateHistory) {
     63       const path = `/blog/${encodeURIComponent(blogID)}`;
     64       if (window.location.pathname !== path) {
     65         history.replaceState({ tab: "blog" }, "", path);
     66       }
     67     }
     68 
     69     updateAppTitle("blog");
     70   }
     71 
     72   function closePost(updateHistory = true) {
     73     postOpen = false;
     74     listView.classList.remove("hidden");
     75     postView.classList.add("hidden");
     76     clearAppTitleDetail("blog");
     77 
     78     if (updateHistory && window.location.pathname !== "/blog") {
     79       history.replaceState({ tab: "blog" }, "", "/blog");
     80     }
     81 
     82     updateAppTitle("blog");
     83   }
     84 
     85   listItems.forEach((item, index) => {
     86     if (item.dataset.boundClick === "1") return;
     87     item.dataset.boundClick = "1";
     88 
     89     item.addEventListener("click", () => {
     90       select(index);
     91       openPost(item.dataset.id, true);
     92     });
     93   });
     94 
     95   if (backBtn && backBtn.dataset.boundClick !== "1") {
     96     backBtn.dataset.boundClick = "1";
     97     backBtn.addEventListener("click", () => closePost(true));
     98   }
     99 
    100   if (blogHandlers.keydown) {
    101     document.removeEventListener("keydown", blogHandlers.keydown);
    102   }
    103 
    104   blogHandlers.keydown = (e) => {
    105     if (!section.isConnected) return;
    106     if (window.isLightboxOpen?.()) return;
    107     if (isEditableTarget(e.target)) return;
    108 
    109     if (!postOpen) {
    110       switch (e.key) {
    111         case "ArrowUp":
    112           e.preventDefault();
    113           if (selectedIndex > 0) select(selectedIndex - 1);
    114           break;
    115         case "ArrowDown":
    116           e.preventDefault();
    117           if (selectedIndex < listItems.length - 1) select(selectedIndex + 1);
    118           break;
    119         case "ArrowRight":
    120         case "Enter":
    121           e.preventDefault();
    122           listItems[selectedIndex]?.click();
    123           break;
    124         default:
    125           break;
    126       }
    127     } else {
    128       switch (e.key) {
    129         case "ArrowLeft":
    130         case "Escape":
    131           e.preventDefault();
    132           closePost(true);
    133           break;
    134         case "ArrowUp":
    135           e.preventDefault();
    136           postView.scrollBy({ top: -80, behavior: "smooth" });
    137           break;
    138         case "ArrowDown":
    139           e.preventDefault();
    140           postView.scrollBy({ top: 80, behavior: "smooth" });
    141           break;
    142         default:
    143           break;
    144       }
    145     }
    146   };
    147 
    148   document.addEventListener("keydown", blogHandlers.keydown);
    149 
    150   if (blogHandlers.popstate) {
    151     window.removeEventListener("popstate", blogHandlers.popstate);
    152   }
    153 
    154   blogHandlers.popstate = () => {
    155     if (!section.isConnected) return;
    156 
    157     const currentID = pathBlogID();
    158     if (!currentID) {
    159       closePost(false);
    160       return;
    161     }
    162 
    163     const item = itemByBlogID(currentID);
    164     if (!item) return;
    165 
    166     const idx = Number(item.dataset.index || 0);
    167     select(idx);
    168     openPost(currentID, false);
    169 
    170     if (!document.getElementById("blog-post-content")?.children.length) {
    171       item.click();
    172     }
    173   };
    174 
    175   window.addEventListener("popstate", blogHandlers.popstate);
    176 
    177   const pathID = pathBlogID();
    178   const selectedFromPath = itemByBlogID(pathID);
    179   const initialItem = selectedFromPath || section.querySelector(".blog-list-item.selected") || listItems[0];
    180   if (!initialItem) return;
    181 
    182   const initialIndex = Number(initialItem.dataset.index || 0);
    183   select(initialIndex);
    184 
    185   if (postOpen && !hasPrefilledPost) {
    186     initialItem.click();
    187   }
    188 }