[READ-ONLY] a fast, modern browser for the npm registry
0
fork

Configure Feed

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

fix: truncate long package versions

+121 -60
+6 -2
app/components/PackageCard.vue
··· 34 34 > 35 35 {{ result.package.name }} 36 36 </component> 37 - <div class="flex items-center gap-1.5 shrink-0"> 38 - <span v-if="result.package.version" class="font-mono text-xs text-fg-subtle"> 37 + <div class="flex items-center gap-1.5 shrink-0 max-w-32"> 38 + <span 39 + v-if="result.package.version" 40 + class="font-mono text-xs text-fg-subtle truncate" 41 + :title="result.package.version" 42 + > 39 43 v{{ result.package.version }} 40 44 </span> 41 45 <ProvenanceBadge
+6 -19
app/components/PackageDependencies.vue
··· 32 32 return a.name.localeCompare(b.name) 33 33 }) 34 34 }) 35 - 36 - // Check if a version string is "long" (multiple alternatives) 37 - function isLongVersion(version: string): boolean { 38 - return version.length > 20 || version.includes('||') 39 - } 40 - 41 - // Truncate long version strings for display 42 - function truncateVersion(version: string, maxLength = 20): string { 43 - if (version.length <= maxLength) return version 44 - return `${version.slice(0, maxLength)}…` 45 - } 46 35 </script> 47 36 48 37 <template> ··· 77 66 {{ dep }} 78 67 </NuxtLink> 79 68 <span 80 - class="font-mono text-xs text-fg-subtle max-w-[50%] text-right" 81 - :class="isLongVersion(version) ? 'truncate' : 'shrink-0'" 82 - :title="isLongVersion(version) ? version : undefined" 69 + class="font-mono text-xs text-fg-subtle max-w-[50%] text-right truncate" 70 + :title="version" 83 71 > 84 - {{ isLongVersion(version) ? truncateVersion(version) : version }} 72 + {{ version }} 85 73 </span> 86 74 </li> 87 75 </ul> ··· 125 113 </span> 126 114 </div> 127 115 <span 128 - class="font-mono text-xs text-fg-subtle max-w-[40%] text-right" 129 - :class="isLongVersion(peer.version) ? 'truncate' : 'shrink-0'" 130 - :title="isLongVersion(peer.version) ? peer.version : undefined" 116 + class="font-mono text-xs text-fg-subtle max-w-[40%] text-right truncate" 117 + :title="peer.version" 131 118 > 132 - {{ isLongVersion(peer.version) ? truncateVersion(peer.version) : peer.version }} 119 + {{ peer.version }} 133 120 </span> 134 121 </li> 135 122 </ul>
+77 -16
app/components/PackageVersions.vue
··· 16 16 time: Record<string, string> 17 17 }>() 18 18 19 + /** Maximum number of dist-tag rows to show before collapsing into "Other versions" */ 20 + const MAX_VISIBLE_TAGS = 10 21 + 19 22 /** A version with its metadata */ 20 23 interface VersionDisplay { 21 24 version: string ··· 42 45 // Version to tags lookup (supports multiple tags per version) 43 46 const versionToTags = computed(() => buildVersionToTagsMap(props.distTags)) 44 47 45 - // Initial tag rows derived from props (SSR-safe) 48 + // All tag rows derived from props (SSR-safe) 46 49 // Deduplicates so each version appears only once, with all its tags 47 - const initialTagRows = computed(() => { 50 + const allTagRows = computed(() => { 48 51 // Group tags by version with their metadata 49 52 const versionMap = new Map< 50 53 string, ··· 87 90 .sort((a, b) => compareVersions(b.primaryVersion.version, a.primaryVersion.version)) 88 91 }) 89 92 93 + // Visible tag rows (limited to MAX_VISIBLE_TAGS) 94 + const visibleTagRows = computed(() => allTagRows.value.slice(0, MAX_VISIBLE_TAGS)) 95 + 96 + // Hidden tag rows (overflow beyond MAX_VISIBLE_TAGS) - shown in "Other versions" 97 + const hiddenTagRows = computed(() => allTagRows.value.slice(MAX_VISIBLE_TAGS)) 98 + 90 99 // Client-side state for expansion and loaded versions 91 100 const expandedTags = ref<Set<string>>(new Set()) 92 101 const tagVersions = ref<Map<string, VersionDisplay[]>>(new Map()) ··· 159 168 // For each tag, find versions in its channel (same major + same prerelease channel) 160 169 const claimedVersions = new Set<string>() 161 170 162 - for (const row of initialTagRows.value) { 171 + for (const row of allTagRows.value) { 163 172 const tagVersion = distTags[row.tag] 164 173 if (!tagVersion) continue 165 174 ··· 292 301 </script> 293 302 294 303 <template> 295 - <section v-if="initialTagRows.length > 0" aria-labelledby="versions-heading"> 304 + <section v-if="allTagRows.length > 0" aria-labelledby="versions-heading" class="overflow-hidden"> 296 305 <h2 id="versions-heading" class="text-xs text-fg-subtle uppercase tracking-wider mb-3"> 297 306 Versions 298 307 </h2> 299 308 300 - <div class="space-y-0.5"> 301 - <!-- Dist-tag rows --> 302 - <div v-for="row in initialTagRows" :key="row.id"> 309 + <div class="space-y-0.5 min-w-0"> 310 + <!-- Dist-tag rows (limited to MAX_VISIBLE_TAGS) --> 311 + <div v-for="row in visibleTagRows" :key="row.id"> 303 312 <div class="flex items-center gap-2"> 304 313 <!-- Expand button (only if there are more versions to show) --> 305 314 <button ··· 327 336 <NuxtLink 328 337 :to="versionRoute(row.primaryVersion.version)" 329 338 class="font-mono text-sm text-fg-muted hover:text-fg transition-colors duration-200 truncate" 339 + :title="row.primaryVersion.version" 330 340 > 331 341 {{ row.primaryVersion.version }} 332 342 </NuxtLink> ··· 346 356 /> 347 357 </div> 348 358 </div> 349 - <div v-if="row.tags.length" class="flex items-center gap-1 mt-0.5"> 359 + <div v-if="row.tags.length" class="flex items-center gap-1 mt-0.5 flex-wrap"> 350 360 <span 351 361 v-for="tag in row.tags" 352 362 :key="tag" 353 - class="text-[9px] font-semibold text-fg-subtle uppercase tracking-wide" 363 + class="text-[9px] font-semibold text-fg-subtle uppercase tracking-wide truncate max-w-[150px]" 364 + :title="tag" 354 365 > 355 366 {{ tag }} 356 367 </span> ··· 368 379 <NuxtLink 369 380 :to="versionRoute(v.version)" 370 381 class="font-mono text-xs text-fg-subtle hover:text-fg-muted transition-colors duration-200 truncate" 382 + :title="v.version" 371 383 > 372 384 {{ v.version }} 373 385 </NuxtLink> ··· 390 402 <span 391 403 v-for="tag in filterExcludedTags(v.tags, row.tags)" 392 404 :key="tag" 393 - class="text-[8px] font-semibold text-fg-subtle uppercase tracking-wide" 405 + class="text-[8px] font-semibold text-fg-subtle uppercase tracking-wide truncate max-w-[120px]" 406 + :title="tag" 394 407 > 395 408 {{ tag }} 396 409 </span> ··· 417 430 :class="otherVersionsExpanded ? 'i-carbon-chevron-down' : 'i-carbon-chevron-right'" 418 431 /> 419 432 </span> 420 - <span class="text-xs text-fg-muted py-1.5"> Other versions </span> 433 + <span class="text-xs text-fg-muted py-1.5"> 434 + Other versions 435 + <span v-if="hiddenTagRows.length > 0" class="text-fg-subtle"> 436 + ({{ hiddenTagRows.length }} more tagged) 437 + </span> 438 + </span> 421 439 </button> 422 440 423 441 <!-- Expanded other versions --> 424 442 <div v-if="otherVersionsExpanded" class="ml-4 pl-2 border-l border-border space-y-0.5"> 443 + <!-- Hidden tag rows (overflow from visible tags) --> 444 + <div v-for="row in hiddenTagRows" :key="row.id" class="py-1"> 445 + <div class="flex items-center justify-between gap-2"> 446 + <NuxtLink 447 + :to="versionRoute(row.primaryVersion.version)" 448 + class="font-mono text-xs text-fg-muted hover:text-fg transition-colors duration-200 truncate" 449 + :title="row.primaryVersion.version" 450 + > 451 + {{ row.primaryVersion.version }} 452 + </NuxtLink> 453 + <div class="flex items-center gap-2 shrink-0"> 454 + <time 455 + v-if="row.primaryVersion.time" 456 + :datetime="row.primaryVersion.time" 457 + class="text-[10px] text-fg-subtle" 458 + > 459 + {{ formatDate(row.primaryVersion.time) }} 460 + </time> 461 + </div> 462 + </div> 463 + <div v-if="row.tags.length" class="flex items-center gap-1 mt-0.5 flex-wrap"> 464 + <span 465 + v-for="tag in row.tags" 466 + :key="tag" 467 + class="text-[8px] font-semibold text-fg-subtle uppercase tracking-wide truncate max-w-[120px]" 468 + :title="tag" 469 + > 470 + {{ tag }} 471 + </span> 472 + </div> 473 + </div> 474 + 475 + <!-- Major version groups (untagged versions) --> 425 476 <template v-if="otherMajorGroups.length > 0"> 426 477 <div v-for="(group, groupIndex) in otherMajorGroups" :key="group.major"> 427 478 <!-- Major group header --> ··· 430 481 type="button" 431 482 class="w-full text-left py-1" 432 483 :aria-expanded="group.expanded" 484 + :title="group.versions[0]?.version" 433 485 @click="toggleMajorGroup(groupIndex)" 434 486 > 435 487 <div class="flex items-center gap-2"> ··· 437 489 class="w-3 h-3 transition-transform duration-200 text-fg-subtle" 438 490 :class="group.expanded ? 'i-carbon-chevron-down' : 'i-carbon-chevron-right'" 439 491 /> 440 - <span class="font-mono text-xs text-fg-muted"> 492 + <span class="font-mono text-xs text-fg-muted truncate"> 441 493 {{ group.versions[0]?.version }} 442 494 </span> 443 495 </div> 444 - <div v-if="group.versions[0]?.tags?.length" class="flex items-center gap-1 ml-5"> 496 + <div 497 + v-if="group.versions[0]?.tags?.length" 498 + class="flex items-center gap-1 ml-5 flex-wrap" 499 + > 445 500 <span 446 501 v-for="tag in group.versions[0].tags" 447 502 :key="tag" 448 - class="text-[8px] font-semibold text-fg-subtle uppercase tracking-wide" 503 + class="text-[8px] font-semibold text-fg-subtle uppercase tracking-wide truncate max-w-[120px]" 504 + :title="tag" 449 505 > 450 506 {{ tag }} 451 507 </span> ··· 458 514 <NuxtLink 459 515 v-if="group.versions[0]" 460 516 :to="versionRoute(group.versions[0].version)" 461 - class="font-mono text-xs text-fg-muted hover:text-fg transition-colors duration-200" 517 + class="font-mono text-xs text-fg-muted hover:text-fg transition-colors duration-200 truncate" 518 + :title="group.versions[0].version" 462 519 > 463 520 {{ group.versions[0].version }} 464 521 </NuxtLink> ··· 481 538 <NuxtLink 482 539 :to="versionRoute(v.version)" 483 540 class="font-mono text-xs text-fg-subtle hover:text-fg-muted transition-colors duration-200 truncate" 541 + :title="v.version" 484 542 > 485 543 {{ v.version }} 486 544 </NuxtLink> ··· 509 567 </div> 510 568 </div> 511 569 </template> 512 - <div v-else-if="hasLoadedAll" class="py-1 text-xs text-fg-subtle"> 570 + <div 571 + v-else-if="hasLoadedAll && hiddenTagRows.length === 0" 572 + class="py-1 text-xs text-fg-subtle" 573 + > 513 574 All versions are covered by tags above 514 575 </div> 515 576 </div>
+22 -16
app/pages/[...package].vue
··· 249 249 </script> 250 250 251 251 <template> 252 - <main class="container py-8 sm:py-12"> 252 + <main class="container py-8 sm:py-12 overflow-hidden"> 253 253 <PackageSkeleton v-if="status === 'pending'" /> 254 254 255 - <article v-else-if="status === 'success' && pkg" class="animate-fade-in"> 255 + <article v-else-if="status === 'success' && pkg" class="animate-fade-in min-w-0"> 256 256 <!-- Package header --> 257 257 <header class="mb-8 pb-8 border-b border-border"> 258 258 <div class="mb-4"> 259 259 <!-- Package name and version --> 260 - <div class="flex items-center gap-3 mb-2 flex-wrap"> 261 - <h1 class="font-mono text-2xl sm:text-3xl font-medium"> 260 + <div class="flex items-start gap-3 mb-2 flex-wrap min-w-0"> 261 + <h1 262 + class="font-mono text-2xl sm:text-3xl font-medium min-w-0 break-words" 263 + :title="pkg.name" 264 + > 262 265 <NuxtLink 263 266 v-if="orgName" 264 267 :to="{ name: 'org', params: { org: orgName } }" ··· 276 279 " 277 280 :target="hasProvenance(displayVersion) ? '_blank' : undefined" 278 281 :rel="hasProvenance(displayVersion) ? 'noopener noreferrer' : undefined" 279 - class="shrink-0 inline-flex items-center gap-1.5 px-3 py-1 font-mono text-sm bg-bg-muted border border-border rounded-md transition-colors duration-200" 282 + class="inline-flex items-center gap-1.5 px-3 py-1 font-mono text-sm bg-bg-muted border border-border rounded-md transition-colors duration-200 max-w-full shrink-0" 280 283 :class=" 281 284 hasProvenance(displayVersion) 282 285 ? 'hover:border-border-hover cursor-pointer' 283 286 : 'cursor-default' 284 287 " 285 - :title="hasProvenance(displayVersion) ? 'Verified provenance' : undefined" 288 + :title="`v${displayVersion.version}`" 286 289 > 287 - v{{ displayVersion.version }} 290 + <span class="truncate max-w-32 sm:max-w-48"> v{{ displayVersion.version }} </span> 288 291 <span 289 292 v-if=" 290 293 requestedVersion && 291 294 latestVersion && 292 295 displayVersion.version !== latestVersion.version 293 296 " 294 - class="text-fg-subtle" 297 + class="text-fg-subtle shrink-0" 295 298 >(not latest)</span 296 299 > 297 300 <span 298 301 v-if="hasProvenance(displayVersion)" 299 - class="i-solar-shield-check-outline w-4 h-4 text-fg-muted" 302 + class="i-solar-shield-check-outline w-4 h-4 text-fg-muted shrink-0" 300 303 aria-label="Verified provenance" 301 304 /> 302 305 </a> ··· 562 565 </div> 563 566 564 567 <!-- Sidebar --> 565 - <aside class="order-1 lg:order-2 space-y-8"> 568 + <aside class="order-1 lg:order-2 space-y-8 min-w-0 overflow-hidden"> 566 569 <!-- Maintainers (with admin actions when connected) --> 567 570 <PackageMaintainers :package-name="pkg.name" :maintainers="pkg.maintainers" /> 568 571 ··· 603 606 <dl class="space-y-2"> 604 607 <div 605 608 v-if="displayVersion.engines.node" 606 - class="flex items-center justify-between py-1" 609 + class="flex items-center justify-between gap-4 py-1" 607 610 > 608 - <dt class="text-fg-muted text-sm">node</dt> 609 - <dd class="font-mono text-sm text-fg"> 611 + <dt class="text-fg-muted text-sm shrink-0">node</dt> 612 + <dd class="font-mono text-sm text-fg truncate" :title="displayVersion.engines.node"> 610 613 {{ displayVersion.engines.node }} 611 614 </dd> 612 615 </div> 613 - <div v-if="displayVersion.engines.npm" class="flex items-center justify-between py-1"> 614 - <dt class="text-fg-muted text-sm">npm</dt> 615 - <dd class="font-mono text-sm text-fg"> 616 + <div 617 + v-if="displayVersion.engines.npm" 618 + class="flex items-center justify-between gap-4 py-1" 619 + > 620 + <dt class="text-fg-muted text-sm shrink-0">npm</dt> 621 + <dd class="font-mono text-sm text-fg truncate" :title="displayVersion.engines.npm"> 616 622 {{ displayVersion.engines.npm }} 617 623 </dd> 618 624 </div>
+10 -7
app/pages/code/[...path].vue
··· 301 301 <header class="border-b border-border bg-bg sticky top-0 z-10"> 302 302 <div class="container py-4"> 303 303 <!-- Package info and navigation --> 304 - <div class="flex items-center gap-2 mb-3 flex-wrap"> 304 + <div class="flex items-center gap-2 mb-3 flex-wrap min-w-0"> 305 305 <NuxtLink 306 306 :to="packageRoute(version)" 307 - class="font-mono text-lg font-medium hover:text-fg transition-colors" 307 + class="font-mono text-lg font-medium hover:text-fg transition-colors min-w-0 truncate max-w-[60vw] sm:max-w-none" 308 + :title="packageName" 308 309 > 309 310 <span v-if="orgName" class="text-fg-muted">@{{ orgName }}/</span 310 311 >{{ orgName ? packageName.replace(`@${orgName}/`, '') : packageName }} 311 312 </NuxtLink> 312 313 <!-- Version selector --> 313 - <div v-if="version && availableVersions.length > 0" class="relative"> 314 + <div v-if="version && availableVersions.length > 0" class="relative shrink-0"> 314 315 <select 315 316 :value="version" 316 - class="appearance-none pl-2 pr-6 py-0.5 font-mono text-sm bg-bg-muted border border-border rounded cursor-pointer hover:border-border-hover transition-colors" 317 + :title="`v${version}`" 318 + class="appearance-none pl-2 pr-6 py-0.5 font-mono text-sm bg-bg-muted border border-border rounded cursor-pointer hover:border-border-hover transition-colors max-w-32 sm:max-w-48 truncate" 317 319 @change="switchVersion(($event.target as HTMLSelectElement).value)" 318 320 > 319 321 <option v-for="v in availableVersions" :key="v.version" :value="v.version"> ··· 326 328 </div> 327 329 <span 328 330 v-else-if="version" 329 - class="px-2 py-0.5 font-mono text-sm bg-bg-muted border border-border rounded" 331 + class="px-2 py-0.5 font-mono text-sm bg-bg-muted border border-border rounded truncate max-w-32 sm:max-w-48" 332 + :title="`v${version}`" 330 333 > 331 334 v{{ version }} 332 335 </span> 333 - <span class="text-fg-subtle">/</span> 334 - <span class="font-mono text-sm text-fg-muted">code</span> 336 + <span class="text-fg-subtle shrink-0">/</span> 337 + <span class="font-mono text-sm text-fg-muted shrink-0">code</span> 335 338 </div> 336 339 337 340 <!-- Breadcrumb navigation -->