|
1 | 1 | (function () { |
2 | | - function createRepoWidget({ |
3 | | - username, // GitHub username |
4 | | - containerId, // ID of the container element |
5 | | - columns = { mobile: 1, tablet: 2, desktop: 3 }, |
6 | | - cardStyles = {}, // Optional custom styles for the card background and container |
7 | | - textStyles = {}, // Optional custom styles for text and icon colors |
8 | | - scaleOnHover = 1.05, // Default scale factor on hover; set to 0 or false to disable |
9 | | - maxRepos = columns.desktop === 1 ? 10 : columns.desktop * 2, // Default maxRepos is double the desktop column count |
10 | | - sortBy = 'stars', // Sorting parameter; options: "stars", "forks", "size", "name", "updated" |
11 | | - }) { |
12 | | - const repoContainer = document.getElementById(containerId); |
13 | | - |
14 | | - const languageColors = { |
15 | | - JavaScript: '#f1e05a', |
16 | | - Python: '#3572A5', |
17 | | - TypeScript: '#2b7489', |
18 | | - Vue: '#41b883', |
19 | | - React: '#61DAFB', |
20 | | - Angular: '#E53238', |
21 | | - Node: '#339933', |
22 | | - Express: '#000000', |
23 | | - Django: '#092E20', |
24 | | - CSS: '#563d7c', |
25 | | - HTML: '#e34c26', |
26 | | - Java: '#b07219', |
27 | | - C: '#555555', |
28 | | - 'C#': '#178600', |
29 | | - 'C++': '#f34b7d', |
30 | | - Go: '#00add8', |
31 | | - Ruby: '#701516', |
32 | | - PHP: '#4F5D95', |
33 | | - Swift: '#ffac45', |
34 | | - Kotlin: '#F18E33', |
35 | | - Rust: '#dea584', |
36 | | - SQL: '#e38c00', |
37 | | - MySQL: '#4479A1', |
38 | | - PostgreSQL: '#336791', |
39 | | - MongoDB: '#47A248', |
40 | | - Docker: '#2496ED', |
41 | | - GitHub: '#181717', |
42 | | - Azure: '#0078D4', |
43 | | - AWS: '#FF9900', |
44 | | - }; |
45 | | - |
46 | | - repoContainer.style.display = 'grid'; |
47 | | - repoContainer.style.gap = '16px'; |
48 | | - |
49 | | - const styles = ` |
| 2 | + function createRepoWidget({ |
| 3 | + username, // GitHub username |
| 4 | + containerId, // ID of the container element |
| 5 | + columns = { mobile: 1, tablet: 2, desktop: 3 }, |
| 6 | + cardStyles = {}, // Optional custom styles for the card background and container |
| 7 | + textStyles = {}, // Optional custom styles for text and icon colors |
| 8 | + scaleOnHover = 1.05, // Default scale factor on hover; set to 0 or false to disable |
| 9 | + maxRepos = columns.desktop === 1 ? 10 : columns.desktop * 2, // Default maxRepos is double the desktop column count |
| 10 | + sortBy = 'stars', // Sorting parameter; options: "stars", "forks", "size", "name", "updated" |
| 11 | + exclude = [], // Array of repository names to exclude |
| 12 | + }) { |
| 13 | + const repoContainer = document.getElementById(containerId); |
| 14 | + |
| 15 | + const languageColors = { |
| 16 | + JavaScript: '#f1e05a', |
| 17 | + Python: '#3572A5', |
| 18 | + TypeScript: '#2b7489', |
| 19 | + Vue: '#41b883', |
| 20 | + React: '#61DAFB', |
| 21 | + Angular: '#E53238', |
| 22 | + Node: '#339933', |
| 23 | + Express: '#000000', |
| 24 | + Django: '#092E20', |
| 25 | + CSS: '#563d7c', |
| 26 | + HTML: '#e34c26', |
| 27 | + Java: '#b07219', |
| 28 | + C: '#555555', |
| 29 | + 'C#': '#178600', |
| 30 | + 'C++': '#f34b7d', |
| 31 | + Go: '#00add8', |
| 32 | + Ruby: '#701516', |
| 33 | + PHP: '#4F5D95', |
| 34 | + Swift: '#ffac45', |
| 35 | + Kotlin: '#F18E33', |
| 36 | + Rust: '#dea584', |
| 37 | + SQL: '#e38c00', |
| 38 | + MySQL: '#4479A1', |
| 39 | + PostgreSQL: '#336791', |
| 40 | + MongoDB: '#47A248', |
| 41 | + Docker: '#2496ED', |
| 42 | + GitHub: '#181717', |
| 43 | + Azure: '#0078D4', |
| 44 | + AWS: '#FF9900', |
| 45 | + }; |
| 46 | + |
| 47 | + repoContainer.style.display = 'grid'; |
| 48 | + repoContainer.style.gap = '16px'; |
| 49 | + |
| 50 | + const styles = ` |
50 | 51 | #${containerId} { |
51 | 52 | grid-template-columns: repeat(${columns.mobile}, 1fr); |
52 | 53 | } |
|
62 | 63 | } |
63 | 64 | `; |
64 | 65 |
|
65 | | - const styleSheet = document.createElement('style'); |
66 | | - styleSheet.innerText = styles; |
67 | | - document.head.appendChild(styleSheet); |
68 | | - |
69 | | - // Cache response for 1 day |
70 | | - const CACHE_EXPIRATION = 24 * 60 * 60 * 1000; |
71 | | - |
72 | | - // async function fetchCommitCount(repo) { |
73 | | - // const response = await fetch( |
74 | | - // `https://api.github.com/repos/${repo.owner.login}/${repo.name}/commits` |
75 | | - // ); |
76 | | - // if (!response.ok) { |
77 | | - // console.error('GitHub API error:', response.statusText); |
78 | | - // return 0; |
79 | | - // } |
80 | | - // const commits = await response.json(); |
81 | | - // return commits.length; |
82 | | - // } |
83 | | - |
84 | | - async function fetchRepos() { |
85 | | - const cacheKey = `repos_${username}`; |
86 | | - const cachedData = localStorage.getItem(cacheKey); |
87 | | - const cachedETag = localStorage.getItem(`${cacheKey}_etag`); |
88 | | - const cacheTimestamp = localStorage.getItem(`${cacheKey}_timestamp`); |
89 | | - const now = Date.now(); |
90 | | - const headers = {}; |
91 | | - |
92 | | - if (cachedETag) { |
93 | | - headers['If-None-Match'] = cachedETag; |
94 | | - } |
95 | | - |
96 | | - const response = await fetch(`https://api.github.com/users/${username}/repos`, { |
97 | | - headers, |
98 | | - }); |
99 | | - |
100 | | - if (response.status === 304 && cachedData) { |
101 | | - return JSON.parse(cachedData); |
102 | | - } |
103 | | - |
104 | | - if (!response.ok) { |
105 | | - console.error('GitHub API error:', response.statusText); |
106 | | - return []; |
107 | | - } |
108 | | - |
109 | | - const repos = await response.json(); |
110 | | - const eTag = response.headers.get('ETag'); |
111 | | - |
112 | | - // for (const repo of repos) { |
113 | | - // repo.commit_count = await fetchCommitCount(repo); |
114 | | - // } |
115 | | - |
116 | | - localStorage.setItem(cacheKey, JSON.stringify(repos)); |
117 | | - localStorage.setItem(`${cacheKey}_timestamp`, now); |
118 | | - if (eTag) { |
119 | | - localStorage.setItem(`${cacheKey}_etag`, eTag); |
120 | | - } |
121 | | - |
122 | | - return repos; |
123 | | - } |
124 | | - |
125 | | - // Sort repositories based on the provided sortBy parameter |
126 | | - function sortRepositories(repos) { |
127 | | - switch (sortBy) { |
128 | | - case 'stars': |
129 | | - return repos.sort((a, b) => b.stargazers_count - a.stargazers_count); |
130 | | - case 'forks': |
131 | | - return repos.sort((a, b) => b.forks_count - a.forks_count); |
132 | | - case 'size': |
133 | | - return repos.sort((a, b) => b.size - a.size); |
134 | | - case 'name': |
135 | | - return repos.sort((a, b) => a.name.localeCompare(b.name)); |
136 | | - case 'updated': |
137 | | - return repos.sort((a, b) => new Date(b.updated_at) - new Date(a.updated_at)); |
138 | | - // case 'commits': |
139 | | - // return repos.sort((a, b) => b.commit_count - a.commit_count); |
140 | | - default: |
141 | | - return repos; |
142 | | - } |
143 | | - } |
144 | | - |
145 | | - async function initializeWidget() { |
146 | | - let repos = await fetchRepos(); |
147 | | - repos = sortRepositories(repos).slice(0, maxRepos); |
148 | | - |
149 | | - const fragment = document.createDocumentFragment(); |
150 | | - |
151 | | - repos.forEach((repo) => { |
152 | | - const card = document.createElement('article'); |
153 | | - card.setAttribute('role', 'region'); |
154 | | - card.setAttribute('aria-labelledby', `repo-title-${repo.name}`); |
155 | | - card.style.cssText = ` |
| 66 | + const styleSheet = document.createElement('style'); |
| 67 | + styleSheet.innerText = styles; |
| 68 | + document.head.appendChild(styleSheet); |
| 69 | + |
| 70 | + // Cache response for 1 day |
| 71 | + const CACHE_EXPIRATION = 24 * 60 * 60 * 1000; |
| 72 | + |
| 73 | + // async function fetchCommitCount(repo) { |
| 74 | + // const response = await fetch( |
| 75 | + // `https://api.github.com/repos/${repo.owner.login}/${repo.name}/commits` |
| 76 | + // ); |
| 77 | + // if (!response.ok) { |
| 78 | + // console.error('GitHub API error:', response.statusText); |
| 79 | + // return 0; |
| 80 | + // } |
| 81 | + // const commits = await response.json(); |
| 82 | + // return commits.length; |
| 83 | + // } |
| 84 | + |
| 85 | + async function fetchRepos() { |
| 86 | + const cacheKey = `repos_${username}`; |
| 87 | + const cachedData = localStorage.getItem(cacheKey); |
| 88 | + const cachedETag = localStorage.getItem(`${cacheKey}_etag`); |
| 89 | + const cacheTimestamp = localStorage.getItem(`${cacheKey}_timestamp`); |
| 90 | + const now = Date.now(); |
| 91 | + const headers = {}; |
| 92 | + |
| 93 | + if (cachedETag) { |
| 94 | + headers['If-None-Match'] = cachedETag; |
| 95 | + } |
| 96 | + |
| 97 | + const response = await fetch(`https://api.github.com/users/${username}/repos`, { |
| 98 | + headers, |
| 99 | + }); |
| 100 | + |
| 101 | + if (response.status === 304 && cachedData) { |
| 102 | + return JSON.parse(cachedData); |
| 103 | + } |
| 104 | + |
| 105 | + if (!response.ok) { |
| 106 | + console.error('GitHub API error:', response.statusText); |
| 107 | + return []; |
| 108 | + } |
| 109 | + |
| 110 | + const repos = await response.json(); |
| 111 | + const eTag = response.headers.get('ETag'); |
| 112 | + |
| 113 | + // for (const repo of repos) { |
| 114 | + // repo.commit_count = await fetchCommitCount(repo); |
| 115 | + // } |
| 116 | + |
| 117 | + localStorage.setItem(cacheKey, JSON.stringify(repos)); |
| 118 | + localStorage.setItem(`${cacheKey}_timestamp`, now); |
| 119 | + if (eTag) { |
| 120 | + localStorage.setItem(`${cacheKey}_etag`, eTag); |
| 121 | + } |
| 122 | + |
| 123 | + return repos; |
| 124 | + } |
| 125 | + |
| 126 | + // Sort repositories based on the provided sortBy parameter |
| 127 | + function sortRepositories(repos) { |
| 128 | + switch (sortBy) { |
| 129 | + case 'stars': |
| 130 | + return repos.sort((a, b) => b.stargazers_count - a.stargazers_count); |
| 131 | + case 'forks': |
| 132 | + return repos.sort((a, b) => b.forks_count - a.forks_count); |
| 133 | + case 'size': |
| 134 | + return repos.sort((a, b) => b.size - a.size); |
| 135 | + case 'name': |
| 136 | + return repos.sort((a, b) => a.name.localeCompare(b.name)); |
| 137 | + case 'updated': |
| 138 | + return repos.sort((a, b) => new Date(b.updated_at) - new Date(a.updated_at)); |
| 139 | + // case 'commits': |
| 140 | + // return repos.sort((a, b) => b.commit_count - a.commit_count); |
| 141 | + default: |
| 142 | + return repos; |
| 143 | + } |
| 144 | + } |
| 145 | + |
| 146 | + async function initializeWidget() { |
| 147 | + let repos = await fetchRepos(); |
| 148 | + |
| 149 | + if (exclude && exclude.length > 0) { |
| 150 | + repos = repos.filter((repo) => !exclude.includes(repo.name)); |
| 151 | + } |
| 152 | + |
| 153 | + repos = sortRepositories(repos).slice(0, maxRepos); |
| 154 | + |
| 155 | + const fragment = document.createDocumentFragment(); |
| 156 | + |
| 157 | + repos.forEach((repo) => { |
| 158 | + const card = document.createElement('article'); |
| 159 | + card.setAttribute('role', 'region'); |
| 160 | + card.setAttribute('aria-labelledby', `repo-title-${repo.name}`); |
| 161 | + card.style.cssText = ` |
156 | 162 | background: #fff; |
157 | 163 | box-shadow: 0 4px 8px rgba(0,0,0,0.1); |
158 | 164 | border-radius: 8px; |
159 | 165 | overflow: hidden; |
160 | 166 | transition: transform 0.3s; |
161 | 167 | `; |
162 | 168 |
|
163 | | - Object.assign(card.style, cardStyles); |
| 169 | + Object.assign(card.style, cardStyles); |
164 | 170 |
|
165 | | - if (scaleOnHover) { |
166 | | - card.onmouseover = () => (card.style.transform = `scale(${scaleOnHover})`); |
167 | | - card.onmouseleave = () => (card.style.transform = 'scale(1)'); |
168 | | - } |
| 171 | + if (scaleOnHover) { |
| 172 | + card.onmouseover = () => (card.style.transform = `scale(${scaleOnHover})`); |
| 173 | + card.onmouseleave = () => (card.style.transform = 'scale(1)'); |
| 174 | + } |
169 | 175 |
|
170 | | - const languageColor = languageColors[repo.language] || '#cccccc'; |
| 176 | + const languageColor = languageColors[repo.language] || '#cccccc'; |
171 | 177 |
|
172 | | - card.innerHTML = ` |
173 | | - <a href="${ |
174 | | - repo.html_url |
175 | | - }" target="_blank" style="text-decoration: none; color: inherit; display: flex; flex-direction: column; height: 100%; padding: 16px;" aria-label="Repository ${ |
176 | | - repo.name |
177 | | - }"> |
| 178 | + card.innerHTML = ` |
| 179 | + <a href="${repo.html_url |
| 180 | + }" target="_blank" style="text-decoration: none; color: inherit; display: flex; flex-direction: column; height: 100%; padding: 16px;" aria-label="Repository ${repo.name |
| 181 | + }"> |
178 | 182 | <div style="flex: 1;"> |
179 | | - <h3 id="repo-title-${ |
180 | | - repo.name |
181 | | - }" style="font-size: 1.25rem; font-weight: bold; color: ${ |
182 | | - textStyles.titleColor || '#333333' |
183 | | - };">${repo.name}</h3> |
184 | | - <p style="color: ${textStyles.descriptionColor || '#666666'}; margin: 8px 0;">${ |
185 | | - repo.description || 'No description provided' |
186 | | - }</p> |
| 183 | + <h3 id="repo-title-${repo.name |
| 184 | + }" style="font-size: 1.25rem; font-weight: bold; color: ${textStyles.titleColor || '#333333' |
| 185 | + };">${repo.name}</h3> |
| 186 | + <p style="color: ${textStyles.descriptionColor || '#666666'}; margin: 8px 0;">${repo.description || 'No description provided' |
| 187 | + }</p> |
187 | 188 | </div> |
188 | 189 | <div style="margin-top: auto;"> |
189 | | - <div style="display: flex; align-items: center; color: ${ |
190 | | - textStyles.iconColor || '#888888' |
191 | | - }; font-size: 0.875rem;"> |
| 190 | + <div style="display: flex; align-items: center; color: ${textStyles.iconColor || '#888888' |
| 191 | + }; font-size: 0.875rem;"> |
192 | 192 | <span style="display: flex; align-items: center; margin-right: 16px;"> |
193 | 193 | <span style="width: 10px; height: 10px; background-color: ${languageColor}; border-radius: 50%; margin-right: 4px;"></span> |
194 | 194 | ${repo.language || 'N/A'} |
195 | 195 | </span> |
196 | 196 | <span style="display: flex; align-items: center; margin-right: 16px;"> |
197 | | - <svg width="16" height="16" fill="${ |
198 | | - textStyles.iconColor || '#888888' |
199 | | - }" style="margin-right: 4px;"><path d="M5 5.372v.878c0 .414.336.75.75.75h4.5a.75.75 0 0 0 .75-.75v-.878a2.25 2.25 0 1 1 1.5 0v.878a2.25 2.25 0 0 1-2.25 2.25h-1.5v2.128a2.251 2.251 0 1 1-1.5 0V8.5h-1.5A2.25 2.25 0 0 1 3.5 6.25v-.878a2.25 2.25 0 1 1 1.5 0ZM5 3.25a.75.75 0 1 0-1.5 0 .75.75 0 0 0 1.5 0Zm6.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Zm-3 8.75a.75.75 0 1 0-1.5 0 .75.75 0 0 0 1.5 0Z"></path></svg> |
| 197 | + <svg width="16" height="16" fill="${textStyles.iconColor || '#888888' |
| 198 | + }" style="margin-right: 4px;"><path d="M5 5.372v.878c0 .414.336.75.75.75h4.5a.75.75 0 0 0 .75-.75v-.878a2.25 2.25 0 1 1 1.5 0v.878a2.25 2.25 0 0 1-2.25 2.25h-1.5v2.128a2.251 2.251 0 1 1-1.5 0V8.5h-1.5A2.25 2.25 0 0 1 3.5 6.25v-.878a2.25 2.25 0 1 1 1.5 0ZM5 3.25a.75.75 0 1 0-1.5 0 .75.75 0 0 0 1.5 0Zm6.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Zm-3 8.75a.75.75 0 1 0-1.5 0 .75.75 0 0 0 1.5 0Z"></path></svg> |
200 | 199 | ${repo.forks_count} |
201 | 200 | </span> |
202 | 201 | <span style="display: flex; align-items: center;"> |
203 | | - <svg width="16" height="16" fill="${ |
204 | | - textStyles.iconColor || '#888888' |
205 | | - }" style="margin-right: 4px;"><path d="M8 .25a.75.75 0 0 1 .673.418l1.882 3.815 4.21.612a.75.75 0 0 1 .416 1.279l-3.046 2.97.719 4.192a.751.751 0 0 1-1.088.791L8 12.347l-3.766 1.98a.75.75 0 0 1-1.088-.79l.72-4.194L.818 6.374a.75.75 0 0 1 .416-1.28l4.21-.611L7.327.668A.75.75 0 0 1 8 .25Z"></path></svg> |
| 202 | + <svg width="16" height="16" fill="${textStyles.iconColor || '#888888' |
| 203 | + }" style="margin-right: 4px;"><path d="M8 .25a.75.75 0 0 1 .673.418l1.882 3.815 4.21.612a.75.75 0 0 1 .416 1.279l-3.046 2.97.719 4.192a.751.751 0 0 1-1.088.791L8 12.347l-3.766 1.98a.75.75 0 0 1-1.088-.79l.72-4.194L.818 6.374a.75.75 0 0 1 .416-1.28l4.21-.611L7.327.668A.75.75 0 0 1 8 .25Z"></path></svg> |
206 | 204 | ${repo.stargazers_count} |
207 | 205 | </span> |
208 | 206 | </div> |
209 | | - <div style="color: ${ |
210 | | - textStyles.sizeColor || '#aaaaaa' |
211 | | - }; font-size: 0.75rem; margin-top: 8px;">Size: ${repo.size} KB</div> |
| 207 | + <div style="color: ${textStyles.sizeColor || '#aaaaaa' |
| 208 | + }; font-size: 0.75rem; margin-top: 8px;">Size: ${repo.size} KB</div> |
212 | 209 | </div> |
213 | 210 | </a> |
214 | 211 | `; |
215 | 212 |
|
216 | | - fragment.appendChild(card); |
217 | | - }); |
| 213 | + fragment.appendChild(card); |
| 214 | + }); |
218 | 215 |
|
219 | | - repoContainer.innerHTML = ''; |
220 | | - repoContainer.appendChild(fragment); |
221 | | - } |
| 216 | + repoContainer.innerHTML = ''; |
| 217 | + repoContainer.appendChild(fragment); |
| 218 | + } |
222 | 219 |
|
223 | | - initializeWidget(); |
224 | | - } |
| 220 | + initializeWidget(); |
| 221 | + } |
225 | 222 |
|
226 | | - window.createRepoWidget = createRepoWidget; |
| 223 | + window.createRepoWidget = createRepoWidget; |
227 | 224 | })(); |
0 commit comments