├── .editorconfig ├── .github └── workflows │ └── php.yml ├── .gitignore ├── .phpunit-watcher.yml ├── LICENSE ├── README.md ├── composer.json ├── frontend ├── .eslintrc.js ├── .gitignore ├── assets │ ├── .gitkeep │ ├── index.js │ └── style.css ├── gulpfile.esm.js ├── index.html ├── package.json ├── postcss.config.js ├── public │ ├── favicon.ico │ ├── images │ │ ├── spinner-2x.gif │ │ ├── spinner.gif │ │ ├── stars-2x.png │ │ └── stars.png │ ├── wp-includes │ │ └── fonts │ │ │ ├── dashicons.eot │ │ │ ├── dashicons.svg │ │ │ ├── dashicons.ttf │ │ │ ├── dashicons.woff │ │ │ └── dashicons.woff2 │ └── wp.css ├── src │ ├── App.vue │ ├── assets │ │ └── logo.png │ ├── components │ │ ├── Spinner.vue │ │ ├── Windsor.vue │ │ ├── WindsorError.vue │ │ ├── WindsorFieldGroupItem.vue │ │ ├── WindsorFieldGroups.vue │ │ ├── WindsorHeader.vue │ │ ├── WindsorSidebar.vue │ │ ├── WindsorYamlPreview.vue │ │ └── useLoadYaml.js │ ├── index.css │ ├── main.js │ ├── store.js │ └── utils │ │ ├── ajax.js │ │ ├── clipboard.js │ │ ├── config.js │ │ └── repository.js ├── tailwind.config.js ├── vite.config.js └── yarn.lock ├── phpcs.xml ├── screenshot.png ├── src ├── AcfBlock.php ├── AcfServiceProvider.php ├── Admin │ ├── Exporter │ │ ├── CompactRules │ │ │ ├── CompactAccordion.php │ │ │ ├── CompactButtonGroup.php │ │ │ ├── CompactCheckbox.php │ │ │ ├── CompactClone.php │ │ │ ├── CompactField.php │ │ │ ├── CompactFieldGroup.php │ │ │ ├── CompactFile.php │ │ │ ├── CompactFlexibleContent.php │ │ │ ├── CompactGallery.php │ │ │ ├── CompactGoogleMap.php │ │ │ ├── CompactHelper.php │ │ │ ├── CompactImage.php │ │ │ ├── CompactLayout.php │ │ │ ├── CompactMessage.php │ │ │ ├── CompactNumber.php │ │ │ ├── CompactOEmbed.php │ │ │ ├── CompactPageLink.php │ │ │ ├── CompactPostObject.php │ │ │ ├── CompactRadio.php │ │ │ ├── CompactRange.php │ │ │ ├── CompactRelationship.php │ │ │ ├── CompactRepeater.php │ │ │ ├── CompactRichEditor.php │ │ │ ├── CompactSelect.php │ │ │ ├── CompactTab.php │ │ │ ├── CompactTaxonomy.php │ │ │ ├── CompactTextArea.php │ │ │ ├── CompactTrueFalse.php │ │ │ └── CompactUser.php │ │ ├── FieldGroupsStore.php │ │ ├── FieldsPacker.php │ │ ├── MutableField.php │ │ ├── MutableFieldCollection.php │ │ └── YamlComposer.php │ └── WordPress │ │ ├── AjaxHandler.php │ │ └── UiLoader.php ├── Capsule │ ├── AbstractCapsule.php │ ├── Block.php │ ├── BlueprintBuilder.php │ ├── BlueprintsFactory.php │ ├── FieldGroup.php │ └── Manager.php ├── Contracts │ └── ParserContract.php ├── Parser │ ├── Finder.php │ └── YamlParser.php ├── Rules │ ├── FieldConditionRule.php │ ├── FieldDefaultsRule.php │ ├── GroupLocationRule.php │ ├── HelperRule.php │ ├── Utilities │ │ └── TransformConditionalLogic.php │ └── WrapperShortcuts.php ├── Support │ ├── Config.php │ ├── Fluent.php │ ├── RulesCollector.php │ └── Singleton.php ├── config.php └── helpers.php └── tests ├── BaseTestCase.php ├── BlockSimpleTest.php ├── BlueprintsTest.php ├── ChangeInstructionsToFoo.php ├── CompactRulesTest.php ├── ConditionalTest.php ├── DummyHandler.php ├── FieldsPackerTest.php ├── KeysTest.php ├── MutableFieldTest.php ├── RulesTest.php ├── SetupTest.php ├── bootstrap.php ├── raw ├── default.php ├── simple-conditional.php ├── simple-flex-content.php ├── simple-group.php └── simple-repeater.php └── yaml ├── blocks ├── block-with-handler.acf.yaml └── sample-block.acf.yaml ├── blueprints ├── my-blueprint.acf.yaml ├── test-default.acf.yaml ├── test-exclude.acf.yaml ├── test-layout.acf.yaml ├── test-merge.acf.yaml ├── test-only.acf.yaml └── test-prefix.acf.yaml ├── clones.acf.yaml ├── index-empty.yaml ├── index.yaml ├── keys-static.acf.yaml ├── keys.acf.yaml ├── rules ├── conditional-rules.acf.yaml ├── default-rules.acf.yaml ├── helper-rules.acf.yaml └── wrapper-rules.acf.yaml ├── sample-field.acf.yaml ├── sample-page.acf.yaml ├── sample-post.acf.yaml ├── simple.acf.yaml └── sub ├── fields └── empty-field.acf.yaml └── index.yaml /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.php] 13 | indent_size = 4 14 | -------------------------------------------------------------------------------- /.github/workflows/php.yml: -------------------------------------------------------------------------------- 1 | name: PHP Composer 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | 17 | - name: Validate composer.json and composer.lock 18 | run: composer validate 19 | 20 | - name: Cache Composer packages 21 | id: composer-cache 22 | uses: actions/cache@v2 23 | with: 24 | path: vendor 25 | key: ${{ runner.os }}-node-${{ hashFiles('**/composer.lock') }} 26 | restore-keys: | 27 | ${{ runner.os }}-node- 28 | 29 | - name: Install dependencies 30 | run: composer install --prefer-dist --no-progress --no-suggest 31 | 32 | # Add a test script to composer.json, for instance: "test": "vendor/bin/phpunit" 33 | # Docs: https://getcomposer.org/doc/articles/scripts.md 34 | 35 | - name: Run test suite 36 | run: composer run-script test 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Composer 2 | composer.phar 3 | composer.lock 4 | /vendor/ 5 | 6 | # OS files 7 | .DS_Store 8 | 9 | # Environment file 10 | .env 11 | .env.*.php 12 | .env.php 13 | 14 | # Logs 15 | php_errors.log 16 | nginx-error.log 17 | nginx-access.log 18 | nginx-ssl.access.log 19 | nginx-ssl.error.log 20 | php-errors.log 21 | yarn-error.log 22 | 23 | # IDE specific 24 | nbproject 25 | .phpintel 26 | .idea 27 | _ide_helper.php 28 | .phpstorm.meta.php 29 | .vscode -------------------------------------------------------------------------------- /.phpunit-watcher.yml: -------------------------------------------------------------------------------- 1 | phpunit: 2 | binaryPath: vendor/bin/phpunit 3 | arguments: 'tests --bootstrap tests/bootstrap.php --colors=always' 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ACF Windsor 2 | 3 | [](//packagist.org/packages/jofrysutanto/windsor) [](//packagist.org/packages/jofrysutanto/windsor) [](//packagist.org/packages/jofrysutanto/windsor) [](//packagist.org/packages/jofrysutanto/windsor) 4 | 5 | This package extends [Advanced Custom Fields](https://advancedcustomfields.com) plugin for WordPress and enable developers to write their ACF fields blazingly fast in configuration file. 6 | 7 |  8 | 9 | ### Features 10 | - Permanently lock your custom fields in your version-controlled code, preventing accidental edits that quickly leads to out-of-sync configurations. 11 | - Create your fields much faster, especially when complimented with the IDE integration. 12 | - Composition is at the heart of it. Write your own rules to further supercharge your development productivity. 13 | 14 | ### Getting Started 15 | 16 | - The easiest way to install Windsor is to use composer: 17 | ```sh 18 | composer require jofrysutanto/windsor 19 | ``` 20 | - If you are using VSCode, be sure to add the Schema file to your configuration. 21 | - Ensure you have included composer auto-loader file. If you're not sure, add the following line into your `functions.php` file: 22 | ```php 23 | require_once __DIR__ . '/vendor/autoload.php'; 24 | ``` 25 | - Register Windsor on ACF initialization. You may also do this in `functions.php` file: 26 | ```php 27 | function register_acf_windsor() 28 | { 29 | \Windsor\Capsule\Manager::make()->register(); 30 | } 31 | add_action('acf/init', 'register_acf_windsor'); 32 | ``` 33 | - Create YAML entry file at `[your-active-theme]/acf-fields/index.yaml`, where `[your-active-theme]` refers to your currently active WordPress theme directory. At minimum, your entry file should contain: 34 | ```yaml 35 | fields: [] 36 | pages: [] 37 | blocks: [] 38 | ``` 39 | - Test your installation: 40 | - Create your first custom field YAML, for example create a file `your-theme/acf-fields/page-default.acf.yaml`: 41 | ```yaml 42 | title: 'Page Default' 43 | key: 'page_default' 44 | position: 'acf_after_title' 45 | hide_on_screen: [] 46 | location: 47 | - 48 | - 49 | param: 'page_template' 50 | operator: '==' 51 | value: 'default' 52 | fields: 53 | heading: 54 | type: text 55 | label: Heading 56 | ``` 57 | - Register this new ACF file in your index: 58 | ```yaml 59 | fields: [] 60 | pages: 61 | - page-default.acf.yaml 62 | blocks: [] 63 | ``` 64 | - You have successfully registered a new field group which will be made available when creating a new default page. 65 | - Check out our full documentation below. Now go and create beautiful ACF fields! 66 | 67 | ## Migrating Existing Fields 68 | If you have existing field groups created through ACF interface, you can easily export them out to YAML by enabling the exporter through `ui` configuration when registering Windsor: 69 | ```php 70 | function register_acf_windsor() 71 | { 72 | \Windsor\Capsule\Manager::make([ 73 | 'ui' => true 74 | ]) 75 | ->register(); 76 | } 77 | add_action('acf/init', 'register_acf_windsor'); 78 | ``` 79 | 80 | Once enabled, you access the exporter within WordPress backend by clicking on Custom Fields > Export to YAML link in sidebar. 81 | 82 | More information about this tool can be found in [configurations section](https://windsor-docs.netlify.app/configurations.html#ui). 83 | 84 | ## Learn More 85 | Check out full documentations at [https://windsor-docs.netlify.app/](https://windsor-docs.netlify.app/) 86 | 87 | ## IDE Integration 88 | 89 | Only VSCode integration is available at the moment. To enable autocompletion and useful snippets, follow the installation steps below: 90 | - If not already installed, download and enable [YAML language server](https://marketplace.visualstudio.com/items?itemName=redhat.vscode-yaml) extension. 91 | - Update your VSCode [settings](https://code.visualstudio.com/docs/getstarted/settings#_settings-file-locations) (i.e. `settings.json`): 92 | ```json 93 | "yaml.schemas": { 94 | "https://windsor-docs.netlify.app/schema.json": "*.acf.yaml" 95 | } 96 | ``` 97 | 98 | ## Credits 99 | 100 | This package is written to be used with [Advanced Custom Fields](https://www.advancedcustomfields.com/) plugin by [Elliot Condon](https://www.elliotcondon.com/), who deserves most of the credits for delivering and maintaining such an incredible plugin for WordPress developers. 101 | 102 | If you have not already started using Advanced Custom Fields, be sure to check it out; it will definitely be worth your while. 103 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jofrysutanto/windsor", 3 | "description": "YAML-ised Configuration for ACF", 4 | "homepage": "https://github.com/jofrysutanto/windsor", 5 | "type": "package", 6 | "keywords": [ 7 | "wordpress", "advanced-custom-fields" 8 | ], 9 | "license": "Apache-2.0", 10 | "authors": [ 11 | { 12 | "name": "Jofry Sutanto", 13 | "email": "jofrysutanto@gmail.com" 14 | } 15 | ], 16 | "require": { 17 | "php": ">=7.0.0", 18 | "symfony/yaml": "~3.4|~4.0|~5.0", 19 | "tightenco/collect": "^8.0" 20 | }, 21 | "require-dev": { 22 | "squizlabs/php_codesniffer": "^3.2", 23 | "phpunit/phpunit": "^9", 24 | "mockery/mockery": "^1.4", 25 | "spatie/phpunit-watcher": "^1.22" 26 | }, 27 | "autoload": { 28 | "psr-4": { 29 | "Windsor\\": "src/" 30 | }, 31 | "files": [ 32 | "src/helpers.php" 33 | ] 34 | }, 35 | "scripts": { 36 | "test": [ 37 | "phpcs --extensions=php --standard=PSR2 src/", 38 | "vendor/bin/phpunit tests --bootstrap tests/bootstrap.php" 39 | ], 40 | "fix": [ 41 | "phpcbf src/" 42 | ] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /frontend/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'root': true, 3 | 'extends': [ 4 | 'eslint:recommended', 5 | 'plugin:vue/essential', 6 | ], 7 | 'globals': { 8 | 'wp': true, 9 | }, 10 | 'env': { 11 | 'node': true, 12 | 'es6': true, 13 | 'amd': true, 14 | 'browser': true, 15 | 'jquery': true, 16 | }, 17 | 'parserOptions': { 18 | 'parser': 'babel-eslint', 19 | 'ecmaFeatures': { 20 | 'globalReturn': true, 21 | 'generators': false, 22 | 'objectLiteralDuplicateProperties': false 23 | }, 24 | 'ecmaVersion': 2017, 25 | 'sourceType': 'module', 26 | }, 27 | 'plugins': [ 28 | 'import', 29 | ], 30 | 'settings': { 31 | 'import/core-modules': [], 32 | 'import/ignore': [ 33 | 'node_modules', 34 | '\\.(coffee|scss|css|less|hbs|svg|json)$', 35 | ], 36 | }, 37 | 'rules': { 38 | 'no-console': 0, 39 | 'quotes': ['error', 'single'], 40 | 'comma-dangle': [ 41 | 'error', 42 | { 43 | 'arrays': 'always-multiline', 44 | 'objects': 'always-multiline', 45 | 'imports': 'always-multiline', 46 | 'exports': 'always-multiline', 47 | 'functions': 'ignore', 48 | }, 49 | ], 50 | }, 51 | }; 52 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | *.local -------------------------------------------------------------------------------- /frontend/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jofrysutanto/windsor/0a130f2920283ea42033777600aec9d5a45b88e1/frontend/assets/.gitkeep -------------------------------------------------------------------------------- /frontend/gulpfile.esm.js: -------------------------------------------------------------------------------- 1 | const { join, extname, basename } = require('path') 2 | const { readdir, stat, copyFileSync } = require('fs') 3 | const { promisify } = require('util') 4 | const readdirP = promisify(readdir) 5 | const statP = promisify(stat) 6 | 7 | const config = { 8 | src: './dist/_assets', 9 | output: './assets/', 10 | copy: [ 11 | 'index.js', 12 | 'style.css', 13 | ] 14 | } 15 | 16 | /** 17 | * Scan and retrieve all files from given directory 18 | * @param {String} dir 19 | * @param {Array} allFiles 20 | */ 21 | async function rreaddir(dir, allFiles = []) { 22 | const files = (await readdirP(dir)).map(f => join(dir, f)) 23 | allFiles.push(...files) 24 | await Promise.all( 25 | files.map( 26 | async f => (await statP(f)).isDirectory() && rreaddir(f, allFiles) 27 | ) 28 | ) 29 | return allFiles 30 | } 31 | 32 | /** 33 | * Clean up given file path by removing hash. 34 | * e.g. 'dist/_assets/index.abcdefg.js' -> 'index.js' 35 | * @param {String} filepath 36 | */ 37 | function removeFileHash (filepath) { 38 | let fragments = basename(filepath).split('.') 39 | fragments.splice(fragments.length - 2, 1) 40 | return fragments.join('.') 41 | } 42 | 43 | /** 44 | * Copy and normalise compiled Vite files 45 | * @param {Closure} cb 46 | */ 47 | async function copy (cb) { 48 | let assets = await rreaddir(config.src) 49 | assets.forEach(filepath => { 50 | if (!extname(filepath)) { 51 | return 52 | } 53 | if (!(filepath.endsWith('.js') || (filepath.endsWith('.css')))) { 54 | return 55 | } 56 | let filename = removeFileHash(filepath) 57 | if (config.copy.indexOf(filename) === -1) { 58 | return 59 | } 60 | let destination = `${config.output}/${filename}` 61 | copyFileSync(filepath, destination) 62 | }) 63 | cb(); 64 | } 65 | 66 | exports.build = copy 67 | -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 |11 | We can't seem to load this page correctly; please try again later. If issue persists, consider reporting this issue to the developer. If you are a more hands-on type user, more information might be available in your console. 12 |
13 |57 | Exclude empty field settings to generate smaller YAML files. 58 |
59 |85 | Generated YAML filenames are inferred from the title. When disabled, unique keys are used: group_xxxxx.acf.yaml. 86 |
87 |
112 | Include index.yaml
which you can drop into acf-yaml/
directly to auto-register fields via Windsor.
113 |
8 |