@karaoke-cms/theme-default 0.9.3 → 0.9.5

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
@@ -1,65 +1,85 @@
1
1
  # @karaoke-cms/theme-default
2
2
 
3
- Two-column knowledge base theme for karaoke-cms. Includes blog, docs, and tags sections with a clean system-UI design.
3
+ Two-column knowledge base theme for karaoke-cms blog, docs, and tags with a clean system-UI design and automatic dark mode.
4
4
 
5
- ## Where it belongs
5
+ ## Installation
6
6
 
7
- `packages/theme-default/` in the monorepo. Activated via `karaoke.config.ts`:
7
+ ```bash
8
+ npm install @karaoke-cms/theme-default @karaoke-cms/module-blog @karaoke-cms/module-docs
9
+ ```
10
+
11
+ ## Usage
8
12
 
9
13
  ```ts
10
14
  // karaoke.config.ts
15
+ import { defineConfig } from '@karaoke-cms/astro';
11
16
  import { loadEnv } from '@karaoke-cms/astro/env';
12
- const { KARAOKE_VAULT } = loadEnv(new URL('.', import.meta.url));
13
-
14
- export default {
15
- vault: KARAOKE_VAULT,
16
- theme: '@karaoke-cms/theme-default',
17
- };
17
+ import { blog } from '@karaoke-cms/module-blog';
18
+ import { docs } from '@karaoke-cms/module-docs';
19
+ import { themeDefault } from '@karaoke-cms/theme-default';
20
+
21
+ const env = loadEnv(new URL('.', import.meta.url));
22
+
23
+ export default defineConfig({
24
+ vault: env.KARAOKE_VAULT,
25
+ title: 'My Site',
26
+ theme: themeDefault({
27
+ implements: [
28
+ blog({ mount: '/blog' }),
29
+ docs({ mount: '/docs' }),
30
+ ],
31
+ }),
32
+ });
18
33
  ```
19
34
 
20
- `@karaoke-cms/astro` resolves the theme string to this package and loads it as a nested Astro integration at build time.
35
+ ## Configuration
36
+
37
+ Pass modules to `implements` to tell the theme which content sections to wire up. Modules listed here get their routes injected from their own npm packages.
21
38
 
22
- ## What it does
39
+ | Option | Type | Default | Description |
40
+ |--------|------|---------|-------------|
41
+ | `implements` | `ModuleInstance[]` | `[]` | Content modules this theme integrates |
23
42
 
24
- Injects all site routes and sets the `@theme` Vite alias so that `Base.astro` can resolve `@theme/styles.css`:
43
+ ## Routes
25
44
 
26
- | Route | Page |
27
- |-------|------|
45
+ | Route | Description |
46
+ |-------|-------------|
28
47
  | `/` | Home — recent blog posts + recent docs |
29
- | `/blog` | Blog index all published posts, sorted by date |
30
- | `/blog/[slug]` | Blog post full content with related posts |
31
- | `/docs` | Docs index all published docs, sorted alphabetically |
32
- | `/docs/[slug]` | Doc page full content |
33
- | `/tags` | Tags index — all tags with post counts |
34
- | `/tags/[tag]` | Tag page — posts filtered by tag |
48
+ | `/blog`, `/blog/[slug]`, `/blog/page/[page]` | Blog section (from `module-blog`) |
49
+ | `/docs`, `/docs/list`, `/docs/[slug]` | Docs section (from `module-docs`) |
50
+ | `/tags` | All tags with post counts |
51
+ | `/tags/[tag]` | Posts filtered by tag |
35
52
  | `/404` | Not found page |
36
53
 
37
- ### Design system
54
+ Routes for modules listed in `implements` are injected by the module package. Other routes are injected by the theme as fallbacks.
38
55
 
39
- `src/styles.css` defines all tokens as CSS variables on `:root`:
56
+ ## Design system
57
+
58
+ All tokens are CSS variables on `:root`:
40
59
 
41
60
  - **Typography**: `--font-body`, `--font-mono`, `--font-size-base`, `--font-size-sm/lg/xl`
42
- - **Color**: `--color-bg`, `--color-text`, `--color-muted`, `--color-border`, `--color-link`, `--color-link-hover`, `--color-link-visited`
61
+ - **Color**: `--color-bg`, `--color-text`, `--color-muted`, `--color-border`, `--color-link`
43
62
  - **Spacing**: `--spacing-xs/sm/md/lg/xl`
44
- - **Sizing**: `--width-content` (680px), `--width-site` (800px), `--radius-sm`
63
+ - **Sizing**: `--width-content` (680px), `--width-site` (800px)
45
64
 
46
65
  Dark mode is automatic via `@media (prefers-color-scheme: dark)` — no JavaScript toggle.
47
66
 
48
- ## How to use
67
+ ## Layout regions
49
68
 
50
- Install alongside `@karaoke-cms/astro`:
69
+ Configure sidebar content in `karaoke.config.ts`:
51
70
 
52
- ```bash
53
- npm install @karaoke-cms/astro @karaoke-cms/theme-default
71
+ ```ts
72
+ layout: {
73
+ regions: {
74
+ right: { components: ['recent-posts'] },
75
+ },
76
+ }
54
77
  ```
55
78
 
56
- Set `theme: '@karaoke-cms/theme-default'` (or omit — it's the default) in `karaoke.config.ts`.
57
-
58
- Your vault needs `blog/` and/or `docs/` directories with Markdown files. Only files with `publish: true` in frontmatter appear on the site.
79
+ Available: `'header'`, `'main-menu'`, `'search'`, `'recent-posts'`, `'footer'`.
59
80
 
60
- ## How it changes the behavior of the system
81
+ ## What's new in 0.9.5
61
82
 
62
- - Provides the only routes that make up the site's public pages. Without a theme, nothing is served at `/`.
63
- - The `@theme` alias means `Base.astro` and all pages resolve styles and components from this package at build time. Switching themes is a one-line change in `karaoke.config.ts`.
64
- - Does not include `/docs` routes — that differentiates it from `theme-blog`. Suitable for mixed blog + documentation sites.
65
- - The layout is driven by `Base.astro`'s region system — sidebar content (recent posts, search) is controlled by the `layout.regions` config in `karaoke.config.ts`, not hardcoded in the theme.
83
+ - **`themeDefault()` function API** replaces the old string `theme: '@karaoke-cms/theme-default'`
84
+ - **`implements` option** pass `blog()` and/or `docs()` module instances to wire up content sections; routes come from the module packages
85
+ - No user-facing visual changes in this release
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.3",
4
+ "version": "0.9.5",
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,11 +17,11 @@
17
17
  "karaoke-cms"
18
18
  ],
19
19
  "dependencies": {
20
- "@karaoke-cms/module-blog": "workspace:*"
20
+ "@karaoke-cms/module-blog": "^0.9.5"
21
21
  },
22
22
  "peerDependencies": {
23
23
  "astro": ">=6.0.0",
24
- "@karaoke-cms/astro": "^0.9.2"
24
+ "@karaoke-cms/astro": "^0.9.5"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@karaoke-cms/astro": "workspace:*",
package/src/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { AstroIntegration } from 'astro';
2
+ import type { ModuleInstance } from '@karaoke-cms/astro';
2
3
  import { fileURLToPath } from 'url';
3
4
  import { existsSync } from 'fs';
4
5
 
@@ -7,27 +8,30 @@ const __dirname = fileURLToPath(new URL('.', import.meta.url));
7
8
  // Constructs the Astro integration for theme-default.
8
9
  // Exported separately so it can be called by both the new factory (themeDefault)
9
10
  // and the legacy default export without triggering a @karaoke-cms/astro import.
10
- function buildIntegration(): AstroIntegration {
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 {
11
15
  return {
12
16
  name: '@karaoke-cms/theme-default',
13
17
  hooks: {
14
18
  'astro:config:setup': ({ injectRoute, updateConfig, config: astroConfig }) => {
15
- // Blog routes are owned by @karaoke-cms/module-blog.
16
- // theme-default injects them here so that the old string-based theme
17
- // config (theme: '@karaoke-cms/theme-default') continues to work.
18
- // When the new modules[] array config is fully adopted, theme-default
19
- // will stop injecting these and let the core integration do it.
20
- injectRoute({ pattern: '/blog', entrypoint: '@karaoke-cms/module-blog/pages/list' });
21
- injectRoute({ pattern: '/blog/[slug]', entrypoint: '@karaoke-cms/module-blog/pages/post' });
19
+ // Blog post route: fall back to module-blog post page when blog is not in implements[].
20
+ if (!implementedModuleIds.includes('blog')) {
21
+ injectRoute({ pattern: '/blog/[slug]', entrypoint: '@karaoke-cms/module-blog/pages/post' });
22
+ }
23
+
24
+ // Docs routes: fall back to built-in pages when docs is not in implements[].
25
+ if (!implementedModuleIds.includes('docs')) {
26
+ injectRoute({ pattern: '/docs', entrypoint: `${__dirname}pages/docs/index.astro` });
27
+ injectRoute({ pattern: '/docs/[slug]', entrypoint: `${__dirname}pages/docs/[slug].astro` });
28
+ }
22
29
 
23
- // Docs, tags, and homepage remain owned by theme-default.
24
30
  // Skip injecting '/' if the user already has src/pages/index.astro.
25
31
  const userIndex = fileURLToPath(new URL('src/pages/index.astro', astroConfig.root));
26
32
  if (!existsSync(userIndex)) {
27
33
  injectRoute({ pattern: '/', entrypoint: `${__dirname}pages/index.astro` });
28
34
  }
29
- injectRoute({ pattern: '/docs', entrypoint: `${__dirname}pages/docs/index.astro` });
30
- injectRoute({ pattern: '/docs/[slug]', entrypoint: `${__dirname}pages/docs/[slug].astro` });
31
35
  injectRoute({ pattern: '/tags', entrypoint: `${__dirname}pages/tags/index.astro` });
32
36
  injectRoute({ pattern: '/tags/[tag]', entrypoint: `${__dirname}pages/tags/[tag].astro` });
33
37
  injectRoute({ pattern: '/404', entrypoint: `${__dirname}pages/404.astro` });
@@ -47,10 +51,6 @@ function buildIntegration(): AstroIntegration {
47
51
  /**
48
52
  * New API: returns a ThemeInstance for use with defineConfig().
49
53
  *
50
- * Note: ThemeInstance is constructed manually here (without importing defineTheme
51
- * from @karaoke-cms/astro) so that this file remains loadable via Node's native
52
- * ESM when used through the legacy string-based theme config path.
53
- *
54
54
  * @example
55
55
  * // karaoke.config.ts
56
56
  * import { themeDefault } from '@karaoke-cms/theme-default';
@@ -58,12 +58,15 @@ function buildIntegration(): AstroIntegration {
58
58
  * theme: themeDefault({ implements: [blog({ mount: '/blog' })] }),
59
59
  * });
60
60
  */
61
- export function themeDefault(config: { implements?: Array<{ id: string }> } = {}) {
61
+ export function themeDefault(config: { implements?: ModuleInstance[] } = {}) {
62
+ const implementedModules = config.implements ?? [];
63
+ const implementedModuleIds = implementedModules.map(m => m.id);
62
64
  return {
63
65
  _type: 'theme-instance' as const,
64
66
  id: 'theme-default',
65
- implementedModuleIds: (config.implements ?? []).map(m => m.id),
66
- toAstroIntegration: () => buildIntegration(),
67
+ implementedModuleIds,
68
+ implementedModules,
69
+ toAstroIntegration: () => buildIntegration(implementedModuleIds),
67
70
  };
68
71
  }
69
72
 
@@ -72,7 +75,9 @@ export function themeDefault(config: { implements?: Array<{ id: string }> } = {}
72
75
  * theme: '@karaoke-cms/theme-default'
73
76
  * The core integration calls themeModule.default(config) when loading
74
77
  * themes by package name string.
78
+ *
79
+ * Injects blog routes directly (legacy path — blog module not in modules[]).
75
80
  */
76
81
  export default function themeDefaultLegacy(_config: unknown): AstroIntegration {
77
- return buildIntegration();
82
+ return buildIntegration([]);
78
83
  }
package/src/styles.css CHANGED
@@ -87,6 +87,10 @@ header {
87
87
  margin-bottom: var(--spacing-xl);
88
88
  }
89
89
 
90
+ header.landing-header {
91
+ margin-bottom: 0;
92
+ }
93
+
90
94
  .header-inner {
91
95
  max-width: var(--width-landing);
92
96
  margin: 0 auto;
@@ -107,6 +111,10 @@ header {
107
111
  letter-spacing: -0.01em;
108
112
  }
109
113
 
114
+ .site-name:visited {
115
+ color: var(--color-on-dark);
116
+ }
117
+
110
118
  .site-name:hover {
111
119
  color: #fff;
112
120
  }
@@ -134,6 +142,10 @@ header nav ul a {
134
142
  transition: color 0.15s;
135
143
  }
136
144
 
145
+ header nav ul a:visited {
146
+ color: #94a3b8;
147
+ }
148
+
137
149
  header nav ul a:hover,
138
150
  header nav ul a[aria-current="page"] {
139
151
  color: var(--color-on-dark);
@@ -271,6 +283,10 @@ footer a {
271
283
  text-decoration: none;
272
284
  }
273
285
 
286
+ footer a:visited {
287
+ color: var(--color-on-dark-2);
288
+ }
289
+
274
290
  footer a:hover {
275
291
  color: var(--color-on-dark);
276
292
  }
@@ -284,6 +300,10 @@ footer a:hover {
284
300
  padding-bottom: var(--spacing-xs);
285
301
  }
286
302
 
303
+ .footer-col nav.karaoke-menu > ul > li > a:visited {
304
+ color: var(--color-on-dark);
305
+ }
306
+
287
307
  .footer-col nav.karaoke-menu > ul > li > ul a {
288
308
  font-size: var(--font-size-sm);
289
309
  color: #94a3b8;
@@ -291,6 +311,10 @@ footer a:hover {
291
311
  padding: 2px 0;
292
312
  }
293
313
 
314
+ .footer-col nav.karaoke-menu > ul > li > ul a:visited {
315
+ color: #94a3b8;
316
+ }
317
+
294
318
  .footer-col nav.karaoke-menu > ul > li > ul a:hover {
295
319
  color: var(--color-on-dark);
296
320
  }
@@ -616,12 +640,12 @@ nav.karaoke-menu[data-orientation="vertical"] li ul {
616
640
 
617
641
  /* ── Header CTA button ──────────────────────────────────────────────── */
618
642
 
619
- .btn-cta {
643
+ a.btn-cta {
620
644
  font-size: var(--font-size-sm);
621
645
  font-weight: 600;
622
646
  color: #fff;
623
647
  background: var(--color-accent);
624
- padding: 5px 14px;
648
+ padding: 6px 20px;
625
649
  border-radius: 999px;
626
650
  text-decoration: none;
627
651
  transition: opacity 0.15s;
@@ -629,8 +653,9 @@ nav.karaoke-menu[data-orientation="vertical"] li ul {
629
653
  white-space: nowrap;
630
654
  }
631
655
 
632
- .btn-cta:hover,
633
- .btn-cta:visited {
656
+ a.btn-cta:link,
657
+ a.btn-cta:visited,
658
+ a.btn-cta:hover {
634
659
  opacity: 0.85;
635
660
  color: #fff;
636
661
  text-decoration: none;