├── .editorconfig ├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── dist ├── blocks.build.js ├── blocks.editor.build.css └── blocks.style.build.css ├── package-lock.json ├── package.json ├── plugin.php ├── readme.md └── src ├── block ├── block.js ├── editor.scss └── style.scss ├── blocks.js ├── common.scss └── init.php /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs 2 | # editorconfig.org 3 | 4 | # WordPress Coding Standards 5 | # https://make.wordpress.org/core/handbook/coding-standards/ 6 | 7 | root = true 8 | 9 | [*] 10 | charset = utf-8 11 | end_of_line = lf 12 | insert_final_newline = true 13 | trim_trailing_whitespace = true 14 | indent_style = tab 15 | 16 | [*.yml] 17 | indent_style = space 18 | indent_size = 2 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/*.min.js 2 | **/*.build.js 3 | **/node_modules/** 4 | **/vendor/** 5 | build 6 | coverage 7 | cypress 8 | node_modules 9 | vendor 10 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "babel-eslint", 4 | "extends": [ 5 | "wordpress", 6 | "plugin:react/recommended", 7 | "plugin:jsx-a11y/recommended", 8 | "plugin:jest/recommended" 9 | ], 10 | "env": { 11 | "browser": false, 12 | "es6": true, 13 | "node": true, 14 | "mocha": true, 15 | "jest/globals": true 16 | }, 17 | "parserOptions": { 18 | "sourceType": "module", 19 | "ecmaFeatures": { 20 | "jsx": true 21 | } 22 | }, 23 | "globals": { 24 | "wp": true, 25 | "wpApiSettings": true, 26 | "window": true, 27 | "document": true 28 | }, 29 | "plugins": ["react", "jsx-a11y", "jest"], 30 | "settings": { 31 | "react": { 32 | "pragma": "wp" 33 | } 34 | }, 35 | "rules": { 36 | "array-bracket-spacing": ["error", "always"], 37 | "brace-style": ["error", "1tbs"], 38 | "camelcase": ["error", { "properties": "never" }], 39 | "comma-dangle": ["error", "always-multiline"], 40 | "comma-spacing": "error", 41 | "comma-style": "error", 42 | "computed-property-spacing": ["error", "always"], 43 | "constructor-super": "error", 44 | "dot-notation": "error", 45 | "eol-last": "error", 46 | "eqeqeq": "error", 47 | "func-call-spacing": "error", 48 | "indent": ["error", "tab", { "SwitchCase": 1 }], 49 | "jsx-a11y/label-has-for": [ 50 | "error", 51 | { 52 | "required": "id" 53 | } 54 | ], 55 | "jsx-a11y/media-has-caption": "off", 56 | "jsx-a11y/no-noninteractive-tabindex": "off", 57 | "jsx-a11y/role-has-required-aria-props": "off", 58 | "jsx-quotes": "error", 59 | "key-spacing": "error", 60 | "keyword-spacing": "error", 61 | "lines-around-comment": "off", 62 | "no-alert": "error", 63 | "no-bitwise": "error", 64 | "no-caller": "error", 65 | "no-console": "error", 66 | "no-const-assign": "error", 67 | "no-debugger": "error", 68 | "no-dupe-args": "error", 69 | "no-dupe-class-members": "error", 70 | "no-dupe-keys": "error", 71 | "no-duplicate-case": "error", 72 | "no-duplicate-imports": "error", 73 | "no-else-return": "error", 74 | "no-eval": "error", 75 | "no-extra-semi": "error", 76 | "no-fallthrough": "error", 77 | "no-lonely-if": "error", 78 | "no-mixed-operators": "error", 79 | "no-mixed-spaces-and-tabs": "error", 80 | "no-multiple-empty-lines": ["error", { "max": 1 }], 81 | "no-multi-spaces": "error", 82 | "no-multi-str": "off", 83 | "no-negated-in-lhs": "error", 84 | "no-nested-ternary": "error", 85 | "no-redeclare": "error", 86 | "no-restricted-syntax": [ 87 | "error", 88 | { 89 | "selector": 90 | "ImportDeclaration[source.value=/^@wordpress\\u002F.+\\u002F/]", 91 | "message": "Path access on WordPress dependencies is not allowed." 92 | }, 93 | { 94 | "selector": "ImportDeclaration[source.value=/^blocks$/]", 95 | "message": "Use @wordpress/blocks as import path instead." 96 | }, 97 | { 98 | "selector": "ImportDeclaration[source.value=/^components$/]", 99 | "message": "Use @wordpress/components as import path instead." 100 | }, 101 | { 102 | "selector": "ImportDeclaration[source.value=/^date$/]", 103 | "message": "Use @wordpress/date as import path instead." 104 | }, 105 | { 106 | "selector": "ImportDeclaration[source.value=/^editor$/]", 107 | "message": "Use @wordpress/editor as import path instead." 108 | }, 109 | { 110 | "selector": "ImportDeclaration[source.value=/^element$/]", 111 | "message": "Use @wordpress/element as import path instead." 112 | }, 113 | { 114 | "selector": "ImportDeclaration[source.value=/^i18n$/]", 115 | "message": "Use @wordpress/i18n as import path instead." 116 | }, 117 | { 118 | "selector": "ImportDeclaration[source.value=/^data$/]", 119 | "message": "Use @wordpress/data as import path instead." 120 | }, 121 | { 122 | "selector": "ImportDeclaration[source.value=/^utils$/]", 123 | "message": "Use @wordpress/utils as import path instead." 124 | }, 125 | { 126 | "selector": 127 | "CallExpression[callee.name=/^__|_n|_x$/]:not([arguments.0.type=/^Literal|BinaryExpression$/])", 128 | "message": "Translate function arguments must be string literals." 129 | }, 130 | { 131 | "selector": 132 | "CallExpression[callee.name=/^_n|_x$/]:not([arguments.1.type=/^Literal|BinaryExpression$/])", 133 | "message": "Translate function arguments must be string literals." 134 | }, 135 | { 136 | "selector": 137 | "CallExpression[callee.name=_nx]:not([arguments.2.type=/^Literal|BinaryExpression$/])", 138 | "message": "Translate function arguments must be string literals." 139 | } 140 | ], 141 | "no-shadow": "error", 142 | "no-undef": "error", 143 | "no-undef-init": "error", 144 | "no-unreachable": "error", 145 | "no-unsafe-negation": "error", 146 | "no-unused-expressions": "error", 147 | "no-unused-vars": "error", 148 | "no-useless-computed-key": "error", 149 | "no-useless-constructor": "error", 150 | "no-useless-return": "error", 151 | "no-var": "error", 152 | "no-whitespace-before-property": "error", 153 | "object-curly-spacing": ["error", "always"], 154 | "padded-blocks": ["error", "never"], 155 | "prefer-const": "error", 156 | "quote-props": ["error", "as-needed"], 157 | "react/display-name": "off", 158 | "react/jsx-curly-spacing": [ 159 | "error", 160 | { 161 | "when": "always", 162 | "children": true 163 | } 164 | ], 165 | "react/jsx-equals-spacing": "error", 166 | "react/jsx-indent": ["error", "tab"], 167 | "react/jsx-indent-props": ["error", "tab"], 168 | "react/jsx-key": "error", 169 | "react/jsx-tag-spacing": "error", 170 | "react/no-children-prop": "off", 171 | "react/no-find-dom-node": "warn", 172 | "react/prop-types": "off", 173 | "semi": "error", 174 | "semi-spacing": "error", 175 | "space-before-blocks": ["error", "always"], 176 | "space-before-function-paren": ["error", "never"], 177 | "space-in-parens": ["error", "always"], 178 | "space-infix-ops": ["error", { "int32Hint": false }], 179 | "space-unary-ops": [ 180 | "error", 181 | { 182 | "overrides": { 183 | "!": true 184 | } 185 | } 186 | ], 187 | "template-curly-spacing": ["error", "always"], 188 | "valid-jsdoc": ["error", { "requireReturn": false }], 189 | "valid-typeof": "error", 190 | "yoda": "off" 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | ## Uncomment line below if you prefer to 4 | ## keep compiled files out of version control 5 | # dist/ 6 | -------------------------------------------------------------------------------- /dist/blocks.build.js: -------------------------------------------------------------------------------- 1 | !function(e){function t(r){if(n[r])return n[r].exports;var o=n[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,t),o.l=!0,o.exports}var n={};t.m=e,t.c=n,t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:r})},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s=0)}([function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});n(1)},function(e,t,n){"use strict";function r(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t { 60 | const handleAddLocation = () => { 61 | const locations = [ ...props.attributes.locations ]; 62 | locations.push( { 63 | address: '', 64 | } ); 65 | props.setAttributes( { locations } ); 66 | }; 67 | 68 | const handleRemoveLocation = ( index ) => { 69 | const locations = [ ...props.attributes.locations ]; 70 | locations.splice( index, 1 ); 71 | props.setAttributes( { locations } ); 72 | }; 73 | 74 | const handleLocationChange = ( address, index ) => { 75 | const locations = [ ...props.attributes.locations ]; 76 | locations[ index ].address = address; 77 | props.setAttributes( { locations } ); 78 | }; 79 | 80 | let locationFields, 81 | locationDisplay; 82 | 83 | if ( props.attributes.locations.length ) { 84 | locationFields = props.attributes.locations.map( ( location, index ) => { 85 | return 86 | handleLocationChange( address, index ) } 91 | /> 92 | handleRemoveLocation( index ) } 97 | /> 98 | ; 99 | } ); 100 | 101 | locationDisplay = props.attributes.locations.map( ( location, index ) => { 102 | return

{ location.address }

; 103 | } ); 104 | } 105 | 106 | return [ 107 | 108 | 109 | { locationFields } 110 | 116 | 117 | , 118 |
119 |

Block

120 | { locationDisplay } 121 |
, 122 | ]; 123 | }, 124 | save: ( props ) => { 125 | const locationFields = props.attributes.locations.map( ( location, index ) => { 126 | return

{ location.address }

; 127 | } ); 128 | 129 | return ( 130 |
131 |

Block

132 | { locationFields } 133 |
134 | ); 135 | }, 136 | } ); 137 | -------------------------------------------------------------------------------- /src/block/editor.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * #.# Editor Styles 3 | * 4 | * CSS for just Backend enqueued after style.scss 5 | * which makes it higher in priority. 6 | */ 7 | 8 | .grf { 9 | 10 | &__location-address { 11 | float: left; 12 | width: calc(100% - 40px); 13 | } 14 | 15 | &__remove-location-address { 16 | float: left; 17 | margin-left: 10px; 18 | margin-top: 1px; 19 | padding: 5px 5px 4px; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/block/style.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * #.# Styles 3 | * 4 | * CSS for both Frontend+Backend. 5 | */ 6 | -------------------------------------------------------------------------------- /src/blocks.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Gutenberg Blocks 3 | * 4 | * All blocks related JavaScript files should be imported here. 5 | * You can create a new block folder in this dir and include code 6 | * for that block here as well. 7 | * 8 | * All blocks should be included here since this is the file that 9 | * Webpack is compiling as the input file. 10 | */ 11 | 12 | import './block/block.js'; 13 | -------------------------------------------------------------------------------- /src/common.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * #.# Common SCSS 3 | * 4 | * Can include things like variables and mixins 5 | * that are used across the project. 6 | */ 7 | -------------------------------------------------------------------------------- /src/init.php: -------------------------------------------------------------------------------- 1 | plugin_dir_path( __DIR__ ), 62 | 'pluginDirUrl' => plugin_dir_url( __DIR__ ), 63 | // Add more data here that you want to access from `cgbGlobal` object. 64 | ] 65 | ); 66 | 67 | /** 68 | * Register Gutenberg block on server-side. 69 | * 70 | * Register the block on server-side to ensure that the block 71 | * scripts and styles for both frontend and backend are 72 | * enqueued when the editor loads. 73 | * 74 | * @link https://wordpress.org/gutenberg/handbook/blocks/writing-your-first-block-type#enqueuing-block-scripts 75 | * @since 1.16.0 76 | */ 77 | register_block_type( 78 | 'cgb/block-gutenberg-repeater-field', array( 79 | // Enqueue blocks.style.build.css on both frontend & backend. 80 | 'style' => 'gutenberg_repeater_field-cgb-style-css', 81 | // Enqueue blocks.build.js in the editor only. 82 | 'editor_script' => 'gutenberg_repeater_field-cgb-block-js', 83 | // Enqueue blocks.editor.build.css in the editor only. 84 | 'editor_style' => 'gutenberg_repeater_field-cgb-block-editor-css', 85 | ) 86 | ); 87 | } 88 | 89 | // Hook: Block assets. 90 | add_action( 'init', 'gutenberg_repeater_field_cgb_block_assets' ); 91 | --------------------------------------------------------------------------------