diff --git a/docs/.vitepress/components/UsingWxtSection.vue b/docs/.vitepress/components/UsingWxtSection.vue index 40bc082ad..6cd71f719 100644 --- a/docs/.vitepress/components/UsingWxtSection.vue +++ b/docs/.vitepress/components/UsingWxtSection.vue @@ -1,143 +1,326 @@ diff --git a/docs/.vitepress/composables/useFirefoxAddonDetails.ts b/docs/.vitepress/composables/useFirefoxAddonDetails.ts new file mode 100644 index 000000000..1f1dd40d1 --- /dev/null +++ b/docs/.vitepress/composables/useFirefoxAddonDetails.ts @@ -0,0 +1,100 @@ +import { ref } from 'vue'; + +export interface FirefoxAddonDisplay { + slug: string; + name: string; + iconUrl: string; + weeklyActiveUsers: number; + shortDescription: string; + rating: number | undefined; + firefoxUrl: string; +} + +function pickLocalized( + value: Record | string | undefined, +): string { + if (value == null) return ''; + if (typeof value === 'string') return value; + return value['en-US'] ?? Object.values(value)[0] ?? ''; +} + +export default function useFirefoxAddonDetails(slugs: string[]) { + const data = ref(); + const err = ref(); + const isLoading = ref(true); + + if (slugs.length === 0) { + data.value = []; + isLoading.value = false; + return { + data, + err, + isLoading, + }; + } + + async function fetchOne(slug: string): Promise { + try { + const res = await fetch( + `https://addons.mozilla.org/api/v5/addons/addon/${encodeURIComponent(slug)}/`, + ); + if (!res.ok) { + console.warn( + `[useFirefoxAddonDetails] ${slug}: HTTP ${res.status} — skipping`, + ); + return null; + } + const json = (await res.json()) as { + slug?: string; + name?: Record | string; + summary?: Record | string; + icons?: Record; + average_daily_users?: number; + ratings?: { average?: number; bayesian_average?: number }; + }; + const canonicalSlug = json.slug ?? slug; + const name = pickLocalized(json.name); + const shortDescription = pickLocalized(json.summary); + const iconUrl = + json.icons?.['128'] ?? json.icons?.['64'] ?? json.icons?.['32'] ?? ''; + const rating = + json.ratings?.average ?? json.ratings?.bayesian_average ?? undefined; + const firefoxUrl = `https://addons.mozilla.org/firefox/addon/${canonicalSlug}/?utm_source=wxt.dev`; + return { + slug, + name, + iconUrl, + weeklyActiveUsers: json.average_daily_users ?? 0, + shortDescription, + rating, + firefoxUrl, + } satisfies FirefoxAddonDisplay; + } catch (e) { + console.warn(`[useFirefoxAddonDetails] ${slug}:`, e); + return null; + } + } + + Promise.all(slugs.map((slug) => fetchOne(slug))) + .then((rows) => { + const ok = rows.filter((r): r is FirefoxAddonDisplay => r != null); + data.value = ok; + err.value = + ok.length === 0 && slugs.length > 0 + ? new Error('No Firefox addons could be loaded') + : undefined; + isLoading.value = false; + }) + .catch((error) => { + console.error(error); + data.value = []; + err.value = error; + isLoading.value = false; + }); + + return { + data, + err, + isLoading, + }; +} diff --git a/docs/.vitepress/composables/useListExtensionDetails.ts b/docs/.vitepress/composables/useListExtensionDetails.ts index 119236c07..df4e3af08 100644 --- a/docs/.vitepress/composables/useListExtensionDetails.ts +++ b/docs/.vitepress/composables/useListExtensionDetails.ts @@ -8,6 +8,7 @@ export interface ChromeExtension { shortDescription: string; storeUrl: string; rating: number | undefined; + firefoxUrl?: string; } const operationName = 'WxtDocsUsedBy'; @@ -28,6 +29,16 @@ export default function (ids: string[]) { const err = ref(); const isLoading = ref(true); + if (ids.length === 0) { + data.value = []; + isLoading.value = false; + return { + data, + err, + isLoading, + }; + } + fetch('https://queue.wxt.dev/api', { method: 'POST', body: JSON.stringify({