diff --git a/app/composables/useFacetSelection.ts b/app/composables/useFacetSelection.ts index 31d503119..ecb014b60 100644 --- a/app/composables/useFacetSelection.ts +++ b/app/composables/useFacetSelection.ts @@ -23,56 +23,62 @@ export interface FacetInfoWithLabels extends Omit { export function useFacetSelection(queryParam = 'facets') { const { t } = useI18n() - const facetLabels = computed(() => ({ - downloads: { - label: t(`compare.facets.items.downloads.label`), - description: t(`compare.facets.items.downloads.description`), - }, - packageSize: { - label: t(`compare.facets.items.packageSize.label`), - description: t(`compare.facets.items.packageSize.description`), - }, - installSize: { - label: t(`compare.facets.items.installSize.label`), - description: t(`compare.facets.items.installSize.description`), - }, - moduleFormat: { - label: t(`compare.facets.items.moduleFormat.label`), - description: t(`compare.facets.items.moduleFormat.description`), - }, - types: { - label: t(`compare.facets.items.types.label`), - description: t(`compare.facets.items.types.description`), - }, - engines: { - label: t(`compare.facets.items.engines.label`), - description: t(`compare.facets.items.engines.description`), - }, - vulnerabilities: { - label: t(`compare.facets.items.vulnerabilities.label`), - description: t(`compare.facets.items.vulnerabilities.description`), - }, - lastUpdated: { - label: t(`compare.facets.items.lastUpdated.label`), - description: t(`compare.facets.items.lastUpdated.description`), - }, - license: { - label: t(`compare.facets.items.license.label`), - description: t(`compare.facets.items.license.description`), - }, - dependencies: { - label: t(`compare.facets.items.dependencies.label`), - description: t(`compare.facets.items.dependencies.description`), - }, - totalDependencies: { - label: t(`compare.facets.items.totalDependencies.label`), - description: t(`compare.facets.items.totalDependencies.description`), - }, - deprecated: { - label: t(`compare.facets.items.deprecated.label`), - description: t(`compare.facets.items.deprecated.description`), - }, - })) + const facetLabels = computed( + (): Record => ({ + downloads: { + label: t(`compare.facets.items.downloads.label`), + description: t(`compare.facets.items.downloads.description`), + }, + totalLikes: { + label: t(`compare.facets.items.totalLikes.label`), + description: t(`compare.facets.items.totalLikes.description`), + }, + packageSize: { + label: t(`compare.facets.items.packageSize.label`), + description: t(`compare.facets.items.packageSize.description`), + }, + installSize: { + label: t(`compare.facets.items.installSize.label`), + description: t(`compare.facets.items.installSize.description`), + }, + moduleFormat: { + label: t(`compare.facets.items.moduleFormat.label`), + description: t(`compare.facets.items.moduleFormat.description`), + }, + types: { + label: t(`compare.facets.items.types.label`), + description: t(`compare.facets.items.types.description`), + }, + engines: { + label: t(`compare.facets.items.engines.label`), + description: t(`compare.facets.items.engines.description`), + }, + vulnerabilities: { + label: t(`compare.facets.items.vulnerabilities.label`), + description: t(`compare.facets.items.vulnerabilities.description`), + }, + lastUpdated: { + label: t(`compare.facets.items.lastUpdated.label`), + description: t(`compare.facets.items.lastUpdated.description`), + }, + license: { + label: t(`compare.facets.items.license.label`), + description: t(`compare.facets.items.license.description`), + }, + dependencies: { + label: t(`compare.facets.items.dependencies.label`), + description: t(`compare.facets.items.dependencies.description`), + }, + totalDependencies: { + label: t(`compare.facets.items.totalDependencies.label`), + description: t(`compare.facets.items.totalDependencies.description`), + }, + deprecated: { + label: t(`compare.facets.items.deprecated.label`), + description: t(`compare.facets.items.deprecated.description`), + }, + }), + ) // Helper to build facet info with i18n labels function buildFacetInfo(facet: ComparisonFacet): FacetInfoWithLabels { diff --git a/app/composables/usePackageComparison.ts b/app/composables/usePackageComparison.ts index 72d40c197..4d4217acb 100644 --- a/app/composables/usePackageComparison.ts +++ b/app/composables/usePackageComparison.ts @@ -5,6 +5,7 @@ import type { Packument, VulnerabilityTreeResult, } from '#shared/types' +import type { PackageLikes } from '#shared/types/social' import { encodePackageName } from '#shared/utils/npm' import type { PackageAnalysisResponse } from './usePackageAnalysis' import { isBinaryOnlyPackage } from '#shared/utils/binary-detection' @@ -28,6 +29,8 @@ export const NoDependencyDisplay = { export interface PackageComparisonData { package: ComparisonPackage downloads?: number + /** Total likes from atproto */ + totalLikes?: number /** Package's own unpacked size (from dist.unpackedSize) */ packageSize?: number /** Number of direct dependencies */ @@ -127,7 +130,7 @@ export function usePackageComparison(packageNames: MaybeRefOrGetter) { if (!latestVersion) return null // Fetch fast additional data in parallel (optional - failures are ok) - const [downloads, analysis, vulns] = await Promise.all([ + const [downloads, analysis, vulns, likes] = await Promise.all([ $fetch<{ downloads: number }>( `https://api.npmjs.org/downloads/point/last-week/${encodePackageName(name)}`, ).catch(() => null), @@ -137,8 +140,8 @@ export function usePackageComparison(packageNames: MaybeRefOrGetter) { $fetch( `/api/registry/vulnerabilities/${encodePackageName(name)}`, ).catch(() => null), + $fetch(`/api/social/likes/${name}`).catch(() => null), ]) - const versionData = pkgData.versions[latestVersion] const packageSize = versionData?.dist?.unpackedSize @@ -188,6 +191,7 @@ export function usePackageComparison(packageNames: MaybeRefOrGetter) { deprecated: versionData?.deprecated, }, isBinaryOnly: isBinary, + totalLikes: likes?.totalLikes, } } catch { return null @@ -299,6 +303,7 @@ function createNoDependencyData(): PackageComparisonData { }, isNoDependency: true, downloads: undefined, + totalLikes: undefined, packageSize: 0, directDeps: 0, installSize: { @@ -353,6 +358,14 @@ function computeFacetValue( status: 'neutral', } } + case 'totalLikes': { + if (data.totalLikes === undefined) return null + return { + raw: data.totalLikes, + display: formatCompactNumber(data.totalLikes), + status: 'neutral', + } + } case 'packageSize': { // A size of zero is valid if (data.packageSize == null) return null diff --git a/i18n/locales/en.json b/i18n/locales/en.json index 3673dd9c6..21f141067 100644 --- a/i18n/locales/en.json +++ b/i18n/locales/en.json @@ -931,6 +931,10 @@ "label": "Downloads/wk", "description": "Weekly download count" }, + "totalLikes": { + "label": "Likes", + "description": "Number of likes" + }, "lastUpdated": { "label": "Published", "description": "When this version was published" diff --git a/i18n/locales/fr-FR.json b/i18n/locales/fr-FR.json index f7b379a27..098ddaba1 100644 --- a/i18n/locales/fr-FR.json +++ b/i18n/locales/fr-FR.json @@ -903,6 +903,10 @@ "label": "Téléch./semaine", "description": "Nombre de téléchargements par semaine" }, + "totalLikes": { + "label": "Likes", + "description": "Nombre de likes" + }, "lastUpdated": { "label": "Publié", "description": "Quand cette version a été publiée" diff --git a/lunaria/files/en-GB.json b/lunaria/files/en-GB.json index 0fdda09dc..81c3a43e4 100644 --- a/lunaria/files/en-GB.json +++ b/lunaria/files/en-GB.json @@ -931,6 +931,10 @@ "label": "Downloads/wk", "description": "Weekly download count" }, + "totalLikes": { + "label": "Likes", + "description": "Number of likes" + }, "lastUpdated": { "label": "Published", "description": "When this version was published" diff --git a/lunaria/files/en-US.json b/lunaria/files/en-US.json index 3673dd9c6..21f141067 100644 --- a/lunaria/files/en-US.json +++ b/lunaria/files/en-US.json @@ -931,6 +931,10 @@ "label": "Downloads/wk", "description": "Weekly download count" }, + "totalLikes": { + "label": "Likes", + "description": "Number of likes" + }, "lastUpdated": { "label": "Published", "description": "When this version was published" diff --git a/lunaria/files/fr-FR.json b/lunaria/files/fr-FR.json index f7b379a27..098ddaba1 100644 --- a/lunaria/files/fr-FR.json +++ b/lunaria/files/fr-FR.json @@ -903,6 +903,10 @@ "label": "Téléch./semaine", "description": "Nombre de téléchargements par semaine" }, + "totalLikes": { + "label": "Likes", + "description": "Nombre de likes" + }, "lastUpdated": { "label": "Publié", "description": "Quand cette version a été publiée" diff --git a/shared/types/comparison.ts b/shared/types/comparison.ts index ec3113f0f..9dfe229eb 100644 --- a/shared/types/comparison.ts +++ b/shared/types/comparison.ts @@ -16,6 +16,7 @@ export type ComparisonFacet = | 'dependencies' | 'totalDependencies' | 'deprecated' + | 'totalLikes' /** Facet metadata for UI display */ export interface FacetInfo { @@ -46,6 +47,9 @@ export const FACET_INFO: Record> = { downloads: { category: 'health', }, + totalLikes: { + category: 'health', + }, lastUpdated: { category: 'health', }, diff --git a/test/nuxt/components/compare/FacetSelector.spec.ts b/test/nuxt/components/compare/FacetSelector.spec.ts index 6d98e87ae..9dc2757e6 100644 --- a/test/nuxt/components/compare/FacetSelector.spec.ts +++ b/test/nuxt/components/compare/FacetSelector.spec.ts @@ -31,6 +31,7 @@ const facetLabels: Record = {