PluginProbe
Extendify / 3.2.2
Extendify v3.2.2
3.2.2 3.2.1 3.2.0 3.1.6 3.1.5 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.6 3.0.5 3.0.4 trunk 0.1.0 0.10.0 0.10.1 0.10.2 0.11.0 0.11.1 0.2.0 0.3.0 0.3.1 0.4.0 0.5.0 All 128 releases
extendify / src / Agent / lib / block-schema.js

block-schema.js in Extendify 3.2.2, at src/Agent/lib/block-schema.js

210 lines 6.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 // Patch schema from the block's real `supports` — the model never hand-authors HTML.
2 // Structure only: the backend overlays the model-facing field descriptions.
3
4 import { SETTING_TEXT_BLOCKS } from '@agent/lib/setting-text-blocks';
5
6 const str = { type: 'string' };
7 const obj = (properties) => ({
8 type: 'object',
9 additionalProperties: false,
10 properties,
11 });
12
13 // Color channels default on once color support is present, unless opted out.
14 const colorChannel = (color, channel) => {
15 if (color === true) return true;
16 if (!color || typeof color !== 'object') return false;
17 return color[channel] !== false;
18 };
19
20 // Border/spacing/typography/dimensions are opt-in: `true`, or a truthy per-feature key.
21 const feat = (support, key) =>
22 support === true ||
23 (!!support && support[key] != null && support[key] !== false);
24
25 const prune = (properties) => {
26 const out = {};
27 for (const [key, value] of Object.entries(properties)) {
28 if (value == null) continue;
29 if (value.type === 'object' && !Object.keys(value.properties).length)
30 continue;
31 out[key] = value;
32 }
33 return out;
34 };
35
36 const styleSchema = (supports) => {
37 const border = supports.border ?? supports.__experimentalBorder;
38 const spacing = supports.spacing;
39 const typography = supports.typography;
40 const dimensions = supports.dimensions;
41
42 const borderStyle = prune({
43 color: feat(border, 'color') ? str : null,
44 width: feat(border, 'width') ? str : null,
45 style: feat(border, 'style') ? str : null,
46 radius: feat(border, 'radius') ? str : null,
47 });
48
49 const spacingStyle = prune({
50 padding: feat(spacing, 'padding') ? str : null,
51 margin: feat(spacing, 'margin') ? str : null,
52 blockGap: feat(spacing, 'blockGap') ? str : null,
53 });
54
55 const typographyStyle = prune({
56 textAlign:
57 feat(typography, 'textAlign') ||
58 feat(typography, '__experimentalTextAlign')
59 ? str
60 : null,
61 lineHeight: feat(typography, 'lineHeight') ? str : null,
62 fontFamily: feat(typography, '__experimentalFontFamily') ? str : null,
63 fontWeight: feat(typography, '__experimentalFontWeight') ? str : null,
64 fontStyle: feat(typography, '__experimentalFontStyle') ? str : null,
65 textTransform: feat(typography, '__experimentalTextTransform') ? str : null,
66 textDecoration: feat(typography, '__experimentalTextDecoration')
67 ? str
68 : null,
69 letterSpacing: feat(typography, '__experimentalLetterSpacing') ? str : null,
70 });
71
72 const dimensionsStyle = prune({
73 minHeight: feat(dimensions, 'minHeight') ? str : null,
74 aspectRatio: feat(dimensions, 'aspectRatio') ? str : null,
75 });
76
77 const style = prune({
78 border: Object.keys(borderStyle).length ? obj(borderStyle) : null,
79 spacing: Object.keys(spacingStyle).length ? obj(spacingStyle) : null,
80 typography: Object.keys(typographyStyle).length
81 ? obj(typographyStyle)
82 : null,
83 dimensions: Object.keys(dimensionsStyle).length
84 ? obj(dimensionsStyle)
85 : null,
86 shadow: supports.shadow ? str : null,
87 // The arbitrary-CSS escape hatch — always offered.
88 css: str,
89 });
90
91 return obj(style);
92 };
93
94 const ALIGN_OPTIONS = ['left', 'center', 'right', 'wide', 'full'];
95
96 // supports.align is `true` (everything) or the block's own subset.
97 const alignSchema = (align) => {
98 if (!align) return null;
99 return {
100 type: 'string',
101 enum: align === true ? ALIGN_OPTIONS : align,
102 };
103 };
104
105 // Positioning knobs only — allowedBlocks/default/allowEditing are editor config.
106 const layoutSchema = (supports) => {
107 const layout = supports.layout ?? supports.__experimentalLayout;
108 if (!layout || layout.allowEditing === false) return null;
109 return obj({
110 type: str,
111 contentSize: str,
112 wideSize: str,
113 justifyContent: str,
114 orientation: str,
115 flexWrap: str,
116 verticalAlignment: str,
117 });
118 };
119
120 // Where a block keeps its editable text, most-specific first: `text` (button),
121 // `content` (paragraph, heading), `label` (menu item, search).
122 export const RICH_TEXT_ATTRIBUTES = ['text', 'content', 'label'];
123
124 // Identity attributes (ref, id, className, lock, metadata) stay out —
125 // bad values there break the block, not just its look.
126 const SIMPLE_ATTRIBUTES = [
127 'level',
128 'ordered',
129 'reversed',
130 'start',
131 'width',
132 'height',
133 'verticalAlignment',
134 'isStackedOnMobile',
135 // media-text's image side is an attribute, not child order.
136 'mediaPosition',
137 // Without a size field the model writes picture size into style.css.
138 'mediaWidth',
139 'minHeight',
140 'minHeightUnit',
141 // One field per colour; the patcher routes a slug or a hex into WP's pair.
142 'overlayColor',
143 'dimRatio',
144 'iconColor',
145 'iconBackgroundColor',
146 // The icon follows the network; a new url alone keeps the old icon.
147 'service',
148 ];
149
150 // core/navigation paints from colour attributes but declares no supports.color.
151 const ATTRIBUTE_COLOR_TYPES = new Set(['core/navigation']);
152
153 const simpleAttrSchema = (definition) => {
154 if (!definition) return null;
155 const schema = {};
156 if (definition.type) schema.type = definition.type;
157 if (definition.enum) schema.enum = definition.enum;
158 return Object.keys(schema).length ? schema : str;
159 };
160
161 export const buildBlockSchema = (blockType) => {
162 if (!blockType) return null;
163 const attributes = blockType.attributes ?? {};
164 const supports = blockType.supports ?? {};
165 const attrColor = ATTRIBUTE_COLOR_TYPES.has(blockType.name);
166 const typography = supports.typography;
167
168 const simple = Object.fromEntries(
169 SIMPLE_ATTRIBUTES.map((name) => [name, simpleAttrSchema(attributes[name])]),
170 );
171
172 const properties = prune({
173 ...simple,
174 align: attributes.align ? alignSchema(supports.align) : null,
175 // Quote and pullquote align text with an attribute, not a typography support.
176 textAlign: attributes.textAlign
177 ? { type: 'string', enum: ['left', 'center', 'right'] }
178 : null,
179 layout: attributes.layout ? layoutSchema(supports) : null,
180 // Model-facing name; block-patch maps it back to the real home.
181 text:
182 RICH_TEXT_ATTRIBUTES.some((name) => attributes[name]) ||
183 SETTING_TEXT_BLOCKS[blockType.name]
184 ? str
185 : null,
186 url: attributes.url ? str : null,
187 backgroundColor:
188 attributes.backgroundColor &&
189 (attrColor || colorChannel(supports.color, 'background'))
190 ? str
191 : null,
192 textColor:
193 attributes.textColor &&
194 (attrColor || colorChannel(supports.color, 'text'))
195 ? str
196 : null,
197 // Gradients omitted: the theme's preset slugs are too opaque for the model
198 // to pick reliably, so gradients go through style.css instead.
199 fontSize: attributes.fontSize && feat(typography, 'fontSize') ? str : null,
200 fontFamily:
201 attributes.fontFamily && feat(typography, '__experimentalFontFamily')
202 ? str
203 : null,
204 style: styleSchema(supports),
205 });
206
207 if (!Object.keys(properties).length) return null;
208 return obj(properties);
209 };
210