From 2cb4a87f35c0b11050a163110cac6579806a2417 Mon Sep 17 00:00:00 2001 From: Martin Donadieu Date: Fri, 3 Apr 2026 18:12:12 +0200 Subject: [PATCH 1/8] sync plugin registry and docs --- astro.config.mjs | 56 ++- src/components/doc/PluginsDirectory.astro | 50 ++ src/config/plugins.ts | 32 ++ .../plugins/contentsquare/getting-started.mdx | 66 +++ .../docs/docs/plugins/contentsquare/index.mdx | 40 ++ .../incoming-call-kit/getting-started.mdx | 88 ++++ .../docs/plugins/incoming-call-kit/index.mdx | 40 ++ src/content/docs/docs/plugins/index.mdx | 436 +----------------- .../docs/plugins/supabase/getting-started.mdx | 95 ++++ .../docs/docs/plugins/supabase/index.mdx | 40 ++ .../plugins/transitions/getting-started.mdx | 95 ++++ .../docs/docs/plugins/transitions/index.mdx | 40 ++ src/data/npm-downloads.json | 204 ++++---- src/pages/plugins.astro | 45 +- src/services/pluginDocs.ts | 62 +++ 15 files changed, 863 insertions(+), 526 deletions(-) create mode 100644 src/components/doc/PluginsDirectory.astro create mode 100644 src/content/docs/docs/plugins/contentsquare/getting-started.mdx create mode 100644 src/content/docs/docs/plugins/contentsquare/index.mdx create mode 100644 src/content/docs/docs/plugins/incoming-call-kit/getting-started.mdx create mode 100644 src/content/docs/docs/plugins/incoming-call-kit/index.mdx create mode 100644 src/content/docs/docs/plugins/supabase/getting-started.mdx create mode 100644 src/content/docs/docs/plugins/supabase/index.mdx create mode 100644 src/content/docs/docs/plugins/transitions/getting-started.mdx create mode 100644 src/content/docs/docs/plugins/transitions/index.mdx create mode 100644 src/services/pluginDocs.ts diff --git a/astro.config.mjs b/astro.config.mjs index 037ac0760..f86e571e1 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -24,7 +24,9 @@ const CPU_COUNT = Number.isFinite(BUILD_CONCURRENCY) && BUILD_CONCURRENCY > 0 ? const SRC_DIR = `${fileURLToPath(new URL('./src/', import.meta.url)) .replace(/\\/g, '/') .replace(/\/$/, '')}/` -const PUBLIC_DIR = fileURLToPath(new URL('./public/', import.meta.url)).replace(/\\/g, '/').replace(/\/$/, '') +const PUBLIC_DIR = fileURLToPath(new URL('./public/', import.meta.url)) + .replace(/\\/g, '/') + .replace(/\/$/, '') // Build a map of page paths to their lastmod dates for sitemap function getPageLastModDates() { @@ -301,6 +303,11 @@ export default defineConfig({ description: 'contacts access plugin for reading device contacts', paths: ['docs/plugins/contacts/**'], }, + { + label: 'Plugin Contentsquare', + description: 'Contentsquare mobile analytics and session replay plugin', + paths: ['docs/plugins/contentsquare/**'], + }, { label: 'Plugin Crisp', description: 'Crisp chat integration plugin', @@ -391,6 +398,11 @@ export default defineConfig({ description: 'in-app review prompt plugin for app store ratings', paths: ['docs/plugins/in-app-review/**'], }, + { + label: 'Plugin Incoming Call Kit', + description: 'native incoming call UI plugin with Android notifications and iOS CallKit', + paths: ['docs/plugins/incoming-call-kit/**'], + }, { label: 'Plugin Intent Launcher', description: 'Android intent launcher plugin', @@ -591,6 +603,11 @@ export default defineConfig({ description: 'text-to-speech synthesis plugin', paths: ['docs/plugins/speech-synthesis/**'], }, + { + label: 'Plugin Supabase', + description: 'native Supabase authentication and JWT access plugin', + paths: ['docs/plugins/supabase/**'], + }, { label: 'Plugin SSL Pinning', description: 'certificate pinning plugin for CapacitorHttp requests', @@ -606,6 +623,11 @@ export default defineConfig({ description: 'text selection and interaction plugin', paths: ['docs/plugins/textinteraction/**'], }, + { + label: 'Plugin Transitions', + description: 'framework-agnostic page transition plugin for Capacitor apps', + paths: ['docs/plugins/transitions/**'], + }, { label: 'Plugin Twilio Voice', description: 'Twilio voice calling plugin', @@ -989,6 +1011,14 @@ export default defineConfig({ ], collapsed: true, }, + { + label: 'Contentsquare', + items: [ + { label: 'Overview', link: '/docs/plugins/contentsquare/' }, + { label: 'Getting started', link: '/docs/plugins/contentsquare/getting-started' }, + ], + collapsed: true, + }, { label: 'Crisp', items: [ @@ -1117,6 +1147,14 @@ export default defineConfig({ ], collapsed: true, }, + { + label: 'Incoming Call Kit', + items: [ + { label: 'Overview', link: '/docs/plugins/incoming-call-kit/' }, + { label: 'Getting started', link: '/docs/plugins/incoming-call-kit/getting-started' }, + ], + collapsed: true, + }, { label: 'Intercom', items: [ @@ -1428,6 +1466,14 @@ export default defineConfig({ ], collapsed: true, }, + { + label: 'Supabase', + items: [ + { label: 'Overview', link: '/docs/plugins/supabase/' }, + { label: 'Getting started', link: '/docs/plugins/supabase/getting-started' }, + ], + collapsed: true, + }, { label: 'SSL Pinning', items: [ @@ -1504,6 +1550,14 @@ export default defineConfig({ ], collapsed: true, }, + { + label: 'Transitions', + items: [ + { label: 'Overview', link: '/docs/plugins/transitions/' }, + { label: 'Getting started', link: '/docs/plugins/transitions/getting-started' }, + ], + collapsed: true, + }, { label: 'Twilio Voice', items: [ diff --git a/src/components/doc/PluginsDirectory.astro b/src/components/doc/PluginsDirectory.astro new file mode 100644 index 000000000..f127e58a1 --- /dev/null +++ b/src/components/doc/PluginsDirectory.astro @@ -0,0 +1,50 @@ +--- +import fs from 'node:fs' +import path from 'node:path' +import { CardGrid, LinkCard } from '@astrojs/starlight/components' +import { actions } from '@/config/plugins' +import { defaultLocale } from '@/services/locale' +import { resolvePluginDocsSlug } from '@/services/pluginDocs' +import { getRelativeLocaleUrl } from 'astro:i18n' + +const locale = Astro.locals.locale ?? defaultLocale + +const readDocsDirectory = (dir: string) => { + if (!fs.existsSync(dir)) return [] + return fs + .readdirSync(dir, { withFileTypes: true }) + .filter((entry) => entry.isDirectory()) + .map((entry) => entry.name) +} + +const defaultDocsDirectory = path.join(process.cwd(), 'src', 'content', 'docs', 'docs', 'plugins') +const localizedDocsDirectory = locale === defaultLocale ? defaultDocsDirectory : path.join(process.cwd(), 'src', 'content', 'docs', locale, 'docs', 'plugins') +const defaultDocsSlugs = new Set(readDocsDirectory(defaultDocsDirectory)) +const localizedDocsSlugs = locale === defaultLocale ? defaultDocsSlugs : new Set(readDocsDirectory(localizedDocsDirectory)) +const docsSlugs = new Set([...defaultDocsSlugs, ...localizedDocsSlugs]) +const seenDocsSlugs = new Set() + +const plugins = actions + .map((plugin) => { + const docsSlug = resolvePluginDocsSlug(plugin, docsSlugs) + if (!docsSlug || seenDocsSlugs.has(docsSlug)) return null + + seenDocsSlugs.add(docsSlug) + + const docsLocale = localizedDocsSlugs.has(docsSlug) ? locale : defaultLocale + + return { + description: plugin.description, + href: getRelativeLocaleUrl(docsLocale, `docs/plugins/${docsSlug}/`), + title: plugin.title, + } + }) + .filter((plugin): plugin is NonNullable => Boolean(plugin)) + .sort((left, right) => left.title.localeCompare(right.title)) +--- + +

{plugins.length} documented plugins currently resolve from the live Capgo plugin registry.

+ + + {plugins.map((plugin) => )} + diff --git a/src/config/plugins.ts b/src/config/plugins.ts index 375942fa0..a967f292d 100644 --- a/src/config/plugins.ts +++ b/src/config/plugins.ts @@ -596,6 +596,14 @@ export const actions = [ title: 'Contacts', icon: 'UserGroup', }, + { + name: '@capgo/capacitor-contentsquare', + author: 'github.com/Cap-go', + description: 'Contentsquare analytics, screen tracking, transaction events, and session replay controls for Capacitor 8 apps', + href: 'https://github.com/Cap-go/capacitor-contentsquare/', + title: 'Contentsquare', + icon: 'ChartBar', + }, { name: '@capgo/capacitor-audio-recorder', author: 'github.com/Cap-go', @@ -652,6 +660,14 @@ export const actions = [ title: 'Speech Synthesis', icon: 'SpeakerWave', }, + { + name: '@capgo/capacitor-supabase', + author: 'github.com/Cap-go', + description: 'Native Supabase authentication, JWT session access, and basic database operations for Capacitor apps', + href: 'https://github.com/Cap-go/capacitor-supabase/', + title: 'Supabase', + icon: 'CircleStack', + }, { name: '@capgo/capacitor-ssl-pinning', author: 'github.com/Cap-go', @@ -660,6 +676,14 @@ export const actions = [ title: 'SSL Pinning', icon: 'ShieldCheck', }, + { + name: '@capgo/transitions', + author: 'github.com/Cap-go', + description: 'Framework-agnostic page transitions for Capacitor apps with iOS-style navigation and platform-aware animations', + href: 'https://github.com/Cap-go/capacitor-transitions/', + title: 'Transitions', + icon: 'ArrowsRightLeft', + }, { name: '@capgo/capacitor-printer', author: 'github.com/Cap-go', @@ -876,6 +900,14 @@ export const actions = [ title: 'In App Review', icon: 'Star', }, + { + name: '@capgo/capacitor-incoming-call-kit', + author: 'github.com/Cap-go', + description: 'Native incoming call UI for Android full-screen notifications and iOS CallKit with typed call lifecycle events', + href: 'https://github.com/Cap-go/capacitor-incoming-call-kit/', + title: 'Incoming Call Kit', + icon: 'Phone', + }, { name: '@capgo/capacitor-file-picker', author: 'github.com/Cap-go', diff --git a/src/content/docs/docs/plugins/contentsquare/getting-started.mdx b/src/content/docs/docs/plugins/contentsquare/getting-started.mdx new file mode 100644 index 000000000..98399c1aa --- /dev/null +++ b/src/content/docs/docs/plugins/contentsquare/getting-started.mdx @@ -0,0 +1,66 @@ +--- +title: Getting Started +description: Install and configure the Contentsquare Capacitor plugin for analytics and session replay. +sidebar: + order: 2 +--- + +import { Steps } from '@astrojs/starlight/components'; +import { PackageManagers } from 'starlight-package-managers' + + +1. **Install the plugin** + + +2. **Sync native platforms** + + +3. **Review the upstream product configuration** + - Follow the official [Contentsquare Capacitor guide](https://docs.contentsquare.com/en/capacitor/) for project keys, replay settings, and dashboard setup. + + +## iOS setup + +To enable Contentsquare in-app features on iOS, add the `cs-$(PRODUCT_BUNDLE_IDENTIFIER)` URL scheme to your app and forward incoming URLs with `Contentsquare.handle(url: url)` from your `AppDelegate`, `SceneDelegate`, or SwiftUI `onOpenURL`. + +## Basic usage + +```typescript +import { ContentsquarePlugin, CurrencyCode } from '@capgo/capacitor-contentsquare'; + +await ContentsquarePlugin.optIn(); + +await ContentsquarePlugin.sendScreenName('Home'); + +await ContentsquarePlugin.sendTransaction({ + transactionValue: 10, + transactionCurrency: CurrencyCode.EUR, + transactionId: 'order-42', +}); + +await ContentsquarePlugin.sendDynamicVar({ + dynVarKey: 'store', + dynVarValue: 'rome', +}); +``` + +## Replay privacy controls + +Use the built-in masking helpers to keep sensitive content out of Session Replay: + +```typescript +await ContentsquarePlugin.excludeURLForReplay('/checkout/'); + +await ContentsquarePlugin.setPIISelectors({ + PIISelectors: ['.credit-card', '.email-address'], + Attributes: [{ selector: '[data-secret]', attrName: ['value', 'placeholder'] }], +}); +``` + +## Available API groups + +- Consent control with `optIn()` and `optOut()`. +- Screen, transaction, and dynamic variable tracking. +- Session Replay filtering with URL and selector masking. + +For the full API surface and platform-specific behavior, keep the official Contentsquare docs open alongside this guide. diff --git a/src/content/docs/docs/plugins/contentsquare/index.mdx b/src/content/docs/docs/plugins/contentsquare/index.mdx new file mode 100644 index 000000000..f53970999 --- /dev/null +++ b/src/content/docs/docs/plugins/contentsquare/index.mdx @@ -0,0 +1,40 @@ +--- +title: "@capgo/capacitor-contentsquare" +description: Capacitor 8 wrapper for the official Contentsquare mobile analytics SDKs. +tableOfContents: false +next: false +prev: false +sidebar: + order: 1 + label: "Introduction" +hero: + tagline: Use the official Contentsquare API shape from Capacitor 8 apps with Capgo's updated packaging. + actions: + - text: Get started + link: /docs/plugins/contentsquare/getting-started/ + icon: right-arrow + variant: primary + - text: Github + link: https://github.com/Cap-go/capacitor-contentsquare/ + icon: external + variant: minimal +--- + +import { Card, CardGrid } from '@astrojs/starlight/components'; + +`@capgo/capacitor-contentsquare` keeps the documented Contentsquare Capacitor API while updating the plugin for Capacitor 8, Swift Package Manager, and the Capgo plugin template. + + + + Keep the same `optIn`, `sendScreenName`, transaction, and replay masking calls documented by Contentsquare. + + + Use a package that matches modern Capacitor projects without maintaining your own fork. + + + Apply URL exclusions and selector-based masking for Session Replay from JavaScript. + + + Product-specific setup details still live in the official [Contentsquare Capacitor documentation](https://docs.contentsquare.com/en/capacitor/). + + diff --git a/src/content/docs/docs/plugins/incoming-call-kit/getting-started.mdx b/src/content/docs/docs/plugins/incoming-call-kit/getting-started.mdx new file mode 100644 index 000000000..b23fe0122 --- /dev/null +++ b/src/content/docs/docs/plugins/incoming-call-kit/getting-started.mdx @@ -0,0 +1,88 @@ +--- +title: Getting Started +description: Install and wire the incoming call kit plugin for native call presentation in Capacitor. +sidebar: + order: 2 +--- + +import { Steps } from '@astrojs/starlight/components'; +import { PackageManagers } from 'starlight-package-managers' + + +1. **Install the plugin** + + +2. **Sync native platforms** + + +3. **Decide what triggers the ring UI** + - Use your existing push, SIP, or backend event flow. + - Call `showIncomingCall()` only when your app has enough information to render the incoming call. + + +## Permissions + +```typescript +import { IncomingCallKit } from '@capgo/capacitor-incoming-call-kit'; + +await IncomingCallKit.requestPermissions(); +await IncomingCallKit.requestFullScreenIntentPermission(); +``` + +- Android 13+ may require notification permission. +- Android 14+ may require full-screen intent approval. +- iOS CallKit does not show a runtime permission prompt for the call sheet itself. + +## Show an incoming call + +```typescript +import { IncomingCallKit } from '@capgo/capacitor-incoming-call-kit'; + +await IncomingCallKit.addListener('callAccepted', async ({ call }) => { + console.log('Accepted', call.callId, call.extra); + // Start your real voice or video session here. +}); + +await IncomingCallKit.addListener('callDeclined', ({ call }) => { + console.log('Declined', call.callId); +}); + +await IncomingCallKit.showIncomingCall({ + callId: 'call-42', + callerName: 'Ada Lovelace', + handle: '+39 555 010 020', + appName: 'Capgo Phone', + hasVideo: true, + timeoutMs: 45_000, + extra: { + roomId: 'room-42', + callerUserId: 'user_ada', + }, + android: { + channelId: 'calls', + channelName: 'Incoming Calls', + showFullScreen: true, + }, + ios: { + handleType: 'phoneNumber', + }, +}); +``` + +## End or inspect calls + +```typescript +const active = await IncomingCallKit.getActiveCalls(); +console.log(active.calls); + +await IncomingCallKit.endCall({ + callId: 'call-42', + reason: 'joined-session', +}); +``` + +## Architecture notes + +- Keep FCM, APNs, PushKit, SIP, Twilio, Stream, or Daily integration outside this plugin. +- Use the plugin only for the native incoming-call presentation layer. +- Start media and networking after the user accepts the call. diff --git a/src/content/docs/docs/plugins/incoming-call-kit/index.mdx b/src/content/docs/docs/plugins/incoming-call-kit/index.mdx new file mode 100644 index 000000000..6ade6d9a8 --- /dev/null +++ b/src/content/docs/docs/plugins/incoming-call-kit/index.mdx @@ -0,0 +1,40 @@ +--- +title: "@capgo/capacitor-incoming-call-kit" +description: Native incoming call UI for Capacitor with Android full-screen notifications and iOS CallKit. +tableOfContents: false +next: false +prev: false +sidebar: + order: 1 + label: "Introduction" +hero: + tagline: Present a real native incoming call surface while keeping transport and media ownership in your own stack. + actions: + - text: Get started + link: /docs/plugins/incoming-call-kit/getting-started/ + icon: right-arrow + variant: primary + - text: Github + link: https://github.com/Cap-go/capacitor-incoming-call-kit/ + icon: external + variant: minimal +--- + +import { Card, CardGrid } from '@astrojs/starlight/components'; + +`@capgo/capacitor-incoming-call-kit` is the native ringing layer for apps that already have push delivery and voice or video sessions handled elsewhere. + + + + Show Android incoming call notifications with optional full-screen UI and iOS CallKit sheets from JavaScript. + + + Listen for `callAccepted`, `callDeclined`, `callEnded`, and `callTimedOut` with your original metadata attached. + + + Bring your own FCM, PushKit, SIP, or backend trigger and call the plugin when you are ready to ring. + + + The plugin handles presentation only. Your app still owns the real audio or video session. + + diff --git a/src/content/docs/docs/plugins/index.mdx b/src/content/docs/docs/plugins/index.mdx index f6bb69570..a2919aff5 100644 --- a/src/content/docs/docs/plugins/index.mdx +++ b/src/content/docs/docs/plugins/index.mdx @@ -6,438 +6,26 @@ sidebar: label: "All Plugins" --- -import { Card, CardGrid, LinkCard, Icon } from '@astrojs/starlight/components'; +import { LinkCard } from '@astrojs/starlight/components'; +import PluginsDirectory from '@/components/doc/PluginsDirectory.astro'; -Welcome to the Capgo Capacitor Plugins collection! We maintain a suite of high-quality, well-documented plugins to help you build amazing native experiences in your Capacitor apps. +Welcome to the Capgo Capacitor Plugins collection. This landing page is generated from the live plugin registry plus the docs tree so newly documented plugins show up here automatically. -## ⭐ Capgo Cloud - Live Updates +## Capgo Cloud - Live Updates -The Updater plugin is the foundation of Capgo Cloud, enabling you to: -- 🚀 Deploy updates instantly without app store reviews -- 📱 Update your app's JavaScript, HTML, CSS, and assets -- 🎯 Target specific user segments with channels -- 📊 Monitor update success with built-in analytics -- 🔒 Secure updates with encryption and code signing +The Updater plugin is the foundation of Capgo Cloud and lets you: -## 🚀 Featured Plugins +- Deploy JavaScript, HTML, CSS, and asset changes in minutes. +- Roll out updates to targeted user groups with channels. +- Monitor adoption and failure signals from the Capgo console. +- Secure releases with encryption and code signing. - - - - - - +## Plugin Directory -## 📱 Device & System Plugins - - - - - - - - - - - - -## 🎥 Media & Camera Plugins - - - - - - - - - - - - - -## 🛠️ Utility Plugins - - - - - - - - - - - - - -## 🤖 AI & Advanced Media - - - - - - - - -## 📍 Location & Background Services - - - - - - - -## 📞 Communication & Analytics - - - - - - - - - - - -## 🔐 Security & System - - - - - - - - - - - - - -## 📊 Android-Specific Features - - - - - - - - - -## 📥 Download & Navigation - - - - - - -## 🎯 Why Choose Capgo Plugins? - - - **Well Maintained**: Regular updates and compatibility with latest Capacitor versions - - - **Comprehensive Docs**: Detailed documentation with examples for every plugin - - - **Easy Integration**: Simple APIs that follow Capacitor best practices - - - **TypeScript Support**: Full TypeScript definitions for better development experience - - -## 🚀 Getting Started - -Each plugin follows a similar installation pattern: - -1. **Install the plugin** using your preferred package manager -2. **Sync your project** with `npx cap sync` -3. **Configure platform-specific settings** if needed -4. **Start using the plugin** in your code - -Visit each plugin's documentation for detailed setup instructions and API references. - -## 💡 Need Help? - -- 📖 Check the individual plugin documentation -- 💬 Join our [Discord community](https://discord.capgo.app) -- 🐛 Report issues on the plugin's GitHub repository -- 📧 Contact support for enterprise inquiries - -## 🛠️ Need a Custom Plugin? - -Can't find the exact functionality you need? We can help! - - - -Whether you need: -- 🔧 A completely new plugin built from scratch -- 🔄 Modifications to existing plugins -- 🤝 Expert consulting on native development -- 📱 Platform-specific implementations - -[Get in touch with our team](/consulting/) to discuss your custom plugin needs. - -## 🤝 Contributing - -We welcome contributions! Each plugin is open source and available on GitHub. Feel free to: - -- Report bugs and request features -- Submit pull requests -- Improve documentation -- Share your use cases - ---- - -
- Built with ❤️ by the Capgo team -
+ diff --git a/src/content/docs/docs/plugins/supabase/getting-started.mdx b/src/content/docs/docs/plugins/supabase/getting-started.mdx new file mode 100644 index 000000000..02af22440 --- /dev/null +++ b/src/content/docs/docs/plugins/supabase/getting-started.mdx @@ -0,0 +1,95 @@ +--- +title: Getting Started +description: Install and initialize the native Supabase Capacitor plugin. +sidebar: + order: 2 +--- + +import { Steps } from '@astrojs/starlight/components'; +import { PackageManagers } from 'starlight-package-managers' + + +1. **Install the plugin** + + +2. **Sync native platforms** + + +3. **Confirm Android minimum SDK** + - Set `minSdkVersion = 26` in `android/variables.gradle`. + + +## Initialize the client + +```typescript +import { CapacitorSupabase } from '@capgo/capacitor-supabase'; + +await CapacitorSupabase.initialize({ + supabaseUrl: 'https://your-project.supabase.co', + supabaseKey: 'your-anon-key', +}); +``` + +## Sign in and access the JWT + +```typescript +const { session, user } = await CapacitorSupabase.signInWithPassword({ + email: 'user@example.com', + password: 'password123', +}); + +console.log('User', user?.id); +console.log('JWT', session?.accessToken); +``` + +## Listen for auth changes + +```typescript +const listener = await CapacitorSupabase.addListener('authStateChange', ({ event, session }) => { + console.log('Auth event', event); + console.log('Current JWT', session?.accessToken); +}); + +// Remove it when the page is done. +await listener.remove(); +``` + +## Pair native auth with `@supabase/supabase-js` + +```typescript +import { createClient } from '@supabase/supabase-js'; + +const { session } = await CapacitorSupabase.getSession(); + +const supabase = createClient('https://your-project.supabase.co', 'your-anon-key', { + global: { + headers: { + Authorization: `Bearer ${session?.accessToken}`, + }, + }, +}); + +const { data } = await supabase.from('table').select('*'); +console.log(data); +``` + +## Native database helpers + +```typescript +const { data, error } = await CapacitorSupabase.select({ + table: 'users', + columns: 'id, name, email', + filter: { active: true }, + limit: 10, + orderBy: 'created_at', + ascending: false, +}); + +console.log(data, error); +``` + +## Recommended usage + +- Use this plugin for authentication and session management. +- Keep Realtime, Storage, Edge Functions, and advanced querying in `@supabase/supabase-js`. +- Pass the native JWT into the JavaScript client whenever you need the rest of the Supabase surface area. diff --git a/src/content/docs/docs/plugins/supabase/index.mdx b/src/content/docs/docs/plugins/supabase/index.mdx new file mode 100644 index 000000000..19b65e3fc --- /dev/null +++ b/src/content/docs/docs/plugins/supabase/index.mdx @@ -0,0 +1,40 @@ +--- +title: "@capgo/capacitor-supabase" +description: Native Supabase SDK integration for Capacitor with JWT access and basic database operations. +tableOfContents: false +next: false +prev: false +sidebar: + order: 1 + label: "Introduction" +hero: + tagline: Use native Supabase authentication in Capacitor while still pairing it with `@supabase/supabase-js` where it makes sense. + actions: + - text: Get started + link: /docs/plugins/supabase/getting-started/ + icon: right-arrow + variant: primary + - text: Github + link: https://github.com/Cap-go/capacitor-supabase/ + icon: external + variant: minimal +--- + +import { Card, CardGrid } from '@astrojs/starlight/components'; + +`@capgo/capacitor-supabase` focuses on the parts where native Supabase SDKs give you real value: secure auth flows, session persistence, token refresh, and direct JWT access. + + + + Handle email and password, OAuth, OTP, magic links, and session refresh with native SDK behavior. + + + Read the access token from native auth, then pass it to `@supabase/supabase-js` for the rest of your app. + + + Subscribe to native auth state changes and react to `SIGNED_IN`, `SIGNED_OUT`, or `TOKEN_REFRESHED`. + + + Run simple native select, insert, update, and delete operations when you want them close to the auth layer. + + diff --git a/src/content/docs/docs/plugins/transitions/getting-started.mdx b/src/content/docs/docs/plugins/transitions/getting-started.mdx new file mode 100644 index 000000000..a58315386 --- /dev/null +++ b/src/content/docs/docs/plugins/transitions/getting-started.mdx @@ -0,0 +1,95 @@ +--- +title: Getting Started +description: Install and initialize the transitions package in a Capacitor app. +sidebar: + order: 2 +--- + +import { Steps } from '@astrojs/starlight/components'; +import { PackageManagers } from 'starlight-package-managers' + + +1. **Install the package** + + +2. **Import the runtime once at app startup** + - Import `@capgo/transitions`. + - Call `initTransitions({ platform: 'auto' })` from the binding that matches your stack. + +3. **Wrap your routed pages** + - Put your pages inside `cap-router-outlet`. + - Use `cap-page`, `cap-header`, `cap-content`, and `cap-footer` for coordinated transitions. + + +## Vanilla setup + +```html + + + +

My Page

+
+ +

Page content here

+
+ + + +
+
+``` + +```typescript +import '@capgo/transitions'; + +const outlet = document.querySelector('cap-router-outlet'); +outlet?.push(newPageElement); +``` + +## React setup + +```tsx +import { useEffect, useRef } from 'react'; +import { initTransitions, setDirection, setupPage, setupRouterOutlet } from '@capgo/transitions/react'; +import '@capgo/transitions'; + +initTransitions({ platform: 'auto' }); + +function HomePage() { + const pageRef = useRef(null); + + useEffect(() => { + if (!pageRef.current) return; + return setupPage(pageRef.current, { + onDidEnter: () => console.log('entered'), + }); + }, []); + + return ( + + +

Home

+
+ + + +
+ ); +} +``` + +## Core primitives + +- `initTransitions()` bootstraps the transition controller. +- `setDirection()` defines the next navigation direction. +- `setupRouterOutlet()` attaches lifecycle behavior to the outlet. +- `setupPage()` registers enter and leave hooks for a page element. + +Use the binding that matches your framework and keep your existing router. The package does not require a native sync step because it runs in the web layer of your Capacitor app. diff --git a/src/content/docs/docs/plugins/transitions/index.mdx b/src/content/docs/docs/plugins/transitions/index.mdx new file mode 100644 index 000000000..07624f205 --- /dev/null +++ b/src/content/docs/docs/plugins/transitions/index.mdx @@ -0,0 +1,40 @@ +--- +title: "@capgo/transitions" +description: Framework-agnostic page transitions for Capacitor apps with iOS-style navigation and platform-aware animations. +tableOfContents: false +next: false +prev: false +sidebar: + order: 1 + label: "Introduction" +hero: + tagline: Add native-feeling page transitions to Capacitor apps without adopting a full design system. + actions: + - text: Get started + link: /docs/plugins/transitions/getting-started/ + icon: right-arrow + variant: primary + - text: Github + link: https://github.com/Cap-go/capacitor-transitions/ + icon: external + variant: minimal +--- + +import { Card, CardGrid } from '@astrojs/starlight/components'; + +`@capgo/transitions` provides route transitions only. It does not force Ionic styles or framework-specific layout choices. + + + + Use the same package with vanilla web components or framework bindings for React, Vue, Angular, Svelte, and Solid. + + + Get iOS and Android style transitions with Web Animations API and progressive View Transitions support. + + + Coordinate transitions across headers, content, and footers with `cap-router-outlet` and `cap-page`. + + + Bring your own styles and navigation structure. The library focuses on transition behavior, not UI chrome. + + diff --git a/src/data/npm-downloads.json b/src/data/npm-downloads.json index e1cc472fa..770c71ba0 100644 --- a/src/data/npm-downloads.json +++ b/src/data/npm-downloads.json @@ -1,97 +1,119 @@ { - "@capgo/native-market": 11743, - "@capgo/capacitor-native-biometric": 251793, - "@capgo/camera-preview": 20820, - "@capgo/capacitor-updater": 432152, - "@capgo/capacitor-uploader": 6754, - "@revenuecat/purchases-capacitor": 210671, - "@capgo/capacitor-flash": 12681, - "@capgo/capacitor-screen-recorder": 11588, - "@capgo/capacitor-crisp": 11452, - "@capgo/nativegeocoder": 21442, - "@capgo/inappbrowser": 248280, - "@capgo/capacitor-mute": 39514, - "@capgo/native-audio": 27359, - "@capgo/capacitor-shake": 63911, - "@capgo/capacitor-navigation-bar": 79976, - "@capgo/ivs-player": 2920, - "@capgo/home-indicator": 22824, - "@capgo/native-purchases": 19165, - "@capgo/capacitor-data-storage-sqlite": 5996, - "@capgo/capacitor-android-usagestatsmanager": 4737, + "@capgo/native-market": 12846, + "@capgo/capacitor-native-biometric": 480444, + "@capgo/camera-preview": 36507, + "@capgo/capacitor-updater": 690611, + "@capgo/capacitor-uploader": 12666, + "@revenuecat/purchases-capacitor": 684337, + "@capgo/capacitor-flash": 36456, + "@capgo/capacitor-screen-recorder": 16174, + "@capgo/capacitor-crisp": 12025, + "@capgo/capacitor-intercom": 422, + "@capgo/capacitor-appsflyer": 184, + "@capgo/nativegeocoder": 20322, + "@capgo/inappbrowser": 350683, + "@capgo/capacitor-mqtt": 55, + "@capgo/capacitor-mute": 37064, + "@capgo/native-audio": 40200, + "@capgo/capacitor-shake": 27562, + "@capgo/capacitor-navigation-bar": 133788, + "@capgo/ivs-player": 551, + "@capgo/home-indicator": 18920, + "@capgo/native-purchases": 80264, + "@capgo/capacitor-data-storage-sqlite": 7548, + "@capgo/capacitor-android-usagestatsmanager": 5741, "@capgo/capacitor-streamcall": 0, - "@capgo/capacitor-autofill-save-password": 13184, - "@capgo/capacitor-social-login": 213556, - "@capgo/capacitor-jw-player": 2108, + "@capgo/capacitor-autofill-save-password": 28005, + "@capgo/capacitor-social-login": 473953, + "@capgo/capacitor-jw-player": 2007, "@capgo/capacitor-ricoh360-camera-plugin": 0, - "@capgo/capacitor-admob": 1817, - "@capgo/capacitor-alarm": 3997, - "@capgo/capacitor-android-inline-install": 2411, - "@capgo/capacitor-android-kiosk": 473, + "@capgo/capacitor-admob": 2590, + "@capgo/capacitor-alarm": 908, + "@capgo/capacitor-android-inline-install": 3040, + "@capgo/capacitor-android-kiosk": 992, "@capgo/capacitor-appinsights": 0, + "@capgo/capacitor-app-attest": 1140, "@capgo/capacitor-audiosession": 0, "@capgo/capacitor-background-geolocation": 0, - "@capgo/capacitor-document-scanner": 4517, - "@capgo/capacitor-downloader": 4022, - "@capgo/capacitor-env": 3352, - "@capgo/capacitor-ffmpeg": 59, - "@capgo/capacitor-gtm": 1287, - "@capgo/capacitor-health": 5873, - "@capgo/capacitor-is-root": 2927, - "@capgo/capacitor-launch-navigator": 2210, - "@capgo/capacitor-live-reload": 0, - "@capgo/capacitor-llm": 2336, - "@capgo/capacitor-media-session": 2313, - "@capgo/capacitor-mux-player": 2025, - "@capgo/capacitor-pay": 2323, - "@capgo/capacitor-pdf-generator": 964, - "@capgo/capacitor-persistent-account": 4077, - "@capgo/capacitor-photo-library": 3024, - "@capgo/capacitor-sim": 2694, - "@capgo/capacitor-speech-recognition": 1415, - "@capgo/capacitor-textinteraction": 1381, - "@capgo/capacitor-twilio-voice": 24577, - "@capgo/capacitor-video-player": 3492, - "@capgo/capacitor-volume-buttons": 2006, - "@capgo/capacitor-youtube-player": 921, - "@capgo/capacitor-wechat": 1548, - "@capgo/capacitor-ibeacon": 546, - "@capgo/capacitor-nfc": 2352, - "@capgo/capacitor-android-age-signals": 502, - "@capgo/capacitor-barometer": 1370, - "@capgo/capacitor-accelerometer": 1664, - "@capgo/capacitor-contacts": 764, - "@capgo/capacitor-audio-recorder": 3508, - "@capgo/capacitor-share-target": 1427, - "@capgo/capacitor-realtimekit": 1933, - "@capgo/capacitor-pedometer": 2579, - "@capgo/capacitor-fast-sql": 1442, - "@capgo/capacitor-file-compressor": 574, - "@capgo/capacitor-speech-synthesis": 996, - "@capgo/capacitor-printer": 491, - "@capgo/capacitor-zip": 932, - "@capgo/capacitor-wifi": 1995, - "@capgo/capacitor-screen-orientation": 947, - "@capgo/capacitor-webview-guardian": 1403, - "@capgo/capacitor-firebase-analytics": 442, - "@capgo/capacitor-firebase-app": 314, - "@capgo/capacitor-firebase-app-check": 299, - "@capgo/capacitor-firebase-authentication": 306, - "@capgo/capacitor-firebase-crashlytics": 333, - "@capgo/capacitor-firebase-firestore": 304, - "@capgo/capacitor-firebase-functions": 301, - "@capgo/capacitor-firebase-messaging": 331, - "@capgo/capacitor-firebase-performance": 309, - "@capgo/capacitor-firebase-remote-config": 295, - "@capgo/capacitor-firebase-storage": 303, - "@capacitor-plus/core": 326, - "@capacitor-plus/cli": 305, - "@capacitor-plus/android": 291, - "@capacitor-plus/ios": 293, - "@capgo/capacitor-compass": 824, - "@capgo/capacitor-file": 679, - "@capgo/capacitor-bluetooth-low-energy": 391, - "@capgo/capacitor-keep-awake": 285, - "@capgo/capacitor-in-app-review": 490, - "@capgo/capacitor-file-picker": 143 -} \ No newline at end of file + "@capgo/capacitor-document-scanner": 20093, + "@capgo/capacitor-downloader": 4418, + "@capgo/capacitor-env": 5321, + "@capgo/capacitor-ffmpeg": 617, + "@capgo/capacitor-gtm": 1864, + "@capgo/capacitor-rudderstack": 0, + "@capgo/capacitor-health": 61298, + "@capgo/capacitor-is-root": 5742, + "@capgo/capacitor-app-tracking-transparency": 5329, + "@capgo/capacitor-launch-navigator": 9214, + "@capgo/capacitor-live-reload": 963, + "@capgo/capacitor-llm": 1515, + "@capgo/capacitor-media-session": 9876, + "@capgo/capacitor-mux-player": 1036, + "@capgo/capacitor-pay": 7193, + "@capgo/capacitor-privacy-screen": 68, + "@capgo/capacitor-pdf-generator": 3893, + "@capgo/capacitor-persistent-account": 13793, + "@capgo/capacitor-photo-library": 1572, + "@capgo/capacitor-sim": 3896, + "@capgo/capacitor-speech-recognition": 24638, + "@capgo/capacitor-textinteraction": 2206, + "@capgo/capacitor-twilio-voice": 48190, + "@capgo/capacitor-video-player": 6096, + "@capgo/capacitor-volume-buttons": 1015, + "@capgo/capacitor-youtube-player": 3870, + "@capgo/capacitor-wechat": 2647, + "@capgo/capacitor-ibeacon": 3910, + "@capgo/capacitor-nfc": 18332, + "@capgo/capacitor-age-range": 447, + "@capgo/capacitor-persona": 225, + "@capgo/capacitor-intune": 45, + "@capgo/capacitor-android-age-signals": 987, + "@capgo/capacitor-barometer": 1904, + "@capgo/capacitor-accelerometer": 2604, + "@capgo/capacitor-contacts": 4283, + "@capgo/capacitor-contentsquare": 144, + "@capgo/capacitor-audio-recorder": 22307, + "@capgo/capacitor-share-target": 12245, + "@capgo/capacitor-realtimekit": 2218, + "@capgo/capacitor-pedometer": 6232, + "@capgo/capacitor-fast-sql": 2875, + "@capgo/capacitor-file-compressor": 3150, + "@capgo/capacitor-speech-synthesis": 2383, + "@capgo/capacitor-supabase": 1135, + "@capgo/capacitor-ssl-pinning": 43, + "@capgo/transitions": 1605, + "@capgo/capacitor-printer": 16953, + "@capgo/capacitor-zip": 1832, + "@capgo/capacitor-zebra-datawedge": 50, + "@capgo/capacitor-wifi": 8608, + "@capgo/capacitor-screen-orientation": 10097, + "@capgo/capacitor-webview-guardian": 1571, + "@capgo/capacitor-webview-version-checker": 277, + "@capgo/capacitor-firebase-analytics": 737, + "@capgo/capacitor-firebase-app": 35, + "@capgo/capacitor-firebase-app-check": 37, + "@capgo/capacitor-firebase-authentication": 567, + "@capgo/capacitor-firebase-crashlytics": 732, + "@capgo/capacitor-firebase-firestore": 42, + "@capgo/capacitor-firebase-functions": 38, + "@capgo/capacitor-firebase-messaging": 509, + "@capgo/capacitor-firebase-performance": 497, + "@capgo/capacitor-firebase-remote-config": 34, + "@capgo/capacitor-firebase-storage": 357, + "@capacitor-plus/core": 590, + "@capacitor-plus/cli": 528, + "@capacitor-plus/android": 539, + "@capacitor-plus/ios": 397, + "@capgo/capacitor-compass": 5789, + "@capgo/capacitor-file": 3232, + "@capgo/capacitor-bluetooth-low-energy": 3736, + "@capgo/capacitor-keep-awake": 2174, + "@capgo/capacitor-in-app-review": 17925, + "@capgo/capacitor-incoming-call-kit": 41, + "@capgo/capacitor-file-picker": 4661, + "@capgo/capacitor-watch": 1970, + "@capgo/capacitor-brightness": 1738, + "@capgo/capacitor-light-sensor": 846, + "@capgo/capacitor-video-thumbnails": 1833, + "@capgo/capacitor-intent-launcher": 3064 +} diff --git a/src/pages/plugins.astro b/src/pages/plugins.astro index 811a4efb6..86932fa71 100644 --- a/src/pages/plugins.astro +++ b/src/pages/plugins.astro @@ -1,8 +1,12 @@ --- +import fs from 'node:fs' +import path from 'node:path' import { actions } from '@/config/plugins' import Layout from '@/layouts/Layout.astro' import * as m from '@/paraglide/messages' import { getSlug, getPluginsWithStars, getTotalStars, getTotalDownloads } from '@/services/github' +import { defaultLocale } from '@/services/locale' +import { resolvePluginDocsSlug } from '@/services/pluginDocs' import PluginIcon from '@/components/PluginIcon.astro' import { Icon as AstroIcon } from 'astro-icon/components' import { getCollection } from 'astro:content' @@ -39,25 +43,46 @@ const title = makeSeoTitle(m.plugins({}, { locale }), description) const content = { title, description } -const slugsSet = new Set() +const tutorialSlugs = new Set() await getCollection('plugin', ({ data, filePath }) => { if (data.published !== false && filePath) { - slugsSet.add(getSlug(filePath).replace('.md', '')) + tutorialSlugs.add(getSlug(filePath).replace('.md', '')) } }) +const readDocsDirectory = (dir: string) => { + if (!fs.existsSync(dir)) return [] + return fs + .readdirSync(dir, { withFileTypes: true }) + .filter((entry) => entry.isDirectory()) + .map((entry) => entry.name) +} + +const defaultDocsDirectory = path.join(process.cwd(), 'src', 'content', 'docs', 'docs', 'plugins') +const localizedDocsDirectory = locale === defaultLocale ? defaultDocsDirectory : path.join(process.cwd(), 'src', 'content', 'docs', locale, 'docs', 'plugins') +const defaultDocsSlugs = new Set(readDocsDirectory(defaultDocsDirectory)) +const localizedDocsSlugs = locale === defaultLocale ? defaultDocsSlugs : new Set(readDocsDirectory(localizedDocsDirectory)) +const docsSlugs = new Set([...defaultDocsSlugs, ...localizedDocsSlugs]) + const pluginsWithStars = getPluginsWithStars(actions) const totalStars = getTotalStars() const totalDownloads = getTotalDownloads() // Parse descriptions for all plugins -const slugs = [...slugsSet] as string[] const plugins = await Promise.all( - pluginsWithStars.map(async (item) => ({ - ...item, - description: await marked.parse(item.description), - hasTutorial: slugs.includes(getSlug(item.href).replace('.md', '')), - })), + pluginsWithStars.map(async (item) => { + const tutorialSlug = getSlug(item.href).replace('.md', '') + const docsSlug = resolvePluginDocsSlug(item, docsSlugs) + const hasTutorial = tutorialSlugs.has(tutorialSlug) + const docsLocale = docsSlug && localizedDocsSlugs.has(docsSlug) ? locale : defaultLocale + + return { + ...item, + description: await marked.parse(item.description), + docsHref: hasTutorial ? getRelativeLocaleUrl(locale, `plugins/${tutorialSlug}`) : docsSlug ? getRelativeLocaleUrl(docsLocale, `docs/plugins/${docsSlug}/`) : item.href, + hasSiteDocs: hasTutorial || Boolean(docsSlug), + } + }), ) // Helper function to format counts @@ -193,9 +218,9 @@ const formatCount = (count: number): string => {
- {item.hasTutorial ? ( + {item.hasSiteDocs ? ( Documentation diff --git a/src/services/pluginDocs.ts b/src/services/pluginDocs.ts new file mode 100644 index 000000000..868a35e06 --- /dev/null +++ b/src/services/pluginDocs.ts @@ -0,0 +1,62 @@ +import type { Action } from '@/config/plugins' + +type PluginReference = Pick + +const getPackageSegment = (name?: string): string => { + if (!name) return '' + const slashIndex = name.indexOf('/') + return slashIndex >= 0 ? name.slice(slashIndex + 1) : name +} + +const addCandidate = (items: string[], value?: string) => { + if (value && !items.includes(value)) items.push(value) +} + +const addNormalizedCandidates = (items: string[], value: string) => { + addCandidate(items, value) + + if (value.startsWith('capacitor-')) { + const withoutPrefix = value.slice('capacitor-'.length) + addCandidate(items, withoutPrefix) + + if (withoutPrefix.startsWith('android-')) { + addCandidate(items, withoutPrefix.slice('android-'.length)) + } + } + + if (value.endsWith('-plugin')) { + addCandidate(items, value.slice(0, -'-plugin'.length)) + } + + if (value.startsWith('capacitor-') && value.endsWith('-plugin')) { + addCandidate(items, value.slice('capacitor-'.length, -'-plugin'.length)) + } +} + +export const getPluginDocsSlugCandidates = (plugin: PluginReference): string[] => { + const candidates: string[] = [] + const packageSegment = getPackageSegment(plugin.name) + + if (packageSegment.startsWith('capacitor-plus')) { + addCandidate(candidates, 'capacitor-plus') + } + + if (packageSegment) { + addNormalizedCandidates(candidates, packageSegment) + } + + const normalizedHref = plugin.href.replace(/\/$/, '') + const repoHref = normalizedHref.includes('/tree/') ? normalizedHref.slice(0, normalizedHref.indexOf('/tree/')) : normalizedHref + const repoName = repoHref.slice(repoHref.lastIndexOf('/') + 1) + + if (repoName) { + addNormalizedCandidates(candidates, repoName) + } + + return candidates +} + +export const resolvePluginDocsSlug = (plugin: PluginReference, docsSlugs: Iterable): string | undefined => { + const availableSlugs = docsSlugs instanceof Set ? docsSlugs : new Set(docsSlugs) + return getPluginDocsSlugCandidates(plugin).find((slug) => availableSlugs.has(slug)) +} From 3706de48347272082b31bf5398b1ff9ff12e3604 Mon Sep 17 00:00:00 2001 From: Martin Donadieu Date: Fri, 3 Apr 2026 18:23:17 +0200 Subject: [PATCH 2/8] reduce plugin docs duplication --- astro.config.mjs | 77 ++++++------------- src/components/doc/PluginOverview.astro | 21 +++++ src/components/doc/PluginSetupSteps.astro | 32 ++++++++ .../plugins/contentsquare/getting-started.mdx | 25 +++--- .../docs/docs/plugins/contentsquare/index.mdx | 39 ++++++---- .../incoming-call-kit/getting-started.mdx | 25 +++--- .../docs/plugins/incoming-call-kit/index.mdx | 39 ++++++---- .../docs/plugins/supabase/getting-started.mdx | 23 +++--- .../docs/docs/plugins/supabase/index.mdx | 39 ++++++---- .../plugins/transitions/getting-started.mdx | 28 ++++--- .../docs/docs/plugins/transitions/index.mdx | 39 ++++++---- 11 files changed, 210 insertions(+), 177 deletions(-) create mode 100644 src/components/doc/PluginOverview.astro create mode 100644 src/components/doc/PluginSetupSteps.astro diff --git a/astro.config.mjs b/astro.config.mjs index f86e571e1..411aec7d0 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -22,10 +22,10 @@ const AVAILABLE_PARALLELISM = typeof os.availableParallelism === 'function' ? os const BUILD_CONCURRENCY = Number.parseInt(process.env.BUILD_CONCURRENCY ?? '', 10) const CPU_COUNT = Number.isFinite(BUILD_CONCURRENCY) && BUILD_CONCURRENCY > 0 ? BUILD_CONCURRENCY : AVAILABLE_PARALLELISM const SRC_DIR = `${fileURLToPath(new URL('./src/', import.meta.url)) - .replace(/\\/g, '/') + .replaceAll('\\', '/') .replace(/\/$/, '')}/` const PUBLIC_DIR = fileURLToPath(new URL('./public/', import.meta.url)) - .replace(/\\/g, '/') + .replaceAll('\\', '/') .replace(/\/$/, '') // Build a map of page paths to their lastmod dates for sitemap @@ -78,6 +78,19 @@ const toHeroiconName = (value) => const pluginIcons = [ ...new Set(['arrow-up-right-solid', ...[...readFileSync('src/config/plugins.ts', 'utf8').matchAll(/icon:\s*'([^']+)'/g)].map(([, iconName]) => toHeroiconName(iconName))]), ].sort() +const createPluginDocSet = (label, description, slug) => ({ + label: `Plugin ${label}`, + description, + paths: [`docs/plugins/${slug}/**`], +}) +const createPluginSidebarItem = (label, slug) => ({ + label, + items: [ + { label: 'Overview', link: `/docs/plugins/${slug}/` }, + { label: 'Getting started', link: `/docs/plugins/${slug}/getting-started` }, + ], + collapsed: true, +}) export default defineConfig({ trailingSlash: 'always', @@ -303,11 +316,7 @@ export default defineConfig({ description: 'contacts access plugin for reading device contacts', paths: ['docs/plugins/contacts/**'], }, - { - label: 'Plugin Contentsquare', - description: 'Contentsquare mobile analytics and session replay plugin', - paths: ['docs/plugins/contentsquare/**'], - }, + createPluginDocSet('Contentsquare', 'Contentsquare mobile analytics and session replay plugin', 'contentsquare'), { label: 'Plugin Crisp', description: 'Crisp chat integration plugin', @@ -398,11 +407,7 @@ export default defineConfig({ description: 'in-app review prompt plugin for app store ratings', paths: ['docs/plugins/in-app-review/**'], }, - { - label: 'Plugin Incoming Call Kit', - description: 'native incoming call UI plugin with Android notifications and iOS CallKit', - paths: ['docs/plugins/incoming-call-kit/**'], - }, + createPluginDocSet('Incoming Call Kit', 'native incoming call UI plugin with Android notifications and iOS CallKit', 'incoming-call-kit'), { label: 'Plugin Intent Launcher', description: 'Android intent launcher plugin', @@ -603,11 +608,7 @@ export default defineConfig({ description: 'text-to-speech synthesis plugin', paths: ['docs/plugins/speech-synthesis/**'], }, - { - label: 'Plugin Supabase', - description: 'native Supabase authentication and JWT access plugin', - paths: ['docs/plugins/supabase/**'], - }, + createPluginDocSet('Supabase', 'native Supabase authentication and JWT access plugin', 'supabase'), { label: 'Plugin SSL Pinning', description: 'certificate pinning plugin for CapacitorHttp requests', @@ -623,11 +624,7 @@ export default defineConfig({ description: 'text selection and interaction plugin', paths: ['docs/plugins/textinteraction/**'], }, - { - label: 'Plugin Transitions', - description: 'framework-agnostic page transition plugin for Capacitor apps', - paths: ['docs/plugins/transitions/**'], - }, + createPluginDocSet('Transitions', 'framework-agnostic page transition plugin for Capacitor apps', 'transitions'), { label: 'Plugin Twilio Voice', description: 'Twilio voice calling plugin', @@ -1011,14 +1008,7 @@ export default defineConfig({ ], collapsed: true, }, - { - label: 'Contentsquare', - items: [ - { label: 'Overview', link: '/docs/plugins/contentsquare/' }, - { label: 'Getting started', link: '/docs/plugins/contentsquare/getting-started' }, - ], - collapsed: true, - }, + createPluginSidebarItem('Contentsquare', 'contentsquare'), { label: 'Crisp', items: [ @@ -1147,14 +1137,7 @@ export default defineConfig({ ], collapsed: true, }, - { - label: 'Incoming Call Kit', - items: [ - { label: 'Overview', link: '/docs/plugins/incoming-call-kit/' }, - { label: 'Getting started', link: '/docs/plugins/incoming-call-kit/getting-started' }, - ], - collapsed: true, - }, + createPluginSidebarItem('Incoming Call Kit', 'incoming-call-kit'), { label: 'Intercom', items: [ @@ -1466,14 +1449,7 @@ export default defineConfig({ ], collapsed: true, }, - { - label: 'Supabase', - items: [ - { label: 'Overview', link: '/docs/plugins/supabase/' }, - { label: 'Getting started', link: '/docs/plugins/supabase/getting-started' }, - ], - collapsed: true, - }, + createPluginSidebarItem('Supabase', 'supabase'), { label: 'SSL Pinning', items: [ @@ -1550,14 +1526,7 @@ export default defineConfig({ ], collapsed: true, }, - { - label: 'Transitions', - items: [ - { label: 'Overview', link: '/docs/plugins/transitions/' }, - { label: 'Getting started', link: '/docs/plugins/transitions/getting-started' }, - ], - collapsed: true, - }, + createPluginSidebarItem('Transitions', 'transitions'), { label: 'Twilio Voice', items: [ diff --git a/src/components/doc/PluginOverview.astro b/src/components/doc/PluginOverview.astro new file mode 100644 index 000000000..056951317 --- /dev/null +++ b/src/components/doc/PluginOverview.astro @@ -0,0 +1,21 @@ +--- +import { Card, CardGrid } from '@astrojs/starlight/components' + +interface PluginOverviewCard { + body: string + title: string +} + +interface Props { + cards: PluginOverviewCard[] + summary: string +} + +const { cards, summary } = Astro.props +--- + +

{summary}

+ + + {cards.map((card) => {card.body})} + diff --git a/src/components/doc/PluginSetupSteps.astro b/src/components/doc/PluginSetupSteps.astro new file mode 100644 index 000000000..b2c3c3fc2 --- /dev/null +++ b/src/components/doc/PluginSetupSteps.astro @@ -0,0 +1,32 @@ +--- +import { PackageManagers } from 'starlight-package-managers' + +interface Props { + installLabel?: string + pkg: string + stepTitle: string +} + +const { installLabel = 'plugin', pkg, stepTitle } = Astro.props +--- + +
    +
  1. +

    + Install the {installLabel} +

    + +
  2. +
  3. +

    + Sync native platforms +

    + +
  4. +
  5. +

    + {stepTitle} +

    + +
  6. +
diff --git a/src/content/docs/docs/plugins/contentsquare/getting-started.mdx b/src/content/docs/docs/plugins/contentsquare/getting-started.mdx index 98399c1aa..1fa368849 100644 --- a/src/content/docs/docs/plugins/contentsquare/getting-started.mdx +++ b/src/content/docs/docs/plugins/contentsquare/getting-started.mdx @@ -5,19 +5,18 @@ sidebar: order: 2 --- -import { Steps } from '@astrojs/starlight/components'; -import { PackageManagers } from 'starlight-package-managers' - - -1. **Install the plugin** - - -2. **Sync native platforms** - - -3. **Review the upstream product configuration** - - Follow the official [Contentsquare Capacitor guide](https://docs.contentsquare.com/en/capacitor/) for project keys, replay settings, and dashboard setup. - +import PluginSetupSteps from '@/components/doc/PluginSetupSteps.astro'; + + +
+ ## iOS setup diff --git a/src/content/docs/docs/plugins/contentsquare/index.mdx b/src/content/docs/docs/plugins/contentsquare/index.mdx index f53970999..de3b5ba0e 100644 --- a/src/content/docs/docs/plugins/contentsquare/index.mdx +++ b/src/content/docs/docs/plugins/contentsquare/index.mdx @@ -20,21 +20,26 @@ hero: variant: minimal --- -import { Card, CardGrid } from '@astrojs/starlight/components'; +import PluginOverview from '@/components/doc/PluginOverview.astro'; -`@capgo/capacitor-contentsquare` keeps the documented Contentsquare Capacitor API while updating the plugin for Capacitor 8, Swift Package Manager, and the Capgo plugin template. - - - - Keep the same `optIn`, `sendScreenName`, transaction, and replay masking calls documented by Contentsquare. - - - Use a package that matches modern Capacitor projects without maintaining your own fork. - - - Apply URL exclusions and selector-based masking for Session Replay from JavaScript. - - - Product-specific setup details still live in the official [Contentsquare Capacitor documentation](https://docs.contentsquare.com/en/capacitor/). - - + diff --git a/src/content/docs/docs/plugins/incoming-call-kit/getting-started.mdx b/src/content/docs/docs/plugins/incoming-call-kit/getting-started.mdx index b23fe0122..ee49c4ff5 100644 --- a/src/content/docs/docs/plugins/incoming-call-kit/getting-started.mdx +++ b/src/content/docs/docs/plugins/incoming-call-kit/getting-started.mdx @@ -5,20 +5,17 @@ sidebar: order: 2 --- -import { Steps } from '@astrojs/starlight/components'; -import { PackageManagers } from 'starlight-package-managers' - - -1. **Install the plugin** - - -2. **Sync native platforms** - - -3. **Decide what triggers the ring UI** - - Use your existing push, SIP, or backend event flow. - - Call `showIncomingCall()` only when your app has enough information to render the incoming call. - +import PluginSetupSteps from '@/components/doc/PluginSetupSteps.astro'; + + +
    +
  • Use your existing push, SIP, or backend event flow.
  • +
  • Call showIncomingCall() only when your app has enough information to render the incoming call.
  • +
+
## Permissions diff --git a/src/content/docs/docs/plugins/incoming-call-kit/index.mdx b/src/content/docs/docs/plugins/incoming-call-kit/index.mdx index 6ade6d9a8..2c82b8eda 100644 --- a/src/content/docs/docs/plugins/incoming-call-kit/index.mdx +++ b/src/content/docs/docs/plugins/incoming-call-kit/index.mdx @@ -20,21 +20,26 @@ hero: variant: minimal --- -import { Card, CardGrid } from '@astrojs/starlight/components'; +import PluginOverview from '@/components/doc/PluginOverview.astro'; -`@capgo/capacitor-incoming-call-kit` is the native ringing layer for apps that already have push delivery and voice or video sessions handled elsewhere. - - - - Show Android incoming call notifications with optional full-screen UI and iOS CallKit sheets from JavaScript. - - - Listen for `callAccepted`, `callDeclined`, `callEnded`, and `callTimedOut` with your original metadata attached. - - - Bring your own FCM, PushKit, SIP, or backend trigger and call the plugin when you are ready to ring. - - - The plugin handles presentation only. Your app still owns the real audio or video session. - - + diff --git a/src/content/docs/docs/plugins/supabase/getting-started.mdx b/src/content/docs/docs/plugins/supabase/getting-started.mdx index 02af22440..d0dc0f904 100644 --- a/src/content/docs/docs/plugins/supabase/getting-started.mdx +++ b/src/content/docs/docs/plugins/supabase/getting-started.mdx @@ -5,19 +5,16 @@ sidebar: order: 2 --- -import { Steps } from '@astrojs/starlight/components'; -import { PackageManagers } from 'starlight-package-managers' - - -1. **Install the plugin** - - -2. **Sync native platforms** - - -3. **Confirm Android minimum SDK** - - Set `minSdkVersion = 26` in `android/variables.gradle`. - +import PluginSetupSteps from '@/components/doc/PluginSetupSteps.astro'; + + +
    +
  • Set minSdkVersion = 26 in android/variables.gradle.
  • +
+
## Initialize the client diff --git a/src/content/docs/docs/plugins/supabase/index.mdx b/src/content/docs/docs/plugins/supabase/index.mdx index 19b65e3fc..acd751fc9 100644 --- a/src/content/docs/docs/plugins/supabase/index.mdx +++ b/src/content/docs/docs/plugins/supabase/index.mdx @@ -20,21 +20,26 @@ hero: variant: minimal --- -import { Card, CardGrid } from '@astrojs/starlight/components'; +import PluginOverview from '@/components/doc/PluginOverview.astro'; -`@capgo/capacitor-supabase` focuses on the parts where native Supabase SDKs give you real value: secure auth flows, session persistence, token refresh, and direct JWT access. - - - - Handle email and password, OAuth, OTP, magic links, and session refresh with native SDK behavior. - - - Read the access token from native auth, then pass it to `@supabase/supabase-js` for the rest of your app. - - - Subscribe to native auth state changes and react to `SIGNED_IN`, `SIGNED_OUT`, or `TOKEN_REFRESHED`. - - - Run simple native select, insert, update, and delete operations when you want them close to the auth layer. - - + diff --git a/src/content/docs/docs/plugins/transitions/getting-started.mdx b/src/content/docs/docs/plugins/transitions/getting-started.mdx index a58315386..60d45bb80 100644 --- a/src/content/docs/docs/plugins/transitions/getting-started.mdx +++ b/src/content/docs/docs/plugins/transitions/getting-started.mdx @@ -5,21 +5,19 @@ sidebar: order: 2 --- -import { Steps } from '@astrojs/starlight/components'; -import { PackageManagers } from 'starlight-package-managers' - - -1. **Install the package** - - -2. **Import the runtime once at app startup** - - Import `@capgo/transitions`. - - Call `initTransitions({ platform: 'auto' })` from the binding that matches your stack. - -3. **Wrap your routed pages** - - Put your pages inside `cap-router-outlet`. - - Use `cap-page`, `cap-header`, `cap-content`, and `cap-footer` for coordinated transitions. - +import PluginSetupSteps from '@/components/doc/PluginSetupSteps.astro'; + + +
    +
  • Import @capgo/transitions and call initTransitions() from the binding that matches your stack.
  • +
  • Put your pages inside cap-router-outlet.
  • +
  • Use cap-page, cap-header, cap-content, and cap-footer for coordinated transitions.
  • +
+
## Vanilla setup diff --git a/src/content/docs/docs/plugins/transitions/index.mdx b/src/content/docs/docs/plugins/transitions/index.mdx index 07624f205..507b40a7f 100644 --- a/src/content/docs/docs/plugins/transitions/index.mdx +++ b/src/content/docs/docs/plugins/transitions/index.mdx @@ -20,21 +20,26 @@ hero: variant: minimal --- -import { Card, CardGrid } from '@astrojs/starlight/components'; +import PluginOverview from '@/components/doc/PluginOverview.astro'; -`@capgo/transitions` provides route transitions only. It does not force Ionic styles or framework-specific layout choices. - - - - Use the same package with vanilla web components or framework bindings for React, Vue, Angular, Svelte, and Solid. - - - Get iOS and Android style transitions with Web Animations API and progressive View Transitions support. - - - Coordinate transitions across headers, content, and footers with `cap-router-outlet` and `cap-page`. - - - Bring your own styles and navigation structure. The library focuses on transition behavior, not UI chrome. - - + From 2e44574e0dbd946809541a5928a90fc78e89d92d Mon Sep 17 00:00:00 2001 From: Martin Donadieu Date: Fri, 3 Apr 2026 18:30:53 +0200 Subject: [PATCH 3/8] dedupe plugin metadata wiring --- astro.config.mjs | 18 ++++++----- src/config/plugins.ts | 73 ++++++++++++++++++++++++------------------- 2 files changed, 51 insertions(+), 40 deletions(-) diff --git a/astro.config.mjs b/astro.config.mjs index 411aec7d0..f13db7372 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -91,6 +91,14 @@ const createPluginSidebarItem = (label, slug) => ({ ], collapsed: true, }) +const additionalPluginDocs = [ + ['Contentsquare', 'Contentsquare mobile analytics and session replay plugin', 'contentsquare'], + ['Incoming Call Kit', 'native incoming call UI plugin with Android notifications and iOS CallKit', 'incoming-call-kit'], + ['Supabase', 'native Supabase authentication and JWT access plugin', 'supabase'], + ['Transitions', 'framework-agnostic page transition plugin for Capacitor apps', 'transitions'], +] +const additionalPluginDocSets = additionalPluginDocs.map(([label, description, slug]) => createPluginDocSet(label, description, slug)) +const additionalPluginSidebarItems = additionalPluginDocs.map(([label, , slug]) => createPluginSidebarItem(label, slug)) export default defineConfig({ trailingSlash: 'always', @@ -316,7 +324,6 @@ export default defineConfig({ description: 'contacts access plugin for reading device contacts', paths: ['docs/plugins/contacts/**'], }, - createPluginDocSet('Contentsquare', 'Contentsquare mobile analytics and session replay plugin', 'contentsquare'), { label: 'Plugin Crisp', description: 'Crisp chat integration plugin', @@ -407,7 +414,6 @@ export default defineConfig({ description: 'in-app review prompt plugin for app store ratings', paths: ['docs/plugins/in-app-review/**'], }, - createPluginDocSet('Incoming Call Kit', 'native incoming call UI plugin with Android notifications and iOS CallKit', 'incoming-call-kit'), { label: 'Plugin Intent Launcher', description: 'Android intent launcher plugin', @@ -608,7 +614,6 @@ export default defineConfig({ description: 'text-to-speech synthesis plugin', paths: ['docs/plugins/speech-synthesis/**'], }, - createPluginDocSet('Supabase', 'native Supabase authentication and JWT access plugin', 'supabase'), { label: 'Plugin SSL Pinning', description: 'certificate pinning plugin for CapacitorHttp requests', @@ -624,7 +629,6 @@ export default defineConfig({ description: 'text selection and interaction plugin', paths: ['docs/plugins/textinteraction/**'], }, - createPluginDocSet('Transitions', 'framework-agnostic page transition plugin for Capacitor apps', 'transitions'), { label: 'Plugin Twilio Voice', description: 'Twilio voice calling plugin', @@ -690,6 +694,7 @@ export default defineConfig({ description: 'file compression and extraction plugin', paths: ['docs/plugins/zip/**'], }, + ...additionalPluginDocSets, // Firebase plugins { label: 'Plugin Firebase Analytics', @@ -1008,7 +1013,6 @@ export default defineConfig({ ], collapsed: true, }, - createPluginSidebarItem('Contentsquare', 'contentsquare'), { label: 'Crisp', items: [ @@ -1137,7 +1141,6 @@ export default defineConfig({ ], collapsed: true, }, - createPluginSidebarItem('Incoming Call Kit', 'incoming-call-kit'), { label: 'Intercom', items: [ @@ -1449,7 +1452,6 @@ export default defineConfig({ ], collapsed: true, }, - createPluginSidebarItem('Supabase', 'supabase'), { label: 'SSL Pinning', items: [ @@ -1526,7 +1528,6 @@ export default defineConfig({ ], collapsed: true, }, - createPluginSidebarItem('Transitions', 'transitions'), { label: 'Twilio Voice', items: [ @@ -1610,6 +1611,7 @@ export default defineConfig({ ], collapsed: true, }, + ...additionalPluginSidebarItems, { label: '👋 Get a custom plugin', link: '/consulting/', diff --git a/src/config/plugins.ts b/src/config/plugins.ts index a967f292d..051e8a87c 100644 --- a/src/config/plugins.ts +++ b/src/config/plugins.ts @@ -19,6 +19,46 @@ export interface Plugin extends Action { locale?: string } +const createCapgoPlugin = (name: string, title: string, description: string, href: string, icon: string): Action => ({ + name, + title, + description, + href, + icon, + author: 'github.com/Cap-go', +}) + +const additionalCapgoPlugins = [ + createCapgoPlugin( + '@capgo/capacitor-contentsquare', + 'Contentsquare', + 'Contentsquare analytics, screen tracking, transaction events, and session replay controls for Capacitor 8 apps', + 'https://github.com/Cap-go/capacitor-contentsquare/', + 'ChartBar', + ), + createCapgoPlugin( + '@capgo/capacitor-supabase', + 'Supabase', + 'Native Supabase authentication, JWT session access, and basic database operations for Capacitor apps', + 'https://github.com/Cap-go/capacitor-supabase/', + 'CircleStack', + ), + createCapgoPlugin( + '@capgo/transitions', + 'Transitions', + 'Framework-agnostic page transitions for Capacitor apps with iOS-style navigation and platform-aware animations', + 'https://github.com/Cap-go/capacitor-transitions/', + 'ArrowsRightLeft', + ), + createCapgoPlugin( + '@capgo/capacitor-incoming-call-kit', + 'Incoming Call Kit', + 'Native incoming call UI for Android full-screen notifications and iOS CallKit with typed call lifecycle events', + 'https://github.com/Cap-go/capacitor-incoming-call-kit/', + 'Phone', + ), +] + export const actions = [ { name: '@capgo/native-market', @@ -596,14 +636,6 @@ export const actions = [ title: 'Contacts', icon: 'UserGroup', }, - { - name: '@capgo/capacitor-contentsquare', - author: 'github.com/Cap-go', - description: 'Contentsquare analytics, screen tracking, transaction events, and session replay controls for Capacitor 8 apps', - href: 'https://github.com/Cap-go/capacitor-contentsquare/', - title: 'Contentsquare', - icon: 'ChartBar', - }, { name: '@capgo/capacitor-audio-recorder', author: 'github.com/Cap-go', @@ -660,14 +692,6 @@ export const actions = [ title: 'Speech Synthesis', icon: 'SpeakerWave', }, - { - name: '@capgo/capacitor-supabase', - author: 'github.com/Cap-go', - description: 'Native Supabase authentication, JWT session access, and basic database operations for Capacitor apps', - href: 'https://github.com/Cap-go/capacitor-supabase/', - title: 'Supabase', - icon: 'CircleStack', - }, { name: '@capgo/capacitor-ssl-pinning', author: 'github.com/Cap-go', @@ -676,14 +700,6 @@ export const actions = [ title: 'SSL Pinning', icon: 'ShieldCheck', }, - { - name: '@capgo/transitions', - author: 'github.com/Cap-go', - description: 'Framework-agnostic page transitions for Capacitor apps with iOS-style navigation and platform-aware animations', - href: 'https://github.com/Cap-go/capacitor-transitions/', - title: 'Transitions', - icon: 'ArrowsRightLeft', - }, { name: '@capgo/capacitor-printer', author: 'github.com/Cap-go', @@ -900,14 +916,7 @@ export const actions = [ title: 'In App Review', icon: 'Star', }, - { - name: '@capgo/capacitor-incoming-call-kit', - author: 'github.com/Cap-go', - description: 'Native incoming call UI for Android full-screen notifications and iOS CallKit with typed call lifecycle events', - href: 'https://github.com/Cap-go/capacitor-incoming-call-kit/', - title: 'Incoming Call Kit', - icon: 'Phone', - }, + ...additionalCapgoPlugins, { name: '@capgo/capacitor-file-picker', author: 'github.com/Cap-go', From ef8ce16f1257822284ea851eb14475c061d3b453 Mon Sep 17 00:00:00 2001 From: Martin Donadieu Date: Fri, 3 Apr 2026 18:43:50 +0200 Subject: [PATCH 4/8] fix plugin docs review issues --- src/components/doc/PluginOverview.astro | 13 ++-- src/components/doc/PluginSetupSteps.astro | 9 +-- src/components/doc/PluginsDirectory.astro | 46 ++++++------ .../docs/plugins/supabase/getting-started.mdx | 4 +- src/pages/plugins.astro | 47 +++++++----- src/services/pluginDocs.ts | 71 +++++++++++++++++++ 6 files changed, 137 insertions(+), 53 deletions(-) diff --git a/src/components/doc/PluginOverview.astro b/src/components/doc/PluginOverview.astro index 056951317..531069c96 100644 --- a/src/components/doc/PluginOverview.astro +++ b/src/components/doc/PluginOverview.astro @@ -11,11 +11,14 @@ interface Props { summary: string } -const { cards, summary } = Astro.props +const { cards, summary } = Astro.props as Props --- -

{summary}

+
+

Plugin overview

+

{summary}

- - {cards.map((card) => {card.body})} - + + {cards.map((card) => {card.body})} + +
diff --git a/src/components/doc/PluginSetupSteps.astro b/src/components/doc/PluginSetupSteps.astro index b2c3c3fc2..8ad8b03e1 100644 --- a/src/components/doc/PluginSetupSteps.astro +++ b/src/components/doc/PluginSetupSteps.astro @@ -7,21 +7,22 @@ interface Props { stepTitle: string } -const { installLabel = 'plugin', pkg, stepTitle } = Astro.props +const { installLabel = 'plugin', pkg, stepTitle } = Astro.props as Props +const pkgManagers: Array<'npm' | 'pnpm' | 'yarn' | 'bun'> = ['npm', 'pnpm', 'yarn', 'bun'] --- -
    +
    1. Install the {installLabel}

      - +
    2. Sync native platforms

      - +
    3. diff --git a/src/components/doc/PluginsDirectory.astro b/src/components/doc/PluginsDirectory.astro index f127e58a1..fdb68fb63 100644 --- a/src/components/doc/PluginsDirectory.astro +++ b/src/components/doc/PluginsDirectory.astro @@ -1,41 +1,39 @@ --- -import fs from 'node:fs' -import path from 'node:path' import { CardGrid, LinkCard } from '@astrojs/starlight/components' import { actions } from '@/config/plugins' import { defaultLocale } from '@/services/locale' -import { resolvePluginDocsSlug } from '@/services/pluginDocs' +import { getPluginDocsSlugs, resolvePluginDocsSlug } from '@/services/pluginDocs' import { getRelativeLocaleUrl } from 'astro:i18n' const locale = Astro.locals.locale ?? defaultLocale - -const readDocsDirectory = (dir: string) => { - if (!fs.existsSync(dir)) return [] - return fs - .readdirSync(dir, { withFileTypes: true }) - .filter((entry) => entry.isDirectory()) - .map((entry) => entry.name) -} - -const defaultDocsDirectory = path.join(process.cwd(), 'src', 'content', 'docs', 'docs', 'plugins') -const localizedDocsDirectory = locale === defaultLocale ? defaultDocsDirectory : path.join(process.cwd(), 'src', 'content', 'docs', locale, 'docs', 'plugins') -const defaultDocsSlugs = new Set(readDocsDirectory(defaultDocsDirectory)) -const localizedDocsSlugs = locale === defaultLocale ? defaultDocsSlugs : new Set(readDocsDirectory(localizedDocsDirectory)) -const docsSlugs = new Set([...defaultDocsSlugs, ...localizedDocsSlugs]) -const seenDocsSlugs = new Set() +const { localizedDocsSlugs, docsSlugs } = await getPluginDocsSlugs(locale) +const seenPluginLinks = new Set() const plugins = actions .map((plugin) => { const docsSlug = resolvePluginDocsSlug(plugin, docsSlugs) - if (!docsSlug || seenDocsSlugs.has(docsSlug)) return null + if (docsSlug) { + const docsKey = `docs:${docsSlug}` + if (seenPluginLinks.has(docsKey)) return null + + seenPluginLinks.add(docsKey) - seenDocsSlugs.add(docsSlug) + const docsLocale = localizedDocsSlugs.has(docsSlug) ? locale : defaultLocale + + return { + description: plugin.description, + href: getRelativeLocaleUrl(docsLocale, `docs/plugins/${docsSlug}/`), + title: plugin.title, + } + } - const docsLocale = localizedDocsSlugs.has(docsSlug) ? locale : defaultLocale + const fallbackKey = `repo:${plugin.href}` + if (seenPluginLinks.has(fallbackKey)) return null + seenPluginLinks.add(fallbackKey) return { - description: plugin.description, - href: getRelativeLocaleUrl(docsLocale, `docs/plugins/${docsSlug}/`), + description: `${plugin.description} Opens the plugin repository until a dedicated docs page is available.`, + href: plugin.href, title: plugin.title, } }) @@ -43,7 +41,7 @@ const plugins = actions .sort((left, right) => left.title.localeCompare(right.title)) --- -

      {plugins.length} documented plugins currently resolve from the live Capgo plugin registry.

      +

      {plugins.length} plugins currently resolve from the live Capgo registry. Dedicated docs open when available, and repository links remain visible for the rest.

      {plugins.map((plugin) => )} diff --git a/src/content/docs/docs/plugins/supabase/getting-started.mdx b/src/content/docs/docs/plugins/supabase/getting-started.mdx index d0dc0f904..0bef44312 100644 --- a/src/content/docs/docs/plugins/supabase/getting-started.mdx +++ b/src/content/docs/docs/plugins/supabase/getting-started.mdx @@ -36,7 +36,7 @@ const { session, user } = await CapacitorSupabase.signInWithPassword({ }); console.log('User', user?.id); -console.log('JWT', session?.accessToken); +console.log('JWT available', Boolean(session?.accessToken)); ``` ## Listen for auth changes @@ -44,7 +44,7 @@ console.log('JWT', session?.accessToken); ```typescript const listener = await CapacitorSupabase.addListener('authStateChange', ({ event, session }) => { console.log('Auth event', event); - console.log('Current JWT', session?.accessToken); + console.log('Current JWT available', Boolean(session?.accessToken)); }); // Remove it when the page is done. diff --git a/src/pages/plugins.astro b/src/pages/plugins.astro index 86932fa71..4e586b500 100644 --- a/src/pages/plugins.astro +++ b/src/pages/plugins.astro @@ -1,12 +1,11 @@ --- -import fs from 'node:fs' -import path from 'node:path' import { actions } from '@/config/plugins' import Layout from '@/layouts/Layout.astro' +import { createItemListLdJson, createLdJsonGraph } from '@/lib/ldJson' import * as m from '@/paraglide/messages' import { getSlug, getPluginsWithStars, getTotalStars, getTotalDownloads } from '@/services/github' import { defaultLocale } from '@/services/locale' -import { resolvePluginDocsSlug } from '@/services/pluginDocs' +import { getPluginDocsSlugs, resolvePluginDocsSlug } from '@/services/pluginDocs' import PluginIcon from '@/components/PluginIcon.astro' import { Icon as AstroIcon } from 'astro-icon/components' import { getCollection } from 'astro:content' @@ -41,8 +40,6 @@ const makeSeoTitle = (primary: string, descriptionText: string) => { const title = makeSeoTitle(m.plugins({}, { locale }), description) -const content = { title, description } - const tutorialSlugs = new Set() await getCollection('plugin', ({ data, filePath }) => { if (data.published !== false && filePath) { @@ -50,19 +47,7 @@ await getCollection('plugin', ({ data, filePath }) => { } }) -const readDocsDirectory = (dir: string) => { - if (!fs.existsSync(dir)) return [] - return fs - .readdirSync(dir, { withFileTypes: true }) - .filter((entry) => entry.isDirectory()) - .map((entry) => entry.name) -} - -const defaultDocsDirectory = path.join(process.cwd(), 'src', 'content', 'docs', 'docs', 'plugins') -const localizedDocsDirectory = locale === defaultLocale ? defaultDocsDirectory : path.join(process.cwd(), 'src', 'content', 'docs', locale, 'docs', 'plugins') -const defaultDocsSlugs = new Set(readDocsDirectory(defaultDocsDirectory)) -const localizedDocsSlugs = locale === defaultLocale ? defaultDocsSlugs : new Set(readDocsDirectory(localizedDocsDirectory)) -const docsSlugs = new Set([...defaultDocsSlugs, ...localizedDocsSlugs]) +const { localizedDocsSlugs, docsSlugs } = await getPluginDocsSlugs(locale) const pluginsWithStars = getPluginsWithStars(actions) const totalStars = getTotalStars() @@ -95,6 +80,32 @@ const formatCount = (count: number): string => { } return count.toString() } + +const baseUrl = Astro.locals.runtimeConfig.public.baseUrl +const pluginsUrl = new URL(getRelativeLocaleUrl(locale, '/plugins/'), baseUrl).toString() +const ldJSON = createLdJsonGraph( + Astro.locals.runtimeConfig.public, + createItemListLdJson(Astro.locals.runtimeConfig.public, { + url: pluginsUrl, + name: title, + description, + items: plugins.map((plugin) => ({ + name: plugin.title, + url: new URL(plugin.docsHref, baseUrl).toString(), + description: plugin.description.replace(/<[^>]+>/g, '').trim(), + })), + }), + { + includeOrganization: true, + }, +) + +const content = { + title, + description, + keywords: 'capacitor plugins, mobile plugins, ionic plugins, native plugins, app plugins', + ldJSON, +} --- diff --git a/src/services/pluginDocs.ts b/src/services/pluginDocs.ts index 868a35e06..9ad6ede8a 100644 --- a/src/services/pluginDocs.ts +++ b/src/services/pluginDocs.ts @@ -1,6 +1,28 @@ import type { Action } from '@/config/plugins' +import { defaultLocale, locales, type Locales } from '@/services/locale' +import { getCollection } from 'astro:content' type PluginReference = Pick +type PluginDocsSlugs = { + defaultDocsSlugs: Set + localizedDocsSlugs: Set + docsSlugs: Set +} + +const pluginDocsIndexPromise: Promise>> = getCollection('docs').then((entries) => { + const docsByLocale = new Map>() + + for (const entry of entries) { + const docEntry = getPluginDocEntry(entry.filePath) + if (!docEntry) continue + + const localeDocs = docsByLocale.get(docEntry.locale) ?? new Set() + localeDocs.add(docEntry.slug) + docsByLocale.set(docEntry.locale, localeDocs) + } + + return docsByLocale +}) const getPackageSegment = (name?: string): string => { if (!name) return '' @@ -8,6 +30,42 @@ const getPackageSegment = (name?: string): string => { return slashIndex >= 0 ? name.slice(slashIndex + 1) : name } +const normalizeFilePath = (filePath: string): string => { + const normalizedPath = filePath.replaceAll('\\', '/') + const docsRoot = 'src/content/docs/' + const docsRootIndex = normalizedPath.indexOf(docsRoot) + + return docsRootIndex >= 0 ? normalizedPath.slice(docsRootIndex + docsRoot.length) : normalizedPath +} + +const getPluginDocEntry = (filePath?: string): { locale: Locales; slug: string } | null => { + if (!filePath) return null + + const segments = normalizeFilePath(filePath).split('/').filter(Boolean) + if (segments.length < 3) return null + + let locale = defaultLocale + let pluginsIndex = 0 + + if (segments[0] === 'docs') { + pluginsIndex = 1 + } else if (locales.includes(segments[0] as Locales)) { + locale = segments[0] as Locales + pluginsIndex = segments[1] === 'docs' ? 2 : 1 + } else { + return null + } + + if (segments[pluginsIndex] !== 'plugins') return null + + const slug = segments[pluginsIndex + 1] + const fileName = segments.at(-1) + + if (!slug || !fileName?.startsWith('index.')) return null + + return { locale, slug } +} + const addCandidate = (items: string[], value?: string) => { if (value && !items.includes(value)) items.push(value) } @@ -60,3 +118,16 @@ export const resolvePluginDocsSlug = (plugin: PluginReference, docsSlugs: Iterab const availableSlugs = docsSlugs instanceof Set ? docsSlugs : new Set(docsSlugs) return getPluginDocsSlugCandidates(plugin).find((slug) => availableSlugs.has(slug)) } + +export const getPluginDocsSlugs = async (locale?: string): Promise => { + const docsByLocale = await pluginDocsIndexPromise + const normalizedLocale = locales.includes(locale as Locales) ? (locale as Locales) : defaultLocale + const defaultDocsSlugs = docsByLocale.get(defaultLocale) ?? new Set() + const localizedDocsSlugs = normalizedLocale === defaultLocale ? defaultDocsSlugs : (docsByLocale.get(normalizedLocale) ?? new Set()) + + return { + defaultDocsSlugs, + localizedDocsSlugs, + docsSlugs: normalizedLocale === defaultLocale ? new Set(defaultDocsSlugs) : new Set([...defaultDocsSlugs, ...localizedDocsSlugs]), + } +} From 26efc43c655ae4e42f84c7b049ef052ac82763a7 Mon Sep 17 00:00:00 2001 From: Martin Donadieu Date: Sat, 4 Apr 2026 18:55:23 +0200 Subject: [PATCH 5/8] Add missing Capgo plugin pages --- AGENTS.md | 12 ++ astro.config.mjs | 4 + src/config/plugins.ts | 28 +++ .../live-activities/getting-started.mdx | 116 +++++++++++ .../docs/plugins/live-activities/index.mdx | 45 ++++ .../plugins/twilio-video/getting-started.mdx | 89 ++++++++ .../docs/docs/plugins/twilio-video/index.mdx | 45 ++++ .../plugins/widget-kit/getting-started.mdx | 125 +++++++++++ .../docs/docs/plugins/widget-kit/index.mdx | 45 ++++ .../en/capacitor-live-activities.md | 102 +++++++++ .../en/capacitor-twilio-video.md | 84 ++++++++ .../en/capacitor-widget-kit.md | 99 +++++++++ .../plugins-tutorials/en/electron-updater.md | 97 +++++++++ src/data/npm-downloads.json | 196 +++++++++--------- 14 files changed, 991 insertions(+), 96 deletions(-) create mode 100644 src/content/docs/docs/plugins/live-activities/getting-started.mdx create mode 100644 src/content/docs/docs/plugins/live-activities/index.mdx create mode 100644 src/content/docs/docs/plugins/twilio-video/getting-started.mdx create mode 100644 src/content/docs/docs/plugins/twilio-video/index.mdx create mode 100644 src/content/docs/docs/plugins/widget-kit/getting-started.mdx create mode 100644 src/content/docs/docs/plugins/widget-kit/index.mdx create mode 100644 src/content/plugins-tutorials/en/capacitor-live-activities.md create mode 100644 src/content/plugins-tutorials/en/capacitor-twilio-video.md create mode 100644 src/content/plugins-tutorials/en/capacitor-widget-kit.md create mode 100644 src/content/plugins-tutorials/en/electron-updater.md diff --git a/AGENTS.md b/AGENTS.md index 65e0213a5..41a147870 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -22,3 +22,15 @@ bunx @tailwindcss/upgrade --force ``` - Use that command to fix deprecated class syntax across the repo instead of doing large manual replacements. + +## Adding A Plugin To The Website + +- Add the plugin registry entry in `src/config/plugins.ts` with the package name, title, short description, GitHub URL, and Heroicon name. +- Add the plugin docs in `src/content/docs/docs/plugins//index.mdx` and `src/content/docs/docs/plugins//getting-started.mdx`. +- Add the English tutorial in `src/content/plugins-tutorials/en/.md`. +- The tutorial slug must match the GitHub URL slug used by `item.href` on the plugin entry because `/plugins/[slug]` resolves from that repo slug. Examples: + `https://github.com/Cap-go/capacitor-live-activities/` -> `src/content/plugins-tutorials/en/capacitor-live-activities.md` + `https://github.com/Cap-go/electron-updater/` -> `src/content/plugins-tutorials/en/electron-updater.md` +- Register the docs in `astro.config.mjs` by extending `additionalPluginDocs` when the plugin should appear in the Starlight plugin sidebar and search sets. +- Refresh metadata after adding a plugin with `bun run fetch:stars` and `bun run fetch:downloads`. +- Validate the change with `bunx prettier --write ` and `NODE_OPTIONS=--max-old-space-size=16384 ./node_modules/.bin/astro check`. diff --git a/astro.config.mjs b/astro.config.mjs index f13db7372..3073a4641 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -93,9 +93,13 @@ const createPluginSidebarItem = (label, slug) => ({ }) const additionalPluginDocs = [ ['Contentsquare', 'Contentsquare mobile analytics and session replay plugin', 'contentsquare'], + ['Electron Updater', 'live update plugin for Electron apps with Capgo-compatible APIs', 'electron-updater'], ['Incoming Call Kit', 'native incoming call UI plugin with Android notifications and iOS CallKit', 'incoming-call-kit'], + ['Live Activities', 'iOS Live Activities and Dynamic Island plugin with JSON layouts', 'live-activities'], ['Supabase', 'native Supabase authentication and JWT access plugin', 'supabase'], ['Transitions', 'framework-agnostic page transition plugin for Capacitor apps', 'transitions'], + ['Twilio Video', 'native Twilio Video room plugin for Capacitor', 'twilio-video'], + ['Widget Kit', 'iOS WidgetKit and Live Activities plugin with SVG templates', 'widget-kit'], ] const additionalPluginDocSets = additionalPluginDocs.map(([label, description, slug]) => createPluginDocSet(label, description, slug)) const additionalPluginSidebarItems = additionalPluginDocs.map(([label, , slug]) => createPluginSidebarItem(label, slug)) diff --git a/src/config/plugins.ts b/src/config/plugins.ts index 051e8a87c..2ce0be1c9 100644 --- a/src/config/plugins.ts +++ b/src/config/plugins.ts @@ -57,6 +57,34 @@ const additionalCapgoPlugins = [ 'https://github.com/Cap-go/capacitor-incoming-call-kit/', 'Phone', ), + createCapgoPlugin( + '@capgo/electron-updater', + 'Electron Updater', + 'OTA live updates for Electron apps with the same API surface as capacitor-updater', + 'https://github.com/Cap-go/electron-updater/', + 'ComputerDesktop', + ), + createCapgoPlugin( + '@capgo/capacitor-live-activities', + 'Live Activities', + 'Manage iOS Live Activities and Dynamic Island layouts from Capacitor with JSON-driven templates', + 'https://github.com/Cap-go/capacitor-live-activities/', + 'RectangleGroup', + ), + createCapgoPlugin( + '@capgo/capacitor-twilio-video', + 'Twilio Video', + 'Join Twilio Video rooms from Capacitor with native audio, camera, and room lifecycle events', + 'https://github.com/Cap-go/capacitor-twilio-video/', + 'VideoCamera', + ), + createCapgoPlugin( + '@capgo/capacitor-widget-kit', + 'Widget Kit', + 'Build iOS widgets and Live Activities from Capacitor with SVG templates, timers, and action hotspots', + 'https://github.com/Cap-go/capacitor-widget-kit/', + 'Squares2X2', + ), ] export const actions = [ diff --git a/src/content/docs/docs/plugins/live-activities/getting-started.mdx b/src/content/docs/docs/plugins/live-activities/getting-started.mdx new file mode 100644 index 000000000..f14e6f6b8 --- /dev/null +++ b/src/content/docs/docs/plugins/live-activities/getting-started.mdx @@ -0,0 +1,116 @@ +--- +title: Getting Started +description: Install and configure Capgo Live Activities in a Capacitor app. +sidebar: + order: 2 +--- + +import PluginSetupSteps from '@/components/doc/PluginSetupSteps.astro'; + + +
        +
      • Create a Widget Extension target in Xcode and name it LiveActivities.
      • +
      • Add the same App Group to the main app target and the widget extension target.
      • +
      +
      + +## Platform requirements + +- iOS 16.1 or later is required. +- Live Activities are not available on Android or web, so use `areActivitiesSupported()` before showing related UI. + +## Enable Live Activities + +Add the following key to your app `Info.plist`: + +```xml +NSSupportsLiveActivities + +``` + +Add an App Group for the app and widget targets. A common pattern is: + +```xml +group.YOUR_BUNDLE_ID.liveactivities +``` + +## Check support + +```typescript +import { CapgoLiveActivities } from '@capgo/capacitor-live-activities'; + +const { supported, reason } = await CapgoLiveActivities.areActivitiesSupported(); + +if (!supported) { + console.warn('Live Activities unavailable:', reason); +} +``` + +## Start an activity + +```typescript +import { CapgoLiveActivities } from '@capgo/capacitor-live-activities'; + +const { activityId } = await CapgoLiveActivities.startActivity({ + layout: { + type: 'container', + direction: 'vertical', + spacing: 8, + children: [ + { type: 'text', content: 'Order #{{orderNumber}}', fontSize: 16, fontWeight: 'bold' }, + { type: 'text', content: '{{status}}', fontSize: 14, color: '#666666' }, + { type: 'progress', value: 'progress', tint: '#34C759' }, + ], + }, + dynamicIslandLayout: { + expanded: { + center: { type: 'text', content: '{{status}}' }, + trailing: { type: 'text', content: '{{eta}}' }, + }, + compactLeading: { type: 'text', content: 'ETA' }, + compactTrailing: { type: 'text', content: '{{eta}}' }, + minimal: { type: 'text', content: '{{eta}}' }, + }, + behavior: { + widgetUrl: 'myapp://orders/12345', + }, + data: { + orderNumber: '12345', + status: 'On the way', + eta: '10 min', + progress: 0.6, + }, +}); +``` + +## Update and end the activity + +```typescript +await CapgoLiveActivities.updateActivity({ + activityId, + data: { + status: 'Arriving soon', + eta: '2 min', + progress: 0.9, + }, +}); + +await CapgoLiveActivities.endActivity({ + activityId, + data: { + status: 'Delivered', + progress: 1, + }, + dismissalPolicy: 'after', + dismissAfter: Date.now() + 60 * 60 * 1000, +}); +``` + +## Recommended setup + +- Keep the widget extension focused on rendering and let the Capacitor app own activity creation and updates. +- Use App Groups for any shared images or data the widget needs to display. +- Model your JSON layout from the real-time state you already have in the app so updates stay simple and deterministic. diff --git a/src/content/docs/docs/plugins/live-activities/index.mdx b/src/content/docs/docs/plugins/live-activities/index.mdx new file mode 100644 index 000000000..1a2fd52ab --- /dev/null +++ b/src/content/docs/docs/plugins/live-activities/index.mdx @@ -0,0 +1,45 @@ +--- +title: "@capgo/capacitor-live-activities" +description: Manage iOS Live Activities and Dynamic Island experiences from Capacitor with JSON-based layouts. +tableOfContents: false +next: false +prev: false +sidebar: + order: 1 + label: "Introduction" +hero: + tagline: Ship lock-screen and Dynamic Island experiences for delivery, timers, scores, and other real-time flows without building the layout in Swift. + actions: + - text: Get started + link: /docs/plugins/live-activities/getting-started/ + icon: right-arrow + variant: primary + - text: Github + link: https://github.com/Cap-go/capacitor-live-activities/ + icon: external + variant: minimal +--- + +import PluginOverview from '@/components/doc/PluginOverview.astro'; + + diff --git a/src/content/docs/docs/plugins/twilio-video/getting-started.mdx b/src/content/docs/docs/plugins/twilio-video/getting-started.mdx new file mode 100644 index 000000000..b70101627 --- /dev/null +++ b/src/content/docs/docs/plugins/twilio-video/getting-started.mdx @@ -0,0 +1,89 @@ +--- +title: Getting Started +description: Install and use the Twilio Video Capacitor plugin. +sidebar: + order: 2 +--- + +import PluginSetupSteps from '@/components/doc/PluginSetupSteps.astro'; + + +
        +
      • The plugin expects a valid Twilio Video access token before it can connect to a room.
      • +
      • Keep room creation and token issuance on your server instead of hardcoding credentials in the app.
      • +
      +
      + +## Permissions + +Add the required usage descriptions on iOS: + +```xml +NSCameraUsageDescription +This app uses the camera for video calls. +NSMicrophoneUsageDescription +This app uses the microphone for video calls. +``` + +On Android, request camera and microphone access before joining a room. + +## Authenticate and join a room + +```typescript +import { CapacitorTwilioVideo } from '@capgo/capacitor-twilio-video'; + +await CapacitorTwilioVideo.requestMicrophonePermission(); +await CapacitorTwilioVideo.requestCameraPermission(); + +await CapacitorTwilioVideo.login({ + accessToken: 'TWILIO_ACCESS_TOKEN', +}); + +await CapacitorTwilioVideo.joinRoom({ + roomName: 'support-room', + enableAudio: true, + enableVideo: true, +}); +``` + +## Listen for room lifecycle events + +```typescript +await CapacitorTwilioVideo.addListener('roomConnected', ({ roomName, participantCount }) => { + console.log('Connected to room', roomName, participantCount); +}); + +await CapacitorTwilioVideo.addListener('participantConnected', (event) => { + console.log('Participant joined', event); +}); + +await CapacitorTwilioVideo.addListener('roomReconnecting', () => { + console.log('Reconnecting to Twilio Video'); +}); +``` + +## Toggle local media + +```typescript +await CapacitorTwilioVideo.setMicrophoneEnabled({ enabled: false }); +await CapacitorTwilioVideo.setCameraEnabled({ enabled: true }); + +const status = await CapacitorTwilioVideo.getCallStatus(); +console.log(status.callState, status.participantCount); +``` + +## Leave the room + +```typescript +await CapacitorTwilioVideo.leaveRoom(); +await CapacitorTwilioVideo.logout(); +``` + +## Recommended architecture + +- Use this plugin for room connection and media state, not for token generation. +- Keep your visual call UI in your framework layer and react to plugin events. +- Rotate access tokens from your backend so reconnects and multi-device use stay secure. diff --git a/src/content/docs/docs/plugins/twilio-video/index.mdx b/src/content/docs/docs/plugins/twilio-video/index.mdx new file mode 100644 index 000000000..88b7d656a --- /dev/null +++ b/src/content/docs/docs/plugins/twilio-video/index.mdx @@ -0,0 +1,45 @@ +--- +title: "@capgo/capacitor-twilio-video" +description: Headless Twilio Video room integration for Capacitor with native audio, camera, and room lifecycle events. +tableOfContents: false +next: false +prev: false +sidebar: + order: 1 + label: "Introduction" +hero: + tagline: Connect to Twilio Video rooms from Capacitor while keeping your own UI layer and app navigation. + actions: + - text: Get started + link: /docs/plugins/twilio-video/getting-started/ + icon: right-arrow + variant: primary + - text: Github + link: https://github.com/Cap-go/capacitor-twilio-video/ + icon: external + variant: minimal +--- + +import PluginOverview from '@/components/doc/PluginOverview.astro'; + + diff --git a/src/content/docs/docs/plugins/widget-kit/getting-started.mdx b/src/content/docs/docs/plugins/widget-kit/getting-started.mdx new file mode 100644 index 000000000..e88b82a38 --- /dev/null +++ b/src/content/docs/docs/plugins/widget-kit/getting-started.mdx @@ -0,0 +1,125 @@ +--- +title: Getting Started +description: Install and configure the Widget Kit plugin for Capacitor. +sidebar: + order: 2 +--- + +import PluginSetupSteps from '@/components/doc/PluginSetupSteps.astro'; + + +
        +
      • Add the same App Group to the main app target and the widget extension target.
      • +
      • Set CapgoWidgetKitAppGroup in both Info.plist files to that shared App Group identifier.
      • +
      +
      + +## Platform requirements + +- iOS 17 or later is recommended for the interactive completion button flow. +- Add `NSSupportsLiveActivities` to the app `Info.plist`. +- WidgetKit support is iOS-only. The web implementation is only for development previews. + +Example App Group configuration: + +```xml +CapgoWidgetKitAppGroup +group.com.example.app.widgetkit +``` + +## Register the widget bundle + +Inside the widget extension, import the plugin package and expose your bundle: + +```swift +import ActivityKit +import SwiftUI +import WidgetKit +import CapgoWidgetKitPlugin + +@main +struct ExampleWidgetBundle: WidgetBundle { + var body: some Widget { + if #available(iOS 16.2, *) { + ExampleTemplateLiveActivityWidget() + } + } +} +``` + +## Start a template activity + +```typescript +import { CapgoWidgetKit } from '@capgo/capacitor-widget-kit'; + +const { supported, reason } = await CapgoWidgetKit.areActivitiesSupported(); + +if (!supported) { + console.warn(reason); +} + +const { activity } = await CapgoWidgetKit.startTemplateActivity({ + activityId: 'session-1', + openUrl: 'widgetkitdemo://session/session-1', + state: { + title: 'Chest Day', + count: 0, + restDurationMs: 90000, + }, + definition: { + id: 'generic-session-card', + timers: [ + { + id: 'rest', + durationPath: 'state.restDurationMs', + }, + ], + actions: [ + { + id: 'complete-set', + eventName: 'workout.set.completed', + patches: [{ op: 'increment', path: 'count', amount: 1 }], + }, + ], + layouts: { + lockScreen: { + width: 100, + height: 40, + svg: ` + + {{state.title}} + {{timers.rest.remainingText}} +`, + }, + }, + }, +}); + +console.log(activity.activityId); +``` + +## Perform an action and read events + +```typescript +await CapgoWidgetKit.performTemplateAction({ + activityId: activity.activityId, + actionId: 'complete-set', + sourceId: 'app-complete-set-button', +}); + +const pendingEvents = await CapgoWidgetKit.listTemplateEvents({ + activityId: activity.activityId, + unacknowledgedOnly: true, +}); + +console.log(pendingEvents.events); +``` + +## Recommended usage + +- Treat the shipped workout flow as an example, not the core abstraction. +- Keep the widget extension responsible for rendering and let the Capacitor app own activity state and event processing. +- Model actions declaratively so app-side buttons and widget hotspots can reuse the same behavior. diff --git a/src/content/docs/docs/plugins/widget-kit/index.mdx b/src/content/docs/docs/plugins/widget-kit/index.mdx new file mode 100644 index 000000000..60efdfa5a --- /dev/null +++ b/src/content/docs/docs/plugins/widget-kit/index.mdx @@ -0,0 +1,45 @@ +--- +title: "@capgo/capacitor-widget-kit" +description: Generic WidgetKit and Live Activities bridge for Capacitor with SVG templates, timers, and action hotspots. +tableOfContents: false +next: false +prev: false +sidebar: + order: 1 + label: "Introduction" +hero: + tagline: Build interactive iOS widgets and Live Activities from Capacitor with shared App Group storage and declarative SVG templates. + actions: + - text: Get started + link: /docs/plugins/widget-kit/getting-started/ + icon: right-arrow + variant: primary + - text: Github + link: https://github.com/Cap-go/capacitor-widget-kit/ + icon: external + variant: minimal +--- + +import PluginOverview from '@/components/doc/PluginOverview.astro'; + + diff --git a/src/content/plugins-tutorials/en/capacitor-live-activities.md b/src/content/plugins-tutorials/en/capacitor-live-activities.md new file mode 100644 index 000000000..15e8ca4d6 --- /dev/null +++ b/src/content/plugins-tutorials/en/capacitor-live-activities.md @@ -0,0 +1,102 @@ +--- +locale: en +--- + +# Using @capgo/capacitor-live-activities + +`@capgo/capacitor-live-activities` lets a Capacitor app start, update, and end iOS Live Activities with a JSON-based layout definition. It is a good fit for delivery tracking, timers, ride progress, sports scores, and any flow that needs persistent lock-screen or Dynamic Island state. + +## Installation + +```bash +bun add @capgo/capacitor-live-activities +bunx cap sync +``` + +## Before you write code + +- Live Activities require iOS 16.1 or later. +- Create a Widget Extension target in Xcode and share an App Group between the main app and the widget extension. +- Add `NSSupportsLiveActivities` to your app `Info.plist`. + +## Check device support + +```typescript +import { CapgoLiveActivities } from '@capgo/capacitor-live-activities'; + +const { supported, reason } = await CapgoLiveActivities.areActivitiesSupported(); + +if (!supported) { + console.warn('Live Activities unavailable:', reason); +} +``` + +## Start an activity + +```typescript +import { CapgoLiveActivities } from '@capgo/capacitor-live-activities'; + +const { activityId } = await CapgoLiveActivities.startActivity({ + layout: { + type: 'container', + direction: 'vertical', + spacing: 8, + children: [ + { type: 'text', content: 'Order #{{orderNumber}}', fontSize: 16, fontWeight: 'bold' }, + { type: 'text', content: '{{status}}', fontSize: 14, color: '#666666' }, + { type: 'progress', value: 'progress', tint: '#34C759' }, + ], + }, + dynamicIslandLayout: { + expanded: { + center: { type: 'text', content: '{{status}}' }, + trailing: { type: 'text', content: '{{eta}}' }, + }, + compactLeading: { type: 'text', content: 'ETA' }, + compactTrailing: { type: 'text', content: '{{eta}}' }, + minimal: { type: 'text', content: '{{eta}}' }, + }, + behavior: { + widgetUrl: 'myapp://orders/12345', + }, + data: { + orderNumber: '12345', + status: 'On the way', + eta: '10 min', + progress: 0.6, + }, +}); +``` + +## Update the activity + +```typescript +await CapgoLiveActivities.updateActivity({ + activityId, + data: { + status: 'Arriving soon', + eta: '2 min', + progress: 0.9, + }, +}); +``` + +## End the activity + +```typescript +await CapgoLiveActivities.endActivity({ + activityId, + data: { + status: 'Delivered', + progress: 1, + }, + dismissalPolicy: 'after', + dismissAfter: Date.now() + 60 * 60 * 1000, +}); +``` + +## Practical advice + +- Build your layout from stable state keys so updates only change data, not structure. +- Keep the widget extension focused on rendering and let your Capacitor app own activity state transitions. +- Always gate the feature behind `areActivitiesSupported()` so unsupported devices fall back cleanly. diff --git a/src/content/plugins-tutorials/en/capacitor-twilio-video.md b/src/content/plugins-tutorials/en/capacitor-twilio-video.md new file mode 100644 index 000000000..07701c431 --- /dev/null +++ b/src/content/plugins-tutorials/en/capacitor-twilio-video.md @@ -0,0 +1,84 @@ +--- +locale: en +--- + +# Using @capgo/capacitor-twilio-video + +`@capgo/capacitor-twilio-video` connects a Capacitor app to Twilio Video rooms through the native mobile SDKs. The plugin is headless, so you keep your own call UI while delegating room connection, participant lifecycle events, and media state to Twilio. + +## Installation + +```bash +bun add @capgo/capacitor-twilio-video +bunx cap sync +``` + +## Prepare your backend + +This plugin expects a Twilio access token. Generate that token server-side and return it to the app when a user is ready to join a room. + +## Request permissions + +```typescript +import { CapacitorTwilioVideo } from '@capgo/capacitor-twilio-video'; + +await CapacitorTwilioVideo.requestMicrophonePermission(); +await CapacitorTwilioVideo.requestCameraPermission(); +``` + +On iOS, add `NSCameraUsageDescription` and `NSMicrophoneUsageDescription` to `Info.plist`. + +## Authenticate and join a room + +```typescript +import { CapacitorTwilioVideo } from '@capgo/capacitor-twilio-video'; + +await CapacitorTwilioVideo.login({ + accessToken: 'TWILIO_ACCESS_TOKEN', +}); + +await CapacitorTwilioVideo.joinRoom({ + roomName: 'support-room', + enableAudio: true, + enableVideo: true, +}); +``` + +## Listen for room events + +```typescript +await CapacitorTwilioVideo.addListener('roomConnected', ({ roomName, participantCount }) => { + console.log('Connected', roomName, participantCount); +}); + +await CapacitorTwilioVideo.addListener('participantConnected', (event) => { + console.log('Participant joined', event); +}); + +await CapacitorTwilioVideo.addListener('roomDisconnected', (event) => { + console.log('Disconnected', event); +}); +``` + +## Control local media + +```typescript +await CapacitorTwilioVideo.setMicrophoneEnabled({ enabled: false }); +await CapacitorTwilioVideo.setCameraEnabled({ enabled: true }); + +const status = await CapacitorTwilioVideo.getCallStatus(); +console.log(status.callState, status.participantCount); +``` + +## Leave the room + +```typescript +await CapacitorTwilioVideo.leaveRoom(); +await CapacitorTwilioVideo.logout(); +``` + +## Practical advice + +- Keep token creation and room authorization on your backend. +- Render your participant tiles and call controls in your app layer instead of relying on an embedded vendor UI. +- Treat plugin events as the source of truth for connection state and participant changes. diff --git a/src/content/plugins-tutorials/en/capacitor-widget-kit.md b/src/content/plugins-tutorials/en/capacitor-widget-kit.md new file mode 100644 index 000000000..3eaf6cba1 --- /dev/null +++ b/src/content/plugins-tutorials/en/capacitor-widget-kit.md @@ -0,0 +1,99 @@ +--- +locale: en +--- + +# Using @capgo/capacitor-widget-kit + +`@capgo/capacitor-widget-kit` brings WidgetKit and Live Activities to Capacitor through a generic SVG-template bridge. You store template definitions and state in the app, let the widget extension render them, and process action events back in JavaScript. + +## Installation + +```bash +bun add @capgo/capacitor-widget-kit +bunx cap sync +``` + +## iOS requirements + +- Add the same App Group to the main app target and the widget extension target. +- Set `CapgoWidgetKitAppGroup` in both `Info.plist` files. +- Add `NSSupportsLiveActivities` to the app `Info.plist`. +- iOS 17 or later is recommended for interactive widget actions. + +## Check support + +```typescript +import { CapgoWidgetKit } from '@capgo/capacitor-widget-kit'; + +const { supported, reason } = await CapgoWidgetKit.areActivitiesSupported(); + +if (!supported) { + console.warn(reason); +} +``` + +## Start a template activity + +```typescript +import { CapgoWidgetKit } from '@capgo/capacitor-widget-kit'; + +const { activity } = await CapgoWidgetKit.startTemplateActivity({ + activityId: 'session-1', + openUrl: 'widgetkitdemo://session/session-1', + state: { + title: 'Chest Day', + count: 0, + restDurationMs: 90000, + }, + definition: { + id: 'generic-session-card', + timers: [ + { + id: 'rest', + durationPath: 'state.restDurationMs', + }, + ], + actions: [ + { + id: 'complete-set', + eventName: 'workout.set.completed', + patches: [{ op: 'increment', path: 'count', amount: 1 }], + }, + ], + layouts: { + lockScreen: { + width: 100, + height: 40, + svg: ` + + {{state.title}} + {{timers.rest.remainingText}} +`, + }, + }, + }, +}); +``` + +## Trigger an action and read the event log + +```typescript +await CapgoWidgetKit.performTemplateAction({ + activityId: activity.activityId, + actionId: 'complete-set', + sourceId: 'app-complete-set-button', +}); + +const pendingEvents = await CapgoWidgetKit.listTemplateEvents({ + activityId: activity.activityId, + unacknowledgedOnly: true, +}); + +console.log(pendingEvents.events); +``` + +## Practical advice + +- Treat the widget extension as a renderer and keep business state in the app. +- Reuse the same declarative actions for widget hotspots and in-app buttons. +- Keep SVG templates simple and driven by state placeholders so updates stay predictable. diff --git a/src/content/plugins-tutorials/en/electron-updater.md b/src/content/plugins-tutorials/en/electron-updater.md new file mode 100644 index 000000000..e3ac3acfc --- /dev/null +++ b/src/content/plugins-tutorials/en/electron-updater.md @@ -0,0 +1,97 @@ +--- +locale: en +--- + +# Using @capgo/electron-updater + +`@capgo/electron-updater` gives Electron apps the same Capgo live-update model as `@capgo/capacitor-updater`. You initialize it in the main process, expose the renderer bridge through preload, and call `notifyAppReady()` on every launch so rollback protection knows the bundle is healthy. + +## Installation + +```bash +bun add @capgo/electron-updater +``` + +## Main process setup + +```typescript +import { app, BrowserWindow } from 'electron'; +import path from 'node:path'; +import { ElectronUpdater, setupEventForwarding, setupIPCHandlers } from '@capgo/electron-updater'; + +const updater = new ElectronUpdater({ + appId: 'com.example.desktop', + autoUpdate: true, +}); + +app.whenReady().then(async () => { + const window = new BrowserWindow({ + webPreferences: { + preload: path.join(__dirname, 'preload.js'), + contextIsolation: true, + }, + }); + + const builtinPath = path.join(__dirname, 'www/index.html'); + await updater.initialize(window, builtinPath); + setupIPCHandlers(updater); + setupEventForwarding(updater, window); + + await window.loadFile(updater.getCurrentBundlePath()); +}); +``` + +## Preload bridge + +```typescript +import { exposeUpdaterAPI } from '@capgo/electron-updater/preload'; + +exposeUpdaterAPI(); +``` + +## Renderer usage + +```typescript +import { requireUpdater } from '@capgo/electron-updater/renderer'; + +const updater = requireUpdater(); + +await updater.notifyAppReady(); + +const latest = await updater.getLatest(); + +if (latest.url && !latest.error) { + const bundle = await updater.download({ + url: latest.url, + version: latest.version, + checksum: latest.checksum, + }); + + await updater.next({ id: bundle.id }); +} +``` + +## Listen for update events + +```typescript +updater.addListener('download', ({ percent }) => { + console.log('Download progress', percent); +}); + +updater.addListener('updateFailed', ({ bundle }) => { + console.error('Update failed', bundle.version); +}); +``` + +## Deploy a new bundle + +```bash +bun run build +bunx @capgo/cli@latest bundle upload --channel=production +``` + +## Practical advice + +- Always call `notifyAppReady()` early in the renderer so rollback protection works as intended. +- Keep the builtin path stable and let the updater decide whether to load the shipped bundle or a downloaded one. +- Reuse the same Capgo channel and rollout model you already use on mobile when your Electron app shares a backend release pipeline. diff --git a/src/data/npm-downloads.json b/src/data/npm-downloads.json index 770c71ba0..36a2ae208 100644 --- a/src/data/npm-downloads.json +++ b/src/data/npm-downloads.json @@ -1,119 +1,123 @@ { - "@capgo/native-market": 12846, - "@capgo/capacitor-native-biometric": 480444, - "@capgo/camera-preview": 36507, - "@capgo/capacitor-updater": 690611, - "@capgo/capacitor-uploader": 12666, - "@revenuecat/purchases-capacitor": 684337, - "@capgo/capacitor-flash": 36456, - "@capgo/capacitor-screen-recorder": 16174, - "@capgo/capacitor-crisp": 12025, - "@capgo/capacitor-intercom": 422, - "@capgo/capacitor-appsflyer": 184, - "@capgo/nativegeocoder": 20322, - "@capgo/inappbrowser": 350683, + "@capgo/native-market": 12815, + "@capgo/capacitor-native-biometric": 483908, + "@capgo/camera-preview": 37132, + "@capgo/capacitor-updater": 698084, + "@capgo/capacitor-uploader": 12923, + "@revenuecat/purchases-capacitor": 691688, + "@capgo/capacitor-flash": 36461, + "@capgo/capacitor-screen-recorder": 16124, + "@capgo/capacitor-crisp": 11991, + "@capgo/capacitor-intercom": 429, + "@capgo/capacitor-appsflyer": 191, + "@capgo/nativegeocoder": 20603, + "@capgo/inappbrowser": 350916, "@capgo/capacitor-mqtt": 55, - "@capgo/capacitor-mute": 37064, - "@capgo/native-audio": 40200, - "@capgo/capacitor-shake": 27562, - "@capgo/capacitor-navigation-bar": 133788, - "@capgo/ivs-player": 551, - "@capgo/home-indicator": 18920, - "@capgo/native-purchases": 80264, - "@capgo/capacitor-data-storage-sqlite": 7548, - "@capgo/capacitor-android-usagestatsmanager": 5741, + "@capgo/capacitor-mute": 37114, + "@capgo/native-audio": 40398, + "@capgo/capacitor-shake": 27880, + "@capgo/capacitor-navigation-bar": 135232, + "@capgo/ivs-player": 546, + "@capgo/home-indicator": 19107, + "@capgo/native-purchases": 81163, + "@capgo/capacitor-data-storage-sqlite": 7561, + "@capgo/capacitor-android-usagestatsmanager": 5778, "@capgo/capacitor-streamcall": 0, - "@capgo/capacitor-autofill-save-password": 28005, - "@capgo/capacitor-social-login": 473953, + "@capgo/capacitor-autofill-save-password": 27971, + "@capgo/capacitor-social-login": 478094, "@capgo/capacitor-jw-player": 2007, "@capgo/capacitor-ricoh360-camera-plugin": 0, - "@capgo/capacitor-admob": 2590, - "@capgo/capacitor-alarm": 908, - "@capgo/capacitor-android-inline-install": 3040, - "@capgo/capacitor-android-kiosk": 992, + "@capgo/capacitor-admob": 2598, + "@capgo/capacitor-alarm": 895, + "@capgo/capacitor-android-inline-install": 3080, + "@capgo/capacitor-android-kiosk": 997, "@capgo/capacitor-appinsights": 0, - "@capgo/capacitor-app-attest": 1140, + "@capgo/capacitor-app-attest": 1215, "@capgo/capacitor-audiosession": 0, "@capgo/capacitor-background-geolocation": 0, - "@capgo/capacitor-document-scanner": 20093, - "@capgo/capacitor-downloader": 4418, - "@capgo/capacitor-env": 5321, - "@capgo/capacitor-ffmpeg": 617, - "@capgo/capacitor-gtm": 1864, + "@capgo/capacitor-document-scanner": 20549, + "@capgo/capacitor-downloader": 4452, + "@capgo/capacitor-env": 5368, + "@capgo/capacitor-ffmpeg": 630, + "@capgo/capacitor-gtm": 1863, "@capgo/capacitor-rudderstack": 0, - "@capgo/capacitor-health": 61298, - "@capgo/capacitor-is-root": 5742, - "@capgo/capacitor-app-tracking-transparency": 5329, - "@capgo/capacitor-launch-navigator": 9214, + "@capgo/capacitor-health": 62160, + "@capgo/capacitor-is-root": 5885, + "@capgo/capacitor-app-tracking-transparency": 5522, + "@capgo/capacitor-launch-navigator": 9301, "@capgo/capacitor-live-reload": 963, - "@capgo/capacitor-llm": 1515, - "@capgo/capacitor-media-session": 9876, - "@capgo/capacitor-mux-player": 1036, - "@capgo/capacitor-pay": 7193, - "@capgo/capacitor-privacy-screen": 68, - "@capgo/capacitor-pdf-generator": 3893, - "@capgo/capacitor-persistent-account": 13793, - "@capgo/capacitor-photo-library": 1572, - "@capgo/capacitor-sim": 3896, - "@capgo/capacitor-speech-recognition": 24638, - "@capgo/capacitor-textinteraction": 2206, - "@capgo/capacitor-twilio-voice": 48190, - "@capgo/capacitor-video-player": 6096, - "@capgo/capacitor-volume-buttons": 1015, - "@capgo/capacitor-youtube-player": 3870, - "@capgo/capacitor-wechat": 2647, - "@capgo/capacitor-ibeacon": 3910, - "@capgo/capacitor-nfc": 18332, - "@capgo/capacitor-age-range": 447, + "@capgo/capacitor-llm": 1512, + "@capgo/capacitor-media-session": 9994, + "@capgo/capacitor-mux-player": 1038, + "@capgo/capacitor-pay": 7233, + "@capgo/capacitor-privacy-screen": 69, + "@capgo/capacitor-pdf-generator": 3939, + "@capgo/capacitor-persistent-account": 13738, + "@capgo/capacitor-photo-library": 1567, + "@capgo/capacitor-sim": 3891, + "@capgo/capacitor-speech-recognition": 25450, + "@capgo/capacitor-textinteraction": 2193, + "@capgo/capacitor-twilio-voice": 48584, + "@capgo/capacitor-video-player": 6123, + "@capgo/capacitor-volume-buttons": 1004, + "@capgo/capacitor-youtube-player": 3889, + "@capgo/capacitor-wechat": 2650, + "@capgo/capacitor-ibeacon": 3953, + "@capgo/capacitor-nfc": 18519, + "@capgo/capacitor-age-range": 457, "@capgo/capacitor-persona": 225, - "@capgo/capacitor-intune": 45, - "@capgo/capacitor-android-age-signals": 987, + "@capgo/capacitor-intune": 50, + "@capgo/capacitor-android-age-signals": 1002, "@capgo/capacitor-barometer": 1904, - "@capgo/capacitor-accelerometer": 2604, - "@capgo/capacitor-contacts": 4283, - "@capgo/capacitor-contentsquare": 144, - "@capgo/capacitor-audio-recorder": 22307, - "@capgo/capacitor-share-target": 12245, - "@capgo/capacitor-realtimekit": 2218, - "@capgo/capacitor-pedometer": 6232, - "@capgo/capacitor-fast-sql": 2875, - "@capgo/capacitor-file-compressor": 3150, - "@capgo/capacitor-speech-synthesis": 2383, - "@capgo/capacitor-supabase": 1135, - "@capgo/capacitor-ssl-pinning": 43, - "@capgo/transitions": 1605, - "@capgo/capacitor-printer": 16953, - "@capgo/capacitor-zip": 1832, + "@capgo/capacitor-accelerometer": 2596, + "@capgo/capacitor-contacts": 4415, + "@capgo/capacitor-audio-recorder": 22848, + "@capgo/capacitor-share-target": 12627, + "@capgo/capacitor-realtimekit": 2222, + "@capgo/capacitor-pedometer": 6282, + "@capgo/capacitor-fast-sql": 2887, + "@capgo/capacitor-file-compressor": 3158, + "@capgo/capacitor-speech-synthesis": 2377, + "@capgo/capacitor-ssl-pinning": 46, + "@capgo/capacitor-printer": 17188, + "@capgo/capacitor-zip": 1869, "@capgo/capacitor-zebra-datawedge": 50, - "@capgo/capacitor-wifi": 8608, + "@capgo/capacitor-wifi": 8688, "@capgo/capacitor-screen-orientation": 10097, - "@capgo/capacitor-webview-guardian": 1571, + "@capgo/capacitor-webview-guardian": 1615, "@capgo/capacitor-webview-version-checker": 277, - "@capgo/capacitor-firebase-analytics": 737, - "@capgo/capacitor-firebase-app": 35, - "@capgo/capacitor-firebase-app-check": 37, - "@capgo/capacitor-firebase-authentication": 567, - "@capgo/capacitor-firebase-crashlytics": 732, - "@capgo/capacitor-firebase-firestore": 42, + "@capgo/capacitor-firebase-analytics": 753, + "@capgo/capacitor-firebase-app": 33, + "@capgo/capacitor-firebase-app-check": 34, + "@capgo/capacitor-firebase-authentication": 573, + "@capgo/capacitor-firebase-crashlytics": 737, + "@capgo/capacitor-firebase-firestore": 39, "@capgo/capacitor-firebase-functions": 38, - "@capgo/capacitor-firebase-messaging": 509, - "@capgo/capacitor-firebase-performance": 497, - "@capgo/capacitor-firebase-remote-config": 34, + "@capgo/capacitor-firebase-messaging": 512, + "@capgo/capacitor-firebase-performance": 500, + "@capgo/capacitor-firebase-remote-config": 31, "@capgo/capacitor-firebase-storage": 357, "@capacitor-plus/core": 590, - "@capacitor-plus/cli": 528, + "@capacitor-plus/cli": 529, "@capacitor-plus/android": 539, "@capacitor-plus/ios": 397, - "@capgo/capacitor-compass": 5789, + "@capgo/capacitor-compass": 5847, "@capgo/capacitor-file": 3232, - "@capgo/capacitor-bluetooth-low-energy": 3736, - "@capgo/capacitor-keep-awake": 2174, - "@capgo/capacitor-in-app-review": 17925, - "@capgo/capacitor-incoming-call-kit": 41, - "@capgo/capacitor-file-picker": 4661, - "@capgo/capacitor-watch": 1970, - "@capgo/capacitor-brightness": 1738, + "@capgo/capacitor-bluetooth-low-energy": 3740, + "@capgo/capacitor-keep-awake": 2188, + "@capgo/capacitor-in-app-review": 17788, + "@capgo/capacitor-contentsquare": 146, + "@capgo/capacitor-supabase": 1138, + "@capgo/transitions": 1616, + "@capgo/capacitor-incoming-call-kit": 43, + "@capgo/electron-updater": 984, + "@capgo/capacitor-live-activities": 0, + "@capgo/capacitor-twilio-video": 0, + "@capgo/capacitor-widget-kit": 0, + "@capgo/capacitor-file-picker": 4757, + "@capgo/capacitor-watch": 2012, + "@capgo/capacitor-brightness": 1741, "@capgo/capacitor-light-sensor": 846, - "@capgo/capacitor-video-thumbnails": 1833, - "@capgo/capacitor-intent-launcher": 3064 + "@capgo/capacitor-video-thumbnails": 1844, + "@capgo/capacitor-intent-launcher": 3085 } From b5b002d9c9acbb376f3731d1f9dc540ed25de0da Mon Sep 17 00:00:00 2001 From: Martin Donadieu Date: Sat, 4 Apr 2026 19:00:57 +0200 Subject: [PATCH 6/8] Fix plugin tutorial locale fallback --- src/pages/plugins.astro | 20 +++++++++++++++---- src/pages/plugins/[slug].astro | 35 +++++++++++++++++----------------- 2 files changed, 34 insertions(+), 21 deletions(-) diff --git a/src/pages/plugins.astro b/src/pages/plugins.astro index 4e586b500..13d5f4639 100644 --- a/src/pages/plugins.astro +++ b/src/pages/plugins.astro @@ -40,13 +40,20 @@ const makeSeoTitle = (primary: string, descriptionText: string) => { const title = makeSeoTitle(m.plugins({}, { locale }), description) -const tutorialSlugs = new Set() +const tutorialSlugsByLocale = new Map>() await getCollection('plugin', ({ data, filePath }) => { if (data.published !== false && filePath) { - tutorialSlugs.add(getSlug(filePath).replace('.md', '')) + const tutorialLocale = data.locale || defaultLocale + const tutorialSlug = getSlug(filePath).replace('.md', '') + const localeTutorialSlugs = tutorialSlugsByLocale.get(tutorialLocale) ?? new Set() + + localeTutorialSlugs.add(tutorialSlug) + tutorialSlugsByLocale.set(tutorialLocale, localeTutorialSlugs) } }) +const defaultTutorialSlugs = tutorialSlugsByLocale.get(defaultLocale) ?? new Set() +const localizedTutorialSlugs = locale === defaultLocale ? defaultTutorialSlugs : (tutorialSlugsByLocale.get(locale) ?? new Set()) const { localizedDocsSlugs, docsSlugs } = await getPluginDocsSlugs(locale) const pluginsWithStars = getPluginsWithStars(actions) @@ -58,13 +65,18 @@ const plugins = await Promise.all( pluginsWithStars.map(async (item) => { const tutorialSlug = getSlug(item.href).replace('.md', '') const docsSlug = resolvePluginDocsSlug(item, docsSlugs) - const hasTutorial = tutorialSlugs.has(tutorialSlug) + const tutorialLocale = localizedTutorialSlugs.has(tutorialSlug) ? locale : defaultTutorialSlugs.has(tutorialSlug) ? defaultLocale : undefined + const hasTutorial = Boolean(tutorialLocale) const docsLocale = docsSlug && localizedDocsSlugs.has(docsSlug) ? locale : defaultLocale return { ...item, description: await marked.parse(item.description), - docsHref: hasTutorial ? getRelativeLocaleUrl(locale, `plugins/${tutorialSlug}`) : docsSlug ? getRelativeLocaleUrl(docsLocale, `docs/plugins/${docsSlug}/`) : item.href, + docsHref: hasTutorial + ? getRelativeLocaleUrl(tutorialLocale, `plugins/${tutorialSlug}`) + : docsSlug + ? getRelativeLocaleUrl(docsLocale, `docs/plugins/${docsSlug}/`) + : item.href, hasSiteDocs: hasTutorial || Boolean(docsSlug), } }), diff --git a/src/pages/plugins/[slug].astro b/src/pages/plugins/[slug].astro index 87dfe8596..dadb2787b 100644 --- a/src/pages/plugins/[slug].astro +++ b/src/pages/plugins/[slug].astro @@ -37,7 +37,9 @@ const plugin = actions.find((item) => getSlug(item.href) === Astro.params.slug) if (!plugin || !plugin.title) return new Response(`plugin is not found for: ${Astro.url.pathname}`, { status: 404 }) let tutorialPlugin = '' -const thisTut = (Astro.props.posts as CollectionEntry<'plugin'>[]).find((i) => (i.data.locale || defaultLocale) === Astro.locals.locale) +const tutorialEntries = Astro.props.posts as CollectionEntry<'plugin'>[] +const thisTut = + tutorialEntries.find((i) => (i.data.locale || defaultLocale) === Astro.locals.locale) ?? tutorialEntries.find((i) => (i.data.locale || defaultLocale) === defaultLocale) if (thisTut?.body) tutorialPlugin = thisTut.body if (tutorialPlugin.length > 0) { @@ -140,19 +142,18 @@ content['ldJSON'] = createSoftwareApplicationLdJson(Astro.locals.runtimeConfig.p : undefined, }) - const { title, description, githubStars, npmDownloads, href, name, tutorial, icon } = plugin --- -
      -
      - +
      + -
      - @@ -161,10 +162,10 @@ const { title, description, githubStars, npmDownloads, href, name, tutorial, ico {title} -->
      -
      -