diff --git a/src/home/rendering/render-github-issues.ts b/src/home/rendering/render-github-issues.ts index c4073a92..fb175ca7 100644 --- a/src/home/rendering/render-github-issues.ts +++ b/src/home/rendering/render-github-issues.ts @@ -6,6 +6,8 @@ import { getLocalStore } from "../getters/get-local-store"; import { AvatarCache } from "../github-types"; import { issuesContainer, preview, previewBodyInner, titleAnchor, titleHeader } from "./render-preview-modal"; +const openNewLinkIcon = ``; + export function renderGitHubIssues(container: HTMLDivElement, issues: GitHubIssueWithNewFlag[]) { if (container.classList.contains("ready")) { container.classList.remove("ready"); @@ -20,129 +22,147 @@ export function renderGitHubIssues(container: HTMLDivElement, issues: GitHubIssu for (const issue of issues) { if (!existingIssueIds.has(issue.id.toString())) { - const issueWrapper = document.createElement("div"); - const issueElement = document.createElement("div"); - issueElement.setAttribute("data-issue-id", issue.id.toString()); - - if (issue.isNew) { - issueWrapper.classList.add("new-issue"); - } - issueElement.classList.add("issue-element-inner"); + const issueWrapper = everyNewIssue({ issue, avatarCache, fetchInProgress, container }); setTimeout(() => issueWrapper.classList.add("active"), delay); - delay += baseDelay; + } + } + container.classList.add("ready"); +} - const urlRegex = /(https?:\/\/[^\s]+)/g; - const mirrorUrls = issue.body.match(urlRegex); - - const urlPattern = /https:\/\/github\.com\/([^/]+)\/([^/]+)\//; - const match = mirrorUrls?.shift()?.match(urlPattern); - const organizationName = match?.[1]; - const repositoryName = match?.[2]; - - type LabelKey = "Pricing: " | "Time: " | "Priority: "; - - const labelOrder: Record = { "Pricing: ": 1, "Time: ": 2, "Priority: ": 3 }; - - issue.labels.sort((a, b) => { - const matchA = a.name.match(/^(Pricing|Time|Priority): /)?.[0] as LabelKey | undefined; - const matchB = b.name.match(/^(Pricing|Time|Priority): /)?.[0] as LabelKey | undefined; - const orderA = matchA ? labelOrder[matchA] : 0; - const orderB = matchB ? labelOrder[matchB] : 0; - return orderA - orderB; - }); - - // Filter labels that begin with specific prefixes - const filteredLabels = issue.labels.filter((label) => { - return label.name.startsWith("Time: ") || label.name.startsWith("Pricing: ") || label.name.startsWith("Priority: "); - }); - - // Map the filtered labels to HTML elements - const labels = filteredLabels.map((label) => { - // Remove the prefix from the label name - const name = label.name.replace(/(Time|Pricing|Priority): /, ""); - if (label.name.startsWith("Pricing: ")) { - return ``; - } else { - return ``; - } - }); +function everyNewIssue({ + issue, + avatarCache, + fetchInProgress, + container, +}: { + issue: GitHubIssueWithNewFlag; + avatarCache: AvatarCache; + fetchInProgress: Set; + container: HTMLDivElement; +}) { + const issueWrapper = document.createElement("div"); + const issueElement = document.createElement("div"); + issueElement.setAttribute("data-issue-id", issue.id.toString()); + issueElement.classList.add("issue-element-inner"); + + if (issue.isNew) { + issueWrapper.classList.add("new-issue"); + } - const openNewLinkIcon = ``; + const urlPattern = /https:\/\/github\.com\/([^/]+)\/([^/]+)\//; + const match = issue.body.match(urlPattern); + const organizationName = match?.[1]; + const repositoryName = match?.[2]; + const labels = parseAndGenerateLabels(issue); + setUpIssueElement(issueElement, issue, organizationName, repositoryName, labels, match); + issueWrapper.appendChild(issueElement); + + // Set the issueWrapper background-image to the organization's avatar + if (organizationName) { + organizationAvatar(avatarCache, organizationName, issueElement, fetchInProgress); + } + container.appendChild(issueWrapper); + return issueWrapper; +} - issueElement.innerHTML = ` +function setUpIssueElement( + issueElement: HTMLDivElement, + issue: GitHubIssueWithNewFlag, + organizationName: string | undefined, + repositoryName: string | undefined, + labels: string[], + match: RegExpMatchArray | null +) { + issueElement.innerHTML = ` ${openNewLinkIcon}

${ issue.title }

${organizationName}

${repositoryName}

${labels.join( "" )}
`; - issueElement.addEventListener("click", function () { - const previewId = Number(this.getAttribute("data-issue-id")); - console.trace({ mapping, previewId }); - const full = mapping.get(previewId); - if (!full) { - window.open(match?.input, "_blank"); - } else { - previewIssue(issue); - } - }); - - issueWrapper.appendChild(issueElement); + issueElement.addEventListener("click", function () { + const previewId = Number(this.getAttribute("data-issue-id")); + console.trace({ mapping, previewId }); + const full = mapping.get(previewId); + if (!full) { + window.open(match?.input, "_blank"); + } else { + previewIssue(issue); + } + }); +} - // Set the issueWrapper background-image to the organization's avatar - if (organizationName) { - const cachedAvatar = avatarCache[organizationName]; - const image = issueElement.querySelector("img") as HTMLImageElement; - if (cachedAvatar) { - image.src = cachedAvatar; - } else if (!fetchInProgress.has(organizationName)) { - // Mark this organization's avatar as being fetched - fetchInProgress.add(organizationName); +function parseAndGenerateLabels(issue: GitHubIssueWithNewFlag) { + type LabelKey = "Pricing: " | "Time: " | "Priority: "; + + const labelOrder: Record = { "Pricing: ": 1, "Time: ": 2, "Priority: ": 3 }; + + issue.labels.sort((a, b) => { + const matchA = a.name.match(/^(Pricing|Time|Priority): /)?.[0] as LabelKey | undefined; + const matchB = b.name.match(/^(Pricing|Time|Priority): /)?.[0] as LabelKey | undefined; + const orderA = matchA ? labelOrder[matchA] : 0; + const orderB = matchB ? labelOrder[matchB] : 0; + return orderA - orderB; + }); + + // Filter labels that begin with specific prefixes + const filteredLabels = issue.labels.filter((label) => { + return label.name.startsWith("Time: ") || label.name.startsWith("Pricing: ") || label.name.startsWith("Priority: "); + }); + + // Map the filtered labels to HTML elements + const labels = filteredLabels.map((label) => { + // Remove the prefix from the label name + const name = label.name.replace(/(Time|Pricing|Priority): /, ""); + if (label.name.startsWith("Pricing: ")) { + return ``; + } else { + return ``; + } + }); + return labels; +} - // Update the avatarCache synchronously here - avatarCache[organizationName] = null; // Placeholder value to indicate fetch in progress +function organizationAvatar(avatarCache: AvatarCache, organizationName: string, issueElement: HTMLDivElement, fetchInProgress: Set) { + const cachedAvatar = avatarCache[organizationName]; + const image = issueElement.querySelector("img") as HTMLImageElement; + if (cachedAvatar) { + image.src = cachedAvatar; + } else if (!fetchInProgress.has(organizationName)) { + // Mark this organization's avatar as being fetched + fetchInProgress.add(organizationName); + + // Update the avatarCache synchronously here + avatarCache[organizationName] = null; // Placeholder value to indicate fetch in progress + localStorage.setItem("avatarCache", JSON.stringify(avatarCache)); + + fetch(`https://api.github.com/orgs/${organizationName}`) + .then((response) => response.json()) + .then((data) => { + if (data && data.avatar_url) { + avatarCache[organizationName] = data.avatar_url; localStorage.setItem("avatarCache", JSON.stringify(avatarCache)); - - fetch(`https://api.github.com/orgs/${organizationName}`) - .then((response) => response.json()) - .then((data) => { - if (data && data.avatar_url) { - avatarCache[organizationName] = data.avatar_url; - localStorage.setItem("avatarCache", JSON.stringify(avatarCache)); - if (data.avatar_url) { - image.src = data.avatar_url; - } - } - }) - .catch((error) => { - console.error("Error fetching avatar:", error); - }) - .finally(() => { - // Fetch is complete, remove from the in-progress set - fetchInProgress.delete(organizationName); - }); + if (data.avatar_url) { + updateImageSrc(image, data.avatar_url); + } } - } - container.appendChild(issueWrapper); - } + }) + .catch((error) => { + console.error("Error fetching avatar:", error); + }) + .finally(() => { + // Fetch is complete, remove from the in-progress set + fetchInProgress.delete(organizationName); + }); } - container.classList.add("ready"); } // Function to update and show the preview function previewIssue(issuePreview: GitHubIssueWithNewFlag) { - // const issuesFull = mapping; const issueFull = mapping.get(issuePreview.id); - // const issuePreviewUrl = issuePreview.body.match(/https:\/\/github\.com\/[^/]+\/[^/]+\/issues\/\d+/)?.[0]; - // if (!issuePreviewUrl) { - // throw new Error("Issue preview URL not found"); - // } - - // const issueFull = findIssueByUrl(issuesFull, issuePreviewUrl); if (!issueFull) { - // console.trace({ issuePreviewUrl, issuesFull }); throw new Error("Issue not found"); } @@ -156,8 +176,6 @@ function previewIssue(issuePreview: GitHubIssueWithNewFlag) { issuesContainer?.classList.add("preview-active"); } -// Function to find an issue by URL -// function findIssueByUrl(issues: GitHubIssue[], url: string) { -// console.trace({ issues, url }); -// return issues.find((issue) => issue.html_url === url); -// } +function updateImageSrc(imageElement: HTMLImageElement, src: string) { + imageElement.src = src; +}