prime8dev

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

projects.js (3924B)


      1 import { isEditableTarget } from "./dom.js";
      2 import { setAppTitleDetail, updateAppTitle } from "./header.js";
      3 
      4 const projectsHandlers = {
      5   keydown: null,
      6 };
      7 
      8 export function initProjects() {
      9   const section = document.getElementById("projects");
     10   if (!section) return;
     11 
     12   const items = Array.from(section.querySelectorAll(".list-item"));
     13   if (items.length === 0) return;
     14 
     15   const detailView = section.querySelector(".detail-view");
     16 
     17   let selectedIndex = 0;
     18 
     19   function readmeScroller() {
     20     if (!detailView) return null;
     21 
     22     const readme = detailView.querySelector(".readme-content");
     23     if (readme && readme.scrollHeight > readme.clientHeight) {
     24       return readme;
     25     }
     26 
     27     if (detailView.scrollHeight > detailView.clientHeight) {
     28       return detailView;
     29     }
     30 
     31     return null;
     32   }
     33 
     34   function setSelected(selectedItem) {
     35     items.forEach((item) => item.classList.toggle("selected", item === selectedItem));
     36     selectedIndex = items.indexOf(selectedItem);
     37     if (selectedIndex < 0) selectedIndex = 0;
     38 
     39     selectedItem.scrollIntoView({ behavior: "smooth", block: "nearest" });
     40 
     41     const detail = selectedItem.querySelector(".list-title")?.textContent?.trim() || "";
     42     setAppTitleDetail("projects", detail);
     43     updateAppTitle("projects");
     44   }
     45 
     46   function updateProjectURL(projectID) {
     47     if (!projectID) return;
     48     const path = `/projects/${encodeURIComponent(projectID)}`;
     49     if (window.location.pathname !== path) {
     50       history.replaceState({ tab: "projects" }, "", path);
     51       updateAppTitle("projects");
     52     }
     53   }
     54 
     55   function loadDetailForItem(item) {
     56     const url = item.getAttribute("hx-get");
     57     if (!url) return;
     58 
     59     updateProjectURL(item.dataset.id);
     60 
     61     if (window.htmx && typeof window.htmx.ajax === "function") {
     62       window.htmx.ajax("GET", url, "#project-detail");
     63       return;
     64     }
     65 
     66     item.click();
     67   }
     68 
     69   function selectByIndex(index, loadDetail) {
     70     if (index < 0 || index >= items.length) return;
     71     setSelected(items[index]);
     72     if (loadDetail) loadDetailForItem(items[index]);
     73   }
     74 
     75   function openExternalForIndex(index) {
     76     if (index < 0 || index >= items.length) return;
     77     const url = items[index]?.dataset.url || "";
     78     if (!url) return;
     79     window.open(url, "_blank", "noopener,noreferrer");
     80   }
     81 
     82   items.forEach((item, index) => {
     83     if (item.dataset.boundClick === "1") return;
     84     item.dataset.boundClick = "1";
     85     item.addEventListener("click", () => {
     86       selectedIndex = index;
     87       setSelected(item);
     88       updateProjectURL(item.dataset.id);
     89     });
     90   });
     91 
     92   if (projectsHandlers.keydown) {
     93     document.removeEventListener("keydown", projectsHandlers.keydown);
     94   }
     95 
     96   projectsHandlers.keydown = (e) => {
     97     if (!section.isConnected) return;
     98     if (isEditableTarget(e.target)) return;
     99     if (window.isLightboxOpen?.()) return;
    100 
    101     if ((e.key === "ArrowUp" || e.key === "ArrowDown") && e.shiftKey) {
    102       const scroller = readmeScroller();
    103       if (scroller) {
    104         e.preventDefault();
    105         scroller.scrollBy({ top: e.key === "ArrowUp" ? -80 : 80, behavior: "smooth" });
    106         return;
    107       }
    108     }
    109 
    110     switch (e.key) {
    111       case "ArrowUp":
    112         e.preventDefault();
    113         selectByIndex(selectedIndex - 1, true);
    114         break;
    115       case "ArrowDown":
    116         e.preventDefault();
    117         selectByIndex(selectedIndex + 1, true);
    118         break;
    119       case "Enter":
    120         e.preventDefault();
    121         openExternalForIndex(selectedIndex);
    122         break;
    123       default:
    124         break;
    125     }
    126   };
    127 
    128   document.addEventListener("keydown", projectsHandlers.keydown);
    129 
    130   const initialItem = section.querySelector(".list-item.selected") || items[0];
    131   setSelected(initialItem);
    132 
    133   const detailContainer = document.getElementById("project-detail");
    134   const needsInitialFetch = detailContainer?.querySelector("[data-project-placeholder='true']");
    135   if (needsInitialFetch) {
    136     setTimeout(() => loadDetailForItem(initialItem), 0);
    137   }
    138 }