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
← All changes | src/Agent/lib/block-patch.js +79 -5 3.2.1 → 3.2.2 View file →
@@ -110,8 +110,50 @@
110 110 }
111 111 return out;
112 112 };
113 113
114 +// Colours WP stores as a slug attribute beside a custom-value attribute.
115 +const PAIRED_COLORS = [
116 + {
117 + named: 'overlayColor',
118 + custom: 'customOverlayColor',
119 + flag: 'isUserOverlayColor',
120 + },
121 + // The icon renders from the value alone; the slug only adds a class.
122 + { named: 'iconColor', custom: 'iconColorValue', valueForSlug: true },
123 + {
124 + named: 'iconBackgroundColor',
125 + custom: 'iconBackgroundColorValue',
126 + valueForSlug: true,
127 + },
128 +];
129 +
130 +const routePairedColors = (attributes, patch, colorSlugs, colorValues) => {
131 + const slugs = new Set(colorSlugs ?? []);
132 + let out = attributes;
133 + for (const { named, custom, flag, valueForSlug } of PAIRED_COLORS) {
134 + const value = patch?.[named];
135 + if (value == null) continue;
136 + const slug = slugs.has(value) ? value : slugForValue(value, colorValues);
137 + if (slug) {
138 + out = { ...out, [named]: slug };
139 + const slugHex = colorValues?.[slug];
140 + out =
141 + valueForSlug && slugHex
142 + ? { ...out, [custom]: colord(String(slugHex)).toHex() }
143 + : unsetPath(out, custom);
144 + } else {
145 + const parsed = colord(value);
146 + out = unsetPath(
147 + { ...out, [custom]: parsed.isValid() ? parsed.toHex() : value },
148 + named,
149 + );
150 + }
151 + if (flag) out = { ...out, [flag]: true };
152 + }
153 + return out;
154 +};
155 +
114 156 // fontSize / fontFamily mirror the color routing: known slug → named attribute
115 157 // (preset class), anything else → the inline style path.
116 158 const NAMED_PRESETS = [
117 159 {
@@ -160,8 +202,9 @@
160 202 ['backgroundColor', 'style.color.background'],
161 203 ['textColor', 'style.color.text'],
162 204 ['gradient', 'style.color.gradient'],
163 205 ...NAMED_PRESETS.map(({ named, path }) => [named, path]),
206 + ...PAIRED_COLORS.map(({ named, custom }) => [named, custom]),
164 207 ];
165 208 const CLEAR_ALIASES = Object.fromEntries(
166 209 ROUTED_PAIRS.flatMap((pair) => pair.map((p) => [p, pair])),
167 210 );
@@ -199,16 +242,45 @@
199 242 if (isMergeable(value)) return emptyLeafPaths(value, path);
200 243 return value === '' ? [path] : [];
201 244 });
202 245
246 +// A block with no style would otherwise save the patch's null and emptied keys.
203 247 const withoutEmptyLeaves = (patch) =>
204 248 Object.fromEntries(
205 249 Object.entries(patch ?? {}).flatMap(([key, value]) => {
206 - if (isMergeable(value)) return [[key, withoutEmptyLeaves(value)]];
207 - return value === '' ? [] : [[key, value]];
250 + if (isMergeable(value)) {
251 + const kept = withoutEmptyLeaves(value);
252 + return Object.keys(kept).length ? [[key, kept]] : [];
253 + }
254 + return value === '' || value == null ? [] : [[key, value]];
208 255 }),
209 256 );
210 257
258 +// Patch values arrive as strings; the parser drops "30" on a number attribute.
259 +const coerceTypes = (block, patch) => {
260 + const attributes = getBlockType(block.name)?.attributes ?? {};
261 + let out = patch;
262 + for (const [key, value] of Object.entries(patch ?? {})) {
263 + if (typeof value !== 'string') continue;
264 + const type = attributes[key]?.type;
265 + if (type === 'boolean' && ['true', 'false'].includes(value)) {
266 + out = { ...out, [key]: value === 'true' };
267 + continue;
268 + }
269 + if (type !== 'number') continue;
270 + const [, amount, unit] =
271 + value.trim().match(/^(-?\d*\.?\d+)\s*([a-z%]*)$/i) ?? [];
272 + if (amount == null) continue;
273 + out = { ...out, [key]: Number(amount) };
274 + // The model writes lengths like "60vh" into the number field.
275 + const unitKey = `${key}Unit`;
276 + if (unit && attributes[unitKey] && out[unitKey] == null) {
277 + out = { ...out, [unitKey]: unit };
278 + }
279 + }
280 + return out;
281 +};
282 +
211 283 export const applyBlockPatch = (
212 284 serializedBlock,
213 285 patch,
214 286 clear = [],
@@ -216,15 +288,17 @@
216 288 ) => {
217 289 const blocks = parse(serializedBlock).map((block) => {
218 290 if (!block.name) return block;
219 291 const remapped = remapText(block, patch);
220 - const filled = withoutEmptyLeaves(remapped);
292 + const filled = coerceTypes(block, withoutEmptyLeaves(remapped));
221 293 const merged = deepMerge(block.attributes, filled);
222 - const routed = routeNamedPresets(
294 + const colored = routePairedColors(
223 295 routeColors(merged, filled, presetSlugs.color, presetSlugs.colorValues),
224 296 filled,
225 - presetSlugs,
297 + presetSlugs.color,
298 + presetSlugs.colorValues,
226 299 );
300 + const routed = routeNamedPresets(colored, filled, presetSlugs);
227 301 return {
228 302 ...block,
229 303 attributes: applyClears(routed, [
230 304 ...(clear ?? []),