strapi-dz-component-duplicator 0.1.0 → 0.1.2
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.
|
@@ -78,6 +78,10 @@ const cloneValue = (value) => {
|
|
|
78
78
|
return JSON.parse(JSON.stringify(value));
|
|
79
79
|
};
|
|
80
80
|
|
|
81
|
+
const isMediaObject = (value) => {
|
|
82
|
+
return isPlainObject(value) && typeof value.mime === 'string';
|
|
83
|
+
};
|
|
84
|
+
|
|
81
85
|
const stripTransientKeys = (value) => {
|
|
82
86
|
if (Array.isArray(value)) {
|
|
83
87
|
return value.map(stripTransientKeys);
|
|
@@ -87,6 +91,12 @@ const stripTransientKeys = (value) => {
|
|
|
87
91
|
return value;
|
|
88
92
|
}
|
|
89
93
|
|
|
94
|
+
// Media references must keep their id/documentId so Strapi can
|
|
95
|
+
// maintain the relation on save.
|
|
96
|
+
if (isMediaObject(value)) {
|
|
97
|
+
return value;
|
|
98
|
+
}
|
|
99
|
+
|
|
90
100
|
const next = {};
|
|
91
101
|
|
|
92
102
|
for (const [key, nested] of Object.entries(value)) {
|
|
@@ -107,10 +117,20 @@ const getActionAnchor = (listItem) => {
|
|
|
107
117
|
return null;
|
|
108
118
|
}
|
|
109
119
|
|
|
120
|
+
// Dynamic zone headers include a drag handle, a delete button, and a
|
|
121
|
+
// more-actions menu. We look for the drag button by its aria-label and
|
|
122
|
+
// fall back to verifying that at least three buttons exist (the minimum
|
|
123
|
+
// for a dynamic zone header as of Strapi 5).
|
|
124
|
+
const dragButton =
|
|
125
|
+
header.querySelector('button[aria-label="Drag"]') ||
|
|
126
|
+
header.querySelector('button[aria-label="drag"]');
|
|
127
|
+
|
|
128
|
+
if (dragButton) {
|
|
129
|
+
return dragButton;
|
|
130
|
+
}
|
|
131
|
+
|
|
110
132
|
const buttons = header.querySelectorAll('button');
|
|
111
133
|
|
|
112
|
-
// Dynamic zone headers include delete, drag and more-actions controls.
|
|
113
|
-
// Repeatable components usually don't include the extra menu action.
|
|
114
134
|
if (buttons.length < 3) {
|
|
115
135
|
return null;
|
|
116
136
|
}
|
|
@@ -214,7 +234,7 @@ const findDynamicZoneLocation = (listItem, values, components) => {
|
|
|
214
234
|
return findDynamicZoneLocationFromList(listItem, values, components);
|
|
215
235
|
};
|
|
216
236
|
|
|
217
|
-
const createDuplicateButton = (anchor, label, onClick) => {
|
|
237
|
+
const createDuplicateButton = (anchor, label, onClick, signal) => {
|
|
218
238
|
const button = document.createElement('button');
|
|
219
239
|
button.type = 'button';
|
|
220
240
|
button.className = anchor.className;
|
|
@@ -248,7 +268,7 @@ const createDuplicateButton = (anchor, label, onClick) => {
|
|
|
248
268
|
event.preventDefault();
|
|
249
269
|
event.stopPropagation();
|
|
250
270
|
onClick();
|
|
251
|
-
});
|
|
271
|
+
}, { signal });
|
|
252
272
|
|
|
253
273
|
return button;
|
|
254
274
|
};
|
|
@@ -258,9 +278,13 @@ const DynamicZoneActionInjector = () => {
|
|
|
258
278
|
const { toggleNotification } = useNotification();
|
|
259
279
|
const { form, isLoading, components } = useContentManagerContext();
|
|
260
280
|
|
|
281
|
+
const isBrowser = typeof document !== 'undefined';
|
|
282
|
+
|
|
261
283
|
const values = form?.values;
|
|
262
284
|
const valuesRef = React.useRef(values);
|
|
263
285
|
const observerRef = React.useRef(null);
|
|
286
|
+
const frameRef = React.useRef(0);
|
|
287
|
+
const abortRef = React.useRef(null);
|
|
264
288
|
|
|
265
289
|
valuesRef.current = values;
|
|
266
290
|
|
|
@@ -275,8 +299,9 @@ const DynamicZoneActionInjector = () => {
|
|
|
275
299
|
});
|
|
276
300
|
|
|
277
301
|
const cleanupInjectedButtons = React.useCallback(() => {
|
|
278
|
-
if (
|
|
279
|
-
|
|
302
|
+
if (abortRef.current) {
|
|
303
|
+
abortRef.current.abort();
|
|
304
|
+
abortRef.current = null;
|
|
280
305
|
}
|
|
281
306
|
|
|
282
307
|
const injectedNodes = document.querySelectorAll(`[${DUPLICATE_CONTAINER_ATTR}]`);
|
|
@@ -311,7 +336,7 @@ const DynamicZoneActionInjector = () => {
|
|
|
311
336
|
);
|
|
312
337
|
|
|
313
338
|
const injectButtons = React.useCallback(() => {
|
|
314
|
-
if (
|
|
339
|
+
if (!isBrowser) {
|
|
315
340
|
return;
|
|
316
341
|
}
|
|
317
342
|
|
|
@@ -336,6 +361,9 @@ const DynamicZoneActionInjector = () => {
|
|
|
336
361
|
return;
|
|
337
362
|
}
|
|
338
363
|
|
|
364
|
+
const controller = new AbortController();
|
|
365
|
+
abortRef.current = controller;
|
|
366
|
+
|
|
339
367
|
const listItems = document.querySelectorAll('ol > li');
|
|
340
368
|
|
|
341
369
|
for (const listItem of listItems) {
|
|
@@ -353,7 +381,7 @@ const DynamicZoneActionInjector = () => {
|
|
|
353
381
|
|
|
354
382
|
const duplicateButton = createDuplicateButton(anchor, duplicateLabel, () => {
|
|
355
383
|
handleDuplicate(location.dynamicZonePath, location.index);
|
|
356
|
-
});
|
|
384
|
+
}, controller.signal);
|
|
357
385
|
anchor.parentElement.insertBefore(
|
|
358
386
|
duplicateButton,
|
|
359
387
|
anchor
|
|
@@ -366,15 +394,16 @@ const DynamicZoneActionInjector = () => {
|
|
|
366
394
|
subtree: true,
|
|
367
395
|
});
|
|
368
396
|
}
|
|
369
|
-
}, [cleanupInjectedButtons, duplicateLabel, handleDuplicate, isLoading]);
|
|
397
|
+
}, [cleanupInjectedButtons, components, duplicateLabel, handleDuplicate, isBrowser, isLoading]);
|
|
370
398
|
|
|
371
399
|
React.useEffect(() => {
|
|
372
|
-
if (
|
|
400
|
+
if (!isBrowser) {
|
|
373
401
|
return undefined;
|
|
374
402
|
}
|
|
375
403
|
|
|
376
404
|
const observer = new MutationObserver(() => {
|
|
377
|
-
|
|
405
|
+
cancelAnimationFrame(frameRef.current);
|
|
406
|
+
frameRef.current = requestAnimationFrame(() => injectButtons());
|
|
378
407
|
});
|
|
379
408
|
|
|
380
409
|
observerRef.current = observer;
|
|
@@ -387,15 +416,12 @@ const DynamicZoneActionInjector = () => {
|
|
|
387
416
|
injectButtons();
|
|
388
417
|
|
|
389
418
|
return () => {
|
|
419
|
+
cancelAnimationFrame(frameRef.current);
|
|
390
420
|
observer.disconnect();
|
|
391
421
|
observerRef.current = null;
|
|
392
422
|
cleanupInjectedButtons();
|
|
393
423
|
};
|
|
394
|
-
}, [cleanupInjectedButtons, injectButtons]);
|
|
395
|
-
|
|
396
|
-
React.useEffect(() => {
|
|
397
|
-
injectButtons();
|
|
398
|
-
}, [injectButtons, values]);
|
|
424
|
+
}, [cleanupInjectedButtons, isBrowser, injectButtons]);
|
|
399
425
|
|
|
400
426
|
return null;
|
|
401
427
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "strapi-dz-component-duplicator",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Duplicate dynamic zone components directly from Strapi 5 edit view.",
|
|
5
5
|
"author": "Djordje Savanovic",
|
|
6
6
|
"keywords": [
|
|
@@ -44,11 +44,10 @@
|
|
|
44
44
|
"assets"
|
|
45
45
|
],
|
|
46
46
|
"peerDependencies": {
|
|
47
|
-
"@strapi/admin": "^5.0.0",
|
|
48
|
-
"@strapi/content-manager": "^5.0.0",
|
|
49
47
|
"@strapi/strapi": "^5.0.0",
|
|
50
48
|
"react": "^18.0.0",
|
|
51
|
-
"react-
|
|
49
|
+
"react-dom": "^18.0.0",
|
|
50
|
+
"react-intl": "^7.0.0"
|
|
52
51
|
},
|
|
53
52
|
"strapi": {
|
|
54
53
|
"name": "strapi-dz-component-duplicator",
|