/**
* Proto-Blocks Schema Knowledge Module
* Complete block.json schema documentation and block generator
*/
export function getBlockJsonSchema() {
return `# Proto-Blocks block.json Schema Reference
Complete reference for all block.json configuration options.
---
## Full Schema Structure
\`\`\`json
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "namespace/block-name",
"title": "Block Title",
"category": "proto-blocks",
"icon": "admin-post",
"description": "Block description",
"keywords": ["keyword1", "keyword2"],
"textdomain": "your-textdomain",
"parent": ["parent/block-name"],
"ancestor": ["ancestor/block-name"],
"allowedBlocks": ["allowed/block-name"],
"supports": { /* WordPress supports */ },
"attributes": { /* Custom attributes */ },
"styles": [ /* Block style variations */ ],
"example": { /* Preview example */ },
"editorScript": "file:./editor.js",
"editorStyle": "file:./editor.css",
"style": "file:./style.css",
"viewScript": "file:./view.js",
"viewScriptModule": "file:./view.js",
"render": "file:./render.php",
"protoBlocks": { /* Proto-Blocks configuration */ }
}
\`\`\`
---
## Core Properties
### name (required)
Unique block identifier in \`namespace/block-name\` format.
\`\`\`json
"name": "proto-blocks/feature-card"
\`\`\`
### title (required)
Human-readable block title shown in the inserter.
\`\`\`json
"title": "Feature Card"
\`\`\`
### category (required)
Block category slug. Use \`"proto-blocks"\` or any WordPress category.
\`\`\`json
"category": "proto-blocks"
\`\`\`
Standard categories: \`text\`, \`media\`, \`design\`, \`widgets\`, \`theme\`, \`embed\`
### icon
Dashicon name or custom SVG.
\`\`\`json
"icon": "admin-post"
\`\`\`
Common icons: \`admin-post\`, \`format-quote\`, \`format-image\`, \`id-alt\`, \`grid-view\`, \`list-view\`, \`cover-image\`, \`button\`
### description
Block description for the inserter.
\`\`\`json
"description": "A feature card with image and content"
\`\`\`
### keywords
Search keywords for the inserter.
\`\`\`json
"keywords": ["card", "feature", "box", "content"]
\`\`\`
---
## Supports
### Complete Supports Reference
\`\`\`json
"supports": {
"html": false,
"anchor": true,
"customClassName": true,
"className": true,
"align": ["left", "center", "right", "wide", "full"],
"alignWide": true,
"ariaLabel": true,
"background": {
"backgroundImage": true,
"backgroundSize": true
},
"color": {
"background": true,
"text": true,
"link": true,
"gradients": true,
"enableContrastChecker": true
},
"dimensions": {
"minHeight": true,
"aspectRatio": true
},
"filter": {
"duotone": true
},
"layout": {
"default": { "type": "constrained" },
"allowSwitching": true,
"allowEditing": true,
"allowInheriting": true,
"allowSizingOnChildren": true,
"allowVerticalAlignment": true,
"allowJustification": true,
"allowOrientation": true,
"allowCustomContentAndWideSize": true
},
"position": {
"sticky": true
},
"shadow": true,
"spacing": {
"padding": true,
"margin": true,
"blockGap": true
},
"typography": {
"fontSize": true,
"lineHeight": true,
"textAlign": true,
"textTransform": true,
"letterSpacing": true,
"fontFamily": true,
"fontStyle": true,
"fontWeight": true,
"textDecoration": true,
"writingMode": true
},
"interactivity": true
}
\`\`\`
### Common Supports Configurations
**Minimal Block:**
\`\`\`json
"supports": {
"html": false,
"anchor": true,
"customClassName": true
}
\`\`\`
**Content Block:**
\`\`\`json
"supports": {
"html": false,
"anchor": true,
"customClassName": true,
"color": {
"background": true,
"text": true
},
"spacing": {
"padding": true,
"margin": true
},
"typography": {
"fontSize": true
}
}
\`\`\`
**Layout Block:**
\`\`\`json
"supports": {
"html": false,
"align": ["wide", "full"],
"color": {
"background": true,
"gradients": true
},
"spacing": {
"padding": true,
"margin": true
}
}
\`\`\`
**Interactive Block:**
\`\`\`json
"supports": {
"html": false,
"interactivity": true
}
\`\`\`
---
## protoBlocks Configuration
### version (required)
Proto-Blocks schema version.
\`\`\`json
"protoBlocks": {
"version": "1.0"
}
\`\`\`
### template (required)
PHP template file path relative to block folder.
\`\`\`json
"protoBlocks": {
"template": "template.php"
}
\`\`\`
### useTailwind
Enable Tailwind CSS compilation.
\`\`\`json
"protoBlocks": {
"useTailwind": true
}
\`\`\`
### fields
Editable field definitions.
\`\`\`json
"protoBlocks": {
"fields": {
"fieldName": {
"type": "text|wysiwyg|image|link|repeater|innerblocks",
/* type-specific options */
}
}
}
\`\`\`
### controls
Inspector panel control definitions.
\`\`\`json
"protoBlocks": {
"controls": {
"controlName": {
"type": "text|textarea|select|toggle|checkbox|range|number|color|color-palette|radio|image",
"label": "Control Label",
"default": "defaultValue",
/* type-specific options */
}
}
}
\`\`\`
### isExample
Mark as example/demo block.
\`\`\`json
"protoBlocks": {
"isExample": true
}
\`\`\`
---
## Field Type Schemas
### Text Field
\`\`\`json
{
"type": "text",
"tagName": "h2"
}
\`\`\`
### WYSIWYG Field
\`\`\`json
{
"type": "wysiwyg"
}
\`\`\`
### Image Field
\`\`\`json
{
"type": "image",
"sizes": ["thumbnail", "medium", "large", "full"]
}
\`\`\`
### Link Field
\`\`\`json
{
"type": "link",
"tagName": "a"
}
\`\`\`
### Repeater Field
\`\`\`json
{
"type": "repeater",
"min": 1,
"max": 10,
"itemLabel": "title",
"collapsible": true,
"allowAdd": true,
"allowRemove": true,
"allowReorder": true,
"allowDuplicate": true,
"fields": {
"nestedField": { "type": "text" }
}
}
\`\`\`
### Inner Blocks Field
\`\`\`json
{
"type": "innerblocks",
"allowedBlocks": ["core/paragraph", "core/heading"],
"template": [
["core/heading", { "level": 2 }],
["core/paragraph", {}]
],
"templateLock": false,
"orientation": "vertical"
}
\`\`\`
---
## Control Type Schemas
### Text Control
\`\`\`json
{
"type": "text",
"label": "Button Text",
"default": "Click Here",
"help": "Help text here"
}
\`\`\`
### Textarea Control
\`\`\`json
{
"type": "textarea",
"label": "Description",
"default": ""
}
\`\`\`
### Select Control
\`\`\`json
{
"type": "select",
"label": "Layout",
"default": "vertical",
"options": [
{ "key": "vertical", "label": "Vertical" },
{ "key": "horizontal", "label": "Horizontal" }
]
}
\`\`\`
### Toggle Control
\`\`\`json
{
"type": "toggle",
"label": "Show Title",
"default": true
}
\`\`\`
### Checkbox Control
\`\`\`json
{
"type": "checkbox",
"label": "Enable Feature",
"default": false
}
\`\`\`
### Range Control
\`\`\`json
{
"type": "range",
"label": "Columns",
"min": 1,
"max": 6,
"step": 1,
"default": 3
}
\`\`\`
### Number Control
\`\`\`json
{
"type": "number",
"label": "Min Height",
"min": 100,
"max": 1000,
"default": 400
}
\`\`\`
### Color Control
\`\`\`json
{
"type": "color",
"label": "Background Color",
"default": "#ffffff"
}
\`\`\`
### Color Palette Control
\`\`\`json
{
"type": "color-palette",
"label": "Accent Color"
}
\`\`\`
### Radio Control
\`\`\`json
{
"type": "radio",
"label": "Alignment",
"default": "left",
"options": [
{ "key": "left", "label": "Left" },
{ "key": "center", "label": "Center" },
{ "key": "right", "label": "Right" }
]
}
\`\`\`
### Image Control
\`\`\`json
{
"type": "image",
"label": "Icon",
"sizes": ["thumbnail", "medium"]
}
\`\`\`
---
## Conditional Controls
\`\`\`json
{
"layout": {
"type": "select",
"default": "simple",
"options": [
{ "key": "simple", "label": "Simple" },
{ "key": "advanced", "label": "Advanced" }
]
},
"advancedOption": {
"type": "text",
"label": "Advanced Option",
"conditions": {
"visible": {
"layout": ["advanced"]
}
}
}
}
\`\`\`
---
## Script and Style Properties
\`\`\`json
{
"editorScript": "file:./editor.js",
"editorStyle": "file:./editor.css",
"style": "file:./style.css",
"viewScript": "file:./view.js",
"viewScriptModule": "file:./view.js"
}
\`\`\`
| Property | Loaded In | Format |
|----------|-----------|--------|
| editorScript | Editor only | Traditional |
| editorStyle | Editor only | CSS |
| style | Both | CSS |
| viewScript | Frontend only | Traditional |
| viewScriptModule | Frontend only | ES Module |
---
## Example Block Preview
\`\`\`json
{
"example": {
"attributes": {
"title": "Example Title",
"content": "<p>Example content</p>",
"image": {
"url": "https://example.com/image.jpg"
}
}
}
}
\`\`\`
`;
}
export function generateBlockJson(options) {
const {
name,
title,
description,
useTailwind = false,
useInteractivity = false,
} = options;
// Parse the description to determine what fields/controls are needed
const lowerDesc = description.toLowerCase();
const fields = {};
const controls = {};
// Detect field types from description
if (lowerDesc.includes('image') || lowerDesc.includes('photo') || lowerDesc.includes('picture')) {
fields.image = {
type: 'image',
sizes: ['medium', 'large'],
};
}
if (lowerDesc.includes('title') || lowerDesc.includes('heading') || lowerDesc.includes('headline')) {
fields.title = {
type: 'text',
tagName: 'h2',
};
}
if (lowerDesc.includes('content') || lowerDesc.includes('description') || lowerDesc.includes('text') || lowerDesc.includes('paragraph')) {
fields.content = {
type: 'wysiwyg',
};
}
if (lowerDesc.includes('link') || lowerDesc.includes('button') || lowerDesc.includes('cta')) {
fields.link = {
type: 'link',
tagName: 'a',
};
}
if (lowerDesc.includes('list') || lowerDesc.includes('items') || lowerDesc.includes('repeater') || lowerDesc.includes('multiple')) {
fields.items = {
type: 'repeater',
min: 1,
max: 10,
itemLabel: 'title',
collapsible: true,
fields: {
title: { type: 'text', tagName: 'h3' },
description: { type: 'wysiwyg' },
},
};
}
if (lowerDesc.includes('inner') || lowerDesc.includes('nested') || lowerDesc.includes('contain')) {
fields.innerContent = {
type: 'innerblocks',
allowedBlocks: ['core/paragraph', 'core/heading', 'core/image', 'core/button'],
template: [
['core/paragraph', { placeholder: 'Add content...' }],
],
};
}
// Add common controls
if (lowerDesc.includes('layout') || lowerDesc.includes('style') || lowerDesc.includes('variant')) {
controls.layout = {
type: 'select',
label: 'Layout',
default: 'default',
options: [
{ key: 'default', label: 'Default' },
{ key: 'alternate', label: 'Alternate' },
],
};
}
if (lowerDesc.includes('color') || lowerDesc.includes('background')) {
controls.backgroundColor = {
type: 'color',
label: 'Background Color',
default: '#ffffff',
};
}
// Ensure we have at least one field
if (Object.keys(fields).length === 0) {
fields.title = { type: 'text', tagName: 'h2' };
fields.content = { type: 'wysiwyg' };
}
// Build block.json
const blockJson = {
$schema: 'https://schemas.wp.org/trunk/block.json',
apiVersion: 3,
name: `proto-blocks/${name}`,
title: title,
category: 'proto-blocks',
icon: 'admin-post',
description: description,
supports: {
html: false,
anchor: true,
customClassName: true,
color: {
background: true,
text: true,
},
spacing: {
padding: true,
margin: true,
},
},
protoBlocks: {
version: '1.0',
template: 'template.php',
useTailwind: useTailwind,
fields: fields,
controls: Object.keys(controls).length > 0 ? controls : undefined,
},
};
if (useInteractivity) {
blockJson.supports.interactivity = true;
blockJson.viewScriptModule = 'file:./view.js';
}
// Generate template
const template = generateTemplate(name, fields, controls, useTailwind, useInteractivity);
// Generate styles
const styles = generateStyles(name, fields, useTailwind);
// Generate view.js if interactive
const viewJs = useInteractivity ? generateViewJs(name) : null;
return `# Generated Proto-Block: ${title}
## File Structure
\`\`\`
${name}/
├── block.json
├── template.php
├── style.css${useInteractivity ? '\n└── view.js' : ''}
\`\`\`
---
## block.json
\`\`\`json
${JSON.stringify(blockJson, null, 4)}
\`\`\`
---
## template.php
\`\`\`php
${template}
\`\`\`
---
## style.css
\`\`\`css
${styles}
\`\`\`
${useInteractivity ? `
---
## view.js
\`\`\`javascript
${viewJs}
\`\`\`
` : ''}
---
## Usage
1. Create a folder named \`${name}\` in your theme's \`proto-blocks\` directory
2. Copy each file's content into the respective file
3. The block will be automatically discovered and registered
4. Find "${title}" in the block inserter
`;
}
function generateTemplate(name, fields, controls, useTailwind, useInteractivity) {
const className = name.replace(/-/g, '_');
const cssClass = name;
let php = `<?php
/**
* ${name.charAt(0).toUpperCase() + name.slice(1).replace(/-/g, ' ')} Block Template
*
* @var array $attributes Block attributes
* @var string $content Inner blocks content
* @var WP_Block $block Block instance
*/
// Extract attributes with defaults
`;
// Add attribute extraction
Object.keys(fields).forEach((field) => {
const varName = field.replace(/([A-Z])/g, '_$1').toLowerCase();
if (fields[field].type === 'repeater') {
php += `$${varName} = $attributes['${field}'] ?? [];\n`;
} else if (fields[field].type === 'image') {
php += `$${varName} = $attributes['${field}'] ?? null;\n`;
} else if (fields[field].type === 'link') {
php += `$${varName} = $attributes['${field}'] ?? null;\n`;
} else {
php += `$${varName} = $attributes['${field}'] ?? '';\n`;
}
});
Object.keys(controls).forEach((control) => {
php += `$${control} = $attributes['${control}'] ?? '${controls[control].default || ''}';\n`;
});
if (useInteractivity) {
php += `
// Interactivity context
$context = [
'isOpen' => false,
];
`;
}
php += `
$wrapper_attributes = get_block_wrapper_attributes([
'class' => '${cssClass}',`;
if (useInteractivity) {
php += `
'data-wp-interactive' => 'proto-blocks/${name}',
'data-wp-context' => wp_json_encode($context),`;
}
php += `
]);
?>
<div <?php echo $wrapper_attributes; ?>>
`;
// Add field rendering
if (fields.image) {
php += ` <?php if ($image && !empty($image['url'])) : ?>
<div class="${cssClass}__image" data-proto-field="image">
<img src="<?php echo esc_url($image['url']); ?>" alt="<?php echo esc_attr($image['alt'] ?? ''); ?>" />
</div>
<?php else : ?>
<div class="${cssClass}__image ${cssClass}__image--placeholder" data-proto-field="image">
<span>+</span>
</div>
<?php endif; ?>
`;
}
php += ` <div class="${cssClass}__content">
`;
if (fields.title) {
php += ` <h2 class="${cssClass}__title" data-proto-field="title"><?php echo esc_html($title); ?></h2>
`;
}
if (fields.content) {
php += ` <div class="${cssClass}__description" data-proto-field="content">
<?php echo wp_kses_post($content); ?>
</div>
`;
}
if (fields.items) {
php += ` <ul class="${cssClass}__items" data-proto-repeater="items">
<?php foreach ($items as $item) : ?>
<li class="${cssClass}__item" data-proto-repeater-item>
<h3 data-proto-field="title"><?php echo esc_html($item['title'] ?? ''); ?></h3>
<div data-proto-field="description"><?php echo wp_kses_post($item['description'] ?? ''); ?></div>
</li>
<?php endforeach; ?>
</ul>
`;
}
if (fields.innerContent) {
php += ` <div class="${cssClass}__inner" data-proto-inner-blocks>
<?php echo $content; ?>
</div>
`;
}
if (fields.link) {
php += ` <?php if ($link && !empty($link['url'])) : ?>
<a
href="<?php echo esc_url($link['url']); ?>"
class="${cssClass}__link"
data-proto-field="link"
<?php echo !empty($link['target']) ? 'target="' . esc_attr($link['target']) . '"' : ''; ?>
>
<?php echo esc_html($link['text'] ?? 'Learn More'); ?>
</a>
<?php endif; ?>
`;
}
php += ` </div>
</div>`;
return php;
}
function generateStyles(name, fields, useTailwind) {
if (useTailwind) {
return `/* Tailwind CSS - styles are applied inline in the template */
.${name} {
@apply p-6 bg-white rounded-lg shadow-md;
}
.${name}__title {
@apply text-2xl font-bold mb-2;
}
.${name}__description {
@apply text-gray-600;
}`;
}
let css = `.${name} {
padding: 1.5rem;
background: #ffffff;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
`;
if (fields.image) {
css += `.${name}__image {
margin-bottom: 1rem;
border-radius: 4px;
overflow: hidden;
}
.${name}__image img {
width: 100%;
height: auto;
display: block;
}
.${name}__image--placeholder {
min-height: 150px;
background: #f1f5f9;
display: flex;
align-items: center;
justify-content: center;
color: #94a3b8;
font-size: 2rem;
}
`;
}
if (fields.title) {
css += `.${name}__title {
margin: 0 0 0.5rem;
font-size: 1.5rem;
font-weight: 600;
}
.${name}__title:empty::before {
content: 'Enter title';
color: #94a3b8;
}
`;
}
if (fields.content) {
css += `.${name}__description {
color: #64748b;
line-height: 1.6;
}
`;
}
if (fields.link) {
css += `.${name}__link {
display: inline-flex;
align-items: center;
margin-top: 1rem;
color: #3b82f6;
font-weight: 500;
text-decoration: none;
}
.${name}__link:hover {
text-decoration: underline;
}
`;
}
return css;
}
function generateViewJs(name) {
return `import { store, getContext } from '@wordpress/interactivity';
store('proto-blocks/${name}', {
state: {
get isOpen() {
const context = getContext();
return context.isOpen;
},
},
actions: {
toggle() {
const context = getContext();
context.isOpen = !context.isOpen;
},
},
});`;
}