Revert "Revert "Update to new Snackbar component and notice store"" (https://github.com/woocommerce/woocommerce-admin/pull/2643)

* Revert "Revert "Update to new Snackbar component and notice store""

* Update uploader component to use createNotice

* Bump required WP version to 5.2

* Update required PHP and WP versions in plugin header
This commit is contained in:
Paul Sealock 2019-07-23 15:26:46 +12:00 committed by Joshua T Flowers
parent 9f6e200ab7
commit d98eceffc7
37 changed files with 357 additions and 454 deletions

View File

@ -10,7 +10,7 @@ This is a feature plugin for a modern, javascript-driven WooCommerce Admin exper
## Prerequisites
[WordPress 5.0 or greater](https://wordpress.org/download/) and [WooCommerce 3.6.0 or greater](https://wordpress.org/plugins/woocommerce/) should be installed prior to activating the WooCommerce Admin feature plugin.
[WordPress 5.2 or greater](https://wordpress.org/download/) and [WooCommerce 3.6.0 or greater](https://wordpress.org/plugins/woocommerce/) should be installed prior to activating the WooCommerce Admin feature plugin.
For better debugging, it's also recommended you add `define( 'SCRIPT_DEBUG', true );` to your wp-config. This will load the unminified version of all libraries, and specifically the development build of React.

View File

@ -19,8 +19,8 @@ WP_CORE_DIR=${WP_CORE_DIR-$TMPDIR/wordpress/}
# Error if WP < 5
if [[ $WP_VERSION =~ ^([0-9]+)[0-9\.]+\-? ]]; then
if [ "5" -gt "${BASH_REMATCH[1]}" ]; then
echo "You must use WordPress 5.0 or greater."
if [ "5.2" -gt "${BASH_REMATCH[1]}" ]; then
echo "You must use WordPress 5.2 or greater."
exit 1
fi
fi

View File

@ -48,13 +48,13 @@ class HistoricalData extends Component {
}
makeQuery( path, errorMessage ) {
const { addNotice } = this.props;
const { createNotice } = this.props;
apiFetch( { path, method: 'POST' } )
.then( response => {
if ( 'success' === response.status ) {
addNotice( { status: 'success', message: response.message } );
createNotice( 'success', response.message );
} else {
addNotice( { status: 'error', message: errorMessage } );
createNotice( 'error', errorMessage );
this.setState( {
activeImport: false,
lastImportStopTimestamp: Date.now(),
@ -63,7 +63,7 @@ class HistoricalData extends Component {
} )
.catch( error => {
if ( error && error.message ) {
addNotice( { status: 'error', message: error.message } );
createNotice( 'error', error.message );
this.setState( {
activeImport: false,
lastImportStopTimestamp: Date.now(),

View File

@ -85,25 +85,22 @@ class Settings extends Component {
};
componentDidUpdate() {
const { addNotice, isError, isRequesting } = this.props;
const { createNotice, isError, isRequesting } = this.props;
const { saving, isDirty } = this.state;
let newIsDirtyState = isDirty;
if ( saving && ! isRequesting ) {
if ( ! isError ) {
addNotice( {
status: 'success',
message: __( 'Your settings have been successfully saved.', 'woocommerce-admin' ),
} );
createNotice(
'success',
__( 'Your settings have been successfully saved.', 'woocommerce-admin' )
);
newIsDirtyState = false;
} else {
addNotice( {
status: 'error',
message: __(
'There was an error saving your settings. Please try again.',
'woocommerce-admin'
),
} );
createNotice(
'error',
__( 'There was an error saving your settings. Please try again.', 'woocommerce-admin' )
);
}
/* eslint-disable react/no-did-update-set-state */
this.setState( { saving: false, isDirty: newIsDirtyState } );
@ -166,7 +163,7 @@ class Settings extends Component {
}
render() {
const { addNotice } = this.props;
const { createNotice } = this.props;
const { hasError } = this.state;
if ( hasError ) {
return null;
@ -193,7 +190,7 @@ class Settings extends Component {
</Button>
</div>
</div>
<HistoricalData addNotice={ addNotice } />
<HistoricalData createNotice={ createNotice } />
</Fragment>
);
}
@ -210,11 +207,11 @@ export default compose(
return { getSettings, isError, isRequesting, settings };
} ),
withDispatch( dispatch => {
const { addNotice } = dispatch( 'wc-admin' );
const { createNotice } = dispatch( 'core/notices' );
const { updateSettings } = dispatch( 'wc-api' );
return {
addNotice,
createNotice,
updateSettings,
};
} )

View File

@ -74,7 +74,7 @@ class Setting extends Component {
};
handleInputCallback = () => {
const { addNotice, callback } = this.props;
const { createNotice, callback } = this.props;
if ( 'function' !== typeof callback ) {
return;
@ -82,7 +82,7 @@ class Setting extends Component {
return new Promise( ( resolve, reject ) => {
this.setState( { disabled: true } );
callback( resolve, reject, addNotice );
callback( resolve, reject, createNotice );
} )
.then( () => {
this.setState( { disabled: false } );
@ -197,7 +197,7 @@ Setting.propTypes = {
export default compose(
withDispatch( dispatch => {
const { addNotice } = dispatch( 'wc-admin' );
return { addNotice };
const { createNotice } = dispatch( 'core/notices' );
return { createNotice };
} )
)( Setting );

View File

@ -95,7 +95,7 @@ class ProfileWizard extends Component {
}
async goToNextStep() {
const { addNotice, isError, updateProfileItems } = this.props;
const { createNotice, isError, updateProfileItems } = this.props;
const currentStep = this.getCurrentStep();
const currentStepIndex = getSteps().findIndex( s => s.key === currentStep.key );
const nextStep = getSteps()[ currentStepIndex + 1 ];
@ -104,10 +104,10 @@ class ProfileWizard extends Component {
await updateProfileItems( { completed: true } );
if ( isError ) {
addNotice( {
status: 'error',
message: __( 'There was a problem completing the profiler.', 'woocommerce-admin' ),
} );
createNotice(
'error',
__( 'There was a problem completing the profiler.', 'woocommerce-admin' )
);
}
return;
}
@ -144,10 +144,11 @@ export default compose(
return { isError };
} ),
withDispatch( dispatch => {
const { addNotice, updateProfileItems } = dispatch( 'wc-api' );
const { updateProfileItems } = dispatch( 'wc-api' );
const { createNotice } = dispatch( 'core/notices' );
return {
addNotice,
createNotice,
updateProfileItems,
};
} )

View File

@ -48,7 +48,7 @@ class BusinessDetails extends Component {
return;
}
const { addNotice, goToNextStep, isError, updateProfileItems } = this.props;
const { createNotice, goToNextStep, isError, updateProfileItems } = this.props;
const { extensions, other_platform, product_count, selling_venues } = this.state.fields;
const businessExtensions = keys( pickBy( extensions ) );
@ -70,10 +70,10 @@ class BusinessDetails extends Component {
if ( ! isError ) {
goToNextStep();
} else {
addNotice( {
status: 'error',
message: __( 'There was a problem updating your business details.', 'woocommerce-admin' ),
} );
createNotice(
'error',
__( 'There was a problem updating your business details.', 'woocommerce-admin' )
);
}
}
@ -355,10 +355,11 @@ export default compose(
return { isError };
} ),
withDispatch( dispatch => {
const { addNotice, updateProfileItems } = dispatch( 'wc-api' );
const { updateProfileItems } = dispatch( 'wc-api' );
const { createNotice } = dispatch( 'core/notices' );
return {
addNotice,
createNotice,
updateProfileItems,
};
} )

View File

@ -33,7 +33,7 @@ class Industry extends Component {
return;
}
const { addNotice, goToNextStep, isError, updateProfileItems } = this.props;
const { createNotice, goToNextStep, isError, updateProfileItems } = this.props;
recordEvent( 'storeprofiler_store_industry_continue', { store_industry: this.state.selected } );
await updateProfileItems( { industry: this.state.selected } );
@ -41,10 +41,10 @@ class Industry extends Component {
if ( ! isError ) {
goToNextStep();
} else {
addNotice( {
status: 'error',
message: __( 'There was a problem updating your industries.', 'woocommerce-admin' ),
} );
createNotice(
'error',
__( 'There was a problem updating your industries.', 'woocommerce-admin' )
);
}
}
@ -119,10 +119,11 @@ export default compose(
return { isError };
} ),
withDispatch( dispatch => {
const { addNotice, updateProfileItems } = dispatch( 'wc-api' );
const { updateProfileItems } = dispatch( 'wc-api' );
const { createNotice } = dispatch( 'core/notices' );
return {
addNotice,
createNotice,
updateProfileItems,
};
} )

View File

@ -119,10 +119,7 @@ class Plugins extends Component {
} );
return pluginResponse;
} catch ( err ) {
this.props.addNotice( {
status: 'error',
message: this.getErrorMessage( action, plugin ),
} );
this.props.createNotice( 'error', this.getErrorMessage( action, plugin ) );
this.setState( {
isPending: false,
isError: true,
@ -141,10 +138,7 @@ class Plugins extends Component {
}
throw new Error();
} catch ( err ) {
this.props.addNotice( {
status: 'error',
message: this.getErrorMessage( 'activate', 'jetpack' ),
} );
this.props.createNotice( 'error', this.getErrorMessage( 'activate', 'jetpack' ) );
this.setState( {
isPending: false,
isError: true,
@ -208,9 +202,9 @@ class Plugins extends Component {
export default compose(
withDispatch( dispatch => {
const { addNotice } = dispatch( 'wc-admin' );
const { createNotice } = dispatch( 'core/notices' );
return {
addNotice,
createNotice,
};
} )
)( Plugins );

View File

@ -43,7 +43,7 @@ class ProductTypes extends Component {
return;
}
const { addNotice, goToNextStep, isError, updateProfileItems } = this.props;
const { createNotice, goToNextStep, isError, updateProfileItems } = this.props;
recordEvent( 'storeprofiler_store_product_type_continue', {
product_type: this.state.selected,
@ -53,10 +53,10 @@ class ProductTypes extends Component {
if ( ! isError ) {
goToNextStep();
} else {
addNotice( {
status: 'error',
message: __( 'There was a problem updating your product types.', 'woocommerce-admin' ),
} );
createNotice(
'error',
__( 'There was a problem updating your product types.', 'woocommerce-admin' )
);
}
}
@ -152,10 +152,11 @@ export default compose(
return { isError };
} ),
withDispatch( dispatch => {
const { addNotice, updateProfileItems } = dispatch( 'wc-api' );
const { updateProfileItems } = dispatch( 'wc-api' );
const { createNotice } = dispatch( 'core/notices' );
return {
addNotice,
createNotice,
updateProfileItems,
};
} )

View File

@ -76,7 +76,7 @@ class Start extends Component {
}
async skipWizard() {
const { addNotice, isProfileItemsError, updateProfileItems, isSettingsError } = this.props;
const { createNotice, isProfileItemsError, updateProfileItems, isSettingsError } = this.props;
recordEvent( 'storeprofiler_welcome_clicked', { proceed_without_install: true } );
@ -84,15 +84,15 @@ class Start extends Component {
await this.updateTracking();
if ( isProfileItemsError || isSettingsError ) {
addNotice( {
status: 'error',
message: __( 'There was a problem updating your preferences.', 'woocommerce-admin' ),
} );
createNotice(
'error',
__( 'There was a problem updating your preferences.', 'woocommerce-admin' )
);
}
}
async startWizard() {
const { addNotice, isSettingsError } = this.props;
const { createNotice, isSettingsError } = this.props;
recordEvent( 'storeprofiler_welcome_clicked', { get_started: true } );
@ -101,10 +101,10 @@ class Start extends Component {
if ( ! isSettingsError ) {
this.props.goToNextStep();
} else {
addNotice( {
status: 'error',
message: __( 'There was a problem updating your preferences.', 'woocommerce-admin' ),
} );
createNotice(
'error',
__( 'There was a problem updating your preferences.', 'woocommerce-admin' )
);
}
}
@ -217,10 +217,11 @@ export default compose(
return { getSettings, isSettingsError, isProfileItemsError, isSettingsRequesting };
} ),
withDispatch( dispatch => {
const { addNotice, updateProfileItems, updateSettings } = dispatch( 'wc-api' );
const { updateProfileItems, updateSettings } = dispatch( 'wc-api' );
const { createNotice } = dispatch( 'core/notices' );
return {
addNotice,
createNotice,
updateProfileItems,
updateSettings,
};

View File

@ -85,7 +85,7 @@ class StoreDetails extends Component {
return;
}
const { addNotice, goToNextStep, isError, updateSettings } = this.props;
const { createNotice, goToNextStep, isError, updateSettings } = this.props;
const { addressLine1, addressLine2, city, countryState, postCode } = this.state.fields;
recordEvent( 'storeprofiler_store_details_continue', {
@ -105,10 +105,10 @@ class StoreDetails extends Component {
if ( ! isError ) {
goToNextStep();
} else {
addNotice( {
status: 'error',
message: __( 'There was a problem saving your store details.', 'woocommerce-admin' ),
} );
createNotice(
'error',
__( 'There was a problem saving your store details.', 'woocommerce-admin' )
);
}
}
@ -222,11 +222,11 @@ export default compose(
return { getSettings, isError, isRequesting, settings };
} ),
withDispatch( dispatch => {
const { addNotice } = dispatch( 'wc-api' );
const { createNotice } = dispatch( 'core/notices' );
const { updateSettings } = dispatch( 'wc-api' );
return {
addNotice,
createNotice,
updateSettings,
};
} )

View File

@ -40,7 +40,7 @@ class Theme extends Component {
}
async onChoose( theme ) {
const { addNotice, goToNextStep, isError, updateProfileItems } = this.props;
const { createNotice, goToNextStep, isError, updateProfileItems } = this.props;
recordEvent( 'storeprofiler_store_theme_choose', { theme } );
await updateProfileItems( { theme } );
@ -49,10 +49,10 @@ class Theme extends Component {
// @todo This should send profile information to woocommerce.com.
goToNextStep();
} else {
addNotice( {
status: 'error',
message: __( 'There was a problem selecting your store theme.', 'woocommerce-admin' ),
} );
createNotice(
'error',
__( 'There was a problem selecting your store theme.', 'woocommerce-admin' )
);
}
}
@ -206,10 +206,11 @@ export default compose(
return { isError };
} ),
withDispatch( dispatch => {
const { addNotice, updateProfileItems } = dispatch( 'wc-api' );
const { updateProfileItems } = dispatch( 'wc-api' );
const { createNotice } = dispatch( 'core/notices' );
return {
addNotice,
createNotice,
updateProfileItems,
};
} )

View File

@ -41,7 +41,7 @@ class ThemeUploader extends Component {
}
uploadTheme( file ) {
const { addNotice, onUploadComplete } = this.props;
const { createNotice, onUploadComplete } = this.props;
this.setState( { isUploading: true } );
const body = new FormData();
@ -51,12 +51,12 @@ class ThemeUploader extends Component {
.then( response => {
onUploadComplete( response );
this.setState( { isUploading: false } );
addNotice( { status: response.status, message: response.message } );
createNotice( response.status, response.message );
} )
.catch( error => {
this.setState( { isUploading: false } );
if ( error && error.message ) {
addNotice( { status: 'error', message: error.message } );
createNotice( 'error', error.message );
}
} );
}
@ -118,7 +118,7 @@ ThemeUploader.defaultProps = {
export default compose(
withDispatch( dispatch => {
const { addNotice } = dispatch( 'wc-admin' );
return { addNotice };
const { createNotice } = dispatch( 'core/notices' );
return { createNotice };
} )
)( ThemeUploader );

View File

@ -2,6 +2,7 @@
/**
* External dependencies
*/
import '@wordpress/notices';
import { render } from '@wordpress/element';
/**
@ -9,7 +10,6 @@ import { render } from '@wordpress/element';
*/
import './stylesheets/_embedded.scss';
import { EmbedLayout, PrimaryLayout as NoticeArea } from './layout';
import 'store';
import 'wc-api/wp-data-store';
const embeddedRoot = document.getElementById( 'woocommerce-embedded-root' );

View File

@ -2,6 +2,7 @@
/**
* External dependencies
*/
import '@wordpress/notices';
import { render } from '@wordpress/element';
/**
@ -9,7 +10,6 @@ import { render } from '@wordpress/element';
*/
import './stylesheets/_index.scss';
import { PageLayout } from './layout';
import 'store';
import 'wc-api/wp-data-store';
render( <PageLayout />, document.getElementById( 'root' ) );

View File

@ -6,24 +6,25 @@ import classnames from 'classnames';
import { Component } from '@wordpress/element';
import { compose } from '@wordpress/compose';
import PropTypes from 'prop-types';
import { SnackbarList } from '@wordpress/components';
import { withDispatch } from '@wordpress/data';
/**
* Internal dependencies
*/
import './style.scss';
import TransientNotice from './transient-notice';
import withSelect from 'wc-api/with-select';
class TransientNotices extends Component {
render() {
const { className, notices } = this.props;
const classes = classnames( 'woocommerce-transient-notices', className );
return (
<section className={ classes }>
{ notices && notices.map( ( notice, i ) => <TransientNotice key={ i } { ...notice } /> ) }
</section>
const { className, notices, onRemove } = this.props;
const classes = classnames(
'woocommerce-transient-notices',
'components-notices__snackbar',
className
);
return <SnackbarList notices={ notices } className={ classes } onRemove={ onRemove } />;
}
}
@ -40,9 +41,11 @@ TransientNotices.propTypes = {
export default compose(
withSelect( select => {
const { getNotices } = select( 'wc-admin' );
const notices = getNotices();
const notices = select( 'core/notices' ).getNotices();
return { notices };
} )
} ),
withDispatch( dispatch => ( {
onRemove: dispatch( 'core/notices' ).removeNotice,
} ) )
)( TransientNotices );

View File

@ -5,34 +5,13 @@
bottom: $gap-small;
left: 0;
z-index: 99999;
}
.woocommerce-transient-notice {
transform: translateX(calc(-100% - #{$gap}));
transition: all 300ms cubic-bezier(0.42, 0, 0.58, 1);
max-height: 300px; // Used to animate sliding down when multiple notices exist on exit.
@media screen and (prefers-reduced-motion: reduce) {
transition: none;
.components-snackbar-list__notice-container {
margin-left: $gap-small;
margin-right: $gap-small;
}
&.slide-enter-active,
&.slide-enter-done {
transform: translateX(0%);
}
&.slide-exit-active {
transform: translateX(calc(-100% - #{$gap}));
}
&.slide-exit-done {
max-height: 0;
margin: 0;
padding: 0;
visibility: hidden;
}
.components-notice {
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23);
.components-snackbar {
margin: 0 auto;
}
}

View File

@ -1,104 +0,0 @@
/** @format */
/**
* External dependencies
*/
import classnames from 'classnames';
import { Component } from '@wordpress/element';
import { CSSTransition } from 'react-transition-group';
import { noop } from 'lodash';
import { Notice } from '@wordpress/components';
import PropTypes from 'prop-types';
import { speak } from '@wordpress/a11y';
class TransientNotice extends Component {
constructor( props ) {
super( props );
this.state = {
visible: false,
timeout: null,
};
}
componentDidMount() {
const exitTime = this.props.exitTime;
const timeout = setTimeout(
() => {
this.setState( { visible: false } );
},
exitTime,
name,
exitTime
);
/* eslint-disable react/no-did-mount-set-state */
this.setState( { visible: true, timeout } );
/* eslint-enable react/no-did-mount-set-state */
speak( this.props.message );
}
componentWillUnmount() {
clearTimeout( this.state.timeout );
}
render() {
const { actions, className, isDismissible, message, onRemove, status } = this.props;
const classes = classnames( 'woocommerce-transient-notice', className );
return (
<CSSTransition in={ this.state.visible } timeout={ 300 } classNames="slide">
<div className={ classes }>
<Notice
status={ status }
isDismissible={ isDismissible }
onRemove={ onRemove }
actions={ actions }
>
{ message }
</Notice>
</div>
</CSSTransition>
);
}
}
TransientNotice.propTypes = {
/**
* Array of action objects.
* See https://wordpress.org/gutenberg/handbook/designers-developers/developers/components/notice/
*/
actions: PropTypes.array,
/**
* Additional class name to style the component.
*/
className: PropTypes.string,
/**
* Determines if the notice dimiss button should be shown.
* See https://wordpress.org/gutenberg/handbook/designers-developers/developers/components/notice/
*/
isDismissible: PropTypes.bool,
/**
* Function called when dismissing the notice.
* See https://wordpress.org/gutenberg/handbook/designers-developers/developers/components/notice/
*/
onRemove: PropTypes.func,
/**
* Type of notice to display.
* See https://wordpress.org/gutenberg/handbook/designers-developers/developers/components/notice/
*/
status: PropTypes.oneOf( [ 'success', 'error', 'warning' ] ),
/**
* Time in milliseconds until exit.
*/
exitTime: PropTypes.number,
};
TransientNotice.defaultProps = {
actions: [],
className: '',
exitTime: 7000,
isDismissible: false,
onRemove: noop,
status: 'warning',
};
export default TransientNotice;

View File

@ -1,22 +0,0 @@
/** @format */
/**
* External dependencies
*/
import { combineReducers, registerStore } from '@wordpress/data';
/**
* Internal dependencies
*/
import notices from './notices';
registerStore( 'wc-admin', {
reducer: combineReducers( {
...notices.reducers,
} ),
actions: {
...notices.actions,
},
selectors: {
...notices.selectors,
},
} );

View File

@ -1,9 +0,0 @@
/** @format */
const addNotice = notice => {
return { type: 'ADD_NOTICE', notice };
};
export default {
addNotice,
};

View File

@ -1,13 +0,0 @@
/** @format */
/**
* Internal dependencies
*/
import reducers from './reducers';
import actions from './actions';
import selectors from './selectors';
export default {
reducers,
actions,
selectors,
};

View File

@ -1,15 +0,0 @@
/** @format */
const DEFAULT_STATE = [];
const notices = ( state = DEFAULT_STATE, action ) => {
if ( action.type === 'ADD_NOTICE' ) {
return [ ...state, action.notice ];
}
return state;
};
export default {
notices,
};

View File

@ -1,9 +0,0 @@
/** @format */
const getNotices = state => {
return state.notices;
};
export default {
getNotices,
};

View File

@ -1,7 +0,0 @@
/** @format */
export const DEFAULT_STATE = {
notices: [],
};
export const testNotice = { message: 'Test notice' };

View File

@ -1,55 +0,0 @@
/** @format */
/**
* Internal dependencies
*/
import actions from '../actions';
import { DEFAULT_STATE, testNotice } from './fixtures';
import reducers from '../reducers';
import selectors from '../selectors';
describe( 'actions', () => {
test( 'should create an add notice action', () => {
const expectedAction = {
type: 'ADD_NOTICE',
notice: testNotice,
};
expect( actions.addNotice( testNotice ) ).toEqual( expectedAction );
} );
} );
describe( 'selectors', () => {
const notices = [ testNotice ];
const updatedState = { ...DEFAULT_STATE, ...{ notices } };
test( 'should return an emtpy initial state', () => {
expect( selectors.getNotices( DEFAULT_STATE ) ).toEqual( [] );
} );
test( 'should have an array length matching number of notices', () => {
expect( selectors.getNotices( updatedState ).length ).toEqual( 1 );
} );
test( 'should return the message content', () => {
expect( selectors.getNotices( updatedState )[ 0 ].message ).toEqual( 'Test notice' );
} );
} );
describe( 'reducers', () => {
test( 'should return an emtpy initial state', () => {
expect( reducers.notices( DEFAULT_STATE.notices, {} ) ).toEqual( [] );
} );
test( 'should return the added notice', () => {
expect(
reducers.notices( DEFAULT_STATE.notices, { type: 'ADD_NOTICE', notice: testNotice } )
).toEqual( [ testNotice ] );
} );
const initialNotices = [ { message: 'Initial notice' } ];
test( 'should return the initial notice and the added notice', () => {
expect(
reducers.notices( initialNotices, { type: 'ADD_NOTICE', notice: testNotice } )
).toEqual( [ ...initialNotices, testNotice ] );
} );
} );

View File

@ -22,13 +22,15 @@ $breakpoints: 320px, 400px, 600px, 782px, 960px, 1280px, 1440px;
@media (max-width: $breakpoint) {
@content;
}
} @else {
}
@else {
@if $size == $and-larger {
$approved-value: 2;
@media (min-width: $breakpoint + 1) {
@content;
}
} @else {
}
@else {
@each $breakpoint-end in $breakpoints {
$range: $breakpoint + '-' + $breakpoint-end;
@if $size == $range {
@ -48,7 +50,8 @@ $breakpoints: 320px, 400px, 600px, 782px, 960px, 1280px, 1440px;
}
@warn 'ERROR in breakpoint( #{ $size } ) : You can only use these sizes[ #{$sizes} ] using the following syntax [ <#{ nth( $breakpoints, 1 ) } >#{ nth( $breakpoints, 1 ) } #{ nth( $breakpoints, 1 ) }-#{ nth( $breakpoints, 2 ) } ]';
}
} @else {
}
@else {
$sizes: '';
@each $breakpoint in $breakpoints {
$sizes: $sizes + ' ' + $breakpoint;

View File

@ -41,7 +41,7 @@
}
}
// Gutenberg Button variables. These are temporary until Gutenberg's variables are exposed.
// Gutenberg mixins. These are temporary until Gutenberg's mixins are exposed.
@mixin button-style__focus-active() {
background-color: $white;
color: $dark-gray-900;
@ -52,6 +52,31 @@
outline-offset: -2px;
}
@mixin reduce-motion($property: '') {
@if $property == 'transition' {
@media (prefers-reduced-motion: reduce) {
transition-duration: 0s;
}
}
@else if $property == 'animation' {
@media (prefers-reduced-motion: reduce) {
animation-duration: 1ms;
}
}
@else {
@media (prefers-reduced-motion: reduce) {
transition-duration: 0s;
animation-duration: 1ms;
}
}
}
@mixin break-small() {
@media (min-width: #{ ($break-small) }) {
@content;
}
}
// Sets positions for children of grid elements
@mixin set-grid-item-position( $wrap-after, $number-of-items ) {
@for $i from 1 through $number-of-items {

View File

@ -22,14 +22,20 @@ $sidebar-width: 272px;
// @todo Remove this spacing variable
$spacing: 16px;
// Gutenberg Button variables. These are temporary until Gutenberg's variables are exposed.
$border-width: 1px;
// Gutenberg variables. These are temporary until Gutenberg's variables are exposed.
$default-font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell,
'Helvetica Neue', sans-serif;
$default-font-size: 13px;
$default-line-height: 1.4;
$break-small: 600px;
$border-width: 1px;
$blue-medium-focus: #007cba;
$light-gray-500: $core-grey-light-500;
$dark-gray-300: $core-grey-dark-300;
$dark-gray-700: $core-grey-dark-700;
$dark-gray-900: $core-grey-dark-900;
$alert-red: $error-red;
$radius-round-rectangle: 4px;
// WordPress defaults
$adminbar-height: 32px;

View File

@ -5,3 +5,4 @@ Import Gutenberg component SCSS so webpack's postcss process can handle theme-in
allows Woo themed components based on the config found in postcss.config.js
*/
@import 'gutenberg-components/button/style.scss';
@import 'gutenberg-components/snackbar/style.scss';

View File

@ -11,7 +11,7 @@ import { dispatch } from '@wordpress/data';
import { getResourceName } from '../utils';
const updateProductStock = operations => async ( product, newStock ) => {
const { addNotice } = dispatch( 'wc-admin' );
const { createNotice } = dispatch( 'core/notices' );
const oldStockQuantity = product.stock_quantity;
const resourceName = getResourceName( 'items-query-products-item', product.id );
@ -30,16 +30,16 @@ const updateProductStock = operations => async ( product, newStock ) => {
} );
const response = result[ 0 ][ resourceName ];
if ( response && response.data ) {
addNotice( {
status: 'success',
message: sprintf( __( '%s stock updated.', 'woocommerce-admin' ), product.name ),
} );
createNotice(
'success',
sprintf( __( '%s stock updated.', 'woocommerce-admin' ), product.name )
);
}
if ( response && response.error ) {
addNotice( {
status: 'error',
message: sprintf( __( '%s stock could not be updated.', 'woocommerce-admin' ), product.name ),
} );
createNotice(
'error',
sprintf( __( '%s stock could not be updated.', 'woocommerce-admin' ), product.name )
);
// Revert local changes if the operation failed in the server
operations.updateLocally( [ resourceName ], {
[ resourceName ]: { ...product, stock_quantity: oldStockQuantity },

View File

@ -3096,21 +3096,20 @@
"dev": true
},
"@wordpress/components": {
"version": "7.4.0",
"resolved": "https://registry.npmjs.org/@wordpress/components/-/components-7.4.0.tgz",
"integrity": "sha512-riVey0Z5835YdPZLWFSAs/4Hzo0nr7WA/393mRXwGuUtkqdk7ia++5emKfhyaCLYbinVBd6366xFFfiFxxcsCA==",
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/@wordpress/components/-/components-8.0.0.tgz",
"integrity": "sha512-8Lo9VHcNME+0s8RGQ0FaTYbSw7UdoIPZYK8+OB+jZgwM88kbVW6SkgCM45QdxSLtaxkCYdWX1IF/DRJzI22WyQ==",
"requires": {
"@babel/runtime": "^7.4.4",
"@wordpress/a11y": "^2.3.0",
"@wordpress/api-fetch": "^3.2.0",
"@wordpress/compose": "^3.3.0",
"@wordpress/a11y": "^2.4.0",
"@wordpress/compose": "^3.4.0",
"@wordpress/dom": "^2.3.0",
"@wordpress/element": "^2.4.0",
"@wordpress/hooks": "^2.3.0",
"@wordpress/i18n": "^3.4.0",
"@wordpress/is-shallow-equal": "^1.3.0",
"@wordpress/keycodes": "^2.3.0",
"@wordpress/rich-text": "^3.3.0",
"@wordpress/element": "^2.5.0",
"@wordpress/hooks": "^2.4.0",
"@wordpress/i18n": "^3.5.0",
"@wordpress/is-shallow-equal": "^1.4.0",
"@wordpress/keycodes": "^2.4.0",
"@wordpress/rich-text": "^3.4.0",
"@wordpress/url": "^2.6.0",
"classnames": "^2.2.5",
"clipboard": "^2.0.1",
@ -3123,34 +3122,53 @@
"re-resizable": "^4.7.1",
"react-click-outside": "^3.0.0",
"react-dates": "^17.1.1",
"react-spring": "^8.0.20",
"rememo": "^3.0.0",
"tinycolor2": "^1.4.1",
"uuid": "^3.3.2"
},
"dependencies": {
"@wordpress/api-fetch": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/@wordpress/api-fetch/-/api-fetch-3.3.0.tgz",
"integrity": "sha512-EddkSzj2csdDWhIZueBthue8er5akGDBAnOtUQ5d8cdOQd1RrQbAgHmpKZtNHD8qXbfS40Ay1K42w7Si0uOksw==",
"@wordpress/element": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/@wordpress/element/-/element-2.5.0.tgz",
"integrity": "sha512-gqtNcBkVtF//OHKJQAn4c7nwfDqsIy0UMq7ed9l6lpXOKS5ic8cw7BgNtdx4PiicV46ev9s/yHieFKWZNFpfIw==",
"requires": {
"@babel/runtime": "^7.4.4",
"@wordpress/escape-html": "^1.4.0",
"lodash": "^4.17.11",
"react": "^16.8.4",
"react-dom": "^16.8.4"
}
},
"@wordpress/hooks": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/@wordpress/hooks/-/hooks-2.4.0.tgz",
"integrity": "sha512-z3+8Yq4IQ3DOvUF4VsXOfntdyk1fJ8RZBYlZTW8WfmHV7VKTDbX3LfHXmC2VCjXhVkeWQVKozi2weoEYopfGKA==",
"requires": {
"@babel/runtime": "^7.4.4"
}
},
"@wordpress/i18n": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-3.5.0.tgz",
"integrity": "sha512-QAYFsHe/raG0MPUX32gCmuSM6UN/5gWCVSFWxOVSuXVuzPP5WnL78CgpuXtUresDZXTJm3FrFcB9IKvh93e1DQ==",
"requires": {
"@babel/runtime": "^7.4.4",
"gettext-parser": "^1.3.1",
"lodash": "^4.17.11",
"memize": "^1.0.5",
"sprintf-js": "^1.1.1",
"tannin": "^1.0.1"
}
},
"@wordpress/keycodes": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/@wordpress/keycodes/-/keycodes-2.4.0.tgz",
"integrity": "sha512-qGRBKcDroYzUffAGMzotyos2k+FA89IjYYFjWlUrDp4TVaadCHK1ESPcmX6Y87ExgcsJKXDkNVZfsCPvh7V7Kw==",
"requires": {
"@babel/runtime": "^7.4.4",
"@wordpress/i18n": "^3.5.0",
"@wordpress/url": "^2.6.0"
},
"dependencies": {
"@wordpress/i18n": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-3.5.0.tgz",
"integrity": "sha512-QAYFsHe/raG0MPUX32gCmuSM6UN/5gWCVSFWxOVSuXVuzPP5WnL78CgpuXtUresDZXTJm3FrFcB9IKvh93e1DQ==",
"requires": {
"@babel/runtime": "^7.4.4",
"gettext-parser": "^1.3.1",
"lodash": "^4.17.11",
"memize": "^1.0.5",
"sprintf-js": "^1.1.1",
"tannin": "^1.0.1"
}
}
"lodash": "^4.17.11"
}
}
}
@ -3366,6 +3384,50 @@
"lodash": "^4.17.11"
}
},
"@wordpress/notices": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@wordpress/notices/-/notices-1.5.0.tgz",
"integrity": "sha512-aKCVDYPVVzYBm2cW4pQzAsshLOZx0f9AZiBZOV+AD9D+eSAscF0SBcvmbduFFQ4KhIgj76IXz5cxXIrafFXaDQ==",
"requires": {
"@babel/runtime": "^7.4.4",
"@wordpress/a11y": "^2.4.0",
"@wordpress/data": "^4.6.0",
"lodash": "^4.17.11"
},
"dependencies": {
"@wordpress/data": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/@wordpress/data/-/data-4.6.0.tgz",
"integrity": "sha512-42BNtWtN0ppnzbnxz0tA72KNtuEKjrIRr4PbVCw3JVrHN2Nfhbo9SiCUutReKAXGQaT4LPnO7gAGmIsdC/mBUw==",
"requires": {
"@babel/runtime": "^7.4.4",
"@wordpress/compose": "^3.4.0",
"@wordpress/deprecated": "^2.4.0",
"@wordpress/element": "^2.5.0",
"@wordpress/is-shallow-equal": "^1.4.0",
"@wordpress/priority-queue": "^1.2.0",
"@wordpress/redux-routine": "^3.4.0",
"equivalent-key-map": "^0.2.2",
"is-promise": "^2.1.0",
"lodash": "^4.17.11",
"redux": "^4.0.0",
"turbo-combine-reducers": "^1.0.2"
}
},
"@wordpress/element": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/@wordpress/element/-/element-2.5.0.tgz",
"integrity": "sha512-gqtNcBkVtF//OHKJQAn4c7nwfDqsIy0UMq7ed9l6lpXOKS5ic8cw7BgNtdx4PiicV46ev9s/yHieFKWZNFpfIw==",
"requires": {
"@babel/runtime": "^7.4.4",
"@wordpress/escape-html": "^1.4.0",
"lodash": "^4.17.11",
"react": "^16.8.4",
"react-dom": "^16.8.4"
}
}
}
},
"@wordpress/npm-package-json-lint-config": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@wordpress/npm-package-json-lint-config/-/npm-package-json-lint-config-1.3.0.tgz",
@ -13604,6 +13666,66 @@
"requires": {
"@wordpress/components": "^7.3.1",
"@wordpress/element": "^2.3.0"
},
"dependencies": {
"@wordpress/api-fetch": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/@wordpress/api-fetch/-/api-fetch-3.3.0.tgz",
"integrity": "sha512-EddkSzj2csdDWhIZueBthue8er5akGDBAnOtUQ5d8cdOQd1RrQbAgHmpKZtNHD8qXbfS40Ay1K42w7Si0uOksw==",
"requires": {
"@babel/runtime": "^7.4.4",
"@wordpress/i18n": "^3.5.0",
"@wordpress/url": "^2.6.0"
},
"dependencies": {
"@wordpress/i18n": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-3.5.0.tgz",
"integrity": "sha512-QAYFsHe/raG0MPUX32gCmuSM6UN/5gWCVSFWxOVSuXVuzPP5WnL78CgpuXtUresDZXTJm3FrFcB9IKvh93e1DQ==",
"requires": {
"@babel/runtime": "^7.4.4",
"gettext-parser": "^1.3.1",
"lodash": "^4.17.11",
"memize": "^1.0.5",
"sprintf-js": "^1.1.1",
"tannin": "^1.0.1"
}
}
}
},
"@wordpress/components": {
"version": "7.4.0",
"resolved": "https://registry.npmjs.org/@wordpress/components/-/components-7.4.0.tgz",
"integrity": "sha512-riVey0Z5835YdPZLWFSAs/4Hzo0nr7WA/393mRXwGuUtkqdk7ia++5emKfhyaCLYbinVBd6366xFFfiFxxcsCA==",
"requires": {
"@babel/runtime": "^7.4.4",
"@wordpress/a11y": "^2.3.0",
"@wordpress/api-fetch": "^3.2.0",
"@wordpress/compose": "^3.3.0",
"@wordpress/dom": "^2.3.0",
"@wordpress/element": "^2.4.0",
"@wordpress/hooks": "^2.3.0",
"@wordpress/i18n": "^3.4.0",
"@wordpress/is-shallow-equal": "^1.3.0",
"@wordpress/keycodes": "^2.3.0",
"@wordpress/rich-text": "^3.3.0",
"@wordpress/url": "^2.6.0",
"classnames": "^2.2.5",
"clipboard": "^2.0.1",
"diff": "^3.5.0",
"dom-scroll-into-view": "^1.2.1",
"lodash": "^4.17.11",
"memize": "^1.0.5",
"moment": "^2.22.1",
"mousetrap": "^1.6.2",
"re-resizable": "^4.7.1",
"react-click-outside": "^3.0.0",
"react-dates": "^17.1.1",
"rememo": "^3.0.0",
"tinycolor2": "^1.4.1",
"uuid": "^3.3.2"
}
}
}
},
"nice-try": {
@ -18408,6 +18530,15 @@
"tiny-warning": "^1.0.0"
}
},
"react-spring": {
"version": "8.0.27",
"resolved": "https://registry.npmjs.org/react-spring/-/react-spring-8.0.27.tgz",
"integrity": "sha512-nDpWBe3ZVezukNRandTeLSPcwwTMjNVu1IDq9qA/AMiUqHuRN4BeSWvKr3eIxxg1vtiYiOLy4FqdfCP5IoP77g==",
"requires": {
"@babel/runtime": "^7.3.1",
"prop-types": "^15.5.8"
}
},
"react-test-renderer": {
"version": "16.8.6",
"resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.8.6.tgz",

View File

@ -67,7 +67,7 @@
"dependencies": {
"@fresh-data/framework": "0.6.1",
"@wordpress/api-fetch": "2.2.8",
"@wordpress/components": "7.4.0",
"@wordpress/components": "8.0.0",
"@wordpress/data": "4.5.0",
"@wordpress/date": "3.3.0",
"@wordpress/element": "2.4.0",
@ -75,6 +75,7 @@
"@wordpress/html-entities": "2.3.0",
"@wordpress/i18n": "3.4.0",
"@wordpress/keycodes": "2.3.0",
"@wordpress/notices": "1.5.0",
"@wordpress/scripts": "3.2.1",
"@wordpress/url": "2.6.0",
"@wordpress/viewport": "2.4.0",

View File

@ -43,6 +43,7 @@ exports[`SearchListControl should render a search box and list of hierarchical o
className="woocommerce-search-list__list components-menu-group"
>
<div
aria-hidden="true"
className="components-menu-group__label"
id="components-menu-group-label-8"
>
@ -50,9 +51,7 @@ exports[`SearchListControl should render a search box and list of hierarchical o
</div>
<div
aria-labelledby="components-menu-group-label-8"
aria-orientation="vertical"
onKeyDown={[Function]}
role="menu"
role="group"
>
<button
aria-checked={false}
@ -345,6 +344,7 @@ exports[`SearchListControl should render a search box and list of options 1`] =
className="woocommerce-search-list__list components-menu-group"
>
<div
aria-hidden="true"
className="components-menu-group__label"
id="components-menu-group-label-0"
>
@ -352,9 +352,7 @@ exports[`SearchListControl should render a search box and list of options 1`] =
</div>
<div
aria-labelledby="components-menu-group-label-0"
aria-orientation="vertical"
onKeyDown={[Function]}
role="menu"
role="group"
>
<button
aria-checked={false}
@ -632,6 +630,7 @@ exports[`SearchListControl should render a search box and list of options with a
className="woocommerce-search-list__list components-menu-group"
>
<div
aria-hidden="true"
className="components-menu-group__label"
id="components-menu-group-label-1"
>
@ -639,9 +638,7 @@ exports[`SearchListControl should render a search box and list of options with a
</div>
<div
aria-labelledby="components-menu-group-label-1"
aria-orientation="vertical"
onKeyDown={[Function]}
role="menu"
role="group"
>
<button
aria-checked={false}
@ -919,6 +916,7 @@ exports[`SearchListControl should render a search box and list of options, with
className="woocommerce-search-list__list components-menu-group"
>
<div
aria-hidden="true"
className="components-menu-group__label"
id="components-menu-group-label-7"
>
@ -926,9 +924,7 @@ exports[`SearchListControl should render a search box and list of options, with
</div>
<div
aria-labelledby="components-menu-group-label-7"
aria-orientation="vertical"
onKeyDown={[Function]}
role="menu"
role="group"
>
<div>
Apricots
@ -1002,6 +998,7 @@ exports[`SearchListControl should render a search box and list of options, with
className="woocommerce-search-list__list components-menu-group"
>
<div
aria-hidden="true"
className="components-menu-group__label"
id="components-menu-group-label-6"
>
@ -1009,9 +1006,7 @@ exports[`SearchListControl should render a search box and list of options, with
</div>
<div
aria-labelledby="components-menu-group-label-6"
aria-orientation="vertical"
onKeyDown={[Function]}
role="menu"
role="group"
>
<button
aria-checked={false}
@ -1433,6 +1428,7 @@ exports[`SearchListControl should render a search box with a search term, and on
className="woocommerce-search-list__list components-menu-group"
>
<div
aria-hidden="true"
className="components-menu-group__label"
id="components-menu-group-label-4"
>
@ -1440,9 +1436,7 @@ exports[`SearchListControl should render a search box with a search term, and on
</div>
<div
aria-labelledby="components-menu-group-label-4"
aria-orientation="vertical"
onKeyDown={[Function]}
role="menu"
role="group"
>
<button
aria-checked={false}
@ -1569,6 +1563,7 @@ exports[`SearchListControl should render a search box with a search term, and on
className="woocommerce-search-list__list components-menu-group"
>
<div
aria-hidden="true"
className="components-menu-group__label"
id="components-menu-group-label-5"
>
@ -1576,9 +1571,7 @@ exports[`SearchListControl should render a search box with a search term, and on
</div>
<div
aria-labelledby="components-menu-group-label-5"
aria-orientation="vertical"
onKeyDown={[Function]}
role="menu"
role="group"
>
<button
aria-checked={false}
@ -1757,6 +1750,7 @@ exports[`SearchListControl should render a search box, a list of options, and 1
className="woocommerce-search-list__list components-menu-group"
>
<div
aria-hidden="true"
className="components-menu-group__label"
id="components-menu-group-label-2"
>
@ -1764,9 +1758,7 @@ exports[`SearchListControl should render a search box, a list of options, and 1
</div>
<div
aria-labelledby="components-menu-group-label-2"
aria-orientation="vertical"
onKeyDown={[Function]}
role="menu"
role="group"
>
<button
aria-checked={false}
@ -2142,6 +2134,7 @@ exports[`SearchListControl should render a search box, a list of options, and 2
className="woocommerce-search-list__list components-menu-group"
>
<div
aria-hidden="true"
className="components-menu-group__label"
id="components-menu-group-label-3"
>
@ -2149,9 +2142,7 @@ exports[`SearchListControl should render a search box, a list of options, and 2
</div>
<div
aria-labelledby="components-menu-group-label-3"
aria-orientation="vertical"
onKeyDown={[Function]}
role="menu"
role="group"
>
<button
aria-checked={false}

View File

@ -1,9 +1,9 @@
=== WooCommerce Admin ===
Contributors: automattic
Tags: ecommerce, e-commerce, store, sales, reports, analytics, dashboard, activity, notices, insights, stats, woo commerce, woocommerce
Requires at least: 5.0.0
Requires at least: 5.2.0
Tested up to: 5.2.1
Requires PHP: 5.4.0
Requires PHP: 5.6.20
Stable tag: 1.0.0
License: GPLv3
License URI: https://github.com/woocommerce/woocommerce-admin/blob/master/license.txt
@ -34,7 +34,7 @@ WooCommerce Admin also allows store owners to customize a new dashboard screen w
= Minimum Requirements =
* WordPress 5.0
* WordPress 5.2
* WooCommerce 3.6.0 or greater
* PHP version 5.4 or greater. PHP 7.2 or greater is recommended
* MySQL version 5.0 or greater. MySQL 5.6 or greater is recommended

View File

@ -26,8 +26,6 @@ const WC_ADMIN_CONFIG = require( path.join( __dirname, 'config', WC_ADMIN_PHASE
const externals = {
'@wordpress/api-fetch': { this: [ 'wp', 'apiFetch' ] },
'@wordpress/blocks': { this: [ 'wp', 'blocks' ] },
'@wordpress/components': { this: [ 'wp', 'components' ] },
'@wordpress/compose': { this: [ 'wp', 'compose' ] },
'@wordpress/data': { this: [ 'wp', 'data' ] },
'@wordpress/editor': { this: [ 'wp', 'editor' ] },
'@wordpress/element': { this: [ 'wp', 'element' ] },

View File

@ -8,6 +8,8 @@
* Text Domain: woocommerce-admin
* Domain Path: /languages
* Version: 0.15.0
* Requires at least: 5.2.0
* Requires PHP: 5.6.20
*
* WC requires at least: 3.6.0
* WC tested up to: 3.6.4
@ -227,7 +229,7 @@ class WC_Admin_Feature_Plugin {
}
$wordpress_version = get_bloginfo( 'version' );
return version_compare( $wordpress_version, '4.9.9', '>' );
return version_compare( $wordpress_version, '5.2.0', '>=' );
}
/**
@ -252,10 +254,10 @@ class WC_Admin_Feature_Plugin {
*/
public function render_dependencies_notice() {
// The notice varies by WordPress version.
$wordpress_version = get_bloginfo( 'version' );
$wordpress_includes_gutenberg = version_compare( $wordpress_version, '4.9.9', '>' );
$wordpress_version = get_bloginfo( 'version' );
$has_valid_wp_version = version_compare( $wordpress_version, '5.2.0', '>=' );
if ( $wordpress_includes_gutenberg ) {
if ( $has_valid_wp_version ) {
$message = sprintf(
/* translators: URL of WooCommerce plugin */
__( 'The WooCommerce Admin feature plugin requires <a href="%s">WooCommerce</a> 3.6 or greater to be installed and active.', 'woocommerce-admin' ),
@ -264,7 +266,7 @@ class WC_Admin_Feature_Plugin {
} else {
$message = sprintf(
/* translators: 1: URL of WordPress.org, 2: URL of WooCommerce plugin */
__( 'The WooCommerce Admin feature plugin requires both <a href="%1$s">WordPress</a> 5.0 or greater and <a href="%2$s">WooCommerce</a> 3.6 or greater to be installed and active.', 'woocommerce-admin' ),
__( 'The WooCommerce Admin feature plugin requires both <a href="%1$s">WordPress</a> 5.2 or greater and <a href="%2$s">WooCommerce</a> 3.6 or greater to be installed and active.', 'woocommerce-admin' ),
'https://wordpress.org/',
'https://wordpress.org/plugins/woocommerce/'
);