diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index cb45b1a..e50d447 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -4,10 +4,10 @@ on:
workflow_dispatch:
push:
branches:
- - master
+ - main
pull_request:
branches:
- - master
+ - main
- develop
jobs:
diff --git a/admin/src/components/DuplicateButton/index.jsx b/admin/src/components/DuplicateButton/index.jsx
new file mode 100644
index 0000000..57e0919
--- /dev/null
+++ b/admin/src/components/DuplicateButton/index.jsx
@@ -0,0 +1,131 @@
+import React, { useEffect, useState } from 'react';
+// eslint-disable-next-line import/no-unresolved
+import { renderToStaticMarkup } from 'react-dom/server';
+import { useCMEditViewDataManager } from '@strapi/helper-plugin';
+import { Duplicate } from '@strapi/icons';
+
+const Button = () => (
+
+);
+
+const insertDuplicateButton = (node) => {
+ const deleteButtons = node.querySelectorAll('button');
+ deleteButtons.forEach((button) => {
+ const span = button.querySelector('span');
+ if (span && span.textContent.trim() === 'Delete') {
+
+ const buttonContainer = button.parentElement.parentElement;
+
+ // Check if there already is a duplicate button in the container.
+ // If so we should not attempt to add a new one.
+ if (buttonContainer && !buttonContainer.querySelector('.duplicator-span')) {
+
+ const duplicatorSpan = document.createElement('span');
+ duplicatorSpan.classList.add('duplicator-span');
+ duplicatorSpan.style.display = 'inline-block';
+
+ const duplicatorButtonHTML = renderToStaticMarkup();
+ duplicatorSpan.innerHTML = duplicatorButtonHTML;
+
+ buttonContainer.insertBefore(duplicatorSpan, button.parentElement.nextSibling);
+ }
+ }
+ });
+};
+
+
+const DuplicateButton = () => {
+ const { modifiedData, onChange, allLayoutData } = useCMEditViewDataManager();
+ const [count, setCount] = useState(0);
+ const visibleFields = allLayoutData.contentType.layouts.edit;
+ const repeatableComponentFields = visibleFields.filter((field) => field[0].fieldSchema.type === 'component' && field[0].fieldSchema.repeatable);
+
+ // Initially insert the duplicate buttons for all existing repeatable components.
+ useEffect(() => {
+ repeatableComponentFields.forEach((field) => {
+ const { label } = field[0].metadatas;
+
+ for (const labelElement of document.querySelectorAll('label')) {
+ if (labelElement.textContent?.startsWith(`${label}`)) {
+ const wrapperElement = labelElement.parentElement.parentElement.parentElement;
+ insertDuplicateButton(wrapperElement);
+ }
+ }
+ });
+ }, [allLayoutData]);
+
+ // Insert the duplicate button for any new repeatable components that are added to the DOM.
+ // This happens when somebody adds a new component to the list, or drags it to a new position.
+ useEffect(() => {
+ const targetNode = document.body;
+ const config = { childList: true, subtree: true };
+
+ const callback = (mutationsList, observer) => {
+ mutationsList.forEach((mutation) => {
+ if (mutation.type === 'childList') {
+ mutation.addedNodes.forEach((node) => {
+ if (node.nodeType === Node.ELEMENT_NODE) {
+ insertDuplicateButton(node);
+ setCount((prevCount) => prevCount + 1);
+ }
+ });
+ }
+ });
+ };
+
+ const observer = new MutationObserver(callback);
+ observer.observe(targetNode, config);
+ }, []);
+
+ // Add a click event listener to all the duplicate buttons.
+ useEffect(() => {
+ const duplicatorButtons = document.querySelectorAll('.duplicator-button');
+
+ if (!duplicatorButtons || duplicatorButtons.length === 0) {
+ return () => {};
+ }
+
+ const clickFunction = (e) => {
+ const button = e.target.nodeName === 'BUTTON' ? e.target : e.target.closest('button');
+ const listItem = button.parentElement.parentElement.parentElement.parentElement.parentElement.parentElement;
+ const siblings = Array.from(listItem.parentElement.children);
+ const index = siblings.indexOf(listItem);
+
+ const wrapper = listItem.parentElement.parentElement.parentElement.parentElement.parentElement;
+ const componentLabel = wrapper.querySelector('label').textContent;
+ const componentInfo = repeatableComponentFields.filter((field) => componentLabel?.startsWith(`${field[0].metadatas.label}`));
+ const componentName = componentInfo[0][0].name;
+
+ const componentData = modifiedData[componentName][index];
+
+ const currentComponents = [...modifiedData[componentName]] || [];
+ const newComponent = { ...componentData, __temp_key__: Date.now() };
+ delete newComponent.id;
+
+ currentComponents.splice(index + 1, 0, newComponent);
+
+ onChange({ target: { name: componentName, value: currentComponents } });
+ };
+
+ duplicatorButtons.forEach((button) => {
+ button.addEventListener('click', clickFunction);
+ });
+
+ return () => {
+ duplicatorButtons.forEach((button) => {
+ button.removeEventListener('click', clickFunction);
+ });
+ };
+ }, [modifiedData, count]);
+};
+
+export default DuplicateButton;
diff --git a/admin/src/components/Duplicator/index.jsx b/admin/src/components/Duplicator/index.jsx
deleted file mode 100644
index 9dc5327..0000000
--- a/admin/src/components/Duplicator/index.jsx
+++ /dev/null
@@ -1,30 +0,0 @@
-// admin/src/components/Duplicator.jsx
-import React from 'react';
-import { Button } from '@strapi/design-system/Button';
-import { useCMEditViewDataManager } from '@strapi/helper-plugin';
-import { useIntl } from 'react-intl';
-import getTrad from '../../helpers/getTrad';
-
-const Duplicator = () => {
- const { modifiedData, onChange } = useCMEditViewDataManager();
- const { formatMessage } = useIntl();
-
- const handleDuplicate = () => {
- const currentVariants = modifiedData.variants || [];
- const lastVariant = currentVariants[currentVariants.length - 1] || {};
-
- const newVariant = {
- ...lastVariant,
- id: Date.now(), // Unieke ID
- name: '', // Optioneel: reset naam
- // Voeg andere velden toe die je wilt resetten
- };
-
- const updatedVariants = [...currentVariants, newVariant];
- onChange({ target: { name: 'variants', value: updatedVariants } });
- };
-
- return null;
-};
-
-export default Duplicator;
diff --git a/admin/src/domManipulator.js b/admin/src/domManipulator.js
deleted file mode 100644
index 7e8a224..0000000
--- a/admin/src/domManipulator.js
+++ /dev/null
@@ -1,187 +0,0 @@
-import React, { useEffect, useState } from 'react';
-import { renderToStaticMarkup } from 'react-dom/server';
-import { useCMEditViewDataManager, useNotification } from '@strapi/helper-plugin';
-import { useIntl } from 'react-intl';
-import getTrad from './helpers/getTrad';
-
-/**
- * DuplicatorButton Component
- * Deze component rendert de duplicator knop met de innerlijke span en SVG.
- */
-const DuplicatorButton = ({ index }) => (
-
-);
-
-const insertDuplicateButton = (node) => {
- // Zoek naar alle 'Delete' knoppen binnen het nieuwe node
- const deleteButtons = node.querySelectorAll('button');
- deleteButtons.forEach((button) => {
- const duplicatorButtons = document.querySelectorAll('.duplicator-button');
- const index = duplicatorButtons.length;
- const span = button.querySelector('span');
- if (span && span.textContent.trim() === 'Delete') {
- console.log('Gevonden Delete knop:', button);
-
- const buttonContainer = button.parentElement; // De element rond de 'Delete' knop
-
- if (buttonContainer) {
- // Controleer of de duplicator knop al bestaat binnen dezelfde container
- if (!buttonContainer.querySelector('.duplicator-span')) { // Check for duplicator-span
- console.log('Voeg duplicator knop toe aan:', buttonContainer);
-
- // Creƫer een nieuwe element voor de duplicator knop
- const duplicatorSpan = document.createElement('span');
- duplicatorSpan.classList.add('duplicator-span');
- duplicatorSpan.style.display = 'inline-block'; // Zorg ervoor dat de span naast bestaande spans staat
-
- // Render de DuplicatorButton component als HTML string en geef de variant index door
- const duplicatorButtonHTML = renderToStaticMarkup();
- duplicatorSpan.innerHTML = duplicatorButtonHTML;
-
- // Voeg de duplicator span toe naast de bestaande span
- buttonContainer.parentElement.insertBefore(duplicatorSpan, buttonContainer.nextSibling);
- }
- } else {
- console.warn('buttonContainer niet gevonden voor Delete knop:', button);
- }
- }
- });
-};
-
-
-/**
- * Setup DOM Manipulator
- * Initialiseert een MutationObserver die de DOM observeert op toevoeging van nieuwe nodes.
- * Het zoekt specifiek naar 'Delete' knoppen en voegt daar de Duplicator-knop naast toe in een nieuwe .
- *
- * @param {object} app - Strapi app instance
- */
-export const setupDOMManipulator = (app) => {
- const [insertedButtons, setInsertedButtons] = useState(0);
- const targetNode = document.body; // Observeer het gehele body-element
- const config = { childList: true, subtree: true }; // Observeer kind-elementen en de gehele subtree
- const { modifiedData, onChange, slug, allLayoutData } = useCMEditViewDataManager();
-
- console.log(modifiedData);
-
- useEffect(() => {
- const visibleFields = allLayoutData.contentType.layouts.edit;
- const repeatableComponentFields = visibleFields.filter((field) => field[0].fieldSchema.type === 'component' && field[0].fieldSchema.repeatable);
-
- repeatableComponentFields.forEach((field) => {
- const { label } = field[0].metadatas;
-
- for (const labelElement of document.querySelectorAll('label')) {
- if (labelElement.textContent?.startsWith(`${label}`)) {
- const wrapperElement = labelElement.parentElement.parentElement.parentElement;
- insertDuplicateButton(wrapperElement);
- setInsertedButtons(insertedButtons + 1);
- }
- }
- });
- }, [allLayoutData]);
-
- useEffect(() => {
- const callback = (mutationsList, observer) => {
- mutationsList.forEach((mutation) => {
- if (mutation.type === 'childList') {
- mutation.addedNodes.forEach((node) => {
- if (node.nodeType === Node.ELEMENT_NODE) {
- insertDuplicateButton(node);
- setInsertedButtons(insertedButtons + 1);
- console.log('inserted button');
- }
- });
- }
- });
- };
-
- // Initialiseer de MutationObserver met de callback en config
-
- // @todo: De mutiation observer zorgt er voor dat de duplicator knoppen
- // alleen worden toegevoegd aan de DOM wanneer er nieuwe nodes worden toegevoegd.
- // Echter betekend dat voor bestaande wielen, met al eerder aangemaakte varianten,
- // de duplicator knoppen niet worden toegevoegd. Dit moet nog worden opgelost.
- const observer = new MutationObserver(callback);
- observer.observe(targetNode, config);
- }, []);
-
- useEffect(() => {
- // @todo: Hier moet een loop komen om alle duplicator-buttons te selecteren
- // en event listeners toe te voegen.
-
- // Selecteer de duplicator-button om een event listener toe te voegen
- const duplicatorButtons = document.querySelectorAll('.duplicator-button');
-
- if (!duplicatorButtons || duplicatorButtons.length === 0) {
- return () => {};
- }
-
- const clickFunction = (e) => {
- console.log('Dupliceer knop geklikt via innerHTML', e);
-
- // Gebruik de variantIndex om de juiste variant te vinden
- const index = parseInt(e.target.parentElement.parentElement.getAttribute('data-index'), 10);
- console.log('Dupliceer knop indexx:', index);
-
- // Haal de variant data op via de index
- // @todo: Hier staat .Variant hardcoded. Dit moet dynamisch worden gemaakt.
- const componentData = modifiedData.Variant[index];
- if (!componentData) {
- console.error('Variant data niet gevonden voor index:', index);
- return;
- }
-
- // Log de variant data
- console.log('Variant data:', componentData);
-
- // Implementeer de duplicatie logica
- // @todo: Hier staat .Variant hardcoded. Dit moet dynamisch worden gemaakt.
- const currentVariants = modifiedData.Variant || [];
- const newVariant = { ...componentData, __temp_key__: Date.now() }; // Voeg unieke ID toe indien nodig
- delete newVariant.id;
-
- // Update de varianten lijst en voeg het nieuwe item toe
- const updatedVariants = [...currentVariants, newVariant];
-
- // Gebruik onChange om de nieuwe variant lijst in te stellen
- // @todo: Hier staat Variant hardcoded. Dit moet dynamisch worden gemaakt.
- onChange({ target: { name: 'Variant', value: updatedVariants } });
-
- // // Gebruik notification in plaats van alert
- // app.toggleNotification({
- // type: 'success',
- // message: { id: getTrad('duplicator.success'), defaultMessage: 'Component gedupliceerd!' },
- // });
- };
-
- duplicatorButtons.forEach((button) => {
- button.addEventListener('click', clickFunction);
- });
-
- console.log('added click function');
-
- return () => {
- duplicatorButtons.forEach((button) => {
- button.removeEventListener('click', clickFunction);
- });
- };
- }, [modifiedData, insertedButtons]);
-};
-
-export default setupDOMManipulator;
diff --git a/admin/src/index.js b/admin/src/index.js
index 8ec3c1d..762c745 100644
--- a/admin/src/index.js
+++ b/admin/src/index.js
@@ -4,7 +4,7 @@ import pluginPkg from '../../package.json';
import pluginId from './helpers/pluginId';
import Duplicator from './components/Duplicator';
import pluginPermissions from './permissions';
-import { setupDOMManipulator } from './domManipulator';
+import DuplicateButton from './components/DuplicateButton';
// import getTrad from './helpers/getTrad';
const pluginDescription = pluginPkg.strapi.description || pluginPkg.description;
@@ -31,7 +31,7 @@ export default {
// Inject CMEditViewExclude
app.injectContentManagerComponent('editView', 'informations', {
name: 'component-duplicator-exclude-filter-edit-view',
- Component: setupDOMManipulator,
+ Component: DuplicateButton,
});
// Inject Duplicator - tijdelijk uitgeschakeld
diff --git a/package.json b/package.json
index 25e5347..c90a542 100644
--- a/package.json
+++ b/package.json
@@ -39,7 +39,6 @@
"immutable": "^3.8.2",
"redux-immutable": "^4.0.0",
"redux-thunk": "^2.3.0",
- "component-duplicator": "^7.1.0",
"xml2js": "^0.5.0"
},
"author": {
diff --git a/playground/.strapi/client/app.js b/playground/.strapi/client/app.js
index 36f5ecc..5328939 100644
--- a/playground/.strapi/client/app.js
+++ b/playground/.strapi/client/app.js
@@ -4,13 +4,13 @@
*/
import i18N from "@strapi/plugin-i18n/strapi-admin";
import usersPermissions from "@strapi/plugin-users-permissions/strapi-admin";
-import component-duplicator from "../../src/plugins/component-duplicator/strapi-admin";
+import componentDuplicator from "../../src/plugins/component-duplicator/strapi-admin";
import { renderAdmin } from "@strapi/strapi/admin";
renderAdmin(document.getElementById("strapi"), {
plugins: {
i18n: i18N,
"users-permissions": usersPermissions,
- component-duplicator: component-duplicator,
+ "component-duplicator": componentDuplicator,
},
});
diff --git a/playground/config/env/ci/plugins.ts b/playground/config/env/ci/plugins.ts
index 5d0c320..b815ade 100644
--- a/playground/config/env/ci/plugins.ts
+++ b/playground/config/env/ci/plugins.ts
@@ -1,7 +1,7 @@
const path = require('path');
export default {
- component-duplicator: {
+ 'component-duplicator': {
enabled: true,
resolve: path.resolve(__dirname, '../../../../node_modules/strapi-plugin-component-duplicator'),
},
diff --git a/playground/config/env/test/plugins.ts b/playground/config/env/test/plugins.ts
index 21fdbbc..c76636e 100644
--- a/playground/config/env/test/plugins.ts
+++ b/playground/config/env/test/plugins.ts
@@ -1,7 +1,7 @@
const path = require('path');
export default {
- component-duplicator: {
+ 'component-duplicator': {
enabled: true,
resolve: path.resolve(__dirname, '../../../../src/plugins/component-duplicator'),
},
diff --git a/playground/src/api/wielen/content-types/wielen/schema.json b/playground/src/api/wielen/content-types/wielen/schema.json
index 2e430f3..5cde699 100644
--- a/playground/src/api/wielen/content-types/wielen/schema.json
+++ b/playground/src/api/wielen/content-types/wielen/schema.json
@@ -4,7 +4,8 @@
"info": {
"singularName": "wielen",
"pluralName": "wielens",
- "displayName": "Wielen"
+ "displayName": "Wielen",
+ "description": ""
},
"options": {
"draftAndPublish": true
@@ -19,6 +20,11 @@
"type": "component",
"repeatable": true,
"component": "variants.variant"
+ },
+ "tralali": {
+ "type": "component",
+ "repeatable": true,
+ "component": "variants.variant"
}
}
}
diff --git a/playground/types/generated/contentTypes.d.ts b/playground/types/generated/contentTypes.d.ts
index f267ce1..f3b8aa7 100644
--- a/playground/types/generated/contentTypes.d.ts
+++ b/playground/types/generated/contentTypes.d.ts
@@ -833,6 +833,7 @@ export interface ApiWielenWielen extends Schema.CollectionType {
singularName: 'wielen';
pluralName: 'wielens';
displayName: 'Wielen';
+ description: '';
};
options: {
draftAndPublish: true;
@@ -840,6 +841,7 @@ export interface ApiWielenWielen extends Schema.CollectionType {
attributes: {
modelnummer: Attribute.String;
Variant: Attribute.Component<'variants.variant', true>;
+ tralali: Attribute.Component<'variants.variant', true>;
createdAt: Attribute.DateTime;
updatedAt: Attribute.DateTime;
publishedAt: Attribute.DateTime;
diff --git a/yarn.lock b/yarn.lock
index 0f82b07..9cb2eea 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2446,11 +2446,6 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.3.2.tgz#fa6a90f2600e052a03c18b8cb3fd83dd4e599898"
integrity sha512-vOBLVQeCQfIcF/2Y7eKFTqrMnizK5lRNQ7ykML/5RuwVXVWxYkgwS7xbt4B6fKCUPgbSL5FSsjHQpaGQP/dQmw==
-"@types/node@^17.0.5":
- version "17.0.45"
- resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.45.tgz#2c0fafd78705e7a18b7906b5201a522719dc5190"
- integrity sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==
-
"@types/parse-json@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
@@ -2492,13 +2487,6 @@
"@types/scheduler" "*"
csstype "^3.0.2"
-"@types/sax@^1.2.1":
- version "1.2.4"
- resolved "https://registry.yarnpkg.com/@types/sax/-/sax-1.2.4.tgz#8221affa7f4f3cb21abd22f244cfabfa63e6a69e"
- integrity sha512-pSAff4IAxJjfAXUG6tFkO7dsSbTmf8CtUpfhhZ5VhkRpC4628tJhh3+V6H1E+/Gs9piSzYKT5yzHO5M4GG9jkw==
- dependencies:
- "@types/node" "*"
-
"@types/scheduler@*":
version "0.16.3"
resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.3.tgz#cef09e3ec9af1d63d2a6cc5b383a737e24e6dcf5"
@@ -2732,11 +2720,6 @@ anymatch@^3.0.3:
normalize-path "^3.0.0"
picomatch "^2.0.4"
-arg@^5.0.0:
- version "5.0.2"
- resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c"
- integrity sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==
-
argparse@^1.0.7:
version "1.0.10"
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
@@ -6279,7 +6262,7 @@ safer-buffer@^2.1.0:
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
-sax@>=0.6.0, sax@^1.2.4:
+sax@>=0.6.0:
version "1.2.4"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
@@ -6355,16 +6338,6 @@ sisteransi@^1.0.5:
resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"
integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==
-component-duplicator@^7.1.0:
- version "7.1.1"
- resolved "https://registry.yarnpkg.com/component-duplicator/-/component-duplicator-7.1.1.tgz#eeed9ad6d95499161a3eadc60f8c6dce4bea2bef"
- integrity sha512-mK3aFtjz4VdJN0igpIJrinf3EO8U8mxOPsTBzSsy06UtjZQJ3YY3o3Xa7zSc5nMqcMrRwlChHZ18Kxg0caiPBg==
- dependencies:
- "@types/node" "^17.0.5"
- "@types/sax" "^1.2.1"
- arg "^5.0.0"
- sax "^1.2.4"
-
slash@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"