@mytechtoday/augment-extensions 0.1.1 → 0.2.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.
- package/augment-extensions/domain-rules/wordpress/README.md +163 -0
- package/augment-extensions/domain-rules/wordpress/module.json +32 -0
- package/augment-extensions/domain-rules/wordpress/rules/coding-standards.md +617 -0
- package/augment-extensions/domain-rules/wordpress/rules/directory-structure.md +270 -0
- package/augment-extensions/domain-rules/wordpress/rules/file-patterns.md +423 -0
- package/augment-extensions/domain-rules/wordpress/rules/gutenberg-blocks.md +493 -0
- package/augment-extensions/domain-rules/wordpress/rules/performance.md +568 -0
- package/augment-extensions/domain-rules/wordpress/rules/plugin-development.md +510 -0
- package/augment-extensions/domain-rules/wordpress/rules/project-detection.md +251 -0
- package/augment-extensions/domain-rules/wordpress/rules/rest-api.md +501 -0
- package/augment-extensions/domain-rules/wordpress/rules/security.md +564 -0
- package/augment-extensions/domain-rules/wordpress/rules/theme-development.md +388 -0
- package/augment-extensions/domain-rules/wordpress/rules/woocommerce.md +441 -0
- package/augment-extensions/domain-rules/wordpress-plugin/README.md +139 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/ajax-plugin.md +1599 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/custom-post-type-plugin.md +1727 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/gutenberg-block-plugin.md +428 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/gutenberg-block.md +422 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/mvc-plugin.md +1623 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/object-oriented-plugin.md +1343 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/rest-endpoint.md +734 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/settings-page-plugin.md +1350 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/simple-procedural-plugin.md +503 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/singleton-plugin.md +971 -0
- package/augment-extensions/domain-rules/wordpress-plugin/module.json +53 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/activation-hooks.md +770 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/admin-interface.md +874 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/ajax-handlers.md +629 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/asset-management.md +559 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/context-providers.md +709 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/cron-jobs.md +736 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/database-management.md +1057 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/documentation-standards.md +463 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/frontend-functionality.md +478 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/gutenberg-blocks.md +818 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/internationalization.md +416 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/migration.md +667 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/performance-optimization.md +878 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/plugin-architecture.md +693 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/plugin-structure.md +352 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/rest-api.md +818 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/scaffolding-workflow.md +624 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/security-best-practices.md +866 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/testing-patterns.md +1165 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/testing.md +414 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/vscode-integration.md +751 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/woocommerce-integration.md +949 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/wordpress-org-submission.md +458 -0
- package/augment-extensions/examples/gutenberg-block-plugin/README.md +101 -0
- package/augment-extensions/examples/gutenberg-block-plugin/examples/testimonial-block.md +428 -0
- package/augment-extensions/examples/gutenberg-block-plugin/module.json +40 -0
- package/augment-extensions/examples/rest-api-plugin/README.md +98 -0
- package/augment-extensions/examples/rest-api-plugin/examples/task-manager-api.md +1299 -0
- package/augment-extensions/examples/rest-api-plugin/module.json +40 -0
- package/augment-extensions/examples/woocommerce-extension/README.md +98 -0
- package/augment-extensions/examples/woocommerce-extension/examples/product-customizer.md +763 -0
- package/augment-extensions/examples/woocommerce-extension/module.json +40 -0
- package/augment-extensions/workflows/wordpress-plugin/README.md +232 -0
- package/augment-extensions/workflows/wordpress-plugin/ai-prompts.md +839 -0
- package/augment-extensions/workflows/wordpress-plugin/bead-decomposition-patterns.md +854 -0
- package/augment-extensions/workflows/wordpress-plugin/examples/complete-plugin-example.md +540 -0
- package/augment-extensions/workflows/wordpress-plugin/examples/custom-post-type-example.md +1083 -0
- package/augment-extensions/workflows/wordpress-plugin/examples/feature-addition-workflow.md +669 -0
- package/augment-extensions/workflows/wordpress-plugin/examples/plugin-creation-workflow.md +597 -0
- package/augment-extensions/workflows/wordpress-plugin/examples/secure-form-handler-example.md +925 -0
- package/augment-extensions/workflows/wordpress-plugin/examples/security-audit-workflow.md +752 -0
- package/augment-extensions/workflows/wordpress-plugin/examples/wordpress-org-submission-workflow.md +773 -0
- package/augment-extensions/workflows/wordpress-plugin/module.json +49 -0
- package/augment-extensions/workflows/wordpress-plugin/rules/best-practices.md +942 -0
- package/augment-extensions/workflows/wordpress-plugin/rules/development-workflow.md +702 -0
- package/augment-extensions/workflows/wordpress-plugin/rules/submission-workflow.md +728 -0
- package/augment-extensions/workflows/wordpress-plugin/rules/testing-workflow.md +775 -0
- package/cli/dist/cli.js +5 -1
- package/cli/dist/cli.js.map +1 -1
- package/cli/dist/commands/show.d.ts.map +1 -1
- package/cli/dist/commands/show.js +41 -0
- package/cli/dist/commands/show.js.map +1 -1
- package/modules.md +52 -0
- package/package.json +1 -1
|
@@ -0,0 +1,818 @@
|
|
|
1
|
+
# Gutenberg Block Development
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
This guide covers WordPress Gutenberg block development for plugins using modern block development tools (@wordpress/create-block, @wordpress/scripts, block.json).
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Block Scaffolding
|
|
10
|
+
|
|
11
|
+
### Create Block with @wordpress/create-block
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Navigate to plugins directory
|
|
15
|
+
cd wp-content/plugins
|
|
16
|
+
|
|
17
|
+
# Create new block (interactive)
|
|
18
|
+
npx @wordpress/create-block my-custom-block
|
|
19
|
+
|
|
20
|
+
# Or with options
|
|
21
|
+
npx @wordpress/create-block my-custom-block \
|
|
22
|
+
--namespace="my-plugin" \
|
|
23
|
+
--title="My Custom Block" \
|
|
24
|
+
--category="widgets" \
|
|
25
|
+
--template="@wordpress/create-block/block-template"
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Generated structure:**
|
|
29
|
+
```
|
|
30
|
+
my-custom-block/
|
|
31
|
+
├── build/ # Compiled files (gitignored)
|
|
32
|
+
├── src/ # Source files
|
|
33
|
+
│ ├── index.js # Block registration
|
|
34
|
+
│ ├── edit.js # Editor component
|
|
35
|
+
│ ├── save.js # Save function
|
|
36
|
+
│ ├── style.scss # Frontend styles
|
|
37
|
+
│ └── editor.scss # Editor styles
|
|
38
|
+
├── block.json # Block metadata
|
|
39
|
+
├── my-custom-block.php # Main plugin file
|
|
40
|
+
├── package.json # Dependencies
|
|
41
|
+
└── readme.txt # Plugin readme
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Build Commands
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# Development mode (watch for changes)
|
|
48
|
+
npm start
|
|
49
|
+
|
|
50
|
+
# Production build
|
|
51
|
+
npm run build
|
|
52
|
+
|
|
53
|
+
# Lint JavaScript
|
|
54
|
+
npm run lint:js
|
|
55
|
+
|
|
56
|
+
# Format code
|
|
57
|
+
npm run format
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Block Metadata (block.json)
|
|
63
|
+
|
|
64
|
+
### Complete block.json Example
|
|
65
|
+
|
|
66
|
+
```json
|
|
67
|
+
{
|
|
68
|
+
"$schema": "https://schemas.wp.org/trunk/block.json",
|
|
69
|
+
"apiVersion": 2,
|
|
70
|
+
"name": "my-plugin/custom-block",
|
|
71
|
+
"version": "1.0.0",
|
|
72
|
+
"title": "Custom Block",
|
|
73
|
+
"category": "widgets",
|
|
74
|
+
"icon": "smiley",
|
|
75
|
+
"description": "A custom Gutenberg block",
|
|
76
|
+
"keywords": ["custom", "block", "example"],
|
|
77
|
+
"textdomain": "my-plugin",
|
|
78
|
+
"supports": {
|
|
79
|
+
"html": false,
|
|
80
|
+
"align": true,
|
|
81
|
+
"alignWide": true,
|
|
82
|
+
"color": {
|
|
83
|
+
"background": true,
|
|
84
|
+
"text": true,
|
|
85
|
+
"gradients": true
|
|
86
|
+
},
|
|
87
|
+
"spacing": {
|
|
88
|
+
"padding": true,
|
|
89
|
+
"margin": true
|
|
90
|
+
},
|
|
91
|
+
"typography": {
|
|
92
|
+
"fontSize": true,
|
|
93
|
+
"lineHeight": true
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
"attributes": {
|
|
97
|
+
"content": {
|
|
98
|
+
"type": "string",
|
|
99
|
+
"source": "html",
|
|
100
|
+
"selector": "p",
|
|
101
|
+
"default": ""
|
|
102
|
+
},
|
|
103
|
+
"alignment": {
|
|
104
|
+
"type": "string",
|
|
105
|
+
"default": "left"
|
|
106
|
+
},
|
|
107
|
+
"showIcon": {
|
|
108
|
+
"type": "boolean",
|
|
109
|
+
"default": false
|
|
110
|
+
},
|
|
111
|
+
"iconSize": {
|
|
112
|
+
"type": "number",
|
|
113
|
+
"default": 24
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
"example": {
|
|
117
|
+
"attributes": {
|
|
118
|
+
"content": "This is example content",
|
|
119
|
+
"alignment": "center"
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
"editorScript": "file:./index.js",
|
|
123
|
+
"editorStyle": "file:./index.css",
|
|
124
|
+
"style": "file:./style-index.css"
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Attribute Types
|
|
129
|
+
|
|
130
|
+
```json
|
|
131
|
+
{
|
|
132
|
+
"attributes": {
|
|
133
|
+
"stringAttr": {
|
|
134
|
+
"type": "string",
|
|
135
|
+
"default": ""
|
|
136
|
+
},
|
|
137
|
+
"numberAttr": {
|
|
138
|
+
"type": "number",
|
|
139
|
+
"default": 0
|
|
140
|
+
},
|
|
141
|
+
"booleanAttr": {
|
|
142
|
+
"type": "boolean",
|
|
143
|
+
"default": false
|
|
144
|
+
},
|
|
145
|
+
"arrayAttr": {
|
|
146
|
+
"type": "array",
|
|
147
|
+
"default": []
|
|
148
|
+
},
|
|
149
|
+
"objectAttr": {
|
|
150
|
+
"type": "object",
|
|
151
|
+
"default": {}
|
|
152
|
+
},
|
|
153
|
+
"htmlContent": {
|
|
154
|
+
"type": "string",
|
|
155
|
+
"source": "html",
|
|
156
|
+
"selector": "p"
|
|
157
|
+
},
|
|
158
|
+
"textContent": {
|
|
159
|
+
"type": "string",
|
|
160
|
+
"source": "text",
|
|
161
|
+
"selector": ".my-class"
|
|
162
|
+
},
|
|
163
|
+
"attributeValue": {
|
|
164
|
+
"type": "string",
|
|
165
|
+
"source": "attribute",
|
|
166
|
+
"selector": "img",
|
|
167
|
+
"attribute": "src"
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Block Registration
|
|
176
|
+
|
|
177
|
+
### src/index.js
|
|
178
|
+
|
|
179
|
+
```js
|
|
180
|
+
import { registerBlockType } from '@wordpress/blocks';
|
|
181
|
+
import './style.scss';
|
|
182
|
+
import Edit from './edit';
|
|
183
|
+
import save from './save';
|
|
184
|
+
import metadata from './block.json';
|
|
185
|
+
|
|
186
|
+
registerBlockType(metadata.name, {
|
|
187
|
+
edit: Edit,
|
|
188
|
+
save,
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## Edit Component
|
|
194
|
+
|
|
195
|
+
### Basic Edit Component
|
|
196
|
+
|
|
197
|
+
**src/edit.js:**
|
|
198
|
+
```jsx
|
|
199
|
+
import { __ } from '@wordpress/i18n';
|
|
200
|
+
import { useBlockProps, RichText } from '@wordpress/block-editor';
|
|
201
|
+
import './editor.scss';
|
|
202
|
+
|
|
203
|
+
export default function Edit({ attributes, setAttributes }) {
|
|
204
|
+
const { content } = attributes;
|
|
205
|
+
|
|
206
|
+
const blockProps = useBlockProps();
|
|
207
|
+
|
|
208
|
+
return (
|
|
209
|
+
<div {...blockProps}>
|
|
210
|
+
<RichText
|
|
211
|
+
tagName="p"
|
|
212
|
+
value={content}
|
|
213
|
+
onChange={(newContent) => setAttributes({ content: newContent })}
|
|
214
|
+
placeholder={__('Enter your content...', 'my-plugin')}
|
|
215
|
+
/>
|
|
216
|
+
</div>
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Edit Component with Controls
|
|
222
|
+
|
|
223
|
+
**src/edit.js:**
|
|
224
|
+
```jsx
|
|
225
|
+
import { __ } from '@wordpress/i18n';
|
|
226
|
+
import {
|
|
227
|
+
useBlockProps,
|
|
228
|
+
RichText,
|
|
229
|
+
AlignmentToolbar,
|
|
230
|
+
BlockControls,
|
|
231
|
+
InspectorControls
|
|
232
|
+
} from '@wordpress/block-editor';
|
|
233
|
+
import {
|
|
234
|
+
PanelBody,
|
|
235
|
+
ToggleControl,
|
|
236
|
+
RangeControl,
|
|
237
|
+
TextControl
|
|
238
|
+
} from '@wordpress/components';
|
|
239
|
+
import './editor.scss';
|
|
240
|
+
|
|
241
|
+
export default function Edit({ attributes, setAttributes }) {
|
|
242
|
+
const { content, alignment, showIcon, iconSize, customClass } = attributes;
|
|
243
|
+
|
|
244
|
+
const blockProps = useBlockProps({
|
|
245
|
+
className: `has-text-align-${alignment} ${customClass}`,
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
return (
|
|
249
|
+
<>
|
|
250
|
+
<BlockControls>
|
|
251
|
+
<AlignmentToolbar
|
|
252
|
+
value={alignment}
|
|
253
|
+
onChange={(newAlignment) => setAttributes({ alignment: newAlignment })}
|
|
254
|
+
/>
|
|
255
|
+
</BlockControls>
|
|
256
|
+
|
|
257
|
+
<InspectorControls>
|
|
258
|
+
<PanelBody title={__('Settings', 'my-plugin')}>
|
|
259
|
+
<ToggleControl
|
|
260
|
+
label={__('Show Icon', 'my-plugin')}
|
|
261
|
+
checked={showIcon}
|
|
262
|
+
onChange={(value) => setAttributes({ showIcon: value })}
|
|
263
|
+
/>
|
|
264
|
+
|
|
265
|
+
{showIcon && (
|
|
266
|
+
<RangeControl
|
|
267
|
+
label={__('Icon Size', 'my-plugin')}
|
|
268
|
+
value={iconSize}
|
|
269
|
+
onChange={(value) => setAttributes({ iconSize: value })}
|
|
270
|
+
min={10}
|
|
271
|
+
max={100}
|
|
272
|
+
/>
|
|
273
|
+
)}
|
|
274
|
+
|
|
275
|
+
<TextControl
|
|
276
|
+
label={__('Custom Class', 'my-plugin')}
|
|
277
|
+
value={customClass}
|
|
278
|
+
onChange={(value) => setAttributes({ customClass: value })}
|
|
279
|
+
/>
|
|
280
|
+
</PanelBody>
|
|
281
|
+
</InspectorControls>
|
|
282
|
+
|
|
283
|
+
<div {...blockProps}>
|
|
284
|
+
{showIcon && (
|
|
285
|
+
<span className="block-icon" style={{ fontSize: `${iconSize}px` }}>
|
|
286
|
+
★
|
|
287
|
+
</span>
|
|
288
|
+
)}
|
|
289
|
+
<RichText
|
|
290
|
+
tagName="p"
|
|
291
|
+
value={content}
|
|
292
|
+
onChange={(newContent) => setAttributes({ content: newContent })}
|
|
293
|
+
placeholder={__('Enter your content...', 'my-plugin')}
|
|
294
|
+
/>
|
|
295
|
+
</div>
|
|
296
|
+
</>
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
---
|
|
302
|
+
|
|
303
|
+
## Save Function
|
|
304
|
+
|
|
305
|
+
### Basic Save Function
|
|
306
|
+
|
|
307
|
+
**src/save.js:**
|
|
308
|
+
```jsx
|
|
309
|
+
import { useBlockProps, RichText } from '@wordpress/block-editor';
|
|
310
|
+
|
|
311
|
+
export default function save({ attributes }) {
|
|
312
|
+
const { content } = attributes;
|
|
313
|
+
|
|
314
|
+
const blockProps = useBlockProps.save();
|
|
315
|
+
|
|
316
|
+
return (
|
|
317
|
+
<div {...blockProps}>
|
|
318
|
+
<RichText.Content tagName="p" value={content} />
|
|
319
|
+
</div>
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### Save Function with Attributes
|
|
325
|
+
|
|
326
|
+
**src/save.js:**
|
|
327
|
+
```jsx
|
|
328
|
+
import { useBlockProps, RichText } from '@wordpress/block-editor';
|
|
329
|
+
|
|
330
|
+
export default function save({ attributes }) {
|
|
331
|
+
const { content, alignment, showIcon, iconSize, customClass } = attributes;
|
|
332
|
+
|
|
333
|
+
const blockProps = useBlockProps.save({
|
|
334
|
+
className: `has-text-align-${alignment} ${customClass}`,
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
return (
|
|
338
|
+
<div {...blockProps}>
|
|
339
|
+
{showIcon && (
|
|
340
|
+
<span className="block-icon" style={{ fontSize: `${iconSize}px` }}>
|
|
341
|
+
★
|
|
342
|
+
</span>
|
|
343
|
+
)}
|
|
344
|
+
<RichText.Content tagName="p" value={content} />
|
|
345
|
+
</div>
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
## Dynamic Blocks (Server-Side Rendering)
|
|
353
|
+
|
|
354
|
+
### Configure block.json for Dynamic Block
|
|
355
|
+
|
|
356
|
+
```json
|
|
357
|
+
{
|
|
358
|
+
"apiVersion": 2,
|
|
359
|
+
"name": "my-plugin/dynamic-block",
|
|
360
|
+
"title": "Dynamic Block",
|
|
361
|
+
"category": "widgets",
|
|
362
|
+
"attributes": {
|
|
363
|
+
"numberOfPosts": {
|
|
364
|
+
"type": "number",
|
|
365
|
+
"default": 5
|
|
366
|
+
},
|
|
367
|
+
"postType": {
|
|
368
|
+
"type": "string",
|
|
369
|
+
"default": "post"
|
|
370
|
+
}
|
|
371
|
+
},
|
|
372
|
+
"editorScript": "file:./index.js",
|
|
373
|
+
"render": "file:./render.php"
|
|
374
|
+
}
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
### PHP Render Template
|
|
378
|
+
|
|
379
|
+
**src/render.php:**
|
|
380
|
+
```php
|
|
381
|
+
<?php
|
|
382
|
+
/**
|
|
383
|
+
* Server-side rendering for dynamic block
|
|
384
|
+
*
|
|
385
|
+
* @param array $attributes Block attributes
|
|
386
|
+
* @param string $content Block content
|
|
387
|
+
* @param WP_Block $block Block instance
|
|
388
|
+
*/
|
|
389
|
+
|
|
390
|
+
$number_of_posts = isset( $attributes['numberOfPosts'] ) ? absint( $attributes['numberOfPosts'] ) : 5;
|
|
391
|
+
$post_type = isset( $attributes['postType'] ) ? sanitize_text_field( $attributes['postType'] ) : 'post';
|
|
392
|
+
|
|
393
|
+
$args = array(
|
|
394
|
+
'posts_per_page' => $number_of_posts,
|
|
395
|
+
'post_type' => $post_type,
|
|
396
|
+
'post_status' => 'publish',
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
$query = new WP_Query( $args );
|
|
400
|
+
|
|
401
|
+
$wrapper_attributes = get_block_wrapper_attributes();
|
|
402
|
+
|
|
403
|
+
$output = sprintf( '<div %s>', $wrapper_attributes );
|
|
404
|
+
|
|
405
|
+
if ( $query->have_posts() ) {
|
|
406
|
+
$output .= '<ul class="dynamic-block-list">';
|
|
407
|
+
|
|
408
|
+
while ( $query->have_posts() ) {
|
|
409
|
+
$query->the_post();
|
|
410
|
+
$output .= sprintf(
|
|
411
|
+
'<li><a href="%s">%s</a></li>',
|
|
412
|
+
esc_url( get_permalink() ),
|
|
413
|
+
esc_html( get_the_title() )
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
$output .= '</ul>';
|
|
418
|
+
} else {
|
|
419
|
+
$output .= '<p>' . esc_html__( 'No posts found.', 'my-plugin' ) . '</p>';
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
$output .= '</div>';
|
|
423
|
+
|
|
424
|
+
wp_reset_postdata();
|
|
425
|
+
|
|
426
|
+
echo $output;
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
### Edit Component for Dynamic Block
|
|
430
|
+
|
|
431
|
+
**src/edit.js:**
|
|
432
|
+
```jsx
|
|
433
|
+
import { __ } from '@wordpress/i18n';
|
|
434
|
+
import { useBlockProps, InspectorControls } from '@wordpress/block-editor';
|
|
435
|
+
import { PanelBody, RangeControl, SelectControl } from '@wordpress/components';
|
|
436
|
+
import { useSelect } from '@wordpress/data';
|
|
437
|
+
|
|
438
|
+
export default function Edit({ attributes, setAttributes }) {
|
|
439
|
+
const { numberOfPosts, postType } = attributes;
|
|
440
|
+
|
|
441
|
+
// Fetch posts for preview
|
|
442
|
+
const posts = useSelect((select) => {
|
|
443
|
+
return select('core').getEntityRecords('postType', postType, {
|
|
444
|
+
per_page: numberOfPosts,
|
|
445
|
+
});
|
|
446
|
+
}, [numberOfPosts, postType]);
|
|
447
|
+
|
|
448
|
+
const blockProps = useBlockProps();
|
|
449
|
+
|
|
450
|
+
return (
|
|
451
|
+
<>
|
|
452
|
+
<InspectorControls>
|
|
453
|
+
<PanelBody title={__('Settings', 'my-plugin')}>
|
|
454
|
+
<SelectControl
|
|
455
|
+
label={__('Post Type', 'my-plugin')}
|
|
456
|
+
value={postType}
|
|
457
|
+
options={[
|
|
458
|
+
{ label: 'Posts', value: 'post' },
|
|
459
|
+
{ label: 'Pages', value: 'page' },
|
|
460
|
+
]}
|
|
461
|
+
onChange={(value) => setAttributes({ postType: value })}
|
|
462
|
+
/>
|
|
463
|
+
|
|
464
|
+
<RangeControl
|
|
465
|
+
label={__('Number of Posts', 'my-plugin')}
|
|
466
|
+
value={numberOfPosts}
|
|
467
|
+
onChange={(value) => setAttributes({ numberOfPosts: value })}
|
|
468
|
+
min={1}
|
|
469
|
+
max={10}
|
|
470
|
+
/>
|
|
471
|
+
</PanelBody>
|
|
472
|
+
</InspectorControls>
|
|
473
|
+
|
|
474
|
+
<div {...blockProps}>
|
|
475
|
+
<p className="block-label">
|
|
476
|
+
{__('Dynamic Block Preview', 'my-plugin')}
|
|
477
|
+
</p>
|
|
478
|
+
|
|
479
|
+
{!posts && <p>{__('Loading...', 'my-plugin')}</p>}
|
|
480
|
+
|
|
481
|
+
{posts && posts.length === 0 && (
|
|
482
|
+
<p>{__('No posts found.', 'my-plugin')}</p>
|
|
483
|
+
)}
|
|
484
|
+
|
|
485
|
+
{posts && posts.length > 0 && (
|
|
486
|
+
<ul className="dynamic-block-list">
|
|
487
|
+
{posts.map((post) => (
|
|
488
|
+
<li key={post.id}>
|
|
489
|
+
<a href={post.link}>{post.title.rendered}</a>
|
|
490
|
+
</li>
|
|
491
|
+
))}
|
|
492
|
+
</ul>
|
|
493
|
+
)}
|
|
494
|
+
</div>
|
|
495
|
+
</>
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
### Save Function for Dynamic Block
|
|
501
|
+
|
|
502
|
+
**src/save.js:**
|
|
503
|
+
```jsx
|
|
504
|
+
// Dynamic blocks don't need a save function
|
|
505
|
+
// Content is rendered server-side via render.php
|
|
506
|
+
export default function save() {
|
|
507
|
+
return null;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
---
|
|
511
|
+
|
|
512
|
+
## Block Variations
|
|
513
|
+
|
|
514
|
+
### Register Block Variations
|
|
515
|
+
|
|
516
|
+
**src/index.js:**
|
|
517
|
+
```js
|
|
518
|
+
import { registerBlockType, registerBlockVariation } from '@wordpress/blocks';
|
|
519
|
+
import { __ } from '@wordpress/i18n';
|
|
520
|
+
|
|
521
|
+
registerBlockType(metadata.name, {
|
|
522
|
+
edit: Edit,
|
|
523
|
+
save,
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
// Register variations
|
|
527
|
+
registerBlockVariation('my-plugin/custom-block', {
|
|
528
|
+
name: 'highlighted',
|
|
529
|
+
title: __('Highlighted Block', 'my-plugin'),
|
|
530
|
+
description: __('A highlighted version of the block', 'my-plugin'),
|
|
531
|
+
icon: 'star-filled',
|
|
532
|
+
attributes: {
|
|
533
|
+
className: 'is-style-highlighted',
|
|
534
|
+
showIcon: true,
|
|
535
|
+
},
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
registerBlockVariation('my-plugin/custom-block', {
|
|
539
|
+
name: 'minimal',
|
|
540
|
+
title: __('Minimal Block', 'my-plugin'),
|
|
541
|
+
description: __('A minimal version of the block', 'my-plugin'),
|
|
542
|
+
icon: 'minus',
|
|
543
|
+
attributes: {
|
|
544
|
+
className: 'is-style-minimal',
|
|
545
|
+
showIcon: false,
|
|
546
|
+
},
|
|
547
|
+
});
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
---
|
|
551
|
+
|
|
552
|
+
## Block Styles
|
|
553
|
+
|
|
554
|
+
### Register Block Styles
|
|
555
|
+
|
|
556
|
+
**src/index.js:**
|
|
557
|
+
```js
|
|
558
|
+
import { registerBlockStyle } from '@wordpress/blocks';
|
|
559
|
+
|
|
560
|
+
// Register after block registration
|
|
561
|
+
registerBlockStyle('my-plugin/custom-block', {
|
|
562
|
+
name: 'rounded',
|
|
563
|
+
label: __('Rounded', 'my-plugin'),
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
registerBlockStyle('my-plugin/custom-block', {
|
|
567
|
+
name: 'shadow',
|
|
568
|
+
label: __('Shadow', 'my-plugin'),
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
registerBlockStyle('my-plugin/custom-block', {
|
|
572
|
+
name: 'bordered',
|
|
573
|
+
label: __('Bordered', 'my-plugin'),
|
|
574
|
+
isDefault: true,
|
|
575
|
+
});
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
### Style CSS
|
|
579
|
+
|
|
580
|
+
**src/style.scss:**
|
|
581
|
+
```scss
|
|
582
|
+
.wp-block-my-plugin-custom-block {
|
|
583
|
+
padding: 20px;
|
|
584
|
+
|
|
585
|
+
&.is-style-rounded {
|
|
586
|
+
border-radius: 10px;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
&.is-style-shadow {
|
|
590
|
+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
&.is-style-bordered {
|
|
594
|
+
border: 2px solid #ddd;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
---
|
|
600
|
+
|
|
601
|
+
## Common Block Components
|
|
602
|
+
|
|
603
|
+
### Media Upload
|
|
604
|
+
|
|
605
|
+
```jsx
|
|
606
|
+
import { MediaUpload, MediaUploadCheck } from '@wordpress/block-editor';
|
|
607
|
+
import { Button } from '@wordpress/components';
|
|
608
|
+
|
|
609
|
+
<MediaUploadCheck>
|
|
610
|
+
<MediaUpload
|
|
611
|
+
onSelect={(media) => setAttributes({ imageUrl: media.url, imageId: media.id })}
|
|
612
|
+
allowedTypes={['image']}
|
|
613
|
+
value={imageId}
|
|
614
|
+
render={({ open }) => (
|
|
615
|
+
<Button onClick={open} variant="primary">
|
|
616
|
+
{imageUrl ? __('Replace Image', 'my-plugin') : __('Select Image', 'my-plugin')}
|
|
617
|
+
</Button>
|
|
618
|
+
)}
|
|
619
|
+
/>
|
|
620
|
+
</MediaUploadCheck>
|
|
621
|
+
|
|
622
|
+
{imageUrl && (
|
|
623
|
+
<img src={imageUrl} alt="" />
|
|
624
|
+
)}
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
### Color Picker
|
|
628
|
+
|
|
629
|
+
```jsx
|
|
630
|
+
import { InspectorControls, ColorPalette } from '@wordpress/block-editor';
|
|
631
|
+
import { PanelBody } from '@wordpress/components';
|
|
632
|
+
|
|
633
|
+
<InspectorControls>
|
|
634
|
+
<PanelBody title={__('Color Settings', 'my-plugin')}>
|
|
635
|
+
<ColorPalette
|
|
636
|
+
value={backgroundColor}
|
|
637
|
+
onChange={(color) => setAttributes({ backgroundColor: color })}
|
|
638
|
+
/>
|
|
639
|
+
</PanelBody>
|
|
640
|
+
</InspectorControls>
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
### Link Control
|
|
644
|
+
|
|
645
|
+
```jsx
|
|
646
|
+
import { __experimentalLinkControl as LinkControl } from '@wordpress/block-editor';
|
|
647
|
+
import { Popover } from '@wordpress/components';
|
|
648
|
+
|
|
649
|
+
const [isLinkPickerVisible, setIsLinkPickerVisible] = useState(false);
|
|
650
|
+
|
|
651
|
+
<Button onClick={() => setIsLinkPickerVisible(true)}>
|
|
652
|
+
{__('Add Link', 'my-plugin')}
|
|
653
|
+
</Button>
|
|
654
|
+
|
|
655
|
+
{isLinkPickerVisible && (
|
|
656
|
+
<Popover onClose={() => setIsLinkPickerVisible(false)}>
|
|
657
|
+
<LinkControl
|
|
658
|
+
value={{ url: linkUrl, opensInNewTab: linkTarget === '_blank' }}
|
|
659
|
+
onChange={(newLink) => {
|
|
660
|
+
setAttributes({
|
|
661
|
+
linkUrl: newLink.url,
|
|
662
|
+
linkTarget: newLink.opensInNewTab ? '_blank' : '_self',
|
|
663
|
+
});
|
|
664
|
+
}}
|
|
665
|
+
/>
|
|
666
|
+
</Popover>
|
|
667
|
+
)}
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
---
|
|
671
|
+
|
|
672
|
+
## Best Practices
|
|
673
|
+
|
|
674
|
+
### Block Development
|
|
675
|
+
|
|
676
|
+
1. **Use block.json**: Define all metadata in block.json (required for WordPress 5.8+)
|
|
677
|
+
2. **Use @wordpress/scripts**: Leverage official build tools for consistency
|
|
678
|
+
3. **Enable REST API**: Set `show_in_rest: true` for custom post types used in blocks
|
|
679
|
+
4. **Use useBlockProps**: Always use `useBlockProps()` in edit and `useBlockProps.save()` in save
|
|
680
|
+
5. **Sanitize attributes**: Validate and sanitize all attribute values
|
|
681
|
+
6. **Escape output**: Use proper escaping in save function and render.php
|
|
682
|
+
7. **Support block styles**: Enable color, spacing, typography supports when appropriate
|
|
683
|
+
8. **Provide examples**: Include example attributes for block preview
|
|
684
|
+
9. **Use semantic HTML**: Choose appropriate HTML tags for accessibility
|
|
685
|
+
10. **Test in editor**: Test block in both editor and frontend
|
|
686
|
+
|
|
687
|
+
### Performance
|
|
688
|
+
|
|
689
|
+
1. **Minimize dependencies**: Only import what you need from @wordpress packages
|
|
690
|
+
2. **Use dynamic blocks wisely**: Only when content must be server-rendered
|
|
691
|
+
3. **Optimize queries**: Limit posts fetched in dynamic blocks
|
|
692
|
+
4. **Cache dynamic content**: Use transients for expensive queries
|
|
693
|
+
5. **Lazy load assets**: Only enqueue scripts/styles when block is used
|
|
694
|
+
|
|
695
|
+
### Accessibility
|
|
696
|
+
|
|
697
|
+
1. **Use ARIA labels**: Provide labels for controls
|
|
698
|
+
2. **Keyboard navigation**: Ensure all controls are keyboard accessible
|
|
699
|
+
3. **Color contrast**: Ensure sufficient contrast in block styles
|
|
700
|
+
4. **Alt text**: Require alt text for images
|
|
701
|
+
5. **Semantic markup**: Use proper heading hierarchy
|
|
702
|
+
|
|
703
|
+
---
|
|
704
|
+
|
|
705
|
+
## Common Pitfalls
|
|
706
|
+
|
|
707
|
+
### ❌ DON'T
|
|
708
|
+
|
|
709
|
+
```jsx
|
|
710
|
+
// Don't mutate attributes directly
|
|
711
|
+
attributes.content = 'new value'; // BAD!
|
|
712
|
+
|
|
713
|
+
// Don't use inline styles without escaping
|
|
714
|
+
<div style={{ color: userInput }}> // BAD!
|
|
715
|
+
|
|
716
|
+
// Don't forget to save block props
|
|
717
|
+
export default function save({ attributes }) {
|
|
718
|
+
return <div>{attributes.content}</div>; // BAD! Missing useBlockProps.save()
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
// Don't use hooks conditionally
|
|
722
|
+
if (showIcon) {
|
|
723
|
+
const [iconColor, setIconColor] = useState('#000'); // BAD!
|
|
724
|
+
}
|
|
725
|
+
```
|
|
726
|
+
|
|
727
|
+
### ✅ DO
|
|
728
|
+
|
|
729
|
+
```jsx
|
|
730
|
+
// Use setAttributes to update
|
|
731
|
+
setAttributes({ content: 'new value' }); // GOOD!
|
|
732
|
+
|
|
733
|
+
// Sanitize and escape user input
|
|
734
|
+
<div style={{ color: sanitizeColor(userInput) }}> // GOOD!
|
|
735
|
+
|
|
736
|
+
// Always use useBlockProps.save()
|
|
737
|
+
export default function save({ attributes }) {
|
|
738
|
+
const blockProps = useBlockProps.save();
|
|
739
|
+
return <div {...blockProps}>{attributes.content}</div>; // GOOD!
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
// Use hooks at top level
|
|
743
|
+
const [iconColor, setIconColor] = useState('#000');
|
|
744
|
+
if (showIcon) {
|
|
745
|
+
// Use the hook value
|
|
746
|
+
}
|
|
747
|
+
```
|
|
748
|
+
|
|
749
|
+
---
|
|
750
|
+
|
|
751
|
+
## PHP Registration (Alternative to block.json)
|
|
752
|
+
|
|
753
|
+
### Register Block in PHP
|
|
754
|
+
|
|
755
|
+
**my-plugin.php:**
|
|
756
|
+
```php
|
|
757
|
+
<?php
|
|
758
|
+
/**
|
|
759
|
+
* Register block
|
|
760
|
+
*/
|
|
761
|
+
function my_plugin_register_block() {
|
|
762
|
+
register_block_type( __DIR__ . '/build' );
|
|
763
|
+
}
|
|
764
|
+
add_action( 'init', 'my_plugin_register_block' );
|
|
765
|
+
```
|
|
766
|
+
|
|
767
|
+
### Register Dynamic Block in PHP
|
|
768
|
+
|
|
769
|
+
```php
|
|
770
|
+
<?php
|
|
771
|
+
/**
|
|
772
|
+
* Register dynamic block with render callback
|
|
773
|
+
*/
|
|
774
|
+
function my_plugin_register_dynamic_block() {
|
|
775
|
+
register_block_type( 'my-plugin/dynamic-block', array(
|
|
776
|
+
'api_version' => 2,
|
|
777
|
+
'attributes' => array(
|
|
778
|
+
'numberOfPosts' => array(
|
|
779
|
+
'type' => 'number',
|
|
780
|
+
'default' => 5,
|
|
781
|
+
),
|
|
782
|
+
),
|
|
783
|
+
'render_callback' => 'my_plugin_render_dynamic_block',
|
|
784
|
+
) );
|
|
785
|
+
}
|
|
786
|
+
add_action( 'init', 'my_plugin_register_dynamic_block' );
|
|
787
|
+
|
|
788
|
+
/**
|
|
789
|
+
* Render callback
|
|
790
|
+
*/
|
|
791
|
+
function my_plugin_render_dynamic_block( $attributes, $content, $block ) {
|
|
792
|
+
$number_of_posts = isset( $attributes['numberOfPosts'] ) ? absint( $attributes['numberOfPosts'] ) : 5;
|
|
793
|
+
|
|
794
|
+
$query = new WP_Query( array(
|
|
795
|
+
'posts_per_page' => $number_of_posts,
|
|
796
|
+
) );
|
|
797
|
+
|
|
798
|
+
$output = '<div ' . get_block_wrapper_attributes() . '>';
|
|
799
|
+
|
|
800
|
+
if ( $query->have_posts() ) {
|
|
801
|
+
$output .= '<ul>';
|
|
802
|
+
while ( $query->have_posts() ) {
|
|
803
|
+
$query->the_post();
|
|
804
|
+
$output .= '<li>' . esc_html( get_the_title() ) . '</li>';
|
|
805
|
+
}
|
|
806
|
+
$output .= '</ul>';
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
$output .= '</div>';
|
|
810
|
+
|
|
811
|
+
wp_reset_postdata();
|
|
812
|
+
|
|
813
|
+
return $output;
|
|
814
|
+
}
|
|
815
|
+
```
|
|
816
|
+
```
|
|
817
|
+
```
|
|
818
|
+
|