diff --git a/output/playwright/blog-sidebar-desktop.webp b/output/playwright/blog-sidebar-desktop.webp new file mode 100644 index 0000000..7c780a4 Binary files /dev/null and b/output/playwright/blog-sidebar-desktop.webp differ diff --git a/output/playwright/blog-sidebar-mobile.webp b/output/playwright/blog-sidebar-mobile.webp new file mode 100644 index 0000000..d7168ef Binary files /dev/null and b/output/playwright/blog-sidebar-mobile.webp differ diff --git a/src/components/Blog.astro b/src/components/Blog.astro index e1a73fc..ef4a324 100644 --- a/src/components/Blog.astro +++ b/src/components/Blog.astro @@ -16,36 +16,35 @@ const { tag, link, date, title, image, loading } = Astro.props const cannLink = getRelativeLocaleUrl(Astro.locals.locale, `blog/${link}`) --- -
{tag}
} + {formatTime(date)} -+
{title}
diff --git a/src/components/BlogListing.astro b/src/components/BlogListing.astro
index e99ba15..bd47e1e 100644
--- a/src/components/BlogListing.astro
+++ b/src/components/BlogListing.astro
@@ -11,21 +11,222 @@ const content: { title?: string; description?: string; image?: string; author?:
const posts = allPosts.sort((a, b) => (new Date(a.data.created_at) > new Date(b.data.created_at) ? -1 : 1))
+const categoryRules = [
+ { label: 'Authentication', terms: ['auth', 'login', 'signin', 'sign-in', 'single-sign-on', 'sso', 'kakao', 'oauth', 'biometric', 'autofill', 'password'] },
+ { label: 'Firebase & Messaging', terms: ['firebase', 'fcm', 'push', 'notification', 'airship', 'cleverpush', 'marketingcloud', 'azure-notification', 'emarsys'] },
+ { label: 'Maps & Location', terms: ['map', 'maps', 'location', 'geolocation', 'gps'] },
+ { label: 'Media & Camera', terms: ['camera', 'photo', 'video', 'audio', 'music', 'media', 'barcode', 'scanner', 'speech', 'player', 'brightness'] },
+ { label: 'Files & Storage', terms: ['file', 'storage', 'sqlite', 'blob', 'couchbase', 'mediastore', 'document', 'docutain', 'opener', 'uploader'] },
+ { label: 'Payments & Revenue', terms: ['admob', 'ads', 'advertising', 'purchase', 'purchases', 'subscription', 'stripe', 'monetize', 'wallet'] },
+ {
+ label: 'Device APIs',
+ terms: ['bluetooth', 'contacts', 'calendar', 'sim', 'sensor', 'nfc', 'health', 'healthkit', 'device', 'printer', 'datawedge', 'shortcut', 'siri', 'call-number'],
+ },
+ { label: 'App Shell & WebView', terms: ['webview', 'tailwind', 'edge-to-edge', 'navigation-bar', 'home-indicator', 'cookies', 'user-agent'] },
+ { label: 'Background & System', terms: ['background', 'updater', 'update', 'keep-awake', 'exit-app', 'developer-options', 'system-stats', 'ssl', 'privacy-screen', 'launcher'] },
+ { label: 'Analytics & Growth', terms: ['analytics', 'mixpanel', 'facebook-events', 'appmetrica', 'adjust', 'meta', 'bugfender', 'apprate'] },
+]
+const fallbackCategory = 'Capacitor Plugins'
+
+const escapeRegex = (value: string) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
+
+const getPostCategory = (post: CollectionEntry<'blog'>) => {
+ const haystack = [post.data.title, post.data.description, post.data.slug].filter(Boolean).join(' ').toLowerCase()
+ return categoryRules.find((category) => category.terms.some((term) => new RegExp(`\\b${escapeRegex(term)}\\b`).test(haystack)))?.label ?? fallbackCategory
+}
+
+const postsWithCategories = posts.map((post) => ({ post, category: getPostCategory(post) }))
+const categoryCounts = postsWithCategories.reduce((counts, { category }) => {
+ counts.set(category, (counts.get(category) ?? 0) + 1)
+ return counts
+}, new Map Showing {posts.length} articles
+ No articles match your filters.
+ {m.latest_from_the_blog({}, { locale: Astro.locals.locale })}
-
- {config.public.blog_description}
-
- {m.latest_from_the_blog({}, { locale: Astro.locals.locale })}
+
+ {config.public.blog_description}
+
+
+ Categories
+
+
+