1(function () { 2 const CACHE_DURATION = 2 * 60 * 60 * 1000; 3 const CACHE_KEY_PREFIX = 'github-repo-card-'; 4 5 function parseRepoFromHref(href) { 6 if (!href) return null; 7 const match = href.match(/github\.com\/([^/]+)\/([^/?#]+)/i); 8 if (!match) return null; 9 return `${match[1]}/${match[2]}`; 10 } 11 12 function buildCardInnerHTML() { 13 return ` 14 <div class="github-repo-card-inner"> 15 <div class="github-repo-main"> 16 <div class="github-repo-left"> 17 <div class="github-repo-avatar-wrapper"> 18 <div class="github-repo-avatar"></div> 19 </div> 20 <div class="github-repo-title"> 21 <span class="github-repo-owner"></span> 22 <span class="github-repo-slash">/</span> 23 <span class="github-repo-name"></span> 24 </div> 25 </div> 26 <div class="github-repo-logo"> 27 <span class="github-repo-logo-inner"> 28 <svg viewBox="0 0 24 24" aria-hidden="true"> 29 <path 30 d="M12 0C5.37 0 0 5.37 0 12c0 5.3 3.44 9.8 8.21 11.39.6.11.79-.26.79-.58v-2.23c-3.34.73-4.03-1.42-4.03-1.42-.55-1.39-1.34-1.76-1.34-1.76-1.09-.75.08-.73.08-.73 1.21.08 1.84 1.24 1.84 1.24 1.07 1.84 2.81 1.3 3.49.99.11-.78.42-1.3.76-1.6-2.66-.31-5.47-1.33-5.47-5.93 0-1.31.47-2.38 1.24-3.22-.12-.3-.54-1.52.11-3.17 0 0 1.01-.32 3.31 1.23A11.5 11.5 0 0 1 12 4.8c1.02.01 2.04.14 3 .41 2.3-1.55 3.3-1.23 3.3-1.23.66 1.65.24 2.87.12 3.17.77.84 1.23 1.91 1.23 3.22 0 4.61-2.81 5.62-5.49 5.93.43.37.82 1.1.82 2.23v3.29c0 .32.19.7.8.58C20.57 21.8 24 17.3 24 12 24 5.37 18.63 0 12 0Z" 31 /> 32 </svg> 33 </span> 34 </div> 35 </div> 36 <p class="github-repo-description"></p> 37 <div class="github-repo-meta"> 38 <span class="github-repo-meta-item"> 39 <span class="github-repo-meta-icon"> 40 <svg viewBox="0 0 24 24" aria-hidden="true"> 41 <polygon 42 points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" 43 /> 44 </svg> 45 </span> 46 <span class="github-repo-stars"></span> 47 </span> 48 <span class="github-repo-meta-item"> 49 <span class="github-repo-meta-icon"> 50 <svg viewBox="0 0 24 24" aria-hidden="true"> 51 <line x1="6" y1="3" x2="6" y2="15"></line> 52 <circle cx="6" cy="18" r="3"></circle> 53 <circle cx="18" cy="6" r="3"></circle> 54 <path d="M18 9a9 9 0 0 1-9 9"></path> 55 </svg> 56 </span> 57 <span class="github-repo-forks"></span> 58 </span> 59 <span class="github-repo-meta-item"> 60 <span class="github-repo-meta-icon"> 61 <svg viewBox="0 0 24 24" aria-hidden="true"> 62 <path d="M7 20h10" /> 63 <path d="M6 6h12" /> 64 <path d="M6 6 3 12h6Z" /> 65 <path d="m21 12-3-6-3 6Z" /> 66 <path d="M12 6V2" /> 67 <path d="M12 12v8" /> 68 </svg> 69 </span> 70 <span class="github-repo-license"></span> 71 </span> 72 </div> 73 </div> 74 `; 75 } 76 77 function getCachedData(repo) { 78 try { 79 const cacheKey = CACHE_KEY_PREFIX + repo; 80 const cached = localStorage.getItem(cacheKey); 81 const cacheTime = localStorage.getItem(cacheKey + '-time'); 82 83 if (cached && cacheTime) { 84 const age = Date.now() - parseInt(cacheTime, 10); 85 if (age < CACHE_DURATION) { 86 return JSON.parse(cached); 87 } 88 } 89 } catch (e) { 90 // ç¼å读å失败æ¶éé»å¤ç 91 } 92 return null; 93 } 94 95 function setCachedData(repo, data) { 96 try { 97 const cacheKey = CACHE_KEY_PREFIX + repo; 98 localStorage.setItem(cacheKey, JSON.stringify(data)); 99 localStorage.setItem(cacheKey + '-time', Date.now().toString()); 100 } catch (e) { 101 // ç¼ååå ¥å¤±è´¥æ¶éé»å¤çï¼å¦åå¨ç©ºé´ä¸è¶³ï¼ 102 } 103 } 104 105 function clearCachedData(repo) { 106 try { 107 const cacheKey = CACHE_KEY_PREFIX + repo; 108 localStorage.removeItem(cacheKey); 109 localStorage.removeItem(cacheKey + '-time'); 110 } catch (e) { 111 // ç¼åæ¸ ç失败æ¶éé»å¤ç 112 } 113 } 114 115 function hasCustomAttributes(card) { 116 const attrs = ['data-owner', 'data-name', 'data-description', 'data-avatar', 'data-stars', 'data-forks', 'data-license']; 117 return attrs.some(attr => { 118 const val = card.getAttribute(attr); 119 return val !== null && val.trim() !== ''; 120 }); 121 } 122 123 // æ£æ¥æ¯å¦ææå¿ è¦æ°æ®é½æèªå®ä¹å¼ï¼ä¸éè¦ API è°ç¨ï¼ 124 function hasAllCustomData(card, owner, name) { 125 // æ£æ¥ description 126 const hasDescription = card.getAttribute('data-description') !== null && 127 card.getAttribute('data-description').trim() !== ''; 128 129 // æ£æ¥ owner å nameï¼è¦ä¹æ data-owner/data-nameï¼è¦ä¹è½ä» repo è§£æåºææç 130 const customOwner = card.getAttribute('data-owner'); 131 const customName = card.getAttribute('data-name'); 132 const hasOwnerName = (customOwner !== null && customOwner.trim() !== '') || 133 (owner !== null && owner.trim() !== ''); 134 const hasRepoName = (customName !== null && customName.trim() !== '') || 135 (name !== null && name.trim() !== ''); 136 137 // åæ¶è³å°æä¸ä¸ªç»è®¡ä¿¡æ¯ï¼stars æ forksï¼ 138 const hasStats = ['data-stars', 'data-forks'].some(attr => { 139 const val = card.getAttribute(attr); 140 return val !== null && val.trim() !== ''; 141 }); 142 143 return hasDescription && hasOwnerName && hasRepoName && hasStats; 144 } 145 146 function getCustomValue(card, attr) { 147 const val = card.getAttribute(attr); 148 // è¿å null è¡¨ç¤ºä½¿ç¨ API æ°æ® 149 if (val === null || val.trim() === '') return null; 150 return val.trim(); 151 } 152 153 function fillCardData(card, data, owner, name) { 154 const ownerEl = card.querySelector('.github-repo-owner'); 155 const nameEl = card.querySelector('.github-repo-name'); 156 const descEl = card.querySelector('.github-repo-description'); 157 const avatarEl = card.querySelector('.github-repo-avatar'); 158 const starsEl = card.querySelector('.github-repo-stars'); 159 const forksEl = card.querySelector('.github-repo-forks'); 160 const licenseEl = card.querySelector('.github-repo-license'); 161 162 const customOwner = getCustomValue(card, 'data-owner'); 163 const customName = getCustomValue(card, 'data-name'); 164 const customDescription = getCustomValue(card, 'data-description'); 165 const customAvatar = getCustomValue(card, 'data-avatar'); 166 const customStars = getCustomValue(card, 'data-stars'); 167 const customForks = getCustomValue(card, 'data-forks'); 168 const customLicense = getCustomValue(card, 'data-license'); 169 170 // Owner å Nameï¼ä¼å 使ç¨èªå®ä¹ï¼å¦å使ç¨ä» repo è§£æç 171 if (ownerEl) ownerEl.textContent = customOwner || owner || ''; 172 if (nameEl) nameEl.textContent = customName || name || ''; 173 174 // Starsï¼èªå®ä¹ä¼å ï¼å ¶æ¬¡æ¯ API æ°æ® 175 if (starsEl) { 176 if (customStars !== null) { 177 starsEl.textContent = customStars === '0' ? '' : customStars; 178 } else if (typeof data.stargazers_count === 'number') { 179 starsEl.textContent = data.stargazers_count > 0 ? data.stargazers_count.toString() : ''; 180 } 181 } 182 183 // Forks 184 if (forksEl) { 185 if (customForks !== null) { 186 forksEl.textContent = customForks === '0' ? '' : customForks; 187 } else if (typeof data.forks_count === 'number') { 188 forksEl.textContent = data.forks_count > 0 ? data.forks_count.toString() : ''; 189 } 190 } 191 192 // License 193 if (licenseEl) { 194 const licenseText = customLicense !== null 195 ? customLicense 196 : ((data.license && (data.license.spdx_id || data.license.name)) || ''); 197 licenseEl.textContent = licenseText; 198 const metaItem = licenseEl.closest('.github-repo-meta-item'); 199 if (metaItem) { 200 if (!licenseText) { 201 metaItem.classList.add('github-repo-license-empty'); 202 } else { 203 metaItem.classList.remove('github-repo-license-empty'); 204 } 205 } 206 } 207 208 // Description 209 if (descEl) { 210 descEl.textContent = customDescription !== null 211 ? customDescription 212 : (data.description || ''); 213 } 214 215 // Avatar 216 if (avatarEl) { 217 if (customAvatar) { 218 avatarEl.style.backgroundImage = `url(${customAvatar})`; 219 avatarEl.setAttribute('aria-label', 'Custom avatar'); 220 avatarEl.setAttribute('role', 'img'); 221 } else if (data.owner && data.owner.avatar_url) { 222 avatarEl.style.backgroundImage = `url(${data.owner.avatar_url})`; 223 avatarEl.setAttribute('aria-label', data.owner.login || 'Repository owner avatar'); 224 avatarEl.setAttribute('role', 'img'); 225 } 226 } 227 } 228 229 async function fetchRepoData(repo) { 230 const apiUrl = `https://api.github.com/repos/${repo}`; 231 const res = await fetch(apiUrl, { headers: { Accept: 'application/vnd.github+json' } }); 232 if (!res.ok) { 233 throw new Error('GitHub API error: ' + res.status); 234 } 235 return await res.json(); 236 } 237 238 async function initCard(card) { 239 if (card.dataset.githubCardInitialized === 'true') return; 240 card.dataset.githubCardInitialized = 'true'; 241 242 const href = card.getAttribute('href') || ''; 243 const explicitRepo = card.getAttribute('data-repo'); 244 const repo = explicitRepo || parseRepoFromHref(href); 245 if (!repo) return; 246 247 // éªè¯ repo æ ¼å¼ 248 const [owner, name] = repo.split('/'); 249 if (!owner || !name) { 250 console.warn('Invalid repo format:', repo); 251 return; 252 } 253 254 card.innerHTML = buildCardInnerHTML(); 255 256 // æ£æ¥æ¯å¦æææå¿ è¦æ°æ®çèªå®ä¹å¼ 257 const hasAllCustom = hasAllCustomData(card, owner, name); 258 259 // 妿æä»»ä½èªå®ä¹å±æ§ï¼å æ¾ç¤ºèªå®ä¹æ°æ® 260 const hasCustom = hasCustomAttributes(card); 261 if (hasCustom) { 262 fillCardData(card, {}, owner, name); 263 } 264 265 // 妿ææå¿ è¦æ°æ®é½æèªå®ä¹å¼ï¼è·³è¿ API è°ç¨ 266 if (hasAllCustom) { 267 return; 268 } 269 270 // å°è¯è·åç¼åæ°æ® 271 const cachedData = getCachedData(repo); 272 273 if (cachedData) { 274 // æç¼åï¼ç¨ç¼åæ°æ®å¡«å ï¼å¦ææèªå®ä¹å±æ§ï¼èªå®ä¹å¼ä¼è¦çç¼åå¼ï¼ 275 fillCardData(card, cachedData, owner, name); 276 277 // åå°å·æ°æ°æ® 278 fetchRepoData(repo) 279 .then(data => { 280 setCachedData(repo, data); 281 fillCardData(card, data, owner, name); 282 }) 283 .catch(() => { 284 // è·åå¤±è´¥ï¼æ¸ é¤è¿æç¼å 285 clearCachedData(repo); 286 }); 287 } else { 288 // æ ç¼åï¼å°è¯è·å API æ°æ®
289 try { 290 const data = await fetchRepoData(repo); 291 setCachedData(repo, data); 292 fillCardData(card, data, owner, name); 293 } catch (e) { 294 console.warn('å è½½ GitHub ä»åºä¿¡æ¯å¤±è´¥ï¼', repo, e); 295 // API å¤±è´¥ä¸æ²¡æèªå®ä¹å±æ§ï¼æ¾ç¤ºåºæ¬ä¿¡æ¯ 296 if (!hasCustom) { 297 fillCardData(card, {}, owner, name); 298 } 299 } 300 } 301 } 302 303 async function initGitHubRepoCards() { 304 const cards = document.querySelectorAll('.github-repo-card'); 305 if (!cards.length) return; 306 await Promise.all(Array.from(cards).map((card) => initCard(card))); 307 } 308 309 // Render cards that are already in the document immediately. Pages can load 310 // this script next to their cards to reserve the final layout before anchor 311 // scrolling runs; the DOMContentLoaded pass picks up any later cards. 312 initGitHubRepoCards(); 313 if (document.readyState === 'loading') { 314 document.addEventListener('DOMContentLoaded', initGitHubRepoCards); 315 } 316})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.