diff --git a/.babelrc b/.babelrc new file mode 100644 index 000000000..863c815f3 --- /dev/null +++ b/.babelrc @@ -0,0 +1,25 @@ +{ + "presets": [ + [ "env", { + "modules": false, + "targets": { + "browsers": [ + "last 2 Chrome versions", + "last 2 Firefox versions", + "last 2 Safari versions", + "last 2 iOS versions", + "last 1 Android version", + "last 1 ChromeAndroid version" + ] + } + } ] + ], + "plugins": [ + [ "transform-react-jsx", { + "pragma": "wp.element.createElement" + } ], + "transform-class-properties", + "transform-object-rest-spread", + "lodash" + ] +} \ No newline at end of file diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 000000000..2b1060cea --- /dev/null +++ b/.eslintignore @@ -0,0 +1,3 @@ +assets/build +assets/js +node_modules diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 000000000..60dbae418 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,176 @@ +module.exports = { + root: true, + parser: 'babel-eslint', + extends: [ + 'wordpress', + 'plugin:wordpress/esnext', + 'plugin:react/recommended', + 'plugin:jsx-a11y/recommended', + ], + env: { + browser: true, + es6: true, + }, + parserOptions: { + sourceType: 'module', + ecmaFeatures: { + jsx: true, + }, + }, + globals: { + wp: true, + window: true, + document: true, + "jQuery": true, + "ajaxurl": true, + "woo_localized_data": true + }, + plugins: [ + 'wordpress', + 'react', + 'jsx-a11y', + ], + settings: { + react: { + pragma: 'wp', + }, + }, + rules: { + 'array-bracket-spacing': [ 'error', 'always' ], + 'arrow-parens': [ 'error', 'always' ], + 'arrow-spacing': 'error', + 'brace-style': [ 'error', '1tbs' ], + camelcase: [ 'error', { properties: 'never' } ], + 'comma-dangle': [ 'error', 'always-multiline' ], + 'comma-spacing': 'error', + 'comma-style': 'error', + 'computed-property-spacing': [ 'error', 'always' ], + 'dot-notation': 'error', + 'eol-last': 'error', + eqeqeq: 'error', + 'func-call-spacing': 'error', + indent: [ 'error', 'tab', { SwitchCase: 1 } ], + 'jsx-a11y/label-has-for': [ 'error', { + required: 'id', + } ], + 'jsx-a11y/media-has-caption': 'off', + 'jsx-a11y/no-noninteractive-tabindex': 'off', + 'jsx-a11y/role-has-required-aria-props': 'off', + 'jsx-quotes': 'error', + 'key-spacing': 'error', + 'keyword-spacing': 'error', + 'lines-around-comment': 'off', + 'no-alert': 'error', + 'no-bitwise': 'error', + 'no-caller': 'error', + 'no-console': 'error', + 'no-debugger': 'error', + 'no-dupe-args': 'error', + 'no-dupe-keys': 'error', + 'no-duplicate-case': 'error', + 'no-else-return': 'error', + 'no-eval': 'error', + 'no-extra-semi': 'error', + 'no-fallthrough': 'error', + 'no-lonely-if': 'error', + 'no-mixed-operators': 'error', + 'no-mixed-spaces-and-tabs': 'error', + 'no-multiple-empty-lines': [ 'error', { max: 1 } ], + 'no-multi-spaces': 'error', + 'no-multi-str': 'off', + 'no-negated-in-lhs': 'error', + 'no-nested-ternary': 'error', + 'no-redeclare': 'error', + 'no-restricted-syntax': [ + 'error', + { + selector: 'CallExpression[callee.name=/^__|_n|_x$/]:not([arguments.0.type=/^Literal|BinaryExpression$/])', + message: 'Translate function arguments must be string literals.', + }, + { + selector: 'CallExpression[callee.name=/^_n|_x$/]:not([arguments.1.type=/^Literal|BinaryExpression$/])', + message: 'Translate function arguments must be string literals.', + }, + { + selector: 'CallExpression[callee.name=_nx]:not([arguments.2.type=/^Literal|BinaryExpression$/])', + message: 'Translate function arguments must be string literals.', + }, + ], + 'no-shadow': 'error', + 'no-undef': 'error', + 'no-undef-init': 'error', + 'no-unreachable': 'error', + 'no-unsafe-negation': 'error', + 'no-unused-expressions': 'error', + 'no-unused-vars': 'error', + 'no-useless-return': 'error', + 'no-whitespace-before-property': 'error', + 'object-curly-spacing': [ 'error', 'always' ], + 'padded-blocks': [ 'error', 'never' ], + 'quote-props': [ 'error', 'as-needed' ], + 'react/display-name': 'off', + 'react/jsx-curly-spacing': [ 'error', { + when: 'always', + children: true, + } ], + 'react/jsx-equals-spacing': 'error', + 'react/jsx-indent': [ 'error', 'tab' ], + 'react/jsx-indent-props': [ 'error', 'tab' ], + 'react/jsx-key': 'error', + 'react/jsx-tag-spacing': 'error', + 'react/no-children-prop': 'off', + 'react/prop-types': 'off', + semi: 'error', + 'semi-spacing': 'error', + 'space-before-blocks': [ 'error', 'always' ], + 'space-before-function-paren': [ 'error', { + anonymous: 'never', + named: 'never', + asyncArrow: 'always', + } ], + 'space-in-parens': [ 'error', 'always' ], + 'space-infix-ops': [ 'error', { int32Hint: false } ], + 'space-unary-ops': [ 'error', { + overrides: { + '!': true, + yield: true, + }, + } ], + 'valid-jsdoc': [ 'error', { + prefer: { + arg: 'param', + argument: 'param', + extends: 'augments', + returns: 'return', + }, + preferType: { + array: 'Array', + bool: 'boolean', + Boolean: 'boolean', + float: 'number', + Float: 'number', + int: 'number', + integer: 'number', + Integer: 'number', + Number: 'number', + object: 'Object', + String: 'string', + Void: 'void', + }, + requireParamDescription: false, + requireReturn: false, + } ], + 'valid-typeof': 'error', + yoda: 'off', + }, + overrides: [ + { + files: 'packages/**/*.js?', + settings: { + react: { + pragma: 'createElement', + }, + }, + }, + ], +}; diff --git a/Gruntfile.js b/Gruntfile.js index 4d0407493..e958dd7e9 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,5 +1,6 @@ -/* jshint node:true */ -module.exports = function( grunt ){ +/* eslint-disable */ + +module.exports = function( grunt ) { 'use strict'; grunt.initConfig({ @@ -9,6 +10,7 @@ module.exports = function( grunt ){ fonts: 'assets/font', images: 'assets/images', js: 'assets/js', + blocks: 'assets/blocks', build: 'tmp/build', svn: 'tmp/release-svn' }, @@ -16,6 +18,12 @@ module.exports = function( grunt ){ shell: { buildMixtape: { command: 'node_modules/.bin/mixtape build' + }, + webpack: { + command: 'cross-env BABEL_ENV=default NODE_ENV=production webpack' + }, + webpackDev: { + command: 'cross-env BABEL_ENV=default webpack --watch' } }, @@ -274,7 +282,10 @@ module.exports = function( grunt ){ grunt.registerTask( 'build-mixtape', [ 'shell:buildMixtape' ] ); - grunt.registerTask( 'build', [ 'gitinfo', 'clean', 'check-mixtape', 'check-mixtape-fatal', 'test', 'copy' ] ); + grunt.registerTask( 'build-blocks', [ 'shell:webpack' ] ); + grunt.registerTask( 'build-blocks:dev', [ 'shell:webpackDev' ] ); + + grunt.registerTask( 'build', [ 'gitinfo', 'clean', 'check-mixtape', 'check-mixtape-fatal', 'test', 'build-blocks', 'copy' ] ); grunt.registerTask( 'deploy', [ 'checkbranch:master', 'checkrepo', 'build', 'wp_deploy' ] ); grunt.registerTask( 'deploy-unsafe', [ 'build', 'wp_deploy' ] ); diff --git a/includes/class-wp-job-manager-blocks.php b/includes/class-wp-job-manager-blocks.php new file mode 100644 index 000000000..dad09abeb --- /dev/null +++ b/includes/class-wp-job-manager-blocks.php @@ -0,0 +1,56 @@ +=0.8.0", "npm": ">=1.1.0" + }, + "dependencies": { + "lodash": "^4.17.10", + "react": "^16.4.1" } } diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 000000000..cb79959ca --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,56 @@ +/* global require, module, process, __dirname */ + +const CleanWebpackPlugin = require( 'clean-webpack-plugin' ); +const LodashModuleReplacementPlugin = require( 'lodash-webpack-plugin' ); +const UglifyJsPlugin = require( 'uglifyjs-webpack-plugin' ); + +const blockNames = [ + // Add the name of the block as a directory in the assets/blocks directory. +]; + +const webpackConfig = { + mode: process.env.NODE_ENV === 'production' ? 'production' : 'development', + + entry: Object.assign( + blockNames.reduce( ( blocks, blockName ) => { + const path = `./assets/blocks/${ blockName }/index.jsx`; + blocks[ blockName ] = path; + return blocks; + }, {} ) + ), + output: { + filename: 'assets/build/blocks/[name].js', + path: __dirname, + }, + module: { + rules: [ + { + test: /.jsx$/, + use: 'babel-loader', + exclude: /node_modules/, + }, + { + test: /\.css$/, + use: [ + 'style-loader', + 'css-loader', + ], + }, + { + test: /\.scss$/, + use: [ + 'style-loader', + 'css-loader', + 'sass-loader', + ], + }, + ], + }, + plugins: [ + new CleanWebpackPlugin( [ 'assets/build/blocks' ] ), + new LodashModuleReplacementPlugin(), + new UglifyJsPlugin(), + ], +}; + +module.exports = webpackConfig; diff --git a/wp-job-manager.php b/wp-job-manager.php index 03f111059..22cbf5182 100644 --- a/wp-job-manager.php +++ b/wp-job-manager.php @@ -72,6 +72,7 @@ public function __construct() { include_once( JOB_MANAGER_PLUGIN_DIR . '/includes/class-wp-job-manager-api.php' ); include_once( JOB_MANAGER_PLUGIN_DIR . '/includes/class-wp-job-manager-forms.php' ); include_once( JOB_MANAGER_PLUGIN_DIR . '/includes/class-wp-job-manager-geocode.php' ); + include_once( JOB_MANAGER_PLUGIN_DIR . '/includes/class-wp-job-manager-blocks.php' ); include_once( JOB_MANAGER_PLUGIN_DIR . '/includes/class-wp-job-manager-cache-helper.php' ); include_once( JOB_MANAGER_PLUGIN_DIR . '/includes/helper/class-wp-job-manager-helper.php' ); include_once( JOB_MANAGER_PLUGIN_DIR . '/includes/abstracts/abstract-wp-job-manager-email.php' );