Skip to content

Commit 2393ed4

Browse files
committed
Enhance repository cards with rich metadata and add pagination
Added detailed repository information to the settings page. Repo cards now display description, star count, programming language, latest release version, and last updated time. This gives users better context about each repository they're monitoring. Also implemented a 50 repository limit with search and pagination to handle larger collections. The search filters across repo name, description, and language. Pagination kicks in automatically when you have more than 10 repos. Other improvements: - Moved repo-related status messages to appear under the repo section instead of at the top of the page - Replaced star emoji with SVG icon for better cross-platform rendering - Updated background worker to support both old string format and new object format for backwards compatibility - Added automatic migration to convert existing repos to new format
1 parent 3f06b55 commit 2393ed4

8 files changed

Lines changed: 1541 additions & 149 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ dist/
1313
CLAUDE.md
1414
notes.md
1515
todo.txt
16+
ROADMAP.md

background.js

Lines changed: 143 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,15 @@ async function checkGitHubActivity() {
5959
const newActivities = [];
6060

6161
for (const repo of watchedRepos) {
62-
const activities = await fetchRepoActivity(repo, githubToken, lastCheckDate, enabledFilters);
62+
// Handle both string format (legacy) and object format (new)
63+
const repoName = typeof repo === 'string' ? repo : repo.fullName;
64+
const activities = await fetchRepoActivity(repoName, githubToken, lastCheckDate, enabledFilters);
6365
newActivities.push(...activities);
6466
}
6567

6668
if (newActivities.length > 0) {
6769
await storeActivities(newActivities);
68-
updateBadge(newActivities.length);
70+
await updateBadge();
6971
showNotifications(newActivities);
7072
}
7173

@@ -82,84 +84,134 @@ async function fetchRepoActivity(repo, token, since, filters) {
8284
'Accept': 'application/vnd.github.v3+json'
8385
};
8486

87+
async function fetchWithRateLimit(url) {
88+
const response = await fetch(url, { headers });
89+
90+
// Track rate limits
91+
const remaining = response.headers.get('X-RateLimit-Remaining');
92+
const limit = response.headers.get('X-RateLimit-Limit');
93+
const reset = response.headers.get('X-RateLimit-Reset');
94+
95+
if (remaining && limit) {
96+
await chrome.storage.local.set({
97+
rateLimit: {
98+
remaining: parseInt(remaining),
99+
limit: parseInt(limit),
100+
reset: parseInt(reset) * 1000
101+
}
102+
});
103+
}
104+
105+
if (!response.ok) {
106+
if (response.status === 401) {
107+
throw new Error('Invalid GitHub token');
108+
} else if (response.status === 403) {
109+
throw new Error('Rate limit exceeded');
110+
} else if (response.status === 404) {
111+
throw new Error(`Repository ${repo} not found`);
112+
} else {
113+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
114+
}
115+
}
116+
117+
return response;
118+
}
119+
85120
try {
86121
// Fetch PRs
87122
if (filters.prs) {
88-
const prsResponse = await fetch(
89-
`https://api.github.com/repos/${repo}/pulls?state=open&sort=created&direction=desc`,
90-
{ headers }
123+
const prsResponse = await fetchWithRateLimit(
124+
`https://api.github.com/repos/${repo}/pulls?state=open&sort=created&direction=desc`
91125
);
92126

93-
if (prsResponse.ok) {
94-
const prs = await prsResponse.json();
95-
const newPrs = prs.filter(pr => new Date(pr.created_at) > since);
96-
activities.push(...newPrs.map(pr => ({
97-
type: 'pr',
98-
repo,
99-
title: pr.title,
100-
url: pr.html_url,
101-
createdAt: pr.created_at,
102-
author: pr.user.login
103-
})));
104-
}
127+
const prs = await prsResponse.json();
128+
const newPrs = prs.filter(pr => new Date(pr.created_at) > since);
129+
activities.push(...newPrs.map(pr => ({
130+
id: `pr-${repo}-${pr.number}`,
131+
type: 'pr',
132+
repo,
133+
title: pr.title,
134+
url: pr.html_url,
135+
createdAt: pr.created_at,
136+
author: pr.user.login,
137+
authorAvatar: pr.user.avatar_url,
138+
number: pr.number
139+
})));
105140
}
106141

107142
// Fetch Issues
108143
if (filters.issues) {
109-
const issuesResponse = await fetch(
110-
`https://api.github.com/repos/${repo}/issues?state=open&sort=created&direction=desc`,
111-
{ headers }
144+
const issuesResponse = await fetchWithRateLimit(
145+
`https://api.github.com/repos/${repo}/issues?state=open&sort=created&direction=desc`
112146
);
113147

114-
if (issuesResponse.ok) {
115-
const issues = await issuesResponse.json();
116-
const newIssues = issues.filter(issue => !issue.pull_request && new Date(issue.created_at) > since);
117-
activities.push(...newIssues.map(issue => ({
118-
type: 'issue',
119-
repo,
120-
title: issue.title,
121-
url: issue.html_url,
122-
createdAt: issue.created_at,
123-
author: issue.user.login
124-
})));
125-
}
148+
const issues = await issuesResponse.json();
149+
const newIssues = issues.filter(issue => !issue.pull_request && new Date(issue.created_at) > since);
150+
activities.push(...newIssues.map(issue => ({
151+
id: `issue-${repo}-${issue.number}`,
152+
type: 'issue',
153+
repo,
154+
title: issue.title,
155+
url: issue.html_url,
156+
createdAt: issue.created_at,
157+
author: issue.user.login,
158+
authorAvatar: issue.user.avatar_url,
159+
number: issue.number
160+
})));
126161
}
127162

128163
// Fetch Releases
129164
if (filters.releases) {
130-
const releasesResponse = await fetch(
131-
`https://api.github.com/repos/${repo}/releases`,
132-
{ headers }
165+
const releasesResponse = await fetchWithRateLimit(
166+
`https://api.github.com/repos/${repo}/releases`
133167
);
134168

135-
if (releasesResponse.ok) {
136-
const releases = await releasesResponse.json();
137-
const newReleases = releases.filter(release => new Date(release.published_at) > since);
138-
activities.push(...newReleases.map(release => ({
139-
type: 'release',
140-
repo,
141-
title: release.name || release.tag_name,
142-
url: release.html_url,
143-
createdAt: release.published_at,
144-
author: release.author.login
145-
})));
146-
}
169+
const releases = await releasesResponse.json();
170+
const newReleases = releases.filter(release => new Date(release.published_at) > since);
171+
activities.push(...newReleases.map(release => ({
172+
id: `release-${repo}-${release.id}`,
173+
type: 'release',
174+
repo,
175+
title: release.name || release.tag_name,
176+
url: release.html_url,
177+
createdAt: release.published_at,
178+
author: release.author.login,
179+
authorAvatar: release.author.avatar_url
180+
})));
147181
}
148182
} catch (error) {
149-
console.error(`Error fetching activity for ${repo}:`, error);
183+
console.error(`Error fetching activity for ${repo}:`, error.message);
184+
await chrome.storage.local.set({
185+
lastError: {
186+
message: error.message,
187+
repo,
188+
timestamp: Date.now()
189+
}
190+
});
191+
throw error;
150192
}
151193

152194
return activities;
153195
}
154196

155197
async function storeActivities(newActivities) {
156198
const { activities = [] } = await chrome.storage.local.get(['activities']);
157-
const updated = [...newActivities, ...activities].slice(0, 100); // Keep last 100
199+
200+
// Merge new activities, avoiding duplicates
201+
const existingIds = new Set(activities.map(a => a.id));
202+
const uniqueNew = newActivities.filter(a => !existingIds.has(a.id));
203+
204+
const updated = [...uniqueNew, ...activities].slice(0, 100);
158205
await chrome.storage.local.set({ activities: updated });
159206
}
160207

161-
function updateBadge(count) {
162-
chrome.action.setBadgeText({ text: count > 0 ? count.toString() : '' });
208+
async function updateBadge(count) {
209+
const { readItems = [] } = await chrome.storage.local.get(['readItems']);
210+
const { activities = [] } = await chrome.storage.local.get(['activities']);
211+
212+
const unreadCount = activities.filter(a => !readItems.includes(a.id)).length;
213+
214+
chrome.action.setBadgeText({ text: unreadCount > 0 ? unreadCount.toString() : '' });
163215
chrome.action.setBadgeBackgroundColor({ color: '#0366d6' });
164216
}
165217

@@ -203,4 +255,44 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
203255
chrome.action.setBadgeText({ text: '' });
204256
sendResponse({ success: true });
205257
}
258+
259+
if (request.action === 'markAsRead') {
260+
chrome.storage.local.get(['readItems'], (result) => {
261+
const readItems = result.readItems || [];
262+
if (!readItems.includes(request.id)) {
263+
readItems.push(request.id);
264+
chrome.storage.local.set({ readItems }, () => {
265+
updateBadge();
266+
sendResponse({ success: true });
267+
});
268+
} else {
269+
sendResponse({ success: true });
270+
}
271+
});
272+
return true;
273+
}
274+
275+
if (request.action === 'markAsUnread') {
276+
chrome.storage.local.get(['readItems'], (result) => {
277+
const readItems = result.readItems || [];
278+
const updated = readItems.filter(id => id !== request.id);
279+
chrome.storage.local.set({ readItems: updated }, () => {
280+
updateBadge();
281+
sendResponse({ success: true });
282+
});
283+
});
284+
return true;
285+
}
286+
287+
if (request.action === 'markAllAsRead') {
288+
chrome.storage.local.get(['activities'], (result) => {
289+
const activities = result.activities || [];
290+
const allIds = activities.map(a => a.id);
291+
chrome.storage.local.set({ readItems: allIds }, () => {
292+
updateBadge();
293+
sendResponse({ success: true });
294+
});
295+
});
296+
return true;
297+
}
206298
});

0 commit comments

Comments
 (0)