├── .github
└── workflows
│ └── npm-publish.yml
├── .gitignore
├── .npmignore
├── README.md
├── admin
└── src
│ ├── components
│ ├── DuplicateButton
│ │ └── index.jsx
│ └── Initializer
│ │ └── index.js
│ ├── index.js
│ ├── pluginId.js
│ ├── prefixPluginTranslations.js
│ └── translations
│ ├── de.json
│ ├── en.json
│ ├── fr.json
│ ├── pl.json
│ └── uk.json
├── doc
├── logo-128.png
├── logo-160.jpg
├── logo.png
└── screen.jpg
├── package-lock.json
├── package.json
└── rollup.config.mjs
/.github/workflows/npm-publish.yml:
--------------------------------------------------------------------------------
1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
2 | # For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages
3 |
4 | name: Node.js Package
5 |
6 | on:
7 | - push
8 |
9 | jobs:
10 | publish-npm:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v3
14 | - uses: actions/setup-node@v3
15 | with:
16 | node-version: 18
17 | registry-url: https://registry.npmjs.org/
18 | - run: npm publish
19 | env:
20 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # dependencies
2 | node_modules
3 |
4 | # misc
5 | .DS_Store
6 |
7 | # debug
8 | npm-debug.log*
9 | yarn-debug.log*
10 | yarn-error.log*
11 |
12 | # build
13 | /admin/dist
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | /admin/source
2 | doc
3 | ./.*
4 | rollup.config.mjs
5 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |

3 |
4 |
5 | # Strapi Plugin Duplicate Button
6 | > Adds a Duplicate Button to the edit view
7 |
8 | 
9 |
10 | ## How to Install
11 |
12 | Copy the following code and run from your terminal
13 |
14 | ```
15 | #yarn
16 | yarn add strapi-plugin-duplicate-button
17 | ```
18 | ```
19 | #pnpm
20 | pnpm add strapi-plugin-duplicate-button
21 | ```
22 | ```
23 | #npm
24 | npm install strapi-plugin-duplicate-button
25 | ```
26 |
27 | The plugin should now be active and show the duplicate button.
28 |
29 | ## How to use
30 | After activation of the Plugin, click the Duplicate Button in the edit view.
31 |
32 | ## Troubleshooting
33 | If the duplicate button does not show up, try adding the following attribute in the `config/plugins.js` file:
34 |
35 | ```
36 | 'duplicate-button': true
37 | ```
38 | Or if you do not have the plugins.js file yet, add the file with the following contents:
39 | ```
40 | module.exports = () => ({
41 | 'duplicate-button': true,
42 | });
43 | ```
44 |
45 | ## Issues
46 |
47 | Please report any issues on [GitHub](https://github.com/lautr/strapi-plugin-duplicate-button/issues/new).
48 |
--------------------------------------------------------------------------------
/admin/src/components/DuplicateButton/index.jsx:
--------------------------------------------------------------------------------
1 | import { LinkButton } from "@strapi/design-system";
2 | import { Duplicate } from "@strapi/icons";
3 | import React from "react";
4 | import { useIntl } from "react-intl";
5 | import { useLocation, useNavigate, useParams } from "react-router-dom";
6 |
7 | const DuplicateButton = () => {
8 | const { id: documentId, slug: modelUID, collectionType } = useParams();
9 | const isSingleType = collectionType === "single-types";
10 |
11 | const { formatMessage } = useIntl();
12 |
13 | const content = {
14 | id: "duplicate-button.components.duplicate.button",
15 | defaultMessage: "Duplicate",
16 | };
17 |
18 | const navigate = useNavigate();
19 | const { pathname, search } = useLocation();
20 | const goToCloneRoute = (evt) => {
21 | evt.preventDefault();
22 | navigate(pathname.replace(modelUID, `${modelUID}/clone`) + search);
23 | };
24 |
25 | if (isSingleType || !documentId || !modelUID) return null;
26 | if (documentId === "create" || documentId === "clone") return null;
27 | return (
28 | <>
29 | {window && window.location ? (
30 | }
40 | onClick={goToCloneRoute}
41 | >
42 | {formatMessage(content)}
43 |
44 | ) : null}
45 | >
46 | );
47 | };
48 |
49 | export default DuplicateButton;
50 |
--------------------------------------------------------------------------------
/admin/src/components/Initializer/index.js:
--------------------------------------------------------------------------------
1 | import PropTypes from "prop-types";
2 | import { useEffect, useRef } from "react";
3 | import pluginId from "../../pluginId";
4 |
5 | const Initializer = ({ setPlugin }) => {
6 | const ref = useRef();
7 | ref.current = setPlugin;
8 |
9 | useEffect(() => {
10 | ref.current(pluginId);
11 | }, []);
12 |
13 | return null;
14 | };
15 |
16 | Initializer.propTypes = {
17 | setPlugin: PropTypes.func.isRequired,
18 | };
19 |
20 | export default Initializer;
21 |
--------------------------------------------------------------------------------
/admin/src/index.js:
--------------------------------------------------------------------------------
1 | import pluginPkg from "../../package.json";
2 | import DuplicateButton from "./components/DuplicateButton";
3 | import Initializer from "./components/Initializer";
4 | import pluginId from "./pluginId";
5 | import prefixPluginTranslations from "./prefixPluginTranslations";
6 |
7 | const name = pluginPkg.strapi.name;
8 |
9 | export default {
10 | register(app) {
11 | app.registerPlugin({
12 | id: pluginId,
13 | initializer: Initializer,
14 | isReady: false,
15 | name,
16 | });
17 | },
18 |
19 | bootstrap(app) {
20 | app
21 | .getPlugin("content-manager")
22 | .injectComponent("editView", "right-links", {
23 | name: pluginId,
24 | Component: DuplicateButton,
25 | });
26 | },
27 | async registerTrads({ locales }) {
28 | const importedTrads = await Promise.all(
29 | locales.map((locale) => {
30 | return import(`./translations/${locale}.json`)
31 | .then(({ default: data }) => {
32 | return {
33 | data: prefixPluginTranslations(data, pluginId),
34 | locale,
35 | };
36 | })
37 | .catch(() => {
38 | return {
39 | data: {},
40 | locale,
41 | };
42 | });
43 | })
44 | );
45 |
46 | return Promise.resolve(importedTrads);
47 | },
48 | };
49 |
--------------------------------------------------------------------------------
/admin/src/pluginId.js:
--------------------------------------------------------------------------------
1 | import pluginPkg from "../../package.json";
2 |
3 | const pluginId = pluginPkg.name.replace(
4 | /^(@[^-,.][\w,-]+\/|strapi-)plugin-/i,
5 | ""
6 | );
7 |
8 | export default pluginId;
9 |
--------------------------------------------------------------------------------
/admin/src/prefixPluginTranslations.js:
--------------------------------------------------------------------------------
1 | const prefixPluginTranslations = (trad, pluginId) => {
2 | if (!pluginId) {
3 | throw new TypeError("pluginId can't be empty");
4 | }
5 |
6 | return Object.keys(trad).reduce((acc, current) => {
7 | acc[`${pluginId}.${current}`] = trad[current];
8 |
9 | return acc;
10 | }, {});
11 | };
12 |
13 | export default prefixPluginTranslations;
14 |
--------------------------------------------------------------------------------
/admin/src/translations/de.json:
--------------------------------------------------------------------------------
1 | {
2 | "components.duplicate.button": "Duplizieren"
3 | }
4 |
--------------------------------------------------------------------------------
/admin/src/translations/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "components.duplicate.button": "Duplicate"
3 | }
4 |
--------------------------------------------------------------------------------
/admin/src/translations/fr.json:
--------------------------------------------------------------------------------
1 | {
2 | "components.duplicate.button": "Dupliquer"
3 | }
4 |
--------------------------------------------------------------------------------
/admin/src/translations/pl.json:
--------------------------------------------------------------------------------
1 | {
2 | "components.duplicate.button": "Duplikuj"
3 | }
4 |
--------------------------------------------------------------------------------
/admin/src/translations/uk.json:
--------------------------------------------------------------------------------
1 | {
2 | "components.duplicate.button": "Дублювати"
3 | }
4 |
--------------------------------------------------------------------------------
/doc/logo-128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lautr/strapi-plugin-duplicate-button/ca38f303a78ecc95a69342e80f7746557d19917b/doc/logo-128.png
--------------------------------------------------------------------------------
/doc/logo-160.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lautr/strapi-plugin-duplicate-button/ca38f303a78ecc95a69342e80f7746557d19917b/doc/logo-160.jpg
--------------------------------------------------------------------------------
/doc/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lautr/strapi-plugin-duplicate-button/ca38f303a78ecc95a69342e80f7746557d19917b/doc/logo.png
--------------------------------------------------------------------------------
/doc/screen.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lautr/strapi-plugin-duplicate-button/ca38f303a78ecc95a69342e80f7746557d19917b/doc/screen.jpg
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "strapi-plugin-duplicate-button",
3 | "version": "2.0.0",
4 | "description": "Adds a duplicate button to the edit view",
5 | "strapi": {
6 | "name": "duplicate-button",
7 | "description": "Adds a duplicate button to the edit view",
8 | "kind": "plugin",
9 | "displayName": "Duplicate Button"
10 | },
11 | "peerDependencies": {
12 | "@strapi/design-system": "^2.0.0-rc.10",
13 | "@strapi/icons": "^2.0.0-rc.10",
14 | "@strapi/strapi": "^5.0.0-rc.17",
15 | "@strapi/utils": "^5.0.0-rc.17",
16 | "react-intl": "6.6.2",
17 | "react-router-dom": "6.22.3"
18 | },
19 | "author": {
20 | "name": "Johannes Lauter"
21 | },
22 | "maintainers": [
23 | {
24 | "name": "Johannes Lauter"
25 | }
26 | ],
27 | "exports": {
28 | "./strapi-admin": {
29 | "source": "./admin/src/index.js",
30 | "import": "./admin/dist/index.mjs",
31 | "require": "./admin/dist/index.js",
32 | "default": "./admin/dist/index.js"
33 | },
34 | "./package.json": "./package.json"
35 | },
36 | "scripts": {
37 | "build": "rollup -c",
38 | "prepublish": "npm run build"
39 | },
40 | "engines": {
41 | "node": ">=18.0.0 <=20.x.x"
42 | },
43 | "license": "MIT",
44 | "devDependencies": {
45 | "@babel/preset-react": "^7.24.7",
46 | "@rollup/plugin-babel": "^6.0.4",
47 | "@rollup/plugin-dynamic-import-vars": "^2.1.2",
48 | "@rollup/plugin-json": "^6.1.0",
49 | "@rollup/plugin-node-resolve": "^15.2.3"
50 | }
51 | }
--------------------------------------------------------------------------------
/rollup.config.mjs:
--------------------------------------------------------------------------------
1 | import babel from "@rollup/plugin-babel";
2 | import dynamicImportVariables from "@rollup/plugin-dynamic-import-vars";
3 | import json from "@rollup/plugin-json";
4 | import { nodeResolve } from "@rollup/plugin-node-resolve";
5 | import path from "path";
6 |
7 | export default {
8 | input: "admin/src/index.js",
9 | output: [
10 | {
11 | file: "admin/dist/index.mjs",
12 | format: "es",
13 | sourcemap: true,
14 | inlineDynamicImports: true,
15 | },
16 | {
17 | file: "admin/dist/index.js",
18 | format: "cjs",
19 | sourcemap: true,
20 | inlineDynamicImports: true,
21 | },
22 | ],
23 | plugins: [
24 | nodeResolve({
25 | extensions: [".js", ".jsx"],
26 | }),
27 | json(),
28 | babel({
29 | presets: ["@babel/preset-react"],
30 | babelHelpers: "bundled",
31 | }),
32 | dynamicImportVariables({}),
33 | ],
34 | external: (id) => {
35 | return id.startsWith(path.resolve("node_modules"));
36 | },
37 | };
38 |
--------------------------------------------------------------------------------