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

feat(ui): adds social likes to compare page (#940)

authored by

Sukiu and committed by
GitHub
4aceb9cb 6fb61d4c

+96 -52
+56 -50
app/composables/useFacetSelection.ts
··· 23 23 export function useFacetSelection(queryParam = 'facets') { 24 24 const { t } = useI18n() 25 25 26 - const facetLabels = computed(() => ({ 27 - downloads: { 28 - label: t(`compare.facets.items.downloads.label`), 29 - description: t(`compare.facets.items.downloads.description`), 30 - }, 31 - packageSize: { 32 - label: t(`compare.facets.items.packageSize.label`), 33 - description: t(`compare.facets.items.packageSize.description`), 34 - }, 35 - installSize: { 36 - label: t(`compare.facets.items.installSize.label`), 37 - description: t(`compare.facets.items.installSize.description`), 38 - }, 39 - moduleFormat: { 40 - label: t(`compare.facets.items.moduleFormat.label`), 41 - description: t(`compare.facets.items.moduleFormat.description`), 42 - }, 43 - types: { 44 - label: t(`compare.facets.items.types.label`), 45 - description: t(`compare.facets.items.types.description`), 46 - }, 47 - engines: { 48 - label: t(`compare.facets.items.engines.label`), 49 - description: t(`compare.facets.items.engines.description`), 50 - }, 51 - vulnerabilities: { 52 - label: t(`compare.facets.items.vulnerabilities.label`), 53 - description: t(`compare.facets.items.vulnerabilities.description`), 54 - }, 55 - lastUpdated: { 56 - label: t(`compare.facets.items.lastUpdated.label`), 57 - description: t(`compare.facets.items.lastUpdated.description`), 58 - }, 59 - license: { 60 - label: t(`compare.facets.items.license.label`), 61 - description: t(`compare.facets.items.license.description`), 62 - }, 63 - dependencies: { 64 - label: t(`compare.facets.items.dependencies.label`), 65 - description: t(`compare.facets.items.dependencies.description`), 66 - }, 67 - totalDependencies: { 68 - label: t(`compare.facets.items.totalDependencies.label`), 69 - description: t(`compare.facets.items.totalDependencies.description`), 70 - }, 71 - deprecated: { 72 - label: t(`compare.facets.items.deprecated.label`), 73 - description: t(`compare.facets.items.deprecated.description`), 74 - }, 75 - })) 26 + const facetLabels = computed( 27 + (): Record<ComparisonFacet, { label: string; description: string }> => ({ 28 + downloads: { 29 + label: t(`compare.facets.items.downloads.label`), 30 + description: t(`compare.facets.items.downloads.description`), 31 + }, 32 + totalLikes: { 33 + label: t(`compare.facets.items.totalLikes.label`), 34 + description: t(`compare.facets.items.totalLikes.description`), 35 + }, 36 + packageSize: { 37 + label: t(`compare.facets.items.packageSize.label`), 38 + description: t(`compare.facets.items.packageSize.description`), 39 + }, 40 + installSize: { 41 + label: t(`compare.facets.items.installSize.label`), 42 + description: t(`compare.facets.items.installSize.description`), 43 + }, 44 + moduleFormat: { 45 + label: t(`compare.facets.items.moduleFormat.label`), 46 + description: t(`compare.facets.items.moduleFormat.description`), 47 + }, 48 + types: { 49 + label: t(`compare.facets.items.types.label`), 50 + description: t(`compare.facets.items.types.description`), 51 + }, 52 + engines: { 53 + label: t(`compare.facets.items.engines.label`), 54 + description: t(`compare.facets.items.engines.description`), 55 + }, 56 + vulnerabilities: { 57 + label: t(`compare.facets.items.vulnerabilities.label`), 58 + description: t(`compare.facets.items.vulnerabilities.description`), 59 + }, 60 + lastUpdated: { 61 + label: t(`compare.facets.items.lastUpdated.label`), 62 + description: t(`compare.facets.items.lastUpdated.description`), 63 + }, 64 + license: { 65 + label: t(`compare.facets.items.license.label`), 66 + description: t(`compare.facets.items.license.description`), 67 + }, 68 + dependencies: { 69 + label: t(`compare.facets.items.dependencies.label`), 70 + description: t(`compare.facets.items.dependencies.description`), 71 + }, 72 + totalDependencies: { 73 + label: t(`compare.facets.items.totalDependencies.label`), 74 + description: t(`compare.facets.items.totalDependencies.description`), 75 + }, 76 + deprecated: { 77 + label: t(`compare.facets.items.deprecated.label`), 78 + description: t(`compare.facets.items.deprecated.description`), 79 + }, 80 + }), 81 + ) 76 82 77 83 // Helper to build facet info with i18n labels 78 84 function buildFacetInfo(facet: ComparisonFacet): FacetInfoWithLabels {
+15 -2
app/composables/usePackageComparison.ts
··· 5 5 Packument, 6 6 VulnerabilityTreeResult, 7 7 } from '#shared/types' 8 + import type { PackageLikes } from '#shared/types/social' 8 9 import { encodePackageName } from '#shared/utils/npm' 9 10 import type { PackageAnalysisResponse } from './usePackageAnalysis' 10 11 import { isBinaryOnlyPackage } from '#shared/utils/binary-detection' ··· 28 29 export interface PackageComparisonData { 29 30 package: ComparisonPackage 30 31 downloads?: number 32 + /** Total likes from atproto */ 33 + totalLikes?: number 31 34 /** Package's own unpacked size (from dist.unpackedSize) */ 32 35 packageSize?: number 33 36 /** Number of direct dependencies */ ··· 127 130 if (!latestVersion) return null 128 131 129 132 // Fetch fast additional data in parallel (optional - failures are ok) 130 - const [downloads, analysis, vulns] = await Promise.all([ 133 + const [downloads, analysis, vulns, likes] = await Promise.all([ 131 134 $fetch<{ downloads: number }>( 132 135 `https://api.npmjs.org/downloads/point/last-week/${encodePackageName(name)}`, 133 136 ).catch(() => null), ··· 137 140 $fetch<VulnerabilityTreeResult>( 138 141 `/api/registry/vulnerabilities/${encodePackageName(name)}`, 139 142 ).catch(() => null), 143 + $fetch<PackageLikes>(`/api/social/likes/${name}`).catch(() => null), 140 144 ]) 141 - 142 145 const versionData = pkgData.versions[latestVersion] 143 146 const packageSize = versionData?.dist?.unpackedSize 144 147 ··· 188 191 deprecated: versionData?.deprecated, 189 192 }, 190 193 isBinaryOnly: isBinary, 194 + totalLikes: likes?.totalLikes, 191 195 } 192 196 } catch { 193 197 return null ··· 299 303 }, 300 304 isNoDependency: true, 301 305 downloads: undefined, 306 + totalLikes: undefined, 302 307 packageSize: 0, 303 308 directDeps: 0, 304 309 installSize: { ··· 350 355 return { 351 356 raw: data.downloads, 352 357 display: formatCompactNumber(data.downloads), 358 + status: 'neutral', 359 + } 360 + } 361 + case 'totalLikes': { 362 + if (data.totalLikes === undefined) return null 363 + return { 364 + raw: data.totalLikes, 365 + display: formatCompactNumber(data.totalLikes), 353 366 status: 'neutral', 354 367 } 355 368 }
+4
i18n/locales/en.json
··· 931 931 "label": "Downloads/wk", 932 932 "description": "Weekly download count" 933 933 }, 934 + "totalLikes": { 935 + "label": "Likes", 936 + "description": "Number of likes" 937 + }, 934 938 "lastUpdated": { 935 939 "label": "Published", 936 940 "description": "When this version was published"
+4
i18n/locales/fr-FR.json
··· 903 903 "label": "Téléch./semaine", 904 904 "description": "Nombre de téléchargements par semaine" 905 905 }, 906 + "totalLikes": { 907 + "label": "Likes", 908 + "description": "Nombre de likes" 909 + }, 906 910 "lastUpdated": { 907 911 "label": "Publié", 908 912 "description": "Quand cette version a été publiée"
+4
lunaria/files/en-GB.json
··· 931 931 "label": "Downloads/wk", 932 932 "description": "Weekly download count" 933 933 }, 934 + "totalLikes": { 935 + "label": "Likes", 936 + "description": "Number of likes" 937 + }, 934 938 "lastUpdated": { 935 939 "label": "Published", 936 940 "description": "When this version was published"
+4
lunaria/files/en-US.json
··· 931 931 "label": "Downloads/wk", 932 932 "description": "Weekly download count" 933 933 }, 934 + "totalLikes": { 935 + "label": "Likes", 936 + "description": "Number of likes" 937 + }, 934 938 "lastUpdated": { 935 939 "label": "Published", 936 940 "description": "When this version was published"
+4
lunaria/files/fr-FR.json
··· 903 903 "label": "Téléch./semaine", 904 904 "description": "Nombre de téléchargements par semaine" 905 905 }, 906 + "totalLikes": { 907 + "label": "Likes", 908 + "description": "Nombre de likes" 909 + }, 906 910 "lastUpdated": { 907 911 "label": "Publié", 908 912 "description": "Quand cette version a été publiée"
+4
shared/types/comparison.ts
··· 16 16 | 'dependencies' 17 17 | 'totalDependencies' 18 18 | 'deprecated' 19 + | 'totalLikes' 19 20 20 21 /** Facet metadata for UI display */ 21 22 export interface FacetInfo { ··· 44 45 }, 45 46 // Health 46 47 downloads: { 48 + category: 'health', 49 + }, 50 + totalLikes: { 47 51 category: 'health', 48 52 }, 49 53 lastUpdated: {
+1
test/nuxt/components/compare/FacetSelector.spec.ts
··· 31 31 description: 'Total number of dependencies including transitive', 32 32 }, 33 33 deprecated: { label: 'Deprecated?', description: 'Whether the package is deprecated' }, 34 + totalLikes: { label: 'Likes', description: 'Number of likes' }, 34 35 } 35 36 36 37 const categoryLabels: Record<string, string> = {