Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Packages: Add common utilities to WP.com block editor package #32294

Merged
merged 5 commits into from
Apr 17, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 1 addition & 3 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -284,9 +284,7 @@ jobs:
- run:
name: Build the block editor in WordPress.com integration utils package
command: |
npx lerna run clean --scope='@automattic/wpcom-block-editor'
SOURCEMAP='inline-cheap-source-map' npx lerna run bundle --scope='@automattic/wpcom-block-editor' -- -- --output-path=$CIRCLE_ARTIFACTS/wpcom-block-editor
NODE_ENV=production npx lerna run bundle --scope='@automattic/wpcom-block-editor' -- -- --output-path=$CIRCLE_ARTIFACTS/wpcom-block-editor --output-filename=[name].min.js
npx lerna run build --scope='@automattic/wpcom-block-editor' -- -- --output-path=$CIRCLE_ARTIFACTS/wpcom-block-editor
- store-artifacts-and-test-results

test-client:
Expand Down
1 change: 1 addition & 0 deletions apps/wpcom-block-editor/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
module.exports = {
rules: {
'import/no-extraneous-dependencies': [ 'error', { packageDir: __dirname } ],
'react/react-in-jsx-scope': 0,
},
};
34 changes: 17 additions & 17 deletions apps/wpcom-block-editor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,36 @@ This package provides utilities for the WordPress.com block editor integration.

These utilities are intended to be built and then served from `widgets.wp.com`, so they can be loaded by a WordPress.com or a Jetpack connected site.

## File architecture
## Environments

There are two environments the block editor integration supports:

### `iframe-bridge-server.js`
- **WP Admin block editor**. The block editor loaded by the WP Admin interface on `https://<SITE_SLUG>/wp-admin/post-new.php`.
- **Calypso block editor**. This is the block editor loaded by Calypso on `https://wordpress.com/block-editor/post/<SITE_SLUG>`. It is actually the WP Admin block editor embed on an iframe. We also refer to this implementation as _Gutenframe_.

## File architecture

Server-side handlers of the different communication channels we establish with the client-side when Calypso loads the iframed block editor. See [`calypsoify-iframe.jsx`](https://github.com/Automattic/wp-calypso/blob/master/client/gutenberg/editor/calypsoify-iframe.jsx).
- `/common`: Logic than runs on both environments (WP Admin and Calypso).
- `/calypso`: Logic than runs only on the Calypso iframed block editor.

### `tinymce.js`
### Common utilities

Tiny MCE plugin that overrides the core media modal used on classic blocks with the Calypso media modal.
- `rich-text.js`: Extensions for the Rich Text toolbar with the Calypso buttons missing on Core (i.e. underline, justify).
- `switch-to-classic.js`: Append a button to the "More tools" menu for switching to the classic editor.

### `utils.js`
### Calypso utilities

Shared utilities to be used across the package.
- `iframe-bridge-server.js`: Server-side handlers of the different communication channels we establish with the client-side when Calypso loads the iframed block editor. See [`calypsoify-iframe.jsx`](https://github.com/Automattic/wp-calypso/blob/master/client/gutenberg/editor/calypsoify-iframe.jsx).
- `tinymce.js`: Tiny MCE plugin that overrides the core media modal used on classic blocks with the Calypso media modal.

## Build

### Manual

To manually build the package, execute the command below:

```
npx lerna run build --scope='@automattic/wpcom-block-editor'
```

This will generate the distributable files under the `dist` folder.

If you want to generate the build in a different folder, you can use the `bundle` script instead:
To manually build the package, execute the command below passing the directory where the distributable files will be generated:

```
npx lerna run bundle --scope='@automattic/wpcom-block-editor' -- -- --output-path=/path-to-folder
npx lerna run build --scope='@automattic/wpcom-block-editor' -- -- --output-path=/path-to-folder
```

_Wonky double `--` is needed for first skipping Lerna args and then NPM args to reach Webpack._
Expand Down
14 changes: 10 additions & 4 deletions apps/wpcom-block-editor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,25 @@
},
"homepage": "https://github.com/Automattic/wp-calypso",
"scripts": {
"clean": "npx rimraf dist",
"prebuild": "npm run clean",
"bundle": "webpack --config='../../packages/calypso-build/webpack.config.js' --env.WP='true' iframe-bridge-server='./src/iframe-bridge-server.js' tinymce='./src/tinymce.js'",
"build": "npm run bundle -- --output-path=./dist"
"bundle": "webpack --env.WP='true'",
"build:dev": "npm run bundle --",
"build:prod": "NODE_ENV=production npm run bundle --",
"build": "npm-run-all --parallel \"build:* -- {@}\" --"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this gets too funky at some point, you could also create a simple Webpack config for this package that inherits and extends the one from Calypso-build, similarly to client/notifications/.

},
"dependencies": {
"@wordpress/data": "4.4.0",
"@wordpress/blocks": "6.0.7",
"@wordpress/compose": "3.2.0",
"@wordpress/editor": "9.2.2",
"@wordpress/hooks": "2.0.5",
"@wordpress/rich-text": "3.2.2",
"@wordpress/url": "2.3.3",
"jquery": "^1.12",
"lodash": "4.17.11",
"react": "16.8.6",
"tinymce": "4.8.5"
},
"devDependencies": {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A peculiarity of monorepo: devDependencies in non-root packages don't really mean anything. It's preferable to never include them, any devDependencies must be in the root package.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't that cause a conflict with the import/no-extraneous-dependencies ESLint rule?

Screen Shot 2019-04-18 at 11 07 14

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be. :-) It's not ideal — all I know it's causing trouble. See e.g.: #32383

cc @blowery

"@automattic/calypso-build": "file:../../packages/calypso-build"
}
}
5 changes: 5 additions & 0 deletions apps/wpcom-block-editor/src/common/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/**
* Internal dependencies
*/
import './rich-text';
import './switch-to-classic';
77 changes: 77 additions & 0 deletions apps/wpcom-block-editor/src/common/rich-text.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/* global wpcomGutenberg */

/**
* External dependencies
*/
import { compose, ifCondition } from '@wordpress/compose';
import { withSelect, withDispatch } from '@wordpress/data';
import { RichTextToolbarButton } from '@wordpress/editor';
import { toggleFormat, registerFormatType } from '@wordpress/rich-text';
import { get } from 'lodash';

registerFormatType( 'wpcom/underline', {
title: wpcomGutenberg.richTextToolbar.underline,
tagName: 'span',
className: null,
attributes: { style: 'style' },
edit( { isActive, value, onChange } ) {
const onToggle = () =>
onChange(
toggleFormat( value, {
type: 'wpcom/underline',
attributes: {
style: 'text-decoration: underline;',
},
} )
);

return (
<RichTextToolbarButton
icon="editor-underline"
title={ wpcomGutenberg.richTextToolbar.underline }
onClick={ onToggle }
isActive={ isActive }
/>
);
},
} );

const RichTextJustifyButton = ( { blockId, isBlockJustified, updateBlockAttributes } ) => {
const onToggle = () =>
updateBlockAttributes( blockId, { align: isBlockJustified ? null : 'justify' } );

return (
<RichTextToolbarButton
icon="editor-justify"
title={ wpcomGutenberg.richTextToolbar.justify }
onClick={ onToggle }
isActive={ isBlockJustified }
/>
);
};

const ConnectedRichTextJustifyButton = compose(
withSelect( select => {
const selectedBlock = select( 'core/editor' ).getSelectedBlock();
if ( ! selectedBlock ) {
return {};
}
return {
blockId: selectedBlock.clientId,
blockName: selectedBlock.name,
isBlockJustified: 'justify' === get( selectedBlock, 'attributes.align' ),
};
} ),
withDispatch( dispatch => ( {
updateBlockAttributes: dispatch( 'core/editor' ).updateBlockAttributes,
} ) ),
ifCondition( props => 'core/paragraph' === props.blockName )
)( RichTextJustifyButton );

registerFormatType( 'wpcom/justify', {
title: wpcomGutenberg.richTextToolbar.justify,
tagName: 'p',
className: null,
attributes: { style: 'style' },
edit: ConnectedRichTextJustifyButton,
} );
46 changes: 46 additions & 0 deletions apps/wpcom-block-editor/src/common/switch-to-classic.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/* global _currentSiteId, wpcomGutenberg */

/**
* External dependencies
*/
import $ from 'jquery';

function addSwitchToClassicButton() {
if ( ! wpcomGutenberg.switchToClassic.isVisible ) {
return;
}

$( '#editor' ).on( 'click', '.edit-post-more-menu .components-button', () => {
// We need to wait a few ms until the menu content is rendered
setTimeout( () => {
$( '.edit-post-more-menu__content .components-menu-group:last-child > div[role=menu]' )
.append( `
<button type="button" aria-label="${ wpcomGutenberg.switchToClassic.label }" role="menuitem"
class="components-button components-menu-item__button components-menu-item__button-switch">
${ wpcomGutenberg.switchToClassic.label }
</button>;
` );
$( '.components-menu-item__button-switch' ).on( 'click', () => {
$.wpcom_proxy_request( {
method: 'POST',
path: `/sites/${ _currentSiteId }/gutenberg`,
apiNamespace: 'wpcom/v2',
query: {
platform: 'web',
editor: 'classic',
},
} ).done( () => {
if ( wpcomGutenberg.isCalypsoify ) {
top.window.location.replace( wpcomGutenberg.switchToClassic.url );
} else {
top.window.location.reload();
}
} );
} );
}, 0 );
} );
}

$( () => {
addSwitchToClassicButton();
} );
63 changes: 63 additions & 0 deletions apps/wpcom-block-editor/webpack.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/**
**** WARNING: No ES6 modules here. Not transpiled! ****
*/
/* eslint-disable import/no-nodejs-modules */

/**
* External dependencies
*/
const getBaseWebpackConfig = require( '@automattic/calypso-build/webpack.config.js' );
const path = require( 'path' );

/**
* Internal variables
*/
const isDevelopment = process.env.NODE_ENV !== 'production';

/**
* Return a webpack config object
*
* Arguments to this function replicate webpack's so this config can be used on the command line,
* with individual options overridden by command line args.
*
* @see {@link https://webpack.js.org/configuration/configuration-types/#exporting-a-function}
* @see {@link https://webpack.js.org/api/cli/}
*
* @param {object} env environment options
* @param {object} argv options map
* @param {object} argv.entry Entry point(s)
* @param {string} argv.'output-path' Output path
* @param {string} argv.'output-filename' Output filename pattern
* @param {string} argv.'output-library-target' Output library target
* @return {object} webpack config
*/
function getWebpackConfig(
env = {},
{
entry = {
common: path.join( __dirname, 'src', 'common' ),
'calypso-iframe-bridge-server': path.join(
__dirname,
'src',
'calypso',
'iframe-bridge-server.js'
),
'calypso-tinymce': path.join( __dirname, 'src', 'calypso', 'tinymce.js' ),
},
'output-path': outputPath = path.join( __dirname, 'dist' ),
'output-filename': outputFilename = isDevelopment ? '[name].js' : '[name].min.js',
}
) {
const webpackConfig = getBaseWebpackConfig( env, {
entry,
'output-filename': outputFilename,
'output-path': outputPath,
} );

return {
...webpackConfig,
devtool: isDevelopment ? 'inline-cheap-source-map' : false,
};
}

module.exports = getWebpackConfig;