strapi-dz-component-duplicator 0.1.1 → 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 (typeof document === 'undefined') {
279
- return;
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 (typeof document === 'undefined') {
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 (typeof document === 'undefined') {
400
+ if (!isBrowser) {
373
401
  return undefined;
374
402
  }
375
403
 
376
404
  const observer = new MutationObserver(() => {
377
- injectButtons();
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.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": [
@@ -47,10 +47,6 @@
47
47
  "@strapi/strapi": "^5.0.0",
48
48
  "react": "^18.0.0",
49
49
  "react-dom": "^18.0.0",
50
- "react-router-dom": "^6.0.0",
51
- "styled-components": "^6.0.0"
52
- },
53
- "dependencies": {
54
50
  "react-intl": "^7.0.0"
55
51
  },
56
52
  "strapi": {