Fork of Chiri for Astro for my blog
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at a1e6ead60fb44c152fefa3a86179bcf2b84d6249 234 lines 6.1 kB view raw
1<script> 2 import type { GitHubRepoData, CachedRepoData, CardElements } from '@/types' 3 4 let githubCardsObserver: IntersectionObserver | null = null 5 6 // Retrieve cached GitHub repository data from localStorage with expiration check 7 function getCachedData(repo: string): GitHubRepoData | null { 8 try { 9 const cacheKey = `github-repo-${repo}` 10 const cached = localStorage.getItem(cacheKey) 11 12 if (!cached) { 13 return null 14 } 15 16 const parsedCache: CachedRepoData = JSON.parse(cached) 17 const now = Date.now() 18 const oneHour = 60 * 60 * 1000 // 1 hour in milliseconds 19 20 // Check if cache is expired (older than 1 hour) 21 if (now - parsedCache.timestamp > oneHour) { 22 localStorage.removeItem(cacheKey) 23 return null 24 } 25 26 return parsedCache.data 27 } catch (error) { 28 console.warn('Failed to read from cache:', error) 29 return null 30 } 31 } 32 33 // Store GitHub repository data in localStorage with timestamp for cache expiration 34 function setCachedData(repo: string, data: GitHubRepoData): void { 35 try { 36 const cacheKey = `github-repo-${repo}` 37 const cacheData: CachedRepoData = { 38 data, 39 timestamp: Date.now() 40 } 41 localStorage.setItem(cacheKey, JSON.stringify(cacheData)) 42 } catch (error) { 43 console.warn('Failed to save to cache:', error) 44 } 45 } 46 47 // Update the GitHub card UI elements with fetched repository data 48 function updateCardUI(el: CardElements, data: GitHubRepoData) { 49 const numberFormat = new Intl.NumberFormat('en', { 50 notation: 'compact', 51 maximumFractionDigits: 1 52 }) 53 54 if (el.avatar && data.owner?.avatar_url) { 55 el.avatar.style.backgroundImage = `url(${data.owner.avatar_url})` 56 } 57 58 if (el.desc) { 59 el.desc.textContent = data.description ?? 'No description' 60 } 61 62 if (el.stars) { 63 el.stars.textContent = numberFormat.format(data.stargazers_count ?? 0) 64 } 65 66 if (el.forks) { 67 el.forks.textContent = numberFormat.format(data.forks_count ?? 0) 68 } 69 70 if (el.license) { 71 el.license.textContent = data.license?.spdx_id ?? 'No License' 72 } 73 } 74 75 // Load GitHub repository data for a card, using cache if available or fetching from API 76 async function loadCardData(card: HTMLElement) { 77 const repo = card.dataset.repo 78 if (!repo) { 79 return 80 } 81 82 const el = { 83 avatar: card.querySelector('.gc-owner-avatar') as HTMLElement, 84 desc: card.querySelector('.gc-repo-description') as HTMLElement, 85 stars: card.querySelector('.gc-stars-count') as HTMLElement, 86 forks: card.querySelector('.gc-forks-count') as HTMLElement, 87 license: card.querySelector('.gc-license-info') as HTMLElement 88 } as const 89 90 // Try to get cached data first 91 const cachedData = getCachedData(repo) 92 if (cachedData) { 93 updateCardUI(el, cachedData) 94 return 95 } 96 97 // If no cache, fetch from API 98 try { 99 const response = await fetch(`https://api.github.com/repos/${repo}`) 100 101 if (!response.ok) { 102 if (el.desc) { 103 el.desc.textContent = '--' 104 } 105 return 106 } 107 108 const data = await response.json() 109 110 setCachedData(repo, data) 111 112 updateCardUI(el, data) 113 } catch (error) { 114 console.error(`Failed to fetch ${repo}:`, error) 115 if (el.desc) { 116 el.desc.textContent = '--' 117 } 118 } 119 } 120 121 // Set up intersection observer for lazy loading GitHub cards when they enter viewport 122 function lazySetupGithubCards() { 123 githubCardsObserver?.disconnect() 124 125 const githubCards = document.getElementsByClassName('gc-container') 126 if (githubCards.length === 0) { 127 return 128 } 129 130 // Create an intersection observer to lazy load GitHub repo data when cards enter viewport 131 githubCardsObserver = new IntersectionObserver( 132 (entries) => { 133 entries.forEach((entry) => { 134 if (entry.isIntersecting) { 135 loadCardData(entry.target as HTMLElement) 136 githubCardsObserver?.unobserve(entry.target) 137 } 138 }) 139 }, 140 { rootMargin: '200px' } 141 ) 142 143 Array.from(githubCards).forEach((card) => githubCardsObserver?.observe(card)) 144 } 145 146 lazySetupGithubCards() 147 document.addEventListener('astro:page-load', lazySetupGithubCards) 148</script> 149 150<style is:inline> 151 .prose .gc-container { 152 display: block; 153 border: 0.5px solid var(--border); 154 border-radius: 8px; 155 padding: 1rem 1.25rem 0.75rem 1.25rem; 156 margin: 1.25rem 0 1.75rem 0; 157 text-decoration: none; 158 color: inherit; 159 transition: background 0.2s ease-out; 160 background: var(--astro-code-background); 161 } 162 163 .prose .gc-container:hover { 164 background: color-mix(in srgb, var(--selection) 75%, transparent); 165 text-decoration: none; 166 } 167 168 .prose .gc-title-bar { 169 display: flex; 170 align-items: center; 171 gap: 0.75rem; 172 margin-bottom: 0.75rem; 173 } 174 175 .prose .gc-owner-avatar { 176 width: 1.5rem; 177 height: 1.5rem; 178 border-radius: 50%; 179 background-color: var(--border); 180 flex-shrink: 0; 181 } 182 183 .prose .gc-repo-title { 184 font-size: var(--font-size-xl); 185 font-weight: var(--font-weight-regular); 186 color: var(--text-primary); 187 flex-grow: 1; 188 } 189 190 .prose .gc-repo-title strong { 191 font-weight: var(--font-weight-bold); 192 } 193 194 .prose .gc-slash { 195 color: var(--text-secondary); 196 margin: 0 0.375rem; 197 } 198 199 .prose .gc-github-icon { 200 width: 1.5rem; 201 height: 1.5rem; 202 color: var(--text-primary); 203 flex-shrink: 0; 204 } 205 206 .prose .gc-repo-description { 207 font-size: var(--font-size-m); 208 color: var(--text-primary); 209 opacity: 0.6; 210 margin: 0 0 0.75rem 0; 211 line-height: 1.4; 212 } 213 214 .prose .gc-info-bar { 215 display: flex; 216 align-items: center; 217 color: var(--text-primary); 218 opacity: 0.6; 219 gap: 0.35rem; 220 } 221 222 .prose .gc-info-bar .gc-stars-count, 223 .prose .gc-info-bar .gc-forks-count, 224 .prose .gc-info-bar .gc-license-info { 225 margin-right: 0.675rem; 226 font-size: var(--font-size-s); 227 } 228 229 .prose .gc-info-icon { 230 color: var(--text-primary); 231 width: 0.875rem; 232 height: 0.875rem; 233 } 234</style>