Set up and move things over for a feature plugin
This commit is contained in:
parent
22e23e7252
commit
6533d2f166
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"presets": [
|
||||
"es2015",
|
||||
"stage-2",
|
||||
[ "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",
|
||||
"ie 11"
|
||||
]
|
||||
}
|
||||
} ]
|
||||
],
|
||||
"plugins": [
|
||||
"add-module-exports",
|
||||
[ "transform-react-jsx", {
|
||||
"pragma": "wp.element.createElement"
|
||||
} ]
|
||||
]
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
# Editors
|
||||
project.xml
|
||||
project.properties
|
||||
/nbproject/private/
|
||||
.buildpath
|
||||
.project
|
||||
.settings*
|
||||
.idea
|
||||
.vscode
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
.sublimelinterrc
|
||||
|
||||
# Grunt
|
||||
/node_modules/
|
||||
none
|
||||
|
||||
# Sass
|
||||
.sass-cache/
|
||||
|
||||
# OS X metadata
|
||||
.DS_Store
|
||||
|
||||
# Windows junk
|
||||
Thumbs.db
|
||||
|
||||
# ApiGen
|
||||
/wc-apidocs/
|
||||
|
||||
# Behat/CLI Tests
|
||||
tests/cli/installer
|
||||
tests/cli/composer.phar
|
||||
tests/cli/composer.lock
|
||||
tests/cli/composer.json
|
||||
tests/cli/vendor
|
||||
|
||||
# Unit tests
|
||||
/tmp
|
||||
/tests/bin/tmp
|
||||
/tests/e2e-tests/config/local-*.json
|
||||
|
||||
# Logs
|
||||
/logs
|
||||
|
||||
# Composer
|
||||
/vendor/
|
|
@ -0,0 +1,145 @@
|
|||
/**
|
||||
* This file has a bunch on extra stuff that isn't needed and might not work.
|
||||
* That's OK. This is just for compiling the CSS during Products block prototype development. :)
|
||||
*/
|
||||
|
||||
/* jshint node:true */
|
||||
module.exports = function( grunt ) {
|
||||
'use strict';
|
||||
|
||||
grunt.initConfig({
|
||||
|
||||
// Setting folder templates.
|
||||
dirs: {
|
||||
css: 'assets/css',
|
||||
fonts: 'assets/fonts',
|
||||
images: 'assets/images',
|
||||
js: 'assets/js'
|
||||
},
|
||||
|
||||
// Sass linting with Stylelint.
|
||||
stylelint: {
|
||||
options: {
|
||||
configFile: '.stylelintrc'
|
||||
},
|
||||
all: [
|
||||
'<%= dirs.css %>/*.scss',
|
||||
]
|
||||
},
|
||||
|
||||
// Compile all .scss files.
|
||||
sass: {
|
||||
compile: {
|
||||
options: {
|
||||
sourceMap: 'none'
|
||||
},
|
||||
files: [{
|
||||
expand: true,
|
||||
cwd: '<%= dirs.css %>/',
|
||||
src: ['*.scss'],
|
||||
dest: '<%= dirs.css %>/',
|
||||
ext: '.css'
|
||||
}]
|
||||
}
|
||||
},
|
||||
|
||||
// Generate RTL .css files
|
||||
rtlcss: {
|
||||
woocommerce: {
|
||||
expand: true,
|
||||
cwd: '<%= dirs.css %>',
|
||||
src: [
|
||||
'*.css',
|
||||
'!select2.css',
|
||||
'!*-rtl.css'
|
||||
],
|
||||
dest: '<%= dirs.css %>/',
|
||||
ext: '-rtl.css'
|
||||
}
|
||||
},
|
||||
|
||||
// Minify all .css files.
|
||||
cssmin: {
|
||||
minify: {
|
||||
expand: true,
|
||||
cwd: '<%= dirs.css %>/',
|
||||
src: ['*.css'],
|
||||
dest: '<%= dirs.css %>/',
|
||||
ext: '.css'
|
||||
}
|
||||
},
|
||||
|
||||
// Watch changes for assets.
|
||||
watch: {
|
||||
css: {
|
||||
files: ['<%= dirs.css %>/*.scss'],
|
||||
tasks: ['sass', 'rtlcss', 'cssmin', 'concat']
|
||||
},
|
||||
js: {
|
||||
files: [
|
||||
'<%= dirs.js %>/admin/*js',
|
||||
'<%= dirs.js %>/frontend/*js',
|
||||
'!<%= dirs.js %>/admin/*.min.js',
|
||||
'!<%= dirs.js %>/frontend/*.min.js'
|
||||
],
|
||||
tasks: ['jshint', 'uglify']
|
||||
}
|
||||
},
|
||||
|
||||
// Autoprefixer.
|
||||
postcss: {
|
||||
options: {
|
||||
processors: [
|
||||
require( 'autoprefixer' )({
|
||||
browsers: [
|
||||
'> 0.1%',
|
||||
'ie 8',
|
||||
'ie 9'
|
||||
]
|
||||
})
|
||||
]
|
||||
},
|
||||
dist: {
|
||||
src: [
|
||||
'<%= dirs.css %>/*.css'
|
||||
]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Load NPM tasks to be used here
|
||||
grunt.loadNpmTasks( 'grunt-sass' );
|
||||
grunt.loadNpmTasks( 'grunt-rtlcss' );
|
||||
grunt.loadNpmTasks( 'grunt-postcss' );
|
||||
grunt.loadNpmTasks( 'grunt-stylelint' );
|
||||
grunt.loadNpmTasks( 'grunt-wp-i18n' );
|
||||
grunt.loadNpmTasks( 'grunt-checktextdomain' );
|
||||
grunt.loadNpmTasks( 'grunt-contrib-jshint' );
|
||||
grunt.loadNpmTasks( 'grunt-contrib-uglify' );
|
||||
grunt.loadNpmTasks( 'grunt-contrib-cssmin' );
|
||||
grunt.loadNpmTasks( 'grunt-contrib-concat' );
|
||||
grunt.loadNpmTasks( 'grunt-contrib-watch' );
|
||||
grunt.loadNpmTasks( 'grunt-contrib-clean' );
|
||||
|
||||
// Register tasks
|
||||
grunt.registerTask( 'default', [
|
||||
'css',
|
||||
]);
|
||||
|
||||
grunt.registerTask( 'css', [
|
||||
'sass',
|
||||
'rtlcss',
|
||||
'postcss',
|
||||
'cssmin',
|
||||
]);
|
||||
|
||||
grunt.registerTask( 'docs', [
|
||||
'clean:apidocs',
|
||||
'shell:apidocs'
|
||||
]);
|
||||
|
||||
// Only an alias to 'default' task.
|
||||
grunt.registerTask( 'dev', [
|
||||
'default'
|
||||
]);
|
||||
};
|
|
@ -13,15 +13,4 @@ Feature plugin for the Gutenberg Products block.
|
|||
The source code is in the products-block.jsx file and the compiled code is in products-block.js.
|
||||
|
||||
**Gutenberg Tutorial and Docs**: https://wordpress.org/gutenberg/handbook/blocks/
|
||||
**Using API in Gutenberg**: https://github.com/WordPress/gutenberg/tree/213c64bb495946deffa3b6fe260f95b87de5774a/components/higher-order/with-api-data
|
||||
|
||||
**Gutenberg Products Block initial TODO:**
|
||||
- [x] Redesigned "Display" menu (@claudiulodro in progress)
|
||||
- [x] "Done" button for edit mode.
|
||||
- [ ] Product search/select (@claudiosanches in progress)
|
||||
- [x] Product category search/select
|
||||
- [x] Editor preview (Pull in products using API)
|
||||
- [x] Frontend rendering (Shortcode-based)
|
||||
- [ ] CSS styling
|
||||
- [x] Add keys to everything to prevent `Warning: Each child in an array or iterator should have a unique "key" prop` warning
|
||||
- [ ] Graceful error handling (e.g. if you set columns to 0)
|
||||
**Using API in Gutenberg**: https://github.com/WordPress/gutenberg/tree/213c64bb495946deffa3b6fe260f95b87de5774a/components/higher-order/with-api-data
|
|
@ -0,0 +1 @@
|
|||
.wc-products-block-preview.grid{overflow:hidden}.wc-products-block-preview.grid .product-preview{float:right;text-align:center;margin-left:3.8%}.wc-products-block-preview.grid.cols-1 .product-preview{float:none;margin-left:0}.wc-products-block-preview.grid.cols-2 .product-preview{width:48%}.wc-products-block-preview.grid.cols-2 .product-preview:nth-of-type(2n){margin-left:0}.wc-products-block-preview.grid.cols-3 .product-preview{width:30.75%}.wc-products-block-preview.grid.cols-3 .product-preview:nth-of-type(3n){margin-left:0}.wc-products-block-preview.grid.cols-4 .product-preview{width:22.05%}.wc-products-block-preview.grid.cols-4 .product-preview:nth-of-type(4n){margin-left:0}.wc-products-block-preview.grid.cols-5 .product-preview{width:16.9%}.wc-products-block-preview.grid.cols-5 .product-preview:nth-of-type(5n){margin-left:0}.wc-products-block-preview.list .product-preview{overflow:hidden}.wc-products-block-preview.list img{float:right;width:50%;margin-left:2em;margin-bottom:2em}.wc-products-block-preview .product-add-to-cart{background:gray;border-radius:10px;color:#fff;cursor:pointer;padding:.5em 1em;line-height:3em}.wc-product-display-settings{background-color:#f8f9f9;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif;font-size:13px;padding:1em;min-height:200px;position:relative}.wc-product-display-settings>h4{text-align:center}.wc-product-display-settings .display-settings-container{background-color:#fff}.wc-product-display-settings .display-settings-container.existing{position:absolute;width:100%;z-index:999}.wc-product-display-settings .wc-products-display-option{border:1px solid gray}.wc-product-display-settings .display-select{margin:0 -1em 20px;padding-bottom:20px;border-bottom:1px solid #e6eaee;text-align:center}.wc-product-display-settings .display-select select{margin-right:1em}.wc-product-display-settings .product-specific-select .add-new{background:#ddd;display:inline-block;margin-bottom:20px;padding:60px 30px}.wc-product-display-settings .product-category-select{margin:0 auto;position:relative}.wc-product-display-settings .product-category-select div+div>ul{height:100px;overflow-y:scroll;padding:0 0 15px}.wc-product-display-settings .product-category-select #product-category-search{width:100%;margin:0 0 10px}.wc-product-display-settings .product-category-select ul{list-style-type:none}.wc-product-display-settings .product-category-select ul li{margin:3px 0}.wc-product-display-settings .product-category-select ul li input[type=checkbox]{margin-left:5px}.wc-product-display-settings .product-category-select ul li ul li input[type=checkbox]{margin-left:20px}.wc-product-display-settings .product-category-select:after{content:'';position:absolute;bottom:0;width:100%;height:1.5em;background:-webkit-linear-gradient(rgba(255,255,255,.1) 0,#f8f9f9 100%);background:linear-gradient(rgba(255,255,255,.1) 0,#f8f9f9 100%)}.wc-product-display-settings .block-footer{margin:0 -1em;padding-top:1em;border-top:1px solid #e6eaee;text-align:center}.wc-product-display-settings .display-select+.block-footer{padding-top:0;border:0}@media only screen and (min-width:700px){.wc-product-display-settings .product-category-select{width:400px}.wc-product-display-settings .product-category-select div+div{overflow:hidden}.wc-product-display-settings .product-category-select div+div>ul{width:440px}}
|
|
@ -0,0 +1 @@
|
|||
.wc-products-block-preview.grid{overflow:hidden}.wc-products-block-preview.grid .product-preview{float:left;text-align:center;margin-right:3.8%}.wc-products-block-preview.grid.cols-1 .product-preview{float:none;margin-right:0}.wc-products-block-preview.grid.cols-2 .product-preview{width:48%}.wc-products-block-preview.grid.cols-2 .product-preview:nth-of-type(2n){margin-right:0}.wc-products-block-preview.grid.cols-3 .product-preview{width:30.75%}.wc-products-block-preview.grid.cols-3 .product-preview:nth-of-type(3n){margin-right:0}.wc-products-block-preview.grid.cols-4 .product-preview{width:22.05%}.wc-products-block-preview.grid.cols-4 .product-preview:nth-of-type(4n){margin-right:0}.wc-products-block-preview.grid.cols-5 .product-preview{width:16.9%}.wc-products-block-preview.grid.cols-5 .product-preview:nth-of-type(5n){margin-right:0}.wc-products-block-preview.list .product-preview{overflow:hidden}.wc-products-block-preview.list img{float:left;width:50%;margin-right:2em;margin-bottom:2em}.wc-products-block-preview .product-add-to-cart{background:gray;border-radius:10px;color:#fff;cursor:pointer;padding:.5em 1em;line-height:3em}.wc-product-display-settings{background-color:#f8f9f9;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif;font-size:13px;padding:1em;min-height:200px;position:relative}.wc-product-display-settings>h4{text-align:center}.wc-product-display-settings .display-settings-container{background-color:#fff}.wc-product-display-settings .display-settings-container.existing{position:absolute;width:100%;z-index:999}.wc-product-display-settings .wc-products-display-option{border:1px solid gray}.wc-product-display-settings .display-select{margin:0 -1em 20px;padding-bottom:20px;border-bottom:1px solid #e6eaee;text-align:center}.wc-product-display-settings .display-select select{margin-left:1em}.wc-product-display-settings .product-specific-select .add-new{background:#ddd;display:inline-block;margin-bottom:20px;padding:60px 30px}.wc-product-display-settings .product-category-select{margin:0 auto;position:relative}.wc-product-display-settings .product-category-select div+div>ul{height:100px;overflow-y:scroll;padding:0 0 15px}.wc-product-display-settings .product-category-select #product-category-search{width:100%;margin:0 0 10px}.wc-product-display-settings .product-category-select ul{list-style-type:none}.wc-product-display-settings .product-category-select ul li{margin:3px 0}.wc-product-display-settings .product-category-select ul li input[type=checkbox]{margin-right:5px}.wc-product-display-settings .product-category-select ul li ul li input[type=checkbox]{margin-right:20px}.wc-product-display-settings .product-category-select:after{content:'';position:absolute;bottom:0;width:100%;height:1.5em;background:-webkit-linear-gradient(rgba(255,255,255,.1) 0,#f8f9f9 100%);background:linear-gradient(rgba(255,255,255,.1) 0,#f8f9f9 100%)}.wc-product-display-settings .block-footer{margin:0 -1em;padding-top:1em;border-top:1px solid #e6eaee;text-align:center}.wc-product-display-settings .display-select+.block-footer{padding-top:0;border:0}@media only screen and (min-width:700px){.wc-product-display-settings .product-category-select{width:400px}.wc-product-display-settings .product-category-select div+div{overflow:hidden}.wc-product-display-settings .product-category-select div+div>ul{width:440px}}
|
|
@ -0,0 +1,203 @@
|
|||
/**
|
||||
* Right now these are basic layout/placeholder styles for dev + testing.
|
||||
* @todo Refine these to final styles.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Products block preview mode.
|
||||
*/
|
||||
.wc-products-block-preview {
|
||||
&.grid {
|
||||
overflow: hidden;
|
||||
|
||||
.product-preview {
|
||||
float: left;
|
||||
text-align: center;
|
||||
margin-right: 3.8%;
|
||||
}
|
||||
|
||||
&.cols-1 .product-preview {
|
||||
float: none;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
&.cols-2 .product-preview {
|
||||
width: 48%;
|
||||
|
||||
&:nth-of-type(2n) {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.cols-3 .product-preview {
|
||||
width: 30.75%;
|
||||
|
||||
&:nth-of-type(3n) {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.cols-4 .product-preview {
|
||||
width: 22.05%;
|
||||
|
||||
&:nth-of-type(4n) {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.cols-5 .product-preview {
|
||||
width: 16.9%;
|
||||
|
||||
&:nth-of-type(5n) {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.list {
|
||||
.product-preview {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
img {
|
||||
float: left;
|
||||
width: 50%;
|
||||
margin-right: 2em;
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
}
|
||||
|
||||
.product-add-to-cart {
|
||||
background: gray;
|
||||
border-radius: 10px;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
padding: .5em 1em;
|
||||
line-height: 3em;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Products block edit mode.
|
||||
*/
|
||||
.wc-product-display-settings {
|
||||
background-color: #f8f9f9;
|
||||
font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif;
|
||||
font-size: 13px;
|
||||
padding: 1em;
|
||||
min-height: 200px;
|
||||
position: relative;
|
||||
|
||||
> h4 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.display-settings-container {
|
||||
background-color: #ffffff;
|
||||
|
||||
&.existing {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
z-index: 999;
|
||||
}
|
||||
}
|
||||
|
||||
.wc-products-display-option {
|
||||
border: 1px solid gray; // @todo remove this when doing real styles.
|
||||
}
|
||||
|
||||
.display-select {
|
||||
margin: 0 -1em 20px;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 1px solid #e6eaee;
|
||||
text-align: center;
|
||||
|
||||
select {
|
||||
margin-left: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.product-specific-select {
|
||||
.add-new {
|
||||
background: #ddd;
|
||||
display: inline-block;
|
||||
margin-bottom: 20px;
|
||||
padding: 60px 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.product-category-select {
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
|
||||
div + div {
|
||||
> ul {
|
||||
height: 100px;
|
||||
overflow-y: scroll;
|
||||
padding: 0 0 15px;
|
||||
}
|
||||
}
|
||||
|
||||
#product-category-search {
|
||||
width: 100%;
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: none;
|
||||
|
||||
li {
|
||||
margin: 3px 0;
|
||||
|
||||
input[type="checkbox"] {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
ul {
|
||||
li {
|
||||
input[type="checkbox"] {
|
||||
margin-right: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 1.5em;
|
||||
background: linear-gradient( rgba( 255, 255, 255, .1 ) 0, #f8f9f9 100% );
|
||||
}
|
||||
}
|
||||
|
||||
.block-footer {
|
||||
margin: 0 -1em;
|
||||
padding-top: 1em;
|
||||
border-top: 1px solid #e6eaee;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.display-select + .block-footer {
|
||||
padding-top: 0;
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 700px) {
|
||||
.wc-product-display-settings {
|
||||
.product-category-select {
|
||||
width: 400px;
|
||||
|
||||
div + div {
|
||||
overflow: hidden;
|
||||
|
||||
> ul {
|
||||
width: 440px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,726 @@
|
|||
const { __ } = wp.i18n;
|
||||
const { registerBlockType, InspectorControls, BlockControls } = wp.blocks;
|
||||
const { Toolbar, withAPIData, Dropdown } = wp.components;
|
||||
const { RangeControl, ToggleControl, SelectControl } = InspectorControls;
|
||||
|
||||
/**
|
||||
* When the display mode is 'Specific products' search for and add products to the block.
|
||||
*
|
||||
* @todo Add the functionality and everything.
|
||||
*/
|
||||
class ProductsSpecificSelect extends React.Component {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
constructor( props ) {
|
||||
super( props );
|
||||
|
||||
this.state = {
|
||||
selectedProducts: props.selected_display_setting,
|
||||
}
|
||||
}
|
||||
|
||||
selectProduct( evt ) {
|
||||
evt.preventDefault();
|
||||
|
||||
let selectProduct = this.state.selectProduct;
|
||||
|
||||
this.setState( {
|
||||
selectProduct: selectProduct
|
||||
} );
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="product-specific-select">
|
||||
<div className="add-new">
|
||||
<Dropdown
|
||||
className="my-container-class-name"
|
||||
contentClassName="my-popover-content-classname"
|
||||
position="bottom right"
|
||||
renderToggle={ ( { isOpen, onToggle } ) => (
|
||||
<button className="button button-large" onClick={ onToggle } aria-expanded={ isOpen }>
|
||||
{ __( 'Add product' ) }
|
||||
</button>
|
||||
) }
|
||||
renderContent={ () => (
|
||||
<div>
|
||||
<ProductSpecifcSearch />
|
||||
</div>
|
||||
) }
|
||||
/>
|
||||
</div>
|
||||
<ProductsSpecificList selectedProducts={ this.state.selectedProducts } />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const ProductSpecifcSearch = withAPIData( ( props ) => {
|
||||
return {
|
||||
products: '/wc/v2/products?per_page=10'
|
||||
};
|
||||
} )( ( { products } ) => {
|
||||
if ( ! products.data ) {
|
||||
return __( 'Loading' );
|
||||
}
|
||||
|
||||
if ( 0 === products.data.length ) {
|
||||
return __( 'No products found' );
|
||||
}
|
||||
|
||||
const ProductsList = ( { products } ) => {
|
||||
return ( products.length > 0 ) && (
|
||||
<ul>
|
||||
{ products.map( ( product ) => (
|
||||
<li>
|
||||
<button type="button" class="components-button" id={ 'product-' + product.id }>
|
||||
<img src={ product.images[0].src } width="30px" /> { product.name }
|
||||
</button>
|
||||
</li>
|
||||
) ) }
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
|
||||
let productsData = products.data;
|
||||
|
||||
return (
|
||||
<div role="menu" aria-orientation="vertical" aria-label="{ __( 'Products list' ) }">
|
||||
<ProductsList products={ productsData } />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
const ProductsSpecificList = ( { selectedProducts } ) => {
|
||||
if ( ! selectedProducts || 0 === selectedProducts.length ) {
|
||||
return __( 'No products selected found' );
|
||||
}
|
||||
|
||||
const classes = "wc-products-block-preview";
|
||||
const attributes = {};
|
||||
|
||||
return (
|
||||
<div className={ classes }>
|
||||
{ selectedProducts.data.map( ( product ) => (
|
||||
<ProductPreview product={ product } attributes={ attributes } />
|
||||
) ) }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* When the display mode is 'Product category' search for and select product categories to pull products from.
|
||||
*/
|
||||
class ProductsCategorySelect extends React.Component {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
constructor( props ) {
|
||||
super( props );
|
||||
|
||||
this.state = {
|
||||
selectedCategories: props.selected_display_setting,
|
||||
filterQuery: ''
|
||||
}
|
||||
|
||||
this.checkboxChange = this.checkboxChange.bind( this );
|
||||
this.filterResults = this.filterResults.bind( this );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle checkbox toggle.
|
||||
*
|
||||
* @param Event object evt
|
||||
*/
|
||||
checkboxChange( evt ) {
|
||||
let selectedCategories = this.state.selectedCategories;
|
||||
|
||||
if ( evt.target.checked && ! selectedCategories.includes( parseInt( evt.target.value, 10 ) ) ) {
|
||||
selectedCategories.push( parseInt( evt.target.value, 10 ) );
|
||||
} else if ( ! evt.target.checked ) {
|
||||
selectedCategories = selectedCategories.filter( category => category !== parseInt( evt.target.value, 10 ) );
|
||||
}
|
||||
|
||||
this.setState( {
|
||||
selectedCategories: selectedCategories
|
||||
} );
|
||||
|
||||
this.props.update_display_setting_callback( selectedCategories );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter categories.
|
||||
*
|
||||
* @param Event object evt
|
||||
*/
|
||||
filterResults( evt ) {
|
||||
this.setState( {
|
||||
filterQuery: evt.target.value
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the list of categories and the search input.
|
||||
*/
|
||||
render() {
|
||||
return (
|
||||
<div className="product-category-select">
|
||||
<ProductCategoryFilter filterResults={ this.filterResults } />
|
||||
<ProductCategoryList filterQuery={ this.state.filterQuery } selectedCategories={ this.state.selectedCategories } checkboxChange={ this.checkboxChange } />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The category search input.
|
||||
*/
|
||||
const ProductCategoryFilter = ( { filterResults } ) => {
|
||||
return (
|
||||
<div>
|
||||
<input id="product-category-search" type="search" placeholder={ __( 'Search for categories' ) } onChange={ filterResults } />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch and build a tree of product categories.
|
||||
*/
|
||||
const ProductCategoryList = withAPIData( ( props ) => {
|
||||
return {
|
||||
categories: '/wc/v2/products/categories'
|
||||
};
|
||||
} )( ( { categories, filterQuery, selectedCategories, checkboxChange } ) => {
|
||||
if ( ! categories.data ) {
|
||||
return __( 'Loading' );
|
||||
}
|
||||
|
||||
if ( 0 === categories.data.length ) {
|
||||
return __( 'No categories found' );
|
||||
}
|
||||
|
||||
const CategoryTree = ( { categories, parent } ) => {
|
||||
let filteredCategories = categories.filter( ( category ) => category.parent === parent );
|
||||
|
||||
return ( filteredCategories.length > 0 ) && (
|
||||
<ul>
|
||||
{ filteredCategories.map( ( category ) => (
|
||||
<li key={ category.id }>
|
||||
<label htmlFor={ 'product-category-' + category.id }>
|
||||
<input type="checkbox"
|
||||
id={ 'product-category-' + category.id }
|
||||
value={ category.id }
|
||||
checked={ selectedCategories.includes( category.id ) }
|
||||
onChange={ checkboxChange }
|
||||
/> { category.name }
|
||||
</label>
|
||||
<CategoryTree categories={ categories } parent={ category.id } />
|
||||
</li>
|
||||
) ) }
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
|
||||
let categoriesData = categories.data;
|
||||
|
||||
if ( '' !== filterQuery ) {
|
||||
categoriesData = categoriesData.filter( category => category.slug.includes( filterQuery.toLowerCase() ) );
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<CategoryTree categories={ categoriesData } parent={ 0 } />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* One option from the list of all available ways to display products.
|
||||
*/
|
||||
class ProductsBlockSettingsEditorDisplayOption extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div className="wc-products-display-option" onClick={ () => { this.props.update_display_callback( this.props.value ) } } >
|
||||
<h4>{ this.props.title }</h4>
|
||||
<p>{ this.props.description }</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of all available ways to display products.
|
||||
*/
|
||||
class ProductsBlockSettingsEditorDisplayOptions extends React.Component {
|
||||
render() {
|
||||
const products_block_display_settings = [
|
||||
{
|
||||
title: __( 'All' ),
|
||||
description: __( 'All products' ),
|
||||
value: 'all',
|
||||
},
|
||||
{
|
||||
title: __( 'Specific' ),
|
||||
description: __( 'Hand-picked products' ),
|
||||
value: 'specific',
|
||||
},
|
||||
{
|
||||
title: __( 'Category' ),
|
||||
description: __( 'Products from a specific category' ),
|
||||
value: 'category',
|
||||
}
|
||||
];
|
||||
|
||||
let classes = 'display-settings-container';
|
||||
if ( this.props.existing ) {
|
||||
classes += ' existing';
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={ classes }>
|
||||
<p>{ __( 'Select the scope for products to display:' ) }</p>
|
||||
{ products_block_display_settings.map( ( setting ) =>
|
||||
<ProductsBlockSettingsEditorDisplayOption { ...setting } update_display_callback={ this.props.update_display_callback } />
|
||||
) }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The products block when in Edit mode.
|
||||
*/
|
||||
class ProductsBlockSettingsEditor extends React.Component {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
constructor( props ) {
|
||||
super( props );
|
||||
this.state = {
|
||||
display: props.selected_display,
|
||||
menu_visible: props.selected_display ? false : true,
|
||||
}
|
||||
|
||||
this.updateDisplay = this.updateDisplay.bind( this );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the display settings for the block.
|
||||
*
|
||||
* @param Event object evt
|
||||
*/
|
||||
updateDisplay( value ) {
|
||||
this.setState( {
|
||||
display: value,
|
||||
menu_visible: false,
|
||||
} );
|
||||
|
||||
this.props.update_display_callback( value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the display settings dropdown and any extra contextual settings.
|
||||
*/
|
||||
render() {
|
||||
let extra_settings = null;
|
||||
if ( 'specific' === this.state.display ) {
|
||||
extra_settings = <ProductsSpecificSelect />;
|
||||
} else if ( 'category' === this.state.display ) {
|
||||
extra_settings = <ProductsCategorySelect { ...this.props } />;
|
||||
}
|
||||
|
||||
const menu = this.state.menu_visible ? <ProductsBlockSettingsEditorDisplayOptions existing={ this.state.display ? true : false } update_display_callback={ this.updateDisplay } /> : null;
|
||||
|
||||
let heading = <h4>{ __( 'Products' ) }</h4>;
|
||||
if ( this.state.display && ! this.state.menu_visible ) {
|
||||
heading = <h4>{ __( 'Displaying ' + this.state.display ) } <a onClick={ () => { this.setState( { menu_visible: true } ) } }>{ __( 'Change' ) }</a></h4>;
|
||||
} else if ( this.state.display ) {
|
||||
heading = <h4>{ __( 'Displaying ' + this.state.display ) } <a onClick={ () => { this.setState( { menu_visible: false } ) } }>{ __( 'Cancel' ) }</a></h4>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="wc-product-display-settings">
|
||||
|
||||
{ heading }
|
||||
|
||||
{ menu }
|
||||
|
||||
{ extra_settings }
|
||||
|
||||
<div className="block-footer">
|
||||
<button type="button" className="button button-large" onClick={ this.props.done_callback }>{ __( 'Done' ) }</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* One product in the product block preview.
|
||||
*/
|
||||
class ProductPreview extends React.Component {
|
||||
|
||||
render() {
|
||||
const { attributes, product } = this.props;
|
||||
|
||||
let image = null;
|
||||
if ( product.images.length ) {
|
||||
image = <img src={ product.images[0].src } />
|
||||
}
|
||||
|
||||
let title = null;
|
||||
if ( attributes.display_title ) {
|
||||
title = <div className="product-title">{ product.name }</div>
|
||||
}
|
||||
|
||||
let price = null;
|
||||
if ( attributes.display_price ) {
|
||||
price = <div className="product-price">{ product.price }</div>
|
||||
}
|
||||
|
||||
let add_to_cart = null;
|
||||
if ( attributes.display_add_to_cart ) {
|
||||
add_to_cart = <span className="product-add-to-cart">{ __( 'Add to cart' ) }</span>
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="product-preview">
|
||||
{ image }
|
||||
{ title }
|
||||
{ price }
|
||||
{ add_to_cart }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a preview of what the block will look like with current settings.
|
||||
*/
|
||||
const ProductsBlockPreview = withAPIData( ( { attributes } ) => {
|
||||
|
||||
const { columns, rows, order, display, display_setting, layout } = attributes;
|
||||
|
||||
let query = {
|
||||
per_page: ( 'list' === layout ) ? rows : rows * columns,
|
||||
orderby: order
|
||||
};
|
||||
|
||||
// @todo These will likely need to be modified to work with the final version of the category/product picker attributes.
|
||||
if ( 'specific' === display ) {
|
||||
query.include = JSON.stringify( display_setting );
|
||||
query.orderby = 'include';
|
||||
} else if ( 'category' === display ) {
|
||||
query.category = display_setting.join( ',' );
|
||||
}
|
||||
|
||||
let query_string = '?';
|
||||
for ( const key of Object.keys( query ) ) {
|
||||
query_string += key + '=' + query[ key ] + '&';
|
||||
}
|
||||
|
||||
return {
|
||||
products: '/wc/v2/products' + query_string
|
||||
};
|
||||
|
||||
} )( ( { products, attributes } ) => {
|
||||
|
||||
if ( ! products.data ) {
|
||||
return __( 'Loading' );
|
||||
}
|
||||
|
||||
if ( 0 === products.data.length ) {
|
||||
return __( 'No products found' );
|
||||
}
|
||||
|
||||
const classes = "wc-products-block-preview " + attributes.layout + " cols-" + attributes.columns;
|
||||
|
||||
return (
|
||||
<div className={ classes }>
|
||||
{ products.data.map( ( product ) => (
|
||||
<ProductPreview key={ product.id } product={ product } attributes={ attributes } />
|
||||
) ) }
|
||||
</div>
|
||||
);
|
||||
} );
|
||||
|
||||
/**
|
||||
* Register and run the products block.
|
||||
*/
|
||||
registerBlockType( 'woocommerce/products', {
|
||||
title: __( 'Products' ),
|
||||
icon: 'universal-access-alt', // @todo Needs a good icon.
|
||||
category: 'widgets',
|
||||
|
||||
attributes: {
|
||||
|
||||
/**
|
||||
* Layout to use. 'grid' or 'list'.
|
||||
*/
|
||||
layout: {
|
||||
type: 'string',
|
||||
default: 'grid',
|
||||
},
|
||||
|
||||
/**
|
||||
* Number of columns.
|
||||
*/
|
||||
columns: {
|
||||
type: 'number',
|
||||
default: 3,
|
||||
},
|
||||
|
||||
/**
|
||||
* Number of rows.
|
||||
*/
|
||||
rows: {
|
||||
type: 'number',
|
||||
default: 1,
|
||||
},
|
||||
|
||||
/**
|
||||
* Whether to display product titles.
|
||||
*/
|
||||
display_title: {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
},
|
||||
|
||||
/**
|
||||
* Whether to display prices.
|
||||
*/
|
||||
display_price: {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
},
|
||||
|
||||
/**
|
||||
* Whether to display Add to Cart buttons.
|
||||
*/
|
||||
display_add_to_cart: {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
|
||||
/**
|
||||
* Order to use for products. 'date', or 'title'.
|
||||
*/
|
||||
order: {
|
||||
type: 'string',
|
||||
default: 'date',
|
||||
},
|
||||
|
||||
/**
|
||||
* What types of products to display. 'all', 'specific', or 'category'.
|
||||
*/
|
||||
display: {
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
|
||||
/**
|
||||
* Which products to display if 'display' is 'specific' or 'category'. Array of product ids or category slugs depending on setting.
|
||||
*/
|
||||
display_setting: {
|
||||
type: 'array',
|
||||
default: [],
|
||||
},
|
||||
|
||||
/**
|
||||
* Whether the block is in edit or preview mode.
|
||||
*/
|
||||
edit_mode: {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders and manages the block.
|
||||
*/
|
||||
edit( props ) {
|
||||
const { attributes, className, focus, setAttributes, setFocus } = props;
|
||||
const { layout, rows, columns, display_title, display_price, display_add_to_cart, order, display, display_setting, edit_mode } = attributes;
|
||||
|
||||
/**
|
||||
* Get the components for the sidebar settings area that is rendered while focused on a Products block.
|
||||
*
|
||||
* @return Component
|
||||
*/
|
||||
function getInspectorControls() {
|
||||
return (
|
||||
<InspectorControls key="inspector">
|
||||
<h3>{ __( 'Layout' ) }</h3>
|
||||
<RangeControl
|
||||
label={ __( 'Columns' ) }
|
||||
value={ columns }
|
||||
onChange={ ( value ) => setAttributes( { columns: value } ) }
|
||||
min={ 1 }
|
||||
max={ 6 }
|
||||
/>
|
||||
<RangeControl
|
||||
label={ __( 'Rows' ) }
|
||||
value={ rows }
|
||||
onChange={ ( value ) => setAttributes( { rows: value } ) }
|
||||
min={ 1 }
|
||||
max={ 6 }
|
||||
/>
|
||||
<ToggleControl
|
||||
label={ __( 'Display title' ) }
|
||||
checked={ display_title }
|
||||
onChange={ () => setAttributes( { display_title: ! display_title } ) }
|
||||
/>
|
||||
<ToggleControl
|
||||
label={ __( 'Display price' ) }
|
||||
checked={ display_price }
|
||||
onChange={ () => setAttributes( { display_price: ! display_price } ) }
|
||||
/>
|
||||
<ToggleControl
|
||||
label={ __( 'Display add to cart button' ) }
|
||||
checked={ display_add_to_cart }
|
||||
onChange={ () => setAttributes( { display_add_to_cart: ! display_add_to_cart } ) }
|
||||
/>
|
||||
<SelectControl
|
||||
key="query-panel-select"
|
||||
label={ __( 'Order' ) }
|
||||
value={ order }
|
||||
options={ [
|
||||
{
|
||||
label: __( 'Newness' ),
|
||||
value: 'date',
|
||||
},
|
||||
{
|
||||
label: __( 'Title' ),
|
||||
value: 'title',
|
||||
},
|
||||
] }
|
||||
onChange={ ( value ) => setAttributes( { order: value } ) }
|
||||
/>
|
||||
</InspectorControls>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the components for the toolbar area that appears on top of the block when focused.
|
||||
*
|
||||
* @return Component
|
||||
*/
|
||||
function getToolbarControls() {
|
||||
const layoutControls = [
|
||||
{
|
||||
icon: 'list-view',
|
||||
title: __( 'List View' ),
|
||||
onClick: () => setAttributes( { layout: 'list' } ),
|
||||
isActive: layout === 'list',
|
||||
},
|
||||
{
|
||||
icon: 'grid-view',
|
||||
title: __( 'Grid View' ),
|
||||
onClick: () => setAttributes( { layout: 'grid' } ),
|
||||
isActive: layout === 'grid',
|
||||
},
|
||||
];
|
||||
|
||||
const editButton = [
|
||||
{
|
||||
icon: 'edit',
|
||||
title: __( 'Edit' ),
|
||||
onClick: () => setAttributes( { edit_mode: ! edit_mode } ),
|
||||
isActive: edit_mode,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<BlockControls key="controls">
|
||||
<Toolbar controls={ layoutControls } />
|
||||
<Toolbar controls={ editButton } />
|
||||
</BlockControls>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the block preview component for preview mode.
|
||||
*
|
||||
* @return Component
|
||||
*/
|
||||
function getPreview() {
|
||||
return <ProductsBlockPreview attributes={ attributes } />;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the block edit component for edit mode.
|
||||
*
|
||||
* @return Component
|
||||
*/
|
||||
function getSettingsEditor() {
|
||||
return (
|
||||
<ProductsBlockSettingsEditor
|
||||
selected_display={ display }
|
||||
selected_display_setting={ display_setting }
|
||||
update_display_callback={ ( value ) => setAttributes( { display: value } ) }
|
||||
update_display_setting_callback={ ( value ) => setAttributes( { display_setting: value } ) }
|
||||
done_callback={ () => setAttributes( { edit_mode: false } ) }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return [
|
||||
( !! focus ) ? getInspectorControls() : null,
|
||||
( !! focus ) ? getToolbarControls() : null,
|
||||
edit_mode ? getSettingsEditor() : getPreview(),
|
||||
];
|
||||
},
|
||||
|
||||
/**
|
||||
* Save the block content in the post content. Block content is saved as a products shortcode.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
save( props ) {
|
||||
const { layout, rows, columns, display_title, display_price, display_add_to_cart, order, display, display_setting, className } = props.attributes;
|
||||
|
||||
let shortcode_atts = new Map();
|
||||
shortcode_atts.set( 'orderby', order );
|
||||
shortcode_atts.set( 'limit', 'grid' === layout ? rows * columns : rows );
|
||||
shortcode_atts.set( 'class', 'list' === layout ? className + ' list-layout' : className );
|
||||
|
||||
if ( 'grid' === layout ) {
|
||||
shortcode_atts.set( 'columns', columns );
|
||||
}
|
||||
|
||||
if ( ! display_title ) {
|
||||
shortcode_atts.set( 'show_title', 0 );
|
||||
}
|
||||
|
||||
if ( ! display_price ) {
|
||||
shortcode_atts.set( 'show_price', 0 );
|
||||
}
|
||||
|
||||
if ( ! display_add_to_cart ) {
|
||||
shortcode_atts.set( 'show_add_to_cart', 0 );
|
||||
}
|
||||
|
||||
if ( 'specific' === display ) {
|
||||
shortcode_atts.set( 'include', display_setting.join( ',' ) );
|
||||
}
|
||||
|
||||
if ( 'category' === display ) {
|
||||
shortcode_atts.set( 'category', display_setting.join( ',' ) );
|
||||
}
|
||||
|
||||
// Build the shortcode string out of the set shortcode attributes.
|
||||
let shortcode = '[products';
|
||||
for ( let [key, value] of shortcode_atts ) {
|
||||
shortcode += ' ' + key + '="' + value + '"';
|
||||
}
|
||||
shortcode += ']';
|
||||
|
||||
return shortcode;
|
||||
},
|
||||
} );
|
|
@ -0,0 +1,52 @@
|
|||
{
|
||||
"name": "woocommerceproductsblock",
|
||||
"title": "WooCommerce Products Block",
|
||||
"version": "3.3.0",
|
||||
"homepage": "https://woocommerce.com/",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com:woocommerce/woocommerce-gutenberg-products-block.git"
|
||||
},
|
||||
"license": "GPL-3.0+",
|
||||
"main": "Gruntfile.js",
|
||||
"scripts": {
|
||||
"build-gutenberg": "cross-env BABEL_ENV=default NODE_ENV=production webpack",
|
||||
"build-gutenberg-watch": "cross-env BABEL_ENV=default webpack --watch"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "~7.1.6",
|
||||
"babel": "^6.5.2",
|
||||
"babel-cli": "^6.14.0",
|
||||
"babel-core": "6.25.0",
|
||||
"babel-eslint": "^7.0.0",
|
||||
"babel-loader": "^7.1.1",
|
||||
"babel-plugin-transform-react-jsx": "^6.24.1",
|
||||
"babel-plugin-add-module-exports": "^0.2.1",
|
||||
"babel-preset-env": "^1.6.0",
|
||||
"babel-preset-es2015": "^6.14.0",
|
||||
"babel-preset-stage-2": "^6.13.0",
|
||||
"config": "^1.24.0",
|
||||
"cross-env": "~5.1.1",
|
||||
"grunt": "~1.0.1",
|
||||
"grunt-checktextdomain": "~1.0.1",
|
||||
"grunt-contrib-clean": "~1.1.0",
|
||||
"grunt-contrib-concat": "~1.0.1",
|
||||
"grunt-contrib-cssmin": "~2.2.1",
|
||||
"grunt-contrib-jshint": "~1.1.0",
|
||||
"grunt-contrib-uglify": "~3.1.0",
|
||||
"grunt-contrib-watch": "~1.0.0",
|
||||
"grunt-phpcs": "~0.4.0",
|
||||
"grunt-postcss": "~0.9.0",
|
||||
"grunt-rtlcss": "~2.0.1",
|
||||
"grunt-sass": "~2.0.0",
|
||||
"grunt-shell": "~2.1.0",
|
||||
"grunt-stylelint": "~0.9.0",
|
||||
"grunt-wp-i18n": "~1.0.1",
|
||||
"stylelint": "~8.2.0",
|
||||
"webpack": "^3.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.9.3",
|
||||
"npm": ">=5.5.1"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
/**
|
||||
* Config for compiling Gutenberg blocks JS.
|
||||
*/
|
||||
var GutenbergBlocksConfig = {
|
||||
entry: {
|
||||
'products-block': './assets/js/products-block.jsx',
|
||||
// 'next-block-name': './assets/js/gutenberg/some-other-block.jsx', <-- How to add more gutenblocks to this.
|
||||
},
|
||||
output: {
|
||||
path: __dirname + '/assets/js/',
|
||||
filename: '[name].js',
|
||||
},
|
||||
module: {
|
||||
loaders: [
|
||||
{
|
||||
test: /.jsx$/,
|
||||
loader: 'babel-loader',
|
||||
exclude: /node_modules/,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = [ GutenbergBlocksConfig ];
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
/**
|
||||
* Plugin Name: WooCommerce Gutenberg Products Block
|
||||
* Plugin URI: https://github.com/woocommerce/woocommerce-gutenberg-products-block
|
||||
* Description: Prototype of the WooCommerce Gutenberg Products block.
|
||||
* Version: 1.0.0
|
||||
* Author: Automattic
|
||||
* Author URI: https://woocommerce.com
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || die();
|
||||
|
||||
function wgpb_initialize() {
|
||||
|
||||
if ( function_exists( 'register_block_type' ) ) {
|
||||
add_action( 'init', 'wgpb_register_products_block' );
|
||||
}
|
||||
|
||||
}
|
||||
add_action( 'woocommerce_loaded', 'wgpb_initialize' );
|
||||
|
||||
/**
|
||||
* Register the Products block and its scripts.
|
||||
*/
|
||||
function wgpb_register_products_block() {
|
||||
wp_register_script(
|
||||
'woocommerce-products-block-editor',
|
||||
plugins_url( 'assets/js/products-block.js', __FILE__ ),
|
||||
array( 'wp-blocks', 'wp-element' ),
|
||||
rand() // @todo Change this to WC_VERSION when merged into WooCommerce.
|
||||
);
|
||||
|
||||
wp_register_style(
|
||||
'woocommerce-products-block-editor',
|
||||
plugins_url( 'assets/css/gutenberg-products-block.css', __FILE__ ),
|
||||
array( 'wp-edit-blocks' ),
|
||||
rand() // @todo Change this to WC_VERSION when merged into WooCommerce.
|
||||
);
|
||||
|
||||
register_block_type( 'woocommerce/products', array(
|
||||
'editor_script' => 'woocommerce-products-block-editor',
|
||||
'editor_style' => 'woocommerce-products-block-editor',
|
||||
) );
|
||||
}
|
Loading…
Reference in New Issue