diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 3ac2ed7e6..38b9ee871 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -82,9 +82,13 @@ export default defineConfig({ { text: 'REPL', link: '/repl/' }, { text: 'ElectricSQL', link: 'https://www.electric-sql.com' }, { - text: 'Star on GitHub', + text: 'GitHub', link: 'https://github.com/electric-sql/pglite', }, + { + text: 'NPM', + link: 'https://www.npmjs.com/package/@electric-sql/pglite', + }, ], sidebar: [ { diff --git a/docs/components/starCount.ts b/docs/components/starCount.ts index 99db10725..7cb820528 100644 --- a/docs/components/starCount.ts +++ b/docs/components/starCount.ts @@ -43,3 +43,25 @@ export async function fetchStarCount(currentCount) { return currentCount || FALLBACK_INITIAL_COUNT } + +const FALLBACK_NPMJS_DWN_INITIAL_COUNT = 6842562 + +export async function downloadCount(currentDownloadCount) { + const ttl = 3600 // 1 hour + return localStorageCache('downloadCount', ttl, async () => { + return await fetchNpmJsDownloadCount(currentDownloadCount) + }) +} + +export async function fetchNpmJsDownloadCount(currentCount) { + const resp = await fetch( + 'https://api.npmjs.org/downloads/point/last-week/@electric-sql/pglite', + ) + + if (resp.ok) { + const data = await resp.json() + return data.downloads + } + + return currentCount || FALLBACK_NPMJS_DWN_INITIAL_COUNT +} diff --git a/docs/downloadCount.data.ts b/docs/downloadCount.data.ts new file mode 100644 index 000000000..baeb2dd18 --- /dev/null +++ b/docs/downloadCount.data.ts @@ -0,0 +1,7 @@ +import { fetchNpmJsDownloadCount } from './components/starCount.ts' + +export default { + async load() { + return await fetchNpmJsDownloadCount() + }, +} diff --git a/docs/index.md b/docs/index.md index 821cb0cc8..4abcf146e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -11,8 +11,11 @@ hero: text: Get Started link: /docs/ - theme: alt - text: Star on GitHub + text: GitHub link: https://github.com/electric-sql/pglite + - theme: alt + text: NPM + link: https://www.npmjs.com/package/@electric-sql/pglite features: - title: Lightweight @@ -28,41 +31,108 @@ import { onMounted } from 'vue' import { defineClientComponent } from 'vitepress' import { VPHomeHero } from 'vitepress/theme' import { data as initialStarCount } from './count.data.ts' -import { starCount } from './components/starCount.ts' +import { data as initialDownloadCount } from './downloadCount.data.ts' +import { starCount, downloadCount } from './components/starCount.ts' const Repl = defineClientComponent(() => { return import('./components/Repl.vue') }) -onMounted(async () => { - if (typeof window !== 'undefined' && document.querySelector) { - const linkEl = document.querySelector('.action a[href="https://github.com/electric-sql/pglite"]') - let countEl = linkEl.querySelector('.count') +function toShortDecimal(x) { + return x.toLocaleString('en-US', { + // add suffixes for thousands, millions, and billions + // the maximum number of decimal places to use + maximumFractionDigits: 1, + // specify the abbreviations to use for the suffixes + notation: 'compact', + compactDisplay: 'short' + }); +} + +async function renderGitHub() { + const linkEl = document.querySelector('.action a[href="https://github.com/electric-sql/pglite"]') + let countEl = linkEl.querySelector('.count') - if (!countEl) { - countEl = document.createElement('span') - countEl.classList.add('count') - countEl.innerText = `( ${initialStarCount.toLocaleString()} )`; - - const icon = document.createElement('span') - icon.classList.add('vpi-social-github') - linkEl.prepend(icon) + if (!countEl) { + countEl = document.createElement('span') + countEl.classList.add('count') + countEl.innerText = `(${toShortDecimal(initialStarCount)})`; + + const icon = document.createElement('span') + icon.classList.add('vpi-social-github') + linkEl.prepend(icon) + + Array.from(linkEl.childNodes) + .filter(n => n.nodeType === Node.TEXT_NODE && n.textContent.trim()) + .forEach(n => { + const span = document.createElement('span') + span.classList.add('action-text') + span.textContent = n.textContent + n.replaceWith(span) + }) + } + + linkEl.append(countEl) + + const count = await starCount(initialStarCount) + + let currentCount = Math.max(count - 15, initialStarCount) + const animateCount = () => { + currentCount += 1; + if (currentCount >= count) { + currentCount = count; + clearInterval(intervalId); } + + countEl.innerText = `(${toShortDecimal(currentCount)})`; + }; + const intervalId = setInterval(animateCount, 64); +} + +async function renderNpmJs() { + const linkEl = document.querySelector('.action a[href="https://www.npmjs.com/package/@electric-sql/pglite"]') + let countEl = linkEl.querySelector('.count') - linkEl.append(countEl) - - const count = await starCount(initialStarCount) - - let currentCount = Math.max(count - 15, initialStarCount) - const animateCount = () => { - currentCount += 1; - if (currentCount >= count) { - currentCount = count; - clearInterval(intervalId); - } - countEl.innerText = `( ${currentCount.toLocaleString()} )`; - }; - const intervalId = setInterval(animateCount, 64); + if (!countEl) { + countEl = document.createElement('span') + countEl.classList.add('count') + countEl.innerText = `(${toShortDecimal(initialDownloadCount)})`; + + const icon = document.createElement('span') + icon.classList.add('vpi-social-npm') + linkEl.prepend(icon) + + Array.from(linkEl.childNodes) + .filter(n => n.nodeType === Node.TEXT_NODE && n.textContent.trim()) + .forEach(n => { + const span = document.createElement('span') + span.classList.add('action-text') + span.textContent = n.textContent + n.replaceWith(span) + }) + } + + linkEl.append(countEl) + + const count = await downloadCount(initialDownloadCount) + + let currentCount = Math.max(count - 15, initialDownloadCount) + const animateCount = () => { + currentCount += 1; + if (currentCount >= count) { + currentCount = count; + clearInterval(intervalId); + } + + countEl.innerText = `(${toShortDecimal(currentCount)}/wk)`; + }; + const intervalId = setInterval(animateCount, 64); +} + +onMounted(async () => { + if (typeof window !== 'undefined' && document.querySelector) { + renderGitHub() + renderNpmJs() } }); @@ -73,16 +143,50 @@ onMounted(async () => { display: flex; align-items: center; } + .actions a[href="https://www.npmjs.com/package/@electric-sql/pglite"] { + display: flex; + align-items: center; + } .actions a[href="https://github.com/electric-sql/pglite"] .vpi-social-github { display: block; - width: 1.42rem; - height: 1.42rem; - margin: 0 0.5rem 0 0; + width: 1.22rem; + height: 1.22rem; + margin: 0 0.3rem 0 0; position: relative; } + .actions a[href="https://www.npmjs.com/package/@electric-sql/pglite"] .vpi-social-npm { + display: block; + width: 1.22rem; + height: 1.22rem; + margin: 0 0.3rem 0 0; + position: relative; + } .actions a[href="https://github.com/electric-sql/pglite"] .count { margin-left: 0.25rem; - min-width: 55px; + min-width: 45px; + } + .actions a[href="https://www.npmjs.com/package/@electric-sql/pglite"] .count { + margin-left: 0.25rem; + min-width: 45px; + } + + @media (max-width: 575px) { + .actions .action-text { + display: none; + } + .actions { + flex-wrap: nowrap !important; + gap: 6px !important; + } + .actions .action .VPButton { + padding: 0 12px !important; + font-size: 13px !important; + } + .actions a[href="https://github.com/electric-sql/pglite"] .count, + .actions a[href="https://www.npmjs.com/package/@electric-sql/pglite"] .count { + margin-left: 0.15rem; + min-width: auto; + } }