diff --git a/AGENTS.md b/AGENTS.md index 65e0213a5..cb743093b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -22,3 +22,16 @@ 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 `apps/web/src/config/plugins.ts` inside `actionDefinitionRows` with the package name, title, short description, and GitHub URL. +- Add the plugin docs in `apps/docs/src/content/docs/docs/plugins//index.mdx` and `apps/docs/src/content/docs/docs/plugins//getting-started.mdx`. +- Add the English tutorial in `apps/web/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/` -> `apps/web/src/content/plugins-tutorials/en/capacitor-live-activities.md` + `https://github.com/Cap-go/electron-updater/` -> `apps/web/src/content/plugins-tutorials/en/electron-updater.md` +- Register documented plugins in `apps/docs/src/config/sidebar.mjs` so they appear in the Starlight plugin sidebar. +- Register documented plugins in `apps/docs/src/config/llmsCustomSets.ts` so they appear in the docs search and LLM 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 bunx astro check` in both `apps/web` and `apps/docs`, or `bun run check` from the repo root. diff --git a/apps/docs/src/components/doc/PluginOverview.astro b/apps/docs/src/components/doc/PluginOverview.astro new file mode 100644 index 000000000..531069c96 --- /dev/null +++ b/apps/docs/src/components/doc/PluginOverview.astro @@ -0,0 +1,24 @@ +--- +import { Card, CardGrid } from '@astrojs/starlight/components' + +interface PluginOverviewCard { + body: string + title: string +} + +interface Props { + cards: PluginOverviewCard[] + summary: string +} + +const { cards, summary } = Astro.props as Props +--- + +
+

Plugin overview

+

{summary}

+ + + {cards.map((card) => {card.body})} + +
diff --git a/apps/docs/src/components/doc/PluginSetupSteps.astro b/apps/docs/src/components/doc/PluginSetupSteps.astro new file mode 100644 index 000000000..8ad8b03e1 --- /dev/null +++ b/apps/docs/src/components/doc/PluginSetupSteps.astro @@ -0,0 +1,33 @@ +--- +import { PackageManagers } from 'starlight-package-managers' + +interface Props { + installLabel?: string + pkg: string + stepTitle: string +} + +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. +
  3. +

    + Sync native platforms +

    + +
  4. +
  5. +

    + {stepTitle} +

    + +
  6. +
diff --git a/apps/docs/src/components/doc/PluginsDirectory.astro b/apps/docs/src/components/doc/PluginsDirectory.astro new file mode 100644 index 000000000..fdb68fb63 --- /dev/null +++ b/apps/docs/src/components/doc/PluginsDirectory.astro @@ -0,0 +1,48 @@ +--- +import { CardGrid, LinkCard } from '@astrojs/starlight/components' +import { actions } from '@/config/plugins' +import { defaultLocale } from '@/services/locale' +import { getPluginDocsSlugs, resolvePluginDocsSlug } from '@/services/pluginDocs' +import { getRelativeLocaleUrl } from 'astro:i18n' + +const locale = Astro.locals.locale ?? defaultLocale +const { localizedDocsSlugs, docsSlugs } = await getPluginDocsSlugs(locale) +const seenPluginLinks = new Set() + +const plugins = actions + .map((plugin) => { + const docsSlug = resolvePluginDocsSlug(plugin, docsSlugs) + if (docsSlug) { + const docsKey = `docs:${docsSlug}` + if (seenPluginLinks.has(docsKey)) return null + + seenPluginLinks.add(docsKey) + + const docsLocale = localizedDocsSlugs.has(docsSlug) ? locale : defaultLocale + + return { + description: plugin.description, + href: getRelativeLocaleUrl(docsLocale, `docs/plugins/${docsSlug}/`), + title: plugin.title, + } + } + + const fallbackKey = `repo:${plugin.href}` + if (seenPluginLinks.has(fallbackKey)) return null + seenPluginLinks.add(fallbackKey) + + return { + description: `${plugin.description} Opens the plugin repository until a dedicated docs page is available.`, + href: plugin.href, + title: plugin.title, + } + }) + .filter((plugin): plugin is NonNullable => Boolean(plugin)) + .sort((left, right) => left.title.localeCompare(right.title)) +--- + +

{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/apps/docs/src/config/llmsCustomSets.ts b/apps/docs/src/config/llmsCustomSets.ts index b0df976ad..54b4bd2ed 100644 --- a/apps/docs/src/config/llmsCustomSets.ts +++ b/apps/docs/src/config/llmsCustomSets.ts @@ -4,6 +4,7 @@ Console Tutorial|step-by-step tutorial to get started with Capgo Console and liv Public API|full reference documentation for public API|docs/public-api/** Plugin Accelerometer|accelerometer sensor plugin for detecting device motion and orientation|docs/plugins/accelerometer/** Plugin AdMob|Google AdMob plugin for mobile advertising integration|docs/plugins/admob/** +Plugin Age Range|cross-platform age range detection plugin|docs/plugins/age-range/** Plugin Age Signals|Android age signals plugin for age verification|docs/plugins/age-signals/** Plugin Alarm|alarm and notification scheduling plugin|docs/plugins/alarm/** Plugin Android Inline Install|Android inline app installation plugin|docs/plugins/android-inline-install/** @@ -12,6 +13,7 @@ Plugin Android Kiosk|Android kiosk mode plugin for locked-down device experience Plugin AppInsights|Microsoft Application Insights analytics plugin|docs/plugins/appinsights/** Plugin AppsFlyer|AppsFlyer mobile attribution, analytics, and deep linking plugin|docs/plugins/appsflyer/** Plugin App Attest|cross-platform app attestation plugin using Apple App Attest and Google Play Integrity Standard|docs/plugins/app-attest/** +Plugin App Tracking Transparency|iOS app tracking transparency permission plugin|docs/plugins/app-tracking-transparency/** Plugin Audio Recorder|audio recording plugin for capturing microphone input|docs/plugins/audio-recorder/** Plugin Audio Session|iOS audio session configuration plugin|docs/plugins/audiosession/** Plugin Autofill Save Password|autofill and password save plugin for credential management|docs/plugins/autofill-save-password/** @@ -49,6 +51,7 @@ Plugin JW Player|JW Player video integration plugin|docs/plugins/jw-player/** Plugin Keep Awake|screen wake lock plugin to prevent sleep|docs/plugins/keep-awake/** Plugin Launch Navigator|native maps navigation plugin|docs/plugins/launch-navigator/** Plugin Light Sensor|ambient light sensor plugin|docs/plugins/light-sensor/** +Plugin Live Activities|iOS Live Activities and Dynamic Island plugin|docs/plugins/live-activities/** Plugin Live Reload|development live reload plugin|docs/plugins/live-reload/** Plugin LLM|on-device large language model plugin|docs/plugins/llm/** Plugin Media Session|media session and playback controls plugin|docs/plugins/media-session/** @@ -84,6 +87,7 @@ Plugin Speech Synthesis|text-to-speech synthesis plugin|docs/plugins/speech-synt Plugin SSL Pinning|certificate pinning plugin for CapacitorHttp requests|docs/plugins/ssl-pinning/** Plugin StreamCall|Stream video calling plugin|docs/plugins/streamcall/** Plugin Text Interaction|text selection and interaction plugin|docs/plugins/textinteraction/** +Plugin Twilio Video|Twilio Video room integration plugin|docs/plugins/twilio-video/** Plugin Twilio Voice|Twilio voice calling plugin|docs/plugins/twilio-voice/** Plugin Uploader|file upload plugin with background support|docs/plugins/uploader/** Plugin Video Player|native video player plugin|docs/plugins/video-player/** @@ -94,6 +98,7 @@ Plugin WeChat|WeChat integration plugin|docs/plugins/wechat/** Plugin Webview Guardian|webview security and protection plugin|docs/plugins/webview-guardian/** Plugin Webview Version Checker|Android WebView version validation plugin|docs/plugins/webview-version-checker/** Plugin WiFi|WiFi network information plugin|docs/plugins/wifi/** +Plugin Widget Kit|WidgetKit and Live Activities template plugin|docs/plugins/widget-kit/** Plugin Zebra DataWedge|Zebra DataWedge plugin for barcode profiles, notifications, and scan intents|docs/plugins/zebra-datawedge/** Plugin YouTube Player|YouTube video player plugin|docs/plugins/youtube-player/** Plugin Zip|file compression and extraction plugin|docs/plugins/zip/** @@ -107,7 +112,9 @@ Plugin Firebase Functions|Firebase Cloud Functions plugin|docs/plugins/firebase- Plugin Firebase Messaging|Firebase Cloud Messaging push notifications plugin|docs/plugins/firebase-messaging/** Plugin Firebase Performance|Firebase Performance Monitoring plugin|docs/plugins/firebase-performance/** Plugin Firebase Remote Config|Firebase Remote Config plugin|docs/plugins/firebase-remote-config/** -Plugin Firebase Storage|Firebase Cloud Storage plugin|docs/plugins/firebase-storage/**`.trim().split('\n') +Plugin Firebase Storage|Firebase Cloud Storage plugin|docs/plugins/firebase-storage/**` + .trim() + .split('\n') export const docsLlmsCustomSets = llmsCustomSetRows.map((row, index) => { const [label, description, ...paths] = row.split('|').map((part) => part.trim()) diff --git a/apps/docs/src/config/plugins.ts b/apps/docs/src/config/plugins.ts new file mode 100644 index 000000000..1fd183b1a --- /dev/null +++ b/apps/docs/src/config/plugins.ts @@ -0,0 +1 @@ +export { actions, type Action, type Plugin } from '../../../web/src/config/plugins' diff --git a/apps/docs/src/config/sidebar.mjs b/apps/docs/src/config/sidebar.mjs index efd81966b..1f5267835 100644 --- a/apps/docs/src/config/sidebar.mjs +++ b/apps/docs/src/config/sidebar.mjs @@ -2,61 +2,71 @@ const withCollapsed = (value, collapsed) => (collapsed === undefined ? value : { const linkItem = (label, link) => ({ label, link }) -const autogeneratedItem = (label, directory, collapsed) => withCollapsed({ - label, - autogenerate: { directory }, -}, collapsed) +const autogeneratedItem = (label, directory, collapsed) => + withCollapsed( + { + label, + autogenerate: { directory }, + }, + collapsed, + ) const section = (label, items, collapsed) => withCollapsed({ label, items }, collapsed) -const pluginDoc = (label, slug, extraItems = []) => section(label, [ - linkItem('Overview', `/docs/plugins/${slug}/`), - linkItem('Getting started', `/docs/plugins/${slug}/getting-started`), - ...extraItems, -], true) +const pluginDoc = (label, slug, extraItems = []) => + section(label, [linkItem('Overview', `/docs/plugins/${slug}/`), linkItem('Getting started', `/docs/plugins/${slug}/getting-started`), ...extraItems], true) -const updaterPluginSection = section('Updater', [ - linkItem('Getting Started', '/docs/plugins/updater'), - linkItem('Events', '/docs/plugins/updater/events'), - linkItem('API Reference', '/docs/plugins/updater/api'), - linkItem('Configuration', '/docs/plugins/updater/settings'), - linkItem('notifyAppReady call placement', '/docs/plugins/updater/notify-app-ready'), - linkItem('Known Issues', '/docs/plugins/updater/known-issues'), - linkItem('Common Update Problems', '/docs/plugins/updater/commonproblems'), - linkItem('Debugging', '/docs/plugins/updater/debugging'), - linkItem('Cordova Migration', '/docs/plugins/updater/cordova'), - autogeneratedItem('Local Development', '/docs/plugins/updater/local-dev', true), - section('Self-Hosting', [ - linkItem('Getting Started', '/docs/plugins/updater/self-hosted/getting-started'), - linkItem('Auto Update', '/docs/plugins/updater/self-hosted/auto-update'), - linkItem('Manual Update', '/docs/plugins/updater/self-hosted/manual-update'), - linkItem('Encrypted Bundles', '/docs/plugins/updater/self-hosted/encrypted-bundles'), - linkItem('Update API Endpoint', '/docs/plugins/updater/self-hosted/handling-updates'), - linkItem('Statistics API Endpoint', '/docs/plugins/updater/self-hosted/handling-stats'), - linkItem('Channel API Endpoint', '/docs/plugins/updater/self-hosted/handling-channels'), - ], true), - autogeneratedItem('Migrations', 'docs/upgrade', true), -], true) +const updaterPluginSection = section( + 'Updater', + [ + linkItem('Getting Started', '/docs/plugins/updater'), + linkItem('Events', '/docs/plugins/updater/events'), + linkItem('API Reference', '/docs/plugins/updater/api'), + linkItem('Configuration', '/docs/plugins/updater/settings'), + linkItem('notifyAppReady call placement', '/docs/plugins/updater/notify-app-ready'), + linkItem('Known Issues', '/docs/plugins/updater/known-issues'), + linkItem('Common Update Problems', '/docs/plugins/updater/commonproblems'), + linkItem('Debugging', '/docs/plugins/updater/debugging'), + linkItem('Cordova Migration', '/docs/plugins/updater/cordova'), + autogeneratedItem('Local Development', '/docs/plugins/updater/local-dev', true), + section( + 'Self-Hosting', + [ + linkItem('Getting Started', '/docs/plugins/updater/self-hosted/getting-started'), + linkItem('Auto Update', '/docs/plugins/updater/self-hosted/auto-update'), + linkItem('Manual Update', '/docs/plugins/updater/self-hosted/manual-update'), + linkItem('Encrypted Bundles', '/docs/plugins/updater/self-hosted/encrypted-bundles'), + linkItem('Update API Endpoint', '/docs/plugins/updater/self-hosted/handling-updates'), + linkItem('Statistics API Endpoint', '/docs/plugins/updater/self-hosted/handling-stats'), + linkItem('Channel API Endpoint', '/docs/plugins/updater/self-hosted/handling-channels'), + ], + true, + ), + autogeneratedItem('Migrations', 'docs/upgrade', true), + ], + true, +) const pluginEntries = [ ['Accelerometer', 'accelerometer'], ['AdMob', 'admob'], + ['Age Range', 'age-range'], ['Age Signals', 'age-signals'], ['Alarm', 'alarm'], ['Android Inline Install', 'android-inline-install'], - ['Android Usage Stats', 'android-usagestatsmanager'], ['Android Kiosk', 'android-kiosk'], + ['Android Usage Stats', 'android-usagestatsmanager'], + ['App Attest', 'app-attest', [linkItem('iOS setup', '/docs/plugins/app-attest/ios'), linkItem('Android setup', '/docs/plugins/app-attest/android')]], + ['App Tracking Transparency', 'app-tracking-transparency'], ['AppInsights', 'appinsights'], ['AppsFlyer', 'appsflyer'], - ['App Attest', 'app-attest', [ - linkItem('iOS setup', '/docs/plugins/app-attest/ios'), - linkItem('Android setup', '/docs/plugins/app-attest/android'), - ]], ['Audio Recorder', 'audio-recorder'], ['Audio Session', 'audiosession'], ['Autofill Save Password', 'autofill-save-password'], ['Background Geolocation', 'background-geolocation'], ['Barometer', 'barometer'], + ['Bluetooth Low Energy', 'bluetooth-low-energy'], + ['Brightness', 'brightness'], ['Camera Preview', 'camera-preview'], ['Capacitor+', 'capacitor-plus'], ['Compass', 'compass'], @@ -65,23 +75,30 @@ const pluginEntries = [ ['Data Storage SQLite', 'data-storage-sqlite'], ['Document Scanner', 'document-scanner'], ['Downloader', 'downloader'], + ['Electron Updater', 'electron-updater'], ['Env', 'env'], ['Fast SQL', 'fast-sql'], ['FFmpeg', 'ffmpeg'], ['File', 'file'], ['File Compressor', 'file-compressor'], + ['File Picker', 'file-picker'], ['Flash', 'flash'], ['GTM', 'gtm'], ['RudderStack', 'rudderstack'], ['Health', 'health'], ['Home Indicator', 'home-indicator'], ['iBeacon', 'ibeacon'], + ['In App Review', 'in-app-review'], ['InAppBrowser', 'inappbrowser'], + ['Intent Launcher', 'intent-launcher'], ['Intercom', 'intercom'], ['Is Root', 'is-root'], ['IVS Player', 'ivs-player'], ['JW Player', 'jw-player'], + ['Keep Awake', 'keep-awake'], ['Launch Navigator', 'launch-navigator'], + ['Light Sensor', 'light-sensor'], + ['Live Activities', 'live-activities'], ['Live Reload', 'live-reload'], ['LLM', 'llm'], ['Media Session', 'media-session'], @@ -92,37 +109,35 @@ const pluginEntries = [ ['Native Biometric', 'native-biometric'], ['Native Geocoder', 'nativegeocoder'], ['Native Market', 'native-market'], - section('Native Purchases', [ - linkItem('Overview', '/docs/plugins/native-purchases/'), - linkItem('Getting started', '/docs/plugins/native-purchases/getting-started'), - section('Android Setup', [ - linkItem('Sandbox Testing', '/docs/plugins/native-purchases/android-sandbox-testing'), - linkItem('Create Subscription', '/docs/plugins/native-purchases/android-create-subscription'), - linkItem('Introductory Offers', '/docs/plugins/native-purchases/android-introductory-offer'), - linkItem('Play Store Review', '/docs/plugins/native-purchases/android-play-store-review'), - ]), - section('iOS Setup', [ - linkItem('Sandbox Testing', '/docs/plugins/native-purchases/ios-sandbox-testing'), - linkItem('Subscription Groups', '/docs/plugins/native-purchases/ios-subscription-group'), - linkItem('Create Subscription', '/docs/plugins/native-purchases/ios-create-subscription'), - linkItem('Introductory Offers', '/docs/plugins/native-purchases/ios-introductory-offer'), - linkItem('App Store Review', '/docs/plugins/native-purchases/ios-app-store-review'), - ]), - ], true), + section( + 'Native Purchases', + [ + linkItem('Overview', '/docs/plugins/native-purchases/'), + linkItem('Getting started', '/docs/plugins/native-purchases/getting-started'), + section('Android Setup', [ + linkItem('Sandbox Testing', '/docs/plugins/native-purchases/android-sandbox-testing'), + linkItem('Create Subscription', '/docs/plugins/native-purchases/android-create-subscription'), + linkItem('Introductory Offers', '/docs/plugins/native-purchases/android-introductory-offer'), + linkItem('Play Store Review', '/docs/plugins/native-purchases/android-play-store-review'), + ]), + section('iOS Setup', [ + linkItem('Sandbox Testing', '/docs/plugins/native-purchases/ios-sandbox-testing'), + linkItem('Subscription Groups', '/docs/plugins/native-purchases/ios-subscription-group'), + linkItem('Create Subscription', '/docs/plugins/native-purchases/ios-create-subscription'), + linkItem('Introductory Offers', '/docs/plugins/native-purchases/ios-introductory-offer'), + linkItem('App Store Review', '/docs/plugins/native-purchases/ios-app-store-review'), + ]), + ], + true, + ), ['Navigation Bar', 'navigation-bar'], ['NFC', 'nfc'], ['Pay', 'pay'], - ['Privacy Screen', 'privacy-screen', [ - linkItem('iOS behavior', '/docs/plugins/privacy-screen/ios'), - linkItem('Android behavior', '/docs/plugins/privacy-screen/android'), - ]], + ['Privacy Screen', 'privacy-screen', [linkItem('iOS behavior', '/docs/plugins/privacy-screen/ios'), linkItem('Android behavior', '/docs/plugins/privacy-screen/android')]], ['PDF Generator', 'pdf-generator'], ['Pedometer', 'pedometer'], ['Persona', 'persona'], - ['Intune', 'intune', [ - linkItem('iOS', '/docs/plugins/intune/ios'), - linkItem('Android', '/docs/plugins/intune/android'), - ]], + ['Intune', 'intune', [linkItem('iOS', '/docs/plugins/intune/ios'), linkItem('Android', '/docs/plugins/intune/android')]], ['Persistent Account', 'persistent-account'], ['Photo Library', 'photo-library'], ['Printer', 'printer'], @@ -136,76 +151,85 @@ const pluginEntries = [ ['Speech Recognition', 'speech-recognition'], ['Speech Synthesis', 'speech-synthesis'], ['SSL Pinning', 'ssl-pinning'], - section('Social Login', [ - linkItem('Overview', '/docs/plugins/social-login/'), - linkItem('Getting started', '/docs/plugins/social-login/getting-started'), - autogeneratedItem('Google', 'docs/plugins/social-login/google'), - autogeneratedItem('Apple', 'docs/plugins/social-login/apple'), - linkItem('Facebook', '/docs/plugins/social-login/facebook'), - linkItem('OAuth2', '/docs/plugins/social-login/oauth2'), - section('Integrations', [ - linkItem('Overview', '/docs/plugins/social-login/integrations/'), - linkItem('Better Auth', '/docs/plugins/social-login/better-auth'), - section('OAuth Providers', [ - linkItem('Auth0', '/docs/plugins/social-login/integrations/auth0'), - linkItem('Microsoft Entra ID', '/docs/plugins/social-login/integrations/azure'), - linkItem('AWS Cognito', '/docs/plugins/social-login/integrations/cognito'), - linkItem('GitHub', '/docs/plugins/social-login/integrations/github'), - linkItem('Keycloak', '/docs/plugins/social-login/integrations/keycloak'), - linkItem('Okta', '/docs/plugins/social-login/integrations/okta'), - linkItem('OneLogin', '/docs/plugins/social-login/integrations/onelogin'), - ]), - autogeneratedItem('Firebase', 'docs/plugins/social-login/firebase'), - section('Supabase', [ - linkItem('Introduction', '/docs/plugins/social-login/supabase/introduction'), - autogeneratedItem('Google', 'docs/plugins/social-login/supabase/google'), - section('Apple', [ - linkItem('Supabase Apple Login - General Setup', '/docs/plugins/social-login/supabase/apple/general'), - linkItem('Supabase Apple Login on iOS Setup', '/docs/plugins/social-login/supabase/apple/ios'), - linkItem('Supabase Apple Login on Android Setup', '/docs/plugins/social-login/supabase/apple/android'), - linkItem('Supabase Apple Login on Web Setup', '/docs/plugins/social-login/supabase/apple/web'), + section( + 'Social Login', + [ + linkItem('Overview', '/docs/plugins/social-login/'), + linkItem('Getting started', '/docs/plugins/social-login/getting-started'), + autogeneratedItem('Google', 'docs/plugins/social-login/google'), + autogeneratedItem('Apple', 'docs/plugins/social-login/apple'), + linkItem('Facebook', '/docs/plugins/social-login/facebook'), + linkItem('OAuth2', '/docs/plugins/social-login/oauth2'), + section('Integrations', [ + linkItem('Overview', '/docs/plugins/social-login/integrations/'), + linkItem('Better Auth', '/docs/plugins/social-login/better-auth'), + section('OAuth Providers', [ + linkItem('Auth0', '/docs/plugins/social-login/integrations/auth0'), + linkItem('Microsoft Entra ID', '/docs/plugins/social-login/integrations/azure'), + linkItem('AWS Cognito', '/docs/plugins/social-login/integrations/cognito'), + linkItem('GitHub', '/docs/plugins/social-login/integrations/github'), + linkItem('Keycloak', '/docs/plugins/social-login/integrations/keycloak'), + linkItem('Okta', '/docs/plugins/social-login/integrations/okta'), + linkItem('OneLogin', '/docs/plugins/social-login/integrations/onelogin'), + ]), + autogeneratedItem('Firebase', 'docs/plugins/social-login/firebase'), + section('Supabase', [ + linkItem('Introduction', '/docs/plugins/social-login/supabase/introduction'), + autogeneratedItem('Google', 'docs/plugins/social-login/supabase/google'), + section('Apple', [ + linkItem('Supabase Apple Login - General Setup', '/docs/plugins/social-login/supabase/apple/general'), + linkItem('Supabase Apple Login on iOS Setup', '/docs/plugins/social-login/supabase/apple/ios'), + linkItem('Supabase Apple Login on Android Setup', '/docs/plugins/social-login/supabase/apple/android'), + linkItem('Supabase Apple Login on Web Setup', '/docs/plugins/social-login/supabase/apple/web'), + ]), ]), ]), - ]), - autogeneratedItem('Migrations', 'docs/plugins/social-login/migrations'), - ], true), + autogeneratedItem('Migrations', 'docs/plugins/social-login/migrations'), + ], + true, + ), ['StreamCall', 'streamcall'], ['Text Interaction', 'textinteraction'], + ['Twilio Video', 'twilio-video'], ['Twilio Voice', 'twilio-voice'], ['Uploader', 'uploader'], ['Video Player', 'video-player'], + ['Video Thumbnails', 'video-thumbnails'], ['Volume Buttons', 'volume-buttons'], + ['Watch', 'watch'], ['WeChat', 'wechat'], - ['WebView Version Checker', 'webview-version-checker', [ - linkItem('Android setup', '/docs/plugins/webview-version-checker/android'), - linkItem('iOS', '/docs/plugins/webview-version-checker/ios'), - ]], + [ + 'WebView Version Checker', + 'webview-version-checker', + [linkItem('Android setup', '/docs/plugins/webview-version-checker/android'), linkItem('iOS', '/docs/plugins/webview-version-checker/ios')], + ], + ['Widget Kit', 'widget-kit'], ['WiFi', 'wifi'], ['YouTube Player', 'youtube-player'], ['Zip', 'zip'], - ['Zebra DataWedge', 'zebra-datawedge', [ - linkItem('Android behavior', '/docs/plugins/zebra-datawedge/android'), - ]], + ['Zebra DataWedge', 'zebra-datawedge', [linkItem('Android behavior', '/docs/plugins/zebra-datawedge/android')]], ] const pluginItems = [ linkItem('Getting Started', '/docs/plugins/'), updaterPluginSection, - ...pluginEntries.map((entry) => ( - Array.isArray(entry) ? pluginDoc(entry[0], entry[1], entry[2] ?? []) : entry - )), + ...pluginEntries.map((entry) => (Array.isArray(entry) ? pluginDoc(entry[0], entry[1], entry[2] ?? []) : entry)), linkItem('👋 Get a custom plugin', '/consulting/'), ] export const docsSidebar = [ linkItem('Welcome to Capgo', '/docs/'), autogeneratedItem('Quickstart', 'docs/getting-started', false), - section('Capgo CLI', [ - linkItem('Overview', '/docs/cli/overview'), - autogeneratedItem('Command reference', 'docs/cli/reference', false), - autogeneratedItem('Cloud Build', 'docs/cli/cloud-build', false), - autogeneratedItem('Migrations', 'docs/cli/migrations', true), - ], true), + section( + 'Capgo CLI', + [ + linkItem('Overview', '/docs/cli/overview'), + autogeneratedItem('Command reference', 'docs/cli/reference', false), + autogeneratedItem('Cloud Build', 'docs/cli/cloud-build', false), + autogeneratedItem('Migrations', 'docs/cli/migrations', true), + ], + true, + ), autogeneratedItem('Live Updates', 'docs/live-updates', true), autogeneratedItem('Public API', 'docs/public-api', true), section('Plugins', pluginItems, true), diff --git a/apps/docs/src/content/docs/docs/plugins/index.mdx b/apps/docs/src/content/docs/docs/plugins/index.mdx index f6bb69570..a2919aff5 100644 --- a/apps/docs/src/content/docs/docs/plugins/index.mdx +++ b/apps/docs/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/apps/docs/src/content/docs/docs/plugins/live-activities/getting-started.mdx b/apps/docs/src/content/docs/docs/plugins/live-activities/getting-started.mdx new file mode 100644 index 000000000..f14e6f6b8 --- /dev/null +++ b/apps/docs/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/apps/docs/src/content/docs/docs/plugins/live-activities/index.mdx b/apps/docs/src/content/docs/docs/plugins/live-activities/index.mdx new file mode 100644 index 000000000..1a2fd52ab --- /dev/null +++ b/apps/docs/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/apps/docs/src/content/docs/docs/plugins/twilio-video/getting-started.mdx b/apps/docs/src/content/docs/docs/plugins/twilio-video/getting-started.mdx new file mode 100644 index 000000000..b70101627 --- /dev/null +++ b/apps/docs/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/apps/docs/src/content/docs/docs/plugins/twilio-video/index.mdx b/apps/docs/src/content/docs/docs/plugins/twilio-video/index.mdx new file mode 100644 index 000000000..88b7d656a --- /dev/null +++ b/apps/docs/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/apps/docs/src/content/docs/docs/plugins/widget-kit/getting-started.mdx b/apps/docs/src/content/docs/docs/plugins/widget-kit/getting-started.mdx new file mode 100644 index 000000000..e88b82a38 --- /dev/null +++ b/apps/docs/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/apps/docs/src/content/docs/docs/plugins/widget-kit/index.mdx b/apps/docs/src/content/docs/docs/plugins/widget-kit/index.mdx new file mode 100644 index 000000000..60efdfa5a --- /dev/null +++ b/apps/docs/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/apps/docs/src/services/pluginDocs.ts b/apps/docs/src/services/pluginDocs.ts new file mode 100644 index 000000000..bd607c59a --- /dev/null +++ b/apps/docs/src/services/pluginDocs.ts @@ -0,0 +1,126 @@ +import type { Action } from '@/config/plugins' +import { defaultLocale, locales, type Locales } from '@/services/locale' +import { globSync } from 'glob' +import { fileURLToPath } from 'node:url' + +type PluginReference = Pick +type PluginDocsSlugs = { + defaultDocsSlugs: Set + localizedDocsSlugs: Set + docsSlugs: Set +} + +const DOCS_CONTENT_ROOT = fileURLToPath(new URL('../content/docs/', import.meta.url)) + +const pluginDocsIndexPromise: Promise>> = Promise.resolve( + globSync('**/plugins/*/index.{md,mdx}', { cwd: DOCS_CONTENT_ROOT, nodir: true }).reduce((docsByLocale, filePath) => { + const docEntry = getPluginDocEntry(filePath) + if (!docEntry) return docsByLocale + + const localeDocs = docsByLocale.get(docEntry.locale) ?? new Set() + localeDocs.add(docEntry.slug) + docsByLocale.set(docEntry.locale, localeDocs) + + return docsByLocale + }, new Map>()), +) + +const getPackageSegment = (name?: string): string => { + if (!name) return '' + const slashIndex = name.indexOf('/') + return slashIndex >= 0 ? name.slice(slashIndex + 1) : name +} + +const getPluginDocEntry = (filePath?: string): { locale: Locales; slug: string } | null => { + if (!filePath) return null + + const segments = filePath.replaceAll('\\', '/').split('/').filter(Boolean) + if (segments.length < 3) return null + + let locale: Locales = defaultLocale as Locales + 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) +} + +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)) +} + +export const getPluginDocsSlugs = async (locale?: string): Promise => { + const docsByLocale = await pluginDocsIndexPromise + const normalizedLocale = locales.includes(locale as Locales) ? (locale as Locales) : (defaultLocale as Locales) + const defaultDocsSlugs = docsByLocale.get(defaultLocale as Locales) ?? 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]), + } +} diff --git a/apps/web/src/config/plugins.ts b/apps/web/src/config/plugins.ts index 390a25727..9f84556e7 100644 --- a/apps/web/src/config/plugins.ts +++ b/apps/web/src/config/plugins.ts @@ -19,10 +19,12 @@ export interface Plugin extends Action { locale?: string } -const actionDefinitionRows = String.raw`@capgo/native-market|github.com/Cap-go|Deep link users directly to your app page on Google Play Store or Apple App Store|https://github.com/Cap-go/capacitor-native-market/|Native Market +const actionDefinitionRows = + String.raw`@capgo/native-market|github.com/Cap-go|Deep link users directly to your app page on Google Play Store or Apple App Store|https://github.com/Cap-go/capacitor-native-market/|Native Market @capgo/capacitor-native-biometric|github.com/Cap-go|Secure authentication using Face ID, Touch ID, and Android biometric APIs|https://github.com/Cap-go/capacitor-native-biometric/|Native Biometric @capgo/camera-preview|github.com/Cap-go|Display live camera feed as overlay with customizable controls and capture capabilities|https://github.com/Cap-go/capacitor-camera-preview/|Camera Preview @capgo/capacitor-updater|github.com/Cap-go|Deploy live updates instantly to your users without app store review delays|https://github.com/Cap-go/capacitor-updater/|Updater +@capgo/electron-updater|github.com/Cap-go|OTA live updates for Electron apps with the same API surface as capacitor-updater|https://github.com/Cap-go/electron-updater/|Electron Updater @capgo/capacitor-uploader|github.com/Cap-go|Upload large files reliably in background with progress tracking and retry support|https://github.com/Cap-go/capacitor-uploader/|Uploader @revenuecat/purchases-capacitor|github.com/Cap-go|Implement in-app subscriptions and purchases with RevenueCat SDK for cross-platform monetization|https://github.com/RevenueCat/purchases-capacitor/|Purchases @capgo/capacitor-flash|github.com/Cap-go|Control device flashlight and torch with simple on/off toggle functionality|https://github.com/Cap-go/capacitor-flash/|Flash @@ -65,6 +67,7 @@ const actionDefinitionRows = String.raw`@capgo/native-market|github.com/Cap-go|D @capgo/capacitor-is-root|github.com/Cap-go|Detect rooted Android or jailbroken iOS devices to enhance app security|https://github.com/Cap-go/capacitor-is-root/|Is Root @capgo/capacitor-app-tracking-transparency|github.com/Cap-go|Request and check iOS App Tracking Transparency permission for IDFA access|https://github.com/Cap-go/capacitor-app-tracking-transparency/|App Tracking Transparency @capgo/capacitor-launch-navigator|github.com/Cap-go|Open navigation apps like Google Maps or Apple Maps with directions to destinations|https://github.com/Cap-go/capacitor-launch-navigator/|Launch Navigator +@capgo/capacitor-live-activities|github.com/Cap-go|Manage iOS Live Activities and Dynamic Island layouts from Capacitor with JSON-driven templates|https://github.com/Cap-go/capacitor-live-activities/|Live Activities @capgo/capacitor-live-reload|github.com/Cap-go|Connect to your dev server for instant hot reloading during development|https://github.com/Cap-go/capacitor-live-reload/|Live Reload @capgo/capacitor-llm|github.com/Cap-go|Run Large Language Models locally on-device with Apple Intelligence and MLX support|https://github.com/Cap-go/capacitor-llm/|LLM @capgo/capacitor-media-session|github.com/Cap-go|Control media playback from lock screen and notification center|https://github.com/Cap-go/capacitor-media-session/|Media Session @@ -77,6 +80,7 @@ const actionDefinitionRows = String.raw`@capgo/native-market|github.com/Cap-go|D @capgo/capacitor-sim|github.com/Cap-go|Retrieve SIM card information including carrier name, country code, and phone number|https://github.com/Cap-go/capacitor-sim/|SIM @capgo/capacitor-speech-recognition|github.com/Cap-go|Natural, low-latency speech recognition with streaming partial results and cross-platform parity|https://github.com/Cap-go/capacitor-speech-recognition/|Speech Recognition @capgo/capacitor-textinteraction|github.com/Cap-go|Enable advanced text selection, copy-paste, and interaction features in web views|https://github.com/Cap-go/capacitor-textinteraction/|Text Interaction +@capgo/capacitor-twilio-video|github.com/Cap-go|Join Twilio Video rooms from Capacitor with native audio, camera, and room lifecycle events|https://github.com/Cap-go/capacitor-twilio-video/|Twilio Video @capgo/capacitor-twilio-voice|github.com/Cap-go|Make and receive VoIP calls with Twilio Voice for in-app calling functionality|https://github.com/Cap-go/capacitor-twilio-voice/|Twilio Voice @capgo/capacitor-video-player|github.com/Cap-go|Native video playback with subtitles, fullscreen, and comprehensive controls|https://github.com/Cap-go/capacitor-video-player/|Video Player @capgo/capacitor-volume-buttons|github.com/Cap-go|Capture hardware volume button presses for custom app controls and shortcuts|https://github.com/Cap-go/capacitor-volume-buttons/|Volume Buttons @@ -128,10 +132,13 @@ const actionDefinitionRows = String.raw`@capgo/native-market|github.com/Cap-go|D @capgo/capacitor-in-app-review|github.com/Cap-go|Prompt users to submit app store ratings and reviews without leaving your app using native iOS and Android APIs|https://github.com/Cap-go/capacitor-in-app-review/|In App Review @capgo/capacitor-file-picker|github.com/Cap-go|Pick files, images, videos, and directories with full native support for iOS and Android including HEIC conversion|https://github.com/Cap-go/capacitor-file-picker/|File Picker @capgo/capacitor-watch|github.com/Cap-go|Apple Watch communication with bidirectional messaging between iPhone and watchOS apps|https://github.com/Cap-go/capacitor-watch/|Watch +@capgo/capacitor-widget-kit|github.com/Cap-go|Build iOS widgets and Live Activities from Capacitor with SVG templates, timers, and action hotspots|https://github.com/Cap-go/capacitor-widget-kit/|Widget Kit @capgo/capacitor-brightness|github.com/Cap-go|Control device screen brightness programmatically with support for app-specific and system-wide control|https://github.com/Cap-go/capacitor-brightness/|Brightness @capgo/capacitor-light-sensor|github.com/Cap-go|Access the ambient light sensor to measure illuminance levels in lux with real-time updates|https://github.com/Cap-go/capacitor-light-sensor/|Light Sensor @capgo/capacitor-video-thumbnails|github.com/Cap-go|Generate thumbnail images from local and remote video files at specific timestamps|https://github.com/Cap-go/capacitor-video-thumbnails/|Video Thumbnails -@capgo/capacitor-intent-launcher|github.com/Cap-go|Launch Android intents, open system settings, and interact with other apps using the Intent system|https://github.com/Cap-go/capacitor-intent-launcher/|Intent Launcher`.trim().split('\n') +@capgo/capacitor-intent-launcher|github.com/Cap-go|Launch Android intents, open system settings, and interact with other apps using the Intent system|https://github.com/Cap-go/capacitor-intent-launcher/|Intent Launcher` + .trim() + .split('\n') export const actions: Action[] = actionDefinitionRows.map((row) => { const [name, author, description, href, title] = row.split('|') diff --git a/apps/web/src/content/plugins-tutorials/en/capacitor-live-activities.md b/apps/web/src/content/plugins-tutorials/en/capacitor-live-activities.md new file mode 100644 index 000000000..15e8ca4d6 --- /dev/null +++ b/apps/web/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/apps/web/src/content/plugins-tutorials/en/capacitor-twilio-video.md b/apps/web/src/content/plugins-tutorials/en/capacitor-twilio-video.md new file mode 100644 index 000000000..07701c431 --- /dev/null +++ b/apps/web/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/apps/web/src/content/plugins-tutorials/en/capacitor-widget-kit.md b/apps/web/src/content/plugins-tutorials/en/capacitor-widget-kit.md new file mode 100644 index 000000000..3eaf6cba1 --- /dev/null +++ b/apps/web/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/apps/web/src/content/plugins-tutorials/en/electron-updater.md b/apps/web/src/content/plugins-tutorials/en/electron-updater.md new file mode 100644 index 000000000..a2a64fe63 --- /dev/null +++ b/apps/web/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 built-in 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/apps/web/src/data/npm-downloads.json b/apps/web/src/data/npm-downloads.json index 7a1ed76a7..b081fa284 100644 --- a/apps/web/src/data/npm-downloads.json +++ b/apps/web/src/data/npm-downloads.json @@ -1,115 +1,119 @@ { - "@capgo/native-market": 12565, - "@capgo/capacitor-native-biometric": 483962, - "@capgo/camera-preview": 37424, - "@capgo/capacitor-updater": 701035, - "@capgo/capacitor-uploader": 12961, - "@revenuecat/purchases-capacitor": 701819, - "@capgo/capacitor-flash": 35044, - "@capgo/capacitor-screen-recorder": 16004, - "@capgo/capacitor-crisp": 11649, - "@capgo/capacitor-intercom": 437, - "@capgo/capacitor-appsflyer": 198, - "@capgo/nativegeocoder": 20815, - "@capgo/inappbrowser": 347828, - "@capgo/capacitor-mqtt": 72, - "@capgo/capacitor-mute": 36546, - "@capgo/native-audio": 40216, - "@capgo/capacitor-shake": 27966, - "@capgo/capacitor-navigation-bar": 135105, - "@capgo/ivs-player": 537, - "@capgo/home-indicator": 19025, - "@capgo/native-purchases": 82830, - "@capgo/capacitor-data-storage-sqlite": 7432, - "@capgo/capacitor-android-usagestatsmanager": 5772, + "@capgo/native-market": 12531, + "@capgo/capacitor-native-biometric": 487600, + "@capgo/camera-preview": 38060, + "@capgo/capacitor-updater": 712894, + "@capgo/electron-updater": 1048, + "@capgo/capacitor-uploader": 13408, + "@revenuecat/purchases-capacitor": 716640, + "@capgo/capacitor-flash": 34656, + "@capgo/capacitor-screen-recorder": 15945, + "@capgo/capacitor-crisp": 11671, + "@capgo/capacitor-intercom": 470, + "@capgo/capacitor-appsflyer": 256, + "@capgo/nativegeocoder": 21217, + "@capgo/inappbrowser": 346536, + "@capgo/capacitor-mqtt": 80, + "@capgo/capacitor-mute": 35981, + "@capgo/native-audio": 40729, + "@capgo/capacitor-shake": 28087, + "@capgo/capacitor-navigation-bar": 136696, + "@capgo/ivs-player": 535, + "@capgo/home-indicator": 19133, + "@capgo/native-purchases": 84890, + "@capgo/capacitor-data-storage-sqlite": 7464, + "@capgo/capacitor-android-usagestatsmanager": 5828, "@capgo/capacitor-streamcall": 0, - "@capgo/capacitor-autofill-save-password": 27377, - "@capgo/capacitor-social-login": 480110, - "@capgo/capacitor-jw-player": 1838, + "@capgo/capacitor-autofill-save-password": 27456, + "@capgo/capacitor-social-login": 486872, + "@capgo/capacitor-jw-player": 1828, "@capgo/capacitor-ricoh360-camera-plugin": 0, - "@capgo/capacitor-admob": 2606, - "@capgo/capacitor-alarm": 891, - "@capgo/capacitor-android-inline-install": 3111, - "@capgo/capacitor-android-kiosk": 1077, + "@capgo/capacitor-admob": 2642, + "@capgo/capacitor-alarm": 898, + "@capgo/capacitor-android-inline-install": 3215, + "@capgo/capacitor-android-kiosk": 1082, "@capgo/capacitor-appinsights": 0, - "@capgo/capacitor-app-attest": 1377, + "@capgo/capacitor-app-attest": 1475, "@capgo/capacitor-audiosession": 0, "@capgo/capacitor-background-geolocation": 0, - "@capgo/capacitor-document-scanner": 20733, - "@capgo/capacitor-downloader": 4485, - "@capgo/capacitor-env": 5319, - "@capgo/capacitor-ffmpeg": 644, + "@capgo/capacitor-document-scanner": 21680, + "@capgo/capacitor-downloader": 4622, + "@capgo/capacitor-env": 5300, + "@capgo/capacitor-ffmpeg": 636, "@capgo/capacitor-gtm": 1863, "@capgo/capacitor-rudderstack": 0, - "@capgo/capacitor-health": 63716, - "@capgo/capacitor-is-root": 5997, - "@capgo/capacitor-app-tracking-transparency": 5674, - "@capgo/capacitor-launch-navigator": 9331, - "@capgo/capacitor-live-reload": 963, - "@capgo/capacitor-llm": 1528, - "@capgo/capacitor-media-session": 10225, - "@capgo/capacitor-mux-player": 1040, - "@capgo/capacitor-pay": 7212, - "@capgo/capacitor-privacy-screen": 78, - "@capgo/capacitor-pdf-generator": 3964, - "@capgo/capacitor-persistent-account": 13365, - "@capgo/capacitor-photo-library": 1555, - "@capgo/capacitor-sim": 3891, - "@capgo/capacitor-speech-recognition": 26305, - "@capgo/capacitor-textinteraction": 2184, - "@capgo/capacitor-twilio-voice": 48851, - "@capgo/capacitor-video-player": 6063, - "@capgo/capacitor-volume-buttons": 997, - "@capgo/capacitor-youtube-player": 3910, - "@capgo/capacitor-wechat": 2632, - "@capgo/capacitor-ibeacon": 3955, - "@capgo/capacitor-nfc": 19058, + "@capgo/capacitor-health": 65288, + "@capgo/capacitor-is-root": 6266, + "@capgo/capacitor-app-tracking-transparency": 6036, + "@capgo/capacitor-launch-navigator": 9576, + "@capgo/capacitor-live-activities": 0, + "@capgo/capacitor-live-reload": 1031, + "@capgo/capacitor-llm": 1529, + "@capgo/capacitor-media-session": 10546, + "@capgo/capacitor-mux-player": 1041, + "@capgo/capacitor-pay": 7144, + "@capgo/capacitor-privacy-screen": 85, + "@capgo/capacitor-pdf-generator": 4011, + "@capgo/capacitor-persistent-account": 13383, + "@capgo/capacitor-photo-library": 1566, + "@capgo/capacitor-sim": 3926, + "@capgo/capacitor-speech-recognition": 27479, + "@capgo/capacitor-textinteraction": 2200, + "@capgo/capacitor-twilio-video": 0, + "@capgo/capacitor-twilio-voice": 49584, + "@capgo/capacitor-video-player": 6199, + "@capgo/capacitor-volume-buttons": 999, + "@capgo/capacitor-youtube-player": 4050, + "@capgo/capacitor-wechat": 2631, + "@capgo/capacitor-ibeacon": 3981, + "@capgo/capacitor-nfc": 19703, "@capgo/capacitor-age-range": 475, - "@capgo/capacitor-persona": 226, - "@capgo/capacitor-intune": 52, - "@capgo/capacitor-android-age-signals": 1033, - "@capgo/capacitor-barometer": 1911, - "@capgo/capacitor-accelerometer": 2575, - "@capgo/capacitor-contacts": 4556, - "@capgo/capacitor-audio-recorder": 23404, - "@capgo/capacitor-share-target": 13015, - "@capgo/capacitor-realtimekit": 2176, - "@capgo/capacitor-pedometer": 6297, - "@capgo/capacitor-fast-sql": 2893, - "@capgo/capacitor-file-compressor": 3159, - "@capgo/capacitor-speech-synthesis": 2404, - "@capgo/capacitor-ssl-pinning": 52, - "@capgo/capacitor-printer": 17446, - "@capgo/capacitor-zip": 1876, - "@capgo/capacitor-zebra-datawedge": 54, - "@capgo/capacitor-wifi": 8810, - "@capgo/capacitor-screen-orientation": 9820, - "@capgo/capacitor-webview-guardian": 1651, + "@capgo/capacitor-persona": 227, + "@capgo/capacitor-intune": 53, + "@capgo/capacitor-android-age-signals": 1034, + "@capgo/capacitor-barometer": 1925, + "@capgo/capacitor-accelerometer": 2570, + "@capgo/capacitor-contacts": 4645, + "@capgo/capacitor-audio-recorder": 24365, + "@capgo/capacitor-share-target": 13254, + "@capgo/capacitor-realtimekit": 2175, + "@capgo/capacitor-pedometer": 6444, + "@capgo/capacitor-fast-sql": 2905, + "@capgo/capacitor-file-compressor": 3181, + "@capgo/capacitor-speech-synthesis": 2455, + "@capgo/capacitor-ssl-pinning": 59, + "@capgo/capacitor-printer": 17988, + "@capgo/capacitor-zip": 1901, + "@capgo/capacitor-zebra-datawedge": 55, + "@capgo/capacitor-wifi": 9050, + "@capgo/capacitor-screen-orientation": 9845, + "@capgo/capacitor-webview-guardian": 1722, "@capgo/capacitor-webview-version-checker": 277, - "@capgo/capacitor-firebase-analytics": 758, + "@capgo/capacitor-firebase-analytics": 766, "@capgo/capacitor-firebase-app": 33, "@capgo/capacitor-firebase-app-check": 34, - "@capgo/capacitor-firebase-authentication": 569, - "@capgo/capacitor-firebase-crashlytics": 742, + "@capgo/capacitor-firebase-authentication": 566, + "@capgo/capacitor-firebase-crashlytics": 748, "@capgo/capacitor-firebase-firestore": 39, "@capgo/capacitor-firebase-functions": 38, - "@capgo/capacitor-firebase-messaging": 515, - "@capgo/capacitor-firebase-performance": 504, - "@capgo/capacitor-firebase-remote-config": 31, - "@capgo/capacitor-firebase-storage": 348, - "@capacitor-plus/core": 590, - "@capacitor-plus/cli": 529, - "@capacitor-plus/android": 539, - "@capacitor-plus/ios": 397, - "@capgo/capacitor-compass": 5866, - "@capgo/capacitor-file": 3190, - "@capgo/capacitor-bluetooth-low-energy": 3886, - "@capgo/capacitor-keep-awake": 2263, - "@capgo/capacitor-in-app-review": 17419, - "@capgo/capacitor-file-picker": 4894, - "@capgo/capacitor-watch": 1952, - "@capgo/capacitor-brightness": 1599, - "@capgo/capacitor-light-sensor": 718, - "@capgo/capacitor-video-thumbnails": 1683, - "@capgo/capacitor-intent-launcher": 2940 -} \ No newline at end of file + "@capgo/capacitor-firebase-messaging": 521, + "@capgo/capacitor-firebase-performance": 509, + "@capgo/capacitor-firebase-remote-config": 30, + "@capgo/capacitor-firebase-storage": 346, + "@capacitor-plus/core": 588, + "@capacitor-plus/cli": 524, + "@capacitor-plus/android": 537, + "@capacitor-plus/ios": 395, + "@capgo/capacitor-compass": 5899, + "@capgo/capacitor-file": 3163, + "@capgo/capacitor-bluetooth-low-energy": 3962, + "@capgo/capacitor-keep-awake": 2296, + "@capgo/capacitor-in-app-review": 17550, + "@capgo/capacitor-file-picker": 5253, + "@capgo/capacitor-watch": 1766, + "@capgo/capacitor-widget-kit": 0, + "@capgo/capacitor-brightness": 1611, + "@capgo/capacitor-light-sensor": 716, + "@capgo/capacitor-video-thumbnails": 1701, + "@capgo/capacitor-intent-launcher": 2829 +} diff --git a/apps/web/src/pages/plugins.astro b/apps/web/src/pages/plugins.astro index 811a4efb6..b717d3047 100644 --- a/apps/web/src/pages/plugins.astro +++ b/apps/web/src/pages/plugins.astro @@ -1,15 +1,18 @@ --- 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 { getPluginDocsSlugs, resolvePluginDocsSlug } from '@/services/pluginDocs' import PluginIcon from '@/components/PluginIcon.astro' import { Icon as AstroIcon } from 'astro-icon/components' import { getCollection } from 'astro:content' import { getRelativeLocaleUrl } from 'astro:i18n' import { marked } from 'marked' -const locale = Astro.locals.locale +const locale = Astro.locals.locale ?? defaultLocale const brand = Astro.locals.runtimeConfig.public.brand const description = m.plugins_description({}, { locale }) @@ -37,27 +40,43 @@ const makeSeoTitle = (primary: string, descriptionText: string) => { const title = makeSeoTitle(m.plugins({}, { locale }), description) -const content = { title, description } - -const slugsSet = new Set() +const tutorialSlugsByLocale = new Map>() await getCollection('plugin', ({ data, filePath }) => { if (data.published !== false && filePath) { - slugsSet.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) 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 tutorialLocale = localizedTutorialSlugs.has(tutorialSlug) ? locale : defaultTutorialSlugs.has(tutorialSlug) ? defaultLocale : undefined + const hasTutorial = Boolean(tutorialLocale) + const tutorialHref = tutorialLocale ? getRelativeLocaleUrl(tutorialLocale, `plugins/${tutorialSlug}`) : undefined + const docsLocale = docsSlug && localizedDocsSlugs.has(docsSlug) ? locale : defaultLocale + + return { + ...item, + description: await marked.parse(item.description), + docsHref: tutorialHref ?? (docsSlug ? getRelativeLocaleUrl(docsLocale, `docs/plugins/${docsSlug}/`) : item.href), + hasSiteDocs: hasTutorial || Boolean(docsSlug), + } + }), ) // Helper function to format counts @@ -70,6 +89,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, +} --- @@ -193,9 +238,9 @@ const formatCount = (count: number): string => {
- {item.hasTutorial ? ( + {item.hasSiteDocs ? ( Documentation diff --git a/apps/web/src/pages/plugins/[slug].astro b/apps/web/src/pages/plugins/[slug].astro index 87dfe8596..dadb2787b 100644 --- a/apps/web/src/pages/plugins/[slug].astro +++ b/apps/web/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} -->
-
-