diff --git a/LICENSE b/LICENSE index 3a3b2d2..98845b0 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Nuxt UI Templates +Copyright (c) 2025 GitFlux Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/app/components/Issue/Card.vue b/app/components/Issue/Card.vue new file mode 100644 index 0000000..4e98190 --- /dev/null +++ b/app/components/Issue/Card.vue @@ -0,0 +1,170 @@ + + + \ No newline at end of file diff --git a/app/components/Issue/List.vue b/app/components/Issue/List.vue new file mode 100644 index 0000000..80738d0 --- /dev/null +++ b/app/components/Issue/List.vue @@ -0,0 +1,207 @@ + + + + + \ No newline at end of file diff --git a/app/components/Readme.vue b/app/components/Readme.vue index ae28e3b..1bb8562 100644 --- a/app/components/Readme.vue +++ b/app/components/Readme.vue @@ -14,21 +14,21 @@ function handleClick(event: MouseEvent) { if (!target) return; // Handle copy button clicks - const copyTarget = target.closest('[data-copy]'); + const copyTarget = target.closest("[data-copy]"); if (copyTarget) { - const wrapper = copyTarget.closest('.readme-code-block'); + const wrapper = copyTarget.closest(".readme-code-block"); if (!wrapper) return; - const pre = wrapper.querySelector('pre'); + const pre = wrapper.querySelector("pre"); if (!pre?.textContent) return; copy(pre.textContent); - const icon = copyTarget.querySelector('span'); + const icon = copyTarget.querySelector("span"); if (!icon) return; - const originalIcon = 'i-heroicons-document-duplicate'; - const successIcon = 'i-heroicons-check'; + const originalIcon = "i-heroicons-document-duplicate"; + const successIcon = "i-heroicons-check"; icon.classList.remove(originalIcon); icon.classList.add(successIcon); @@ -62,6 +62,26 @@ function handleClick(event: MouseEvent) { min-width: 0; } +@media screen and (prefers-color-scheme: light) { + .readme { + color: #000; + } + + .readme svg { + filter: invert(1); + } +} + +@media screen and (prefers-color-scheme: dark) { + .readme { + color: #fff; + } + + .readme svg { + filter: invert(1); + } +} + /* README headings */ .readme :deep(h1), .readme :deep(h2), @@ -71,7 +91,6 @@ function handleClick(event: MouseEvent) { .readme :deep(h6) { font-family: "Monaspace Krypton", ui-monospace, SFMono-Regular, monospace; font-weight: 500; - color: rgb(17 24 39); margin-top: 2rem; margin-bottom: 1rem; line-height: 1.3; @@ -260,4 +279,4 @@ function handleClick(event: MouseEvent) { border-top-color: rgb(75 85 99); } } - \ No newline at end of file + diff --git a/app/composables/useGithub.ts b/app/composables/useGithub.ts index a7314be..fdc6340 100644 --- a/app/composables/useGithub.ts +++ b/app/composables/useGithub.ts @@ -3,11 +3,11 @@ import { Octokit } from "octokit"; const useOctokit = () => { const { user } = useUserSession(); - if (!user.value?.githubToken) { + if (!(user.value as any)?.githubToken) { throw new Error("GitHub access token not found. Please login first."); } - return new Octokit({ auth: user.value.githubToken }); + return new Octokit({ auth: (user.value as any).githubToken }); }; export const useAuthenticatedUser = async (token: string) => { @@ -99,3 +99,70 @@ export const useGithubRepoReleases = (owner: string, repo: string) => { {}, ); }; + +export const useGithubRepoIssues = ( + owner: string, + repo: string, + options: MaybeRef<{ + state?: "open" | "closed" | "all"; + sort?: "created" | "updated" | "comments"; + direction?: "asc" | "desc"; + per_page?: number; + page?: number; + }> = {}, +) => { + return useAsyncData( + () => `github-issues-${owner}-${repo}-${JSON.stringify(unref(options))}`, + async () => { + const octokit = useOctokit(); + const opts = unref(options); + return octokit.rest.issues.listForRepo({ + owner, + repo, + state: opts.state || "open", + sort: opts.sort || "created", + direction: opts.direction || "desc", + per_page: opts.per_page || 25, + page: opts.page || 1, + }); + }, + { + watch: [() => unref(options)], + }, + ); +}; + +export const useGithubRepoIssuesCount = (owner: string, repo: string, state: "closed") => { + return useAsyncData( + `github-issues-count-${owner}-${repo}-${state}`, + async () => { + const octokit = useOctokit(); + try { + // Use the search API to get an accurate count of closed issues + // This is more efficient than fetching all issues just for the count + const searchQuery = `repo:${owner}/${repo} type:issue state:${state}`; + const response = await octokit.rest.search.issuesAndPullRequests({ + q: searchQuery, + per_page: 1, // We only need the total_count, not the actual results + }); + return response.data.total_count; + } catch { + // Fallback: try to fetch first page of closed issues and estimate + try { + const response = await octokit.rest.issues.listForRepo({ + owner, + repo, + state, + per_page: 1, + }); + // This won't give us the exact count, but it's better than nothing + // GitHub doesn't provide total count in the response headers for issues API + return response.data.length > 0 ? 1 : 0; + } catch { + return 0; + } + } + }, + {}, + ); +}; diff --git a/app/pages/[org]/[repo].vue b/app/pages/[org]/[repo]/index.vue similarity index 94% rename from app/pages/[org]/[repo].vue rename to app/pages/[org]/[repo]/index.vue index 0a557f7..2aff136 100644 --- a/app/pages/[org]/[repo].vue +++ b/app/pages/[org]/[repo]/index.vue @@ -136,7 +136,7 @@ function getLanguageColor(language: string): string { diff --git a/nuxt.config.ts b/nuxt.config.ts index cdf4621..fe29f09 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -21,7 +21,7 @@ export default defineNuxtConfig({ }, session: { name: "nuxt-session", - password: process.env.NUXT_SESSION_PASSWORD || "", + password: "", cookie: { sameSite: "lax", secure: false, // Allow cookies over HTTP in development