wp-astrojs-integration 0.1.0

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.
@@ -0,0 +1,199 @@
1
+ import { z } from 'astro/zod';
2
+ /**
3
+ * Base schema shared by all WordPress content types
4
+ * Can be extended with custom fields using .extend() or .merge()
5
+ */
6
+ export const baseWordPressSchema = z.object({
7
+ id: z.number(),
8
+ date: z.string(),
9
+ date_gmt: z.string(),
10
+ guid: z.object({
11
+ rendered: z.string(),
12
+ }),
13
+ modified: z.string(),
14
+ modified_gmt: z.string(),
15
+ slug: z.string(),
16
+ status: z.string(),
17
+ type: z.string(),
18
+ link: z.string().url(),
19
+ title: z.object({
20
+ rendered: z.string(),
21
+ }),
22
+ author: z.number(),
23
+ meta: z.union([z.record(z.any()), z.array(z.any())]).optional(),
24
+ _links: z.any(),
25
+ });
26
+ /**
27
+ * Schema for content types (posts and pages)
28
+ * Extend this for custom post types with content fields
29
+ */
30
+ export const contentWordPressSchema = baseWordPressSchema.extend({
31
+ content: z.object({
32
+ rendered: z.string(),
33
+ protected: z.boolean(),
34
+ }),
35
+ excerpt: z.object({
36
+ rendered: z.string(),
37
+ protected: z.boolean(),
38
+ }),
39
+ featured_media: z.number().optional(),
40
+ comment_status: z.string(),
41
+ ping_status: z.string(),
42
+ template: z.string(),
43
+ acf: z.union([z.record(z.any()), z.array(z.any())]).optional(),
44
+ _embedded: z.any().optional(),
45
+ });
46
+ /**
47
+ * Default schema for WordPress posts
48
+ * Extend with .extend() to add custom ACF fields or taxonomies
49
+ *
50
+ * @example
51
+ * const customPostSchema = postSchema.extend({
52
+ * acf: z.object({
53
+ * custom_field: z.string().optional(),
54
+ * }).optional(),
55
+ * });
56
+ */
57
+ export const postSchema = contentWordPressSchema.extend({
58
+ sticky: z.boolean(),
59
+ format: z.string(),
60
+ categories: z.array(z.number()).default([]),
61
+ tags: z.array(z.number()).default([]),
62
+ });
63
+ /**
64
+ * Default schema for WordPress pages
65
+ * Extend with .extend() to add custom ACF fields
66
+ *
67
+ * @example
68
+ * const customPageSchema = pageSchema.extend({
69
+ * acf: z.object({
70
+ * hero_image: z.number().optional(),
71
+ * }).optional(),
72
+ * });
73
+ */
74
+ export const pageSchema = contentWordPressSchema.extend({
75
+ parent: z.number().default(0),
76
+ menu_order: z.number().default(0),
77
+ class_list: z.array(z.string()).default([]),
78
+ });
79
+ /**
80
+ * Schema for WordPress media items
81
+ */
82
+ export const mediaSchema = baseWordPressSchema.extend({
83
+ comment_status: z.string(),
84
+ ping_status: z.string(),
85
+ alt_text: z.string(),
86
+ caption: z.object({
87
+ rendered: z.string(),
88
+ }),
89
+ description: z.object({
90
+ rendered: z.string(),
91
+ }),
92
+ media_type: z.string(),
93
+ mime_type: z.string(),
94
+ media_details: z.object({
95
+ width: z.number(),
96
+ height: z.number(),
97
+ file: z.string(),
98
+ filesize: z.number().optional(),
99
+ sizes: z.record(z.object({
100
+ file: z.string(),
101
+ width: z.number(),
102
+ height: z.number(),
103
+ filesize: z.number().optional(),
104
+ mime_type: z.string(),
105
+ source_url: z.string(),
106
+ })),
107
+ image_meta: z.any(),
108
+ }),
109
+ source_url: z.string(),
110
+ });
111
+ /**
112
+ * Schema for WordPress categories and taxonomies
113
+ * Extend with .extend() to add custom ACF fields to taxonomies
114
+ *
115
+ * @example
116
+ * const customCategorySchema = categorySchema.extend({
117
+ * acf: z.object({
118
+ * color: z.string().optional(),
119
+ * }).optional(),
120
+ * });
121
+ */
122
+ export const categorySchema = z.object({
123
+ id: z.number(),
124
+ count: z.number(),
125
+ description: z.string(),
126
+ link: z.string().url(),
127
+ name: z.string(),
128
+ slug: z.string(),
129
+ taxonomy: z.string(),
130
+ parent: z.number().default(0),
131
+ meta: z.array(z.any()).or(z.record(z.any())),
132
+ acf: z.union([z.record(z.any()), z.array(z.any())]).optional(),
133
+ _embedded: z.any().optional(),
134
+ _links: z.any(),
135
+ });
136
+ /**
137
+ * Schema for WordPress embedded media (used in _embedded field)
138
+ */
139
+ export const embeddedMediaSchema = z.object({
140
+ id: z.number(),
141
+ date: z.string(),
142
+ slug: z.string(),
143
+ type: z.string(),
144
+ link: z.string(),
145
+ title: z.object({
146
+ rendered: z.string(),
147
+ }),
148
+ author: z.number(),
149
+ featured_media: z.number(),
150
+ caption: z.object({
151
+ rendered: z.string(),
152
+ }),
153
+ alt_text: z.string(),
154
+ media_type: z.string(),
155
+ mime_type: z.string(),
156
+ media_details: z.object({
157
+ width: z.number(),
158
+ height: z.number(),
159
+ file: z.string(),
160
+ filesize: z.number().optional(),
161
+ sizes: z.record(z.object({
162
+ file: z.string(),
163
+ width: z.number(),
164
+ height: z.number(),
165
+ filesize: z.number().optional(),
166
+ mime_type: z.string(),
167
+ source_url: z.string(),
168
+ })),
169
+ image_meta: z.any().optional(),
170
+ }),
171
+ source_url: z.string(),
172
+ acf: z.any().optional(),
173
+ _links: z.any().optional(),
174
+ });
175
+ /**
176
+ * Schema for WordPress site settings (requires authentication)
177
+ */
178
+ export const settingsSchema = z.object({
179
+ title: z.string(),
180
+ description: z.string(),
181
+ url: z.string().url(),
182
+ email: z.string().email().optional(),
183
+ timezone: z.string(),
184
+ date_format: z.string(),
185
+ time_format: z.string(),
186
+ start_of_week: z.number(),
187
+ language: z.string(),
188
+ use_smilies: z.boolean(),
189
+ default_category: z.number(),
190
+ default_post_format: z.string(),
191
+ posts_per_page: z.number(),
192
+ show_on_front: z.string(),
193
+ page_on_front: z.number(),
194
+ page_for_posts: z.number(),
195
+ default_ping_status: z.string(),
196
+ default_comment_status: z.string(),
197
+ site_logo: z.number().nullable().optional(),
198
+ site_icon: z.number().nullable().optional(),
199
+ });
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "wp-astrojs-integration",
3
+ "version": "0.1.0",
4
+ "description": "Fast and better WordPress integration for Astro.js with live loaders, client API, and Gutenberg block support",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ },
13
+ "./components": {
14
+ "import": "./src/components/index.ts"
15
+ },
16
+ "./components/WPImage.astro": {
17
+ "import": "./src/components/WPImage.astro"
18
+ },
19
+ "./components/WPContent.astro": {
20
+ "import": "./src/components/WPContent.astro"
21
+ }
22
+ },
23
+ "files": [
24
+ "dist",
25
+ "src/components"
26
+ ],
27
+ "scripts": {
28
+ "build": "tsc",
29
+ "dev": "tsc --watch",
30
+ "prepublishOnly": "npm run build"
31
+ },
32
+ "keywords": [
33
+ "astro",
34
+ "wordpress",
35
+ "cms",
36
+ "gutenberg",
37
+ "headless",
38
+ "astro-integration",
39
+ "wp-api"
40
+ ],
41
+ "author": "juvojustin",
42
+ "repository": {
43
+ "type": "git",
44
+ "url": "git+https://github.com/JUVOJustin/astrojs-wp-integration.git"
45
+ },
46
+ "bugs": {
47
+ "url": "https://github.com/JUVOJustin/astrojs-wp-integration/issues"
48
+ },
49
+ "homepage": "https://github.com/JUVOJustin/astrojs-wp-integration#readme",
50
+ "license": "MIT",
51
+ "peerDependencies": {
52
+ "astro": "^5.0.0"
53
+ },
54
+ "dependencies": {
55
+ "astro": "^5.16.6"
56
+ },
57
+ "devDependencies": {
58
+ "typescript": "^5.7.2"
59
+ }
60
+ }
@@ -0,0 +1,48 @@
1
+ ---
2
+ /**
3
+ * WPContent component for rendering WordPress Gutenberg content
4
+ * Automatically loads block styles from WordPress site
5
+ *
6
+ * Features:
7
+ * - Dynamically loads Gutenberg block CSS from WordPress
8
+ * - Supports both posts and pages with Gutenberg content
9
+ * - Handles embedded media and custom blocks
10
+ */
11
+
12
+ interface Props {
13
+ /** Rendered HTML content from WordPress */
14
+ content: string;
15
+ /** WordPress base URL for loading block styles */
16
+ baseUrl: string;
17
+ /** Additional CSS classes to apply */
18
+ class?: string;
19
+ /** Whether to load block styles (default: true) */
20
+ loadBlockStyles?: boolean;
21
+ }
22
+
23
+ const {
24
+ content,
25
+ baseUrl,
26
+ class: className = '',
27
+ loadBlockStyles = true,
28
+ } = Astro.props;
29
+
30
+ // Build the block styles URL
31
+ const blockStylesUrl = loadBlockStyles
32
+ ? `${baseUrl}/wp-includes/css/dist/block-library/style.min.css`
33
+ : null;
34
+
35
+ // Optional: Load theme styles for blocks
36
+ const themeBlockStylesUrl = loadBlockStyles
37
+ ? `${baseUrl}/wp-includes/css/dist/block-library/theme.min.css`
38
+ : null;
39
+ ---
40
+
41
+ {loadBlockStyles && blockStylesUrl && (
42
+ <>
43
+ <link rel="stylesheet" href={blockStylesUrl} />
44
+ {themeBlockStylesUrl && <link rel="stylesheet" href={themeBlockStylesUrl} />}
45
+ </>
46
+ )}
47
+
48
+ <div class:list={['wp-block-content', className]} set:html={content} />
@@ -0,0 +1,106 @@
1
+ ---
2
+ import type { WordPressMedia, WordPressEmbeddedMedia } from '../schemas';
3
+
4
+ /**
5
+ * Props for the WPImage component.
6
+ * Accepts either a mediaId for fetching, or pre-loaded media data from embedded responses.
7
+ */
8
+ interface Props {
9
+ /** Media ID for fetching from WordPress API */
10
+ mediaId?: number;
11
+ /** Pre-loaded media data from _embed=true responses */
12
+ media?: WordPressMedia | WordPressEmbeddedMedia;
13
+ /** WordPress base URL (required if using mediaId) */
14
+ baseUrl?: string;
15
+ class?: string;
16
+ loading?: 'lazy' | 'eager';
17
+ decoding?: 'async' | 'auto' | 'sync';
18
+ width?: number;
19
+ height?: number;
20
+ sizes?: string;
21
+ }
22
+
23
+ const {
24
+ mediaId,
25
+ media: providedMedia,
26
+ baseUrl,
27
+ class: className = '',
28
+ loading = 'lazy',
29
+ decoding = 'async',
30
+ width: customWidth,
31
+ height: customHeight,
32
+ sizes = 'auto',
33
+ } = Astro.props;
34
+
35
+ let media: WordPressMedia | WordPressEmbeddedMedia | undefined;
36
+
37
+ if (providedMedia) {
38
+ media = providedMedia;
39
+ } else if (mediaId && baseUrl) {
40
+ try {
41
+ const url = new URL(`${baseUrl}/index.php?rest_route=/wp/v2/media/${mediaId}`);
42
+ const response = await fetch(url.toString());
43
+ if (response.ok) {
44
+ media = await response.json();
45
+ }
46
+ } catch (error) {
47
+ console.error(`Failed to fetch media ${mediaId}:`, error);
48
+ }
49
+ }
50
+
51
+ if (!media) {
52
+ console.error('WPImage: Either (mediaId + baseUrl) or media prop must be provided');
53
+ return;
54
+ }
55
+
56
+ /** Size data structure from WordPress media_details.sizes */
57
+ type MediaSize = {
58
+ file: string;
59
+ width: number;
60
+ height: number;
61
+ filesize?: number;
62
+ mime_type: string;
63
+ source_url: string;
64
+ };
65
+
66
+ /**
67
+ * Generates srcset string from WordPress media sizes for responsive images.
68
+ */
69
+ function generateSrcset(media: WordPressMedia | WordPressEmbeddedMedia): string {
70
+ if (!media.media_details?.sizes) {
71
+ return '';
72
+ }
73
+
74
+ const srcsetItems: string[] = [];
75
+ const sizes = media.media_details.sizes as Record<string, MediaSize>;
76
+
77
+ Object.entries(sizes).forEach(([_sizeName, sizeData]) => {
78
+ srcsetItems.push(`${sizeData.source_url} ${sizeData.width}w`);
79
+ });
80
+
81
+ srcsetItems.sort((a, b) => {
82
+ const widthA = parseInt(a.split(' ')[1]);
83
+ const widthB = parseInt(b.split(' ')[1]);
84
+ return widthA - widthB;
85
+ });
86
+
87
+ return srcsetItems.join(', ');
88
+ }
89
+
90
+ const alt = media.alt_text || media.title?.rendered || '';
91
+ const width = customWidth || media.media_details?.width;
92
+ const height = customHeight || media.media_details?.height;
93
+ const srcset = generateSrcset(media);
94
+ ---
95
+
96
+ <img
97
+ src={media.source_url}
98
+ srcset={srcset || undefined}
99
+ alt={alt}
100
+ width={width}
101
+ height={height}
102
+ loading={loading}
103
+ decoding={decoding}
104
+ sizes={sizes}
105
+ class={className}
106
+ />
@@ -0,0 +1,9 @@
1
+ /**
2
+ * WordPress Astro.js Integration Components
3
+ *
4
+ * Export Astro components for WordPress content rendering
5
+ */
6
+
7
+ // Re-export components for named imports
8
+ export { default as WPImage } from './WPImage.astro';
9
+ export { default as WPContent } from './WPContent.astro';