From 36aa9bc7a62f5eca99468a176d4cac522363cb66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=A2=E3=83=AC=E3=82=AF=E3=82=B5=E3=83=B3=E3=83=80?= =?UTF-8?q?=E3=83=BC=2Eeth?= Date: Fri, 4 Oct 2024 06:59:18 +0900 Subject: [PATCH 1/9] chore: use new cache system but its unstable --- bun.lockb | Bin 0 -> 287150 bytes .../fetch-and-display-previews.ts | 78 ++++---- src/home/fetch-github/fetch-issues-full.ts | 89 +++++---- src/home/fetch-github/fetch-issues-preview.ts | 174 +++++++++--------- src/home/fetch-github/handle-rate-limit.ts | 8 +- .../fetch-github/preview-to-full-mapping.ts | 42 +++-- src/home/github-types.ts | 12 +- src/home/home.ts | 47 ++--- src/home/register-service-worker.ts | 12 ++ src/home/render-service-message.ts | 11 ++ src/home/rendering/display-popup-modal.ts | 10 +- src/home/rendering/render-github-issues.ts | 59 +++--- src/home/rendering/render-preview-modal.ts | 42 ++--- .../rendering/setup-keyboard-navigation.ts | 26 ++- src/home/sorting/sort-issues-by-price.ts | 10 +- src/home/sorting/sort-issues-by-priority.ts | 16 +- src/home/sorting/sort-issues-by-time.ts | 10 +- .../sorting/sort-issues-by-updated-time.ts | 8 +- src/home/sorting/sort-issues-by.ts | 4 +- src/home/sorting/sort-issues-controller.ts | 4 +- src/home/sorting/sorting-manager.ts | 10 +- src/home/task-manager.ts | 51 ++--- src/progressive-web-app.ts | 6 +- tsconfig.json | 2 +- 24 files changed, 358 insertions(+), 373 deletions(-) create mode 100755 bun.lockb create mode 100644 src/home/register-service-worker.ts create mode 100644 src/home/render-service-message.ts diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..82574019d1deb103947fd8194f0d96e5898f033d GIT binary patch literal 287150 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%0DEJUrY?X9aP*LN> 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[^/]+)\/(?[^/]+)\/issues\/(?\d+)/; - const avatarPromises = cachedTasks.map(async (task) => { - const match = task.preview.body?.match(urlPattern); + const avatarPromises = cachedTasks.map(async (task: GitHubIssue) => { + const match = task.body?.match(urlPattern); const orgName = match?.groups?.org; if (orgName) { return fetchAvatar(orgName); @@ -91,39 +89,39 @@ export async function fetchAvatars() { return cachedTasks; } -export function taskWithFullTest(task: TaskNoFull | TaskWithFull): task is TaskWithFull { - return (task as TaskWithFull).full !== null && (task as TaskWithFull).full !== undefined; -} - -export function verifyGitHubIssueState(cachedTasks: TaskMaybeFull[], fetchedPreviews: TaskNoFull[]): (TaskNoFull | TaskWithFull)[] { - return fetchedPreviews.map((fetched) => { - const cachedTask = cachedTasks.find((c) => c.full?.id === fetched.preview.id); - if (cachedTask) { - if (taskWithFullTest(cachedTask)) { - const cachedFullIssue = cachedTask.full; - const task = { ...fetched, full: cachedFullIssue }; - return task; - } else { - // no full issue in task - } - } else { - // no cached task - } - return { - preview: fetched.preview, - } as TaskNoFull; - }); -} +// export function taskWithFullTest(task: TaskNoFull | TaskWithFull): task is TaskWithFull { +// return (task as TaskWithFull).full !== null && (task as TaskWithFull).full !== undefined; +// } + +// export function verifyGitHubIssueState(cachedTasks: GitHubIssue[], fetchedPreviews: TaskNoFull[]): (TaskNoFull | TaskWithFull)[] { +// return fetchedPreviews.map((fetched) => { +// const cachedTask = cachedTasks.find((c) => c.full?.id === fetched.preview.id); +// if (cachedTask) { +// if (taskWithFullTest(cachedTask)) { +// const cachedFullIssue = cachedTask.full; +// const task = { ...fetched, full: cachedFullIssue }; +// return task; +// } else { +// // no full issue in task +// } +// } else { +// // no cached task +// } +// return { +// preview: fetched.preview, +// } as TaskNoFull; +// }); +// } export function displayGitHubIssues(sorting?: Sorting, options = { ordering: "normal" }) { // Load avatars from cache const urlPattern = /https:\/\/github\.com\/(?[^/]+)\/(?[^/]+)\/issues\/(?\d+)/; const cachedTasks = taskManager.getTasks(); - cachedTasks.forEach(async ({ preview }) => { - if (!preview.body) { - throw new Error(`Preview body is undefined for task with id: ${preview.id}`); + cachedTasks.forEach(async (issue) => { + if (!issue.body) { + throw new Error(`Preview body is undefined for task with id: ${issue.id}`); } - const match = preview.body.match(urlPattern); + const match = issue.body.match(urlPattern); const orgName = match?.groups?.org; if (orgName) { const avatarUrl = await getImageFromCache({ dbName: "GitHubAvatars", storeName: "ImageStore", orgName: `avatarUrl-${orgName}` }); @@ -140,10 +138,10 @@ export function displayGitHubIssues(sorting?: Sorting, options = { ordering: "no } function getProposalsOnlyFilter(getProposals: boolean) { - return (task: TaskMaybeFull) => { - if (!task.full?.labels) return false; + return (issue: GitHubIssue) => { + if (!issue?.labels) return false; - const hasPriceLabel = task.full.labels.some((label) => { + const hasPriceLabel = issue.labels.some((label) => { if (typeof label === "string") return false; return label.name?.startsWith("Price: ") || label.name?.startsWith("Pricing: "); }); diff --git a/src/home/fetch-github/fetch-issues-full.ts b/src/home/fetch-github/fetch-issues-full.ts index b10df1b4..d822752f 100644 --- a/src/home/fetch-github/fetch-issues-full.ts +++ b/src/home/fetch-github/fetch-issues-full.ts @@ -1,49 +1,48 @@ -import { Octokit } from "@octokit/rest"; -import { getGitHubAccessToken } from "../getters/get-github-access-token"; import { GitHubIssue } from "../github-types"; -import { taskWithFullTest } from "./fetch-and-display-previews"; -import { fetchAvatar } from "./fetch-avatar"; -import { TaskMaybeFull, TaskWithFull } from "./preview-to-full-mapping"; - export const organizationImageCache = new Map(); -export async function fetchIssuesFull(taskPreviews: TaskMaybeFull[]): Promise { - const octokit = new Octokit({ auth: await getGitHubAccessToken() }); - const urlPattern = /https:\/\/github\.com\/(?[^/]+)\/(?[^/]+)\/issues\/(?\d+)/; - - const fullTaskPromises = taskPreviews.map(async (task) => { - const match = task.preview.body.match(urlPattern); - - if (!match || !match.groups) { - console.error("Invalid issue body URL format"); - return Promise.resolve(null); - } - - const { org, repo, issue_number } = match.groups; - - const { data: response } = await octokit.request("GET /repos/{org}/{repo}/issues/{issue_number}", { issue_number, repo, org }); - - task.full = response as GitHubIssue; - - const urlMatch = task.full.html_url.match(urlPattern); - const orgName = urlMatch?.groups?.org; - if (orgName) { - await fetchAvatar(orgName); - } - const isTaskWithFull = taskWithFullTest(task); - - if (isTaskWithFull) { - return task; - } else { - throw new Error("Task is not a TaskWithFull"); - } - }); - - const settled = await Promise.allSettled(fullTaskPromises); - const fullTasks = settled - .filter((result): result is PromiseFulfilledResult => result.status === "fulfilled") - .map((result) => result.value) - .filter((issue): issue is TaskWithFull => issue !== null); - - return fullTasks; +// export async function fetchIssuesFull(taskPreviews: GitHubIssue[]): Promise { +// const octokit = new Octokit({ auth: await getGitHubAccessToken() }); +// const urlPattern = /https:\/\/github\.com\/(?[^/]+)\/(?[^/]+)\/issues\/(?\d+)/; + +// const fullTaskPromises = taskPreviews.map(async (task) => { +// const match = task.preview.body.match(urlPattern); + +// if (!match || !match.groups) { +// console.error("Invalid issue body URL format"); +// return Promise.resolve(null); +// } + +// const { org, repo, issue_number } = match.groups; + +// const { data: response } = await octokit.request("GET /repos/{org}/{repo}/issues/{issue_number}", { issue_number, repo, org }); + +// task.full = response as GitHubIssue; + +// const urlMatch = task.full.html_url.match(urlPattern); +// const orgName = urlMatch?.groups?.org; +// if (orgName) { +// await fetchAvatar(orgName); +// } +// const isTaskWithFull = taskWithFullTest(task); + +// if (isTaskWithFull) { +// return task; +// } else { +// throw new Error("Task is not a TaskWithFull"); +// } +// }); + +// const settled = await Promise.allSettled(fullTaskPromises); +// const fullTasks = settled +// .filter((result): result is PromiseFulfilledResult => result.status === "fulfilled") +// .map((result) => result.value) +// .filter((issue): issue is TaskWithFull => issue !== null); + +// return fullTasks; +// } +export async function fetchIssuesFull(): Promise { + const response = await fetch("https://raw.githubusercontent.com/ubiquity/devpool-directory/refs/heads/development/devpool-issues.json"); + const jsonData = await response.json(); + return jsonData; } diff --git a/src/home/fetch-github/fetch-issues-preview.ts b/src/home/fetch-github/fetch-issues-preview.ts index b2c6cccc..1e964537 100644 --- a/src/home/fetch-github/fetch-issues-preview.ts +++ b/src/home/fetch-github/fetch-issues-preview.ts @@ -1,92 +1,86 @@ -import { RequestError } from "@octokit/request-error"; -import { Octokit } from "@octokit/rest"; -import { getGitHubAccessToken, getGitHubUserName } from "../getters/get-github-access-token"; -import { GitHubIssue } from "../github-types"; import { displayPopupMessage } from "../rendering/display-popup-modal"; -import { handleRateLimit } from "./handle-rate-limit"; -import { TaskNoFull } from "./preview-to-full-mapping"; - -async function checkPrivateRepoAccess(): Promise { - const octokit = new Octokit({ auth: await getGitHubAccessToken() }); - const username = getGitHubUserName(); - - if (username) { - try { - const response = await octokit.repos.checkCollaborator({ - owner: "ubiquity", - repo: "devpool-directory-private", - username, - }); - - if (response.status === 204) { - // If the response is successful, it means the user has access to the private repository - return true; - } - return false; - } catch (error) { - if (!!error && typeof error === "object" && "status" in error && (error.status === 404 || error.status === 401)) { - // If the status is 404, it means the user is not a collaborator, hence no access - return false; - } else { - // Handle other errors if needed - console.error("Error checking repository access:", error); - throw error; - } - } - } - - return false; -} -export async function fetchIssuePreviews(): Promise { - const octokit = new Octokit({ auth: await getGitHubAccessToken() }); - let freshIssues: GitHubIssue[] = []; - let hasPrivateRepoAccess = false; // Flag to track access to the private repository - - try { - // Check if the user has access to the private repository - hasPrivateRepoAccess = await checkPrivateRepoAccess(); - - // Fetch issues from public repository - const publicResponse = await octokit.paginate(octokit.issues.listForRepo, { - owner: "ubiquity", - repo: "devpool-directory", - state: "open", - }); - - const publicIssues = publicResponse.filter((issue: GitHubIssue) => !issue.pull_request); - - // Fetch issues from the private repository only if the user has access - if (hasPrivateRepoAccess) { - await fetchPrivateIssues(publicIssues); - } else { - // If user doesn't have access, only load issues from the public repository - freshIssues = publicIssues; - } - } catch (error) { - if (!!error && typeof error === "object" && "status" in error && error.status === 403) { - await handleRateLimit(octokit, error as RequestError); - } else { - throw error; - } - } - - const tasks = freshIssues.map((preview: GitHubIssue) => ({ - preview: preview, - full: null, - isNew: true, - isModified: true, - })) as TaskNoFull[]; - - return tasks; - - async function fetchPrivateIssues(publicIssues: GitHubIssue[]) { - const privateResponse = await octokit.paginate(octokit.issues.listForRepo, { - owner: "ubiquity", - repo: "devpool-directory-private", - state: "open", - }); - const privateIssues = privateResponse.filter((issue: GitHubIssue) => !issue.pull_request); +// async function checkPrivateRepoAccess(): Promise { +// const octokit = new Octokit({ auth: await getGitHubAccessToken() }); +// const username = getGitHubUserName(); + +// if (username) { +// try { +// const response = await octokit.repos.checkCollaborator({ +// owner: "ubiquity", +// repo: "devpool-directory-private", +// username, +// }); + +// if (response.status === 204) { +// // If the response is successful, it means the user has access to the private repository +// return true; +// } +// return false; +// } catch (error) { +// if (!!error && typeof error === "object" && "status" in error && (error.status === 404 || error.status === 401)) { +// // If the status is 404, it means the user is not a collaborator, hence no access +// return false; +// } else { +// // Handle other errors if needed +// console.error("Error checking repository access:", error); +// throw error; +// } +// } +// } + +// return false; +// } + +// export async function fetchIssues(): Promise { + // const octokit = new Octokit({ auth: await getGitHubAccessToken() }); + // let freshIssues: GitHubIssue[] = []; + // let hasPrivateRepoAccess = false; // Flag to track access to the private repository + + // try { + // // Check if the user has access to the private repository + // hasPrivateRepoAccess = await checkPrivateRepoAccess(); + + // // // Fetch issues from public repository + // // const publicResponse = await octokit.paginate(octokit.issues.listForRepo, { + // // owner: "ubiquity", + // // repo: "devpool-directory", + // // state: "open", + // // }); + + // // const publicIssues = publicResponse.filter((issue: GitHubIssue) => !issue.pull_request); + + // // Fetch issues from the private repository only if the user has access + // if (hasPrivateRepoAccess) { + // await fetchPrivateIssues(); + // } else { + // // If user doesn't have access, only load issues from the public repository + // freshIssues = publicIssues; + // } + // } catch (error) { + // if (!!error && typeof error === "object" && "status" in error && error.status === 403) { + // await handleRateLimit(octokit, error as RequestError); + // } else { + // throw error; + // } + // } + + // const tasks = freshIssues.map((preview: GitHubIssue) => ({ + // preview: preview, + // full: null, + // isNew: true, + // isModified: true, + // })); + + // return tasks; + + // async function fetchPrivateIssues() { + // const privateResponse = await octokit.paginate(octokit.issues.listForRepo, { + // owner: "ubiquity", + // repo: "devpool-directory-private", + // state: "open", + // }); + // const privateIssues = privateResponse.filter((issue: GitHubIssue) => !issue.pull_request); // Mark private issues // TODO: indicate private issues in the UI @@ -96,9 +90,9 @@ export async function fetchIssuePreviews(): Promise { // }); // Combine public and private issues - freshIssues = [...privateIssues, ...publicIssues]; - } -} + // return privateIssues + // } +// } export function rateLimitModal(message: string) { displayPopupMessage({ modalHeader: `GitHub API rate limit exceeded.`, modalBody: message, isError: false }); diff --git a/src/home/fetch-github/handle-rate-limit.ts b/src/home/fetch-github/handle-rate-limit.ts index bdb7c08f..9f4367e0 100644 --- a/src/home/fetch-github/handle-rate-limit.ts +++ b/src/home/fetch-github/handle-rate-limit.ts @@ -1,11 +1,11 @@ import { RequestError } from "@octokit/request-error"; import { Octokit } from "@octokit/rest"; import { getGitHubUser } from "../getters/get-github-user"; +import { toolbar } from "../ready-toolbar"; import { renderErrorInModal } from "../rendering/display-popup-modal"; -import { rateLimitModal } from "./fetch-issues-preview"; import { gitHubLoginButton } from "../rendering/render-github-login-button"; -import { preview } from "../rendering/render-preview-modal"; -import { toolbar } from "../ready-toolbar"; +import { modal } from "../rendering/render-preview-modal"; +import { rateLimitModal } from "./fetch-issues-preview"; type RateLimit = { reset: number | null; @@ -18,7 +18,7 @@ export async function handleRateLimit(octokit?: Octokit, error?: RequestError) { user: false, }; - preview.classList.add("active"); + modal.classList.add("active"); document.body.classList.add("preview-active"); if (toolbar) { diff --git a/src/home/fetch-github/preview-to-full-mapping.ts b/src/home/fetch-github/preview-to-full-mapping.ts index f36eac24..fa60debf 100644 --- a/src/home/fetch-github/preview-to-full-mapping.ts +++ b/src/home/fetch-github/preview-to-full-mapping.ts @@ -5,23 +5,29 @@ export type TaskNoState = { full: null | GitHubIssue; }; -export type TaskNoFull = { - preview: GitHubIssue; - full: null; - isNew: boolean; - isModified: boolean; -}; +// export type TaskNoFull = { +// preview: GitHubIssue; +// full: null; +// isNew: boolean; +// isModified: boolean; +// }; -export type TaskMaybeFull = { - preview: GitHubIssue; - full: null | GitHubIssue; - isNew: boolean; - isModified: boolean; -}; +// export type TaskMaybeFull = { +// preview: GitHubIssue; +// full: null | GitHubIssue; +// isNew: boolean; +// isModified: boolean; +// }; -export type TaskWithFull = { - preview: GitHubIssue; - full: GitHubIssue; - isNew: boolean; - isModified: boolean; -}; +// export type TaskWithFull = { +// preview: GitHubIssue; +// full: GitHubIssue; +// isNew: boolean; +// isModified: boolean; +// }; + +// export type TaskFull = { +// issue: GitHubIssue; +// isNew: boolean; +// isModified: boolean; +// } diff --git a/src/home/github-types.ts b/src/home/github-types.ts index 89d34e93..6dea2ce2 100644 --- a/src/home/github-types.ts +++ b/src/home/github-types.ts @@ -1,5 +1,4 @@ import { RestEndpointMethodTypes } from "@octokit/plugin-rest-endpoint-methods"; -import { TaskNoState } from "./fetch-github/preview-to-full-mapping"; export interface AvatarCache { [organization: string]: string | null; @@ -9,10 +8,19 @@ export const GITHUB_TASKS_STORAGE_KEY = "gitHubTasks"; export type TaskStorageItems = { timestamp: number; - tasks: TaskNoState[]; + tasks: GitHubIssue[]; loggedIn: boolean; }; export type GitHubUserResponse = RestEndpointMethodTypes["users"]["getByUsername"]["response"]; export type GitHubUser = GitHubUserResponse["data"]; export type GitHubIssue = RestEndpointMethodTypes["issues"]["get"]["response"]["data"]; +export type GitHubLabel = { + id?: number; + node_id?: string; + url?: string; + name: string; + description?: string | null; + color?: string | null; + default?: boolean; +} | string; \ No newline at end of file diff --git a/src/home/home.ts b/src/home/home.ts index ae120b95..e62b924d 100644 --- a/src/home/home.ts +++ b/src/home/home.ts @@ -1,23 +1,25 @@ import { grid } from "../the-grid"; import { authentication } from "./authentication"; import { initiateDevRelTracking } from "./devrel-tracker"; -import { displayGitHubIssues, fetchAndDisplayPreviewsFromCache } from "./fetch-github/fetch-and-display-previews"; +import { displayGitHubIssues } from "./fetch-github/fetch-and-display-previews"; import { fetchIssuesFull } from "./fetch-github/fetch-issues-full"; import { readyToolbar } from "./ready-toolbar"; -import { renderErrorInModal } from "./rendering/display-popup-modal"; +import { registerServiceWorker } from "./register-service-worker"; +import { renderServiceMessage } from "./render-service-message"; +// import { renderErrorInModal } from "./rendering/display-popup-modal"; import { applyAvatarsToIssues } from "./rendering/render-github-issues"; import { renderGitRevision } from "./rendering/render-github-login-button"; import { generateSortingToolbar } from "./sorting/generate-sorting-buttons"; import { TaskManager } from "./task-manager"; // All unhandled errors are caught and displayed in a modal -window.addEventListener("error", (event: ErrorEvent) => renderErrorInModal(event.error)); +// window.addEventListener("error", (event: ErrorEvent) => renderErrorInModal(event.error)); // All unhandled promise rejections are caught and displayed in a modal -window.addEventListener("unhandledrejection", (event: PromiseRejectionEvent) => { - renderErrorInModal(event.reason as Error); - event.preventDefault(); -}); +// window.addEventListener("unhandledrejection", (event: PromiseRejectionEvent) => { +// renderErrorInModal(event.reason as Error); +// event.preventDefault(); +// }); renderGitRevision(); initiateDevRelTracking(); @@ -36,39 +38,16 @@ export const taskManager = new TaskManager(container); void (async function home() { void authentication(); void readyToolbar(); - const previews = await fetchAndDisplayPreviewsFromCache(); - const fullTasks = await fetchIssuesFull(previews); - taskManager.syncTasks(fullTasks); - await taskManager.writeToStorage(); + const gitHubIssues = await fetchIssuesFull(); + taskManager.syncTasks(gitHubIssues); if ("serviceWorker" in navigator) { - window.addEventListener("load", () => { - navigator.serviceWorker.register("/dist/src/progressive-web-app.js").then( - (registration) => { - console.log("ServiceWorker registration successful with scope: ", registration.scope); - }, - (err) => { - console.log("ServiceWorker registration failed: ", err); - } - ); - }); + registerServiceWorker(); } if (!container.childElementCount) { displayGitHubIssues(); applyAvatarsToIssues(); } - return fullTasks; + return gitHubIssues; })(); - -function renderServiceMessage() { - const urlParams = new URLSearchParams(window.location.search); - const message = urlParams.get("message"); - if (message) { - const serviceMessageContainer = document.querySelector("#bottom-bar > div"); - if (serviceMessageContainer) { - serviceMessageContainer.textContent = message; - serviceMessageContainer.parentElement?.classList.add("ready"); - } - } -} diff --git a/src/home/register-service-worker.ts b/src/home/register-service-worker.ts new file mode 100644 index 00000000..9301a322 --- /dev/null +++ b/src/home/register-service-worker.ts @@ -0,0 +1,12 @@ +export function registerServiceWorker() { + window.addEventListener("load", () => { + navigator.serviceWorker.register("/dist/src/progressive-web-app.js").then( + (registration) => { + console.log("ServiceWorker registration successful with scope: ", registration.scope); + }, + (err) => { + console.log("ServiceWorker registration failed: ", err); + } + ); + }); +} diff --git a/src/home/render-service-message.ts b/src/home/render-service-message.ts new file mode 100644 index 00000000..20029283 --- /dev/null +++ b/src/home/render-service-message.ts @@ -0,0 +1,11 @@ +export function renderServiceMessage() { + const urlParams = new URLSearchParams(window.location.search); + const message = urlParams.get("message"); + if (message) { + const serviceMessageContainer = document.querySelector("#bottom-bar > div"); + if (serviceMessageContainer) { + serviceMessageContainer.textContent = message; + serviceMessageContainer.parentElement?.classList.add("ready"); + } + } +} diff --git a/src/home/rendering/display-popup-modal.ts b/src/home/rendering/display-popup-modal.ts index 71c76e44..02831dbf 100644 --- a/src/home/rendering/display-popup-modal.ts +++ b/src/home/rendering/display-popup-modal.ts @@ -1,13 +1,13 @@ import { toolbar } from "../ready-toolbar"; -import { preview, previewBodyInner, titleAnchor, titleHeader } from "./render-preview-modal"; +import { modal, modalBodyInner, titleAnchor, titleHeader } from "./render-preview-modal"; export function displayPopupMessage({ modalHeader, modalBody, isError, url }: { modalHeader: string; modalBody: string; isError: boolean; url?: string }) { titleHeader.textContent = modalHeader; if (url) { titleAnchor.href = url; } - previewBodyInner.innerHTML = modalBody; + modalBodyInner.innerHTML = modalBody; - preview.classList.add("active"); + modal.classList.add("active"); document.body.classList.add("preview-active"); if (toolbar) { @@ -18,9 +18,9 @@ export function displayPopupMessage({ modalHeader, modalBody, isError, url }: { } if (isError) { - preview.classList.add("error"); + modal.classList.add("error"); } else { - preview.classList.remove("error"); + modal.classList.remove("error"); } console.trace({ modalHeader, diff --git a/src/home/rendering/render-github-issues.ts b/src/home/rendering/render-github-issues.ts index e98c6a38..7a223e12 100644 --- a/src/home/rendering/render-github-issues.ts +++ b/src/home/rendering/render-github-issues.ts @@ -1,26 +1,25 @@ import { marked } from "marked"; import { organizationImageCache } from "../fetch-github/fetch-issues-full"; -import { TaskMaybeFull } from "../fetch-github/preview-to-full-mapping"; import { GitHubIssue } from "../github-types"; import { taskManager } from "../home"; import { renderErrorInModal } from "./display-popup-modal"; -import { preview, previewBodyInner, titleAnchor, titleHeader } from "./render-preview-modal"; +import { modal, modalBodyInner, titleAnchor, titleHeader } from './render-preview-modal'; import { setupKeyboardNavigation } from "./setup-keyboard-navigation"; -export function renderGitHubIssues(tasks: TaskMaybeFull[]) { +export function renderGitHubIssues(tasks: GitHubIssue[]) { const container = taskManager.getContainer(); if (container.classList.contains("ready")) { container.classList.remove("ready"); container.innerHTML = ""; } - const existingIssueIds = new Set(Array.from(container.querySelectorAll(".issue-element-inner")).map((element) => element.getAttribute("data-preview-id"))); + const existingIssueIds = new Set(Array.from(container.querySelectorAll(".issue-element-inner")).map((element) => element.getAttribute("data-issue-id"))); let delay = 0; const baseDelay = 1000 / 15; // Base delay in milliseconds for (const task of tasks) { - if (!existingIssueIds.has(task.preview.id.toString())) { - const issueWrapper = everyNewIssue({ taskPreview: task, container }); + if (!existingIssueIds.has(task.id.toString())) { + const issueWrapper = everyNewIssue({ gitHubIssue: task, container }); if (issueWrapper) { setTimeout(() => issueWrapper.classList.add("active"), delay); delay += baseDelay; @@ -32,22 +31,22 @@ export function renderGitHubIssues(tasks: TaskMaybeFull[]) { setupKeyboardNavigation(container); } -function everyNewIssue({ taskPreview, container }: { taskPreview: TaskMaybeFull; container: HTMLDivElement }) { +function everyNewIssue({ gitHubIssue: gitHubIssue, container }: { gitHubIssue: GitHubIssue; container: HTMLDivElement }) { const issueWrapper = document.createElement("div"); const issueElement = document.createElement("div"); - issueElement.setAttribute("data-preview-id", taskPreview.preview.id.toString()); + issueElement.setAttribute("data-issue-id", gitHubIssue.id.toString()); issueElement.classList.add("issue-element-inner"); const urlPattern = /https:\/\/github\.com\/([^/]+)\/([^/]+)\//; - if (!taskPreview.preview.body) { - console.warn(`No body found for issue ${taskPreview.preview.id}.`); + if (!gitHubIssue.body) { + console.warn(`No body found for issue ${gitHubIssue.id}.`); return; } - const match = taskPreview.preview.body.match(urlPattern); + const match = gitHubIssue.body.match(urlPattern); const organizationName = match?.[1]; if (!organizationName) { - console.warn(`No organization name found for issue ${taskPreview.preview.id}.`); + console.warn(`No organization name found for issue ${gitHubIssue.id}.`); return; } @@ -56,8 +55,8 @@ function everyNewIssue({ taskPreview, container }: { taskPreview: TaskMaybeFull; console.warn("No repository name found"); return; } - const labels = parseAndGenerateLabels(taskPreview); - setUpIssueElement(issueElement, taskPreview, organizationName, repositoryName, labels, match); + const labels = parseAndGenerateLabels(gitHubIssue); + setUpIssueElement(issueElement, gitHubIssue, organizationName, repositoryName, labels, match); issueWrapper.appendChild(issueElement); container.appendChild(issueWrapper); @@ -66,7 +65,7 @@ function everyNewIssue({ taskPreview, container }: { taskPreview: TaskMaybeFull; function setUpIssueElement( issueElement: HTMLDivElement, - task: TaskMaybeFull, + task: GitHubIssue, organizationName: string, repositoryName: string, labels: string[], @@ -76,7 +75,7 @@ function setUpIssueElement( issueElement.innerHTML = `

${ - task.preview.title + task.title }

${organizationName}

${repositoryName}

${labels.join( "" )}${image}
`; @@ -95,7 +94,7 @@ function setUpIssueElement( issueWrapper.classList.add("selected"); - const full = task.full; + const full = task; if (!full) { window.open(match?.input, "_blank"); } else { @@ -107,12 +106,12 @@ function setUpIssueElement( }); } -function parseAndGenerateLabels(task: TaskMaybeFull) { +function parseAndGenerateLabels(task: GitHubIssue) { type LabelKey = "Pricing: " | "Time: " | "Priority: "; const labelOrder: Record = { "Pricing: ": 1, "Time: ": 2, "Priority: ": 3 }; - const { labels, otherLabels } = task.preview.labels.reduce( + const { labels, otherLabels } = task.labels.reduce( (acc, label) => { // check if label is a single string if (typeof label === "string") { @@ -144,7 +143,7 @@ function parseAndGenerateLabels(task: TaskMaybeFull) { ); // Sort labels - labels.sort((a, b) => a.order - b.order); + labels.sort((a: { order: number }, b: { order: number }) => a.order - b.order); // Log the other labels if (otherLabels.length) { @@ -156,18 +155,8 @@ function parseAndGenerateLabels(task: TaskMaybeFull) { } // Function to update and show the preview -function previewIssue(taskPreview: TaskMaybeFull) { - const task = taskManager.getTaskByPreviewId(taskPreview.preview.id); - - if (!task) { - throw new Error("Issue not found"); - } - - if (!task.full) { - throw new Error("No full issue found"); - } - - viewIssueDetails(task.full); +function previewIssue(gitHubIssue: GitHubIssue) { + viewIssueDetails(gitHubIssue); } export function viewIssueDetails(full: GitHubIssue) { @@ -175,11 +164,11 @@ export function viewIssueDetails(full: GitHubIssue) { titleHeader.textContent = full.title; titleAnchor.href = full.html_url; if (!full.body) return; - previewBodyInner.innerHTML = marked(full.body) as string; + modalBodyInner.innerHTML = marked(full.body) as string; // Show the preview - preview.classList.add("active"); - preview.classList.remove("error"); + modal.classList.add("active"); + modal.classList.remove("error"); document.body.classList.add("preview-active"); } diff --git a/src/home/rendering/render-preview-modal.ts b/src/home/rendering/render-preview-modal.ts index 7ffad85e..818821bd 100644 --- a/src/home/rendering/render-preview-modal.ts +++ b/src/home/rendering/render-preview-modal.ts @@ -1,9 +1,9 @@ -export const preview = document.createElement("div"); -preview.classList.add("preview"); -const previewContent = document.createElement("div"); -previewContent.classList.add("preview-content"); -const previewHeader = document.createElement("div"); -previewHeader.classList.add("preview-header"); +export const modal = document.createElement("div"); +modal.classList.add("preview"); +const modalContent = document.createElement("div"); +modalContent.classList.add("preview-content"); +const modalHeader = document.createElement("div"); +modalHeader.classList.add("preview-header"); export const titleAnchor = document.createElement("a"); titleAnchor.setAttribute("target", "_blank"); // titleAnchor.href = "#"; @@ -11,12 +11,12 @@ export const titleHeader = document.createElement("h1"); const closeButton = document.createElement("button"); closeButton.classList.add("close-preview"); closeButton.innerHTML = ``; -const previewBody = document.createElement("div"); -previewBody.classList.add("preview-body"); -export const previewBodyInner = document.createElement("div"); -previewBodyInner.classList.add("preview-body-inner"); +const modalBody = document.createElement("div"); +modalBody.classList.add("preview-body"); +export const modalBodyInner = document.createElement("div"); +modalBodyInner.classList.add("preview-body-inner"); // Assemble the preview box -previewHeader.appendChild(closeButton); +modalHeader.appendChild(closeButton); titleAnchor.appendChild(titleHeader); const openNewLinkIcon = ``; const openNewLink = document.createElement("span"); @@ -30,22 +30,22 @@ error.innerHTML = errorIcon; titleAnchor.appendChild(error); titleAnchor.appendChild(openNewLink); -previewHeader.appendChild(titleAnchor); -previewBody.appendChild(previewBodyInner); -previewContent.appendChild(previewHeader); -previewContent.appendChild(previewBody); -preview.appendChild(previewContent); -document.body.appendChild(preview); +modalHeader.appendChild(titleAnchor); +modalBody.appendChild(modalBodyInner); +modalContent.appendChild(modalHeader); +modalContent.appendChild(modalBody); +modal.appendChild(modalContent); +document.body.appendChild(modal); export const issuesContainer = document.getElementById("issues-container"); -closeButton.addEventListener("click", closePreview); +closeButton.addEventListener("click", closeModal); document.addEventListener("keydown", (event) => { if (event.key === "Escape") { - closePreview(); + closeModal(); } }); -function closePreview() { - preview.classList.remove("active"); +function closeModal() { + modal.classList.remove("active"); document.body.classList.remove("preview-active"); } diff --git a/src/home/rendering/setup-keyboard-navigation.ts b/src/home/rendering/setup-keyboard-navigation.ts index f5e0324b..6e04e82a 100644 --- a/src/home/rendering/setup-keyboard-navigation.ts +++ b/src/home/rendering/setup-keyboard-navigation.ts @@ -58,27 +58,25 @@ function keyDownHandler() { container.classList.add("keyboard-selection"); - const previewId = visibleIssues[newIndex].children[0].getAttribute("data-preview-id"); - - const issueElement = visibleIssues.find((issue) => issue.children[0].getAttribute("data-preview-id") === previewId); - - if (issueElement) { - const issueFull = taskManager.getTaskByPreviewId(Number(previewId)).full; - if (issueFull) { - viewIssueDetails(issueFull); + const issueId = visibleIssues[newIndex].children[0].getAttribute("data-issue-id"); + if (issueId) { + const gitHubIssue = taskManager.getGitHubIssueById(parseInt(issueId, 10)); + if (gitHubIssue) { + viewIssueDetails(gitHubIssue); } } } } else if (event.key === "Enter") { const selectedIssue = container.querySelector("#issues-container > div.selected"); if (selectedIssue) { - const previewId = selectedIssue.children[0].getAttribute("data-preview-id"); + const gitHubIssueId = selectedIssue.children[0].getAttribute("data-issue-id"); + if (!gitHubIssueId) { + return; + } - if (previewId) { - const issueFull = taskManager.getTaskByPreviewId(Number(previewId)).full; - if (issueFull) { - window.open(issueFull.html_url, "_blank"); - } + const gitHubIssue = taskManager.getGitHubIssueById(parseInt(gitHubIssueId, 10)); + if (gitHubIssue) { + window.open(gitHubIssue.html_url, "_blank"); } } } else if (event.key === "Escape") { diff --git a/src/home/sorting/sort-issues-by-price.ts b/src/home/sorting/sort-issues-by-price.ts index ddce9167..8ce932ae 100644 --- a/src/home/sorting/sort-issues-by-price.ts +++ b/src/home/sorting/sort-issues-by-price.ts @@ -1,9 +1,11 @@ -import { TaskMaybeFull } from "../fetch-github/preview-to-full-mapping"; +// @ts-nocheck -export function sortIssuesByPrice(issues: TaskMaybeFull[]) { +import { GitHubIssue } from "../github-types"; + +export function sortIssuesByPrice(issues: GitHubIssue[]) { return issues.sort((a, b) => { - const aPriceLabel = a.preview.labels.find((label) => label.name.startsWith("Pricing: ")); - const bPriceLabel = b.preview.labels.find((label) => label.name.startsWith("Pricing: ")); + const aPriceLabel = a.labels.find((label) => label.name.startsWith("Pricing: ")); + const bPriceLabel = b.labels.find((label) => label.name.startsWith("Pricing: ")); const aPriceMatch = aPriceLabel ? aPriceLabel.name.match(/Pricing: (\d+)/) : null; const bPriceMatch = bPriceLabel ? bPriceLabel.name.match(/Pricing: (\d+)/) : null; diff --git a/src/home/sorting/sort-issues-by-priority.ts b/src/home/sorting/sort-issues-by-priority.ts index 3c7b4d22..2c5b9eda 100644 --- a/src/home/sorting/sort-issues-by-priority.ts +++ b/src/home/sorting/sort-issues-by-priority.ts @@ -1,13 +1,17 @@ -import { TaskMaybeFull } from "../fetch-github/preview-to-full-mapping"; +import { GitHubIssue } from "../github-types"; -export function sortIssuesByPriority(issues: TaskMaybeFull[]) { +export function sortIssuesByPriority(issues: GitHubIssue[]) { return issues.sort((a, b) => { const priorityRegex = /Priority: (\d+)/; - const aPriorityMatch = a.preview.labels.find((label) => priorityRegex.test(label.name)); - const bPriorityMatch = b.preview.labels.find((label) => priorityRegex.test(label.name)); + const aPriorityMatch = a.labels.find((label): label is { name: string } => + typeof label === 'object' && label !== null && 'name' in label && priorityRegex.test(label.name) + )?.name || 'No Priority'; + const bPriorityMatch = b.labels.find((label): label is { name: string } => + typeof label === 'object' && label !== null && 'name' in label && priorityRegex.test(label.name) + )?.name || 'No Priority'; - const priorityA = aPriorityMatch ? aPriorityMatch.name.match(priorityRegex) : null; - const priorityB = bPriorityMatch ? bPriorityMatch.name.match(priorityRegex) : null; + const priorityA = aPriorityMatch.match(priorityRegex); + const priorityB = bPriorityMatch.match(priorityRegex); const aPriority = priorityA && priorityA[1] ? parseInt(priorityA[1], 10) : 0; const bPriority = priorityB && priorityB[1] ? parseInt(priorityB[1], 10) : 0; diff --git a/src/home/sorting/sort-issues-by-time.ts b/src/home/sorting/sort-issues-by-time.ts index ad05b72b..9f4d0abc 100644 --- a/src/home/sorting/sort-issues-by-time.ts +++ b/src/home/sorting/sort-issues-by-time.ts @@ -1,10 +1,12 @@ -import { TaskMaybeFull } from "../fetch-github/preview-to-full-mapping"; +import { GitHubIssue } from "../github-types"; import { calculateTimeLabelValue } from "./calculate-time-label-value"; -export function sortIssuesByTime(tasks: TaskMaybeFull[]) { +export function sortIssuesByTime(tasks: GitHubIssue[]) { return tasks.sort((a, b) => { - const aTimeValue = a.preview.labels.reduce((acc, label) => acc + calculateTimeLabelValue(label.name), 0); - const bTimeValue = b.preview.labels.reduce((acc, label) => acc + calculateTimeLabelValue(label.name), 0); + const aTimeValue = a.labels.reduce((acc, label) => + acc + (typeof label === 'object' && label?.name ? calculateTimeLabelValue(label.name) : 0), 0); + const bTimeValue = b.labels.reduce((acc, label) => + acc + (typeof label === 'object' && label?.name ? calculateTimeLabelValue(label.name) : 0), 0); return bTimeValue - aTimeValue; }); } diff --git a/src/home/sorting/sort-issues-by-updated-time.ts b/src/home/sorting/sort-issues-by-updated-time.ts index 1b68af68..e7f40a79 100644 --- a/src/home/sorting/sort-issues-by-updated-time.ts +++ b/src/home/sorting/sort-issues-by-updated-time.ts @@ -1,9 +1,9 @@ -import { TaskMaybeFull } from "../fetch-github/preview-to-full-mapping"; +import { GitHubIssue } from "../github-types"; -export function sortIssuesByLatestActivity(issues: TaskMaybeFull[], ordering: "normal" | "reverse" = "normal") { +export function sortIssuesByLatestActivity(issues: GitHubIssue[], ordering: "normal" | "reverse" = "normal") { return issues.sort((a, b) => { - const dateA = new Date(a.preview.updated_at); - const dateB = new Date(b.preview.updated_at); + const dateA = new Date(a.updated_at); + const dateB = new Date(b.updated_at); return ordering === "normal" ? dateB.getTime() - dateA.getTime() : dateA.getTime() - dateB.getTime(); }); } diff --git a/src/home/sorting/sort-issues-by.ts b/src/home/sorting/sort-issues-by.ts index 43c5fbd6..55b01362 100644 --- a/src/home/sorting/sort-issues-by.ts +++ b/src/home/sorting/sort-issues-by.ts @@ -1,11 +1,11 @@ -import { TaskMaybeFull } from "../fetch-github/preview-to-full-mapping"; +import { GitHubIssue } from "../github-types"; import { SORTING_OPTIONS } from "./generate-sorting-buttons"; import { sortIssuesByPrice } from "./sort-issues-by-price"; import { sortIssuesByPriority } from "./sort-issues-by-priority"; import { sortIssuesByTime } from "./sort-issues-by-time"; import { sortIssuesByLatestActivity } from "./sort-issues-by-updated-time"; -export function sortIssuesBy(tasks: TaskMaybeFull[], sortBy: (typeof SORTING_OPTIONS)[number]) { +export function sortIssuesBy(tasks: GitHubIssue[], sortBy: (typeof SORTING_OPTIONS)[number]) { switch (sortBy) { case "priority": return sortIssuesByPriority(tasks); diff --git a/src/home/sorting/sort-issues-controller.ts b/src/home/sorting/sort-issues-controller.ts index 77cb2ed9..a8633acb 100644 --- a/src/home/sorting/sort-issues-controller.ts +++ b/src/home/sorting/sort-issues-controller.ts @@ -1,10 +1,10 @@ -import { TaskMaybeFull } from "../fetch-github/preview-to-full-mapping"; +import { GitHubIssue } from "../github-types"; import { Sorting } from "./generate-sorting-buttons"; import { sortIssuesBy } from "./sort-issues-by"; import { sortIssuesByPriority } from "./sort-issues-by-priority"; import { sortIssuesByTime } from "./sort-issues-by-time"; -export function sortIssuesController(tasks: TaskMaybeFull[], sorting?: Sorting, options = { ordering: "normal" }) { +export function sortIssuesController(tasks: GitHubIssue[], sorting?: Sorting, options = { ordering: "normal" }) { let sortedIssues = tasks; if (sorting) { diff --git a/src/home/sorting/sorting-manager.ts b/src/home/sorting/sorting-manager.ts index f6c33c17..97d119bc 100644 --- a/src/home/sorting/sorting-manager.ts +++ b/src/home/sorting/sorting-manager.ts @@ -52,12 +52,12 @@ export class SortingManager { const filterText = textBox.value.toLowerCase(); const issues = Array.from(issuesContainer.children) as HTMLDivElement[]; issues.forEach((issue) => { - const issuePreviewId = issue.children[0].getAttribute("data-preview-id"); - if (!issuePreviewId) throw new Error(`No preview id found for issue ${issue}`); - const fullIssue = taskManager.getTaskByPreviewId(Number(issuePreviewId)).full; - if (!fullIssue) throw new Error(`No full issue found for preview id ${issuePreviewId}`); + const issueId = issue.children[0].getAttribute("data-issue-id"); + if (!issueId) return; + const gitHubIssue = taskManager.getGitHubIssueById(parseInt(issueId)); + if (!gitHubIssue) return; const searchableProperties = ["title", "body", "number", "html_url"] as const; - const searchableStrings = searchableProperties.map((prop) => fullIssue[prop]?.toString().toLowerCase()); + const searchableStrings = searchableProperties.map((prop) => gitHubIssue[prop]?.toString().toLowerCase()); const isVisible = searchableStrings.some((str) => str?.includes(filterText)); issue.style.display = isVisible ? "block" : "none"; }); diff --git a/src/home/task-manager.ts b/src/home/task-manager.ts index 59bde855..9bc5159d 100644 --- a/src/home/task-manager.ts +++ b/src/home/task-manager.ts @@ -1,58 +1,37 @@ -import { TaskMaybeFull } from "./fetch-github/preview-to-full-mapping"; import { getGitHubAccessToken } from "./getters/get-github-access-token"; import { setLocalStore } from "./getters/get-local-store"; -import { GITHUB_TASKS_STORAGE_KEY } from "./github-types"; +import { GITHUB_TASKS_STORAGE_KEY, GitHubIssue } from "./github-types"; export class TaskManager { - private _tasks: TaskMaybeFull[] = []; + private _tasks: GitHubIssue[] = []; private _container: HTMLDivElement; constructor(container: HTMLDivElement) { this._container = container; } - public syncTasks(incoming: TaskMaybeFull[]) { - const incomingIds = new Set(incoming.map((task) => task.preview.id)); - const taskMap = new Map(); - - for (const task of incoming) { - const id = task.full?.id || task.preview.id; - taskMap.set(id, task); - } - - for (const task of this._tasks) { - const id = task.full?.id || task.preview.id; - if (!incomingIds.has(id)) { - continue; - } - if (!taskMap.has(id)) { - taskMap.set(id, task); - } else { - const existingTask = taskMap.get(id); - if (existingTask && !existingTask.full && task.full) { - taskMap.set(id, task); - } - } - } - - this._tasks = Array.from(taskMap.values()); + public syncTasks(incoming: GitHubIssue[]) { + this._tasks = incoming; + void this._writeToStorage(incoming); } public getTasks() { return this._tasks; } - public getTaskByPreviewId(id: number) { - const task = this._tasks.find((task) => task.preview.id === id); - if (!task) throw new Error(`No task found for preview id ${id}`); - return task; - } - public getContainer() { return this._container; } - public async writeToStorage() { + public getGitHubIssueById(id: number): GitHubIssue | undefined { + return this._tasks.find(task => task.id === id); + } + + private async _writeToStorage(tasks: GitHubIssue[]) { const _accessToken = await getGitHubAccessToken(); - setLocalStore(GITHUB_TASKS_STORAGE_KEY, { timestamp: Date.now(), tasks: this._tasks, loggedIn: _accessToken !== null }); + setLocalStore(GITHUB_TASKS_STORAGE_KEY, { + timestamp: Date.now(), + tasks: tasks, + loggedIn: _accessToken !== null, + }); } } diff --git a/src/progressive-web-app.ts b/src/progressive-web-app.ts index 04efdf3a..5c8fac82 100644 --- a/src/progressive-web-app.ts +++ b/src/progressive-web-app.ts @@ -1,3 +1,6 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore + self.addEventListener("install", (event: InstallEvent) => { event.waitUntil( caches.open("v1").then((cache) => { @@ -5,7 +8,8 @@ self.addEventListener("install", (event: InstallEvent) => { }) ); }); - +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore self.addEventListener("fetch", (event: FetchEvent) => { event.respondWith( caches.match(event.request).then((response) => { diff --git a/tsconfig.json b/tsconfig.json index 01415e27..9091000f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,7 +9,7 @@ // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ /* Language and Environment */ - "target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, + "target": "es2022" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, "lib": ["DOM", "ESNext"] /* Specify a set of bundled library declaration files that describe the target runtime environment. */, // "jsx": "preserve", /* Specify what JSX code is generated. */ // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ From be34b435cd09fbed2441ba2788f960020931f8d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=A2=E3=83=AC=E3=82=AF=E3=82=B5=E3=83=B3=E3=83=80?= =?UTF-8?q?=E3=83=BC=2Eeth?= Date: Fri, 4 Oct 2024 07:09:40 +0900 Subject: [PATCH 2/9] chore: more stable --- .../fetch-and-display-previews.ts | 38 ++++++++------ src/home/rendering/render-github-issues.ts | 51 ++++++++++--------- src/home/sorting/sort-issues-by-price.ts | 8 +-- 3 files changed, 52 insertions(+), 45 deletions(-) diff --git a/src/home/fetch-github/fetch-and-display-previews.ts b/src/home/fetch-github/fetch-and-display-previews.ts index ebc3b349..d5b03c27 100644 --- a/src/home/fetch-github/fetch-and-display-previews.ts +++ b/src/home/fetch-github/fetch-and-display-previews.ts @@ -115,24 +115,30 @@ export async function fetchAvatars() { export function displayGitHubIssues(sorting?: Sorting, options = { ordering: "normal" }) { // Load avatars from cache - const urlPattern = /https:\/\/github\.com\/(?[^/]+)\/(?[^/]+)\/issues\/(?\d+)/; - const cachedTasks = taskManager.getTasks(); - cachedTasks.forEach(async (issue) => { - if (!issue.body) { - throw new Error(`Preview body is undefined for task with id: ${issue.id}`); - } - const match = issue.body.match(urlPattern); - const orgName = match?.groups?.org; - if (orgName) { - const avatarUrl = await getImageFromCache({ dbName: "GitHubAvatars", storeName: "ImageStore", orgName: `avatarUrl-${orgName}` }); - if (avatarUrl) { - organizationImageCache.set(orgName, avatarUrl); - } - } + // const urlPattern = /https:\/\/github\.com\/(?[^/]+)\/(?[^/]+)\/issues\/(?\d+)/; + const cached = taskManager.getTasks(); + cached.forEach(async (gitHubIssue) => { + // if (!issue.body) { + // throw new Error(`Preview body is undefined for task with id: ${issue.id}`); + // } + // const match = issue.body.match(urlPattern); + // const orgName = match?.groups?.org; + // if (orgName) { + + const [orgName] = gitHubIssue.repository_url.split("/").slice(-2); + + getImageFromCache({ + dbName: "GitHubAvatars", + storeName: "ImageStore", + orgName: `avatarUrl-${orgName}`, + }) + .then((avatarUrl) => organizationImageCache.set(orgName, avatarUrl)) + .catch(console.error); + // } }); // Render issues - const sortedIssues = sortIssuesController(cachedTasks, sorting, options); + const sortedIssues = sortIssuesController(cached, sorting, options); const sortedAndFiltered = sortedIssues.filter(getProposalsOnlyFilter(isProposalOnlyViewer)); renderGitHubIssues(sortedAndFiltered); } @@ -143,7 +149,7 @@ function getProposalsOnlyFilter(getProposals: boolean) { const hasPriceLabel = issue.labels.some((label) => { if (typeof label === "string") return false; - return label.name?.startsWith("Price: ") || label.name?.startsWith("Pricing: "); + return label.name?.startsWith("Price: ") || label.name?.startsWith("Price: "); }); // If getProposals is true, we want tasks WITHOUT price labels diff --git a/src/home/rendering/render-github-issues.ts b/src/home/rendering/render-github-issues.ts index 7a223e12..79c5d291 100644 --- a/src/home/rendering/render-github-issues.ts +++ b/src/home/rendering/render-github-issues.ts @@ -31,32 +31,33 @@ export function renderGitHubIssues(tasks: GitHubIssue[]) { setupKeyboardNavigation(container); } -function everyNewIssue({ gitHubIssue: gitHubIssue, container }: { gitHubIssue: GitHubIssue; container: HTMLDivElement }) { +function everyNewIssue({ gitHubIssue, container }: { gitHubIssue: GitHubIssue; container: HTMLDivElement }) { const issueWrapper = document.createElement("div"); const issueElement = document.createElement("div"); issueElement.setAttribute("data-issue-id", gitHubIssue.id.toString()); issueElement.classList.add("issue-element-inner"); - const urlPattern = /https:\/\/github\.com\/([^/]+)\/([^/]+)\//; - if (!gitHubIssue.body) { - console.warn(`No body found for issue ${gitHubIssue.id}.`); - return; - } - const match = gitHubIssue.body.match(urlPattern); - const organizationName = match?.[1]; - - if (!organizationName) { - console.warn(`No organization name found for issue ${gitHubIssue.id}.`); - return; - } - - const repositoryName = match?.[2]; - if (!repositoryName) { - console.warn("No repository name found"); - return; - } + // const urlPattern = /https:\/\/github\.com\/([^/]+)\/([^/]+)\//; + // if (!gitHubIssue.body) { + // console.warn(`No body found for issue ${gitHubIssue.id}.`); + // return; + // } + // const match = gitHubIssue.body.match(urlPattern); + // const organizationName = match?.[1]; + + // if (!organizationName) { + // console.warn(`No organization name found for issue ${gitHubIssue.id}.`); + // return; + // } + + // const repositoryName = match?.[2]; + // if (!repositoryName) { + // console.warn("No repository name found"); + // return; + // } const labels = parseAndGenerateLabels(gitHubIssue); - setUpIssueElement(issueElement, gitHubIssue, organizationName, repositoryName, labels, match); + const [organizationName, repositoryName] = gitHubIssue.repository_url.split('/').slice(-2); + setUpIssueElement(issueElement, gitHubIssue, organizationName, repositoryName, labels, gitHubIssue.html_url); issueWrapper.appendChild(issueElement); container.appendChild(issueWrapper); @@ -69,7 +70,7 @@ function setUpIssueElement( organizationName: string, repositoryName: string, labels: string[], - match: RegExpMatchArray | null + url: string ) { const image = ``; @@ -96,7 +97,7 @@ function setUpIssueElement( const full = task; if (!full) { - window.open(match?.input, "_blank"); + window.open(url, "_blank"); } else { previewIssue(task); } @@ -107,9 +108,9 @@ function setUpIssueElement( } function parseAndGenerateLabels(task: GitHubIssue) { - type LabelKey = "Pricing: " | "Time: " | "Priority: "; + type LabelKey = "Price: " | "Time: " | "Priority: "; - const labelOrder: Record = { "Pricing: ": 1, "Time: ": 2, "Priority: ": 3 }; + const labelOrder: Record = { "Price: ": 1, "Time: ": 2, "Priority: ": 3 }; const { labels, otherLabels } = task.labels.reduce( (acc, label) => { @@ -129,7 +130,7 @@ function parseAndGenerateLabels(task: GitHubIssue) { }; } - const match = label.name.match(/^(Pricing|Time|Priority): /); + const match = label.name.match(/^(Price|Time|Priority): /); if (match) { const name = label.name.replace(match[0], ""); const labelStr = ``; diff --git a/src/home/sorting/sort-issues-by-price.ts b/src/home/sorting/sort-issues-by-price.ts index 8ce932ae..8965ea1c 100644 --- a/src/home/sorting/sort-issues-by-price.ts +++ b/src/home/sorting/sort-issues-by-price.ts @@ -4,11 +4,11 @@ import { GitHubIssue } from "../github-types"; export function sortIssuesByPrice(issues: GitHubIssue[]) { return issues.sort((a, b) => { - const aPriceLabel = a.labels.find((label) => label.name.startsWith("Pricing: ")); - const bPriceLabel = b.labels.find((label) => label.name.startsWith("Pricing: ")); + const aPriceLabel = a.labels.find((label) => label.name.startsWith("Price: ")); + const bPriceLabel = b.labels.find((label) => label.name.startsWith("Price: ")); - const aPriceMatch = aPriceLabel ? aPriceLabel.name.match(/Pricing: (\d+)/) : null; - const bPriceMatch = bPriceLabel ? bPriceLabel.name.match(/Pricing: (\d+)/) : null; + const aPriceMatch = aPriceLabel ? aPriceLabel.name.match(/Price: (\d+)/) : null; + const bPriceMatch = bPriceLabel ? bPriceLabel.name.match(/Price: (\d+)/) : null; const aPrice = aPriceMatch && aPriceMatch[1] ? parseInt(aPriceMatch[1], 10) : 0; const bPrice = bPriceMatch && bPriceMatch[1] ? parseInt(bPriceMatch[1], 10) : 0; From b88b5d1b235b69269ba62ee9d163cb96b68ee113 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=A2=E3=83=AC=E3=82=AF=E3=82=B5=E3=83=B3=E3=83=80?= =?UTF-8?q?=E3=83=BC=2Eeth?= Date: Fri, 4 Oct 2024 07:10:48 +0900 Subject: [PATCH 3/9] fix: type error --- src/home/sorting/sort-issues-by-price.ts | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/home/sorting/sort-issues-by-price.ts b/src/home/sorting/sort-issues-by-price.ts index 8965ea1c..67951b71 100644 --- a/src/home/sorting/sort-issues-by-price.ts +++ b/src/home/sorting/sort-issues-by-price.ts @@ -1,18 +1,19 @@ -// @ts-nocheck - import { GitHubIssue } from "../github-types"; export function sortIssuesByPrice(issues: GitHubIssue[]) { return issues.sort((a, b) => { - const aPriceLabel = a.labels.find((label) => label.name.startsWith("Price: ")); - const bPriceLabel = b.labels.find((label) => label.name.startsWith("Price: ")); - - const aPriceMatch = aPriceLabel ? aPriceLabel.name.match(/Price: (\d+)/) : null; - const bPriceMatch = bPriceLabel ? bPriceLabel.name.match(/Price: (\d+)/) : null; - - const aPrice = aPriceMatch && aPriceMatch[1] ? parseInt(aPriceMatch[1], 10) : 0; - const bPrice = bPriceMatch && bPriceMatch[1] ? parseInt(bPriceMatch[1], 10) : 0; + const aPrice = a.labels.map(getPriceFromLabel).find((price) => price !== null) ?? -1; + const bPrice = b.labels.map(getPriceFromLabel).find((price) => price !== null) ?? -1; return bPrice - aPrice; }); } + +function getPriceFromLabel(label: string | { name?: string }) { + if (typeof label === "string" || !label.name) return null; + if (label.name.startsWith("Price: ")) { + const match = label.name.match(/Price: (\d+)/); + return match ? parseInt(match[1], 10) : null; + } + return null; +} From 97382007739496df7fe2d6d33ae50de77d385c4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=A2=E3=83=AC=E3=82=AF=E3=82=B5=E3=83=B3=E3=83=80?= =?UTF-8?q?=E3=83=BC=2Eeth?= Date: Fri, 4 Oct 2024 07:11:01 +0900 Subject: [PATCH 4/9] chore: lint --- src/home/fetch-github/fetch-issues-preview.ts | 118 +++++++++--------- src/home/github-types.ts | 20 +-- src/home/rendering/render-github-issues.ts | 13 +- src/home/sorting/sort-issues-by-priority.ts | 12 +- src/home/sorting/sort-issues-by-time.ts | 6 +- src/home/task-manager.ts | 2 +- 6 files changed, 82 insertions(+), 89 deletions(-) diff --git a/src/home/fetch-github/fetch-issues-preview.ts b/src/home/fetch-github/fetch-issues-preview.ts index 1e964537..78aeca0c 100644 --- a/src/home/fetch-github/fetch-issues-preview.ts +++ b/src/home/fetch-github/fetch-issues-preview.ts @@ -33,65 +33,65 @@ import { displayPopupMessage } from "../rendering/display-popup-modal"; // } // export async function fetchIssues(): Promise { - // const octokit = new Octokit({ auth: await getGitHubAccessToken() }); - // let freshIssues: GitHubIssue[] = []; - // let hasPrivateRepoAccess = false; // Flag to track access to the private repository - - // try { - // // Check if the user has access to the private repository - // hasPrivateRepoAccess = await checkPrivateRepoAccess(); - - // // // Fetch issues from public repository - // // const publicResponse = await octokit.paginate(octokit.issues.listForRepo, { - // // owner: "ubiquity", - // // repo: "devpool-directory", - // // state: "open", - // // }); - - // // const publicIssues = publicResponse.filter((issue: GitHubIssue) => !issue.pull_request); - - // // Fetch issues from the private repository only if the user has access - // if (hasPrivateRepoAccess) { - // await fetchPrivateIssues(); - // } else { - // // If user doesn't have access, only load issues from the public repository - // freshIssues = publicIssues; - // } - // } catch (error) { - // if (!!error && typeof error === "object" && "status" in error && error.status === 403) { - // await handleRateLimit(octokit, error as RequestError); - // } else { - // throw error; - // } - // } - - // const tasks = freshIssues.map((preview: GitHubIssue) => ({ - // preview: preview, - // full: null, - // isNew: true, - // isModified: true, - // })); - - // return tasks; - - // async function fetchPrivateIssues() { - // const privateResponse = await octokit.paginate(octokit.issues.listForRepo, { - // owner: "ubiquity", - // repo: "devpool-directory-private", - // state: "open", - // }); - // const privateIssues = privateResponse.filter((issue: GitHubIssue) => !issue.pull_request); - - // Mark private issues - // TODO: indicate private issues in the UI - - // const privateIssuesWithFlag = privateIssues.map((issue) => { - // return issue; - // }); - - // Combine public and private issues - // return privateIssues - // } +// const octokit = new Octokit({ auth: await getGitHubAccessToken() }); +// let freshIssues: GitHubIssue[] = []; +// let hasPrivateRepoAccess = false; // Flag to track access to the private repository + +// try { +// // Check if the user has access to the private repository +// hasPrivateRepoAccess = await checkPrivateRepoAccess(); + +// // // Fetch issues from public repository +// // const publicResponse = await octokit.paginate(octokit.issues.listForRepo, { +// // owner: "ubiquity", +// // repo: "devpool-directory", +// // state: "open", +// // }); + +// // const publicIssues = publicResponse.filter((issue: GitHubIssue) => !issue.pull_request); + +// // Fetch issues from the private repository only if the user has access +// if (hasPrivateRepoAccess) { +// await fetchPrivateIssues(); +// } else { +// // If user doesn't have access, only load issues from the public repository +// freshIssues = publicIssues; +// } +// } catch (error) { +// if (!!error && typeof error === "object" && "status" in error && error.status === 403) { +// await handleRateLimit(octokit, error as RequestError); +// } else { +// throw error; +// } +// } + +// const tasks = freshIssues.map((preview: GitHubIssue) => ({ +// preview: preview, +// full: null, +// isNew: true, +// isModified: true, +// })); + +// return tasks; + +// async function fetchPrivateIssues() { +// const privateResponse = await octokit.paginate(octokit.issues.listForRepo, { +// owner: "ubiquity", +// repo: "devpool-directory-private", +// state: "open", +// }); +// const privateIssues = privateResponse.filter((issue: GitHubIssue) => !issue.pull_request); + +// Mark private issues +// TODO: indicate private issues in the UI + +// const privateIssuesWithFlag = privateIssues.map((issue) => { +// return issue; +// }); + +// Combine public and private issues +// return privateIssues +// } // } export function rateLimitModal(message: string) { diff --git a/src/home/github-types.ts b/src/home/github-types.ts index 6dea2ce2..33307cba 100644 --- a/src/home/github-types.ts +++ b/src/home/github-types.ts @@ -15,12 +15,14 @@ export type TaskStorageItems = { export type GitHubUserResponse = RestEndpointMethodTypes["users"]["getByUsername"]["response"]; export type GitHubUser = GitHubUserResponse["data"]; export type GitHubIssue = RestEndpointMethodTypes["issues"]["get"]["response"]["data"]; -export type GitHubLabel = { - id?: number; - node_id?: string; - url?: string; - name: string; - description?: string | null; - color?: string | null; - default?: boolean; -} | string; \ No newline at end of file +export type GitHubLabel = + | { + id?: number; + node_id?: string; + url?: string; + name: string; + description?: string | null; + color?: string | null; + default?: boolean; + } + | string; diff --git a/src/home/rendering/render-github-issues.ts b/src/home/rendering/render-github-issues.ts index 79c5d291..ba2f6b99 100644 --- a/src/home/rendering/render-github-issues.ts +++ b/src/home/rendering/render-github-issues.ts @@ -3,7 +3,7 @@ import { organizationImageCache } from "../fetch-github/fetch-issues-full"; import { GitHubIssue } from "../github-types"; import { taskManager } from "../home"; import { renderErrorInModal } from "./display-popup-modal"; -import { modal, modalBodyInner, titleAnchor, titleHeader } from './render-preview-modal'; +import { modal, modalBodyInner, titleAnchor, titleHeader } from "./render-preview-modal"; import { setupKeyboardNavigation } from "./setup-keyboard-navigation"; export function renderGitHubIssues(tasks: GitHubIssue[]) { @@ -56,7 +56,7 @@ function everyNewIssue({ gitHubIssue, container }: { gitHubIssue: GitHubIssue; c // return; // } const labels = parseAndGenerateLabels(gitHubIssue); - const [organizationName, repositoryName] = gitHubIssue.repository_url.split('/').slice(-2); + const [organizationName, repositoryName] = gitHubIssue.repository_url.split("/").slice(-2); setUpIssueElement(issueElement, gitHubIssue, organizationName, repositoryName, labels, gitHubIssue.html_url); issueWrapper.appendChild(issueElement); @@ -64,14 +64,7 @@ function everyNewIssue({ gitHubIssue, container }: { gitHubIssue: GitHubIssue; c return issueWrapper; } -function setUpIssueElement( - issueElement: HTMLDivElement, - task: GitHubIssue, - organizationName: string, - repositoryName: string, - labels: string[], - url: string -) { +function setUpIssueElement(issueElement: HTMLDivElement, task: GitHubIssue, organizationName: string, repositoryName: string, labels: string[], url: string) { const image = ``; issueElement.innerHTML = ` diff --git a/src/home/sorting/sort-issues-by-priority.ts b/src/home/sorting/sort-issues-by-priority.ts index 2c5b9eda..9e486d06 100644 --- a/src/home/sorting/sort-issues-by-priority.ts +++ b/src/home/sorting/sort-issues-by-priority.ts @@ -3,12 +3,12 @@ import { GitHubIssue } from "../github-types"; export function sortIssuesByPriority(issues: GitHubIssue[]) { return issues.sort((a, b) => { const priorityRegex = /Priority: (\d+)/; - const aPriorityMatch = a.labels.find((label): label is { name: string } => - typeof label === 'object' && label !== null && 'name' in label && priorityRegex.test(label.name) - )?.name || 'No Priority'; - const bPriorityMatch = b.labels.find((label): label is { name: string } => - typeof label === 'object' && label !== null && 'name' in label && priorityRegex.test(label.name) - )?.name || 'No Priority'; + const aPriorityMatch = + a.labels.find((label): label is { name: string } => typeof label === "object" && label !== null && "name" in label && priorityRegex.test(label.name)) + ?.name || "No Priority"; + const bPriorityMatch = + b.labels.find((label): label is { name: string } => typeof label === "object" && label !== null && "name" in label && priorityRegex.test(label.name)) + ?.name || "No Priority"; const priorityA = aPriorityMatch.match(priorityRegex); const priorityB = bPriorityMatch.match(priorityRegex); diff --git a/src/home/sorting/sort-issues-by-time.ts b/src/home/sorting/sort-issues-by-time.ts index 9f4d0abc..cbd08995 100644 --- a/src/home/sorting/sort-issues-by-time.ts +++ b/src/home/sorting/sort-issues-by-time.ts @@ -3,10 +3,8 @@ import { calculateTimeLabelValue } from "./calculate-time-label-value"; export function sortIssuesByTime(tasks: GitHubIssue[]) { return tasks.sort((a, b) => { - const aTimeValue = a.labels.reduce((acc, label) => - acc + (typeof label === 'object' && label?.name ? calculateTimeLabelValue(label.name) : 0), 0); - const bTimeValue = b.labels.reduce((acc, label) => - acc + (typeof label === 'object' && label?.name ? calculateTimeLabelValue(label.name) : 0), 0); + const aTimeValue = a.labels.reduce((acc, label) => acc + (typeof label === "object" && label?.name ? calculateTimeLabelValue(label.name) : 0), 0); + const bTimeValue = b.labels.reduce((acc, label) => acc + (typeof label === "object" && label?.name ? calculateTimeLabelValue(label.name) : 0), 0); return bTimeValue - aTimeValue; }); } diff --git a/src/home/task-manager.ts b/src/home/task-manager.ts index 9bc5159d..c785210e 100644 --- a/src/home/task-manager.ts +++ b/src/home/task-manager.ts @@ -23,7 +23,7 @@ export class TaskManager { } public getGitHubIssueById(id: number): GitHubIssue | undefined { - return this._tasks.find(task => task.id === id); + return this._tasks.find((task) => task.id === id); } private async _writeToStorage(tasks: GitHubIssue[]) { From 7a669f6ffaebe0df472d3e74d1d351dc6253606c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=A2=E3=83=AC=E3=82=AF=E3=82=B5=E3=83=B3=E3=83=80?= =?UTF-8?q?=E3=83=BC=2Eeth?= Date: Fri, 4 Oct 2024 07:18:14 +0900 Subject: [PATCH 5/9] fix: render org avatars --- src/home/fetch-github/fetch-and-display-previews.ts | 1 - src/home/home.ts | 12 ++++++------ src/home/task-manager.ts | 2 ++ 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/home/fetch-github/fetch-and-display-previews.ts b/src/home/fetch-github/fetch-and-display-previews.ts index d5b03c27..96e436a5 100644 --- a/src/home/fetch-github/fetch-and-display-previews.ts +++ b/src/home/fetch-github/fetch-and-display-previews.ts @@ -86,7 +86,6 @@ export async function fetchAvatars() { await Promise.allSettled(avatarPromises); applyAvatarsToIssues(); - return cachedTasks; } // export function taskWithFullTest(task: TaskNoFull | TaskWithFull): task is TaskWithFull { diff --git a/src/home/home.ts b/src/home/home.ts index e62b924d..e4002d69 100644 --- a/src/home/home.ts +++ b/src/home/home.ts @@ -1,13 +1,12 @@ import { grid } from "../the-grid"; import { authentication } from "./authentication"; import { initiateDevRelTracking } from "./devrel-tracker"; -import { displayGitHubIssues } from "./fetch-github/fetch-and-display-previews"; +import { fetchAvatars, displayGitHubIssues } from './fetch-github/fetch-and-display-previews'; import { fetchIssuesFull } from "./fetch-github/fetch-issues-full"; import { readyToolbar } from "./ready-toolbar"; import { registerServiceWorker } from "./register-service-worker"; import { renderServiceMessage } from "./render-service-message"; // import { renderErrorInModal } from "./rendering/display-popup-modal"; -import { applyAvatarsToIssues } from "./rendering/render-github-issues"; import { renderGitRevision } from "./rendering/render-github-login-button"; import { generateSortingToolbar } from "./sorting/generate-sorting-buttons"; import { TaskManager } from "./task-manager"; @@ -40,14 +39,15 @@ void (async function home() { void readyToolbar(); const gitHubIssues = await fetchIssuesFull(); taskManager.syncTasks(gitHubIssues); + void fetchAvatars(); + void displayGitHubIssues(); if ("serviceWorker" in navigator) { registerServiceWorker(); } - if (!container.childElementCount) { - displayGitHubIssues(); - applyAvatarsToIssues(); - } + // if (!container.childElementCount) { + // applyAvatarsToIssues(); + // } return gitHubIssues; })(); diff --git a/src/home/task-manager.ts b/src/home/task-manager.ts index c785210e..cb2776b7 100644 --- a/src/home/task-manager.ts +++ b/src/home/task-manager.ts @@ -1,6 +1,7 @@ import { getGitHubAccessToken } from "./getters/get-github-access-token"; import { setLocalStore } from "./getters/get-local-store"; import { GITHUB_TASKS_STORAGE_KEY, GitHubIssue } from "./github-types"; +import { applyAvatarsToIssues } from "./rendering/render-github-issues"; export class TaskManager { private _tasks: GitHubIssue[] = []; @@ -11,6 +12,7 @@ export class TaskManager { public syncTasks(incoming: GitHubIssue[]) { this._tasks = incoming; + applyAvatarsToIssues(); void this._writeToStorage(incoming); } From 8c75b60787cdc388415ef5b45d29abc562fcbf6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=A2=E3=83=AC=E3=82=AF=E3=82=B5=E3=83=B3=E3=83=80?= =?UTF-8?q?=E3=83=BC=2Eeth?= Date: Fri, 4 Oct 2024 07:21:53 +0900 Subject: [PATCH 6/9] feat: proposal viewer does not need auth --- src/home/authentication.ts | 4 ++-- .../fetch-github/fetch-and-display-previews.ts | 14 +++++++------- src/home/home.ts | 2 +- static/index.html | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/home/authentication.ts b/src/home/authentication.ts index 01ff446f..0cbf3ddb 100644 --- a/src/home/authentication.ts +++ b/src/home/authentication.ts @@ -4,7 +4,7 @@ import { getGitHubUser } from "./getters/get-github-user"; import { GitHubUser } from "./github-types"; import { displayGitHubUserInformation } from "./rendering/display-github-user-information"; import { renderGitHubLoginButton } from "./rendering/render-github-login-button"; -import { viewToggle } from "./fetch-github/fetch-and-display-previews"; +// import { viewToggle } from "./fetch-github/fetch-and-display-previews"; export async function authentication() { const accessToken = await getGitHubAccessToken(); @@ -16,6 +16,6 @@ export async function authentication() { if (gitHubUser) { trackDevRelReferral(gitHubUser.login + "|" + gitHubUser.id); await displayGitHubUserInformation(gitHubUser); - viewToggle.disabled = false; + // viewToggle.disabled = false; } } diff --git a/src/home/fetch-github/fetch-and-display-previews.ts b/src/home/fetch-github/fetch-and-display-previews.ts index 96e436a5..1925bb8e 100644 --- a/src/home/fetch-github/fetch-and-display-previews.ts +++ b/src/home/fetch-github/fetch-and-display-previews.ts @@ -32,13 +32,13 @@ export async function fetchAndDisplayPreviewsFromCache(sorting?: Sorting, option // Refresh the storage if there is no logged-in object in cachedTasks but there is one now. if (_cachedTasks && !_cachedTasks.loggedIn && _accessToken) { localStorage.removeItem(GITHUB_TASKS_STORAGE_KEY); - return fetchAndDisplayPreviewsFromNetwork(sorting, options); + return fetchAndDisplayIssuesFromNetwork(sorting, options); } // If previously logged in but not anymore, clear cache and fetch from network. if (_cachedTasks && _cachedTasks.loggedIn && !_accessToken) { localStorage.removeItem(GITHUB_TASKS_STORAGE_KEY); - return fetchAndDisplayPreviewsFromNetwork(sorting, options); + return fetchAndDisplayIssuesFromNetwork(sorting, options); } // makes sure tasks have a timestamp to know how old the cache is, or refresh if older than 15 minutes @@ -55,14 +55,14 @@ export async function fetchAndDisplayPreviewsFromCache(sorting?: Sorting, option if (!cachedTasks.length) { // load from network if there are no cached issues - return fetchAndDisplayPreviewsFromNetwork(sorting, options); + return fetchAndDisplayIssuesFromNetwork(sorting, options); } else { displayGitHubIssues(sorting, options); return fetchAvatars(); } } -export async function fetchAndDisplayPreviewsFromNetwork(sorting?: Sorting, options = { ordering: "normal" }) { +export async function fetchAndDisplayIssuesFromNetwork(sorting?: Sorting, options = { ordering: "normal" }) { // const fetchedPreviews = await fetchIssuePreviews(); // const cachedTasks = taskManager.getTasks(); // const updatedCachedIssues = verifyGitHubIssueState(cachedTasks, fetchedPreviews); @@ -73,11 +73,11 @@ export async function fetchAndDisplayPreviewsFromNetwork(sorting?: Sorting, opti export async function fetchAvatars() { const cachedTasks = taskManager.getTasks(); - const urlPattern = /https:\/\/github\.com\/(?[^/]+)\/(?[^/]+)\/issues\/(?\d+)/; + // const urlPattern = /https:\/\/github\.com\/(?[^/]+)\/(?[^/]+)\/issues\/(?\d+)/; const avatarPromises = cachedTasks.map(async (task: GitHubIssue) => { - const match = task.body?.match(urlPattern); - const orgName = match?.groups?.org; + // const match = task.body?.match(urlPattern); + const [orgName] = task.repository_url.split("/").slice(-2); if (orgName) { return fetchAvatar(orgName); } diff --git a/src/home/home.ts b/src/home/home.ts index e4002d69..711de787 100644 --- a/src/home/home.ts +++ b/src/home/home.ts @@ -1,7 +1,7 @@ import { grid } from "../the-grid"; import { authentication } from "./authentication"; import { initiateDevRelTracking } from "./devrel-tracker"; -import { fetchAvatars, displayGitHubIssues } from './fetch-github/fetch-and-display-previews'; +import { fetchAvatars, displayGitHubIssues } from "./fetch-github/fetch-and-display-previews"; import { fetchIssuesFull } from "./fetch-github/fetch-issues-full"; import { readyToolbar } from "./ready-toolbar"; import { registerServiceWorker } from "./register-service-worker"; diff --git a/static/index.html b/static/index.html index b60d799b..bda43305 100644 --- a/static/index.html +++ b/static/index.html @@ -55,7 +55,7 @@ d="M132 41.1c0-2.3-1.3-4.5-3.3-5.7L69.4 1.2c-1-.6-2.1-.9-3.3-.9-1.1 0-2.3.3-3.3.9L3.6 35.4c-2 1.2-3.3 3.3-3.3 5.7v68.5c0 2.3 1.3 4.5 3.3 5.7l59.3 34.2c2 1.2 4.5 1.2 6.5 0l59.3-34.2c2-1.2 3.3-3.3 3.3-5.7V41.1zm-11.9 62.5c0 2.7-1.4 5.2-3.7 6.5l-46.6 27.5c-1.1.7-2.4 1-3.7 1s-2.5-.3-3.7-1l-46.6-27.5c-2.3-1.3-3.7-3.8-3.7-6.5V54.1c0-1.2.6-2.4 1.7-3 1.1-.6 2.3-.6 3.4 0l8 4.7c1.9 1.1 3 3.3 4.4 5.8.3.5.5 1 .8 1.4 3.5 6.3 5.2 13 6.8 19.5 3 11.9 6 24.2 21.3 28.2 5 1.3 10.4 1.3 15.4 0 15.2-4 18.3-16.3 21.3-28.2C96.8 76 98.5 69.3 102 63c.3-.5.5-1 .8-1.4 1.3-2.5 2.5-4.6 4.4-5.8l8-4.7c1-.6 2.3-.6 3.4 0s1.7 1.7 1.7 3v49.5zM62.6 13.7c2.2-1.3 4.9-1.3 7.1 0L110 37.6c1 .6 1.6 1 1.6 2.2 0 1.2-.6 1.9-1.6 2.5l-7.7 4.6c-3.4 2-5.1 5.2-6.6 8.1l-.1.2c-.2.4-.4.7-.6 1.1-3.8 6.8-6.6 14-8.2 20.4C83.6 89.1 82.4 97.3 72 100c-1.9.5-3.9.7-5.8.7-2 0-3.9-.3-5.8-.7C50 97.3 48.7 89.1 45.6 76.6 44 70.2 41.2 63 37.4 56.2c-.2-.3-.4-.7-.6-1l-.1-.3c-1.5-2.8-3.3-6.1-6.6-8.1l-7.7-4.6c-1-.6-1.6-1.3-1.6-2.5s.6-1.6 1.6-2.2l40.2-23.8z" >Ubiquity DAO | DevPoolUbiquity DAO | DevPool
From 4dd8e61ad85242547e6319b3af3b1cc1439dfa78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=A2=E3=83=AC=E3=82=AF=E3=82=B5=E3=83=B3=E3=83=80?= =?UTF-8?q?=E3=83=BC=2Eeth?= Date: Fri, 4 Oct 2024 07:23:40 +0900 Subject: [PATCH 7/9] refactor: cleanup --- .../fetch-and-display-previews.ts | 49 +--------- src/home/fetch-github/fetch-issues-full.ts | 40 -------- src/home/fetch-github/fetch-issues-preview.ts | 94 ------------------- .../fetch-github/preview-to-full-mapping.ts | 27 ------ src/home/rendering/render-preview-modal.ts | 2 - 5 files changed, 1 insertion(+), 211 deletions(-) diff --git a/src/home/fetch-github/fetch-and-display-previews.ts b/src/home/fetch-github/fetch-and-display-previews.ts index 1925bb8e..2b70c3c8 100644 --- a/src/home/fetch-github/fetch-and-display-previews.ts +++ b/src/home/fetch-github/fetch-and-display-previews.ts @@ -13,7 +13,7 @@ export type Options = { ordering: "normal" | "reverse"; }; -let isProposalOnlyViewer = false; // or proposal viewer +let isProposalOnlyViewer = false; export const viewToggle = document.getElementById("view-toggle") as HTMLInputElement; if (!viewToggle) { @@ -29,19 +29,16 @@ export async function fetchAndDisplayPreviewsFromCache(sorting?: Sorting, option let _cachedTasks = getLocalStore(GITHUB_TASKS_STORAGE_KEY) as TaskStorageItems; const _accessToken = await getGitHubAccessToken(); - // Refresh the storage if there is no logged-in object in cachedTasks but there is one now. if (_cachedTasks && !_cachedTasks.loggedIn && _accessToken) { localStorage.removeItem(GITHUB_TASKS_STORAGE_KEY); return fetchAndDisplayIssuesFromNetwork(sorting, options); } - // If previously logged in but not anymore, clear cache and fetch from network. if (_cachedTasks && _cachedTasks.loggedIn && !_accessToken) { localStorage.removeItem(GITHUB_TASKS_STORAGE_KEY); return fetchAndDisplayIssuesFromNetwork(sorting, options); } - // makes sure tasks have a timestamp to know how old the cache is, or refresh if older than 15 minutes if (!_cachedTasks || !_cachedTasks.timestamp || _cachedTasks.timestamp + 60 * 1000 * 15 <= Date.now()) { _cachedTasks = { timestamp: Date.now(), @@ -54,7 +51,6 @@ export async function fetchAndDisplayPreviewsFromCache(sorting?: Sorting, option taskManager.syncTasks(cachedTasks); if (!cachedTasks.length) { - // load from network if there are no cached issues return fetchAndDisplayIssuesFromNetwork(sorting, options); } else { displayGitHubIssues(sorting, options); @@ -63,20 +59,14 @@ export async function fetchAndDisplayPreviewsFromCache(sorting?: Sorting, option } export async function fetchAndDisplayIssuesFromNetwork(sorting?: Sorting, options = { ordering: "normal" }) { - // const fetchedPreviews = await fetchIssuePreviews(); - // const cachedTasks = taskManager.getTasks(); - // const updatedCachedIssues = verifyGitHubIssueState(cachedTasks, fetchedPreviews); - // taskManager.syncTasks(updatedCachedIssues); displayGitHubIssues(sorting, options); return fetchAvatars(); } export async function fetchAvatars() { const cachedTasks = taskManager.getTasks(); - // const urlPattern = /https:\/\/github\.com\/(?[^/]+)\/(?[^/]+)\/issues\/(?\d+)/; const avatarPromises = cachedTasks.map(async (task: GitHubIssue) => { - // const match = task.body?.match(urlPattern); const [orgName] = task.repository_url.split("/").slice(-2); if (orgName) { return fetchAvatar(orgName); @@ -88,42 +78,9 @@ export async function fetchAvatars() { applyAvatarsToIssues(); } -// export function taskWithFullTest(task: TaskNoFull | TaskWithFull): task is TaskWithFull { -// return (task as TaskWithFull).full !== null && (task as TaskWithFull).full !== undefined; -// } - -// export function verifyGitHubIssueState(cachedTasks: GitHubIssue[], fetchedPreviews: TaskNoFull[]): (TaskNoFull | TaskWithFull)[] { -// return fetchedPreviews.map((fetched) => { -// const cachedTask = cachedTasks.find((c) => c.full?.id === fetched.preview.id); -// if (cachedTask) { -// if (taskWithFullTest(cachedTask)) { -// const cachedFullIssue = cachedTask.full; -// const task = { ...fetched, full: cachedFullIssue }; -// return task; -// } else { -// // no full issue in task -// } -// } else { -// // no cached task -// } -// return { -// preview: fetched.preview, -// } as TaskNoFull; -// }); -// } - export function displayGitHubIssues(sorting?: Sorting, options = { ordering: "normal" }) { - // Load avatars from cache - // const urlPattern = /https:\/\/github\.com\/(?[^/]+)\/(?[^/]+)\/issues\/(?\d+)/; const cached = taskManager.getTasks(); cached.forEach(async (gitHubIssue) => { - // if (!issue.body) { - // throw new Error(`Preview body is undefined for task with id: ${issue.id}`); - // } - // const match = issue.body.match(urlPattern); - // const orgName = match?.groups?.org; - // if (orgName) { - const [orgName] = gitHubIssue.repository_url.split("/").slice(-2); getImageFromCache({ @@ -133,10 +90,8 @@ export function displayGitHubIssues(sorting?: Sorting, options = { ordering: "no }) .then((avatarUrl) => organizationImageCache.set(orgName, avatarUrl)) .catch(console.error); - // } }); - // Render issues const sortedIssues = sortIssuesController(cached, sorting, options); const sortedAndFiltered = sortedIssues.filter(getProposalsOnlyFilter(isProposalOnlyViewer)); renderGitHubIssues(sortedAndFiltered); @@ -151,8 +106,6 @@ function getProposalsOnlyFilter(getProposals: boolean) { return label.name?.startsWith("Price: ") || label.name?.startsWith("Price: "); }); - // If getProposals is true, we want tasks WITHOUT price labels - // If getProposals is false, we want tasks WITH price labels return getProposals ? !hasPriceLabel : hasPriceLabel; }; } diff --git a/src/home/fetch-github/fetch-issues-full.ts b/src/home/fetch-github/fetch-issues-full.ts index d822752f..c5bc6390 100644 --- a/src/home/fetch-github/fetch-issues-full.ts +++ b/src/home/fetch-github/fetch-issues-full.ts @@ -1,46 +1,6 @@ import { GitHubIssue } from "../github-types"; export const organizationImageCache = new Map(); -// export async function fetchIssuesFull(taskPreviews: GitHubIssue[]): Promise { -// const octokit = new Octokit({ auth: await getGitHubAccessToken() }); -// const urlPattern = /https:\/\/github\.com\/(?[^/]+)\/(?[^/]+)\/issues\/(?\d+)/; - -// const fullTaskPromises = taskPreviews.map(async (task) => { -// const match = task.preview.body.match(urlPattern); - -// if (!match || !match.groups) { -// console.error("Invalid issue body URL format"); -// return Promise.resolve(null); -// } - -// const { org, repo, issue_number } = match.groups; - -// const { data: response } = await octokit.request("GET /repos/{org}/{repo}/issues/{issue_number}", { issue_number, repo, org }); - -// task.full = response as GitHubIssue; - -// const urlMatch = task.full.html_url.match(urlPattern); -// const orgName = urlMatch?.groups?.org; -// if (orgName) { -// await fetchAvatar(orgName); -// } -// const isTaskWithFull = taskWithFullTest(task); - -// if (isTaskWithFull) { -// return task; -// } else { -// throw new Error("Task is not a TaskWithFull"); -// } -// }); - -// const settled = await Promise.allSettled(fullTaskPromises); -// const fullTasks = settled -// .filter((result): result is PromiseFulfilledResult => result.status === "fulfilled") -// .map((result) => result.value) -// .filter((issue): issue is TaskWithFull => issue !== null); - -// return fullTasks; -// } export async function fetchIssuesFull(): Promise { const response = await fetch("https://raw.githubusercontent.com/ubiquity/devpool-directory/refs/heads/development/devpool-issues.json"); const jsonData = await response.json(); diff --git a/src/home/fetch-github/fetch-issues-preview.ts b/src/home/fetch-github/fetch-issues-preview.ts index 78aeca0c..01fc453d 100644 --- a/src/home/fetch-github/fetch-issues-preview.ts +++ b/src/home/fetch-github/fetch-issues-preview.ts @@ -1,99 +1,5 @@ import { displayPopupMessage } from "../rendering/display-popup-modal"; -// async function checkPrivateRepoAccess(): Promise { -// const octokit = new Octokit({ auth: await getGitHubAccessToken() }); -// const username = getGitHubUserName(); - -// if (username) { -// try { -// const response = await octokit.repos.checkCollaborator({ -// owner: "ubiquity", -// repo: "devpool-directory-private", -// username, -// }); - -// if (response.status === 204) { -// // If the response is successful, it means the user has access to the private repository -// return true; -// } -// return false; -// } catch (error) { -// if (!!error && typeof error === "object" && "status" in error && (error.status === 404 || error.status === 401)) { -// // If the status is 404, it means the user is not a collaborator, hence no access -// return false; -// } else { -// // Handle other errors if needed -// console.error("Error checking repository access:", error); -// throw error; -// } -// } -// } - -// return false; -// } - -// export async function fetchIssues(): Promise { -// const octokit = new Octokit({ auth: await getGitHubAccessToken() }); -// let freshIssues: GitHubIssue[] = []; -// let hasPrivateRepoAccess = false; // Flag to track access to the private repository - -// try { -// // Check if the user has access to the private repository -// hasPrivateRepoAccess = await checkPrivateRepoAccess(); - -// // // Fetch issues from public repository -// // const publicResponse = await octokit.paginate(octokit.issues.listForRepo, { -// // owner: "ubiquity", -// // repo: "devpool-directory", -// // state: "open", -// // }); - -// // const publicIssues = publicResponse.filter((issue: GitHubIssue) => !issue.pull_request); - -// // Fetch issues from the private repository only if the user has access -// if (hasPrivateRepoAccess) { -// await fetchPrivateIssues(); -// } else { -// // If user doesn't have access, only load issues from the public repository -// freshIssues = publicIssues; -// } -// } catch (error) { -// if (!!error && typeof error === "object" && "status" in error && error.status === 403) { -// await handleRateLimit(octokit, error as RequestError); -// } else { -// throw error; -// } -// } - -// const tasks = freshIssues.map((preview: GitHubIssue) => ({ -// preview: preview, -// full: null, -// isNew: true, -// isModified: true, -// })); - -// return tasks; - -// async function fetchPrivateIssues() { -// const privateResponse = await octokit.paginate(octokit.issues.listForRepo, { -// owner: "ubiquity", -// repo: "devpool-directory-private", -// state: "open", -// }); -// const privateIssues = privateResponse.filter((issue: GitHubIssue) => !issue.pull_request); - -// Mark private issues -// TODO: indicate private issues in the UI - -// const privateIssuesWithFlag = privateIssues.map((issue) => { -// return issue; -// }); - -// Combine public and private issues -// return privateIssues -// } -// } - export function rateLimitModal(message: string) { displayPopupMessage({ modalHeader: `GitHub API rate limit exceeded.`, modalBody: message, isError: false }); } diff --git a/src/home/fetch-github/preview-to-full-mapping.ts b/src/home/fetch-github/preview-to-full-mapping.ts index fa60debf..8e4a86ce 100644 --- a/src/home/fetch-github/preview-to-full-mapping.ts +++ b/src/home/fetch-github/preview-to-full-mapping.ts @@ -4,30 +4,3 @@ export type TaskNoState = { preview: GitHubIssue; full: null | GitHubIssue; }; - -// export type TaskNoFull = { -// preview: GitHubIssue; -// full: null; -// isNew: boolean; -// isModified: boolean; -// }; - -// export type TaskMaybeFull = { -// preview: GitHubIssue; -// full: null | GitHubIssue; -// isNew: boolean; -// isModified: boolean; -// }; - -// export type TaskWithFull = { -// preview: GitHubIssue; -// full: GitHubIssue; -// isNew: boolean; -// isModified: boolean; -// }; - -// export type TaskFull = { -// issue: GitHubIssue; -// isNew: boolean; -// isModified: boolean; -// } diff --git a/src/home/rendering/render-preview-modal.ts b/src/home/rendering/render-preview-modal.ts index 818821bd..3a48beb9 100644 --- a/src/home/rendering/render-preview-modal.ts +++ b/src/home/rendering/render-preview-modal.ts @@ -6,7 +6,6 @@ const modalHeader = document.createElement("div"); modalHeader.classList.add("preview-header"); export const titleAnchor = document.createElement("a"); titleAnchor.setAttribute("target", "_blank"); -// titleAnchor.href = "#"; export const titleHeader = document.createElement("h1"); const closeButton = document.createElement("button"); closeButton.classList.add("close-preview"); @@ -15,7 +14,6 @@ const modalBody = document.createElement("div"); modalBody.classList.add("preview-body"); export const modalBodyInner = document.createElement("div"); modalBodyInner.classList.add("preview-body-inner"); -// Assemble the preview box modalHeader.appendChild(closeButton); titleAnchor.appendChild(titleHeader); const openNewLinkIcon = ``; From c66036ed563cf0dbe50a21c8b3e1c3dd96338333 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=A2=E3=83=AC=E3=82=AF=E3=82=B5=E3=83=B3=E3=83=80?= =?UTF-8?q?=E3=83=BC=2Eeth?= Date: Fri, 4 Oct 2024 07:28:27 +0900 Subject: [PATCH 8/9] refactor: simplify and handle label lint error --- src/home/sorting/sort-issues-by-priority.ts | 24 +++++++++------------ 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/home/sorting/sort-issues-by-priority.ts b/src/home/sorting/sort-issues-by-priority.ts index 9e486d06..ba12b740 100644 --- a/src/home/sorting/sort-issues-by-priority.ts +++ b/src/home/sorting/sort-issues-by-priority.ts @@ -1,21 +1,17 @@ import { GitHubIssue } from "../github-types"; export function sortIssuesByPriority(issues: GitHubIssue[]) { - return issues.sort((a, b) => { - const priorityRegex = /Priority: (\d+)/; - const aPriorityMatch = - a.labels.find((label): label is { name: string } => typeof label === "object" && label !== null && "name" in label && priorityRegex.test(label.name)) - ?.name || "No Priority"; - const bPriorityMatch = - b.labels.find((label): label is { name: string } => typeof label === "object" && label !== null && "name" in label && priorityRegex.test(label.name)) - ?.name || "No Priority"; - - const priorityA = aPriorityMatch.match(priorityRegex); - const priorityB = bPriorityMatch.match(priorityRegex); + const priorityRegex = /Priority: (\d+)/; - const aPriority = priorityA && priorityA[1] ? parseInt(priorityA[1], 10) : 0; - const bPriority = priorityB && priorityB[1] ? parseInt(priorityB[1], 10) : 0; + return issues.sort((a, b) => { + function getPriority(issue: GitHubIssue) { + const priorityLabel = issue.labels.find( + (label): label is { name: string } => typeof label === "object" && "name" in label && typeof label.name === "string" && priorityRegex.test(label.name) + ); + const match = priorityLabel?.name.match(priorityRegex); + return match ? parseInt(match[1], 10) : -1; + } - return bPriority - aPriority; + return getPriority(b) - getPriority(a); }); } From 27749381b19b1c9f96e0b37d981a3197b47b5554 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=A2=E3=83=AC=E3=82=AF=E3=82=B5=E3=83=B3=E3=83=80?= =?UTF-8?q?=E3=83=BC=2Eeth?= Date: Fri, 4 Oct 2024 07:31:30 +0900 Subject: [PATCH 9/9] refactor: cleanup --- src/home/authentication.ts | 2 -- src/home/home.ts | 15 ++++++--------- src/home/rendering/render-github-issues.ts | 18 ------------------ 3 files changed, 6 insertions(+), 29 deletions(-) diff --git a/src/home/authentication.ts b/src/home/authentication.ts index 0cbf3ddb..4e8fb1ae 100644 --- a/src/home/authentication.ts +++ b/src/home/authentication.ts @@ -4,7 +4,6 @@ import { getGitHubUser } from "./getters/get-github-user"; import { GitHubUser } from "./github-types"; import { displayGitHubUserInformation } from "./rendering/display-github-user-information"; import { renderGitHubLoginButton } from "./rendering/render-github-login-button"; -// import { viewToggle } from "./fetch-github/fetch-and-display-previews"; export async function authentication() { const accessToken = await getGitHubAccessToken(); @@ -16,6 +15,5 @@ export async function authentication() { if (gitHubUser) { trackDevRelReferral(gitHubUser.login + "|" + gitHubUser.id); await displayGitHubUserInformation(gitHubUser); - // viewToggle.disabled = false; } } diff --git a/src/home/home.ts b/src/home/home.ts index 711de787..259370bf 100644 --- a/src/home/home.ts +++ b/src/home/home.ts @@ -6,19 +6,19 @@ import { fetchIssuesFull } from "./fetch-github/fetch-issues-full"; import { readyToolbar } from "./ready-toolbar"; import { registerServiceWorker } from "./register-service-worker"; import { renderServiceMessage } from "./render-service-message"; -// import { renderErrorInModal } from "./rendering/display-popup-modal"; +import { renderErrorInModal } from "./rendering/display-popup-modal"; import { renderGitRevision } from "./rendering/render-github-login-button"; import { generateSortingToolbar } from "./sorting/generate-sorting-buttons"; import { TaskManager } from "./task-manager"; // All unhandled errors are caught and displayed in a modal -// window.addEventListener("error", (event: ErrorEvent) => renderErrorInModal(event.error)); +window.addEventListener("error", (event: ErrorEvent) => renderErrorInModal(event.error)); // All unhandled promise rejections are caught and displayed in a modal -// window.addEventListener("unhandledrejection", (event: PromiseRejectionEvent) => { -// renderErrorInModal(event.reason as Error); -// event.preventDefault(); -// }); +window.addEventListener("unhandledrejection", (event: PromiseRejectionEvent) => { + renderErrorInModal(event.reason as Error); + event.preventDefault(); +}); renderGitRevision(); initiateDevRelTracking(); @@ -46,8 +46,5 @@ void (async function home() { registerServiceWorker(); } - // if (!container.childElementCount) { - // applyAvatarsToIssues(); - // } return gitHubIssues; })(); diff --git a/src/home/rendering/render-github-issues.ts b/src/home/rendering/render-github-issues.ts index ba2f6b99..0712fc45 100644 --- a/src/home/rendering/render-github-issues.ts +++ b/src/home/rendering/render-github-issues.ts @@ -37,24 +37,6 @@ function everyNewIssue({ gitHubIssue, container }: { gitHubIssue: GitHubIssue; c issueElement.setAttribute("data-issue-id", gitHubIssue.id.toString()); issueElement.classList.add("issue-element-inner"); - // const urlPattern = /https:\/\/github\.com\/([^/]+)\/([^/]+)\//; - // if (!gitHubIssue.body) { - // console.warn(`No body found for issue ${gitHubIssue.id}.`); - // return; - // } - // const match = gitHubIssue.body.match(urlPattern); - // const organizationName = match?.[1]; - - // if (!organizationName) { - // console.warn(`No organization name found for issue ${gitHubIssue.id}.`); - // return; - // } - - // const repositoryName = match?.[2]; - // if (!repositoryName) { - // console.warn("No repository name found"); - // return; - // } const labels = parseAndGenerateLabels(gitHubIssue); const [organizationName, repositoryName] = gitHubIssue.repository_url.split("/").slice(-2); setUpIssueElement(issueElement, gitHubIssue, organizationName, repositoryName, labels, gitHubIssue.html_url);