astro-blog-kit 0.3.0 → 0.3.2

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/define-config.ts CHANGED
@@ -1,9 +1,8 @@
1
1
  // ─────────────────────────────────────────────────────────────
2
2
  // astro-blog-kit · define-config.ts
3
- // Función helper para tipar la config del blog.
4
3
  // ─────────────────────────────────────────────────────────────
5
4
 
6
- import type { BlogKitConfig, BlogTheme } from "./types";
5
+ import type { BlogKitConfig, BlogTheme, BlogHero, BlogUI } from "./types";
7
6
 
8
7
  export interface BlogConfig {
9
8
  /** URL de tu WordPress. Ej: https://cms.tudominio.com */
@@ -14,8 +13,12 @@ export interface BlogConfig {
14
13
  defaultLayout?: "grid" | "magazine" | "featured" | "cards";
15
14
  /** Locale por defecto. @default "en" */
16
15
  locale?: string;
17
- /** Tema visual */
16
+ /** Tema visual (colores, fuentes, tamaños) */
18
17
  theme?: BlogTheme;
18
+ /** Textos del hero/header del blog */
19
+ hero?: BlogHero;
20
+ /** Overrides de UI (botones, paginación, labels) */
21
+ ui?: BlogUI;
19
22
  /** Configuración de i18n */
20
23
  i18n?: {
21
24
  locales: string[];
@@ -25,7 +28,6 @@ export interface BlogConfig {
25
28
 
26
29
  /**
27
30
  * Define la configuración del blog con tipado completo.
28
- * Genera blog.config.ts en la raíz del proyecto.
29
31
  *
30
32
  * @example
31
33
  * ```ts
@@ -35,10 +37,26 @@ export interface BlogConfig {
35
37
  * export default defineBlogConfig({
36
38
  * wpUrl: 'https://cms.tudominio.com',
37
39
  * postsPerPage: 5,
38
- * defaultLayout: 'magazine',
40
+ * defaultLayout: 'featured',
39
41
  * locale: 'en',
40
42
  * theme: {
41
43
  * accent: '#facc15',
44
+ * background: '#0a1a0a',
45
+ * text: '#ffffff',
46
+ * },
47
+ * hero: {
48
+ * tagline: 'Technical Resources',
49
+ * titleLine1: 'Building',
50
+ * titleLine2: 'Insights',
51
+ * description: 'Practical knowledge for architects and engineers.',
52
+ * },
53
+ * ui: {
54
+ * readMoreLabel: 'Read more →',
55
+ * btnPrev: 'Previous',
56
+ * btnNext: 'Next',
57
+ * commentButtonColor: '#facc15',
58
+ * commentButtonTextColor: '#0a0a0a',
59
+ * paginationStyle: 'minimal',
42
60
  * },
43
61
  * });
44
62
  * ```
@@ -60,6 +78,80 @@ export function toBlogKitConfig(config: BlogConfig): BlogKitConfig {
60
78
  postsPerPage: config.postsPerPage,
61
79
  defaultLayout: config.defaultLayout,
62
80
  theme: config.theme,
81
+ hero: config.hero,
82
+ ui: config.ui,
63
83
  i18n: config.i18n,
64
84
  };
85
+ }
86
+
87
+ /**
88
+ * Resuelve los textos del hero con fallbacks según el locale.
89
+ * Usado internamente por BlogList.astro
90
+ */
91
+ export function resolveHero(
92
+ hero: BlogHero | undefined,
93
+ locale: string
94
+ ): Required<BlogHero> {
95
+ const defaults: Record<string, Required<BlogHero>> = {
96
+ en: {
97
+ tagline: "Our Blog",
98
+ titleLine1: "Latest",
99
+ titleLine2: "Articles",
100
+ description: "Welcome to our blog.",
101
+ },
102
+ es: {
103
+ tagline: "Nuestro Blog",
104
+ titleLine1: "Últimos",
105
+ titleLine2: "Artículos",
106
+ description: "Bienvenido a nuestro blog.",
107
+ },
108
+ };
109
+
110
+ const d = defaults[locale] ?? defaults["en"];
111
+
112
+ return {
113
+ tagline: hero?.tagline ?? d.tagline,
114
+ titleLine1: hero?.titleLine1 ?? d.titleLine1,
115
+ titleLine2: hero?.titleLine2 ?? d.titleLine2,
116
+ description: hero?.description ?? d.description,
117
+ };
118
+ }
119
+
120
+ /**
121
+ * Resuelve los labels de UI con fallbacks según el locale.
122
+ * Usado internamente por BlogList.astro y Pagination.astro
123
+ */
124
+ export function resolveUI(
125
+ ui: BlogUI | undefined,
126
+ locale: string
127
+ ): Required<BlogUI> {
128
+ const defaults: Record<string, Required<BlogUI>> = {
129
+ en: {
130
+ readMoreLabel: "Read more →",
131
+ btnPrev: "Previous",
132
+ btnNext: "Next",
133
+ commentButtonColor: "var(--bk-accent)",
134
+ commentButtonTextColor: "var(--bk-black)",
135
+ paginationStyle: "minimal",
136
+ },
137
+ es: {
138
+ readMoreLabel: "Leer más →",
139
+ btnPrev: "Anterior",
140
+ btnNext: "Siguiente",
141
+ commentButtonColor: "var(--bk-accent)",
142
+ commentButtonTextColor: "var(--bk-black)",
143
+ paginationStyle: "minimal",
144
+ },
145
+ };
146
+
147
+ const d = defaults[locale] ?? defaults["en"];
148
+
149
+ return {
150
+ readMoreLabel: ui?.readMoreLabel ?? d.readMoreLabel,
151
+ btnPrev: ui?.btnPrev ?? d.btnPrev,
152
+ btnNext: ui?.btnNext ?? d.btnNext,
153
+ commentButtonColor: ui?.commentButtonColor ?? d.commentButtonColor,
154
+ commentButtonTextColor: ui?.commentButtonTextColor ?? d.commentButtonTextColor,
155
+ paginationStyle: ui?.paginationStyle ?? d.paginationStyle,
156
+ };
65
157
  }
package/integration.ts CHANGED
@@ -3,8 +3,12 @@
3
3
  // ─────────────────────────────────────────────────────────────
4
4
 
5
5
  import type { AstroIntegration } from "astro";
6
+ import type { Plugin } from "vite";
6
7
  import type { BlogKitConfig, BlogTheme } from "./types";
7
8
 
9
+ const VIRTUAL_MODULE_ID = "virtual:astro-blog-kit/theme";
10
+ const RESOLVED_VIRTUAL_MODULE_ID = "\0" + VIRTUAL_MODULE_ID;
11
+
8
12
  /**
9
13
  * Genera el bloque de CSS variables a partir del tema.
10
14
  */
@@ -27,36 +31,79 @@ function generateThemeCSS(theme: BlogTheme = {}): string {
27
31
  };
28
32
 
29
33
  return `
30
- :root {
31
- --bk-accent: ${t.accent};
32
- --bk-background: ${t.background};
33
- --bk-surface: ${t.surface};
34
- --bk-text: ${t.text};
35
- --bk-muted: ${t.muted};
36
- --bk-muted-light: ${t.mutedLight};
37
- --bk-border: ${t.border};
38
- --bk-black: ${t.black};
39
- --bk-white: ${t.white};
40
- --bk-yellow: ${t.accent};
41
- --bk-gray-100: #f3f4f6;
42
- --bk-gray-200: #e5e7eb;
43
- --bk-gray-300: #d1d5db;
44
- --bk-gray-400: #9ca3af;
45
- --bk-gray-600: #4b5563;
46
- --bk-font-heading: ${t.fontHeading};
47
- --bk-font-body: ${t.fontBody};
48
- --bk-font-mono: ${t.fontMono};
49
- --bk-font-display: ${t.fontDisplay};
50
- --bk-container-max: ${t.containerMax};
51
- --bk-transition: all 0.2s ease;
52
- }
34
+ :root {
35
+ --bk-accent: ${t.accent};
36
+ --bk-background: ${t.background};
37
+ --bk-surface: ${t.surface};
38
+ --bk-text: ${t.text};
39
+ --bk-muted: ${t.muted};
40
+ --bk-muted-light: ${t.mutedLight};
41
+ --bk-border: ${t.border};
42
+ --bk-black: ${t.black};
43
+ --bk-white: ${t.white};
44
+ --bk-yellow: ${t.accent};
45
+ --bk-gray-100: #f3f4f6;
46
+ --bk-gray-200: #e5e7eb;
47
+ --bk-gray-300: #d1d5db;
48
+ --bk-gray-400: #9ca3af;
49
+ --bk-gray-600: #4b5563;
50
+ --bk-font-heading: ${t.fontHeading};
51
+ --bk-font-body: ${t.fontBody};
52
+ --bk-font-mono: ${t.fontMono};
53
+ --bk-font-display: ${t.fontDisplay};
54
+ --bk-container-max: ${t.containerMax};
55
+ --bk-transition: all 0.2s ease;
56
+ }
57
+
58
+ *, *::before, *::after {
59
+ box-sizing: border-box;
60
+ margin: 0;
61
+ padding: 0;
62
+ }
63
+ `.trim();
64
+ }
65
+
66
+ /**
67
+ * Plugin de Vite que expone el tema como un virtual module CSS.
68
+ * Esto permite que Astro procese el CSS en SSR correctamente,
69
+ * sin depender de JavaScript en el cliente para inyectar variables.
70
+ *
71
+ * Uso en cualquier componente .astro del paquete:
72
+ * import 'virtual:astro-blog-kit/theme';
73
+ */
74
+ function createThemePlugin(theme: BlogTheme): Plugin {
75
+ const css = generateThemeCSS(theme);
53
76
 
54
- *, *::before, *::after {
55
- box-sizing: border-box;
56
- margin: 0;
57
- padding: 0;
58
- }
59
- `;
77
+ return {
78
+ name: "astro-blog-kit:theme",
79
+ resolveId(id) {
80
+ if (id === VIRTUAL_MODULE_ID) {
81
+ return RESOLVED_VIRTUAL_MODULE_ID;
82
+ }
83
+ },
84
+ load(id) {
85
+ if (id === RESOLVED_VIRTUAL_MODULE_ID) {
86
+ // Retornamos CSS puro — Vite lo procesa como módulo CSS
87
+ return css;
88
+ }
89
+ },
90
+ // Fuerza el tipo del módulo como CSS para que Vite lo trate correctamente
91
+ transform(code, id) {
92
+ if (id === RESOLVED_VIRTUAL_MODULE_ID) {
93
+ return {
94
+ code: `
95
+ const style = document.createElement('style');
96
+ style.id = 'astro-blog-kit-theme';
97
+ style.textContent = ${JSON.stringify(css)};
98
+ if (!document.getElementById('astro-blog-kit-theme')) {
99
+ document.head.appendChild(style);
100
+ }
101
+ `.trim(),
102
+ map: null,
103
+ };
104
+ }
105
+ },
106
+ };
60
107
  }
61
108
 
62
109
  /**
@@ -72,7 +119,7 @@ function generateThemeCSS(theme: BlogTheme = {}): string {
72
119
  * integrations: [
73
120
  * blogKit({
74
121
  * postsPerPage: 6,
75
- * defaultLayout: 'magazine',
122
+ * defaultLayout: 'featured',
76
123
  * theme: {
77
124
  * accent: '#facc15',
78
125
  * fontHeading: 'Inter, sans-serif',
@@ -89,29 +136,33 @@ export function blogKit(config: BlogKitConfig = {}): AstroIntegration {
89
136
  collectionName: config.collectionName ?? "blog",
90
137
  i18n: config.i18n ?? { locales: [], defaultLocale: "en" },
91
138
  theme: config.theme ?? {},
139
+ hero: config.hero ?? {}, // ← agregar
140
+ ui: config.ui ?? {},
92
141
  };
93
142
 
94
143
  return {
95
144
  name: "astro-blog-kit",
96
145
 
97
146
  hooks: {
98
- "astro:config:setup": ({ injectScript, logger }) => {
147
+ "astro:config:setup": ({ updateConfig, injectScript, logger }) => {
99
148
  logger.info(
100
149
  `astro-blog-kit initialized — layout: ${resolvedConfig.defaultLayout}, postsPerPage: ${resolvedConfig.postsPerPage}`
101
150
  );
102
151
 
103
- // Inyecta CSS variables del tema globalmente
104
- const themeCSS = generateThemeCSS(resolvedConfig.theme);
105
- injectScript("head-inline", `
106
- (() => {
107
- const style = document.createElement('style');
108
- style.id = 'astro-blog-kit-theme';
109
- style.textContent = \`${themeCSS.replace(/`/g, "\\`")}\`;
110
- document.head.appendChild(style);
111
- })();
112
- `);
152
+ // Registra el virtual module como plugin de Vite
153
+ // Esto garantiza que las CSS variables existen en SSR y en el build estático
154
+ updateConfig({
155
+ vite: {
156
+ plugins: [createThemePlugin(resolvedConfig.theme)],
157
+ },
158
+ });
159
+
160
+ // Inyecta el virtual module en cada página como CSS real
161
+ // "page-ssr" = se ejecuta en el servidor, garantiza que el style
162
+ // esté disponible antes del primer paint
163
+ injectScript("page-ssr", `import "${VIRTUAL_MODULE_ID}";`);
113
164
 
114
- // Inyecta config global
165
+ // Inyecta config global accesible desde cualquier componente
115
166
  injectScript(
116
167
  "page-ssr",
117
168
  `globalThis.__BLOG_KIT_CONFIG__ = ${JSON.stringify(resolvedConfig)};`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro-blog-kit",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "A ready-to-use blog system for Astro with WordPress headless support, optional i18n, multiple layouts, and a comment system.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -39,7 +39,7 @@
39
39
  "headless-cms"
40
40
  ],
41
41
  "peerDependencies": {
42
- "astro": "^4.0.0 || ^5.0.0 || ^6.0.0"
42
+ "astro": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
43
43
  },
44
44
  "dependencies": {
45
45
  "@clack/prompts": "latest"
@@ -1,24 +1,41 @@
1
- import { defineBlogConfig } from 'astro-blog-kit';
2
-
3
- export default defineBlogConfig({
4
- wpUrl: import.meta.env.WP_API_URL || '__WP_URL__',
5
- postsPerPage: __POSTS_PER_PAGE__,
6
- defaultLayout: '__DEFAULT_LAYOUT__',
7
- locale: '__LOCALE__',
8
- theme: {
9
- accent: '#facc15',
10
- background: '#ffffff',
11
- surface: '#f8f8f8',
12
- text: '#0a0a0a',
13
- muted: '#6b7280',
14
- mutedLight: '#4b5563',
15
- border: '#e5e7eb',
16
- black: '#0a0a0a',
17
- white: '#ffffff',
18
- fontHeading: 'Georgia, serif',
19
- fontBody: 'system-ui, sans-serif',
20
- fontMono: 'monospace',
21
- fontDisplay: 'Georgia, serif',
22
- containerMax: '1200px',
23
- },
24
- });
1
+ import { defineBlogConfig } from 'astro-blog-kit';
2
+
3
+ export default defineBlogConfig({
4
+ wpUrl: import.meta.env.WP_API_URL || '__WP_URL__',
5
+ postsPerPage: __POSTS_PER_PAGE__,
6
+ defaultLayout: '__DEFAULT_LAYOUT__',
7
+ locale: '__LOCALE__',
8
+
9
+ theme: {
10
+ accent: '#facc15',
11
+ background: '#ffffff',
12
+ surface: '#f8f8f8',
13
+ text: '#0a0a0a',
14
+ muted: '#6b7280',
15
+ mutedLight: '#4b5563',
16
+ border: '#e5e7eb',
17
+ black: '#0a0a0a',
18
+ white: '#ffffff',
19
+ fontHeading: 'Georgia, serif',
20
+ fontBody: 'system-ui, sans-serif',
21
+ fontMono: 'monospace',
22
+ fontDisplay: 'Georgia, serif',
23
+ containerMax:'1200px',
24
+ },
25
+
26
+ hero: {
27
+ tagline: '__T_TAGLINE__',
28
+ titleLine1: '__T_TITLE_LINE1__',
29
+ titleLine2: '__T_TITLE_LINE2__',
30
+ description: '__T_DESCRIPTION__',
31
+ },
32
+
33
+ ui: {
34
+ readMoreLabel: '__T_BTNCTA__',
35
+ btnPrev: '__T_BTN_PREV__',
36
+ btnNext: '__T_BTN_NEXT__',
37
+ commentButtonColor: 'var(--bk-accent)',
38
+ commentButtonTextColor: 'var(--bk-black)',
39
+ paginationStyle: 'minimal',
40
+ },
41
+ });
@@ -1,37 +1,42 @@
1
- ---
2
- import { BlogList } from "astro-blog-kit/components";
3
- import { createWPClient } from "astro-blog-kit/utils";
4
- import config from "../../../blog.config";
5
- __LAYOUT_IMPORT__
6
-
7
- const wp = createWPClient(config.wpUrl);
8
- const { posts, totalPages } = await wp.getPosts({ perPage: config.postsPerPage ?? 5 });
9
-
10
- const bt = {
11
- blog: {
12
- tagline: "__T_TAGLINE__",
13
- title_line1: "__T_TITLE_LINE1__",
14
- title_line2: "__T_TITLE_LINE2__",
15
- description: "__T_DESCRIPTION__",
16
- btncta: "__T_BTNCTA__",
17
- btn_prev: "__T_BTN_PREV__",
18
- btn_next: "__T_BTN_NEXT__",
19
- },
20
- };
21
-
22
- const base = import.meta.env.BASE_URL;
23
- ---
24
-
25
- __LAYOUT_OPEN__
26
- <BlogList
27
- posts={posts}
28
- currentPage={1}
29
- totalPages={totalPages}
30
- basePath={`${base}blog/page/`}
31
- blogBase={`${base}blog/`}
32
- dateLocale={config.locale ?? "en"}
33
- t={bt}
34
- locale={config.locale ?? "en"}
35
- layout={config.defaultLayout ?? "magazine"}
36
- />
37
- __LAYOUT_CLOSE__
1
+ ---
2
+ import { BlogList } from 'astro-blog-kit/components';
3
+ import { createWPClient } from 'astro-blog-kit/utils';
4
+ import { resolveHero, resolveUI } from 'astro-blog-kit';
5
+ import config from '../../../blog.config';
6
+ __LAYOUT_IMPORT__
7
+
8
+ const wp = createWPClient(config.wpUrl);
9
+ const { posts, totalPages } = await wp.getPosts({ perPage: config.postsPerPage ?? 5 });
10
+
11
+ const locale = config.locale ?? 'en';
12
+ const hero = resolveHero(config.hero, locale);
13
+ const ui = resolveUI(config.ui, locale);
14
+
15
+ const t = {
16
+ blog: {
17
+ tagline: hero.tagline,
18
+ title_line1: hero.titleLine1,
19
+ title_line2: hero.titleLine2,
20
+ description: hero.description,
21
+ btncta: ui.readMoreLabel,
22
+ btn_prev: ui.btnPrev,
23
+ btn_next: ui.btnNext,
24
+ },
25
+ };
26
+
27
+ const base = import.meta.env.BASE_URL;
28
+ ---
29
+
30
+ __LAYOUT_OPEN__
31
+ <BlogList
32
+ posts={posts}
33
+ currentPage={1}
34
+ totalPages={totalPages}
35
+ basePath={`${base}blog/page/`}
36
+ blogBase={`${base}blog/`}
37
+ dateLocale={locale}
38
+ t={t}
39
+ locale={locale}
40
+ layout={config.defaultLayout ?? 'magazine'}
41
+ />
42
+ __LAYOUT_CLOSE__
package/types.ts CHANGED
@@ -4,10 +4,6 @@
4
4
 
5
5
  // ── Post ──────────────────────────────────────────────────────
6
6
 
7
- /**
8
- * Forma normalizada de un post de blog.
9
- * Compatible con WordPress REST API (_embedded).
10
- */
11
7
  export interface BlogPost {
12
8
  id?: number;
13
9
  slug: string;
@@ -95,7 +91,7 @@ export interface BlogPostProps {
95
91
  lang: string;
96
92
  }
97
93
 
98
- // ── Config del paquete ────────────────────────────────────────
94
+ // ── Tema ──────────────────────────────────────────────────────
99
95
 
100
96
  export interface BlogTheme {
101
97
  /** Color de acento principal. @default "#facc15" */
@@ -128,6 +124,38 @@ export interface BlogTheme {
128
124
  containerMax?: string;
129
125
  }
130
126
 
127
+ // ── Hero del blog ─────────────────────────────────────────────
128
+
129
+ export interface BlogHero {
130
+ /** Texto del badge superior. @default "Our Blog" */
131
+ tagline?: string;
132
+ /** Primera línea del título. @default "Latest" */
133
+ titleLine1?: string;
134
+ /** Segunda línea del título (resaltada). @default "Articles" */
135
+ titleLine2?: string;
136
+ /** Párrafo descriptivo debajo del título. @default "Welcome to our blog." */
137
+ description?: string;
138
+ }
139
+
140
+ // ── UI overrides ──────────────────────────────────────────────
141
+
142
+ export interface BlogUI {
143
+ /** Texto del botón "leer más". @default "Read more →" */
144
+ readMoreLabel?: string;
145
+ /** Texto del botón de página anterior. @default "Previous" */
146
+ btnPrev?: string;
147
+ /** Texto del botón de página siguiente. @default "Next" */
148
+ btnNext?: string;
149
+ /** Color de fondo del botón de comentarios. @default var(--bk-accent) */
150
+ commentButtonColor?: string;
151
+ /** Color del texto del botón de comentarios. @default var(--bk-black) */
152
+ commentButtonTextColor?: string;
153
+ /** Estilo de paginación. @default "minimal" */
154
+ paginationStyle?: "minimal" | "numbered";
155
+ }
156
+
157
+ // ── Config del paquete ────────────────────────────────────────
158
+
131
159
  export interface BlogKitConfig {
132
160
  /** @default 5 */
133
161
  postsPerPage?: number;
@@ -138,6 +166,10 @@ export interface BlogKitConfig {
138
166
  collectionName?: string;
139
167
  /** Tema visual del blog */
140
168
  theme?: BlogTheme;
169
+ /** Textos del hero/header del blog */
170
+ hero?: BlogHero;
171
+ /** Overrides de UI (botones, paginación, labels) */
172
+ ui?: BlogUI;
141
173
  }
142
174
 
143
175
  // ── getStaticPaths ────────────────────────────────────────────