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 main 379 lines 15 kB view raw
1<script is:inline> 2 function loadNeoDBCards() { 3 const allContainers = document.querySelectorAll('.neodb-card-container') 4 5 allContainers.forEach((container) => { 6 if (!container.hasAttribute('data-url')) { 7 container.remove() 8 } 9 }) 10 11 const containers = document.querySelectorAll('.neodb-card-container[data-url]') 12 13 if (containers.length === 0) { 14 return 15 } 16 17 const renderError = (elem) => { 18 elem.innerHTML = `<div class="neodb-card error"><p class="neodb-error">🔴&nbsp;&nbsp;Error</p></div>` 19 } 20 21 const renderCard = (elem, data, targetUrl) => { 22 const itemRating = data?.rating && data.rating > 0 ? parseFloat(data.rating) : null 23 const coverUrl = data.cover_image_url || data.cover_url 24 const category = data.category || '' 25 26 const getPreferredTitle = () => { 27 // Special handling for music/album types only 28 if (category === 'music' || category === 'album') { 29 if (Array.isArray(data.localized_title)) { 30 const nonZh = data.localized_title.find((t) => t.lang !== 'zh-cn' && t.text) 31 if (nonZh) return nonZh.text 32 const zh = data.localized_title.find((t) => t.lang === 'zh-cn' && t.text) 33 if (zh) return zh.text 34 } 35 } 36 return data.title || data.name || '' 37 } 38 39 const title = getPreferredTitle() 40 41 const isSquare = category === 'music' || category === 'podcast' 42 const coverClass = isSquare ? 'music' : 'other' 43 const cardHeightClass = isSquare ? 'compact' : 'standard' 44 45 // Convert rating to star percentage with half-star precision: 10 points = 100% (5 stars) 46 const starPercentage = itemRating !== null ? Math.round((itemRating / 10) * 10) * 10 : null 47 48 // Build field info 49 const fieldInfo = [] 50 51 if (category === 'movie' || category === 'tv' || category === 'tv/season') { 52 // Movie/TV: Director, Cast, Genre, Release Year 53 if (data.director && data.director.length > 0) { 54 const directors = Array.isArray(data.director) ? data.director.join(', ') : data.director 55 fieldInfo.push(`: ${directors}`) 56 } 57 if (data.actor && data.actor.length > 0) { 58 const actors = Array.isArray(data.actor) ? data.actor.join(', ') : data.actor 59 fieldInfo.push(`: ${actors}`) 60 } 61 if (data.genre && data.genre.length > 0) { 62 const genres = Array.isArray(data.genre) ? data.genre.join(', ') : data.genre 63 fieldInfo.push(`: ${genres}`) 64 } 65 // Add release year - "Release Date" for movies, "Premiere" for TV 66 if (data.year) { 67 const timeLabel = category === 'movie' ? '上映时间' : '首播' 68 fieldInfo.push(`${timeLabel}: ${data.year}`) 69 } 70 } else if (category === 'music' || category === 'album') { 71 // Music: Artist, Genre, Release Date 72 if (data.artist && data.artist.length > 0) { 73 // Only show the first artist for music/album 74 const artist = Array.isArray(data.artist) ? data.artist[0] : data.artist 75 fieldInfo.push(`: ${artist}`) 76 } 77 if (data.genre && data.genre.length > 0) { 78 const genres = Array.isArray(data.genre) ? data.genre.join(', ') : data.genre 79 fieldInfo.push(`: ${genres}`) 80 } 81 if (data.release_date) { 82 fieldInfo.push(`: ${data.release_date}`) 83 } 84 } else if (category === 'book') { 85 // Book: Author, Publisher 86 if (data.author && data.author.length > 0) { 87 const authors = Array.isArray(data.author) ? data.author.join(', ') : data.author 88 fieldInfo.push(`: ${authors}`) 89 } 90 // Check multiple possible publisher fields 91 const publisher = data.publisher || data.pub_house || data.company 92 if (publisher && (Array.isArray(publisher) ? publisher.length > 0 : publisher)) { 93 const publishers = Array.isArray(publisher) ? publisher.join(', ') : publisher 94 fieldInfo.push(`: ${publishers}`) 95 } 96 } else if (category === 'game') { 97 // Game: Developer, Genre, Release Date 98 if (data.developer && data.developer.length > 0) { 99 const developers = Array.isArray(data.developer) ? data.developer.join(', ') : data.developer 100 fieldInfo.push(`: ${developers}`) 101 } 102 if (data.genre && data.genre.length > 0) { 103 const genres = Array.isArray(data.genre) ? data.genre.join(', ') : data.genre 104 fieldInfo.push(`: ${genres}`) 105 } 106 if (data.release_date) { 107 fieldInfo.push(`: ${data.release_date}`) 108 } 109 } else if (category === 'podcast') { 110 // Podcast: Host, Genre 111 const host = data.host || data.artist || data.creator 112 if (host && (Array.isArray(host) ? host.length > 0 : host)) { 113 const hosts = Array.isArray(host) ? host.join(', ') : host 114 fieldInfo.push(`: ${hosts}`) 115 } 116 if (data.genre && data.genre.length > 0) { 117 const genres = Array.isArray(data.genre) ? data.genre.join(', ') : data.genre 118 fieldInfo.push(`: ${genres}`) 119 } 120 } 121 122 elem.innerHTML = ` 123 <a class="neodb-card ${cardHeightClass}" href="${targetUrl}" target="_blank" rel="noopener noreferrer"> 124 ${coverUrl ? `<img class="neodb-cover ${coverClass}" src="${coverUrl}" alt="${title || ''}" loading="lazy" />` : `<div class="neodb-cover ${coverClass}" style="background: #f3f4f6;"></div>`} 125 <div class="neodb-info"> 126 <div class="neodb-title">${title || ''}</div> 127 ${ 128 starPercentage !== null 129 ? `<div class="rating"> 130 <span class="allstarbg"> 131 <span class="allstarfg" style="width:${starPercentage}%"></span> 132 </span> 133 <span class="rating_nums">${itemRating.toFixed(1)}</span> 134 </div>` 135 : `<div class="rating"> 136 <span class="allstargray"></span> 137 </div>` 138 } 139 ${fieldInfo 140 .map((info) => { 141 if (info.startsWith('<div class="neodb-desc">')) { 142 return info 143 } 144 return `<div class="neodb-field">${info}</div>` 145 }) 146 .join('')} 147 </div> 148 </a> 149 ` 150 } 151 152 // Build fetch promises for parallel execution 153 const fetchPromises = Array.from(containers).map(async (container) => { 154 const url = container.getAttribute('data-url') 155 156 if (!url) return 157 158 let fetchUrl = '' 159 const neodbUrlPattern = /neodb\.social\/(movie|book|music|album|game|tv\/season|tv|podcast)\/([\w-]+)/ 160 const isNeoDbUrl = neodbUrlPattern.test(url) 161 162 if (isNeoDbUrl) { 163 const match = url.match(neodbUrlPattern) 164 let category = match ? match[1] : '' 165 const uuid = match ? match[2] : '' 166 167 if (category === 'tv/season') { 168 category = 'tv/season' 169 } 170 171 if (uuid && category) { 172 fetchUrl = `https://neodb.social/api/${category}/${uuid}` 173 } 174 } else { 175 fetchUrl = `https://neodb.social/api/catalog/fetch?url=${encodeURIComponent(url)}` 176 } 177 178 if (fetchUrl) { 179 try { 180 const res = await fetch(fetchUrl, { 181 mode: 'cors', 182 headers: { 183 Accept: 'application/json' 184 } 185 }) 186 if (!res.ok) throw new Error(`Response ${res.status}`) 187 const json = await res.json() 188 const data = json.data ? json.data : json 189 renderCard(container, data, url) 190 } catch { 191 renderError(container) 192 } 193 } else { 194 renderError(container) 195 } 196 }) 197 198 // Execute all fetches in parallel 199 Promise.allSettled(fetchPromises) 200 } 201 202 loadNeoDBCards() 203 document.addEventListener('astro:page-load', loadNeoDBCards) 204</script> 205 206<style is:inline> 207 .prose .neodb-card { 208 width: 100%; 209 height: 100%; 210 background: var(--astro-code-background); 211 color: var(--text-primary); 212 border-radius: 8px; 213 display: flex; 214 position: relative; 215 text-decoration: none !important; 216 transition: background 0.2s ease-out; 217 margin: 1.25rem 0 1.75rem 0; 218 overflow: hidden; 219 } 220 221 .prose .neodb-card.compact { 222 min-height: 6rem; 223 } 224 225 .prose .neodb-card.standard { 226 min-height: 9rem; 227 } 228 229 .prose .neodb-card:hover { 230 background: color-mix(in srgb, var(--selection) 75%, transparent); 231 } 232 233 .prose .neodb-card.error:hover { 234 background: var(--astro-code-background) !important; 235 cursor: default; 236 } 237 238 .prose .neodb-cover { 239 width: 96px !important; 240 border-radius: 6px; 241 margin: 1rem; 242 } 243 244 .prose .neodb-cover.music { 245 height: 96px !important; /* 1:1 for music */ 246 } 247 248 .prose .neodb-cover.other { 249 height: 144px !important; /* 9:16 for other types */ 250 } 251 252 .prose .neodb-info { 253 height: 100%; 254 display: flex; 255 flex-direction: column; 256 gap: 0.25rem; 257 flex: 1; 258 min-width: 0; 259 overflow: hidden; 260 margin: 1rem 1rem 1rem 0; 261 } 262 263 .prose .neodb-field { 264 font-size: var(--font-size-s); 265 color: var(--text-primary); 266 line-height: 1.4; 267 white-space: nowrap; 268 overflow: hidden; 269 text-overflow: ellipsis; 270 } 271 272 .prose .neodb-artist { 273 font-size: var(--font-size-s); 274 color: var(--text-primary); 275 } 276 277 .prose .no-rating { 278 font-size: var(--font-size-s); 279 color: var(--text-primary); 280 line-height: 1; 281 margin: 0 0 0.25rem 0; 282 } 283 284 .prose .neodb-title { 285 color: var(--text-primary); 286 font-weight: var(--font-weight-bold); 287 line-height: 1.35; 288 margin-bottom: 0.125rem; 289 } 290 291 .prose .neodb-title a { 292 text-decoration: none !important; 293 } 294 295 .prose .neodb-desc { 296 font-size: var(--font-size-s); 297 color: var(--text-primary); 298 margin-top: 0.25rem; 299 display: -webkit-box; 300 -webkit-line-clamp: 2; 301 -webkit-box-orient: vertical; 302 text-overflow: ellipsis; 303 overflow: hidden; 304 word-wrap: break-word; 305 max-width: 100%; 306 } 307 308 .prose .rating { 309 margin: 0 0 0.25rem 0; 310 font-size: var(--font-size-s); 311 line-height: 1; 312 display: flex; 313 align-items: center; 314 } 315 316 .prose .rating .allstarbg { 317 position: relative; 318 color: #f99b01; 319 height: 1rem; 320 width: 5rem; 321 background-size: auto 100%; 322 margin-right: 0.25rem; 323 background-repeat: repeat; 324 background-image: url(data:image/svg+xml;base64,PHN2ZyBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMzIiIGhlaWdodD0iMzIiPjxwYXRoIGQ9Ik05MDguMSAzNTMuMWwtMjUzLjktMzYuOUw1NDAuNyA4Ni4xYy0zLjEtNi4zLTguMi0xMS40LTE0LjUtMTQuNS0xNS44LTcuOC0zNS0xLjMtNDIuOSAxNC41TDM2OS44IDMxNi4ybC0yNTMuOSAzNi45Yy03IDEtMTMuNCA0LjMtMTguMyA5LjMtMTIuMyAxMi43LTEyLjEgMzIuOS42IDQ1LjNsMTgzLjcgMTc5LjEtNDMuNCAyNTIuOWMtMS4yIDYuOS0uMSAxNC4xIDMuMiAyMC4zIDguMiAxNS42IDI3LjYgMjEuNyA0My4yIDEzLjRMNTEyIDc1NGwyMjcuMSAxMTkuNGM2LjIgMy4zIDEzLjQgNC40IDIwLjMgMy4yIDE3LjQtMyAyOS4xLTE5LjUgMjYuMS0zNi45bC00My40LTI1Mi45IDE4My43LTE3OS4xYzUtNC45IDguMy0xMS4zIDkuMy0xOC4zIDIuNy0xNy41LTkuNS0zMy43LTI3LTM2LjN6TTY2NC44IDU2MS42bDM2LjEgMjEwLjNMNTEyIDY3Mi43IDMyMy4xIDc3MmwzNi4xLTIxMC4zLTE1Mi44LTE0OUw0MTcuNiAzODIgNTEyIDE5MC43IDYwNi40IDM4MmwyMTEuMiAzMC43LTE1Mi44IDE0OC45eiIgZmlsbD0iI2Y5OWIwMSIvPjwvc3ZnPg==); 325 } 326 327 .prose .rating .allstarfg { 328 position: absolute; 329 left: 0; 330 color: #f99b01; 331 height: 1rem; 332 overflow: hidden; 333 background-size: auto 100%; 334 background-repeat: repeat; 335 background-image: url(data:image/svg+xml;base64,PHN2ZyBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMzIiIGhlaWdodD0iMzIiPjxwYXRoIGQ9Ik05MDguMSAzNTMuMWwtMjUzLjktMzYuOUw1NDAuNyA4Ni4xYy0zLjEtNi4zLTguMi0xMS40LTE0LjUtMTQuNS0xNS44LTcuOC0zNS0xLjMtNDIuOSAxNC41TDM2OS44IDMxNi4ybC0yNTMuOSAzNi45Yy03IDEtMTMuNCA0LjMtMTguMyA5LjMtMTIuMyAxMi43LTEyLjEgMzIuOS42IDQ1LjNsMTgzLjcgMTc5LjEtNDMuNCAyNTIuOWMtMS4yIDYuOS0uMSAxNC4xIDMuMiAyMC4zIDguMiAxNS42IDI3LjYgMjEuNyA0My4yIDEzLjRMNTEyIDc1NGwyMjcuMSAxMTkuNGM2LjIgMy4zIDEzLjQgNC40IDIwLjMgMy4yIDE3LjQtMyAyOS4xLTE5LjUgMjYuMS0zNi45bC00My40LTI1Mi45IDE4My43LTE3OS4xYzUtNC45IDguMy0xMS4zIDkuMy0xOC4zIDIuNy0xNy41LTkuNS0zMy43LTI3LTM2LjN6IiBmaWxsPSIjZjk5YjAxIi8+PC9zdmc+); 336 } 337 338 .prose .rating_nums { 339 font-size: var(--font-size-s); 340 color: var(--text-primary); 341 } 342 343 .prose .rating .allstargray { 344 position: relative; 345 height: 1rem; 346 width: 5rem; 347 margin-right: 8px; 348 background-color: var(--text-primary); 349 opacity: 0.15; 350 -webkit-mask-image: url(data:image/svg+xml;base64,PHN2ZyBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMzIiIGhlaWdodD0iMzIiPjxwYXRoIGQ9Ik05MDguMSAzNTMuMWwtMjUzLjktMzYuOUw1NDAuNyA4Ni4xYy0zLjEtNi4zLTguMi0xMS40LTE0LjUtMTQuNS0xNS44LTcuOC0zNS0xLjMtNDIuOSAxNC41TDM2OS44IDMxNi4ybC0yNTMuOSAzNi45Yy03IDEtMTMuNCA0LjMtMTguMyA5LjMtMTIuMyAxMi43LTEyLjEgMzIuOS42IDQ1LjNsMTgzLjcgMTc5LjEtNDMuNCAyNTIuOWMtMS4yIDYuOS0uMSAxNC4xIDMuMiAyMC4zIDguMiAxNS42IDI3LjYgMjEuNyA0My4yIDEzLjRMNTEyIDc1NGwyMjcuMSAxMTkuNGM2LjIgMy4zIDEzLjQgNC40IDIwLjMgMy4yIDE3LjQtMyAyOS4xLTE5LjUgMjYuMS0zNi45bC00My40LTI1Mi45IDE4My43LTE3OS4xYzUtNC45IDguMy0xMS4zIDkuMy0xOC4zIDIuNy0xNy41LTkuNS0zMy43LTI3LTM2LjN6IiBmaWxsPSIjZmZmZmZmIi8+PC9zdmc+); 351 mask-image: url(data:image/svg+xml;base64,PHN2ZyBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMzIiIGhlaWdodD0iMzIiPjxwYXRoIGQ9Ik05MDguMSAzNTMuMWwtMjUzLjktMzYuOUw1NDAuNyA4Ni4xYy0zLjEtNi4zLTguMi0xMS40LTE0LjUtMTQuNS0xNS44LTcuOC0zNS0xLjMtNDIuOSAxNC41TDM2OS44IDMxNi4ybC0yNTMuOSAzNi45Yy03IDEtMTMuNCA0LjMtMTguMyA5LjMtMTIuMyAxMi43LTEyLjEgMzIuOS42IDQ1LjNsMTgzLjcgMTc5LjEtNDMuNCAyNTIuOWMtMS4yIDYuOS0uMSAxNC4xIDMuMiAyMC4zIDguMiAxNS42IDI3LjYgMjEuNyA0My4yIDEzLjRMNTEyIDc1NGwyMjcuMSAxMTkuNGM2LjIgMy4zIDEzLjQgNC40IDIwLjMgMy4yIDE3LjQtMyAyOS4xLTE5LjUgMjYuMS0zNi45bC00My40LTI1Mi45IDE4My43LTE3OS4xYzUtNC45IDguMy0xMS4zIDkuMy0xOC4zIDIuNy0xNy41LTkuNS0zMy43LTI3LTM2LjN6IiBmaWxsPSIjZmZmZmZmIi8+PC9zdmc+); 352 -webkit-mask-size: auto 100%; 353 mask-size: auto 100%; 354 -webkit-mask-repeat: repeat; 355 mask-repeat: repeat; 356 } 357 358 /* Skeleton/Loading card styles */ 359 .prose .neodb-loading.music { 360 min-height: 8rem !important; 361 background: var(--astro-code-background); 362 display: flex; 363 align-items: center; 364 justify-content: center; 365 } 366 367 .prose .neodb-loading.other { 368 min-height: 11rem !important; 369 background: var(--astro-code-background); 370 display: flex; 371 align-items: center; 372 justify-content: center; 373 } 374 375 .prose .neodb-error { 376 font-size: var(--font-size-m); 377 margin: 1.5rem; 378 } 379</style>