├── .editorconfig ├── .eslintrc.json ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .vscode └── extensions.json ├── README.md ├── e2e └── react-elementor-e2e │ ├── jest.config.js │ ├── tests │ └── react-elementor.spec.ts │ ├── tsconfig.json │ └── tsconfig.spec.json ├── img ├── elementor-widgets.jpg └── storybook.png ├── jest.config.ts ├── jest.preset.js ├── nx.json ├── package-lock.json ├── package.json ├── packages └── react-elementor │ ├── .babelrc │ ├── .eslintrc.json │ ├── README.md │ ├── executors.json │ ├── generators.json │ ├── jest.config.ts │ ├── package.json │ ├── project.json │ ├── src │ ├── executors │ │ └── build │ │ │ ├── executor.ts │ │ │ ├── schema.d.ts │ │ │ └── schema.json │ ├── generators │ │ ├── init │ │ │ ├── init.ts │ │ │ ├── schema.d.ts │ │ │ └── schema.json │ │ ├── plugin │ │ │ ├── app │ │ │ │ ├── __dot__babelrc__template__ │ │ │ │ ├── __dot__browserslistrc__template__ │ │ │ │ ├── class-react-elementor.php__template__ │ │ │ │ ├── class-widgets.php__template__ │ │ │ │ ├── index.html__template__ │ │ │ │ ├── index.php__template__ │ │ │ │ ├── src │ │ │ │ │ ├── app │ │ │ │ │ │ └── __fileName__.ts__template__ │ │ │ │ │ ├── assets │ │ │ │ │ │ ├── __dot__gitkeep │ │ │ │ │ │ ├── app.module.css │ │ │ │ │ │ ├── logo.svg │ │ │ │ │ │ └── star.svg │ │ │ │ │ ├── docker-compose.yml__template__ │ │ │ │ │ ├── environments │ │ │ │ │ │ ├── environment.prod.ts__template__ │ │ │ │ │ │ └── environment.ts__template__ │ │ │ │ │ ├── favicon.ico │ │ │ │ │ ├── index.html__template__ │ │ │ │ │ ├── main.tsx__template__ │ │ │ │ │ └── polyfills.ts__template__ │ │ │ │ ├── tsconfig.app.json__template__ │ │ │ │ ├── tsconfig.json__template__ │ │ │ │ └── wp-react-elementor.php__template__ │ │ │ ├── generator.ts │ │ │ ├── lib │ │ │ │ └── react-component-files.ts │ │ │ ├── libs │ │ │ │ └── ui │ │ │ │ │ ├── index.ts__template__ │ │ │ │ │ └── lib │ │ │ │ │ ├── __fileName__-input__template__ │ │ │ │ │ ├── __fileName__-input.stories.tsx__template__ │ │ │ │ │ ├── __fileName__-input.tsx__template__ │ │ │ │ │ └── __fileName__-input.view.tsx__template__ │ │ │ │ │ ├── __fileName__-title__template__ │ │ │ │ │ ├── __fileName__-title.stories.tsx__template__ │ │ │ │ │ ├── __fileName__-title.tsx__template__ │ │ │ │ │ └── __fileName__-title.view.tsx__template__ │ │ │ │ │ ├── common │ │ │ │ │ ├── index.ts__template__ │ │ │ │ │ ├── types.ts__template__ │ │ │ │ │ └── web-component-wrapper.tsx__template__ │ │ │ │ │ ├── config │ │ │ │ │ └── providers.tsx__template__ │ │ │ │ │ └── store │ │ │ │ │ ├── __fileName__.selector.ts__template__ │ │ │ │ │ ├── __fileName__.store.ts__template__ │ │ │ │ │ └── index.ts__template__ │ │ │ ├── schema.d.ts │ │ │ └── schema.json │ │ └── widget │ │ │ ├── files │ │ │ └── widget.php__template__ │ │ │ ├── generator.ts │ │ │ ├── plugin │ │ │ └── class-widgets.php__template__ │ │ │ ├── schema.d.ts │ │ │ └── schema.json │ ├── img │ │ └── elementor-widgets.jpg │ └── index.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ └── tsconfig.spec.json ├── tools └── tsconfig.tools.json └── tsconfig.base.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "ignorePatterns": ["**/*"], 4 | "plugins": ["@nx"], 5 | "overrides": [ 6 | { 7 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 8 | "rules": { 9 | "@nx/enforce-module-boundaries": [ 10 | "error", 11 | { 12 | "enforceBuildableLibDependency": true, 13 | "allow": [], 14 | "depConstraints": [ 15 | { 16 | "sourceTag": "*", 17 | "onlyDependOnLibsWithTags": ["*"] 18 | } 19 | ] 20 | } 21 | ] 22 | } 23 | }, 24 | { 25 | "files": ["*.ts", "*.tsx"], 26 | "extends": ["plugin:@nx/typescript"], 27 | "rules": {} 28 | }, 29 | { 30 | "files": ["*.js", "*.jsx"], 31 | "extends": ["plugin:@nx/javascript"], 32 | "rules": {} 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.sass-cache 29 | /connect.lock 30 | /coverage 31 | /libpeerconnection.log 32 | npm-debug.log 33 | yarn-error.log 34 | testem.log 35 | /typings 36 | 37 | # System Files 38 | .DS_Store 39 | Thumbs.db 40 | 41 | .nx/cache -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Add files here to ignore them from prettier formatting 2 | 3 | /dist 4 | /coverage 5 | 6 | /.nx/cache -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true 3 | } -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "nrwl.angular-console", 4 | "esbenp.prettier-vscode", 5 | "dbaeumer.vscode-eslint", 6 | "firsttris.vscode-jest-runner" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nx ReactJS elementor widgets 2 | 3 | Nx plugin to generate Wordpress plugin that enrich Elementor with ReactJS widgets. 4 | All widget are wrapped in web component that will act as a proxy between elementor and react. 5 | all web components uses shadow dom to prevent css overload. 6 | 7 | State between component is maintained using Redux. but you can use the provider of your choice. 8 | ![image](https://raw.githubusercontent.com/betrueagency/nx-reactjs-elementor/main/img/elementor-widgets.jpg) 9 | 10 | 11 | ## Plugins 12 | 13 | | Plugin | Description | 14 | | ------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- | 15 | | [`@betrue/react-elementor`](https://github.com/betrueagency/nx-reactjs-elementor/tree/main/e2e/react-elementor-e2e) | Generate Reactjs Elementor widgets in Wordpress plugin; | 16 | 17 | ## Install 18 | 19 | Create a new nx workspace (if doesn't exist) 20 | 21 | npx create-nx-workspace@latest my-workspace 22 | 23 | > NX Let's create a new workspace [https://nx.dev/getting-started/intro] 24 | 25 | ✔ Choose your style · integrated 26 | ✔ What to create in the new workspace · apps 27 | ✔ Enable distributed caching to make your CI faster · No 28 | 29 | 30 | 31 | Install [`@betrue/react-elementor`](https://www.npmjs.com/package/@betrue/react-elementor) 32 | 33 | cd my-workspace 34 | npm install -D @betrue/react-elementor 35 | 36 | 37 | ## Usage 38 | 39 | Create a new plugin 40 | 41 | npx nx g @betrue/react-elementor:plugin my-project 42 | 43 | > NX Generating @betrue/react-elementor:application 44 | 45 | ✔ Which stylesheet format would you like to use? · styled-components 46 | 47 | .... 48 | 49 | npm install 50 | 51 | this generates starting code base made up of two react components (input from and display title) wrapped into elementor widgets. 52 | 53 | you will find the sources of the Wordpress apps directory, and elementor/react widgets in libs/ui. 54 | 55 | Each new widget has. 56 | * React component (my-widget.tsx) 57 | * React component view (my-widget.view.tsx) 58 | * Stroybook stories file (my-widget.stories.tsx) 59 | * MUST be declared in apps/my-project/src/app/my-projet.ts in order to be wrapped in a web component 60 | * MUST have generated elementor widget [@betrue/react-elementor:addWidget](#add-a-new-widget-to-an-existing-plugin) 61 | 62 | if you already have and Wordpress instance with elementor installed, you juste need to build the wordpress plugin 63 | 64 | npx nx pkg my-project 65 | 66 | Zip and upload using Wordpress plugin management the content of `dist/element/my-project`. that's all you can now try to use theses widgets into elementor :) 67 | 68 | For development purpose, You can use storybook to live edit and test your react component [`https://localhost:4400`](http://localhost:4400) 69 | 70 | NODE_OPTIONS=--openssl-legacy-provider npx nx run my-project-ui:storybook 71 | 72 | ![image](https://raw.githubusercontent.com/betrueagency/nx-reactjs-elementor/main/img/storybook.png) 73 | 74 | On build is important to pass the release version to make force resources update and reset cache 75 | 76 | NX_RELEASE_VERSION=xxxx npx nx pkg my-project 77 | 78 | you will find the output in dist/elementor/my-project, all you have to do is zip it and install it like any wordpress plugin 79 | 80 | cd dist/elementor/ 81 | zip -r my-project.zip my-project/ 82 | 83 | #### !!! Warning for MacOS users: do not use the right click to zip the folder, since WordPress 6.4.3 it generates an Incompatible Archive Error. 84 | 85 | ## Try it using docker 86 | 87 | If you have already installed docker and docker-compose you can try the elementor plugin in wordpress 88 | 89 | npx nx pkg my-project ` 90 | 91 | start docker-compose 92 | 93 | docker-compose -f apps/my-project/src/docker-compose.yml up -d 94 | 95 | * open your browser on [`http://localhost:8000`](http://localhost:8000) 96 | * Login into Wordpress admin and [`install and enable elementor `](http://localhost:8000/wp-admin/plugins.php?s=elementor&plugin_status=all) 97 | * Enable your custom [`elementor plugin`](http://localhost:8000/wp-admin/plugins.php) plugin. 98 | * Create a [`new page`](http://localhost:8000/wp-admin/post-new.php?post_type=page) and choose edit with elementor. 99 | * Search and add `my-project-title` and `my-project-input` widgets to your page. 100 | 101 | ### Add a new widget to an existing plugin: 102 | 103 | nx g @betrue/react-elementor:addWidget --name my-widget --plugin my-plugin --attributes attr1,attr2 104 | 105 | | Option | Description | 106 | | ------------------------------- | ------------------------------------------------------- | 107 | | `name` | (Required) name of the Reactjs elementor widget | 108 | | `plugin` | (Required) The name of the Wordpress plugin in which the widget will be generated. | 109 | | `attributes` | List of attribute that are customizable in elementor | 110 | | `author` | Name of who makes this plugin. | 111 | | `tags` | Add tags to the project (used for linting). | 112 | | `widgetDescription` | Widget description that appear in Wordpress plugin view. | 113 | | `version` | Wordpress plugin version. | 114 | 115 | ## Maintainer 116 | 117 | - [https://www.betrue.fr/](https://www.betrue.fr/) 118 | -------------------------------------------------------------------------------- /e2e/react-elementor-e2e/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | displayName: 'react-elementor-e2e', 3 | preset: '../../jest.preset.js', 4 | globals: {}, 5 | transform: { 6 | '^.+\\.[tj]s$': [ 7 | 'ts-jest', 8 | { 9 | tsconfig: '/tsconfig.spec.json', 10 | }, 11 | ], 12 | }, 13 | moduleFileExtensions: ['ts', 'js', 'html'], 14 | coverageDirectory: '../../coverage/e2e/react-elementor-e2e', 15 | }; 16 | -------------------------------------------------------------------------------- /e2e/react-elementor-e2e/tests/react-elementor.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | checkFilesExist, 3 | ensureNxProject, 4 | readFile, 5 | runNxCommandAsync, 6 | uniq, 7 | } from '@nx/plugin/testing'; 8 | 9 | describe('react-elementor:plugin e2e', () => { 10 | it('should create react-elementor', async () => { 11 | const plugin = uniq('react-elementor'); 12 | ensureNxProject('@betrue/react-elementor', 'dist/packages/react-elementor'); 13 | await runNxCommandAsync( 14 | `generate @betrue/react-elementor:plugin ${plugin}` 15 | ); 16 | 17 | await runNxCommandAsync(`pkg ${plugin}`); 18 | expect(() => 19 | checkFilesExist( 20 | `apps/${plugin}/src/index.html`, 21 | `dist/elementor/${plugin}/dist/main.js` 22 | ) 23 | ).not.toThrow(); 24 | }, 120000); 25 | 26 | /**describe('--tags', () => { 27 | it('should add tags to nx.json', async () => { 28 | const plugin = uniq('react-elementor'); 29 | ensureNxProject( 30 | '@betrue/react-elementor', 31 | 'dist/packages/react-elementor' 32 | ); 33 | await runNxCommandAsync( 34 | `generate @betrue/react-elementor:plugin ${plugin} --tags e2etag,e2ePackage` 35 | ); 36 | const nxJson = readJson('nx.json'); 37 | expect(nxJson.projects[plugin].tags).toEqual(['e2etag', 'e2ePackage']); 38 | }, 120000); 39 | });**/ 40 | 41 | describe('NX_RELEASE_VERSION', () => { 42 | it('should update version xxVERSIONxx', async () => { 43 | const plugin = uniq('react-elementor'); 44 | ensureNxProject( 45 | '@betrue/react-elementor', 46 | 'dist/packages/react-elementor' 47 | ); 48 | await runNxCommandAsync( 49 | `generate @betrue/react-elementor:plugin ${plugin} ` 50 | ); 51 | await runNxCommandAsync(`build ${plugin}`); 52 | await runNxCommandAsync(`plugin ${plugin} `); 53 | const file = readFile(`dist/elementor/${plugin}/class-widgets.php`); 54 | expect(file.includes('noVersion')).toEqual(true); 55 | }, 120000); 56 | }); 57 | 58 | describe('react-elementor:widget', () => { 59 | it('should create widget in the specified plugin', async () => { 60 | const plugin = uniq('react-elementor'); 61 | const widget1 = uniq('elementor-widget'); 62 | const widget2 = uniq('elementor-widget'); 63 | 64 | ensureNxProject( 65 | '@betrue/react-elementor', 66 | 'dist/packages/react-elementor' 67 | ); 68 | 69 | await runNxCommandAsync( 70 | `generate @betrue/react-elementor:plugin ${plugin}` 71 | ); 72 | 73 | await runNxCommandAsync( 74 | `generate @betrue/react-elementor:addWidget --name ${widget1} --plugin ${plugin} --attributes attrs1,attrs2` 75 | ); 76 | 77 | await runNxCommandAsync( 78 | `generate @betrue/react-elementor:addWidget --name ${widget2} --plugin ${plugin} --attributes attrs1,attrs2` 79 | ); 80 | 81 | expect(() => 82 | checkFilesExist( 83 | `apps/${plugin}/widgets/${widget2}/widget.php`, 84 | `apps/${plugin}/widgets/${widget1}/widget.php` 85 | ) 86 | ).not.toThrow(); 87 | }, 120000); 88 | }); 89 | }); 90 | -------------------------------------------------------------------------------- /e2e/react-elementor-e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.e2e.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /e2e/react-elementor-e2e/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": ["**/*.spec.ts", "**/*.d.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /img/elementor-widgets.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/betrueagency/nx-reactjs-elementor/714d345e5fac5cf5ab199abe52fdc3441558424c/img/elementor-widgets.jpg -------------------------------------------------------------------------------- /img/storybook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/betrueagency/nx-reactjs-elementor/714d345e5fac5cf5ab199abe52fdc3441558424c/img/storybook.png -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | const { getJestProjects } = require('@nx/jest'); 2 | 3 | export default { 4 | projects: [...getJestProjects(), '/e2e/react-elementor-e2e'], 5 | }; 6 | -------------------------------------------------------------------------------- /jest.preset.js: -------------------------------------------------------------------------------- 1 | const nxPreset = require('@nx/jest/preset').default; 2 | 3 | module.exports = { 4 | ...nxPreset, 5 | /* TODO: Update to latest Jest snapshotFormat 6 | * By default Nx has kept the older style of Jest Snapshot formats 7 | * to prevent breaking of any existing tests with snapshots. 8 | * It's recommend you update to the latest format. 9 | * You can do this by removing snapshotFormat property 10 | * and running tests with --update-snapshot flag. 11 | * Example: "nx affected --targets=e2e --update-snapshot" 12 | * More info: https://jestjs.io/docs/upgrading-to-jest29#snapshot-format 13 | */ 14 | snapshotFormat: { escapeString: true, printBasicPrototype: true }, 15 | }; 16 | -------------------------------------------------------------------------------- /nx.json: -------------------------------------------------------------------------------- 1 | { 2 | "affected": { 3 | "defaultBase": "master" 4 | }, 5 | "targetDependencies": { 6 | "build": [ 7 | { 8 | "target": "build", 9 | "projects": "dependencies" 10 | } 11 | ] 12 | }, 13 | "workspaceLayout": { 14 | "appsDir": "e2e", 15 | "libsDir": "packages" 16 | }, 17 | "namedInputs": { 18 | "default": ["{projectRoot}/**/*", "sharedGlobals"], 19 | "sharedGlobals": [], 20 | "production": [ 21 | "default", 22 | "!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)", 23 | "!{projectRoot}/tsconfig.spec.json", 24 | "!{projectRoot}/jest.config.[jt]s", 25 | "!{projectRoot}/.eslintrc.json", 26 | "!{projectRoot}/src/test-setup.[jt]s" 27 | ] 28 | }, 29 | "targetDefaults": { 30 | "build": { 31 | "inputs": ["production", "^production"], 32 | "cache": true 33 | }, 34 | "@nx/eslint:lint": { 35 | "inputs": ["default", "{workspaceRoot}/.eslintrc.json"], 36 | "cache": true 37 | }, 38 | "@nx/jest:jest": { 39 | "cache": true, 40 | "inputs": ["default", "^production", "{workspaceRoot}/jest.preset.js"], 41 | "options": { 42 | "passWithNoTests": true 43 | }, 44 | "configurations": { 45 | "ci": { 46 | "ci": true, 47 | "codeCoverage": true 48 | } 49 | } 50 | } 51 | }, 52 | "parallel": 1 53 | } 54 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "betrue", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "nx": "nx", 7 | "start": "nx serve", 8 | "build": "nx build", 9 | "test": "nx test", 10 | "lint": "nx workspace-lint && nx lint", 11 | "e2e": "nx e2e", 12 | "affected:apps": "nx affected:apps", 13 | "affected:libs": "nx affected:libs", 14 | "affected:build": "nx affected:build", 15 | "affected:e2e": "nx affected:e2e", 16 | "affected:test": "nx affected:test", 17 | "affected:lint": "nx affected:lint", 18 | "affected:dep-graph": "nx affected:dep-graph", 19 | "affected": "nx affected", 20 | "format": "nx format:write", 21 | "format:write": "nx format:write", 22 | "format:check": "nx format:check", 23 | "update": "nx migrate latest", 24 | "workspace-generator": "nx workspace-generator", 25 | "dep-graph": "nx dep-graph", 26 | "help": "nx help" 27 | }, 28 | "private": true, 29 | "dependencies": { 30 | "@nx/devkit": "*", 31 | "@nx/workspace": "*", 32 | "fs-extra": "10.1.0", 33 | "nx": "18.0.1", 34 | "tslib": "^2.0.0", 35 | "@nx/web": "*", 36 | "@nx/react": "18.0.1" 37 | }, 38 | "devDependencies": { 39 | "@nrwl/js": "18.0.1", 40 | "@nx/devkit": "18.0.1", 41 | "@nx/eslint": "18.0.1", 42 | "@nx/eslint-plugin": "18.0.1", 43 | "@nx/jest": "18.0.1", 44 | "@nx/js": "18.0.1", 45 | "@nx/plugin": "18.0.1", 46 | "@nx/webpack": "18.0.1", 47 | "@nx/workspace": "18.0.1", 48 | "@swc-node/register": "1.6.8", 49 | "@swc/core": "1.3.107", 50 | "@types/jest": "29.4.4", 51 | "@types/node": "18.19.14", 52 | "@typescript-eslint/eslint-plugin": "6.20.0", 53 | "@typescript-eslint/parser": "6.20.0", 54 | "dotenv": "10.0.0", 55 | "eslint": "8.48.0", 56 | "eslint-config-prettier": "9.1.0", 57 | "jest": "29.4.3", 58 | "jest-environment-jsdom": "29.4.3", 59 | "prettier": "2.7.1", 60 | "ts-jest": "29.1.2", 61 | "ts-node": "10.9.1", 62 | "tslib": "^2.0.0", 63 | "typescript": "5.3.3", 64 | "@nx/web": "18.0.1", 65 | "@nx/node": "18.0.1" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /packages/react-elementor/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@nrwl/js/babel", 5 | { 6 | "useBuiltIns": "usage" 7 | } 8 | ] 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /packages/react-elementor/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["*.ts", "*.tsx"], 11 | "rules": {} 12 | }, 13 | { 14 | "files": ["*.js", "*.jsx"], 15 | "rules": {} 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /packages/react-elementor/README.md: -------------------------------------------------------------------------------- 1 | # Nx ReactJS elementor widgets 2 | 3 | Nx plugin to generate Wordpress plugin that enrich Elementor with ReactJS widgets. 4 | All widget are wrapped in web component that will act as a proxy between elementor and react. 5 | all web components uses shadow dom to prevent css overload. 6 | 7 | State between component is maintained using Redux. but you can use the provider of your choice. 8 | ![image](https://raw.githubusercontent.com/betrueagency/nx-reactjs-elementor/main/img/elementor-widgets.jpg) 9 | 10 | 11 | ## Plugins 12 | 13 | | Plugin | Description | 14 | | ------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- | 15 | | [`@betrue/react-elementor`](https://github.com/betrueagency/nx-reactjs-elementor/tree/main/e2e/react-elementor-e2e) | Generate Reactjs Elementor widgets in Wordpress plugin; | 16 | 17 | ## Install 18 | 19 | Create a new nx workspace (if doesn't exist) 20 | 21 | npx create-nx-workspace@latest my-workspace 22 | 23 | > NX Let's create a new workspace [https://nx.dev/getting-started/intro] 24 | 25 | ✔ Choose your style · integrated 26 | ✔ What to create in the new workspace · apps 27 | ✔ Enable distributed caching to make your CI faster · No 28 | 29 | 30 | 31 | Install [`@betrue/react-elementor`](https://www.npmjs.com/package/@betrue/react-elementor) 32 | 33 | cd my-workspace 34 | npm install -D @betrue/react-elementor 35 | 36 | 37 | ## Usage 38 | 39 | Create a new plugin 40 | 41 | npx nx g @betrue/react-elementor:plugin my-project 42 | 43 | > NX Generating @betrue/react-elementor:application 44 | 45 | ✔ Which stylesheet formationi would you like to use? · styled-components 46 | 47 | .... 48 | 49 | npm install 50 | 51 | this generates starting code base made up of two react components (input from and display title) wrapped into elementor widgets. 52 | 53 | you will find the sources of the Wordpress apps directory, and elementor/react widgets in libs/ui. 54 | 55 | Each new widget has. 56 | * React component (my-widget.tsx) 57 | * React component view (my-widget.view.tsx) 58 | * Stroybook stories file (my-widget.stories.tsx) 59 | * MUST be declared in apps/my-project/src/app/my-projet.ts in order to be wrapped in a web component 60 | * MUST have generated elementor widget [@betrue/react-elementor:addWidget](#add-a-new-widget-to-an-existing-plugin) 61 | 62 | if you already have and Wordpress instance with elementor installed, you juste need to build the wordpress plugin 63 | 64 | npx nx pkg my-project 65 | 66 | Zip and upload using Wordpress plugin management the content of `dist/element/my-project`. that's all you can now try to use theses widgets into elementor :) 67 | 68 | For development purpose, You can use storybook to live edit and test your react component [`https://localhost:4400`](http://localhost:4400) 69 | 70 | npx nx run my-project-ui:storybook 71 | 72 | ![image](https://raw.githubusercontent.com/betrueagency/nx-reactjs-elementor/main/img/storybook.png) 73 | 74 | On build is important to pass the release version to make force resources update and reset cache 75 | 76 | NX_RELEASE_VERSION=xxxx npx nx pkg my-project 77 | 78 | you will find the output in dist/elementor/my-project, all you have to do is zip it and install it like any wordpress plugin 79 | 80 | cd dist/elementor/ 81 | zip -r my-project.zip my-project/ 82 | 83 | #### !!! Warning for MacOS users: do not use the right click to zip the folder, since WordPress 6.4.3 it generates an Incompatible Archive Error. 84 | 85 | ## Try it using docker 86 | 87 | If you have already installed docker and docker-compose you can try the elementor plugin in wordpress 88 | 89 | npx nx pkg my-project ` 90 | 91 | start docker-compose 92 | 93 | docker-compose -f apps/my-project/src/docker-compose.yml up -d 94 | 95 | * open your browser on [`http://localhost:8000`](http://localhost:8000) 96 | * Login into Wordpress admin and [`install and enable elementor `](http://localhost:8000/wp-admin/plugins.php?s=elementor&plugin_status=all) 97 | * Enable your custom [`elementor plugin`](http://localhost:8000/wp-admin/plugins.php) plugin. 98 | * Create a [`new page`](http://localhost:8000/wp-admin/post-new.php?post_type=page) and choose edit with elementor. 99 | * Search and add `my-project-title` and `my-project-input` widgets to your page. 100 | 101 | ### Add a new widget to an existing plugin: 102 | 103 | nx g @betrue/react-elementor:addWidget --name my-widget --plugin my-plugin --attributes attr1,attr2 104 | 105 | | Option | Description | 106 | | ------------------------------- | ------------------------------------------------------- | 107 | | `name` | (Required) name of the Reactjs elementor widget | 108 | | `plugin` | (Required) The name of the Wordpress plugin in which the widget will be generated. | 109 | | `attributes` | List of attribute that are customizable in elementor | 110 | | `author` | Name of who makes this plugin. | 111 | | `tags` | Add tags to the project (used for linting). | 112 | | `widgetDescription` | Widget description that appear in Wordpress plugin view. | 113 | | `version` | Wordpress plugin version. | 114 | 115 | ## Maintainer 116 | 117 | - [https://www.betrue.fr/](https://www.betrue.fr/) 118 | -------------------------------------------------------------------------------- /packages/react-elementor/executors.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/schema", 3 | "executors": { 4 | "build": { 5 | "implementation": "./src/executors/build/executor", 6 | "schema": "./src/executors/build/schema.json", 7 | "description": "build executor" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/react-elementor/generators.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-elementor", 3 | "version": "0.1", 4 | "extends": ["@nx/react"], 5 | "schematics": { 6 | "init": { 7 | "factory": "./src/generators/init/init#elementorInitSchematic", 8 | "schema": "./src/generators/init/schema.json", 9 | "description": "Initialize the @betrue/react-elementor plugin", 10 | "hidden": true 11 | }, 12 | "application": { 13 | "factory": "./src/generators/plugin/generator#pluginSchematic", 14 | "schema": "./src/generators/plugin/schema.json", 15 | "aliases": ["plugin"], 16 | "x-type": "application", 17 | "description": "Create a react elementor application" 18 | } 19 | }, 20 | "generators": { 21 | "init": { 22 | "factory": "./src/generators/init/init#elementorInitGenerator", 23 | "schema": "./src/generators/init/schema.json", 24 | "description": "Initialize the @nrwl/next plugin", 25 | "hidden": true 26 | }, 27 | "application": { 28 | "factory": "./src/generators/plugin/generator#pluginGenerator", 29 | "schema": "./src/generators/plugin/schema.json", 30 | "aliases": ["plugin"], 31 | "description": "Initialize @betrue/react-elementor Wordpress plugin" 32 | }, 33 | "addWidget": { 34 | "factory": "./src/generators/widget/generator", 35 | "schema": "./src/generators/widget/schema.json", 36 | "description": "Add widget to an existing plugin" 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/react-elementor/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: 'react-elementor', 4 | preset: '../../jest.preset.js', 5 | globals: {}, 6 | testEnvironment: 'node', 7 | transform: { 8 | '^.+\\.[tj]sx?$': [ 9 | 'ts-jest', 10 | { 11 | tsconfig: '/tsconfig.spec.json', 12 | }, 13 | ], 14 | }, 15 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], 16 | coverageDirectory: '../../coverage/packages/react-elementor', 17 | }; 18 | -------------------------------------------------------------------------------- /packages/react-elementor/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@betrue/react-elementor", 3 | "version": "1.2.5", 4 | "homepage": "https://github.com/betrueagency/nx-reactjs-elementor", 5 | "main": "src/index.js", 6 | "generators": "./generators.json", 7 | "executors": "./executors.json", 8 | "peerDependencies": { 9 | "fs-extra": "10.1.0", 10 | "@nx/webpack": "^18.0.0 || ^17.0.0 || ^16.0.0 || ^15.0.0 || ^12.0.0 || ^13.0.0 || ^14.0.0", 11 | "@nx/react": "^18.0.0 || ^17.0.0 || ^16.0.0 || ^15.0.0 || ^12.0.0 || ^13.0.0 || ^14.0.0" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/react-elementor/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-elementor", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "packages/react-elementor/src", 5 | "projectType": "application", 6 | "targets": { 7 | "lint": { 8 | "executor": "@nx/eslint:lint", 9 | "outputs": ["{options.outputFile}"] 10 | }, 11 | "test": { 12 | "executor": "@nx/jest:jest", 13 | "outputs": ["{workspaceRoot}/coverage/packages/react-elementor"], 14 | "options": { 15 | "jestConfig": "packages/react-elementor/jest.config.ts" 16 | } 17 | }, 18 | "build": { 19 | "executor": "@nx/js:tsc", 20 | "outputs": ["{options.outputPath}"], 21 | "options": { 22 | "outputPath": "dist/packages/react-elementor", 23 | "tsConfig": "packages/react-elementor/tsconfig.lib.json", 24 | "packageJson": "packages/react-elementor/package.json", 25 | "main": "packages/react-elementor/src/index.ts", 26 | "assets": [ 27 | "packages/react-elementor/*.md", 28 | { 29 | "input": "./packages/react-elementor/src", 30 | "glob": "**/!(*.ts)", 31 | "output": "./src" 32 | }, 33 | { 34 | "input": "./packages/react-elementor/src", 35 | "glob": "**/*.d.ts", 36 | "output": "./src" 37 | }, 38 | { 39 | "input": "./packages/react-elementor", 40 | "glob": "generators.json", 41 | "output": "." 42 | }, 43 | { 44 | "input": "./packages/react-elementor", 45 | "glob": "executors.json", 46 | "output": "." 47 | } 48 | ] 49 | } 50 | } 51 | }, 52 | "tags": [] 53 | } 54 | -------------------------------------------------------------------------------- /packages/react-elementor/src/executors/build/executor.ts: -------------------------------------------------------------------------------- 1 | import { BuildExecutorSchema } from './schema'; 2 | import { ExecutorContext } from '@nx/devkit'; 3 | import * as fs from 'fs'; 4 | import { readdirSync, renameSync } from 'fs'; 5 | import { join } from 'path'; 6 | import { copySync } from 'fs-extra'; 7 | 8 | const updateVersion = ( 9 | version: string, 10 | files: string[] 11 | ): Promise => { 12 | return Promise.all( 13 | files.map((file) => { 14 | return new Promise((resolve, reject) => { 15 | fs.readFile(file, 'utf8', function (err, data) { 16 | if (err) { 17 | reject(err); 18 | } 19 | const result = data 20 | .replace(/xxVERSIONxx/g, version) 21 | .replace(/noVersion/g, version); 22 | 23 | fs.writeFile(file, result, 'utf8', function (writeErr) { 24 | if (writeErr) { 25 | reject(writeErr); 26 | } else resolve(true); 27 | }); 28 | }); 29 | }); 30 | }) 31 | ); 32 | }; 33 | 34 | export default async function runExecutor( 35 | options: BuildExecutorSchema, 36 | context: ExecutorContext 37 | ) { 38 | await copySync( 39 | `${context.root}/${options.plugin}`, 40 | `${context.root}/dist/elementor/${options.plugin}` 41 | ); 42 | 43 | await copySync( 44 | `${context.root}/dist/${options.plugin}`, 45 | `${context.root}/dist/elementor/${options.plugin}/dist` 46 | ); 47 | const matchMain = RegExp('main\\.[^\\.]+\\.js', 'g'); 48 | const matchRuntime = RegExp('runtime\\.[^\\.]+\\.js', 'g'); 49 | 50 | const files = readdirSync( 51 | `${context.root}/dist/elementor/${options.plugin}/dist` 52 | ); 53 | 54 | files 55 | .filter((file) => file.match(matchMain)) 56 | .forEach((file) => { 57 | console.log('file',file) 58 | const filePath = join( 59 | `${context.root}/dist/elementor/${options.plugin}/dist`, 60 | file 61 | ); 62 | 63 | const newFilePath = join( 64 | `${context.root}/dist/elementor/${options.plugin}/dist`, 65 | file.replace(matchMain, 'main.js') 66 | ); 67 | console.log('newFilePath',newFilePath) 68 | renameSync(filePath, newFilePath); 69 | }); 70 | 71 | files 72 | .filter((file) => file.match(matchRuntime)) 73 | .forEach((file) => { 74 | console.log('file',file) 75 | const filePath = join( 76 | `${context.root}/dist/elementor/${options.plugin}/dist`, 77 | file 78 | ); 79 | 80 | const newFilePath = join( 81 | `${context.root}/dist/elementor/${options.plugin}/dist`, 82 | file.replace(matchRuntime, 'runtime.js') 83 | ); 84 | renameSync(filePath, newFilePath); 85 | }); 86 | 87 | if (process.env.NX_RELEASE_VERSION) { 88 | console.log('update version to : ', process.env.NX_RELEASE_VERSION); 89 | await updateVersion(process.env.NX_RELEASE_VERSION, [ 90 | `${context.root}/dist/elementor/${options.plugin}/class-react-elementor.php`, 91 | `${context.root}/dist/elementor/${options.plugin}/class-widgets.php`, 92 | ]); 93 | } else { 94 | await updateVersion('noVersion', [ 95 | `${context.root}/dist/elementor/${options.plugin}/class-react-elementor.php`, 96 | `${context.root}/dist/elementor/${options.plugin}/class-widgets.php`, 97 | ]); 98 | } 99 | return { 100 | success: true, 101 | }; 102 | } 103 | -------------------------------------------------------------------------------- /packages/react-elementor/src/executors/build/schema.d.ts: -------------------------------------------------------------------------------- 1 | export interface BuildExecutorSchema { 2 | plugin: string; 3 | replaceFilePattern:string; 4 | } // eslint-disable-line 5 | -------------------------------------------------------------------------------- /packages/react-elementor/src/executors/build/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "outputCapture": "direct-nodejs", 4 | "$schema": "http://json-schema.org/schema", 5 | "title": "Build executor", 6 | "description": "", 7 | "type": "object", 8 | "properties": {}, 9 | "required": [] 10 | } 11 | -------------------------------------------------------------------------------- /packages/react-elementor/src/generators/init/init.ts: -------------------------------------------------------------------------------- 1 | import { 2 | addDependenciesToPackageJson, 3 | convertNxGenerator, 4 | GeneratorCallback, runTasksInSerial, 5 | Tree, 6 | updateJson, 7 | } from '@nx/devkit'; 8 | import { reactDomVersion, reactInitGenerator, reactVersion } from '@nx/react'; 9 | 10 | import { InitSchema } from './schema'; 11 | 12 | function updateDependencies(host: Tree) { 13 | updateJson(host, 'package.json', (json) => { 14 | return json; 15 | }); 16 | 17 | return addDependenciesToPackageJson( 18 | host, 19 | { 20 | react: reactVersion, 21 | redux: '^4.2.0', 22 | 'react-redux': '^7.2.5', 23 | '@reduxjs/toolkit': '^1.6.1', 24 | '@emotion/react': '^11.9.3', 25 | '@emotion/styled': '^11.9.3', 26 | '@mui/material': '^5.9.1', 27 | 'react-to-webcomponent': '^1.7.2', 28 | 'prop-types': '15.8.1', 29 | 'react-dom': reactDomVersion, 30 | }, 31 | { 32 | '@betrue/react-elementor': 'latest', 33 | 'babel-plugin-styled-components': '^2.0.7', 34 | } 35 | ); 36 | } 37 | 38 | export async function elementorInitGenerator(host: Tree, schema: InitSchema) { 39 | const tasks: GeneratorCallback[] = []; 40 | 41 | const reactTask = await reactInitGenerator(host, schema); 42 | tasks.push(reactTask); 43 | const installTask = updateDependencies(host); 44 | tasks.push(installTask); 45 | 46 | return runTasksInSerial(...tasks); 47 | } 48 | 49 | export default elementorInitGenerator; 50 | export const elementorInitSchematic = convertNxGenerator( 51 | elementorInitGenerator 52 | ); 53 | -------------------------------------------------------------------------------- /packages/react-elementor/src/generators/init/schema.d.ts: -------------------------------------------------------------------------------- 1 | export interface InitSchema { 2 | unitTestRunner?: 'jest' | 'none'; 3 | e2eTestRunner?: 'cypress' | 'none'; 4 | skipFormat?: boolean; 5 | } 6 | -------------------------------------------------------------------------------- /packages/react-elementor/src/generators/init/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/schema", 3 | "$id": "NxElementorNgInit", 4 | "title": "Init Elementor Plugin", 5 | "type": "object", 6 | "properties": { 7 | "unitTestRunner": { 8 | "description": "Adds the specified unit test runner", 9 | "type": "string", 10 | "enum": [ 11 | "jest", 12 | "none" 13 | ], 14 | "default": "jest" 15 | }, 16 | "e2eTestRunner": { 17 | "description": "Adds the specified e2e test runner", 18 | "type": "string", 19 | "enum": [ 20 | "cypress", 21 | "none" 22 | ], 23 | "default": "cypress" 24 | }, 25 | "skipFormat": { 26 | "description": "Skip formatting files", 27 | "type": "boolean", 28 | "default": false 29 | } 30 | }, 31 | "required": [] 32 | } 33 | -------------------------------------------------------------------------------- /packages/react-elementor/src/generators/plugin/app/__dot__babelrc__template__: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@nx/react/babel", { 5 | "runtime": "automatic"<% if (style === '@emotion/styled') { %>,<% } %> 6 | <% if (style === '@emotion/styled') { %>"importSource": "@emotion/react" <% } %> 7 | } 8 | ] 9 | ], 10 | "plugins": [ 11 | <% if (style === 'styled-components') { %>["styled-components", { "pure": true, "ssr": true }]<% } %><% if (style === 'styled-jsx') { %>"styled-jsx/babel"<% } %><% if (style === '@emotion/styled') { %>"@emotion/babel-plugin"<% } %> 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /packages/react-elementor/src/generators/plugin/app/__dot__browserslistrc__template__: -------------------------------------------------------------------------------- 1 | # This file is used by: 2 | # 1. autoprefixer to adjust CSS to support the below specified browsers 3 | # 2. babel preset-env to adjust included polyfills 4 | # 5 | # For additional information regarding the format and rule options, please see: 6 | # https://github.com/browserslist/browserslist#queries 7 | # 8 | # If you need to support different browsers in production, you may tweak the list below. 9 | 10 | last 1 Chrome version 11 | last 1 Firefox version 12 | last 2 Edge major versions 13 | last 2 Safari major version 14 | last 2 iOS major versions 15 | Firefox ESR 16 | not IE 9-11 # For IE 9-11 support, remove 'not'. 17 | -------------------------------------------------------------------------------- /packages/react-elementor/src/generators/plugin/app/class-react-elementor.php__template__: -------------------------------------------------------------------------------- 1 | class. 4 | * 5 | * @category Class 6 | * @package <%= className %> 7 | * @subpackage WordPress 8 | * @author <%= author %> 9 | * @copyright <%= copyright %> 10 | * @license <%= license %> 11 | * @link link( <%= link %>) 12 | * @since 1.0.0 13 | * Text Domain: <%= fileName %> 14 | * php version 7.3.9 15 | */ 16 | if ( ! defined( 'ABSPATH' ) ) { 17 | // Exit if accessed directly. 18 | exit; 19 | } 20 | 21 | /** 22 | * Main Elementor <%= name %> Class 23 | * 24 | * The init class that runs the Elementor <%= name %> plugin. 25 | * Intended To make sure that the plugin's minimum requirements are met. 26 | * 27 | * You should only modify the constants to match your plugin's needs. 28 | * 29 | * Any custom code should go inside Plugin Class in the plugin.php file. 30 | */ 31 | final class <%= constantName %> { 32 | 33 | /** 34 | * Plugin Version 35 | * 36 | * @since 1.0.0 37 | * @var string The plugin version. 38 | */ 39 | const VERSION = 'xxVERSIONxx'; 40 | 41 | /** 42 | * Minimum Elementor Version 43 | * 44 | * @since 1.0.0 45 | * @var string Minimum Elementor version required to run the plugin. 46 | */ 47 | const MINIMUM_ELEMENTOR_VERSION = '<%= minElementorVersion %>'; 48 | 49 | /** 50 | * Minimum PHP Version 51 | * 52 | * @since 1.0.0 53 | * @var string Minimum PHP version required to run the plugin. 54 | */ 55 | const MINIMUM_PHP_VERSION = '7'; 56 | 57 | /** 58 | * Constructor 59 | * 60 | * @since 1.0.0 61 | * @access public 62 | */ 63 | public function __construct() { 64 | // Load the translation. 65 | add_action( 'init', array( $this, 'i18n' ) ); 66 | 67 | // Initialize the plugin. 68 | add_action( 'plugins_loaded', array( $this, 'init' ) ); 69 | } 70 | 71 | /** 72 | * Load Textdomain 73 | * 74 | * Load plugin localization files. 75 | * Fired by `init` action hook. 76 | * 77 | * @since 1.0.0 78 | * @access public 79 | */ 80 | public function i18n() { 81 | load_plugin_textdomain( '<%= fileName %>' ); 82 | } 83 | 84 | /** 85 | * Initialize the plugin 86 | * 87 | * Validates that Elementor is already loaded. 88 | * Checks for basic plugin requirements, if one check fail don't continue, 89 | * if all check have passed include the plugin class. 90 | * 91 | * Fired by `plugins_loaded` action hook. 92 | * 93 | * @since 1.0.0 94 | * @access public 95 | */ 96 | public function init() { 97 | 98 | // Check if Elementor installed and activated. 99 | if ( ! did_action( 'elementor/loaded' ) ) { 100 | add_action( 'admin_notices', array( $this, 'admin_notice_missing_main_plugin' ) ); 101 | return; 102 | } 103 | 104 | // Check for required Elementor version. 105 | if ( ! version_compare( ELEMENTOR_VERSION, self::MINIMUM_ELEMENTOR_VERSION, '>=' ) ) { 106 | add_action( 'admin_notices', array( $this, 'admin_notice_minimum_elementor_version' ) ); 107 | return; 108 | } 109 | 110 | // Check for required PHP version. 111 | if ( version_compare( PHP_VERSION, self::MINIMUM_PHP_VERSION, '<' ) ) { 112 | add_action( 'admin_notices', array( $this, 'admin_notice_minimum_php_version' ) ); 113 | return; 114 | } 115 | 116 | // Once we get here, We have passed all validation checks so we can safely include our widgets. 117 | require_once 'class-widgets.php'; 118 | } 119 | 120 | /** 121 | * Admin notice 122 | * 123 | * Warning when the site doesn't have Elementor installed or activated. 124 | * 125 | * @since 1.0.0 126 | * @access public 127 | */ 128 | public function admin_notice_missing_main_plugin() { 129 | deactivate_plugins( plugin_basename( <%= constantName %> ) ); 130 | 131 | return sprintf( 132 | wp_kses( 133 | '

"%1$s" requires "%2$s" to be installed and activated.

', 134 | array( 135 | 'div' => array( 136 | 'class' => array(), 137 | 'p' => array(), 138 | 'strong' => array(), 139 | ), 140 | ) 141 | ), 142 | 'Elementor <%= name %>', 143 | 'Elementor' 144 | ); 145 | } 146 | 147 | /** 148 | * Admin notice 149 | * 150 | * Warning when the site doesn't have a minimum required Elementor version. 151 | * 152 | * @since 1.0.0 153 | * @access public 154 | */ 155 | public function admin_notice_minimum_elementor_version() { 156 | deactivate_plugins( plugin_basename( <%= constantName %> ) ); 157 | 158 | return sprintf( 159 | wp_kses( 160 | '

"%1$s" requires "%2$s" version %3$s or greater.

', 161 | array( 162 | 'div' => array( 163 | 'class' => array(), 164 | 'p' => array(), 165 | 'strong' => array(), 166 | ), 167 | ) 168 | ), 169 | 'Elementor <%= name %>', 170 | 'Elementor', 171 | self::MINIMUM_ELEMENTOR_VERSION 172 | ); 173 | } 174 | 175 | /** 176 | * Admin notice 177 | * 178 | * Warning when the site doesn't have a minimum required PHP version. 179 | * 180 | * @since 1.0.0 181 | * @access public 182 | */ 183 | public function admin_notice_minimum_php_version() { 184 | deactivate_plugins( plugin_basename( <%= constantName %> ) ); 185 | 186 | return sprintf( 187 | wp_kses( 188 | '

"%1$s" requires "%2$s" version %3$s or greater.

', 189 | array( 190 | 'div' => array( 191 | 'class' => array(), 192 | 'p' => array(), 193 | 'strong' => array(), 194 | ), 195 | ) 196 | ), 197 | '<%= name %>', 198 | 'Elementor', 199 | self::MINIMUM_ELEMENTOR_VERSION 200 | ); 201 | } 202 | } 203 | 204 | // Instantiate <%= constantName %>. 205 | new <%= constantName %>(); 206 | -------------------------------------------------------------------------------- /packages/react-elementor/src/generators/plugin/app/class-widgets.php__template__: -------------------------------------------------------------------------------- 1 | class. 4 | * 5 | * @category Class 6 | * @package <%= className %> 7 | * @subpackage WordPress 8 | * @author <%= author %> 9 | * @copyright <%= copyright %> 10 | * @license <%= license %> 11 | * @link link( <%= link %>) 12 | * @since 1.0.0 13 | * php version 7.3.9 14 | */ 15 | 16 | namespace <%= className %>; 17 | 18 | // Security Note: Blocks direct access to the plugin PHP files. 19 | defined('ABSPATH') || die(); 20 | 21 | /** 22 | * Class Plugin 23 | * 24 | * Main Plugin class 25 | * 26 | * @since 1.0.0 27 | */ 28 | class Widgets 29 | { 30 | 31 | /** 32 | * Instance 33 | * 34 | * @since 1.0.0 35 | * @access private 36 | * @static 37 | * 38 | * @var Plugin The single instance of the class. 39 | */ 40 | private static $instance = null; 41 | 42 | /** 43 | * Instance 44 | * 45 | * Ensures only one instance of the class is loaded or can be loaded. 46 | * 47 | * @return Plugin An instance of the class. 48 | * @since 1.0.0 49 | * @access public 50 | * 51 | */ 52 | public static function instance() 53 | { 54 | if (is_null(self::$instance)) { 55 | self::$instance = new self(); 56 | } 57 | 58 | return self::$instance; 59 | } 60 | 61 | /** 62 | * Include Widgets files 63 | * 64 | * Load widgets files 65 | * 66 | * @since 1.0.0 67 | * @access private 68 | */ 69 | private function include_widgets_files() 70 | { 71 | } 72 | 73 | public function register_categories() 74 | { 75 | \Elementor\Plugin::instance()->elements_manager->add_category( 76 | '<%= fileName %>-category', 77 | array( 78 | 'title' => __('<%= name %> components', '<%= fileName %>'), 79 | 'icon' => 'fa fa-ship', 80 | ) 81 | ); 82 | } 83 | 84 | 85 | /** 86 | * widget_scripts 87 | * 88 | * Load required plugin core files. 89 | * 90 | * @access public 91 | */ 92 | public function widget_scripts() 93 | { 94 | wp_register_script('<%= fileName %>-script-vendor', plugins_url('dist/vendor.js', __FILE__), array(), 'xxVERSIONxx', true); 95 | wp_register_script('<%= fileName %>-script-main', plugins_url('dist/main.js', __FILE__), array(), 'xxVERSIONxx', true); 96 | wp_register_script('<%= fileName %>-script-runtime', plugins_url('dist/runtime.js', __FILE__), array(), 'xxVERSIONxx', true); 97 | 98 | } 99 | 100 | /** 101 | * Register Widgets 102 | * 103 | * Register new Elementor widgets. 104 | * 105 | * @since 1.0.0 106 | * @access public 107 | */ 108 | public function register_widgets() 109 | { 110 | // It's now safe to include Widgets files. 111 | $this->include_widgets_files(); 112 | 113 | // Register the plugin widget classes. 114 | \Elementor\Plugin::instance()->widgets_manager->register_widget_type(new Widgets\<%= constantName %>_INPUT()); 115 | \Elementor\Plugin::instance()->widgets_manager->register_widget_type(new Widgets\<%= constantName %>_TITLE()); 116 | 117 | 118 | } 119 | 120 | /** 121 | * Plugin class constructor 122 | * 123 | * Register plugin action hooks and filters 124 | * 125 | * @since 1.0.0 126 | * @access public 127 | */ 128 | public function __construct() 129 | { 130 | // Register the widgets. 131 | add_action('elementor/elements/categories_registered', array($this, 'register_categories')); 132 | add_action('elementor/frontend/after_register_scripts', array($this, 'widget_scripts')); 133 | add_action('elementor/widgets/widgets_registered', array($this, 'register_widgets')); 134 | 135 | } 136 | } 137 | 138 | // Instantiate the Widgets class. 139 | Widgets::instance(); 140 | -------------------------------------------------------------------------------- /packages/react-elementor/src/generators/plugin/app/index.html__template__: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/react-elementor/src/generators/plugin/app/index.php__template__: -------------------------------------------------------------------------------- 1 | Input, <%=className%>Title, WebComponentWrapper,} from '@<%=npmScope%>/<%=uiLibName%>'; 2 | 3 | WebComponentWrapper( 4 | '<%= fileName %>-input', 5 | <%=className%>Input, 6 | CustomProviders 7 | ); 8 | 9 | WebComponentWrapper( 10 | '<%= fileName %>-title', 11 | <%=className%>Title, 12 | CustomProviders 13 | ); 14 | -------------------------------------------------------------------------------- /packages/react-elementor/src/generators/plugin/app/src/assets/__dot__gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/betrueagency/nx-reactjs-elementor/714d345e5fac5cf5ab199abe52fdc3441558424c/packages/react-elementor/src/generators/plugin/app/src/assets/__dot__gitkeep -------------------------------------------------------------------------------- /packages/react-elementor/src/generators/plugin/app/src/assets/app.module.css: -------------------------------------------------------------------------------- 1 | .app { 2 | font-family: sans-serif; 3 | min-width: 300px; 4 | max-width: 600px; 5 | margin: 50px auto; 6 | } 7 | 8 | .app .gutter-left{ 9 | margin-left: 9px; 10 | } 11 | 12 | .app .col-span-2{ 13 | grid-column: span 2; 14 | } 15 | 16 | .app .flex{ 17 | display: flex; 18 | align-items: center; 19 | justify-content: center; 20 | } 21 | 22 | .app header{ 23 | background-color: #143055; 24 | color: white; 25 | padding: 5px; 26 | border-radius: 3px; 27 | } 28 | 29 | .app main{ 30 | padding: 0 36px; 31 | } 32 | 33 | .app p{ 34 | text-align: center; 35 | } 36 | 37 | .app h1{ 38 | text-align: center; 39 | margin-left: 18px; 40 | font-size: 24px; 41 | } 42 | 43 | .app h2{ 44 | text-align: center; 45 | font-size: 20px; 46 | margin: 40px 0 10px 0; 47 | } 48 | 49 | .app .resources{ 50 | text-align: center; 51 | list-style: none; 52 | padding: 0; 53 | display: grid; 54 | grid-gap: 9px; 55 | grid-template-columns: 1fr 1fr; 56 | } 57 | 58 | .app .resource{ 59 | color: #0094ba; 60 | height: 36px; 61 | background-color: rgba(0, 0, 0, 0); 62 | border: 1px solid rgba(0, 0, 0, 0.12); 63 | border-radius: 4px; 64 | padding: 3px 9px; 65 | text-decoration: none; 66 | } 67 | 68 | .app .resource:hover{ 69 | background-color: rgba(68, 138, 255, 0.04); 70 | } 71 | 72 | .app pre{ 73 | padding: 9px; 74 | border-radius: 4px; 75 | background-color: black; 76 | color: #eee; 77 | } 78 | 79 | .app details{ 80 | border-radius: 4px; 81 | color: #333; 82 | background-color: rgba(0, 0, 0, 0); 83 | border: 1px solid rgba(0, 0, 0, 0.12); 84 | padding: 3px 9px; 85 | margin-bottom: 9px; 86 | } 87 | 88 | .app summary{ 89 | outline: none; 90 | height: 36px; 91 | line-height: 36px; 92 | } 93 | 94 | .app .github-star-container{ 95 | margin-top: 12px; 96 | line-height: 20px; 97 | } 98 | 99 | .app .github-star-container a{ 100 | display: flex; 101 | align-items: center; 102 | text-decoration: none; 103 | color: #333; 104 | } 105 | 106 | .app .github-star-badge{ 107 | color: #24292e; 108 | display: flex; 109 | align-items: center; 110 | font-size: 12px; 111 | padding: 3px 10px; 112 | border: 1px solid rgba(27, 31, 35, 0.2); 113 | border-radius: 3px; 114 | background-image: linear-gradient(-180deg, #fafbfc, #eff3f6 90%); 115 | margin-left: 4px; 116 | font-weight: 600; 117 | } 118 | 119 | .app .github-star-badge:hover{ 120 | background-image: linear-gradient(-180deg, #f0f3f6, #e6ebf1 90%); 121 | border-color: rgba(27, 31, 35, 0.35); 122 | background-position: -0.5em; 123 | } 124 | .app .github-star-badge .material-icons{ 125 | height: 16px; 126 | width: 16px; 127 | margin-right: 4px; 128 | } 129 | -------------------------------------------------------------------------------- /packages/react-elementor/src/generators/plugin/app/src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /packages/react-elementor/src/generators/plugin/app/src/assets/star.svg: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /packages/react-elementor/src/generators/plugin/app/src/docker-compose.yml__template__: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | db: 5 | image: mysql:5.7 6 | volumes: 7 | - db_data:/var/lib/mysql 8 | restart: always 9 | environment: 10 | MYSQL_ROOT_PASSWORD: somewordpress 11 | MYSQL_DATABASE: wordpress 12 | MYSQL_USER: wordpress 13 | MYSQL_PASSWORD: wordpress 14 | 15 | wordpress: 16 | depends_on: 17 | - db 18 | image: wordpress:latest 19 | volumes: 20 | - wordpress_data:/var/www/html:rw 21 | - ../../../dist/elementor/<%=fileName%>:/var/www/html/wp-content/plugins/<%=fileName%>:ro 22 | ports: 23 | - "8000:80" 24 | restart: always 25 | environment: 26 | WORDPRESS_DB_HOST: db:3306 27 | WORDPRESS_DB_USER: wordpress 28 | WORDPRESS_DB_PASSWORD: wordpress 29 | WORDPRESS_DB_NAME: wordpress 30 | volumes: 31 | db_data: {} 32 | wordpress_data: {} 33 | -------------------------------------------------------------------------------- /packages/react-elementor/src/generators/plugin/app/src/environments/environment.prod.ts__template__: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /packages/react-elementor/src/generators/plugin/app/src/environments/environment.ts__template__: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // When building for production, this file is replaced with `environment.prod.ts`. 3 | 4 | export const environment = { 5 | production: false 6 | }; 7 | -------------------------------------------------------------------------------- /packages/react-elementor/src/generators/plugin/app/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/betrueagency/nx-reactjs-elementor/714d345e5fac5cf5ab199abe52fdc3441558424c/packages/react-elementor/src/generators/plugin/app/src/favicon.ico -------------------------------------------------------------------------------- /packages/react-elementor/src/generators/plugin/app/src/index.html__template__: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ElementorPlugin7636255 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 | 16 |

Your generated display web component

17 | <<%=fileName%>-title label="Here your title"/> 18 | 19 |
20 |
21 |

Your generated update web component

22 | 23 | <<%=fileName%>-input placeholder="Type a new title" button="Apply" /> 24 | 25 | 26 |

Resources & Tools

27 |

Thank you for using and showing some ♥ for Nx.

28 | 42 |

Here are some links to help you get started.

43 | 82 |

Next Steps

83 |

Here are some things you can do with elementor plugin.

84 |
85 | Add UI library 86 |
# Add new elementor widget to your plugin
 87 | nx g @betrue/react-elementor:addWidget my-cool-elementor-widget --plugin=<%=fileName%>
 88 | 
 89 | # Add a component
 90 | nx g @nx/react:component xyz --project=<%=fileName%>
91 |
92 |
93 | View dependency graph 94 |
nx dep-graph
95 |
96 |
97 | Run affected commands 98 |
# see what's been affected by changes
 99 | nx affected:dep-graph
100 | 
101 | # run tests for current changes
102 | nx affected:test
103 | 
104 | # run e2e tests for current changes
105 | nx affected:e2e
106 |
107 |
108 |
109 | 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /packages/react-elementor/src/generators/plugin/app/src/main.tsx__template__: -------------------------------------------------------------------------------- 1 | export * from './app/<%=fileName%>' 2 | -------------------------------------------------------------------------------- /packages/react-elementor/src/generators/plugin/app/src/polyfills.ts__template__: -------------------------------------------------------------------------------- 1 | /** 2 | * Polyfill stable language features. These imports will be optimized by `@babel/preset-env`. 3 | * 4 | * See: https://github.com/zloirock/core-js#babel 5 | */ 6 | import 'core-js/stable'; 7 | import 'regenerator-runtime/runtime'; 8 | -------------------------------------------------------------------------------- /packages/react-elementor/src/generators/plugin/app/tsconfig.app.json__template__: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "<%= offsetFromRoot %>dist/out-tsc", 5 | "types": ["node"] 6 | }, 7 | "files": [ 8 | <% if (style === 'styled-jsx') { %>"<%= offsetFromRoot %>node_modules/@nx/react/typings/styled-jsx.d.ts",<% } %> 9 | "<%= offsetFromRoot %>node_modules/@nx/react/typings/cssmodule.d.ts", 10 | "<%= offsetFromRoot %>node_modules/@nx/react/typings/image.d.ts" 11 | ], 12 | "exclude": ["**/*.spec.ts", "**/*.spec.tsx"], 13 | "include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] 14 | } 15 | -------------------------------------------------------------------------------- /packages/react-elementor/src/generators/plugin/app/tsconfig.json__template__: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "<%= offsetFromRoot %>tsconfig.base.json", 3 | "compilerOptions": { 4 | "jsx": "react-jsx", 5 | "allowJs": true, 6 | "esModuleInterop": true, 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "files": [], 10 | "include": [], 11 | "references": [ 12 | { 13 | "path": "./tsconfig.app.json" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /packages/react-elementor/src/generators/plugin/app/wp-react-elementor.php__template__: -------------------------------------------------------------------------------- 1 | WordPress Plugin 4 | * 5 | * @package <%= className %> 6 | * 7 | * Plugin Name: <%= name %> 8 | * Description: <%= pluginDescription %> 9 | * Plugin URI: <%= pluginUri %> 10 | * Version: 1.0.0 11 | * Author: <%= author %> 12 | * Author URI: <%= author %> 13 | * Text Domain: <%= fileName %> 14 | */ 15 | 16 | define( 'ELEMENTOR_<%= constantName %>', __FILE__ ); 17 | 18 | /** 19 | * Include the <%= name %> class. 20 | */ 21 | require plugin_dir_path(ELEMENTOR_<%= constantName %>) . 'class-react-elementor.php'; 22 | -------------------------------------------------------------------------------- /packages/react-elementor/src/generators/plugin/generator.ts: -------------------------------------------------------------------------------- 1 | import { 2 | formatFiles, 3 | generateFiles, 4 | getWorkspaceLayout, 5 | names, 6 | offsetFromRoot, 7 | Tree, 8 | convertNxGenerator, 9 | updateProjectConfiguration, 10 | readProjectConfiguration, runTasksInSerial, ensurePackage, GeneratorCallback, 11 | } from '@nx/devkit'; 12 | 13 | import * as path from 'path'; 14 | import {ElementorPluginGeneratorSchema} from './schema'; 15 | import elementorInitGenerator from '../init/init'; 16 | import {assertValidStyle, reactInitGenerator, SupportedStyles, withReact} from '@nx/react'; 17 | import {addProject} from '@nx/react/src/generators/application/lib/add-project'; 18 | import widgetGenerator from '../../generators/widget/generator'; 19 | import {reactComponentFiles} from './lib/react-component-files'; 20 | import {getNpmScope} from "@nx/workspace/src/utilities/get-import-path"; 21 | import {nxVersion} from "nx/src/utils/versions"; 22 | import {composePlugins, withNx} from "@nx/webpack"; 23 | import {createApplicationFiles} from "@nx/react/src/generators/application/lib/create-application-files"; 24 | 25 | export interface ElementorNormalizedSchema 26 | extends ElementorPluginGeneratorSchema { 27 | projectName: string; 28 | projectRoot: string; 29 | projectDirectory: string; 30 | styledModule: null | SupportedStyles; 31 | parsedTags: string[]; 32 | } 33 | 34 | function normalizeOptions( 35 | host: Tree, 36 | options: ElementorPluginGeneratorSchema 37 | ): ElementorNormalizedSchema { 38 | const name = names(options.name).fileName; 39 | const projectDirectory = options.directory 40 | ? `${names(options.directory).fileName}/${name}` 41 | : name; 42 | const projectName = projectDirectory.replace(new RegExp('/', 'g'), '-'); 43 | const projectRoot = `${getWorkspaceLayout(host).appsDir}/${projectDirectory}`; 44 | const appProjectRoot = projectRoot 45 | const parsedTags = options.tags 46 | ? options.tags?.split(',').map((s) => s.trim()) 47 | : []; 48 | 49 | const styledModule = /^(css|scss|less|styl|none)$/.test(options.style) 50 | ? null 51 | : options.style; 52 | 53 | assertValidStyle(options.style); 54 | options.strict = options.strict ?? true; 55 | options.styledModule = 'none'; 56 | options.bundler = 'webpack'; 57 | options.classComponent = options.classComponent ?? false; 58 | options.unitTestRunner = options.unitTestRunner ?? 'jest'; 59 | options.e2eTestRunner = options.e2eTestRunner ?? 'cypress'; 60 | return { 61 | npmScope: getNpmScope(host).toLowerCase(), 62 | ...options, 63 | appProjectRoot: appProjectRoot, 64 | projectName, 65 | projectRoot, 66 | projectDirectory, 67 | parsedTags, 68 | styledModule, 69 | name: name, 70 | minimal:true, 71 | routing:false, 72 | inSourceTests:false, 73 | }; 74 | } 75 | 76 | function addFiles( 77 | host: Tree, 78 | options: ElementorNormalizedSchema, 79 | projectName: string 80 | ) { 81 | const templateOptions = { 82 | ...options, 83 | ...names(options.name), 84 | uiLibName: `${projectName}-ui`, 85 | storeLibName: `${projectName}-store`, 86 | offsetFromRoot: offsetFromRoot(options.appProjectRoot), 87 | template: '', 88 | tmpl: '', 89 | dot: '.' 90 | }; 91 | 92 | generateFiles( 93 | host, 94 | path.join(__dirname, 'app'), 95 | options.appProjectRoot, 96 | templateOptions 97 | ); 98 | } 99 | 100 | export async function pluginGenerator( 101 | host: Tree, 102 | options: ElementorPluginGeneratorSchema 103 | ) { 104 | composePlugins(withNx(), withReact()); 105 | const normalizedOptions = normalizeOptions(host, options); 106 | const tasks: GeneratorCallback[] = []; 107 | const initTask = await reactInitGenerator(host, { 108 | ...options, 109 | skipFormat: true, 110 | }); 111 | tasks.push(initTask); 112 | const {webpackInitGenerator} = ensurePackage< 113 | typeof import('@nx/webpack') 114 | >('@nx/webpack', nxVersion); 115 | console.log('nxVersion', webpackInitGenerator?.name) 116 | const webpackInitTask = await webpackInitGenerator(host, { 117 | skipPackageJson: false, 118 | skipFormat: true, 119 | addPlugin: true, 120 | }); 121 | tasks.push(webpackInitTask); 122 | 123 | createApplicationFiles(host, normalizedOptions); 124 | 125 | addProject(host, normalizedOptions); 126 | 127 | const projectConfig = readProjectConfiguration( 128 | host, 129 | normalizedOptions.projectName 130 | ); 131 | 132 | //projectConfig.targets.build.configurations.production.outputHashing = 'none'; 133 | 134 | projectConfig.targets.plugin = { 135 | executor: '@betrue/react-elementor:build', 136 | options: { 137 | plugin: normalizedOptions.projectName, 138 | replaceFilePattern: '.esm.js', 139 | }, 140 | }; 141 | 142 | projectConfig.targets.pkg = { 143 | executor: 'nx:run-commands', 144 | options: { 145 | commands: [ 146 | `nx build ${normalizedOptions.projectName}`, 147 | `nx plugin ${normalizedOptions.projectName}`, 148 | ], 149 | parallel: false, 150 | }, 151 | }; 152 | 153 | updateProjectConfiguration( 154 | host, 155 | normalizedOptions.projectName, 156 | projectConfig 157 | ); 158 | addFiles(host, normalizedOptions, normalizedOptions.projectName); 159 | const widgetDescription = 'simple demo widget generated on project init'; 160 | normalizedOptions.directory = undefined; 161 | await widgetGenerator(host, { 162 | attributes: 'placeholder,button', 163 | author: normalizedOptions.author, 164 | name: `${normalizedOptions.name}-input`, 165 | version: normalizedOptions.version, 166 | widgetDescription: widgetDescription, 167 | plugin: normalizedOptions.projectName, 168 | }); 169 | 170 | await widgetGenerator(host, { 171 | attributes: 'label', 172 | author: normalizedOptions.author, 173 | widgetDescription: widgetDescription, 174 | version: normalizedOptions.version, 175 | name: `${normalizedOptions.name}-title`, 176 | plugin: normalizedOptions.projectName, 177 | }); 178 | 179 | await reactComponentFiles( 180 | host, 181 | normalizedOptions, 182 | normalizedOptions.projectName 183 | ); 184 | console.log('elementorInitGenerator'); 185 | 186 | const elementorTask = await elementorInitGenerator(host, { 187 | ...options, 188 | skipFormat: true, 189 | }); 190 | 191 | await formatFiles(host); 192 | return runTasksInSerial(...tasks,elementorTask); 193 | } 194 | 195 | export const pluginSchematic = convertNxGenerator(pluginGenerator); 196 | -------------------------------------------------------------------------------- /packages/react-elementor/src/generators/plugin/lib/react-component-files.ts: -------------------------------------------------------------------------------- 1 | import { 2 | generateFiles, 3 | getWorkspaceLayout, 4 | names, 5 | offsetFromRoot, 6 | Tree, 7 | } from '@nx/devkit'; 8 | import { ElementorNormalizedSchema } from '../generator'; 9 | import { 10 | componentGenerator, 11 | libraryGenerator, 12 | storybookConfigurationGenerator, 13 | } from '@nx/react'; 14 | import { Linter } from '@nx/eslint'; 15 | import * as path from 'path'; 16 | 17 | const ROOT_UI_LIB = 'ui'; 18 | 19 | export async function reactComponentFiles( 20 | host: Tree, 21 | options: ElementorNormalizedSchema, 22 | projectName: string 23 | ) { 24 | const uiLibName = `${projectName}-${ROOT_UI_LIB}`; 25 | const libRoot = `${getWorkspaceLayout(host).libsDir}/${uiLibName}/src`; 26 | 27 | const templateOptions = { 28 | ...options, 29 | ...names(options.name), 30 | uiLibName, 31 | offsetFromRoot: offsetFromRoot(libRoot), 32 | template: '', 33 | dot: '.', 34 | }; 35 | 36 | console.log('libraryGenerator'); 37 | await libraryGenerator(host, { 38 | linter: Linter.EsLint, 39 | skipFormat: false, 40 | skipTsConfig: false, 41 | style: 'none', 42 | unitTestRunner: 'none', 43 | ...names(uiLibName), 44 | name: uiLibName, 45 | }); 46 | console.log('storybookConfigurationGenerator'); 47 | 48 | await storybookConfigurationGenerator(host, { 49 | project: uiLibName, 50 | configureCypress: false, 51 | generateStories: false, 52 | generateCypressSpecs: false, 53 | js: false, 54 | tsConfiguration: false, 55 | interactionTests: false, 56 | }); 57 | console.log('componentGenerator'); 58 | 59 | await componentGenerator(host, { 60 | style: 'none', 61 | ...names(`${options.name}-title`), 62 | name: `${options.name}-title`, 63 | project: uiLibName, 64 | export: true, 65 | skipTests: true, 66 | }); 67 | console.log('componentGenerator 2'); 68 | 69 | await componentGenerator(host, { 70 | style: 'none', 71 | ...names(`${options.name}-input`), 72 | name: `${options.name}-input`, 73 | project: uiLibName, 74 | export: true, 75 | skipTests: true, 76 | }); 77 | console.log('componentGenerator 3'); 78 | 79 | await componentGenerator(host, { 80 | style: 'none', 81 | ...names(`web-component-wrapper`), 82 | name: `web-component-wrapper`, 83 | project: uiLibName, 84 | export: true, 85 | skipTests: true, 86 | }); 87 | 88 | await generateFiles( 89 | host, 90 | path.join(__dirname, `../libs/ui`), 91 | libRoot, 92 | templateOptions 93 | ); 94 | } 95 | -------------------------------------------------------------------------------- /packages/react-elementor/src/generators/plugin/libs/ui/index.ts__template__: -------------------------------------------------------------------------------- 1 | export * from './lib/config/providers' 2 | export * from './lib/common/index' 3 | export * from './lib/store' 4 | export * from './lib/<%=fileName%>-input/<%=fileName%>-input'; 5 | export * from './lib/<%=fileName%>-title/<%=fileName%>-title'; 6 | -------------------------------------------------------------------------------- /packages/react-elementor/src/generators/plugin/libs/ui/lib/__fileName__-input__template__/__fileName__-input.stories.tsx__template__: -------------------------------------------------------------------------------- 1 | import type {ComponentStory, ComponentMeta} from '@storybook/react'; 2 | import {<%=className%>Input} from './<%=fileName%>-input'; 3 | import {CustomProviders} from "../config/providers"; 4 | 5 | const Story: ComponentMetaInput> = { 6 | component: <%=className%>Input, 7 | title: '<%=className%>Input', 8 | }; 9 | export default Story; 10 | 11 | const Template: ComponentStoryInput> = (args) => ( 12 | <<%=className%>Input {...args} /> 13 | ); 14 | 15 | export const Primary = Template.bind({}); 16 | Primary.args = { 17 | label: 'Push it!', 18 | placeholder:'To the limit' 19 | }; 20 | -------------------------------------------------------------------------------- /packages/react-elementor/src/generators/plugin/libs/ui/lib/__fileName__-input__template__/__fileName__-input.tsx__template__: -------------------------------------------------------------------------------- 1 | import {update<%=classComponent%>Entity} from '../store'; 2 | import React, {FormEvent} from 'react'; 3 | import {useDispatch} from 'react-redux'; 4 | import PropTypes, {InferProps} from 'prop-types'; 5 | import {<%=className%>InputView} from "./<%=fileName%>-input.view"; 6 | 7 | export const <%=className%>InputPropsTypes = { 8 | placeholder: PropTypes.string, 9 | label: PropTypes.string, 10 | } 11 | 12 | export type <%=className%>InputProps = InferPropsInputPropsTypes> 13 | 14 | /** 15 | * Same component linked to the store 16 | */ 17 | export function <%=className%>Input(props: <%=className%>InputProps) { 18 | const dispatch = useDispatch(); 19 | 20 | const handleSubmit = (e: FormEvent) => { 21 | e.preventDefault(); 22 | if (e.target[0]?.value) 23 | { 24 | dispatch(update<%=classComponent%>Entity(e.target[0]?.value)); 25 | e.target[0].value = ''; 26 | } 27 | 28 | }; 29 | return ( 30 | <<%=className%>InputView 31 | {...props} 32 | handleSubmit={handleSubmit} 33 | /> 34 | ); 35 | } 36 | 37 | /** 38 | * Exposing props to elementor through the web component 39 | */ 40 | <%=className%>Input.propTypes = <%=className%>InputPropsTypes 41 | -------------------------------------------------------------------------------- /packages/react-elementor/src/generators/plugin/libs/ui/lib/__fileName__-input__template__/__fileName__-input.view.tsx__template__: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Button, Grid, styled, TextField} from '@mui/material'; 3 | import {StyledBaseProps} from "../common/types"; 4 | import {<%=className%>InputProps} from "./<%=fileName%>-input"; 5 | 6 | interface <%=className%>InputViewProps extends StyledBaseProps, <%=className%>InputProps { 7 | handleSubmit: (e:any)=> void 8 | } 9 | 10 | export const <%=className%>InputView = styled((props: <%=className%>InputViewProps) => ( 11 |
12 |
13 | 20 | 21 | 26 | 27 | 28 | 31 | 32 | 33 |
34 |
35 | ) 36 | )` 37 | & { 38 | .bt-project-select { 39 | width: 100%; 40 | } 41 | }` 42 | 43 | -------------------------------------------------------------------------------- /packages/react-elementor/src/generators/plugin/libs/ui/lib/__fileName__-title__template__/__fileName__-title.stories.tsx__template__: -------------------------------------------------------------------------------- 1 | import type { ComponentStory, ComponentMeta } from '@storybook/react'; 2 | import { <%=className%>Title } from './<%=fileName%>-title'; 3 | import {CustomProviders} from "../config/providers"; 4 | 5 | const Story: ComponentMetaTitle> = { 6 | component: <%=className%>Title, 7 | title: '<%=className%>Title', 8 | }; 9 | export default Story; 10 | 11 | const Template: ComponentStoryTitle> = (args) => ( 12 | <<%=className%>Title {...args} /> 13 | ); 14 | 15 | export const Primary = Template.bind({}); 16 | Primary.args = {label: 'Your fresh title:'}; 17 | -------------------------------------------------------------------------------- /packages/react-elementor/src/generators/plugin/libs/ui/lib/__fileName__-title__template__/__fileName__-title.tsx__template__: -------------------------------------------------------------------------------- 1 | import {useSelector} from 'react-redux'; 2 | import {<%=classComponent%>EntitySelector} from "../store"; 3 | import PropTypes, {InferProps} from 'prop-types'; 4 | import {<%=className%>TitleView} from "./<%=fileName%>-title.view"; 5 | 6 | export const <%=className%>TitlePropsTypes = { 7 | label: PropTypes.string 8 | } 9 | 10 | export type <%=className%>TitleProps = InferPropsTitlePropsTypes> 11 | 12 | 13 | 14 | /** 15 | * Same component linked to the store 16 | */ 17 | export function <%=className%>Title(props:<%=className%>TitleProps) { 18 | const v = useSelector(falseEntitySelector); 19 | 20 | return <<%=className%>TitleView lastValue={v} {...props} />; 21 | } 22 | 23 | /** 24 | * Exposing props to elementor through the web component 25 | * /!\ o not use this for state management 26 | */ 27 | <%=className%>Title.propTypes = <%=className%>TitlePropsTypes 28 | -------------------------------------------------------------------------------- /packages/react-elementor/src/generators/plugin/libs/ui/lib/__fileName__-title__template__/__fileName__-title.view.tsx__template__: -------------------------------------------------------------------------------- 1 | import {styled} from "@mui/material"; 2 | import {<%=className%>TitleProps} from "./<%=fileName%>-title"; 3 | import {StyledBaseProps} from "../common/types"; 4 | 5 | 6 | interface <%=className%>TitleViewProps extends <%=className%>TitleProps, StyledBaseProps { 7 | lastValue: string 8 | 9 | } 10 | 11 | export const <%=className%>TitleView = styled((props: <%=className%>TitleViewProps) => ( 12 |
13 |

14 | {' '} 15 | {props.label ? `${props.label}: ` : 'Title: '} {props.lastValue}{' '} 16 |

17 |
18 | ))`` 19 | -------------------------------------------------------------------------------- /packages/react-elementor/src/generators/plugin/libs/ui/lib/common/index.ts__template__: -------------------------------------------------------------------------------- 1 | export * from './web-component-wrapper' 2 | -------------------------------------------------------------------------------- /packages/react-elementor/src/generators/plugin/libs/ui/lib/common/types.ts__template__: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {Theme} from "@mui/material/styles/createTheme"; 3 | 4 | export interface BaseAppProviderProps { 5 | children: React.ReactNode | React.ReactNode[] | null; 6 | 7 | } 8 | 9 | export interface ProvidersProps extends BaseAppProviderProps { 10 | theme: Theme 11 | } 12 | export interface StyledBaseProps { 13 | className?: string, 14 | children?: any 15 | } 16 | -------------------------------------------------------------------------------- /packages/react-elementor/src/generators/plugin/libs/ui/lib/common/web-component-wrapper.tsx__template__: -------------------------------------------------------------------------------- 1 | import ReactDOM from 'react-dom'; 2 | import React from 'react'; 3 | import reactToWebComponent from 'react-to-webcomponent'; 4 | import {ThemeProvider} from '@mui/system'; 5 | import createCache from '@emotion/cache'; 6 | import {StyledEngineProvider} from '@mui/material'; 7 | import {createRoot} from 'react-dom/client'; 8 | import {CacheProvider} from '@emotion/react'; 9 | import {ProvidersProps} from "./types"; 10 | 11 | 12 | export function ThemeProviders(props: ProvidersProps) { 13 | return ( 14 | 15 | 16 | {props.children} 17 | 18 | 19 | ) 20 | } 21 | 22 | /** 23 | * Web component wrapper that wrap the react component into a shadow dom web component 24 | * shadow dom is used to isolate your custom component from element styles 25 | */ 26 | export function WebComponentWrapper( 27 | NAME: string, 28 | Component: (props) => JSX.Element, 29 | CustomProviders: (props) => React.ReactElement 30 | ): void { 31 | class WebComponent extends reactToWebComponent(Component, React, ReactDOM) { 32 | props = {}; 33 | 34 | 35 | //https://codesandbox.io/s/shadow-dom-rki9k5?from-embed=&file=/index.tsx:1159-1188 36 | connectedCallback() { 37 | const shadowRoot = super.attachShadow({mode: 'open'}); 38 | const mountPoint = document.createElement('span'); 39 | const emotionRoot = document.createElement('style'); 40 | 41 | mountPoint.id = NAME; 42 | shadowRoot.appendChild(mountPoint); 43 | shadowRoot.appendChild(emotionRoot); 44 | const cache = createCache({ 45 | key: 'css', 46 | prepend: true, 47 | container: emotionRoot, 48 | }); 49 | 50 | if ((Component as any).propTypes) { 51 | this.props = Object.keys((Component as any).propTypes).reduce( 52 | (obj, item) => { 53 | return { 54 | ...obj, 55 | [`${item}`]: super.getAttribute(item), 56 | }; 57 | }, 58 | {} 59 | ); 60 | } 61 | const root = createRoot(mountPoint); // createRoot(container!) if you use TypeScript 62 | 63 | 64 | root.render( 65 | 66 | 67 | 68 | 69 | 70 | ); 71 | } 72 | } 73 | 74 | if (!customElements.get(NAME)) { 75 | customElements.define(NAME, WebComponent as any); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /packages/react-elementor/src/generators/plugin/libs/ui/lib/config/providers.tsx__template__: -------------------------------------------------------------------------------- 1 | import {Provider} from "react-redux"; 2 | import React from "react"; 3 | import {configureStore} from "@reduxjs/toolkit"; 4 | import {falseReducer} from "../store"; 5 | import {responsiveFontSizes} from "@mui/material/styles"; 6 | import {createTheme} from "@mui/material"; 7 | import {BaseAppProviderProps} from "../common/types"; 8 | import {ThemeProviders} from "../common"; 9 | 10 | 11 | export const muiTheme = responsiveFontSizes( 12 | createTheme({ 13 | /* your custom theme */ 14 | }) 15 | ); 16 | 17 | // redux config 18 | const reducer = { 19 | <%=classComponent%>Entity: <%=classComponent%>Reducer 20 | } 21 | 22 | const store = configureStore({ 23 | reducer, 24 | middleware: (getDefaultMiddleware) => getDefaultMiddleware(), 25 | devTools: process.env.NODE_ENV !== 'production', 26 | preloadedState: {}, 27 | enhancers: [], 28 | }); 29 | 30 | 31 | export function CustomProviders(props: BaseAppProviderProps) { 32 | return ( 33 | 34 | 35 | {props.children} 36 | 37 | 38 | ) 39 | 40 | } 41 | -------------------------------------------------------------------------------- /packages/react-elementor/src/generators/plugin/libs/ui/lib/store/__fileName__.selector.ts__template__: -------------------------------------------------------------------------------- 1 | export const <%=classComponent%>EntitySelector = ({<%=classComponent%>Entity}: { <%=classComponent%>Entity: string }): string => <%=classComponent%>Entity 2 | -------------------------------------------------------------------------------- /packages/react-elementor/src/generators/plugin/libs/ui/lib/store/__fileName__.store.ts__template__: -------------------------------------------------------------------------------- 1 | import {createSlice} from '@reduxjs/toolkit' 2 | 3 | 4 | const initialState = 'Hello <%=name%>' 5 | 6 | const <%=classComponent%>EntitySlice = createSlice({ 7 | name: '<%=classComponent%>Entity', 8 | initialState, 9 | reducers: { 10 | update<%=classComponent%>Entity: (state, action) => { 11 | return action.payload 12 | }, 13 | } 14 | }) 15 | 16 | export const {update<%=classComponent%>Entity} = <%=classComponent%>EntitySlice.actions 17 | 18 | export const <%=classComponent%>Reducer = <%=classComponent%>EntitySlice.reducer 19 | 20 | -------------------------------------------------------------------------------- /packages/react-elementor/src/generators/plugin/libs/ui/lib/store/index.ts__template__: -------------------------------------------------------------------------------- 1 | export * from './<%=fileName%>.store' 2 | export * from './<%=fileName%>.selector' 3 | -------------------------------------------------------------------------------- /packages/react-elementor/src/generators/plugin/schema.d.ts: -------------------------------------------------------------------------------- 1 | import { SupportedStyles } from '@nx/react'; 2 | import { NormalizedSchema } from '@nx/react/src/generators/application/schema'; 3 | 4 | export interface ElementorPluginGeneratorSchema extends NormalizedSchema { 5 | name: string; 6 | tags?: string; 7 | pluginDescription?: string; 8 | pluginUri?: string; 9 | author?: string; 10 | copyright?: string; 11 | license?: string; 12 | link?: string; 13 | minElementorVersion?: string; 14 | version?: string; 15 | strict?: boolean; 16 | classComponent?: boolean; 17 | unitTestRunner: 'jest' | 'none'; 18 | style: SupportedStyles; 19 | babelJest: boolean; 20 | e2eTestRunner: 'cypress' | 'none'; 21 | npmScope: string; 22 | } 23 | -------------------------------------------------------------------------------- /packages/react-elementor/src/generators/plugin/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/schema", 3 | "id": "ElementorPlugin", 4 | "title": "", 5 | "type": "object", 6 | "properties": { 7 | "name": { 8 | "type": "string", 9 | "description": "", 10 | "$default": { 11 | "$source": "argv", 12 | "index": 0 13 | }, 14 | "x-prompt": "What name would you like to use?" 15 | }, 16 | "tags": { 17 | "type": "string", 18 | "description": "Add tags to the project (used for linting)", 19 | "alias": "t" 20 | }, 21 | "pluginDescription": { 22 | "type": "string", 23 | "description": "Plugin description that appear on wordpress plugin view", 24 | "default": "" 25 | }, 26 | "pluginUri": { 27 | "type": "string", 28 | "description": "Plugin description that appear on wordpress plugin view", 29 | "default": "" 30 | }, 31 | "author": { 32 | "type": "string", 33 | "description": "Plugin description that appear on wordpress plugin view", 34 | "default": "" 35 | }, 36 | "copyright": { 37 | "type": "string", 38 | "description": "Plugin description that appear on wordpress plugin view", 39 | "default": "" 40 | }, 41 | "license": { 42 | "type": "string", 43 | "description": "Plugin description that appear on wordpress plugin view", 44 | "default": "" 45 | }, 46 | "linter": { 47 | "description": "The tool to use for running lint checks.", 48 | "type": "string", 49 | "enum": [ 50 | "eslint", 51 | "tslint" 52 | ], 53 | "default": "eslint" 54 | }, 55 | "skipFormat": { 56 | "description": "Skip formatting files.", 57 | "type": "boolean", 58 | "default": false 59 | }, 60 | "strict": { 61 | "type": "boolean", 62 | "description": "Creates an application with stricter type checking and build optimization options.", 63 | "default": true 64 | }, 65 | "link": { 66 | "type": "string", 67 | "description": "Plugin description that appear on wordpress plugin view", 68 | "default": "" 69 | }, 70 | "minElementorVersion": { 71 | "type": "string", 72 | "description": "Plugin description that appear on wordpress plugin view", 73 | "default": "2.0.0" 74 | }, 75 | "version": { 76 | "type": "string", 77 | "description": "Plugin description that appear on wordpress plugin view", 78 | "default": "1.0.0" 79 | }, 80 | "attributes": { 81 | "type": "array", 82 | "description": "List of the attributes for this web component.", 83 | "default": [] 84 | }, 85 | "style": { 86 | "description": "The file extension to be used for style files.", 87 | "type": "string", 88 | "default": "css", 89 | "alias": "s", 90 | "x-prompt": { 91 | "message": "Which stylesheet format would you like to use?", 92 | "type": "list", 93 | "items": [ 94 | { 95 | "value": "css", 96 | "label": "CSS" 97 | }, 98 | { 99 | "value": "scss", 100 | "label": "SASS(.scss) [ http://sass-lang.com ]" 101 | }, 102 | { 103 | "value": "styl", 104 | "label": "Stylus(.styl) [ http://stylus-lang.com ]" 105 | }, 106 | { 107 | "value": "less", 108 | "label": "LESS [ http://lesscss.org ]" 109 | }, 110 | { 111 | "value": "styled-components", 112 | "label": "styled-components [ https://styled-components.com ]" 113 | }, 114 | { 115 | "value": "@emotion/styled", 116 | "label": "emotion [ https://emotion.sh ]" 117 | }, 118 | { 119 | "value": "styled-jsx", 120 | "label": "styled-jsx [ https://www.npmjs.com/package/styled-jsx ]" 121 | }, 122 | { 123 | "value": "none", 124 | "label": "None" 125 | } 126 | ] 127 | } 128 | } 129 | }, 130 | "required": [ 131 | "name" 132 | ] 133 | } 134 | -------------------------------------------------------------------------------- /packages/react-elementor/src/generators/widget/files/widget.php__template__: -------------------------------------------------------------------------------- 1 | 7 | * @subpackage WordPress 8 | * @author <%= author %> 9 | * @since 1.0.0 10 | * php version 7.3.9 11 | */ 12 | 13 | namespace <%= pluginClassName %>\Widgets; 14 | 15 | use Elementor\Widget_Base; 16 | use Elementor\Controls_Manager; 17 | 18 | // Security Note: Blocks direct access to the plugin PHP files. 19 | defined('ABSPATH') || die(); 20 | 21 | /** 22 | * <%= pluginName %> widget class. 23 | * 24 | * @since 1.0.0 25 | */ 26 | class <%= constantName %> extends Widget_Base 27 | { 28 | /** 29 | * Class constructor. 30 | * 31 | * @param array $data Widget data. 32 | * @param array $args Widget arguments. 33 | */ 34 | public function __construct($data = array(), $args = null) 35 | { 36 | parent::__construct($data, $args); 37 | 38 | #wp_register_style(' <%= pluginFileName %>-styles-vendors', plugins_url('assets/css/2.f87fbcaa.chunk.css', <%= pluginConstantName %>)); 39 | #wp_register_style('<%= pluginFileName %>-styles-main', plugins_url('assets/css/main.6fdcdd19.chunk.css', <%= pluginConstantName %>)); 40 | 41 | } 42 | 43 | public function get_style_depends() 44 | { 45 | return []; //[ '<%= pluginFileName %>-styles-vendors','<%= pluginFileName %>-styles-main' ]; 46 | } 47 | 48 | public function get_script_depends() 49 | { 50 | return ['<%= pluginFileName %>-script-vendor', 51 | '<%= pluginFileName %>-script-main', 52 | '<%= pluginFileName %>-script-runtime']; 53 | } 54 | 55 | /** 56 | * Retrieve the widget name. 57 | * 58 | * @return string Widget name. 59 | * @since 1.0.0 60 | * 61 | * @access public 62 | * 63 | */ 64 | public function get_name() 65 | { 66 | return '<%= fileName %>'; 67 | } 68 | 69 | /** 70 | * Retrieve the widget title. 71 | * 72 | * @return string Widget title. 73 | * @since 1.0.0 74 | * 75 | * @access public 76 | * 77 | */ 78 | public function get_title() 79 | { 80 | return __('<%= name %>', 'elementor-<%= pluginFileName %>'); 81 | } 82 | 83 | /** 84 | * Retrieve the widget icon. 85 | * 86 | * @return string Widget icon. 87 | * @since 1.0.0 88 | * 89 | * @access public 90 | * 91 | */ 92 | public function get_icon() 93 | { 94 | return 'fa'; 95 | } 96 | 97 | /** 98 | * Retrieve the list of categories the widget belongs to. 99 | * 100 | * Used to determine where to display the widget in the editor. 101 | * 102 | * Note that currently Elementor supports only one category. 103 | * When multiple categories passed, Elementor uses the first one. 104 | * 105 | * @return array Widget categories. 106 | * @since 1.0.0 107 | * 108 | * @access public 109 | * 110 | */ 111 | public function get_categories() 112 | { 113 | return array('<%= pluginFileName %>-category'); 114 | } 115 | 116 | /** 117 | * Register the widget controls. 118 | * 119 | * Adds different input fields to allow the user to change and customize the widget settings. 120 | * 121 | * @since 1.0.0 122 | * 123 | * @access protected 124 | */ 125 | protected function _register_controls() 126 | { 127 | $this->start_controls_section( 128 | 'section_content', 129 | array( 130 | 'label' => __( 'Content', 'elementor-<%= pluginFileName %>' ), 131 | ) 132 | ); 133 | 134 | <% parsedAttributes.forEach(function(attribute){ %> 135 | 136 | $this->add_control( 137 | '<%=attribute%>', 138 | array( 139 | 'label' => __('<%=attribute%> label', 'elementor-<%= pluginFileName %>'), 140 | 'type' => Controls_Manager::TEXT, 141 | 'default' => __('My cool <%=attribute%>', 'elementor-<%= pluginFileName %>'), 142 | ) 143 | ); 144 | <% }); %> 145 | 146 | $this->end_controls_section(); 147 | } 148 | 149 | /** 150 | * Render the widget output on the frontend. 151 | * 152 | * Written in PHP and used to generate the final HTML. 153 | * 154 | * @since 1.0.0 155 | * 156 | * @access protected 157 | */ 158 | protected function render() 159 | { 160 | $settings = $this->get_settings_for_display(); 161 | $this->add_render_attribute( 162 | '<%= fileName %>-wrapper', 163 | [ 164 | <% parsedAttributes.forEach(function(attribute){ %> 165 | '<%=attribute%>' => wp_kses( $settings['<%=attribute%>'], array() ), 166 | <% }); %> 167 | ] 168 | ); 169 | ?> 170 | 171 | <<%= fileName %> get_render_attribute_string( '<%= fileName %>-wrapper' ); ?>>> 172 | 187 | <# 188 | view.addRenderAttribute( 189 | '<%= fileName %>-wrapper', 190 | { 191 | <% parsedAttributes.forEach(function(attribute){ %> 192 | '<%=attribute%>': settings.<%=attribute%>, 193 | <% }); %> 194 | } 195 | 196 | ); 197 | #> 198 | 199 | <<%= fileName %> {{{ view.getRenderAttributeString( '<%= fileName %>-wrapper' ) }}}>> 200 | s.trim()) 56 | ? options.attributes?.split(',').map((s) => s.trim()) 57 | : []; 58 | 59 | //widget options 60 | const widgetNames = names(options.name); 61 | const widgetName = options.name; 62 | const widgetFileName = widgetNames.fileName; 63 | const widgetConstantName = widgetNames.constantName; 64 | const widgetClassName = widgetNames.className; 65 | 66 | const widgetRoot = `${plugin.root}/widgets/${widgetNames.fileName}`; 67 | const parsedTags = options.tags 68 | ? options.tags?.split(',').map((s) => s.trim()) 69 | : []; 70 | 71 | const widgets = [widgetFileName]; 72 | const widgetsConstants = [widgetConstantName]; 73 | const widgetsRoot = `${plugin.root}/widgets`; 74 | 75 | const existingWidgets = host 76 | .children(widgetsRoot) 77 | .filter((file) => !host.isFile(`${widgetsRoot}/${file}`)); 78 | existingWidgets.forEach((name) => { 79 | if (widgetConstantName !== names(name).constantName) { 80 | widgets.push(name); 81 | widgetsConstants.push(names(name).constantName); 82 | } 83 | }); 84 | logger.info(`${widgets}`); 85 | return { 86 | ...options, 87 | pluginName, 88 | pluginFileName, 89 | pluginConstantName, 90 | pluginClassName, 91 | pluginDirectory, 92 | pluginRoot, 93 | widgets, 94 | widgetsConstants, 95 | widgetName, 96 | widgetFileName, 97 | widgetConstantName, 98 | widgetClassName, 99 | widgetRoot, 100 | parsedTags, 101 | parsedAttributes, 102 | }; 103 | } 104 | 105 | function addFiles(host: Tree, options: NormalizedSchema) { 106 | const pluginTemplateOptions = { 107 | ...options, 108 | ...names(options.plugin), 109 | ...names(options.name), 110 | uiLibName: `${options.widgetName}-ui`, 111 | storeLibName: `${options.widgetName}-store`, 112 | offsetFromRoot: offsetFromRoot(options.pluginRoot), 113 | template: '', 114 | }; 115 | 116 | const widgetTemplateOptions = { 117 | ...options, 118 | ...names(options.name), 119 | uiLibName: `${options.widgetName}-ui`, 120 | storeLibName: `${options.widgetName}-store`, 121 | offsetFromRoot: offsetFromRoot(options.pluginRoot), 122 | template: '', 123 | }; 124 | 125 | generateFiles( 126 | host, 127 | path.join(__dirname, 'plugin'), 128 | options.pluginRoot, 129 | pluginTemplateOptions 130 | ); 131 | 132 | generateFiles( 133 | host, 134 | path.join(__dirname, 'files'), 135 | options.widgetRoot, 136 | widgetTemplateOptions 137 | ); 138 | } 139 | 140 | export default async function ( 141 | host: Tree, 142 | options: ElementorWidgetGeneratorSchema 143 | ) { 144 | const normalizedOptions = normalizeOptions(host, options); 145 | const widgetProject = getProjects(host).get(normalizedOptions.widgetName); 146 | const projectConfig = { 147 | root: normalizedOptions.widgetRoot, 148 | projectType: 'application', 149 | sourceRoot: `${normalizedOptions.widgetRoot}`, 150 | targets: {}, 151 | tags: normalizedOptions.parsedTags, 152 | } as ProjectConfiguration; 153 | if (!widgetProject) { 154 | addProjectConfiguration(host, normalizedOptions.widgetName, projectConfig); 155 | } else { 156 | updateProjectConfiguration(host, normalizedOptions.widgetName, { 157 | ...widgetProject, 158 | ...projectConfig, 159 | }); 160 | } 161 | 162 | addFiles(host, normalizedOptions); 163 | await formatFiles(host); 164 | } 165 | -------------------------------------------------------------------------------- /packages/react-elementor/src/generators/widget/plugin/class-widgets.php__template__: -------------------------------------------------------------------------------- 1 | class. 4 | * 5 | * @category Class 6 | * @package <%= className %> 7 | * @subpackage WordPress 8 | * @author <%= author %> 9 | * @since 1.0.0 10 | * php version 7.3.9 11 | */ 12 | 13 | namespace <%= pluginClassName %>; 14 | 15 | // Security Note: Blocks direct access to the plugin PHP files. 16 | defined('ABSPATH') || die(); 17 | 18 | /** 19 | * Class Plugin 20 | * 21 | * Main Plugin class 22 | * 23 | * @since 1.0.0 24 | */ 25 | class Widgets 26 | { 27 | 28 | /** 29 | * Instance 30 | * 31 | * @since 1.0.0 32 | * @access private 33 | * @static 34 | * 35 | * @var Plugin The single instance of the class. 36 | */ 37 | private static $instance = null; 38 | 39 | /** 40 | * Instance 41 | * 42 | * Ensures only one instance of the class is loaded or can be loaded. 43 | * 44 | * @return Plugin An instance of the class. 45 | * @since 1.0.0 46 | * @access public 47 | * 48 | */ 49 | public static function instance() 50 | { 51 | if (is_null(self::$instance)) { 52 | self::$instance = new self(); 53 | } 54 | 55 | return self::$instance; 56 | } 57 | 58 | /** 59 | * Include Widgets files 60 | * 61 | * Load widgets files 62 | * 63 | * @since 1.0.0 64 | * @access private 65 | */ 66 | private function include_widgets_files() 67 | { 68 | 69 | <% widgets.forEach(function(widget){ %> 70 | require_once 'widgets/<%= widget %>/widget.php'; 71 | <% }); %> 72 | } 73 | 74 | public function register_categories() 75 | { 76 | \Elementor\Plugin::instance()->elements_manager->add_category( 77 | '<%= fileName %>-category', 78 | array( 79 | 'title' => __('<%= name %> components', '<%= fileName %>'), 80 | 'icon' => 'fa fa-ship', 81 | ) 82 | ); 83 | } 84 | 85 | 86 | /** 87 | * widget_scripts 88 | * 89 | * Load required plugin core files. 90 | * 91 | * @access public 92 | */ 93 | public function widget_scripts() 94 | { 95 | 96 | wp_register_script('<%= pluginFileName %>-script-vendor', plugins_url('dist/vendor.js', __FILE__), array(), 'xxVERSIONxx', true); 97 | wp_register_script('<%= pluginFileName %>-script-main', plugins_url('dist/main.js', __FILE__), array(), 'xxVERSIONxx', true); 98 | wp_register_script('<%= pluginFileName %>-script-runtime', plugins_url('dist/runtime.js', __FILE__), array(), 'xxVERSIONxx', true); 99 | 100 | } 101 | 102 | /** 103 | * Register Widgets 104 | * 105 | * Register new Elementor widgets. 106 | * 107 | * @since 1.0.0 108 | * @access public 109 | */ 110 | public function register_widgets() 111 | { 112 | // It's now safe to include Widgets files. 113 | $this->include_widgets_files(); 114 | 115 | // Register the plugin widget classes. 116 | <% widgetsConstants.forEach(function(widgetConstant){ %> 117 | \Elementor\Plugin::instance()->widgets_manager->register_widget_type(new Widgets\<%= widgetConstant %>()); 118 | <% }); %> 119 | 120 | } 121 | 122 | /** 123 | * Plugin class constructor 124 | * 125 | * Register plugin action hooks and filters 126 | * 127 | * @since 1.0.0 128 | * @access public 129 | */ 130 | public function __construct() 131 | { 132 | // Register the widgets. 133 | add_action('elementor/elements/categories_registered', array($this, 'register_categories')); 134 | add_action('elementor/frontend/after_register_scripts', array($this, 'widget_scripts')); 135 | add_action('elementor/widgets/widgets_registered', array($this, 'register_widgets')); 136 | 137 | } 138 | } 139 | 140 | // Instantiate the Widgets class. 141 | Widgets::instance(); 142 | -------------------------------------------------------------------------------- /packages/react-elementor/src/generators/widget/schema.d.ts: -------------------------------------------------------------------------------- 1 | export interface ElementorWidgetGeneratorSchema { 2 | name: string; 3 | plugin: string; 4 | attributes : string; 5 | tags?: string; 6 | widgetDescription?: string; 7 | author?: string; 8 | license?: string; 9 | link?: string; 10 | version?: string; 11 | } 12 | -------------------------------------------------------------------------------- /packages/react-elementor/src/generators/widget/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/schema", 3 | "id": "ElementorWidget", 4 | "title": "", 5 | "type": "object", 6 | "properties": { 7 | "name": { 8 | "type": "string", 9 | "description": "(Required) name of the Reactjs elementor widget", 10 | "$default": { 11 | "$source": "argv", 12 | "index": 0 13 | }, 14 | "x-prompt": "What name would you like to use for this elementor widget widget?" 15 | }, 16 | "plugin": { 17 | "type": "string", 18 | "description": "(Required) The name of the Wordpress plugin in which the widget will be generated.", 19 | "alias": "p", 20 | "$default": { 21 | "$source": "argv", 22 | "index": 1 23 | }, 24 | "x-prompt": "What is the name the wordpress plugin." 25 | }, 26 | "tags": { 27 | "type": "string", 28 | "description": "Add tags to the project (used for linting)", 29 | "alias": "t" 30 | }, 31 | "author": { 32 | "type": "string", 33 | "description": "Name of who makes this plugin.", 34 | "default": "" 35 | }, 36 | "widgetDescription": { 37 | "type": "string", 38 | "description": "Widget description that appear in wordpress plugin view.", 39 | "default": "" 40 | }, 41 | "attributes": { 42 | "type": "string", 43 | "description": "Widget list of customizable attributes.", 44 | "default": "" 45 | }, 46 | "version": { 47 | "type": "string", 48 | "description": "Wordpress plugin version", 49 | "default": "" 50 | } 51 | }, 52 | "required": [ 53 | "name", 54 | "plugin" 55 | ] 56 | } 57 | -------------------------------------------------------------------------------- /packages/react-elementor/src/img/elementor-widgets.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/betrueagency/nx-reactjs-elementor/714d345e5fac5cf5ab199abe52fdc3441558424c/packages/react-elementor/src/img/elementor-widgets.jpg -------------------------------------------------------------------------------- /packages/react-elementor/src/index.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/betrueagency/nx-reactjs-elementor/714d345e5fac5cf5ab199abe52fdc3441558424c/packages/react-elementor/src/index.ts -------------------------------------------------------------------------------- /packages/react-elementor/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.lib.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /packages/react-elementor/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "outDir": "../../dist/out-tsc", 6 | "esModuleInterop": true, 7 | "declaration": true, 8 | "types": ["node"] 9 | }, 10 | "exclude": ["**/*.spec.ts", "**/*.test.ts", "jest.config.ts"], 11 | "include": ["**/*.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /packages/react-elementor/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": [ 9 | "**/*.spec.ts", 10 | "**/*.test.ts", 11 | "**/*.spec.tsx", 12 | "**/*.test.tsx", 13 | "**/*.spec.js", 14 | "**/*.test.js", 15 | "**/*.spec.jsx", 16 | "**/*.test.jsx", 17 | "**/*.d.ts", 18 | "jest.config.ts" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /tools/tsconfig.tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "../dist/out-tsc/tools", 5 | "rootDir": ".", 6 | "module": "commonjs", 7 | "target": "es5", 8 | "types": ["node"], 9 | "importHelpers": false 10 | }, 11 | "include": ["**/*.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "sourceMap": true, 6 | "declaration": false, 7 | "moduleResolution": "node", 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "importHelpers": true, 11 | "target": "es2015", 12 | "module": "esnext", 13 | "lib": ["es2017", "dom"], 14 | "skipLibCheck": true, 15 | "skipDefaultLibCheck": true, 16 | "baseUrl": ".", 17 | "paths": { 18 | "betrue/react-elementor": ["packages/react-elementor/src/index.ts"] 19 | } 20 | }, 21 | "exclude": ["node_modules", "tmp"] 22 | } 23 | --------------------------------------------------------------------------------