├── .eslintignore ├── .eslintrc ├── .eslintrc.json ├── .github ├── FUNDING.yml └── workflows │ └── wpcs.yml ├── .gitignore ├── Gruntfile.js ├── LICENSE ├── README.md ├── assets └── img │ └── logo.png ├── babel-plugin-macros.config.js ├── babel.config.js ├── blueprints ├── blueprint-export.xml ├── blueprint.json └── files │ ├── christina.png │ ├── dlx-000442@2x-1024x661.png │ ├── dlx-000442@2x-150x150.png │ ├── dlx-000442@2x-1536x992.png │ ├── dlx-000442@2x-300x194.png │ ├── dlx-000442@2x-768x496.png │ ├── dlx-000442@2x.png │ ├── dlx-000443@2x-1024x625.png │ ├── dlx-000443@2x-150x150.png │ ├── dlx-000443@2x-1536x937.png │ ├── dlx-000443@2x-300x183.png │ ├── dlx-000443@2x-768x469.png │ ├── dlx-000443@2x.png │ ├── dlx-000444@2x-1024x490.png │ ├── dlx-000444@2x-150x150.png │ ├── dlx-000444@2x-1536x736.png │ ├── dlx-000444@2x-2048x981.png │ ├── dlx-000444@2x-300x144.png │ ├── dlx-000444@2x-768x368.png │ ├── dlx-000444@2x.png │ ├── dlx-000445@2x-1024x297.png │ ├── dlx-000445@2x-150x150.png │ ├── dlx-000445@2x-1536x446.png │ ├── dlx-000445@2x-2048x595.png │ ├── dlx-000445@2x-300x87.png │ ├── dlx-000445@2x-768x223.png │ ├── dlx-000445@2x.png │ ├── pam.png │ ├── scott.png │ ├── tall-white-gradient-scaled-1-1048x1536.webp │ ├── tall-white-gradient-scaled-1-1398x2048.webp │ ├── tall-white-gradient-scaled-1-150x150.webp │ ├── tall-white-gradient-scaled-1-205x300.webp │ ├── tall-white-gradient-scaled-1-699x1024.webp │ ├── tall-white-gradient-scaled-1-768x1125.webp │ ├── tall-white-gradient-scaled-1.webp │ ├── tall-white-gradient-scaled-2-1048x1536.webp │ ├── tall-white-gradient-scaled-2-1398x2048.webp │ ├── tall-white-gradient-scaled-2-150x150.webp │ ├── tall-white-gradient-scaled-2-205x300.webp │ ├── tall-white-gradient-scaled-2-699x1024.webp │ ├── tall-white-gradient-scaled-2-768x1125.webp │ ├── tall-white-gradient-scaled-2.webp │ ├── tia.png │ ├── white-gradient-bg-1-1024x576.webp │ ├── white-gradient-bg-1-150x150.webp │ ├── white-gradient-bg-1-1536x864.webp │ ├── white-gradient-bg-1-300x169.webp │ ├── white-gradient-bg-1-768x432.webp │ ├── white-gradient-bg-1.webp │ ├── white-gradient-bg-1024x576.webp │ ├── white-gradient-bg-150x150.webp │ ├── white-gradient-bg-1536x864.webp │ ├── white-gradient-bg-300x169.webp │ ├── white-gradient-bg-768x432.webp │ └── white-gradient-bg.webp ├── build ├── dlx-pw-fancybox-rtl.css ├── dlx-pw-fancybox.asset.php ├── dlx-pw-fancybox.css ├── dlx-pw-fancybox.js ├── dlx-pw-preview.asset.php ├── dlx-pw-preview.js ├── index.asset.php ├── index.js └── js │ └── blocks │ └── pattern-importer │ └── block.json ├── composer.json ├── composer.lock ├── lib ├── autoload.php └── composer │ ├── ClassLoader.php │ ├── InstalledVersions.php │ ├── LICENSE │ ├── autoload_classmap.php │ ├── autoload_namespaces.php │ ├── autoload_psr4.php │ ├── autoload_real.php │ ├── autoload_static.php │ ├── installed.json │ └── installed.php ├── package-lock.json ├── package.json ├── pattern-wrangler.php ├── php ├── Admin.php ├── Blocks.php ├── Drafts.php ├── Functions.php ├── Options.php ├── Patterns.php └── Preview.php ├── phpcs.xml.dist ├── readme.txt ├── src ├── index.js ├── js │ ├── blocks │ │ ├── commands │ │ │ └── index.js │ │ ├── components │ │ │ ├── AlertButton │ │ │ │ ├── editor.scss │ │ │ │ └── index.js │ │ │ ├── GBHacksIcon │ │ │ │ └── index.js │ │ │ ├── IconPicker │ │ │ │ ├── editor.scss │ │ │ │ └── index.js │ │ │ ├── icons │ │ │ │ ├── AlertsLogo.js │ │ │ │ ├── BootstrapIcons.js │ │ │ │ ├── BootstrapLogo.js │ │ │ │ ├── ChakraIcons.js │ │ │ │ ├── ChakraUILogo.js │ │ │ │ ├── ContainerLogo.js │ │ │ │ ├── MaterialIcons.js │ │ │ │ ├── MaterialIconsLogo.js │ │ │ │ ├── ReplaceIcon.js │ │ │ │ └── ShoelaceLogo.js │ │ │ └── unit-picker │ │ │ │ ├── editor.scss │ │ │ │ └── index.js │ │ ├── pattern-importer │ │ │ ├── block.js │ │ │ ├── block.json │ │ │ └── index.js │ │ ├── plugins │ │ │ └── pattern-preview.js │ │ ├── sidebar │ │ │ └── index.js │ │ └── utils │ │ │ ├── SendCommand.js │ │ │ └── sanitize-svg │ │ │ └── index.js │ ├── fancybox │ │ └── index.js │ ├── react │ │ ├── components │ │ │ ├── Notice │ │ │ │ └── index.js │ │ │ ├── SaveResetButtons │ │ │ │ └── index.js │ │ │ └── SnackPop │ │ │ │ └── index.js │ │ ├── google-fonts.json │ │ ├── utils │ │ │ └── SendCommand.js │ │ └── views │ │ │ ├── license │ │ │ ├── index.js │ │ │ └── license.js │ │ │ └── main │ │ │ ├── index.js │ │ │ └── main.js │ └── utils │ │ └── index.js └── scss │ ├── admin-utils.scss │ ├── admin.scss │ ├── breakpoints-mixin.scss │ ├── button-resets.scss │ └── common.scss ├── templates └── pattern.php └── webpack.config.js /.eslintignore: -------------------------------------------------------------------------------- 1 | **/*.min.js 2 | **/*.build.js 3 | **/node_modules/** 4 | **/vendor/** 5 | build 6 | dist 7 | coverage 8 | cypress 9 | node_modules 10 | vendor 11 | google-fonts.js 12 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ "plugin:@wordpress/eslint-plugin/recommended" ], 3 | "parserOptions": { 4 | "ecmaVersion": 2017 5 | }, 6 | 7 | "globals": { 8 | "$": true, 9 | "wp":true, 10 | "console":true, 11 | "$uifm":true 12 | }, 13 | "rules": { 14 | "camelcase": "off", 15 | "no-console": "off" 16 | } 17 | } -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@babel/eslint-parser", 3 | "globals": { 4 | "wp": true, 5 | "wpApiSettings": true, 6 | "window": true, 7 | "document": true, 8 | "dlxPatternWranglerLicense": "readonly", 9 | "dlxPatternWranglerAdminUrl": "readonly", 10 | "dlxPatternWranglerPreview": "readonly" 11 | }, 12 | "env": { 13 | "browser": true, 14 | "node": true, 15 | "es6": true 16 | }, 17 | "parserOptions": { 18 | "ecmaVersion": 6, 19 | "sourceType": "module", 20 | "ecmaFeatures": { 21 | "jsx": true, 22 | "experimentalObjectRestSpread": true 23 | }, 24 | "requireConfigFile": false 25 | }, 26 | "extends": ["plugin:@wordpress/eslint-plugin/recommended-with-formatting"], 27 | "plugins": ["react", "jsx-a11y", "jest"], 28 | "settings": { 29 | "react": { 30 | "pragma": "wp" 31 | } 32 | }, 33 | "rules": { 34 | "array-bracket-spacing": ["error", "always"], 35 | "brace-style": ["error", "1tbs"], 36 | "camelcase": 0, 37 | "comma-dangle": ["error", "always-multiline"], 38 | "comma-spacing": "error", 39 | "comma-style": "error", 40 | "computed-property-spacing": ["error", "always"], 41 | "constructor-super": "error", 42 | "dot-notation": "error", 43 | "eol-last": "error", 44 | "eqeqeq": "error", 45 | "func-call-spacing": "error", 46 | "indent": ["error", "tab", { "SwitchCase": 1 }], 47 | "jsx-a11y/label-has-for": [ 48 | "error", 49 | { 50 | "required": "id" 51 | } 52 | ], 53 | "jsx-a11y/media-has-caption": "off", 54 | "jsx-a11y/no-noninteractive-tabindex": "off", 55 | "jsx-a11y/role-has-required-aria-props": "off", 56 | "jsx-quotes": "error", 57 | "key-spacing": "error", 58 | "keyword-spacing": "error", 59 | "lines-around-comment": "off", 60 | "no-alert": "error", 61 | "no-bitwise": "error", 62 | "no-caller": "error", 63 | "no-console": "error", 64 | "no-const-assign": "error", 65 | "no-debugger": "error", 66 | "no-dupe-args": "error", 67 | "no-dupe-class-members": "error", 68 | "no-dupe-keys": "error", 69 | "no-duplicate-case": "error", 70 | "no-duplicate-imports": "error", 71 | "no-else-return": "error", 72 | "no-eval": "error", 73 | "no-extra-semi": "error", 74 | "no-fallthrough": "error", 75 | "no-lonely-if": "error", 76 | "no-mixed-operators": "error", 77 | "no-mixed-spaces-and-tabs": "error", 78 | "no-multiple-empty-lines": ["error", { "max": 1 }], 79 | "no-multi-spaces": "error", 80 | "no-multi-str": "off", 81 | "no-negated-in-lhs": "error", 82 | "no-nested-ternary": "error", 83 | "no-redeclare": "error", 84 | "no-restricted-syntax": [ 85 | "error", 86 | { 87 | "selector": "ImportDeclaration[source.value=/^@wordpress\\u002F.+\\u002F/]", 88 | "message": "Path access on WordPress dependencies is not allowed." 89 | }, 90 | { 91 | "selector": "ImportDeclaration[source.value=/^blocks$/]", 92 | "message": "Use @wordpress/blocks as import path instead." 93 | }, 94 | { 95 | "selector": "ImportDeclaration[source.value=/^components$/]", 96 | "message": "Use @wordpress/components as import path instead." 97 | }, 98 | { 99 | "selector": "ImportDeclaration[source.value=/^date$/]", 100 | "message": "Use @wordpress/date as import path instead." 101 | }, 102 | { 103 | "selector": "ImportDeclaration[source.value=/^editor$/]", 104 | "message": "Use @wordpress/editor as import path instead." 105 | }, 106 | { 107 | "selector": "ImportDeclaration[source.value=/^element$/]", 108 | "message": "Use @wordpress/element as import path instead." 109 | }, 110 | { 111 | "selector": "ImportDeclaration[source.value=/^i18n$/]", 112 | "message": "Use @wordpress/i18n as import path instead." 113 | }, 114 | { 115 | "selector": "ImportDeclaration[source.value=/^data$/]", 116 | "message": "Use @wordpress/data as import path instead." 117 | }, 118 | { 119 | "selector": "ImportDeclaration[source.value=/^utils$/]", 120 | "message": "Use @wordpress/utils as import path instead." 121 | }, 122 | { 123 | "selector": "CallExpression[callee.name=/^__|_n|_x$/]:not([arguments.0.type=/^Literal|BinaryExpression$/])", 124 | "message": "Translate function arguments must be string literals." 125 | }, 126 | { 127 | "selector": "CallExpression[callee.name=/^_n|_x$/]:not([arguments.1.type=/^Literal|BinaryExpression$/])", 128 | "message": "Translate function arguments must be string literals." 129 | }, 130 | { 131 | "selector": "CallExpression[callee.name=_nx]:not([arguments.2.type=/^Literal|BinaryExpression$/])", 132 | "message": "Translate function arguments must be string literals." 133 | } 134 | ], 135 | "no-shadow": "error", 136 | "no-undef": "error", 137 | "no-undef-init": "error", 138 | "no-unreachable": "error", 139 | "no-unsafe-negation": "error", 140 | "no-unused-expressions": "error", 141 | "no-unused-vars": "error", 142 | "no-useless-computed-key": "error", 143 | "no-useless-constructor": "error", 144 | "no-useless-return": "error", 145 | "no-var": "error", 146 | "no-whitespace-before-property": "error", 147 | "object-curly-spacing": ["error", "always"], 148 | "padded-blocks": ["error", "never"], 149 | "prefer-const": "error", 150 | "quote-props": ["error", "as-needed"], 151 | "react/display-name": "off", 152 | "react/jsx-curly-spacing": [ 153 | "error", 154 | { 155 | "when": "always", 156 | "children": true 157 | } 158 | ], 159 | "react/jsx-equals-spacing": "error", 160 | "react/jsx-indent": ["error", "tab"], 161 | "react/jsx-indent-props": ["error", "tab"], 162 | "react/jsx-key": "error", 163 | "react/jsx-tag-spacing": "error", 164 | "react/no-children-prop": "off", 165 | "react/no-find-dom-node": "warn", 166 | "react/prop-types": "off", 167 | "semi": "error", 168 | "semi-spacing": "error", 169 | "space-before-blocks": ["error", "always"], 170 | "space-before-function-paren": ["error", "never"], 171 | "space-in-parens": ["error", "always"], 172 | "space-infix-ops": ["error", { "int32Hint": false }], 173 | "space-unary-ops": [ 174 | "error", 175 | { 176 | "overrides": { 177 | "!": true 178 | } 179 | } 180 | ], 181 | "template-curly-spacing": ["error", "always"], 182 | "valid-jsdoc": ["error", { "requireReturn": false }], 183 | "valid-typeof": "error", 184 | "yoda": "off" 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: ronalfy 4 | -------------------------------------------------------------------------------- /.github/workflows/wpcs.yml: -------------------------------------------------------------------------------- 1 | name: WPCS check 2 | 3 | on: pull_request 4 | 5 | jobs: 6 | phpcs: 7 | name: WPCS 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v3 11 | - name: WPCS check 12 | uses: 10up/wpcs-action@stable 13 | with: 14 | enable_warnings: false # Enable checking for warnings (-w) 15 | paths: '.' # Paths to check, space separated 16 | excludes: '' # Paths to excludes, space separated 17 | standard: 'WordPress' # Standard to use. Accepts WordPress|WordPress-Core|WordPress-Docs|WordPress-Extra|WordPress-VIP-Go|WordPressVIPMinimum|10up-Default. 18 | standard_repo: '' # Public (git) repository URL of the coding standard 19 | repo_branch: 'master' # Branch of Standard repository 20 | phpcs_bin_path: 'phpcs' # Custom PHPCS bin path 21 | use_local_config: 'true' # Use local config if available 22 | extra_args: '--report-json=./phpcs.json' 23 | - name: Update summary 24 | run: | 25 | npm i -g github:10up/phpcs-json-to-md 26 | phpcs-json-to-md --path ./phpcs.json --output ./phpcs.md 27 | cat phpcs.md >> $GITHUB_STEP_SUMMARY 28 | if: always() -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* 131 | 132 | dlx-pattern-wrangler.zip 133 | 134 | pattern-wrangler.zip 135 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function( grunt ) { 2 | grunt.initConfig( { 3 | compress: { 4 | main: { 5 | options: { 6 | archive: 'pattern-wrangler.zip', 7 | }, 8 | files: [ 9 | { src: [ 'pattern-wrangler.php' ], dest: '/', filter: 'isFile' }, 10 | { src: [ 'readme.txt' ], dest: '/', filter: 'isFile' }, 11 | { src: [ 'assets/**' ], dest: '/' }, 12 | { src: [ 'build/**' ], dest: '/' }, 13 | { src: [ 'dist/**' ], dest: '/' }, 14 | { src: [ 'php/**' ], dest: '/' }, 15 | { src: [ 'templates/**' ], dest: '/' }, 16 | { src: [ 'lib/**' ], dest: '/' }, 17 | ], 18 | }, 19 | }, 20 | } ); 21 | grunt.registerTask( 'default', [ 'compress' ] ); 22 | 23 | grunt.loadNpmTasks( 'grunt-contrib-compress' ); 24 | }; 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Welcome to Pattern Wrangler 2 | 3 | Pattern Wrangler is a tool to get your WordPress patterns under control, whether that means hiding core and remote patterns or mapping pattern categories. 4 | 5 | ![Pattern Wrangler Featured](https://github.com/DLXPlugins/dlx-pattern-wrangler/assets/636521/02e6b011-9c63-495f-b65c-ee4824b859de) 6 | 7 | 8 | 9 | ## Download Zip 10 | The latest version can be found on WordPress.org. 11 | 12 | ## Quick Links 13 | 14 | * Docs and Features. 15 | * Create an Issue or Feature Request. 16 | * Leave a Support Request. 17 | * Visit the Plugin's Marketing Page. 18 | 19 | ## Developers 20 | 21 | 1. Clone the `main` branch locally. 22 | 2. Run `npm install` to install the development scripts. 23 | 3. Run `npm run start` to start the build scripts. 24 | 4. Run `npm run build` to do a production build. 25 | 5. Run `grunt` to create a plugin ZIP file. 26 | 27 | Perform a Pull Request against the `main` branch. 28 | 29 | -------------------------------------------------------------------------------- /assets/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/assets/img/logo.png -------------------------------------------------------------------------------- /babel-plugin-macros.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'fontawesome-svg-core': { 3 | license: 'pro', 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function( api ) { 2 | api.cache.never(); 3 | return { 4 | plugins: [ 5 | 'macros', 6 | '@babel/plugin-transform-class-properties', 7 | '@babel/plugin-transform-arrow-functions', 8 | ], 9 | presets: [ '@babel/preset-env', '@babel/preset-react' ], 10 | }; 11 | }; 12 | -------------------------------------------------------------------------------- /blueprints/files/christina.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/christina.png -------------------------------------------------------------------------------- /blueprints/files/dlx-000442@2x-1024x661.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/dlx-000442@2x-1024x661.png -------------------------------------------------------------------------------- /blueprints/files/dlx-000442@2x-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/dlx-000442@2x-150x150.png -------------------------------------------------------------------------------- /blueprints/files/dlx-000442@2x-1536x992.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/dlx-000442@2x-1536x992.png -------------------------------------------------------------------------------- /blueprints/files/dlx-000442@2x-300x194.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/dlx-000442@2x-300x194.png -------------------------------------------------------------------------------- /blueprints/files/dlx-000442@2x-768x496.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/dlx-000442@2x-768x496.png -------------------------------------------------------------------------------- /blueprints/files/dlx-000442@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/dlx-000442@2x.png -------------------------------------------------------------------------------- /blueprints/files/dlx-000443@2x-1024x625.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/dlx-000443@2x-1024x625.png -------------------------------------------------------------------------------- /blueprints/files/dlx-000443@2x-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/dlx-000443@2x-150x150.png -------------------------------------------------------------------------------- /blueprints/files/dlx-000443@2x-1536x937.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/dlx-000443@2x-1536x937.png -------------------------------------------------------------------------------- /blueprints/files/dlx-000443@2x-300x183.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/dlx-000443@2x-300x183.png -------------------------------------------------------------------------------- /blueprints/files/dlx-000443@2x-768x469.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/dlx-000443@2x-768x469.png -------------------------------------------------------------------------------- /blueprints/files/dlx-000443@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/dlx-000443@2x.png -------------------------------------------------------------------------------- /blueprints/files/dlx-000444@2x-1024x490.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/dlx-000444@2x-1024x490.png -------------------------------------------------------------------------------- /blueprints/files/dlx-000444@2x-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/dlx-000444@2x-150x150.png -------------------------------------------------------------------------------- /blueprints/files/dlx-000444@2x-1536x736.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/dlx-000444@2x-1536x736.png -------------------------------------------------------------------------------- /blueprints/files/dlx-000444@2x-2048x981.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/dlx-000444@2x-2048x981.png -------------------------------------------------------------------------------- /blueprints/files/dlx-000444@2x-300x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/dlx-000444@2x-300x144.png -------------------------------------------------------------------------------- /blueprints/files/dlx-000444@2x-768x368.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/dlx-000444@2x-768x368.png -------------------------------------------------------------------------------- /blueprints/files/dlx-000444@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/dlx-000444@2x.png -------------------------------------------------------------------------------- /blueprints/files/dlx-000445@2x-1024x297.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/dlx-000445@2x-1024x297.png -------------------------------------------------------------------------------- /blueprints/files/dlx-000445@2x-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/dlx-000445@2x-150x150.png -------------------------------------------------------------------------------- /blueprints/files/dlx-000445@2x-1536x446.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/dlx-000445@2x-1536x446.png -------------------------------------------------------------------------------- /blueprints/files/dlx-000445@2x-2048x595.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/dlx-000445@2x-2048x595.png -------------------------------------------------------------------------------- /blueprints/files/dlx-000445@2x-300x87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/dlx-000445@2x-300x87.png -------------------------------------------------------------------------------- /blueprints/files/dlx-000445@2x-768x223.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/dlx-000445@2x-768x223.png -------------------------------------------------------------------------------- /blueprints/files/dlx-000445@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/dlx-000445@2x.png -------------------------------------------------------------------------------- /blueprints/files/pam.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/pam.png -------------------------------------------------------------------------------- /blueprints/files/scott.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/scott.png -------------------------------------------------------------------------------- /blueprints/files/tall-white-gradient-scaled-1-1048x1536.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/tall-white-gradient-scaled-1-1048x1536.webp -------------------------------------------------------------------------------- /blueprints/files/tall-white-gradient-scaled-1-1398x2048.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/tall-white-gradient-scaled-1-1398x2048.webp -------------------------------------------------------------------------------- /blueprints/files/tall-white-gradient-scaled-1-150x150.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/tall-white-gradient-scaled-1-150x150.webp -------------------------------------------------------------------------------- /blueprints/files/tall-white-gradient-scaled-1-205x300.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/tall-white-gradient-scaled-1-205x300.webp -------------------------------------------------------------------------------- /blueprints/files/tall-white-gradient-scaled-1-699x1024.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/tall-white-gradient-scaled-1-699x1024.webp -------------------------------------------------------------------------------- /blueprints/files/tall-white-gradient-scaled-1-768x1125.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/tall-white-gradient-scaled-1-768x1125.webp -------------------------------------------------------------------------------- /blueprints/files/tall-white-gradient-scaled-1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/tall-white-gradient-scaled-1.webp -------------------------------------------------------------------------------- /blueprints/files/tall-white-gradient-scaled-2-1048x1536.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/tall-white-gradient-scaled-2-1048x1536.webp -------------------------------------------------------------------------------- /blueprints/files/tall-white-gradient-scaled-2-1398x2048.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/tall-white-gradient-scaled-2-1398x2048.webp -------------------------------------------------------------------------------- /blueprints/files/tall-white-gradient-scaled-2-150x150.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/tall-white-gradient-scaled-2-150x150.webp -------------------------------------------------------------------------------- /blueprints/files/tall-white-gradient-scaled-2-205x300.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/tall-white-gradient-scaled-2-205x300.webp -------------------------------------------------------------------------------- /blueprints/files/tall-white-gradient-scaled-2-699x1024.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/tall-white-gradient-scaled-2-699x1024.webp -------------------------------------------------------------------------------- /blueprints/files/tall-white-gradient-scaled-2-768x1125.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/tall-white-gradient-scaled-2-768x1125.webp -------------------------------------------------------------------------------- /blueprints/files/tall-white-gradient-scaled-2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/tall-white-gradient-scaled-2.webp -------------------------------------------------------------------------------- /blueprints/files/tia.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/tia.png -------------------------------------------------------------------------------- /blueprints/files/white-gradient-bg-1-1024x576.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/white-gradient-bg-1-1024x576.webp -------------------------------------------------------------------------------- /blueprints/files/white-gradient-bg-1-150x150.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/white-gradient-bg-1-150x150.webp -------------------------------------------------------------------------------- /blueprints/files/white-gradient-bg-1-1536x864.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/white-gradient-bg-1-1536x864.webp -------------------------------------------------------------------------------- /blueprints/files/white-gradient-bg-1-300x169.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/white-gradient-bg-1-300x169.webp -------------------------------------------------------------------------------- /blueprints/files/white-gradient-bg-1-768x432.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/white-gradient-bg-1-768x432.webp -------------------------------------------------------------------------------- /blueprints/files/white-gradient-bg-1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/white-gradient-bg-1.webp -------------------------------------------------------------------------------- /blueprints/files/white-gradient-bg-1024x576.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/white-gradient-bg-1024x576.webp -------------------------------------------------------------------------------- /blueprints/files/white-gradient-bg-150x150.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/white-gradient-bg-150x150.webp -------------------------------------------------------------------------------- /blueprints/files/white-gradient-bg-1536x864.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/white-gradient-bg-1536x864.webp -------------------------------------------------------------------------------- /blueprints/files/white-gradient-bg-300x169.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/white-gradient-bg-300x169.webp -------------------------------------------------------------------------------- /blueprints/files/white-gradient-bg-768x432.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/white-gradient-bg-768x432.webp -------------------------------------------------------------------------------- /blueprints/files/white-gradient-bg.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DLXPlugins/pattern-wrangler/690d5926627a0109d2c8f47f0b28c7e0174b517a/blueprints/files/white-gradient-bg.webp -------------------------------------------------------------------------------- /build/dlx-pw-fancybox.asset.php: -------------------------------------------------------------------------------- 1 | array(), 'version' => 'bad5a743eede878200a1'); 2 | -------------------------------------------------------------------------------- /build/dlx-pw-preview.asset.php: -------------------------------------------------------------------------------- 1 | array('wp-data', 'wp-editor', 'wp-i18n', 'wp-plugins'), 'version' => '9b6d4a720612ffcb16cd'); 2 | -------------------------------------------------------------------------------- /build/dlx-pw-preview.js: -------------------------------------------------------------------------------- 1 | (()=>{"use strict";var e={656:e=>{e.exports=window.wp.editor}},r={};const n=window.wp.i18n,t=window.wp.plugins;var i;window.wp.data;try{var a=function n(t){var i=r[t];if(void 0!==i)return i.exports;var a=r[t]={exports:{}};return e[t](a,a.exports,n),a.exports}(656).PluginPreviewMenuItem;i=a}catch(e){}i&&(0,t.registerPlugin)("dlx-pattern-wrangler-preview-button",{render:function(){return i?React.createElement(i,{icon:"external",label:(0,n.__)("Preview Pattern","pattern-wrangler"),onClick:function(){window.open(dlxPatternWranglerPreview.previewUrl,"_blank")}},(0,n.__)("Preview Pattern","pattern-wrangler")):null}})})(); -------------------------------------------------------------------------------- /build/index.asset.php: -------------------------------------------------------------------------------- 1 | array('react', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-data', 'wp-i18n'), 'version' => 'fb0b42dfb9db277c7e2c'); 2 | -------------------------------------------------------------------------------- /build/js/blocks/pattern-importer/block.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schemas.wp.org/trunk/block.json", 3 | "title": "Pattern Inserter", 4 | "apiVersion": 3, 5 | "name": "dlxplugins/dlx-pw-pattern-inserter", 6 | "category": "design", 7 | "icon": "", 8 | "description": "Paste in a pattern and it will be inserted for you.", 9 | "keywords": [ 10 | "remote", 11 | "pattern", 12 | "inserter" 13 | ], 14 | "version": "1.0.0", 15 | "textdomain": "dlx-pattern-wrangler", 16 | "attributes": { 17 | "preview": { 18 | "type": "boolean", 19 | "default": false 20 | } 21 | }, 22 | "example": { 23 | "attributes": { 24 | "preview": true 25 | } 26 | }, 27 | "editorScript": "dlx-pw-pattern-inserter-block" 28 | } -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dlxplugins/pattern-wrangler", 3 | "description": "Manage your block patterns.", 4 | "type": "project", 5 | "license": "GPL", 6 | "authors": [ 7 | { 8 | "name": "Ronald Huereca", 9 | "email": "ronald@dlxplugins.com" 10 | } 11 | ], 12 | "minimum-stability": "dev", 13 | "config": { 14 | "vendor-dir": "lib" 15 | }, 16 | "autoload": { 17 | "psr-4": { 18 | "DLXPlugins\\PatternWrangler\\": "php/" 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "d9c0175fe793c24327118b4eeb6596b7", 8 | "packages": [], 9 | "packages-dev": [], 10 | "aliases": [], 11 | "minimum-stability": "dev", 12 | "stability-flags": [], 13 | "prefer-stable": false, 14 | "prefer-lowest": false, 15 | "platform": [], 16 | "platform-dev": [], 17 | "plugin-api-version": "2.0.0" 18 | } 19 | -------------------------------------------------------------------------------- /lib/autoload.php: -------------------------------------------------------------------------------- 1 | 7 | * Jordi Boggiano 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Composer\Autoload; 14 | 15 | /** 16 | * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. 17 | * 18 | * $loader = new \Composer\Autoload\ClassLoader(); 19 | * 20 | * // register classes with namespaces 21 | * $loader->add('Symfony\Component', __DIR__.'/component'); 22 | * $loader->add('Symfony', __DIR__.'/framework'); 23 | * 24 | * // activate the autoloader 25 | * $loader->register(); 26 | * 27 | * // to enable searching the include path (eg. for PEAR packages) 28 | * $loader->setUseIncludePath(true); 29 | * 30 | * In this example, if you try to use a class in the Symfony\Component 31 | * namespace or one of its children (Symfony\Component\Console for instance), 32 | * the autoloader will first look for the class under the component/ 33 | * directory, and it will then fallback to the framework/ directory if not 34 | * found before giving up. 35 | * 36 | * This class is loosely based on the Symfony UniversalClassLoader. 37 | * 38 | * @author Fabien Potencier 39 | * @author Jordi Boggiano 40 | * @see https://www.php-fig.org/psr/psr-0/ 41 | * @see https://www.php-fig.org/psr/psr-4/ 42 | */ 43 | class ClassLoader 44 | { 45 | private $vendorDir; 46 | 47 | // PSR-4 48 | private $prefixLengthsPsr4 = array(); 49 | private $prefixDirsPsr4 = array(); 50 | private $fallbackDirsPsr4 = array(); 51 | 52 | // PSR-0 53 | private $prefixesPsr0 = array(); 54 | private $fallbackDirsPsr0 = array(); 55 | 56 | private $useIncludePath = false; 57 | private $classMap = array(); 58 | private $classMapAuthoritative = false; 59 | private $missingClasses = array(); 60 | private $apcuPrefix; 61 | 62 | private static $registeredLoaders = array(); 63 | 64 | public function __construct($vendorDir = null) 65 | { 66 | $this->vendorDir = $vendorDir; 67 | } 68 | 69 | public function getPrefixes() 70 | { 71 | if (!empty($this->prefixesPsr0)) { 72 | return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); 73 | } 74 | 75 | return array(); 76 | } 77 | 78 | public function getPrefixesPsr4() 79 | { 80 | return $this->prefixDirsPsr4; 81 | } 82 | 83 | public function getFallbackDirs() 84 | { 85 | return $this->fallbackDirsPsr0; 86 | } 87 | 88 | public function getFallbackDirsPsr4() 89 | { 90 | return $this->fallbackDirsPsr4; 91 | } 92 | 93 | public function getClassMap() 94 | { 95 | return $this->classMap; 96 | } 97 | 98 | /** 99 | * @param array $classMap Class to filename map 100 | */ 101 | public function addClassMap(array $classMap) 102 | { 103 | if ($this->classMap) { 104 | $this->classMap = array_merge($this->classMap, $classMap); 105 | } else { 106 | $this->classMap = $classMap; 107 | } 108 | } 109 | 110 | /** 111 | * Registers a set of PSR-0 directories for a given prefix, either 112 | * appending or prepending to the ones previously set for this prefix. 113 | * 114 | * @param string $prefix The prefix 115 | * @param array|string $paths The PSR-0 root directories 116 | * @param bool $prepend Whether to prepend the directories 117 | */ 118 | public function add($prefix, $paths, $prepend = false) 119 | { 120 | if (!$prefix) { 121 | if ($prepend) { 122 | $this->fallbackDirsPsr0 = array_merge( 123 | (array) $paths, 124 | $this->fallbackDirsPsr0 125 | ); 126 | } else { 127 | $this->fallbackDirsPsr0 = array_merge( 128 | $this->fallbackDirsPsr0, 129 | (array) $paths 130 | ); 131 | } 132 | 133 | return; 134 | } 135 | 136 | $first = $prefix[0]; 137 | if (!isset($this->prefixesPsr0[$first][$prefix])) { 138 | $this->prefixesPsr0[$first][$prefix] = (array) $paths; 139 | 140 | return; 141 | } 142 | if ($prepend) { 143 | $this->prefixesPsr0[$first][$prefix] = array_merge( 144 | (array) $paths, 145 | $this->prefixesPsr0[$first][$prefix] 146 | ); 147 | } else { 148 | $this->prefixesPsr0[$first][$prefix] = array_merge( 149 | $this->prefixesPsr0[$first][$prefix], 150 | (array) $paths 151 | ); 152 | } 153 | } 154 | 155 | /** 156 | * Registers a set of PSR-4 directories for a given namespace, either 157 | * appending or prepending to the ones previously set for this namespace. 158 | * 159 | * @param string $prefix The prefix/namespace, with trailing '\\' 160 | * @param array|string $paths The PSR-4 base directories 161 | * @param bool $prepend Whether to prepend the directories 162 | * 163 | * @throws \InvalidArgumentException 164 | */ 165 | public function addPsr4($prefix, $paths, $prepend = false) 166 | { 167 | if (!$prefix) { 168 | // Register directories for the root namespace. 169 | if ($prepend) { 170 | $this->fallbackDirsPsr4 = array_merge( 171 | (array) $paths, 172 | $this->fallbackDirsPsr4 173 | ); 174 | } else { 175 | $this->fallbackDirsPsr4 = array_merge( 176 | $this->fallbackDirsPsr4, 177 | (array) $paths 178 | ); 179 | } 180 | } elseif (!isset($this->prefixDirsPsr4[$prefix])) { 181 | // Register directories for a new namespace. 182 | $length = strlen($prefix); 183 | if ('\\' !== $prefix[$length - 1]) { 184 | throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 185 | } 186 | $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 187 | $this->prefixDirsPsr4[$prefix] = (array) $paths; 188 | } elseif ($prepend) { 189 | // Prepend directories for an already registered namespace. 190 | $this->prefixDirsPsr4[$prefix] = array_merge( 191 | (array) $paths, 192 | $this->prefixDirsPsr4[$prefix] 193 | ); 194 | } else { 195 | // Append directories for an already registered namespace. 196 | $this->prefixDirsPsr4[$prefix] = array_merge( 197 | $this->prefixDirsPsr4[$prefix], 198 | (array) $paths 199 | ); 200 | } 201 | } 202 | 203 | /** 204 | * Registers a set of PSR-0 directories for a given prefix, 205 | * replacing any others previously set for this prefix. 206 | * 207 | * @param string $prefix The prefix 208 | * @param array|string $paths The PSR-0 base directories 209 | */ 210 | public function set($prefix, $paths) 211 | { 212 | if (!$prefix) { 213 | $this->fallbackDirsPsr0 = (array) $paths; 214 | } else { 215 | $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; 216 | } 217 | } 218 | 219 | /** 220 | * Registers a set of PSR-4 directories for a given namespace, 221 | * replacing any others previously set for this namespace. 222 | * 223 | * @param string $prefix The prefix/namespace, with trailing '\\' 224 | * @param array|string $paths The PSR-4 base directories 225 | * 226 | * @throws \InvalidArgumentException 227 | */ 228 | public function setPsr4($prefix, $paths) 229 | { 230 | if (!$prefix) { 231 | $this->fallbackDirsPsr4 = (array) $paths; 232 | } else { 233 | $length = strlen($prefix); 234 | if ('\\' !== $prefix[$length - 1]) { 235 | throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 236 | } 237 | $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 238 | $this->prefixDirsPsr4[$prefix] = (array) $paths; 239 | } 240 | } 241 | 242 | /** 243 | * Turns on searching the include path for class files. 244 | * 245 | * @param bool $useIncludePath 246 | */ 247 | public function setUseIncludePath($useIncludePath) 248 | { 249 | $this->useIncludePath = $useIncludePath; 250 | } 251 | 252 | /** 253 | * Can be used to check if the autoloader uses the include path to check 254 | * for classes. 255 | * 256 | * @return bool 257 | */ 258 | public function getUseIncludePath() 259 | { 260 | return $this->useIncludePath; 261 | } 262 | 263 | /** 264 | * Turns off searching the prefix and fallback directories for classes 265 | * that have not been registered with the class map. 266 | * 267 | * @param bool $classMapAuthoritative 268 | */ 269 | public function setClassMapAuthoritative($classMapAuthoritative) 270 | { 271 | $this->classMapAuthoritative = $classMapAuthoritative; 272 | } 273 | 274 | /** 275 | * Should class lookup fail if not found in the current class map? 276 | * 277 | * @return bool 278 | */ 279 | public function isClassMapAuthoritative() 280 | { 281 | return $this->classMapAuthoritative; 282 | } 283 | 284 | /** 285 | * APCu prefix to use to cache found/not-found classes, if the extension is enabled. 286 | * 287 | * @param string|null $apcuPrefix 288 | */ 289 | public function setApcuPrefix($apcuPrefix) 290 | { 291 | $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; 292 | } 293 | 294 | /** 295 | * The APCu prefix in use, or null if APCu caching is not enabled. 296 | * 297 | * @return string|null 298 | */ 299 | public function getApcuPrefix() 300 | { 301 | return $this->apcuPrefix; 302 | } 303 | 304 | /** 305 | * Registers this instance as an autoloader. 306 | * 307 | * @param bool $prepend Whether to prepend the autoloader or not 308 | */ 309 | public function register($prepend = false) 310 | { 311 | spl_autoload_register(array($this, 'loadClass'), true, $prepend); 312 | 313 | if (null === $this->vendorDir) { 314 | //no-op 315 | } elseif ($prepend) { 316 | self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; 317 | } else { 318 | unset(self::$registeredLoaders[$this->vendorDir]); 319 | self::$registeredLoaders[$this->vendorDir] = $this; 320 | } 321 | } 322 | 323 | /** 324 | * Unregisters this instance as an autoloader. 325 | */ 326 | public function unregister() 327 | { 328 | spl_autoload_unregister(array($this, 'loadClass')); 329 | 330 | if (null !== $this->vendorDir) { 331 | unset(self::$registeredLoaders[$this->vendorDir]); 332 | } 333 | } 334 | 335 | /** 336 | * Loads the given class or interface. 337 | * 338 | * @param string $class The name of the class 339 | * @return bool|null True if loaded, null otherwise 340 | */ 341 | public function loadClass($class) 342 | { 343 | if ($file = $this->findFile($class)) { 344 | includeFile($file); 345 | 346 | return true; 347 | } 348 | } 349 | 350 | /** 351 | * Finds the path to the file where the class is defined. 352 | * 353 | * @param string $class The name of the class 354 | * 355 | * @return string|false The path if found, false otherwise 356 | */ 357 | public function findFile($class) 358 | { 359 | // class map lookup 360 | if (isset($this->classMap[$class])) { 361 | return $this->classMap[$class]; 362 | } 363 | if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { 364 | return false; 365 | } 366 | if (null !== $this->apcuPrefix) { 367 | $file = apcu_fetch($this->apcuPrefix.$class, $hit); 368 | if ($hit) { 369 | return $file; 370 | } 371 | } 372 | 373 | $file = $this->findFileWithExtension($class, '.php'); 374 | 375 | // Search for Hack files if we are running on HHVM 376 | if (false === $file && defined('HHVM_VERSION')) { 377 | $file = $this->findFileWithExtension($class, '.hh'); 378 | } 379 | 380 | if (null !== $this->apcuPrefix) { 381 | apcu_add($this->apcuPrefix.$class, $file); 382 | } 383 | 384 | if (false === $file) { 385 | // Remember that this class does not exist. 386 | $this->missingClasses[$class] = true; 387 | } 388 | 389 | return $file; 390 | } 391 | 392 | /** 393 | * Returns the currently registered loaders indexed by their corresponding vendor directories. 394 | * 395 | * @return self[] 396 | */ 397 | public static function getRegisteredLoaders() 398 | { 399 | return self::$registeredLoaders; 400 | } 401 | 402 | private function findFileWithExtension($class, $ext) 403 | { 404 | // PSR-4 lookup 405 | $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; 406 | 407 | $first = $class[0]; 408 | if (isset($this->prefixLengthsPsr4[$first])) { 409 | $subPath = $class; 410 | while (false !== $lastPos = strrpos($subPath, '\\')) { 411 | $subPath = substr($subPath, 0, $lastPos); 412 | $search = $subPath . '\\'; 413 | if (isset($this->prefixDirsPsr4[$search])) { 414 | $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); 415 | foreach ($this->prefixDirsPsr4[$search] as $dir) { 416 | if (file_exists($file = $dir . $pathEnd)) { 417 | return $file; 418 | } 419 | } 420 | } 421 | } 422 | } 423 | 424 | // PSR-4 fallback dirs 425 | foreach ($this->fallbackDirsPsr4 as $dir) { 426 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { 427 | return $file; 428 | } 429 | } 430 | 431 | // PSR-0 lookup 432 | if (false !== $pos = strrpos($class, '\\')) { 433 | // namespaced class name 434 | $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) 435 | . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); 436 | } else { 437 | // PEAR-like class name 438 | $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; 439 | } 440 | 441 | if (isset($this->prefixesPsr0[$first])) { 442 | foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { 443 | if (0 === strpos($class, $prefix)) { 444 | foreach ($dirs as $dir) { 445 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 446 | return $file; 447 | } 448 | } 449 | } 450 | } 451 | } 452 | 453 | // PSR-0 fallback dirs 454 | foreach ($this->fallbackDirsPsr0 as $dir) { 455 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 456 | return $file; 457 | } 458 | } 459 | 460 | // PSR-0 include paths. 461 | if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { 462 | return $file; 463 | } 464 | 465 | return false; 466 | } 467 | } 468 | 469 | /** 470 | * Scope isolated include. 471 | * 472 | * Prevents access to $this/self from included files. 473 | */ 474 | function includeFile($file) 475 | { 476 | include $file; 477 | } 478 | -------------------------------------------------------------------------------- /lib/composer/InstalledVersions.php: -------------------------------------------------------------------------------- 1 | 27 | array ( 28 | 'pretty_version' => 'dev-main', 29 | 'version' => 'dev-main', 30 | 'aliases' => 31 | array ( 32 | ), 33 | 'reference' => '98fdd65c94cacda5c9c5206215cac2130140a5db', 34 | 'name' => 'dlxplugins/pattern-wrangler', 35 | ), 36 | 'versions' => 37 | array ( 38 | 'dlxplugins/pattern-wrangler' => 39 | array ( 40 | 'pretty_version' => 'dev-main', 41 | 'version' => 'dev-main', 42 | 'aliases' => 43 | array ( 44 | ), 45 | 'reference' => '98fdd65c94cacda5c9c5206215cac2130140a5db', 46 | ), 47 | ), 48 | ); 49 | private static $canGetVendors; 50 | private static $installedByVendor = array(); 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | public static function getInstalledPackages() 59 | { 60 | $packages = array(); 61 | foreach (self::getInstalled() as $installed) { 62 | $packages[] = array_keys($installed['versions']); 63 | } 64 | 65 | 66 | if (1 === \count($packages)) { 67 | return $packages[0]; 68 | } 69 | 70 | return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); 71 | } 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | public static function isInstalled($packageName) 82 | { 83 | foreach (self::getInstalled() as $installed) { 84 | if (isset($installed['versions'][$packageName])) { 85 | return true; 86 | } 87 | } 88 | 89 | return false; 90 | } 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | public static function satisfies(VersionParser $parser, $packageName, $constraint) 106 | { 107 | $constraint = $parser->parseConstraints($constraint); 108 | $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); 109 | 110 | return $provided->matches($constraint); 111 | } 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | public static function getVersionRanges($packageName) 123 | { 124 | foreach (self::getInstalled() as $installed) { 125 | if (!isset($installed['versions'][$packageName])) { 126 | continue; 127 | } 128 | 129 | $ranges = array(); 130 | if (isset($installed['versions'][$packageName]['pretty_version'])) { 131 | $ranges[] = $installed['versions'][$packageName]['pretty_version']; 132 | } 133 | if (array_key_exists('aliases', $installed['versions'][$packageName])) { 134 | $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); 135 | } 136 | if (array_key_exists('replaced', $installed['versions'][$packageName])) { 137 | $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); 138 | } 139 | if (array_key_exists('provided', $installed['versions'][$packageName])) { 140 | $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); 141 | } 142 | 143 | return implode(' || ', $ranges); 144 | } 145 | 146 | throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 147 | } 148 | 149 | 150 | 151 | 152 | 153 | public static function getVersion($packageName) 154 | { 155 | foreach (self::getInstalled() as $installed) { 156 | if (!isset($installed['versions'][$packageName])) { 157 | continue; 158 | } 159 | 160 | if (!isset($installed['versions'][$packageName]['version'])) { 161 | return null; 162 | } 163 | 164 | return $installed['versions'][$packageName]['version']; 165 | } 166 | 167 | throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 168 | } 169 | 170 | 171 | 172 | 173 | 174 | public static function getPrettyVersion($packageName) 175 | { 176 | foreach (self::getInstalled() as $installed) { 177 | if (!isset($installed['versions'][$packageName])) { 178 | continue; 179 | } 180 | 181 | if (!isset($installed['versions'][$packageName]['pretty_version'])) { 182 | return null; 183 | } 184 | 185 | return $installed['versions'][$packageName]['pretty_version']; 186 | } 187 | 188 | throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 189 | } 190 | 191 | 192 | 193 | 194 | 195 | public static function getReference($packageName) 196 | { 197 | foreach (self::getInstalled() as $installed) { 198 | if (!isset($installed['versions'][$packageName])) { 199 | continue; 200 | } 201 | 202 | if (!isset($installed['versions'][$packageName]['reference'])) { 203 | return null; 204 | } 205 | 206 | return $installed['versions'][$packageName]['reference']; 207 | } 208 | 209 | throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 210 | } 211 | 212 | 213 | 214 | 215 | 216 | public static function getRootPackage() 217 | { 218 | $installed = self::getInstalled(); 219 | 220 | return $installed[0]['root']; 221 | } 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | public static function getRawData() 230 | { 231 | return self::$installed; 232 | } 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | public static function reload($data) 253 | { 254 | self::$installed = $data; 255 | self::$installedByVendor = array(); 256 | } 257 | 258 | 259 | 260 | 261 | private static function getInstalled() 262 | { 263 | if (null === self::$canGetVendors) { 264 | self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); 265 | } 266 | 267 | $installed = array(); 268 | 269 | if (self::$canGetVendors) { 270 | 271 | foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { 272 | if (isset(self::$installedByVendor[$vendorDir])) { 273 | $installed[] = self::$installedByVendor[$vendorDir]; 274 | } elseif (is_file($vendorDir.'/composer/installed.php')) { 275 | $installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php'; 276 | } 277 | } 278 | } 279 | 280 | $installed[] = self::$installed; 281 | 282 | return $installed; 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /lib/composer/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Copyright (c) Nils Adermann, Jordi Boggiano 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is furnished 9 | to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /lib/composer/autoload_classmap.php: -------------------------------------------------------------------------------- 1 | $vendorDir . '/composer/InstalledVersions.php', 10 | ); 11 | -------------------------------------------------------------------------------- /lib/composer/autoload_namespaces.php: -------------------------------------------------------------------------------- 1 | array($baseDir . '/php'), 10 | ); 11 | -------------------------------------------------------------------------------- /lib/composer/autoload_real.php: -------------------------------------------------------------------------------- 1 | = 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); 30 | if ($useStaticLoader) { 31 | require __DIR__ . '/autoload_static.php'; 32 | 33 | call_user_func(\Composer\Autoload\ComposerStaticInit27b17fd2c20dabf2ea56e4a83a2355a0::getInitializer($loader)); 34 | } else { 35 | $map = require __DIR__ . '/autoload_namespaces.php'; 36 | foreach ($map as $namespace => $path) { 37 | $loader->set($namespace, $path); 38 | } 39 | 40 | $map = require __DIR__ . '/autoload_psr4.php'; 41 | foreach ($map as $namespace => $path) { 42 | $loader->setPsr4($namespace, $path); 43 | } 44 | 45 | $classMap = require __DIR__ . '/autoload_classmap.php'; 46 | if ($classMap) { 47 | $loader->addClassMap($classMap); 48 | } 49 | } 50 | 51 | $loader->register(true); 52 | 53 | return $loader; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/composer/autoload_static.php: -------------------------------------------------------------------------------- 1 | 11 | array ( 12 | 'DLXPlugins\\PatternWrangler\\' => 27, 13 | ), 14 | ); 15 | 16 | public static $prefixDirsPsr4 = array ( 17 | 'DLXPlugins\\PatternWrangler\\' => 18 | array ( 19 | 0 => __DIR__ . '/../..' . '/php', 20 | ), 21 | ); 22 | 23 | public static $classMap = array ( 24 | 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', 25 | ); 26 | 27 | public static function getInitializer(ClassLoader $loader) 28 | { 29 | return \Closure::bind(function () use ($loader) { 30 | $loader->prefixLengthsPsr4 = ComposerStaticInit27b17fd2c20dabf2ea56e4a83a2355a0::$prefixLengthsPsr4; 31 | $loader->prefixDirsPsr4 = ComposerStaticInit27b17fd2c20dabf2ea56e4a83a2355a0::$prefixDirsPsr4; 32 | $loader->classMap = ComposerStaticInit27b17fd2c20dabf2ea56e4a83a2355a0::$classMap; 33 | 34 | }, null, ClassLoader::class); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/composer/installed.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [], 3 | "dev": true, 4 | "dev-package-names": [] 5 | } 6 | -------------------------------------------------------------------------------- /lib/composer/installed.php: -------------------------------------------------------------------------------- 1 | 3 | array ( 4 | 'pretty_version' => 'dev-main', 5 | 'version' => 'dev-main', 6 | 'aliases' => 7 | array ( 8 | ), 9 | 'reference' => '98fdd65c94cacda5c9c5206215cac2130140a5db', 10 | 'name' => 'dlxplugins/pattern-wrangler', 11 | ), 12 | 'versions' => 13 | array ( 14 | 'dlxplugins/pattern-wrangler' => 15 | array ( 16 | 'pretty_version' => 'dev-main', 17 | 'version' => 'dev-main', 18 | 'aliases' => 19 | array ( 20 | ), 21 | 'reference' => '98fdd65c94cacda5c9c5206215cac2130140a5db', 22 | ), 23 | ), 24 | ); 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pattern-wrangler", 3 | "version": "1.0.0", 4 | "description": "Manage your block patterns.", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "build": "wp-scripts build --env mode=production", 8 | "start": "wp-scripts start --env mode=development" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/dlxplugins/pattern-wrangler.git" 13 | }, 14 | "keywords": [ 15 | "pattern", 16 | "wrangler" 17 | ], 18 | "author": "Ronald Huereca", 19 | "license": "GPL-3.0-or-later", 20 | "bugs": { 21 | "url": "https://github.com/dlxplugins/pattern-wrangler/issues" 22 | }, 23 | "homepage": "https://dlxplugins.com/plugins/pattern-wrangler/", 24 | "devDependencies": { 25 | "@babel/eslint-parser": "^7.18.2", 26 | "@babel/plugin-transform-arrow-functions": "^7.17.12", 27 | "@babel/plugin-transform-class-properties": "^7.23.3", 28 | "@babel/preset-env": "^7.18.2", 29 | "@babel/preset-react": "^7.17.12", 30 | "@types/photoswipe": "^4.1.2", 31 | "@types/react": "^18.0.14", 32 | "@wordpress/commands": "^0.16.0", 33 | "@wordpress/components": "^25.11.0", 34 | "@wordpress/element": "^4.9.0", 35 | "@wordpress/eslint-plugin": "^12.8.0", 36 | "@wordpress/i18n": "^4.11.0", 37 | "@wordpress/icons": "^9.36.0", 38 | "@wordpress/interface": "^8.3.0", 39 | "@wordpress/scripts": "^30.7.0", 40 | "ajv": "^8.17.1", 41 | "axios": "^1.6.1", 42 | "babel-plugin-macros": "^3.1.0", 43 | "classnames": "^2.3.1", 44 | "css-loader": "^6.7.1", 45 | "dompurify": "^2.4.0", 46 | "eslint": "^8.18.0", 47 | "eslint-config-prettier": "^8.3.0", 48 | "eslint-plugin-prettier": "^4.0.0", 49 | "grunt": "^1.6.1", 50 | "grunt-contrib-compress": "^2.0.0", 51 | "lodash.uniqueid": "^4.0.1", 52 | "lucide-react": "^0.292.0", 53 | "mini-css-extract-plugin": "^2.6.1", 54 | "prettier": "^2.7.1", 55 | "prettier-eslint": "^15.0.1", 56 | "process": "^0.11.10", 57 | "prop-types": "^15.8.1", 58 | "qs": "^6.10.5", 59 | "react": "^18.2.0", 60 | "react-dom": "^18.2.0", 61 | "react-hook-form": "^7.48.2", 62 | "resolve-url-loader": "^5.0.0", 63 | "sass": "^1.66.1", 64 | "sass-loader": "^13.0.2", 65 | "scss-tokenizer": "^0.4.3", 66 | "use-async-resource": "^2.2.2", 67 | "util": "^0.12.4", 68 | "webpack": "^5.73.0", 69 | "webpack-cli": "^4.10.0", 70 | "webpack-remove-empty-scripts": "^0.8.1" 71 | }, 72 | "dependencies": { 73 | "@fancyapps/ui": "^5.0.36", 74 | "@wordpress/a11y": "^3.11.0", 75 | "grunt-cli": "^1.4.3", 76 | "html-to-react": "^1.4.8" 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /pattern-wrangler.php: -------------------------------------------------------------------------------- 1 | run(); 69 | 70 | $patterns = new Patterns(); 71 | $patterns->run(); 72 | 73 | $drafts = new Drafts(); 74 | $drafts->run(); 75 | 76 | $preview = new Preview(); 77 | $preview->run(); 78 | 79 | // Determine if blocks can run or not. 80 | $options = Options::get_options(); 81 | $can_disable_blocks = (bool) $options['disablePatternImporterBlock']; 82 | if ( ! $can_disable_blocks ) { 83 | $blocks = new Blocks(); 84 | $blocks->run(); 85 | } 86 | 87 | /** 88 | * When PatternWrangler can be extended. 89 | * 90 | * Filter when PatternWrangler can be extended. 91 | * 92 | * @since 1.0.0 93 | */ 94 | do_action( 'dlxplugins_pw_loaded' ); 95 | } 96 | 97 | /** 98 | * Init all the things. 99 | */ 100 | public function init() { 101 | 102 | // Nothing here yet. 103 | } 104 | } 105 | 106 | add_action( 107 | 'plugins_loaded', 108 | function () { 109 | $pattern_wrangler = PatternWrangler::get_instance(); 110 | $pattern_wrangler->plugins_loaded(); 111 | } 112 | ); 113 | -------------------------------------------------------------------------------- /php/Admin.php: -------------------------------------------------------------------------------- 1 | sprintf( '%s', esc_url( Functions::get_settings_url() ), esc_html__( 'Settings', 'pattern-wrangler' ) ), 77 | 'docs' => sprintf( '%s', esc_url( 'https://docs.dlxplugins.com/v/pattern-wrangler' ), esc_html__( 'Docs', 'pattern-wrangler' ) ), 78 | 'site' => sprintf( '%s', esc_url( 'https://dlxplugins.com/plugins/pattern-wrangler/' ), esc_html__( 'Plugin Home', 'pattern-wrangler' ) ), 79 | ); 80 | if ( ! is_array( $settings ) ) { 81 | return $setting_links; 82 | } else { 83 | return array_merge( $setting_links, $settings ); 84 | } 85 | } 86 | 87 | /** 88 | * Save the options via Ajax. 89 | */ 90 | public function ajax_save_options() { 91 | // Get form data. 92 | $form_data = filter_input( INPUT_POST, 'formData', FILTER_SANITIZE_SPECIAL_CHARS, FILTER_REQUIRE_ARRAY ); 93 | 94 | $nonce = $form_data['saveNonce'] ?? false; 95 | if ( ! wp_verify_nonce( $nonce, 'dlx-pw-admin-save-options' ) || ! current_user_can( 'manage_options' ) ) { 96 | wp_send_json_error( 97 | array( 98 | 'message' => __( 'Nonce or permission verification failed.', 'pattern-wrangler' ), 99 | 'type' => 'critical', 100 | 'dismissable' => true, 101 | 'title' => __( 'Error', 'pattern-wrangler' ), 102 | ) 103 | ); 104 | } 105 | 106 | // Get array values. 107 | $form_data = Functions::sanitize_array_recursive( $form_data ); 108 | 109 | // Update options. 110 | Options::update_options( $form_data ); 111 | 112 | // Send success message. 113 | wp_send_json_success( 114 | array( 115 | 'message' => __( 'Options saved.', 'pattern-wrangler' ), 116 | 'type' => 'success', 117 | 'dismissable' => true, 118 | ) 119 | ); 120 | } 121 | 122 | /** 123 | * Reset the options. 124 | */ 125 | public function ajax_reset_options() { 126 | // Get form data. 127 | $form_data = filter_input( INPUT_POST, 'formData', FILTER_SANITIZE_SPECIAL_CHARS, FILTER_REQUIRE_ARRAY ); 128 | 129 | $nonce = $form_data['resetNonce'] ?? false; 130 | if ( ! wp_verify_nonce( $nonce, 'dlx-pw-admin-reset-options' ) || ! current_user_can( 'manage_options' ) ) { 131 | wp_send_json_error( 132 | array( 133 | 'message' => __( 'Nonce or permission verification failed.', 'pattern-wrangler' ), 134 | 'type' => 'error', 135 | 'dismissable' => true, 136 | 'title' => __( 'Error', 'pattern-wrangler' ), 137 | ) 138 | ); 139 | } 140 | 141 | // Get existing options. 142 | $options = Options::get_options(); 143 | 144 | // Get defaults and reset. 145 | $default_options = Options::get_defaults(); 146 | 147 | Options::update_options( $default_options ); 148 | 149 | // Pull in nonces to default options before returning. 150 | $default_options['saveNonce'] = $options['saveNonce']; 151 | $default_options['resetNonce'] = $options['resetNonce']; 152 | 153 | // Send success message. 154 | wp_send_json_success( 155 | array( 156 | 'message' => __( 'Options reset.', 'pattern-wrangler' ), 157 | 'type' => 'success', 158 | 'dismissable' => true, 159 | 'formData' => $default_options, 160 | ) 161 | ); 162 | } 163 | 164 | /** 165 | * Retrieve options via Ajax. 166 | */ 167 | public function ajax_get_options() { 168 | // Get nonce. 169 | $nonce = sanitize_text_field( filter_input( INPUT_POST, 'nonce', FILTER_SANITIZE_SPECIAL_CHARS ) ); 170 | 171 | // Verify nonce. 172 | $nonce_action = 'dlx-pw-admin-get-options'; 173 | if ( ! wp_verify_nonce( $nonce, $nonce_action ) || ! current_user_can( 'manage_options' ) ) { 174 | wp_send_json_error( 175 | array( 176 | 'message' => __( 'Nonce or permission verification failed.', 'pattern-wrangler' ), 177 | 'type' => 'error', 178 | 'dismissable' => true, 179 | 'title' => __( 'Error', 'pattern-wrangler' ), 180 | ) 181 | ); 182 | } 183 | $options = Options::get_options(); 184 | 185 | $categories = Functions::get_pattern_categories(); 186 | $options['registered'] = $categories['registered']; 187 | $options['categories'] = $categories['categories']; 188 | 189 | wp_send_json_success( $options ); 190 | } 191 | 192 | /** 193 | * Add synced/unsynced status to patterns. 194 | * 195 | * @param array $columns Columns. 196 | * 197 | * @return array Updated columns. 198 | */ 199 | public function add_pattern_sync_column( $columns ) { 200 | $new_column['pattern_sync'] = __( 'Synced', 'pattern-wrangler' ); 201 | 202 | // Add new column before last item of array. 203 | $columns = array_slice( $columns, 0, -1, true ) + $new_column + array_slice( $columns, -1, null, true ); 204 | return $columns; 205 | } 206 | 207 | /** 208 | * Output synced vs unsynced for post column. 209 | * 210 | * @param string $column Column name. 211 | * @param int $post_id Post ID. 212 | */ 213 | public function output_pattern_sync_column( $column, $post_id ) { 214 | if ( 'pattern_sync' === $column ) { 215 | $synced = get_post_meta( $post_id, 'wp_pattern_sync_status', true ); 216 | if ( 'unsynced' === $synced ) { 217 | // Unsynced patterns are explicitly set in post meta, whereas synced are not and assumed synced. 218 | echo ' ' . esc_html__( 'Unsynced Pattern', 'pattern-wrangler' ); 219 | } else { 220 | echo ' ' . esc_html__( 'Synced Pattern', 'pattern-wrangler' ); 221 | } 222 | } 223 | } 224 | 225 | /** 226 | * Add the admin menu. 227 | */ 228 | public function add_admin_menu() { 229 | $options = Options::get_options(); 230 | $hide_all_patterns = (bool) $options['hideAllPatterns'] ?? false; 231 | $hide_patterns_menu = (bool) $options['hidePatternsMenu'] ?? false; 232 | 233 | remove_submenu_page( 'themes.php', 'edit.php?post_type=wp_block' ); // Remove from Appearance in WP 6.5. 234 | remove_submenu_page( 'generateblocks', 'edit.php?post_type=wp_block' ); // Remove from GenerateBlocks screen. 235 | 236 | if ( $hide_all_patterns && $hide_patterns_menu ) { 237 | $hook = add_submenu_page( 238 | 'themes.php', 239 | __( 'Patterns', 'pattern-wrangler' ), 240 | __( 'Patterns', 'pattern-wrangler' ), 241 | 'manage_options', 242 | 'pattern-wrangler', 243 | array( $this, 'admin_page' ), 244 | 4 245 | ); 246 | add_action( 'admin_print_scripts-' . $hook, array( $this, 'enqueue_scripts' ) ); 247 | return; 248 | } 249 | add_menu_page( 250 | __( 'Patterns', 'pattern-wrangler' ), 251 | __( 'Patterns', 'pattern-wrangler' ), 252 | 'manage_options', 253 | 'edit.php?post_type=wp_block', 254 | '', 255 | 'dashicons-layout', 256 | 6 257 | ); 258 | 259 | add_submenu_page( 260 | 'edit.php?post_type=wp_block', 261 | __( 'Categories', 'pattern-wrangler' ), 262 | __( 'Categories', 'pattern-wrangler' ), 263 | 'edit_posts', 264 | 'edit-tags.php?taxonomy=wp_pattern_category&post_type=wp_block', 265 | '', 266 | 5 267 | ); 268 | 269 | $hook = add_submenu_page( 270 | 'edit.php?post_type=wp_block', 271 | __( 'Settings', 'pattern-wrangler' ), 272 | __( 'Settings', 'pattern-wrangler' ), 273 | 'edit_posts', 274 | 'pattern-wrangler', 275 | array( $this, 'admin_page' ), 276 | 10 277 | ); 278 | add_action( 'admin_print_scripts-' . $hook, array( $this, 'enqueue_scripts' ) ); 279 | } 280 | 281 | /** 282 | * Set the category submenu as current. 283 | * 284 | * @param WP_Screen $screen The current screen. 285 | */ 286 | public function set_category_submenu_current( $screen ) { 287 | if ( ! is_admin() ) { 288 | return; 289 | } 290 | // Check if current page is pattern categories and mark categories as curent if slug matches. 291 | $current_screen = get_current_screen(); 292 | if ( 'edit-wp_pattern_category' === $current_screen->id ) { 293 | // Doing JS here because there are no filters for marking submenus as current. 294 | ?> 295 | 309 | wp_create_nonce( 'dlx-pw-admin-get-options' ), 335 | 'saveNonce' => wp_create_nonce( 'dlx-pw-admin-save-options' ), 336 | 'resetNonce' => wp_create_nonce( 'dlx-pw-admin-reset-options' ), 337 | 'previewNonce' => wp_create_nonce( 'dlx-pw-admin-preview' ), 338 | 'ajaxurl' => admin_url( 'admin-ajax.php' ), 339 | ) 340 | ); 341 | \wp_set_script_translations( 'dlx-pw-admin', 'pattern-wrangler' ); 342 | } 343 | 344 | // Enqueue admin styles. 345 | wp_enqueue_style( 346 | 'dlx-pw-admin-css', 347 | Functions::get_plugin_url( 'dist/dlx-pw-admin-css.css' ), 348 | array(), 349 | Functions::get_plugin_version(), 350 | 'all' 351 | ); 352 | } 353 | 354 | /** 355 | * Render the admin page. 356 | */ 357 | public function admin_page() { 358 | ?> 359 |
360 |
361 |
362 | 367 |
368 | 369 | 370 |
371 |
372 |
373 | 376 |
377 |
378 | 381 |
382 | 385 |
386 |
387 |
388 | post_type ) { 43 | return true; 44 | } 45 | return $use_block_editor; 46 | } 47 | 48 | /** 49 | * Register the rest routes needed. 50 | */ 51 | public function init_rest_api() { 52 | register_rest_route( 53 | 'dlxplugins/pattern-wrangler/v1', 54 | '/process_image', 55 | array( 56 | 'methods' => 'POST', 57 | 'callback' => array( $this, 'rest_add_remote_image' ), 58 | 'permission_callback' => array( $this, 'rest_image_sideload_permissions' ), 59 | ) 60 | ); 61 | } 62 | 63 | /** 64 | * Process a list of images for a pattern. 65 | * 66 | * @param WP_Rest $request REST request. 67 | */ 68 | public function rest_add_remote_image( $request ) { 69 | $image_url = filter_var( $request->get_param( 'imgUrl' ), FILTER_VALIDATE_URL ); 70 | $image_alt = sanitize_text_field( $request->get_param( 'imgAlt' ) ); 71 | 72 | if ( $image_url ) { 73 | // Check file extension. 74 | $extension = pathinfo( $image_url, PATHINFO_EXTENSION ); 75 | 76 | // Strip query vars from extension. 77 | $extension = preg_replace( '/\?.*/', '', $extension ); 78 | 79 | // Get current domain. 80 | $domain = wp_parse_url( $image_url, PHP_URL_HOST ); 81 | 82 | // If we're on same domain, bail successfully. 83 | if ( $domain === $_SERVER['HTTP_HOST'] ) { 84 | \wp_send_json_success( 85 | array( 86 | 'attachmentId' => 0, 87 | 'attachmentUrl' => esc_url( $image_url ), 88 | ) 89 | ); 90 | } 91 | 92 | if ( ! $extension ) { 93 | \wp_send_json_error( 94 | array( 95 | 'message' => __( 'File extension not found.', 'pattern-wrangler' ), 96 | ), 97 | 400 98 | ); 99 | } 100 | $valid_extensions = Functions::get_supported_file_extensions(); 101 | if ( ! in_array( $extension, $valid_extensions, true ) ) { 102 | \wp_send_json_error( 103 | array( 104 | 'message' => __( 'Invalid file extension.', 'pattern-wrangler' ), 105 | ), 106 | 400 107 | ); 108 | } 109 | 110 | // Save the image to the media library. 111 | if ( ! function_exists( 'media_sideload_image' ) ) { 112 | require_once ABSPATH . 'wp-admin/includes/image.php'; 113 | require_once ABSPATH . 'wp-admin/includes/file.php'; 114 | require_once ABSPATH . 'wp-admin/includes/media.php'; 115 | } 116 | $attachment_id = media_sideload_image( $image_url, 0, '', 'id' ); 117 | 118 | // Add order to attachment. 119 | if ( ! is_wp_error( $attachment_id ) ) { 120 | 121 | // Get attachment URL. 122 | $attachment_url_src = wp_get_attachment_image_src( $attachment_id, 'full' ); 123 | $attachment_url = $attachment_url_src[0]; 124 | 125 | // Update alt attribute. 126 | update_post_meta( $attachment_id, '_wp_attachment_image_alt', $image_alt ); 127 | 128 | // Send success. 129 | \wp_send_json_success( 130 | array( 131 | 'attachmentId' => absint( $attachment_id ), 132 | 'attachmentUrl' => esc_url( $attachment_url ), 133 | ) 134 | ); 135 | } else { 136 | \wp_send_json_error( 137 | array( 138 | 'message' => $attachment_id->get_error_message(), 139 | ), 140 | 400 141 | ); 142 | } 143 | } 144 | \wp_send_json_error( 145 | array( 146 | 'message' => __( 'Invalid image URL.', 'pattern-wrangler' ), 147 | ), 148 | 400 149 | ); 150 | } 151 | 152 | /** 153 | * Check if user has access to REST API for retrieving and sideloading images. 154 | */ 155 | public function rest_image_sideload_permissions() { 156 | return current_user_can( 'publish_posts' ); 157 | } 158 | 159 | /** 160 | * Init action callback. 161 | */ 162 | public function init() { 163 | 164 | register_block_type( 165 | Functions::get_plugin_dir( 'build/js/blocks/pattern-importer/block.json' ), 166 | array( 167 | 'render_callback' => '__return_empty_string', 168 | ) 169 | ); 170 | 171 | // Enqueue block assets. 172 | add_action( 'enqueue_block_editor_assets', array( $this, 'register_block_editor_scripts' ) ); 173 | } 174 | 175 | /** 176 | * Register the block editor script with localized vars. 177 | */ 178 | public function register_block_editor_scripts() { 179 | 180 | $deps = require_once Functions::get_plugin_dir( 'build/index.asset.php' ); 181 | 182 | wp_register_script( 183 | 'dlx-pw-pattern-inserter-block', 184 | Functions::get_plugin_url( 'build/index.js' ), 185 | $deps['dependencies'], 186 | $deps['version'], 187 | true 188 | ); 189 | 190 | wp_localize_script( 191 | 'dlx-pw-pattern-inserter-block', 192 | 'dlxPWPatternInserter', 193 | array( 194 | 'restUrl' => rest_url( 'dlxplugins/pattern-wrangler/v1' ), 195 | 'restNonce' => wp_create_nonce( 'wp_rest' ), 196 | ) 197 | ); 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /php/Drafts.php: -------------------------------------------------------------------------------- 1 |

%s

', 55 | esc_html( $notice_message ) 56 | ); 57 | } 58 | 59 | /** 60 | * Intercept draft/publish actions. 61 | */ 62 | public function intercept_draft_publish() { 63 | $action = sanitize_text_field( filter_input( INPUT_GET, 'action', FILTER_SANITIZE_SPECIAL_CHARS ) ); 64 | $nonce = sanitize_text_field( filter_input( INPUT_GET, 'nonce', FILTER_SANITIZE_SPECIAL_CHARS ) ); 65 | $post_id = absint( filter_input( INPUT_GET, 'post', \FILTER_SANITIZE_NUMBER_INT ) ); 66 | 67 | if ( ! $action ) { 68 | return; 69 | } 70 | if ( ! current_user_can( 'edit_posts' ) ) { 71 | return; 72 | } 73 | $notice_action = 'draft_pattern'; 74 | switch ( $action ) { 75 | case 'draft_pattern': 76 | if ( ! wp_verify_nonce( $nonce, 'draft-pattern_' . $post_id ) ) { 77 | return; 78 | } 79 | wp_update_post( 80 | array( 81 | 'ID' => $post_id, 82 | 'post_status' => 'draft', 83 | ) 84 | ); 85 | break; 86 | case 'publish_pattern': 87 | if ( ! wp_verify_nonce( $nonce, 'publish-pattern_' . $post_id ) ) { 88 | return; 89 | } 90 | $notice_action = 'publish_pattern'; 91 | wp_update_post( 92 | array( 93 | 'ID' => $post_id, 94 | 'post_status' => 'publish', 95 | ) 96 | ); 97 | break; 98 | default: 99 | return; 100 | } 101 | 102 | // Build redirect URL. 103 | $redirect_url = add_query_arg( 104 | array( 105 | 'post_type' => 'wp_block', 106 | 'notice_action' => $notice_action, 107 | ), 108 | admin_url( 'edit.php' ) 109 | ); 110 | wp_safe_redirect( esc_url_raw( $redirect_url ) ); 111 | exit; 112 | } 113 | 114 | /** 115 | * Add a draft button to the quick actions for the wp_block post type. 116 | * 117 | * @param array $actions Array of actions. 118 | * @param WP_Post $post Post object. 119 | * 120 | * @return array 121 | */ 122 | public function add_draft_button_quick_action( $actions, $post ) { 123 | if ( 'wp_block' !== $post->post_type ) { 124 | return $actions; 125 | } 126 | if ( ! current_user_can( 'edit_posts' ) ) { 127 | return $actions; 128 | } 129 | $draft_disable_url = add_query_arg( 130 | array( 131 | 'action' => 'draft_pattern', 132 | 'nonce' => wp_create_nonce( 'draft-pattern_' . $post->ID ), 133 | 'post' => $post->ID, 134 | ), 135 | admin_url( 'edit.php?post_type=wp_block' ) 136 | ); 137 | $draft_publish_url = add_query_arg( 138 | array( 139 | 'action' => 'publish_pattern', 140 | 'nonce' => wp_create_nonce( 'publish-pattern_' . $post->ID ), 141 | 'post' => $post->ID, 142 | ), 143 | admin_url( 'edit.php?post_type=wp_block' ) 144 | ); 145 | if ( 'draft' === $post->post_status ) { 146 | $actions['draft_pattern'] = sprintf( 147 | '%s', 148 | esc_url_raw( $draft_publish_url ), 149 | esc_html__( 'Publish', 'pattern-wrangler' ) 150 | ); 151 | } 152 | if ( 'publish' === $post->post_status ) { 153 | $actions['preview_pattern'] = sprintf( 154 | '%s', 155 | esc_url_raw( $draft_disable_url ), 156 | esc_html__( 'Switch to Draft', 'pattern-wrangler' ) 157 | ); 158 | } 159 | return $actions; 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /php/Functions.php: -------------------------------------------------------------------------------- 1 | 'wp_pattern_category', 97 | 'hide_empty' => false, 98 | 'count' => true, 99 | ) 100 | ); 101 | return $categories; 102 | } 103 | 104 | /** 105 | * Get the pattern categories. 106 | * 107 | * @return array The pattern categories. 108 | */ 109 | public static function get_pattern_categories() { 110 | $options = Options::get_options(); 111 | 112 | // Get registered block categories. 113 | $pattern_categories = \WP_Block_Pattern_Categories_Registry::get_instance(); 114 | $pattern_categories = $pattern_categories->get_all_registered(); 115 | 116 | // Get all registered block patterns. We'll use this for a count. 117 | $pattern_registry = \WP_Block_Patterns_Registry::get_instance(); 118 | $pattern_registry = $pattern_registry->get_all_registered(); 119 | 120 | // Get all pattern categories from the built-in WP taxonomy. 121 | $pattern_categories_taxonomy = self::get_pattern_categories_from_taxonomy(); 122 | 123 | // Get saved category data. 124 | $custom_pattern_categories = $options['categories']; 125 | 126 | // Loop through custom categories, and determine if a category is on or off. 127 | $all_categories = array(); 128 | 129 | // Exclude these categories as they are deprecated in WordPress core. 130 | $excluded_cats = array( 131 | 'buttons', 132 | 'columns', 133 | 'query', 134 | ); 135 | 136 | foreach ( $pattern_categories as $category ) { 137 | /* Excluded Categories */ 138 | if ( in_array( $category['name'], $excluded_cats, true ) ) { 139 | continue; 140 | } 141 | 142 | // Loop through custom categories, and determine if a category is on or off. 143 | $category_enabled = isset( $custom_pattern_categories[ $category['name'] ]['enabled'] ) ? (bool) $custom_pattern_categories[ $category['name'] ]['enabled'] : true; 144 | $category_custom = isset( $custom_pattern_categories[ $category['name'] ]['customLabel'] ) ? $custom_pattern_categories[ $category['name'] ]['customLabel'] : $category['label']; 145 | $category_mapped_to = isset( $custom_pattern_categories[ $category['name'] ]['mappedTo'] ) ? $custom_pattern_categories[ $category['name'] ]['mappedTo'] : false; 146 | $all_categories[ $category['name'] ] = array( 147 | 'label' => $category['label'], 148 | 'customLabel' => ! empty( $category_custom ) ? $category_custom : $category['label'], 149 | 'enabled' => $category_enabled, 150 | 'slug' => $category['name'], 151 | 'count' => $category['count'] ?? 0, 152 | 'mappedTo' => $category_mapped_to, 153 | ); 154 | } 155 | 156 | // Ensure all categories are unique. 157 | $all_categories = array_unique( $all_categories, SORT_REGULAR ); 158 | 159 | // Sort by label. 160 | uasort( 161 | $all_categories, 162 | function ( $a, $b ) { 163 | return strcasecmp( $a['customLabel'], $b['customLabel'] ); 164 | } 165 | ); 166 | 167 | // Loop through all patterns and increment a count for each category. Since core tax pattern categories have a count for core patterns. 168 | foreach ( $pattern_registry as $pattern ) { 169 | $pattern_categories = $pattern['categories']; 170 | foreach ( $pattern_categories as $category ) { 171 | if ( isset( $all_categories[ $category ] ) ) { 172 | ++$all_categories[ $category ]['count']; 173 | } 174 | } 175 | } 176 | 177 | return array( 178 | 'registered' => $all_categories, 179 | 'categories' => $pattern_categories_taxonomy, 180 | ); 181 | } 182 | 183 | /** 184 | * Check if a pattern is synced. 185 | * 186 | * @param int $pattern_id The pattern ID. 187 | * 188 | * @return bool true if synced, false if not. 189 | */ 190 | public static function is_pattern_synced( $pattern_id ) { 191 | $synced = get_post_meta( $pattern_id, 'wp_pattern_sync_status', true ); 192 | if ( 'unsynced' === $synced ) { 193 | return false; 194 | } 195 | return true; 196 | } 197 | 198 | /** 199 | * Get preview URL for previewing a pattern. 200 | * 201 | * @param int $post_id The post ID. 202 | * 203 | * @return string The preview URL (unescaped). 204 | */ 205 | public static function get_pattern_preview_url( $post_id ) { 206 | $preview_url = add_query_arg( 207 | array( 208 | 'dlxpw_preview' => '1', 209 | 'action' => 'preview', 210 | 'pattern' => $post_id, 211 | 'nonce' => wp_create_nonce( 'preview-pattern_' . $post_id ), 212 | ), 213 | home_url() 214 | ); 215 | return $preview_url; 216 | } 217 | 218 | /** 219 | * Get the plugin's supported file extensions. 220 | * 221 | * @since 1.0.0 222 | * 223 | * @return array The supported file extensions. 224 | */ 225 | public static function get_supported_file_extensions() { 226 | $file_extensions = array( 227 | 'jpeg', 228 | 'jpg', 229 | 'gif', 230 | 'png', 231 | 'webp', 232 | 'avif', 233 | ); 234 | /** 235 | * Filter the valid file extensions for the photo block. 236 | * 237 | * @param array $file_extensions The valid mime types. 238 | */ 239 | $file_extensions = apply_filters( 'dlxpw_block_file_extensions', $file_extensions ); 240 | 241 | return $file_extensions; 242 | } 243 | 244 | /** 245 | * Get the current admin tab. 246 | * 247 | * @return null|string Current admin tab. 248 | */ 249 | public static function get_admin_tab() { 250 | $tab = filter_input( INPUT_GET, 'tab', FILTER_SANITIZE_SPECIAL_CHARS ); 251 | if ( $tab && is_string( $tab ) ) { 252 | return sanitize_text_field( sanitize_title( $tab ) ); 253 | } 254 | return null; 255 | } 256 | 257 | /** 258 | * Return the URL to the admin screen 259 | * 260 | * @param string $tab Tab path to load. 261 | * @param string $sub_tab Subtab path to load. 262 | * 263 | * @return string URL to admin screen. Output is not escaped. 264 | */ 265 | public static function get_settings_url( $tab = '', $sub_tab = '' ) { 266 | $options = Options::get_options(); 267 | $hide_all_patterns = (bool) $options['hideAllPatterns'] ?? false; 268 | $hide_patterns_menu = (bool) $options['hidePatternsMenu'] ?? false; 269 | $options_url = admin_url( 'edit.php?post_type=wp_block&page=pattern-wrangler' ); 270 | if ( $hide_all_patterns && $hide_patterns_menu ) { 271 | $options_url = admin_url( 'themes.php?page=pattern-wrangler' ); 272 | } 273 | 274 | if ( ! empty( $tab ) ) { 275 | $options_url = add_query_arg( array( 'tab' => sanitize_title( $tab ) ), $options_url ); 276 | if ( ! empty( $sub_tab ) ) { 277 | $options_url = add_query_arg( array( 'subtab' => sanitize_title( $sub_tab ) ), $options_url ); 278 | } 279 | } 280 | return $options_url; 281 | } 282 | 283 | /** 284 | * Checks to see if an asset is activated or not. 285 | * 286 | * @since 1.0.0 287 | * 288 | * @param string $path Path to the asset. 289 | * @param string $type Type to check if it is activated or not. 290 | * 291 | * @return bool true if activated, false if not. 292 | */ 293 | public static function is_activated( $path, $type = 'plugin' ) { 294 | 295 | // Gets all active plugins on the current site. 296 | $active_plugins = self::is_multisite() ? get_site_option( 'active_sitewide_plugins' ) : get_option( 'active_plugins', array() ); 297 | if ( in_array( $path, $active_plugins, true ) ) { 298 | return true; 299 | } 300 | return false; 301 | } 302 | 303 | /** 304 | * Take a _ separated field and convert to camelcase. 305 | * 306 | * @param string $field Field to convert to camelcase. 307 | * 308 | * @return string camelCased field. 309 | */ 310 | public static function to_camelcase( string $field ) { 311 | return str_replace( '_', '', lcfirst( ucwords( $field, '_' ) ) ); 312 | } 313 | 314 | /** 315 | * Return the plugin slug. 316 | * 317 | * @return string plugin slug. 318 | */ 319 | public static function get_plugin_slug() { 320 | return dirname( plugin_basename( DLXPW_PATTERN_WRANGLER_FILE ) ); 321 | } 322 | 323 | /** 324 | * Return the basefile for the plugin. 325 | * 326 | * @return string base file for the plugin. 327 | */ 328 | public static function get_plugin_file() { 329 | return plugin_basename( DLXPW_PATTERN_WRANGLER_FILE ); 330 | } 331 | 332 | /** 333 | * Return the version for the plugin. 334 | * 335 | * @return float version for the plugin. 336 | */ 337 | public static function get_plugin_version() { 338 | return DLXPW_PATTERN_WRANGLER_VERSION; 339 | } 340 | 341 | /** 342 | * Returns appropriate html for KSES. 343 | * 344 | * @param bool $svg Whether to add SVG data to KSES. 345 | */ 346 | public static function get_kses_allowed_html( $svg = true ) { 347 | $allowed_tags = wp_kses_allowed_html(); 348 | 349 | $allowed_tags['nav'] = array( 350 | 'class' => array(), 351 | ); 352 | $allowed_tags['a']['class'] = array(); 353 | $allowed_tags['input'] = array( 354 | 'type' => array(), 355 | 'name' => array(), 356 | 'value' => array(), 357 | 'class' => array(), 358 | 'readonly' => array(), 359 | ); 360 | $allowed_tags['button'] = array( 361 | 'type' => array(), 362 | 'name' => array(), 363 | 'value' => array(), 364 | 'class' => array(), 365 | 'title' => array(), 366 | ); 367 | $allowed_tags['div'] = array( 368 | 'class' => array(), 369 | ); 370 | $allowed_tags['span'] = array( 371 | 'class' => array(), 372 | ); 373 | 374 | if ( ! $svg ) { 375 | return $allowed_tags; 376 | } 377 | $allowed_tags['svg'] = array( 378 | 'xmlns' => array(), 379 | 'fill' => array(), 380 | 'viewbox' => array(), 381 | 'role' => array(), 382 | 'aria-hidden' => array(), 383 | 'focusable' => array(), 384 | 'class' => array(), 385 | ); 386 | 387 | $allowed_tags['path'] = array( 388 | 'd' => array(), 389 | 'fill' => array(), 390 | 'opacity' => array(), 391 | ); 392 | 393 | $allowed_tags['g'] = array(); 394 | 395 | $allowed_tags['use'] = array( 396 | 'xlink:href' => array(), 397 | ); 398 | 399 | $allowed_tags['symbol'] = array( 400 | 'aria-hidden' => array(), 401 | 'viewBox' => array(), 402 | 'id' => array(), 403 | 'xmls' => array(), 404 | ); 405 | 406 | return $allowed_tags; 407 | } 408 | 409 | /** 410 | * Array data that must be sanitized. 411 | * 412 | * @param array $data Data to be sanitized. 413 | * 414 | * @return array Sanitized data. 415 | */ 416 | public static function sanitize_array_recursive( array $data ) { 417 | $sanitized_data = array(); 418 | foreach ( $data as $key => $value ) { 419 | if ( '0' === $value ) { 420 | $value = 0; 421 | } 422 | if ( 'true' === $value ) { 423 | $value = true; 424 | } elseif ( 'false' === $value ) { 425 | $value = false; 426 | } 427 | if ( is_array( $value ) ) { 428 | $value = self::sanitize_array_recursive( $value ); 429 | $sanitized_data[ $key ] = $value; 430 | continue; 431 | } 432 | if ( is_bool( $value ) ) { 433 | $sanitized_data[ $key ] = (bool) $value; 434 | continue; 435 | } 436 | if ( is_int( $value ) ) { 437 | $sanitized_data[ $key ] = (int) $value; 438 | continue; 439 | } 440 | if ( is_string( $value ) ) { 441 | $sanitized_data[ $key ] = sanitize_text_field( $value ); 442 | continue; 443 | } 444 | } 445 | return $sanitized_data; 446 | } 447 | 448 | /** 449 | * Get the plugin directory for a path. 450 | * 451 | * @param string $path The path to the file. 452 | * 453 | * @return string The new path. 454 | */ 455 | public static function get_plugin_dir( $path = '' ) { 456 | $dir = rtrim( plugin_dir_path( DLXPW_PATTERN_WRANGLER_FILE ), '/' ); 457 | if ( ! empty( $path ) && is_string( $path ) ) { 458 | $dir .= '/' . ltrim( $path, '/' ); 459 | } 460 | return $dir; 461 | } 462 | 463 | /** 464 | * Return a plugin URL path. 465 | * 466 | * @param string $path Path to the file. 467 | * 468 | * @return string URL to to the file. 469 | */ 470 | public static function get_plugin_url( $path = '' ) { 471 | $dir = rtrim( plugin_dir_url( DLXPW_PATTERN_WRANGLER_FILE ), '/' ); 472 | if ( ! empty( $path ) && is_string( $path ) ) { 473 | $dir .= '/' . ltrim( $path, '/' ); 474 | } 475 | return $dir; 476 | } 477 | 478 | /** 479 | * Gets the highest priority for a filter. 480 | * 481 | * @param int $subtract The amount to subtract from the high priority. 482 | * 483 | * @return int priority. 484 | */ 485 | public static function get_highest_priority( $subtract = 0 ) { 486 | $highest_priority = PHP_INT_MAX; 487 | $subtract = absint( $subtract ); 488 | if ( 0 === $subtract ) { 489 | --$highest_priority; 490 | } else { 491 | $highest_priority = absint( $highest_priority - $subtract ); 492 | } 493 | return $highest_priority; 494 | } 495 | } 496 | -------------------------------------------------------------------------------- /php/Options.php: -------------------------------------------------------------------------------- 1 | &$option ) { 46 | switch ( $key ) { 47 | case 'enabled': 48 | $option = filter_var( $options[ $key ], FILTER_VALIDATE_BOOLEAN ); 49 | break; 50 | default: 51 | if ( is_array( $option ) ) { 52 | $option = Functions::sanitize_array_recursive( $option ); 53 | } else { 54 | $option = sanitize_text_field( $options[ $key ] ); 55 | } 56 | break; 57 | } 58 | } 59 | $options = wp_parse_args( $options, $current_options ); 60 | if ( Functions::is_multisite() ) { 61 | update_site_option( self::$options_key, $options ); 62 | } else { 63 | update_option( self::$options_key, $options ); 64 | } 65 | self::$options = $options; 66 | return $options; 67 | } 68 | 69 | /** 70 | * Return a list of options. 71 | * 72 | * @param bool $force Whether to get options from cache or not. 73 | * 74 | * @return array Array of options. 75 | */ 76 | public static function get_options( $force = false ) { 77 | if ( is_array( self::$options ) && ! $force ) { 78 | return self::$options; 79 | } 80 | if ( Functions::is_multisite() ) { 81 | $options = get_site_option( self::$options_key, array() ); 82 | } else { 83 | $options = get_option( self::$options_key, array() ); 84 | } 85 | 86 | $defaults = self::get_defaults(); 87 | $options = wp_parse_args( $options, $defaults ); 88 | self::$options = $options; 89 | return $options; 90 | } 91 | 92 | /** 93 | * Get defaults for SCE options 94 | * 95 | * @since 1.0.0 96 | * @access public 97 | * 98 | * @return array default options 99 | */ 100 | public static function get_defaults() { 101 | 102 | $defaults = array( 103 | 'hideAllPatterns' => false, 104 | 'hidePatternsMenu' => false, /* only if hideAllPatterns is true, place in the Appearance menu */ 105 | 'hideCorePatterns' => false, 106 | 'hideRemotePatterns' => false, 107 | 'hideThemePatterns' => false, 108 | 'hidePluginPatterns' => false, 109 | 'hideCoreSyncedPatterns' => false, 110 | 'hideCoreUnsyncedPatterns' => false, 111 | 'disablePatternImporterBlock' => false, 112 | 'categories' => array(), 113 | 'allowFrontendPatternPreview' => true, 114 | 'hideUncategorizedPatterns' => false, 115 | 'showCustomizerUI' => true, 116 | 'showMenusUI' => true, 117 | 'loadCustomizerCSSBlockEditor' => false, 118 | 'loadCustomizerCSSFrontend' => true, 119 | 'makePatternsExportable' => false, 120 | ); 121 | return $defaults; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /php/Preview.php: -------------------------------------------------------------------------------- 1 | post_type ) { 47 | return; 48 | } 49 | $deps = require_once Functions::get_plugin_dir( 'build/dlx-pw-preview.asset.php' ); 50 | wp_enqueue_script( 51 | 'dlx-pattern-wrangler-preview', 52 | Functions::get_plugin_url( 'build/dlx-pw-preview.js' ), 53 | $deps['dependencies'], 54 | $deps['version'], 55 | true 56 | ); 57 | wp_localize_script( 58 | 'dlx-pattern-wrangler-preview', 59 | 'dlxPatternWranglerPreview', 60 | array( 61 | 'previewUrl' => Functions::get_pattern_preview_url( get_the_ID() ), 62 | ) 63 | ); 64 | } 65 | 66 | /** 67 | * Override the template for the wp_block post type. 68 | * 69 | * @param string $template Template path. 70 | * 71 | * @return string Updated path. 72 | */ 73 | public function maybe_override_template( $template ) { 74 | $preview = get_query_var( 'dlxpw_preview' ); 75 | if ( ! $preview ) { 76 | return $template; 77 | } 78 | $template = Functions::get_plugin_dir( 'templates/pattern.php' ); 79 | return $template; 80 | } 81 | 82 | /** 83 | * Add preview query var to frontend. 84 | * 85 | * @param array $query_vars Array of query vars. 86 | * 87 | * @return array updated query vars. 88 | */ 89 | public function add_preview_query_var( $query_vars ) { 90 | $query_vars[] = 'dlxpw_preview'; 91 | return $query_vars; 92 | } 93 | 94 | /** 95 | * Add a preview button to the quick actions for the wp_block post type. 96 | * 97 | * @param array $actions Array of actions. 98 | * @param WP_Post $post Post object. 99 | * 100 | * @return array 101 | */ 102 | public function add_preview_button_quick_action( $actions, $post ) { 103 | if ( 'wp_block' === $post->post_type ) { 104 | $actions['preview_pattern'] = sprintf( 105 | '%s', 106 | esc_url_raw( Functions::get_pattern_preview_url( $post->ID ) ), 107 | esc_html__( 'Preview', 'pattern-wrangler' ) 108 | ); 109 | } 110 | return $actions; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /phpcs.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | . 16 | 17 | /node_modules/ 18 | /vendor/ 19 | /lib/ 20 | /build/ 21 | /dist/ 22 | /php/Plugin_Updater.php 23 | 24 | -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | === Pattern Wrangler - Manage WordPress Block Patterns Effortlessly === 2 | Contributors: ronalfy 3 | Tags: patterns, reusable blocks, block editor, shortcode, block management 4 | Requires at least: 6.5 5 | Tested up to: 6.7 6 | Requires PHP: 7.2 7 | Stable tag: 1.2.0 8 | License: GPLv2 or later 9 | License URI: https://www.gnu.org/licenses/gpl-2.0.html 10 | 11 | Manage your block patterns efficiently with Pattern Wrangler. 12 | 13 | == Description == 14 | 15 | Pattern Wrangler makes managing WordPress block patterns simple and efficient, with features that cater to both beginners and advanced users. Whether you're organizing patterns for a complex site, a hybrid setup, or just hiding ones you don't need, Pattern Wrangler has you covered. 16 | 17 | Here are the major features: 18 | 19 | * **Hide All Patterns** - Completely hide patterns from the block editor in one click. This also hides the Patterns menu item. 20 | * **Selective Hiding** - Hide core, remote, theme, or plugin patterns while keeping your custom patterns visible. You can also hide synced and unsynced patterns together or separately. 21 | * **Category Management** - Disable, map, and rename registered categories from themes and plugins for better organization. This will help you keep local and registered patterns organized together. 22 | * **Output Patterns Anywhere** - Use a shortcode to display local patterns in page builders, widgets, your theme,or other blocks. 23 | * **Pattern Preview** - Preview a pattern on the frontend with shortcuts in the block editor or from the Patterns post list view. 24 | * **Cross-Site Pattern Copying** - Transfer patterns, including the remote images, between WordPress sites effortlessly. This is useful if you're copying a pattern from one site to another or copying a pattern from a production site to a development site. 25 | 26 | > Pattern Wrangler integrates seamlessly with block-based and classic themes offering a hybrid setup with unmatched flexibility. 27 | 28 | === Quick Links === 29 | 30 | All Features and Documentation | Sponsor Us | Pattern Wrangler Home 31 | 32 | > Source code is available on GitHub. 33 | 34 | === Requirements and Compatibility === 35 | 36 | Requires WordPress 6.5 or higher. 6.7 is recommended. 37 | 38 | Fully compatible with most themes, including block themes. Ideal for hybrid setups. 39 | 40 | == Installation == 41 | 42 | 1. Upload the plugin files to the `/wp-content/plugins/pattern-wrangler` directory, or install the plugin through the WordPress plugins screen directly. 43 | 2. Activate the plugin through the 'Plugins' screen in WordPress. 44 | 3. Use the plugin through the block editor by adding new patterns or importing existing ones. 45 | 46 | == Frequently Asked Questions == 47 | 48 | = Can I import Patterns from any WordPress site? = 49 | 50 | Yes! If you have the pattern's code, Pattern Wrangler can import it and localize any associated images. 51 | 52 | = Can I use Patterns in page builders like Elementor? = 53 | 54 | Yes! You can use the `[wp_block slug="pattern-slug"]` shortcode to output block patterns anywhere in your theme or other blocks. 55 | 56 | = Does this work with Block Themes? = 57 | 58 | Yes. Although it is designed for hybrid setups, it works with block themes, allowing you to merge theme and plugin-based patterns with your own local patterns stored in your database. 59 | 60 | Pattern Wrangler simply makes visible the default `wp_block` post type and category, which is where local patterns are stored. 61 | 62 | The Patterns view of this plugin uses the classic Patterns screen, with plans to eventually modernize it and put it on par with the Patterns viewer in the Full-Site Editor. 63 | 64 | == Screenshots == 65 | 66 | 1. An example of an organized Patterns screen. 67 | 2. Enhanced Patterns List View with shortcode and category/sync columns. 68 | 3. Map registered categories to terms, or rename for better organization or translations. 69 | 4. Enable the Customizer UI, and load Additional CSS in the block editor. 70 | 5. Hide all patterns, or hide them from core, remote, themes, or plugins. 71 | 6. Preview a Pattern on the frontend. 72 | 73 | == Changelog == 74 | 75 | = 1.2.0 = 76 | * Released 2024-12-18 77 | * New Feature: Show or hide all unsynced (non-reusable) patterns. 78 | * New Feature: Show or hide all synced (reusable) patterns. 79 | * New Feature: Disable both unsynced and synced patterns to completely disable all local patterns. 80 | * Bug fix: Preview button in the block editor has been fixed for WP 6.7. 81 | * Note: The next major version of Pattern Wrangler (i.e., 1.3.0) will only be compatible with WP 6.7 or higher. The 1.2.x series will involve minor improvements and bug fixes. 82 | 83 | = 1.1.2 = 84 | * Released 2024-08-16 85 | * Loading script translations is now working. 86 | 87 | = 1.1.1 = 88 | * Released 2024-08-16 89 | * Fixing admin script enqueueing for other language support. 90 | 91 | = 1.1.0 = 92 | * Released 2024-05-22 93 | * Updated Pattern Importer icon. 94 | * Added hooks to load custom headers/footers for the preview. 95 | 96 | = 1.0.10 = 97 | * Released 2024-04-18 98 | * Added miscelleanous option to make Patterns exportable via the WP exporter. 99 | * Fixed categories not showing when resetting options. 100 | 101 | = 1.0.9 = 102 | * Released 2024-04-14 103 | * Removed old dead code. 104 | * Fixing settings and docs links. 105 | * Initial WordPress.org release! 106 | 107 | = 1.0.7 = 108 | * Released 2024-04-12 109 | * Fixing sanitization issues. 110 | * Added Fancybox to Patterns screen. See @fancyapps/ui for more information. 111 | * Fixed issue with mapped patterns would not show up if a category was empty. 112 | 113 | = 1.0.3 = 114 | * Released 2024-04-09 115 | * Refactored categories so only registed categories can be mapped to terms. 116 | 117 | = 1.0.1 = 118 | * Added variable height preview image to Patterns screen. 119 | * Added Pattern Categories to Patterns menu item. 120 | * Removing unneeded code. 121 | 122 | = 1.0.0 = 123 | * Initial release. 124 | 125 | == Upgrade Notice == 126 | 127 | = 1.2.0 = 128 | New features: Show or hide all synced or unsynced patterns, or disable both to completely disable all local patterns. Bug fix: Preview has been fixed for WP 6.7. Note: To keep up with the pace of WP development, the nex major version (i.e., 1.3.0) will have WP 6.7 and above as a requirement. -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import './js/blocks/pattern-importer/index'; -------------------------------------------------------------------------------- /src/js/blocks/commands/index.js: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { useCommand } from '@wordpress/commands'; 3 | import { registerPlugin } from '@wordpress/plugins'; 4 | import { settings, upload } from '@wordpress/icons'; 5 | import { 6 | Modal, 7 | SelectControl, 8 | TextControl, 9 | Spinner, 10 | } from '@wordpress/components'; 11 | import SendCommand from '../utils/SendCommand'; 12 | 13 | const GBCommands = () => { 14 | const [ isModalOpen, setIsModalOpen ] = useState( false ); 15 | const [ groupsLoading, setGroupsLoading ] = useState( false ); 16 | const [ groups, setGroups ] = useState( [] ); 17 | 18 | useCommand( { 19 | name: 'dlx-gb-admin-settings', 20 | label: 'Go to GenerateBlocks Settings', 21 | icon: settings, 22 | callback: () => { 23 | document.location.href = 'admin.php?page=generateblocks-settings'; 24 | }, 25 | context: 'block-editor', 26 | } ); 27 | useCommand( { 28 | name: 'dlx-gb-local-patterns', 29 | label: 'Go to GenerateBlocks Local Patterns', 30 | icon: settings, 31 | callback: () => { 32 | document.location.href = 'edit.php?post_type=gblocks_templates'; 33 | }, 34 | context: 'block-editor', 35 | } ); 36 | useCommand( { 37 | name: 'dlx-gb-global-styles', 38 | label: 'Go to GenerateBlocks Global Styles', 39 | icon: settings, 40 | callback: () => { 41 | document.location.href = 'edit.php?post_type=gblocks_templates'; 42 | }, 43 | context: 'block-editor', 44 | } ); 45 | useCommand( { 46 | name: 'dlx-pattern-wrangler-Settings', 47 | label: 'Go to GenerateBlocks (GB) Hacks Settings', 48 | icon: settings, 49 | callback: () => { 50 | document.location.href = 'admin.php?page=dlx-pattern-wrangler'; 51 | }, 52 | context: 'block-editor', 53 | } ); 54 | // useCommand( { 55 | // name: 'dlx-gb-svg-add-asset-library', 56 | // label: 'Add an SVG to the GenerateBlocks Asset Library', 57 | // icon: upload, 58 | // callback: async() => { 59 | // setIsModalOpen( true ); 60 | // setGroupsLoading( true ); 61 | // const response = await SendCommand( 62 | // gbHacksPatternInserter.restNonce, 63 | // {}, 64 | // gbHacksPatternInserter.restUrl + '/get_asset_icon_groups', 65 | // 'get' 66 | // ); 67 | // // Extract out data. 68 | // const { data, success } = response.data; 69 | // if ( success ) { 70 | // setGroups( data.groups ); 71 | // } 72 | // setGroupsLoading( false ); 73 | // }, 74 | // context: 'block-editor', 75 | // } ); 76 | 77 | // const getGroups = () => { 78 | 79 | // } 80 | return ( 81 | <> 82 | { isModalOpen && ( 83 | { 89 | setIsModalOpen( false ); 90 | } } 91 | > 92 | { groupsLoading && ( 93 | <> 94 | 95 | 96 | ) } 97 | 98 | ) } 99 | 100 | ); 101 | }; 102 | 103 | registerPlugin( 'dlxgb-commands', { 104 | render: GBCommands, 105 | } ); 106 | -------------------------------------------------------------------------------- /src/js/blocks/components/AlertButton/editor.scss: -------------------------------------------------------------------------------- 1 | .alerts-dlx-button-popover-base-control { 2 | padding: 16px; 3 | } 4 | .alertx-dlx-button-link { 5 | max-width: 100%; 6 | border: 1px solid #ddd; 7 | border-radius: 2px; 8 | } 9 | 10 | .alerts-dlx-button-wrapper { 11 | position: relative; 12 | display: inline-flex; 13 | align-items: center; 14 | } 15 | .alerts-dlx-button-wrapper { 16 | .alertx-dlx-button-link-icon { 17 | visibility: hidden; 18 | } 19 | 20 | &:hover .alertx-dlx-button-link-icon, &:focus .alertx-dlx-button-link-icon{ 21 | visibility: visible; 22 | } 23 | } 24 | .alerts-dlx-link-toggle { 25 | margin-top: 15px; 26 | } -------------------------------------------------------------------------------- /src/js/blocks/components/AlertButton/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | ToggleControl, 3 | Button, 4 | Popover, 5 | BaseControl, 6 | SlotFillProvider, 7 | } from '@wordpress/components'; 8 | import { URLInput, RichText } from '@wordpress/block-editor'; 9 | import { link } from '@wordpress/icons'; 10 | import { useState } from '@wordpress/element'; 11 | import { __ } from '@wordpress/i18n'; 12 | import './editor.scss'; 13 | 14 | const AlertButton = ( props ) => { 15 | const [ isPopOverVisible, setIsPopOverVisible ] = useState( false ); 16 | const [ isFocusedOutside, setIsFocusedOutside ] = useState( false ); 17 | 18 | const { attributes, setAttributes } = props; 19 | 20 | const { buttonText, buttonUrl, buttonTarget, buttonRelNoFollow, buttonRelSponsored } = 21 | attributes; 22 | 23 | const toggleVisible = () => { 24 | setIsPopOverVisible( ( state ) => ! state ); 25 | }; 26 | 27 | return ( 28 | <> 29 |
33 | 49 |
129 | 130 | ); 131 | }; 132 | 133 | export default AlertButton; 134 | -------------------------------------------------------------------------------- /src/js/blocks/components/GBHacksIcon/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | const GBHacksIcon = ( props ) => ( 3 | 15 | 22 | 29 | 36 | 43 | 50 | 57 | 64 | 65 | ); 66 | export default GBHacksIcon; 67 | -------------------------------------------------------------------------------- /src/js/blocks/components/IconPicker/editor.scss: -------------------------------------------------------------------------------- 1 | .alerts-dlx-icon-popover .components-popover__content > div { 2 | min-width: 350px; 3 | } 4 | .alerts-dlx-icon-picker { 5 | padding: 16px; 6 | text-align: center; 7 | 8 | h2 { 9 | margin: 0; 10 | padding: 0; 11 | } 12 | } 13 | .alerts-dlx-icon-list { 14 | display: grid; 15 | grid-template-columns: 1fr 1fr 1fr; 16 | grid-gap: 10px; 17 | justify-content: center; 18 | max-height: 350px; 19 | overflow: auto; 20 | padding-top: 15px; 21 | 22 | li { 23 | text-align: center; 24 | } 25 | svg { 26 | width: 24px; 27 | height: 24px; 28 | } 29 | } 30 | .alerts-dlx-custom-icon-input { 31 | button { 32 | &:first-child { 33 | margin-right: 15px; 34 | } 35 | &:last-child { 36 | margin-left: 15px; 37 | } 38 | } 39 | } 40 | .components-base-control.alerts-dlx-icon-wrapper { 41 | font-size: 1em; 42 | } 43 | .alerts-dlx-icon-preview { 44 | display: flex; 45 | justify-content: center; 46 | font-size: 1em; 47 | svg { 48 | width: 1.2em; 49 | height: 1.2em; 50 | margin-top: 0.175em; 51 | } 52 | } 53 | .alerts-dlx-custom-icon-preview { 54 | svg { 55 | max-width: 48px; 56 | max-height: 48px; 57 | } 58 | } 59 | button.components-button.alerts-dlx-icon-preview-button { 60 | display: flex; 61 | align-items: flex-start; 62 | justify-content: center; 63 | padding: 0; 64 | margin: 0; 65 | font-size: inherit; 66 | line-height: 0; 67 | } 68 | -------------------------------------------------------------------------------- /src/js/blocks/components/IconPicker/index.js: -------------------------------------------------------------------------------- 1 | import './editor.scss'; 2 | import { __ } from '@wordpress/i18n'; 3 | import { renderToString, useState } from '@wordpress/element'; 4 | import { 5 | BaseControl, 6 | TextControl, 7 | Tooltip, 8 | Button, 9 | Popover, 10 | } from '@wordpress/components'; 11 | import sanitizeSVG from '../../utils/sanitize-svg'; 12 | 13 | const IconPicker = ( props ) => { 14 | const [ isCustomIcon, setIsCustomIcon ] = useState( false ); 15 | const [ selectedIcon, setSelectedIcon ] = useState( props.defaultSvg ); 16 | const [ isPopoverVisible, setIsPopOverVisible ] = useState( false ); 17 | const [ isFocusedOutside, setIsFocusedOutside ] = useState( false ); 18 | 19 | const { defaultSvg, setAttributes, icons } = props; 20 | 21 | /** 22 | * Retrieve popover content for custom icons or regular icons. 23 | * 24 | * @return {string} Popover content. 25 | */ 26 | const getPopoverContent = () => { 27 | if ( ! isCustomIcon ) { 28 | return ( 29 | <> 30 |
    31 | { Object.keys( icons ).map( ( svg, i ) => { 32 | return ( 33 |
  • 34 | 35 | 47 | 48 |
  • 49 | ); 50 | } ) } 51 |
52 | 66 | 67 | ); 68 | } 69 | // Return custom icon interface. 70 | return ( 71 | <> 72 |
73 | 76 |
77 |
78 | { 82 | setSelectedIcon( value ); 83 | } } 84 | /> 85 | 96 | 104 |
105 | 106 | ); 107 | }; 108 | 109 | const toggleVisible = () => { 110 | setIsPopOverVisible( ( state ) => ! state ); 111 | }; 112 | 113 | return ( 114 | <> 115 | 116 |
117 | 129 |
130 |
131 | { isPopoverVisible && ( 132 | { 133 | setIsFocusedOutside( true ); 134 | setIsPopOverVisible( false ); 135 | } }> 136 | 137 |

{ __( 'Select an Icon', 'alerts-dlx' ) }

138 | { getPopoverContent() } 139 |
140 |
141 | ) } 142 | 143 | ); 144 | }; 145 | export default IconPicker; 146 | -------------------------------------------------------------------------------- /src/js/blocks/components/icons/AlertsLogo.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const AlertsLogo = ( props ) => ( 4 | 18 | 24 | 30 | 37 | 38 | ); 39 | 40 | export default AlertsLogo; 41 | -------------------------------------------------------------------------------- /src/js/blocks/components/icons/BootstrapLogo.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-unused-vars 2 | import * as React from 'react'; 3 | 4 | const BootstrapLogo = ( props ) => ( 5 | 17 | 18 | 19 | 20 | ); 21 | 22 | export default BootstrapLogo; 23 | -------------------------------------------------------------------------------- /src/js/blocks/components/icons/ChakraUILogo.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-unused-vars 2 | import * as React from 'react'; 3 | 4 | const ChakraUILogo = ( props ) => ( 5 | 17 | 23 | 30 | 31 | 40 | 47 | 54 | 55 | 56 | 57 | ); 58 | 59 | export default ChakraUILogo; 60 | -------------------------------------------------------------------------------- /src/js/blocks/components/icons/ContainerLogo.js: -------------------------------------------------------------------------------- 1 | const ContainerLogo = () => { 2 | return ( 3 | 4 | ); 5 | } 6 | export default ContainerLogo; -------------------------------------------------------------------------------- /src/js/blocks/components/icons/MaterialIconsLogo.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-unused-vars 2 | import * as React from 'react'; 3 | 4 | const MaterialIconsLogo = ( props ) => ( 5 | 6 | 7 | 11 | 12 | 16 | 17 | 18 | ); 19 | 20 | export default MaterialIconsLogo; 21 | -------------------------------------------------------------------------------- /src/js/blocks/components/icons/ReplaceIcon.js: -------------------------------------------------------------------------------- 1 | const ReplaceIcon = () => { 2 | return ( 3 | 13 | 14 | 15 | ); 16 | }; 17 | export default ReplaceIcon; 18 | -------------------------------------------------------------------------------- /src/js/blocks/components/icons/ShoelaceLogo.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-unused-vars 2 | import * as React from 'react'; 3 | 4 | const ShoelaceLogo = ( props ) => ( 5 | 6 | 11 | 12 | ); 13 | 14 | export default ShoelaceLogo; 15 | -------------------------------------------------------------------------------- /src/js/blocks/components/unit-picker/editor.scss: -------------------------------------------------------------------------------- 1 | /* Unit Picker Component - Forked from @GenerateBlocks */ 2 | .components-has-units-control-header__units { 3 | display: flex; 4 | justify-content: space-between; 5 | margin-bottom: 5px; 6 | align-items: center; 7 | } 8 | 9 | .components-has-control__units { 10 | .components-has-control-buttons__units { 11 | button.components-button { 12 | background: #fff; 13 | box-shadow: none !important; 14 | color: #929da7; 15 | font-size: 10px; 16 | padding: 0 5px; 17 | position: relative; 18 | text-align: center; 19 | text-shadow: none; 20 | border: 0; 21 | border-radius: 0 !important; 22 | line-height: 20px; 23 | padding: 0 5px; 24 | height: auto; 25 | 26 | &.is-primary { 27 | background: #fff !important; 28 | color: #000 !important; 29 | cursor: default; 30 | z-index: 1; 31 | font-weight: bold; 32 | } 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /src/js/blocks/components/unit-picker/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Unit Picker Component. 3 | * Credit: Forked from @GenerateBlocks 4 | */ 5 | 6 | import { __, sprintf, _x } from '@wordpress/i18n'; 7 | import './editor.scss'; 8 | 9 | import { ButtonGroup, Button, Tooltip } from '@wordpress/components'; 10 | 11 | const UnitChooser = ( props ) => { 12 | const { label, value, onClick, units } = props; 13 | 14 | return ( 15 |
16 |
{ label }
17 | 18 |
19 | 23 | { units.map( ( unit ) => { 24 | let unitName = unit; 25 | 26 | if ( 'px' === unit ) { 27 | unitName = _x( 28 | 'Pixel', 29 | 'A size unit for CSS markup', 30 | 'quotes-dlx' 31 | ); 32 | } 33 | 34 | if ( 'em' === unit ) { 35 | unitName = _x( 36 | 'Em', 37 | 'A size unit for CSS markup', 38 | 'quotes-dlx' 39 | ); 40 | } 41 | 42 | if ( '%' === unit ) { 43 | unitName = _x( 44 | 'Percentage', 45 | 'A size unit for CSS markup', 46 | 'quotes-dlx' 47 | ); 48 | } 49 | 50 | if ( 'vw' === unit ) { 51 | unitName = _x( 52 | 'View Width', 53 | 'A size unit for CSS markup', 54 | 'quotes-dlx' 55 | ); 56 | } 57 | 58 | if ( 'rem' === unit ) { 59 | unitName = _x( 60 | 'Rem', 61 | 'A size unit for CSS markup', 62 | 'quotes-dlx' 63 | ); 64 | } 65 | 66 | if ( 'deg' === unit ) { 67 | unitName = _x( 68 | 'Degree', 69 | 'A size unit for CSS markup', 70 | 'quotes-dlx' 71 | ); 72 | } 73 | 74 | return ( 75 | 83 | 98 | 99 | ); 100 | } ) } 101 | 102 |
103 |
104 | ); 105 | }; 106 | 107 | export default UnitChooser; 108 | -------------------------------------------------------------------------------- /src/js/blocks/pattern-importer/block.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | /* eslint-disable no-unused-vars */ 3 | /* eslint-disable camelcase */ 4 | /** 5 | * External dependencies 6 | */ 7 | 8 | import classnames from 'classnames'; 9 | import { useState } from 'react'; 10 | import { __ } from '@wordpress/i18n'; 11 | import uniqueId from 'lodash.uniqueid'; 12 | import { 13 | PanelBody, 14 | PanelRow, 15 | ToggleControl, 16 | TextControl, 17 | Button, 18 | ButtonGroup, 19 | RangeControl, 20 | BaseControl, 21 | TextareaControl, 22 | Card, 23 | CardHeader, 24 | CardFooter, 25 | CardBody, 26 | Spinner, 27 | CheckboxControl, 28 | } from '@wordpress/components'; 29 | 30 | import { parse } from '@wordpress/blocks'; 31 | import { useDispatch } from '@wordpress/data'; 32 | 33 | import { 34 | InspectorControls, 35 | RichText, 36 | useBlockProps, 37 | useInnerBlocksProps, 38 | insertBlocks, 39 | store, 40 | } from '@wordpress/block-editor'; 41 | 42 | import { useInstanceId } from '@wordpress/compose'; 43 | import SendCommand from '../utils/SendCommand'; 44 | 45 | // Image RegEx. 46 | const imageUrlRegex = /(http(?:s?):)([\/|.|@|\w|\s|-])*\.(?:jpg|gif|png|jpeg|webp|avif)/gi; 47 | const uniqueIdRegex = /\"uniqueId\"\:\"([^"]+)\"/gi; 48 | 49 | // Unique ID storing. 50 | const uniqueIds = []; 51 | 52 | // For storing the number of images imported. 53 | let imageCount = 0; 54 | 55 | const escapeRegExp = ( content ) => { 56 | return content.replace( /[.*+\-?^${}()|[\]\\]/g, '\\$&' ); // $& means the whole matched string 57 | }; 58 | 59 | const PatternImporter = ( props ) => { 60 | // Shortcuts. 61 | const { attributes, setAttributes, clientId } = props; 62 | 63 | const [ patternText, setPatternText ] = useState( '' ); 64 | const [ patternImages, setPatternImages ] = useState( [] ); 65 | const [ patternBackgroundImages, setPatternBackgroundImages ] = useState( [] ); 66 | const [ importing, setImporting ] = useState( false ); 67 | const [ imageProcessingCount, setImageProcessingCount ] = useState( 0 ); 68 | const [ doNotImportRemoteImages, setDoNotImportRemoteImages ] = useState( false ); 69 | 70 | const { replaceBlock } = useDispatch( store ); 71 | 72 | const onPatternSubmit = async() => { 73 | setImporting( true ); 74 | const processImage = async( imgUrl, imgAlt ) => { 75 | const response = await SendCommand( 76 | dlxPWPatternInserter.restNonce, 77 | { 78 | imgUrl, 79 | imgAlt, 80 | }, 81 | dlxPWPatternInserter.restUrl + '/process_image' 82 | ); 83 | return response; 84 | }; 85 | 86 | /** 87 | * Import a pattern. 88 | * 89 | * @param {string} pattern The pattern. 90 | */ 91 | const importPattern = ( pattern ) => { 92 | pattern = replaceUniqueIds( pattern ); 93 | 94 | // Convert pattern to blocks. 95 | try { 96 | const patternBlocks = parse( pattern ); 97 | 98 | replaceBlock( clientId, patternBlocks ); 99 | 100 | // Insert block in place of this one. 101 | //replaceInnerBlocks( clientId, patternBlocks ); 102 | } catch ( error ) { 103 | } 104 | }; 105 | 106 | const matches = [ ...patternText.matchAll( imageUrlRegex ) ]; 107 | const imagesToProcess = []; 108 | let localPatternText = patternText; 109 | 110 | if ( ! doNotImportRemoteImages ) { 111 | // If there are matches, we need to process them. 112 | if ( matches.length ) { 113 | matches.forEach( ( match ) => { 114 | // Push if not a duplicate. 115 | if ( ! imagesToProcess.includes( match[ 0 ] ) ) { 116 | imagesToProcess.push( match[ 0 ] ); 117 | } 118 | } ); 119 | setPatternImages( imagesToProcess ); 120 | } 121 | 122 | const imagesProcessed = []; 123 | let imagePromises = []; 124 | 125 | // Let's loop through images and process. 126 | if ( imagesToProcess.length ) { 127 | imagePromises = imagesToProcess.map( ( image ) => { 128 | try { 129 | const response = processImage( image, '' ); 130 | response.then( ( restResponse ) => { 131 | imagesProcessed.push( image ); 132 | const { data, success } = restResponse.data; 133 | if ( success ) { 134 | imageCount++; 135 | setImageProcessingCount( imageCount ); 136 | 137 | // Get the image URL and replace in pattern. 138 | const newImageUrl = data.attachmentUrl; 139 | 140 | // Replace old URL with new URL. 141 | localPatternText = localPatternText.replace( image, newImageUrl ); 142 | setPatternText( localPatternText ); 143 | } else { 144 | // Fail silently. 145 | imageCount++; 146 | setImageProcessingCount( imageCount ); 147 | } 148 | } ).catch( ( error ) => { 149 | // Fail silently. 150 | imageCount++; 151 | setImageProcessingCount( imageCount ); 152 | } ); 153 | return response; 154 | } catch ( error ) { 155 | // Fail silently. 156 | imageCount++; 157 | setImageProcessingCount( imageCount ); 158 | } 159 | } ); 160 | } 161 | 162 | Promise.all( imagePromises ).then( () => { 163 | importPattern( localPatternText ); 164 | } ).catch( ( error ) => { 165 | importPattern( localPatternText ); 166 | } ); 167 | } else { 168 | importPattern( localPatternText ); 169 | } 170 | }; 171 | 172 | /** 173 | * Return and generate a new unique ID. 174 | * 175 | * @param {string} blockPatternText The block pattern text. 176 | * 177 | * @return {string} The blockPatternText. 178 | */ 179 | const replaceUniqueIds = ( blockPatternText ) => { 180 | const pwUniqueIdMatches = [ ...blockPatternText.matchAll( uniqueIdRegex ) ]; 181 | 182 | if ( pwUniqueIdMatches.length ) { 183 | // Loop through matches, generate unique ID, and replace. 184 | pwUniqueIdMatches.forEach( ( match ) => { 185 | const newUniqueId = generateUniqueId(); 186 | uniqueIds.push( newUniqueId ); 187 | blockPatternText.replace( match[ 1 ], `"uniqueId":"${ newUniqueId }"` ); 188 | } ); 189 | } 190 | return blockPatternText; 191 | }; 192 | 193 | /** 194 | * Return and generate a new unique ID. 195 | * 196 | * @return {string} The uniqueId. 197 | */ 198 | const generateUniqueId = () => { 199 | // Get the substr of current client ID for prefix. 200 | const prefix = clientId.substring( 2, 9 ).replace( '-', '' ); 201 | const newUniqueId = uniqueId( prefix ); 202 | 203 | // Make sure it isn't in the array already. Recursive much? 204 | if ( uniqueIds.includes( newUniqueId ) ) { 205 | return generateUniqueId(); 206 | } 207 | return newUniqueId; 208 | }; 209 | 210 | const block = ( 211 | <> 212 | 213 | 214 | { __( 'Pattern Importer', 'pattern-wrangler' ) } 215 | 216 | 217 | setPatternText( value ) } 222 | disabled={ importing } 223 | /> 224 | setDoNotImportRemoteImages( value ) } 228 | disabled={ importing } 229 | /> 230 | 231 | 232 | 239 | { importing && ( 240 | 241 | 242 | { 243 | `Processing ${ imageCount } of ${ patternImages.length } images.` 244 | } 245 | 246 | ) } 247 | 248 | 249 | 250 | ); 251 | 252 | const blockProps = useBlockProps( { className: 'dlx-pattern-inserter-wrapper' } ); 253 | 254 | return ( 255 | <> 256 |
{ block }
257 | 258 | ); 259 | }; 260 | 261 | export default PatternImporter; 262 | -------------------------------------------------------------------------------- /src/js/blocks/pattern-importer/block.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schemas.wp.org/trunk/block.json", 3 | "title": "Pattern Inserter", 4 | "apiVersion": 3, 5 | "name": "dlxplugins/dlx-pw-pattern-inserter", 6 | "category": "design", 7 | "icon": "", 8 | "description": "Paste in a pattern and it will be inserted for you.", 9 | "keywords": ["remote", "pattern", "inserter"], 10 | "version": "1.0.0", 11 | "textdomain": "dlx-pattern-wrangler", 12 | "attributes": { 13 | "preview": { 14 | "type": "boolean", 15 | "default": false 16 | } 17 | }, 18 | "example": { 19 | "attributes": { 20 | "preview": true 21 | } 22 | }, 23 | "editorScript": "dlx-pw-pattern-inserter-block" 24 | } 25 | -------------------------------------------------------------------------------- /src/js/blocks/pattern-importer/index.js: -------------------------------------------------------------------------------- 1 | import { registerBlockType, createBlock } from '@wordpress/blocks'; 2 | import Edit from './block'; 3 | import metaData from './block.json'; 4 | 5 | const PatternIcon = ( 6 | 17 | 24 | 31 | 38 | 45 | 52 | 59 | 66 | 73 | 80 | 81 | ); 82 | 83 | registerBlockType(metaData, { 84 | edit: Edit, 85 | save() { 86 | return null; 87 | }, 88 | icon: PatternIcon, 89 | }); 90 | -------------------------------------------------------------------------------- /src/js/blocks/plugins/pattern-preview.js: -------------------------------------------------------------------------------- 1 | import { __ } from '@wordpress/i18n'; 2 | import { registerPlugin } from '@wordpress/plugins'; 3 | import { useSelect } from '@wordpress/data'; 4 | 5 | // Try to get ActionItem, but don't fail if it's not available 6 | let PluginPreviewMenuItem; 7 | try { 8 | const { PluginPreviewMenuItem: ImportedPluginPreviewMenuItem } = require( '@wordpress/editor' ); 9 | PluginPreviewMenuItem = ImportedPluginPreviewMenuItem; 10 | } catch ( e ) { 11 | // ActionItem not available 12 | } 13 | 14 | /** 15 | * Render a Preview Button. 16 | * 17 | * @return {Object|null} The rendered component or null if ActionItem not available. 18 | */ 19 | const PatternPreviewButton = () => { 20 | // Return early if ActionItem isn't available 21 | if ( ! PluginPreviewMenuItem ) { 22 | return null; 23 | } 24 | 25 | return ( 26 | { 30 | window.open( dlxPatternWranglerPreview.previewUrl, '_blank' ); 31 | } } 32 | > 33 | { __( 'Preview Pattern', 'pattern-wrangler' ) } 34 | 35 | ); 36 | }; 37 | 38 | // Only register if ActionItem is available 39 | if ( PluginPreviewMenuItem ) { 40 | registerPlugin( 'dlx-pattern-wrangler-preview-button', { 41 | render: PatternPreviewButton, 42 | } ); 43 | } 44 | 45 | -------------------------------------------------------------------------------- /src/js/blocks/sidebar/index.js: -------------------------------------------------------------------------------- 1 | import { registerPlugin } from '@wordpress/plugins'; 2 | import { PluginSidebar } from '@wordpress/edit-post'; 3 | import { PanelBody } from '@wordpress/components'; 4 | import GBHacksIcon from '../components/GBHacksIcon'; 5 | const MySidebar = () => ( 6 | 10 | 11 | asdfsadf 12 | 13 | 14 | ); 15 | 16 | registerPlugin( 'dlx-gb-sidebar', { 17 | icon: , 18 | render: MySidebar, 19 | } ); 20 | -------------------------------------------------------------------------------- /src/js/blocks/utils/SendCommand.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | /* eslint-disable camelcase */ 3 | import axios from 'axios'; 4 | import qs from 'qs'; 5 | 6 | /** 7 | * Send a REST request via JS. 8 | * 9 | * @param {string} nonce The REST nonce. 10 | * @param {Object} data The REST data to pass. 11 | * @param {string} restEndPoint The REST endpoint to use. 12 | * @param {string} method The REST method to use. Defaults to 'post'. 13 | * @return {Promise} The REST request promise. 14 | */ 15 | export default function SendCommand( nonce, data, restEndPoint, method = 'post' ) { 16 | if ( 'undefined' === typeof data ) { 17 | data = {}; 18 | } 19 | 20 | const options = { 21 | method, 22 | url: restEndPoint, 23 | params: data, 24 | headers: { 25 | 'X-WP-Nonce': nonce, 26 | }, 27 | data, 28 | }; 29 | 30 | return axios( options ); 31 | } 32 | -------------------------------------------------------------------------------- /src/js/blocks/utils/sanitize-svg/index.js: -------------------------------------------------------------------------------- 1 | import DOMPurify from 'dompurify'; 2 | 3 | export default function sanitizeSVG( svg ) { 4 | return DOMPurify.sanitize( svg, { USE_PROFILES: { svg: true, svgFilters: true } } ); 5 | } 6 | -------------------------------------------------------------------------------- /src/js/fancybox/index.js: -------------------------------------------------------------------------------- 1 | import {Fancybox } from '@fancyapps/ui/dist/fancybox/fancybox.umd.js'; 2 | import "@fancyapps/ui/dist/fancybox/fancybox.css"; 3 | 4 | document.addEventListener("DOMContentLoaded", function () { 5 | const patternPreviews = document.querySelectorAll( '.admin-fancybox' ); 6 | if ( null !== patternPreviews ) { 7 | patternPreviews.forEach( function ( patternPreview ) { 8 | patternPreview.addEventListener( 'click', function ( event ) { 9 | event.preventDefault(); 10 | const anchor = event.target.closest( 'a' ); 11 | Fancybox.show( [ { 12 | src: anchor.href, 13 | caption: anchor.title, 14 | type: 'image', 15 | zoom: false, 16 | compact: true, 17 | width: '60%', 18 | } ] ); 19 | } ); 20 | } ); 21 | } 22 | } ); -------------------------------------------------------------------------------- /src/js/react/components/Notice/index.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-unused-vars 2 | import React, { useEffect } from 'react'; 3 | import PropTypes from 'prop-types'; // ES6 4 | import { speak } from '@wordpress/a11y'; 5 | import { __ } from '@wordpress/i18n'; 6 | import { Notice as WPNotice } from '@wordpress/components'; 7 | import classNames from 'classnames'; 8 | 9 | const Notice = ( props ) => { 10 | const { message, status, politeness, icon, className, inline, children, hasToTop = false } = props; 11 | 12 | useEffect( () => { 13 | speak( message, politeness ); 14 | }, [ message, status, politeness ] ); 15 | 16 | const hasIcon = () => { 17 | return icon !== null; 18 | }; 19 | const getIcon = ( Icon ) => { 20 | return ; 21 | }; 22 | 23 | const containerClasses = classNames( className, 'dlx-pw-admin__notice', { 24 | 'dlx-pw-admin__notice--has-icon': hasIcon(), 25 | [ `dlx-pw-admin__notice-type--${ status }` ]: true, 26 | [ `dlx-pw-admin__notice-appearance--inline` ]: inline, 27 | [ `dlx-pw-admin__notice-appearance--block` ]: ! inline, 28 | } ); 29 | 30 | const actions = [ 31 | { 32 | label: __( 'Back to Top', 'wp-dlx-pw-comments' ), 33 | url: '#dlx-pw-admin-header', 34 | variant: 'link', 35 | className: 'dlx-pw-admin__notice-action dlx-pw-admin__notice-action--to-top', 36 | } ]; 37 | return ( 38 |
39 | 40 | { hasIcon() && 41 |
{ getIcon( icon ) }
42 | } 43 |
<>{ message } { children }
44 |
45 |
46 | ); 47 | }; 48 | 49 | Notice.defaultProps = { 50 | message: '', 51 | status: 'info', 52 | politeness: 'polite', 53 | icon: null, 54 | className: '', 55 | inline: false, 56 | hasToTop: false, 57 | }; 58 | 59 | Notice.propTypes = { 60 | message: PropTypes.string.isRequired, 61 | status: PropTypes.oneOf( [ 'info', 'warning', 'success', 'error' ] ), 62 | politeness: PropTypes.oneOf( [ 'assertive', 'polite' ] ), 63 | icon: PropTypes.func, 64 | className: PropTypes.string, 65 | inline: PropTypes.bool, 66 | hasToTop: PropTypes.bool, 67 | }; 68 | 69 | export default Notice; 70 | -------------------------------------------------------------------------------- /src/js/react/components/SaveResetButtons/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Loader2, ClipboardCheck } from 'lucide-react'; 3 | import { __ } from '@wordpress/i18n'; 4 | import classNames from 'classnames'; 5 | import { Button, Snackbar } from '@wordpress/components'; 6 | import Notice from '../Notice'; 7 | import SendCommand from '../../utils/SendCommand'; 8 | import SnackPop from '../SnackPop'; 9 | 10 | export function onSave( formData, setError ) { 11 | 12 | } 13 | 14 | export function onReset( { formValues, setError, reset } ) { 15 | 16 | } 17 | 18 | const SaveResetButtons = ( props ) => { 19 | // Gather props. 20 | const { 21 | formValues, 22 | setError, 23 | reset, 24 | errors, 25 | isDirty, 26 | dirtyFields, 27 | trigger, 28 | } = props; 29 | 30 | const [ saving, setSaving ] = useState( false ); 31 | const [ resetting, setResetting ] = useState( false ); 32 | const [ isSaved, setIsSaved ] = useState( false ); 33 | const [ isReset, setIsReset ] = useState( false ); 34 | const [ savePromise, setSavePromise ] = useState( null ); 35 | const [ resetPromise, setResetPromise ] = useState( null ); 36 | 37 | /** 38 | * Save the options by setting promise as state. 39 | */ 40 | const saveOptions = async () => { 41 | const saveOptionsPromise = SendCommand( 'dlx_pw_save_options', { formData: formValues } ); 42 | setSavePromise( saveOptionsPromise ); 43 | setSaving( true ); 44 | await saveOptionsPromise; 45 | setSaving( false ); 46 | }; 47 | 48 | /** 49 | * Reset the options by setting promise as state. 50 | */ 51 | const resetOptions = async () => { 52 | const resetOptionsPromise = SendCommand( 'dlx_pw_reset_options', { formData: formValues } ); 53 | setResetPromise( resetOptionsPromise ); 54 | setResetting( true ); 55 | const resetResponse = await resetOptionsPromise; 56 | reset( 57 | resetResponse.data.data.formData, 58 | { 59 | keepErrors: false, 60 | keepDirty: false, 61 | }, 62 | ); 63 | setResetting( false ); 64 | }; 65 | 66 | const hasErrors = () => { 67 | return Object.keys( errors ).length > 0; 68 | }; 69 | 70 | const getSaveIcon = () => { 71 | if ( saving ) { 72 | return () => ; 73 | } 74 | if ( isSaved ) { 75 | return () => ; 76 | } 77 | return false; 78 | }; 79 | 80 | const getSaveText = () => { 81 | if ( saving ) { 82 | return __( 'Saving…', 'pattern-wrangler' ); 83 | } 84 | if ( isSaved ) { 85 | return __( 'Saved', 'pattern-wrangler' ); 86 | } 87 | return __( 'Save Options', 'pattern-wrangler' ); 88 | }; 89 | 90 | const getResetText = () => { 91 | if ( resetting ) { 92 | return __( 'Resetting to Defaults…', 'pattern-wrangler' ); 93 | } 94 | if ( isReset ) { 95 | return __( 'Options Restored to Defaults', 'pattern-wrangler' ); 96 | } 97 | return __( 'Reset to Defaults', 'pattern-wrangler' ); 98 | }; 99 | 100 | return ( 101 | <> 102 |
103 |
146 |
147 | 151 | 155 | { hasErrors() && ( 156 | 164 | ) } 165 |
166 | 167 | ); 168 | }; 169 | export default SaveResetButtons; 170 | -------------------------------------------------------------------------------- /src/js/react/components/SnackPop/index.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { Loader2, CheckCircle2, AlertCircle } from 'lucide-react'; 3 | import { Snackbar as WPSnackBar, Modal, Button } from '@wordpress/components'; 4 | import classnames from 'classnames'; 5 | import { __ } from '@wordpress/i18n'; 6 | import Notice from '../Notice'; 7 | 8 | /** 9 | * SnackPop is a component which handles alerts and notifications for the user. 10 | * It can handle multiple alerts at once, toggles and forms, and will display the notifications in a queue. 11 | * 12 | * @param {Object} props Component props. 13 | * 14 | * @return {Element} JSX markup for the component. 15 | */ 16 | const SnackPop = ( props ) => { 17 | const { ajaxOptions, loadingMessage } = props; 18 | 19 | const snackbarDefaults = { 20 | type: 'info', 21 | message: '', 22 | title: '', 23 | isDismissable: false, 24 | isPersistent: false, 25 | isSuccess: false, 26 | loadingMessage, 27 | politeness: 'polite', /* can also be assertive */ 28 | }; 29 | 30 | const [ notificationOptions, setNotificationOptions ] = useState( snackbarDefaults ); 31 | const [ isBusy, setIsBusy ] = useState( false ); 32 | const [ isModalVisible, setIsModalVisible ] = useState( false ); 33 | const [ isSnackbarVisible, setIsSnackbarVisible ] = useState( false ); 34 | const [ snackbarTimeout, setSnackbarTimeout ] = useState( null ); 35 | 36 | useEffect( () => { 37 | const getPromise = async () => { 38 | const response = await ajaxOptions; 39 | return response; 40 | }; 41 | if ( ajaxOptions instanceof Promise ) { 42 | // Set state to busy. 43 | setNotificationOptions( snackbarDefaults ); 44 | setIsSnackbarVisible( true ); 45 | setIsBusy( true ); 46 | 47 | getPromise().then( ( response ) => { 48 | const { data } = response; 49 | const { success: isSuccess } = data; 50 | const { data: responseData } = data; 51 | 52 | // Get the type of notification. (error, info, success, warning, critical, confirmation). 53 | const type = responseData.type || 'info'; 54 | 55 | // Get the message. 56 | const message = responseData.message || ''; 57 | 58 | // Get the title. 59 | const title = responseData.title || ''; /* title of snackbar or modal */ 60 | 61 | // Get whether the notification is dismissable. 62 | const isDismissable = responseData.dismissable || false; /* whether the snackbar or modal is dismissable */ 63 | 64 | // Get whether the notification is persistent. 65 | const isPersistent = responseData.persistent || false; /* whether the snackbar or modal is persistent */ 66 | 67 | // Get the politeness based on if successful. 68 | const politeness = isSuccess ? 'polite' : 'assertive'; 69 | 70 | // Set state with the notification. 71 | setNotificationOptions( { 72 | type, 73 | message, 74 | title, 75 | isDismissable, 76 | isBusy: false, 77 | isPersistent, 78 | politeness, 79 | } ); 80 | 81 | if ( isSuccess ) { 82 | //onSuccess( notificationOptions ); 83 | } else { 84 | //onError( notificationOptions ); 85 | } 86 | if ( 'critical' === type ) { 87 | setIsSnackbarVisible( false ); 88 | setIsModalVisible( true ); 89 | } else { 90 | clearTimeout( snackbarTimeout ); 91 | setSnackbarTimeout( setTimeout( () => { 92 | setIsSnackbarVisible( false ); 93 | setNotificationOptions( snackbarDefaults ); 94 | }, 6000 ) ); 95 | } 96 | } ).catch( ( error ) => { 97 | // Handle error 98 | setNotificationOptions( { 99 | type: 'critical', 100 | message: error.message, 101 | title: __( 'An Error Has Occurred', 'pattern-wrangler' ), 102 | isDismissable: false, 103 | isBusy: false, 104 | isPersistent: true, 105 | politeness: 'assertive', 106 | } ); 107 | //onError( notificationOptions ); 108 | } ).then( () => { 109 | // Set state to not busy. 110 | setIsBusy( false ); 111 | } ); 112 | } 113 | }, [ ajaxOptions ] ); 114 | 115 | // Bail if no promise. 116 | if ( null === ajaxOptions ) { 117 | return ( 118 | <> 119 | ); 120 | } 121 | 122 | /** 123 | * Gets the icon for the notification. 124 | * 125 | * @return {Element} JSX markup for the icon. 126 | */ 127 | const getIcon = () => { 128 | switch ( notificationOptions.type ) { 129 | case 'success': 130 | return ; 131 | case 'error': 132 | case 'critical': 133 | return ; 134 | default: 135 | return ; 136 | } 137 | }; 138 | 139 | const getSnackbarActions = () => { 140 | const actions = []; 141 | if ( notificationOptions.type === 'success' ) { 142 | actions.push( { 143 | label: __( 'Back to Top', 'pattern-wrangler' ), 144 | url: '#dlx-pw-admin-header', 145 | variant: 'link', 146 | className: 'dlx-pw-admin__notice-action dlx-pw-admin__notice-action--to-top', 147 | } ); 148 | } 149 | return actions; 150 | }; 151 | 152 | const getSnackBar = () => { 153 | return ( 154 | setIsSnackbarVisible( false ) } 166 | explicitDismiss={ notificationOptions.isDismissable } 167 | > 168 | { isBusy ? loadingMessage : notificationOptions.message } 169 | 170 | ); 171 | }; 172 | 173 | const getModal = () => { 174 | if ( 'critical' === notificationOptions.type ) { 175 | return ( 176 | { 188 | setIsModalVisible( false ); 189 | } } 190 | isDismissible={ true } 191 | shouldCloseOnClickOutside={ notificationOptions.isPersistent } 192 | shouldCloseOnEsc={ notificationOptions.isPersistent } 193 | > 194 | 201 |
202 | 211 |
212 |
213 | ); 214 | } 215 | }; 216 | 217 | return ( 218 | <> 219 | { isSnackbarVisible && getSnackBar() } { /* Show snackbar */ } 220 | { isModalVisible && getModal() } { /* Show modal */ } 221 | 222 | ); 223 | }; 224 | export default SnackPop; 225 | -------------------------------------------------------------------------------- /src/js/react/utils/SendCommand.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | /* eslint-disable camelcase */ 3 | import axios from 'axios'; 4 | import qs from 'qs'; 5 | 6 | export default function sendCommand( action, data, ajaxUrl = '' ) { 7 | const params = { 8 | action, 9 | }; 10 | 11 | const default_data = { 12 | nonce: false, 13 | action, 14 | }; 15 | 16 | if ( 'undefined' === typeof data ) { 17 | data = {}; 18 | } 19 | 20 | for ( const opt in default_data ) { 21 | if ( ! data.hasOwnProperty( opt ) ) { 22 | data[ opt ] = default_data[ opt ]; 23 | } 24 | } 25 | 26 | let sendAjaxUrl = ''; 27 | 28 | if ( typeof ajaxurl === 'undefined' ) { 29 | sendAjaxUrl = ajaxUrl; 30 | } else { 31 | sendAjaxUrl = ajaxurl; 32 | } 33 | 34 | const options = { 35 | method: 'post', 36 | url: sendAjaxUrl, 37 | params, 38 | paramsSerializer( jsparams ) { 39 | return qs.stringify( jsparams, { arrayFormat: 'brackets' } ); 40 | }, 41 | data: qs.stringify( data ), 42 | }; 43 | 44 | return axios( options ); 45 | } 46 | -------------------------------------------------------------------------------- /src/js/react/views/license/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { createRoot } from 'react-dom/client'; 4 | import License from './license'; 5 | 6 | const container = document.getElementById( 'dlx-pw-license' ); 7 | const root = createRoot( container ); 8 | root.render( 9 | 10 | 11 | 12 | ); 13 | -------------------------------------------------------------------------------- /src/js/react/views/license/license.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-unused-vars 2 | import React, { Suspense, useState } from 'react'; 3 | import { 4 | Button, 5 | TextControl, 6 | } from '@wordpress/components'; 7 | import { __ } from '@wordpress/i18n'; 8 | import { useForm, Controller, useWatch, useFormState } from 'react-hook-form'; 9 | import { useAsyncResource } from 'use-async-resource'; 10 | import classNames from 'classnames'; 11 | import { CheckCircle, Key, Loader2, Eye, EyeOff, AlertCircle } from 'lucide-react'; 12 | 13 | // Local imports. 14 | import SendCommand from '../../utils/SendCommand'; 15 | import Notice from '../../components/Notice'; 16 | 17 | const retrieveOptions = () => { 18 | return SendCommand( 'dlx_pw_license_get_options', { 19 | nonce: dlxPatternWranglerLicense.getNonce, 20 | } ); 21 | }; 22 | 23 | const License = ( props ) => { 24 | const [ defaults ] = useAsyncResource( 25 | retrieveOptions, 26 | [] 27 | ); 28 | return ( 29 | 32 |

{ __( 'Loading…', 'pattern-wrangler' ) }

33 | 34 | } 35 | > 36 | 37 |
38 | ); 39 | }; 40 | 41 | const Interface = ( props ) => { 42 | const { defaults } = props; 43 | const response = defaults(); 44 | const { data } = response.data; 45 | 46 | const [ showSecret, setShowSecret ] = useState( data.licenseValid ? false : true ); 47 | const [ licenseData, setLicenseData ] = useState( data.licenseData ); 48 | const [ saving, setSaving ] = useState( false ); 49 | const [ isSaved, setIsSaved ] = useState( false ); 50 | const [ betaSaving, setBetaSaving ] = useState( false ); 51 | const [ betaEnabled, setBetaEnabled ] = useState( data.beta ); 52 | const [ revokingLicense, setRevokingLicense ] = useState( false ); 53 | const [ isRevoked, setIsRevoked ] = useState( false ); 54 | const [ validLicense, setValidLicense ] = useState( data.licenseKey ); 55 | 56 | const hasErrors = () => { 57 | return Object.keys( errors ).length > 0; 58 | }; 59 | 60 | const { 61 | control, 62 | handleSubmit, 63 | getValues, 64 | reset, 65 | setError, 66 | trigger, 67 | setValue, 68 | } = useForm( { 69 | defaultValues: { 70 | licenseKey: data.licenseKey, 71 | priceId: data.priceId, 72 | licenseValid: data.licenseValid, 73 | }, 74 | } ); 75 | 76 | 77 | const formValues = useWatch( { control } ); 78 | const { errors, isDirty, dirtyFields } = useFormState( { 79 | control, 80 | } ); 81 | 82 | 83 | const onSubmit = ( formData ) => { 84 | setSaving( true ); 85 | SendCommand( 'dlx_pw_save_license', { 86 | nonce: dlxPatternWranglerLicense.saveNonce, 87 | formData, 88 | } ) 89 | .then( ( ajaxResponse ) => { 90 | const ajaxData = ajaxResponse.data.data; 91 | const ajaxSuccess = ajaxResponse.data.success; 92 | 93 | 94 | if ( ajaxSuccess ) { 95 | reset( ajaxData, { 96 | keepErrors: false, 97 | keepDirty: false, 98 | } ); 99 | setLicenseData( ajaxData.licenseData ); 100 | setIsSaved( true ); 101 | 102 | // Reset count. 103 | setTimeout( () => { 104 | setIsSaved( false ); 105 | }, 3000 ); 106 | } else { 107 | // Error stuff. 108 | setError( 'licenseKey', { 109 | type: 'validate', 110 | message: ajaxData.message, 111 | } ); 112 | } 113 | } ) 114 | .catch( ( ajaxResponse ) => {} ) 115 | .then( ( ajaxResponse ) => { 116 | setSaving( false ); 117 | } ); 118 | }; 119 | 120 | const revokeLicense = ( e ) => { 121 | setRevokingLicense( true ); 122 | SendCommand( 'dlx_pw_revoke_license', { 123 | nonce: dlxPatternWranglerLicense.revokeNonce, 124 | formData: formValues, 125 | } ) 126 | .then( ( ajaxResponse ) => { 127 | const ajaxData = ajaxResponse.data.data; 128 | const ajaxSuccess = ajaxResponse.data.success; 129 | if ( ajaxSuccess ) { 130 | // // Reset count. 131 | setIsRevoked( true ); 132 | reset( ajaxData, { 133 | keepErrors: false, 134 | keepDirty: false, 135 | } ); 136 | setTimeout( () => { 137 | setIsRevoked( false ); 138 | }, 3000 ); 139 | } else { 140 | setError( 'licenseKey', { 141 | type: 'validate', 142 | message: __( 143 | 'Revoking the license resulted in an error and could not be deactivated. Please reactivate the license and try again.', 144 | 'pattern-wrangler' 145 | ), 146 | } ); 147 | } 148 | } ) 149 | .catch( ( ajaxResponse ) => {} ) 150 | .then( ( ajaxResponse ) => { 151 | setRevokingLicense( false ); 152 | } ); 153 | }; 154 | 155 | /** 156 | * Retrieve a license type for a user. 157 | * 158 | * @return {string} License type. 159 | */ 160 | const getLicenseType = () => { 161 | switch ( getValues( 'priceId' ) ) { 162 | case '1': 163 | return 'Guru'; 164 | case '2': 165 | return 'Freelancer'; 166 | case '3': 167 | return 'Agency'; 168 | case '4': 169 | return 'Unlimited'; 170 | default: 171 | return 'Subscriber'; 172 | } 173 | }; 174 | 175 | /** 176 | * Retrieve a license notice. 177 | * 178 | * @return {React.ReactElement} Notice. 179 | */ 180 | const getLicenseNotice = () => { 181 | if ( getValues( 'licenseValid' ) ) { 182 | return ( 183 | } 188 | /> 189 | ); 190 | } 191 | return ( 192 | } 200 | /> 201 | ); 202 | }; 203 | 204 | const getSaveButton = () => { 205 | let saveText = __( 'Save License', 'pattern-wrangler' ); 206 | let saveTextLoading = __( 'Saving…', 'pattern-wrangler' ); 207 | 208 | if ( ! getValues( 'licenseValid' ) ) { 209 | saveText = __( 'Activate License', 'pattern-wrangler' ); 210 | saveTextLoading = __( 'Activating…', 'pattern-wrangler' ); 211 | } 212 | return ( 213 | <> 214 |