Repository navigation
Expand file tree
/
Copy pathcommunity.js
More file actions
252 lines (207 loc) · 8.53 KB
/
Copy pathcommunity.js
File metadata and controls
252 lines (207 loc) · 8.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
// ============================================================
// community.js - Find Trending GitHub Repositories
//
// This page lets you search for trending repos on GitHub
// by language, topic, and how recently they were updated.
//
// No login needed — uses GitHub's free public Search API.
// ============================================================
// Wait for the HTML to finish loading before running any code
window.addEventListener('DOMContentLoaded', function() {
// --- Get references to all the elements we'll use ---
var form = document.getElementById('trend-form');
if (!form) return; // safety check: only run on the community page
var langInput = document.getElementById('trend-lang'); // language filter input
var topicInput = document.getElementById('trend-topic'); // topic filter input
var windowInput = document.getElementById('trend-window'); // time window (days) input
var kInput = document.getElementById('trend-k'); // "how many results" input
var statusEl = document.getElementById('trend-status'); // status message area
var resultsEl = document.getElementById('trend-results'); // where cards appear
var resetBtn = document.getElementById('trend-reset'); // reset button
// --- Cache ---
// Instead of fetching the same data over and over, we remember recent results.
// Format: { "cacheKey": { time: timestamp, results: [repo, repo, ...] } }
var searchCache = {};
var CACHE_MINUTES = 5; // how long to keep cached results
// ============================================================
// HELPER FUNCTIONS
// ============================================================
// Show a status message to the user (red text if it's an error)
function setStatus(message, isError) {
if (!statusEl) return;
statusEl.textContent = message;
statusEl.style.color = isError ? '#b91c1c' : '#111827';
}
// Make text safe to put in HTML (prevents code injection attacks)
function safeHtml(text) {
if (!text) return '';
return String(text)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"');
}
// Convert a date string to "X days ago" text
function timeAgo(dateString) {
var seconds = Math.floor((Date.now() - new Date(dateString)) / 1000);
if (seconds < 60) return 'just now';
if (seconds < 3600) return Math.floor(seconds / 60) + ' minutes ago';
if (seconds < 86400) return Math.floor(seconds / 3600) + ' hours ago';
var days = Math.floor(seconds / 86400);
if (days < 30) return days + ' days ago';
return Math.floor(days / 30) + ' months ago';
}
// Calculate how "trending" a repo is.
// The higher the score, the more trending.
// Formula: stars are most important, forks second, recently updated gets a bonus.
function scoreRepo(repo) {
var stars = repo.stargazers_count || 0;
var forks = repo.forks_count || 0;
// Days since the repo was last pushed to
var daysSinceUpdate = (Date.now() - new Date(repo.pushed_at)) / (1000 * 60 * 60 * 24);
// Recency bonus: 0 if updated 14+ days ago, up to 14 if updated today
var recencyBonus = Math.max(0, 14 - daysSinceUpdate);
return (stars * 0.5) + (forks * 0.3) + (recencyBonus * 5);
}
// Build the HTML for one repo card
function buildRepoCard(repo) {
// Optional description line
var description = '';
if (repo.description) {
description = '<div class="repo-desc">' + safeHtml(repo.description) + '</div>';
}
// Optional language tag
var language = '';
if (repo.language) {
language = '<span>' + safeHtml(repo.language) + '</span>';
}
return (
'<div class="repo-item">' +
'<div class="repo-name">' +
'<a href="' + repo.html_url + '" target="_blank" rel="noopener">' +
safeHtml(repo.full_name) +
'</a>' +
'</div>' +
description +
'<div class="repo-meta">' +
'<span>★ ' + (repo.stargazers_count || 0) + '</span>' +
'<span>⑂ ' + (repo.forks_count || 0) + '</span>' +
language +
'<span>Updated ' + timeAgo(repo.pushed_at) + '</span>' +
'</div>' +
'</div>'
);
}
// Render a list of repos onto the page
function showResults(repos) {
if (!repos || repos.length === 0) {
resultsEl.innerHTML = '<div class="muted">No repos matched your filters.</div>';
return;
}
var html = '';
for (var i = 0; i < repos.length; i++) {
html += buildRepoCard(repos[i]);
}
resultsEl.innerHTML = html;
}
// ============================================================
// DATA FUNCTIONS
// ============================================================
// Fetch repos from the GitHub Search API
// "async" means this talks to the internet and waits for a reply
async function fetchRepos(language, topic, days) {
// Build a date string for "X days ago": e.g. "2025-01-01"
var since = new Date(Date.now() - days * 24 * 60 * 60 * 1000)
.toISOString()
.slice(0, 10);
// Build the GitHub search query string
var query = 'pushed:>=' + since;
if (language) query += ' language:' + language;
if (topic) query += ' topic:' + topic;
var url =
'https://api.github.com/search/repositories' +
'?q=' + encodeURIComponent(query) +
'&sort=stars&order=desc&per_page=100';
var response = await fetch(url, {
headers: {
'Accept': 'application/vnd.github+json',
'User-Agent': 'XAYTHEON'
}
});
// Check for GitHub rate limit (403 = too many requests)
if (response.status === 403) {
throw new Error('GitHub rate limit reached. Please wait a few minutes and try again.');
}
if (!response.ok) {
throw new Error('GitHub API error: ' + response.status);
}
var data = await response.json();
return data.items || [];
}
// From a list of repos, pick the top K by trending score
function pickTopK(repos, k) {
// Add a _score property to each repo
for (var i = 0; i < repos.length; i++) {
repos[i]._score = scoreRepo(repos[i]);
}
// Sort by score: highest first
repos.sort(function(a, b) {
return b._score - a._score;
});
// Return only the first K results
return repos.slice(0, k);
}
// ============================================================
// MAIN FUNCTION
// ============================================================
// Fetch and display trending repos based on the current filter settings
async function loadTrending() {
var language = langInput.value.trim();
var topic = topicInput.value.trim();
var days = parseInt(windowInput.value) || 30;
var k = Math.min(20, parseInt(kInput.value) || 10);
// Check if we have recent cached results for these exact filters
var cacheKey = language + '|' + topic + '|' + days + '|' + k;
if (searchCache[cacheKey]) {
var ageInMinutes = (Date.now() - searchCache[cacheKey].time) / (1000 * 60);
if (ageInMinutes < CACHE_MINUTES) {
// Use the cached results instead of fetching again
showResults(searchCache[cacheKey].results);
setStatus('Done (from cache)');
return;
}
}
// No valid cache — fetch fresh data
setStatus('Loading trending repositories…');
resultsEl.innerHTML = '<div class="muted">Loading…</div>';
try {
var repos = await fetchRepos(language, topic, days);
var topRepos = pickTopK(repos, k);
// Save to cache for next time
searchCache[cacheKey] = { time: Date.now(), results: topRepos };
showResults(topRepos);
setStatus('Done');
} catch (error) {
setStatus(error.message || 'Failed to load repos', true);
resultsEl.innerHTML = '<div class="muted">Could not load repositories right now.</div>';
}
}
// ============================================================
// WIRE UP THE PAGE
// ============================================================
// Search when the form is submitted
form.addEventListener('submit', function(event) {
event.preventDefault(); // stop the browser from reloading the page
loadTrending();
});
// Reset filters and reload when the reset button is clicked
resetBtn.addEventListener('click', function() {
langInput.value = '';
topicInput.value = '';
windowInput.value = '30';
kInput.value = '10';
loadTrending();
});
// Load results immediately when the page opens
loadTrending();
}); // end DOMContentLoaded