@karaoke-cms/theme-default 0.9.8 → 0.11.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -78,6 +78,11 @@ layout: {
78
78
 
79
79
  Available: `'header'`, `'main-menu'`, `'search'`, `'recent-posts'`, `'footer'`.
80
80
 
81
+ ## What's new in 0.10.2
82
+
83
+ - **Section-aware tags and RSS** — `/tags`, `/tags/[tag]`, and `/rss.xml` now aggregate entries across all active docs sections, not just the default `/docs` section.
84
+ - No other user-facing visual changes in this release.
85
+
81
86
  ## What's new in 0.9.5
82
87
 
83
88
  - **`themeDefault()` function API** replaces the old string `theme: '@karaoke-cms/theme-default'`
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@karaoke-cms/theme-default",
3
3
  "type": "module",
4
- "version": "0.9.8",
4
+ "version": "0.11.3",
5
5
  "description": "Default theme for karaoke-cms — two-column knowledge base with blog and docs",
6
6
  "main": "./src/index.ts",
7
7
  "exports": {
@@ -17,17 +17,18 @@
17
17
  "karaoke-cms"
18
18
  ],
19
19
  "dependencies": {
20
- "@karaoke-cms/module-blog": "^0.9.8"
20
+ "@karaoke-cms/module-blog": "0.11.3"
21
21
  },
22
22
  "peerDependencies": {
23
23
  "astro": ">=6.0.0",
24
- "@karaoke-cms/astro": "^0.9.8"
24
+ "@karaoke-cms/contracts": "^0.11.0"
25
25
  },
26
26
  "devDependencies": {
27
- "@karaoke-cms/astro": "workspace:*",
28
- "astro": "^6.0.8"
27
+ "astro": "^6.0.8",
28
+ "@karaoke-cms/astro": "0.11.3",
29
+ "@karaoke-cms/contracts": "0.11.3"
29
30
  },
30
31
  "scripts": {
31
32
  "test": "echo \"Stub — no tests\""
32
33
  }
33
- }
34
+ }
package/src/index.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { AstroIntegration } from 'astro';
2
- import type { ModuleInstance } from '@karaoke-cms/astro';
2
+ import type { ModuleInstance, ThemeInstance } from '@karaoke-cms/contracts';
3
3
  import { fileURLToPath } from 'url';
4
4
  import { existsSync } from 'fs';
5
5
 
@@ -9,20 +9,31 @@ const __dirname = fileURLToPath(new URL('.', import.meta.url));
9
9
  // Exported separately so it can be called by both the new factory (themeDefault)
10
10
  // and the legacy default export without triggering a @karaoke-cms/astro import.
11
11
  //
12
- // When `implementedModuleIds` is provided, routes for those modules are skipped —
13
- // the core integration injects them from the module package instead.
14
- function buildIntegration(implementedModuleIds: string[] = []): AstroIntegration {
12
+ // When modules provide a docs collection, theme-default's hardcoded /docs fallback
13
+ // routes are skipped karaoke() generates per-instance pages via codegen instead.
14
+ function buildIntegration(implementedModules: ModuleInstance[] = [], activeModules: ModuleInstance[] = []): AstroIntegration {
15
+ const allModules = [...implementedModules, ...activeModules];
16
+ const hasBlog = allModules.some(m => m.id === 'blog');
17
+ // Suppress the /docs fallback only when a docs() instance is mounted at /docs specifically.
18
+ // Checking allModules (both theme implements and config.modules) ensures:
19
+ // - docs() in config.modules at /docs → suppresses fallback (codegen handles it)
20
+ // - docs() at /api-docs only → keeps the /docs fallback intact
21
+ const hasDocsCollection = allModules.some(
22
+ m => m.collection != null && m.mount.replace(/\/$/, '') === '/docs'
23
+ );
24
+
15
25
  return {
16
26
  name: '@karaoke-cms/theme-default',
17
27
  hooks: {
18
28
  'astro:config:setup': ({ injectRoute, updateConfig, config: astroConfig }) => {
19
- // Blog post route: fall back to module-blog post page when blog is not in implements[].
20
- if (!implementedModuleIds.includes('blog')) {
29
+ // Blog post route: fall back to module-blog post page when no blog module is active.
30
+ if (!hasBlog) {
21
31
  injectRoute({ pattern: '/blog/[slug]', entrypoint: '@karaoke-cms/module-blog/pages/post' });
22
32
  }
23
33
 
24
- // Docs routes: fall back to built-in pages when docs is not in implements[].
25
- if (!implementedModuleIds.includes('docs')) {
34
+ // Docs routes: skip when any module owns a docs collection.
35
+ // The karaoke() core generates per-instance pages via codegen.
36
+ if (!hasDocsCollection) {
26
37
  injectRoute({ pattern: '/docs', entrypoint: `${__dirname}pages/docs/index.astro` });
27
38
  injectRoute({ pattern: '/docs/[slug]', entrypoint: `${__dirname}pages/docs/[slug].astro` });
28
39
  }
@@ -58,16 +69,14 @@ function buildIntegration(implementedModuleIds: string[] = []): AstroIntegration
58
69
  * theme: themeDefault({ implements: [blog({ mount: '/blog' })] }),
59
70
  * });
60
71
  */
61
- export function themeDefault(config: { implements?: ModuleInstance[] } = {}) {
72
+ export function themeDefault(config: { implements?: ModuleInstance[] } = {}): ThemeInstance {
62
73
  const implementedModules = config.implements ?? [];
63
- const implementedModuleIds = implementedModules.map(m => m.id);
64
74
  return {
65
- _type: 'theme-instance' as const,
75
+ _type: 'theme-instance',
66
76
  id: 'theme-default',
67
- implementedModuleIds,
68
77
  implementedModules,
69
78
  toAstroIntegration: (activeModules: ModuleInstance[] = []) =>
70
- buildIntegration([...implementedModuleIds, ...activeModules.map(m => m.id)]),
79
+ buildIntegration(implementedModules, activeModules),
71
80
  };
72
81
  }
73
82
 
@@ -80,5 +89,5 @@ export function themeDefault(config: { implements?: ModuleInstance[] } = {}) {
80
89
  * Injects blog routes directly (legacy path — blog module not in modules[]).
81
90
  */
82
91
  export default function themeDefaultLegacy(_config: unknown): AstroIntegration {
83
- return buildIntegration([]);
92
+ return buildIntegration();
84
93
  }
@@ -2,16 +2,23 @@
2
2
  import { getCollection } from 'astro:content';
3
3
  import DefaultPage from '@karaoke-cms/astro/layouts/DefaultPage.astro';
4
4
  import { siteTitle } from 'virtual:karaoke-cms/config';
5
+ import { docsSections } from 'virtual:karaoke-cms/docs-sections';
5
6
 
6
7
  export async function getStaticPaths() {
7
- const [blog, docs] = await Promise.all([
8
- getCollection('blog', ({ data }) => data.publish === true),
9
- getCollection('docs', ({ data }) => data.publish === true),
10
- ]);
8
+ const blogEntries = await getCollection('blog', ({ data }) => data.publish === true);
9
+
10
+ // Fetch from all active docs collections; allSettled so a missing collection
11
+ // doesn't abort the entire tags page (e.g., if content.config.ts wasn't updated yet).
12
+ const docsSettled = await Promise.allSettled(
13
+ docsSections.map(s => getCollection(s.collection as any, ({ data }) => data.publish === true))
14
+ );
15
+ const docsEntries = docsSettled.flatMap(r => r.status === 'fulfilled' ? r.value : []);
16
+
17
+ const allEntries = [...blogEntries, ...docsEntries];
11
18
 
12
19
  // Collect all unique tags
13
20
  const tags = new Set<string>();
14
- for (const entry of [...blog, ...docs]) {
21
+ for (const entry of allEntries) {
15
22
  for (const tag of entry.data.tags ?? []) tags.add(tag);
16
23
  }
17
24
 
@@ -19,7 +26,7 @@ export async function getStaticPaths() {
19
26
  params: { tag },
20
27
  props: {
21
28
  tag,
22
- entries: [...blog, ...docs]
29
+ entries: allEntries
23
30
  .filter(e => e.data.tags?.includes(tag))
24
31
  .sort((a, b) => (b.data.date?.valueOf() ?? 0) - (a.data.date?.valueOf() ?? 0)),
25
32
  },
@@ -28,9 +35,12 @@ export async function getStaticPaths() {
28
35
 
29
36
  const { tag, entries } = Astro.props;
30
37
 
31
- // Determine the URL prefix for each entry by its collection
38
+ // Map collection name mount path for correct href generation
39
+ const mountByCollection = Object.fromEntries(docsSections.map(s => [s.collection, s.mount]));
40
+
32
41
  function href(entry: { collection: string; id: string }) {
33
- return `/${entry.collection}/${entry.id}`;
42
+ const mount = mountByCollection[entry.collection];
43
+ return mount ? `${mount}/${entry.id}` : `/${entry.collection}/${entry.id}`;
34
44
  }
35
45
  ---
36
46
 
@@ -2,15 +2,18 @@
2
2
  import { getCollection } from 'astro:content';
3
3
  import DefaultPage from '@karaoke-cms/astro/layouts/DefaultPage.astro';
4
4
  import { siteTitle } from 'virtual:karaoke-cms/config';
5
+ import { docsSections } from 'virtual:karaoke-cms/docs-sections';
5
6
 
6
- const [blog, docs] = await Promise.all([
7
- getCollection('blog', ({ data }) => data.publish === true),
8
- getCollection('docs', ({ data }) => data.publish === true),
9
- ]);
7
+ const blog = await getCollection('blog', ({ data }) => data.publish === true);
10
8
 
11
- // Count occurrences of each tag across both collections
9
+ const docsSettled = await Promise.allSettled(
10
+ docsSections.map(s => getCollection(s.collection as any, ({ data }) => data.publish === true))
11
+ );
12
+ const docsEntries = docsSettled.flatMap(r => r.status === 'fulfilled' ? r.value : []);
13
+
14
+ // Count occurrences of each tag across all collections
12
15
  const counts = new Map<string, number>();
13
- for (const entry of [...blog, ...docs]) {
16
+ for (const entry of [...blog, ...docsEntries]) {
14
17
  for (const tag of entry.data.tags ?? []) {
15
18
  counts.set(tag, (counts.get(tag) ?? 0) + 1);
16
19
  }